Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AVAssetExportSession not starting export / stuck waiting

I am attempting to join 2 video clips using AVFoundation. For testing purposes, here I am trying to load a single video clip, add its video and audio tracks to a composition, then export that using AVAssetExportSession.

When I run the code below, "Exporting" is output, but the export callback is never executed. Furthermore, if I periodically check the progress of the export (print(exporter.progress)), I find that the progress is always at 0.0, even after several minutes. If I print the status, I find that it is "waiting" for something.

// URL to video file
let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("video.mov")

// Create composition and tracks
let comp = AVMutableComposition()
let videoTrack = comp.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)
let audioTrack = comp.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)

// Create asset from file
let asset = AVAsset(url: fileURL)

// Insert video
try! videoTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, asset.duration), of: asset.tracks(withMediaType: AVMediaType.video)[0] as AVAssetTrack, at: kCMTimeZero)

// Insert audio
try! audioTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, asset.duration), of: asset.tracks(withMediaType: AVMediaType.audio)[0] as AVAssetTrack, at: kCMTimeZero)

// Delete existing movie file if exists
let finalURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("FINAL.mov")
try? FileManager.default.removeItem(at: finalURL)

// Create the exporter
exporter = AVAssetExportSession(asset: comp, presetName: AVAssetExportPresetLowQuality)
exporter.outputURL = finalURL
exporter.outputFileType = AVFileType.mov

print("Exporting")
exporter.exportAsynchronously(completionHandler: {
    // This statement is never reached
    print(self.exporter.error)
})

No errors are ever thrown. It just seems like the export never starts. I'm not sure what it is waiting for.

Edit: Something spooky is going on. If I restart my phone and then run the code, it works. But it works exactly 1 time. If I run it a second time, nothing happens as usual. But when I restart my phone and run it again, it works again.

Edit 2: I tried it on someone else's phone and it works reliably every time. What in the world is wrong with my phone?

like image 309
Jacob Brunson Avatar asked Jan 11 '18 05:01

Jacob Brunson


1 Answers

This is the code that basically do the same thing, add an audio track to a video. It really seems yours and it is currently working on an application on appstore.

func merge() -> AVComposition? {
        guard let videoAsset = videoAsset, let audioAsset = audioAsset else {
            return nil
        }
        let avMutableComposition = AVMutableComposition()
        let audiotimeRange = CMTimeRange(start: kCMTimeZero, duration: audioAsset.duration)
        let compositionVideoTrack = avMutableComposition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)
        do {
            try add(asset: videoAsset, withRemaining: audiotimeRange.duration, forComposition: compositionVideoTrack!, cursor: kCMTimeZero)
        } catch let error {
            print(error)
            return nil
        }

        let compositionAudioTrack = avMutableComposition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)
        do {
            try compositionAudioTrack?.insertTimeRange(audiotimeRange, of: audioAsset.tracks(withMediaType: AVMediaType.audio)[0], at: kCMTimeZero)
        } catch let error {
            print(error)
            return nil
        }


        composition = (avMutableComposition.copy() as! AVComposition)
        return composition
    }

I can't see nothing wrong with our code, except for the fact that I'm not really sure that the asset has the correct duration, since getting it, is an async task unless you ask explicitly.
So, when you create the AVAsset pass AVURLAssetPreferPreciseDurationAndTimingKey as option to true.

AVURLAsset(url: <#T##URL#>, options: ["AVURLAssetPreferPreciseDurationAndTimingKey" : true])
like image 148
Andrea Avatar answered Nov 09 '22 22:11

Andrea