Swift -Audio URL to Video URL Doesn't Play in Photos Library

I have an audio url (.m4a) that I create using the AVAudioRecorder. I want to share that audio on Instagram so I convert the audio to a video. The issue is after the conversion, when I save the video url to the Files app using the UIActivityViewController, I can replay the video, see the playback time (eg 7 seconds) and hear the audio with no problem. A black screen with a sound icon appears.

But when I save the same exact converted audio-video file to the Photos Library using the UIActivityViewController, inside the Photos Library the video shows the 7 seconds but nothing plays, the video is all gray, and the sound icon doesn't show.

Why is the video successfully saving/playing in the Files app but saving and not playing in the Photos Library?

I tried setting the exporter.outputFileType as both .mov and .mp4 and the issue is exactly the same.

let asset: AVURLAsset = AVURLAsset(url: audioURL)

let mixComposition = AVMutableComposition()
guard let compositionTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: CMPersistentTrackID()) else { return }

let track = asset.tracks(withMediaType: .audio)
guard let assetTrack = track.first else { return }

do {
            
    try compositionTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: assetTrack.timeRange.duration), of: assetTrack, at: .zero)
            
} catch {
    print(error.localizedDescription)
}

guard let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetPassthrough) else { return }

let dirPath = NSTemporaryDirectory().appending("\(UUID().uuidString).mov")
let outputFileURL = URL(fileURLWithPath: dirPath)

exporter.outputFileType = .mov // I also tried .mp4
exporter.outputURL = outputFileURL
exporter.shouldOptimizeForNetworkUse = true

exporter.exportAsynchronously {
    switch exporter.status {

    // ...
    guard let videoURL = exporter.outputURL else { return }


        // present UIActivityViewController to save videoURL and then save it to the Photos Library via 'Save Video`
    }
}

Replies

So it seems that although the code from my question did covert the audio file to a video file, there still wasn't a video track. I know this for a fact because after I got the exporter's videoURL from my question, I tried to add a watermark to it and in the watermark code it kept crashing on

let videoTrack = asset.tracks(withMediaType: AVMediaType.video)[0]

Basically the code from my question coverts audio to video but doesn't add a video track.

What I assumed happened is when the Files app reads the file, it knows that it's a .mov or .mp4 file and then it'll play the audio track even if the video track is missing.

Conversely, when the Photos app reads the file it also know's that it's a .mov or .mp4 file but if there isn't a video track, it won't play anything.

I had to combine these 2 answers to get the audio to play as a video in the Photos app.

1st- I added my app icon as 1 image to an array of images to make a video track using the code from How do I export UIImage array as a movie? answered by scootermg. The code from scootermg's answer is at this GitHub here by dldnh

2nd- I combined the app icon video that I just made with the audio url from my question using the code from Swift Merge audio and video files into one video answered by TungFam

In the mixCompostion from TungFam's answer I used the audio url's asset duration for the length of the video.

do {
    try mutableCompositionVideoTrack[0].insertTimeRange(CMTimeRangeMake(start: .zero,
                                                                            duration: aAudioAssetTrack.timeRange.duration),
                                                            of: aVideoAssetTrack,
                                                            at: .zero)

    try mutableCompositionAudioTrack[0].insertTimeRange(CMTimeRangeMake(start: .zero,
                                                                            duration: aAudioAssetTrack.timeRange.duration),
                                                            of: aAudioAssetTrack,
                                                            at: .zero)

    if let aAudioOfVideoAssetTrack = aAudioOfVideoAssetTrack {
        try mutableCompositionAudioOfVideoTrack[0].insertTimeRange(CMTimeRangeMake(start: .zero,
                                                                                       duration: aAudioAssetTrack.timeRange.duration),
                                                                       of: aAudioOfVideoAssetTrack,
                                                                       at: .zero)
    }
} catch {
    print(error.localizedDescription)
}