Monday, 29 July 2019

AVAssetExportSession not working on devices, but working on simulator (AVFoundationErrorDomain Code = -11800, Unknown Error code -12780)

I've seen several questions that had the same error, but none of the solutions found there helped me. Figured I'd try my luck.

I'm trying to export a video as is, mainly to learn about AVFoundation and AVAssetExportSession. My export works perfectly fine on the simulator, but does not work on any iOS device I've tried (namely an iPhone X and an iPhone XR running iOS 12 each). I Mainly followed a Ray Wenderleich tutorial found on this link to perform the Video Exporting: https://www.raywenderlich.com/2734-avfoundation-tutorial-adding-overlays-and-animations-to-videos

Would appreciate any help on the topic. My code is as follows:

Retrieving the URL of a video I've added to the App Bundle called Demo.mp4:

@objc func export() {
    let urlString = Bundle.main.path(forResource: "Demo", ofType: ".mp4")!
    let url = URL(fileURLWithPath: urlString)
    ExportManager.shared.exportWithAVFoundation(url:url) { (outputUrl, errorString) in
        if let outputUrl = outputUrl {
            self.playVideo(url: outputUrl)
        } else if let errorString = errorString {
            print("ERROR: \(errorString)")
        }
    }
}

My Exporting function in ExportManager is as follows (Sorry quite long)

func exportWithAVFoundation(url: URL, completion: @escaping (_ outputUrl: URL?, _ errorString: String?) -> ()) {
    let asset = AVAsset(url: url)
    print("URL IS \(url)")
    guard let avAssetTrack = asset.tracks(withMediaType: .video).first else {
        completion(nil, "Couldn't Create Asset Track")
        return
    }

    let mutableComposition = AVMutableComposition()
    guard let videoTrack = mutableComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else { return }
    try? videoTrack.insertTimeRange(CMTimeRange(start: .zero, duration: asset.duration), of: avAssetTrack, at: .zero)
    videoTrack.preferredTransform = avAssetTrack.preferredTransform

    if let audioAssetTrack = asset.tracks(withMediaType: .audio).first {
        let audioTrack = mutableComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
        try? audioTrack?.insertTimeRange(CMTimeRange(start: .zero, duration: asset.duration), of: audioAssetTrack, at: .zero)
    }

    let mainInstruction = AVMutableVideoCompositionInstruction()
    mainInstruction.timeRange = CMTimeRange(start: .zero, duration: asset.duration)

    let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)

    // Fix video orientation
    var videoAssetOrientation = UIImage.Orientation.up
    var isVideoAssetPortrait = false
    let videoTransform = avAssetTrack.preferredTransform

    switch (videoTransform.a, videoTransform.b, videoTransform.c, videoTransform.c) {
    case (0, 1.0, -1.0, 0):
        videoAssetOrientation = .right
        isVideoAssetPortrait = true
    case(0, -1.0, 1.0, 0):
        videoAssetOrientation = .left
        isVideoAssetPortrait = true
    case(1.0, 0, 0, 1.0):
        videoAssetOrientation = .up
    case(-1.0, 0, 0, -1.0):
        videoAssetOrientation = .down
    default:
        break
    }

    var naturalSize = avAssetTrack.naturalSize
    switch (videoAssetOrientation, isVideoAssetPortrait) {
    case (.right, true):
        naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
    case (.left, true):
        naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
    case (.leftMirrored, true):
        naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
    case (.rightMirrored, true):
        naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
    default:
        break
    }

    videoLayerInstruction.setTransform(avAssetTrack.preferredTransform, at: .zero)
    videoLayerInstruction.setOpacity(0, at: asset.duration)

    mainInstruction.layerInstructions = [videoLayerInstruction]

    let mainCompositionInstruction = AVMutableVideoComposition()

    mainCompositionInstruction.renderSize = naturalSize
    mainCompositionInstruction.instructions = [mainInstruction]
    mainCompositionInstruction.frameDuration = CMTimeMake(value: 1, timescale: 30);

    let documentsDirectoryURL = createPath()

    guard let exporter = AVAssetExportSession(asset: mutableComposition, presetName: AVAssetExportPresetHighestQuality) else {
        print("Couldnt create AVAssetExportSession")

        completion(nil, "Couldn't Create AVAssetExportSession")
        return
    }

    exporter.outputURL = documentsDirectoryURL
    exporter.outputFileType = .mov
    exporter.shouldOptimizeForNetworkUse = true
    exporter.videoComposition = mainCompositionInstruction

    exporter.exportAsynchronously {
        if let error = exporter.error {
            print(error)
            completion(nil, error.localizedDescription)
            return
        }

        completion(exporter.outputURL, nil)
        print("Finished Exporting")
    }
}

Some things I've tried to do have been to add an AudioTrack to the composition (which I didn't include previously). Hasn't helped it work on an actual device, but at least my exported video has audio now.

I also tried to change the Preset Quality to Passthrough as opposed to Highest Quality, as I read from other threads that this could help, but to no avail.

Note: createPath() just creates a valid path in the directory to save the exported video in. If a file exists at that path prior to exporting, it gets deleted.



from AVAssetExportSession not working on devices, but working on simulator (AVFoundationErrorDomain Code = -11800, Unknown Error code -12780)

No comments:

Post a Comment