[SOLVED] audio file plays fine but can't add in AVMutableVideoComposition

Issue

i’m trying to add audio file selected with MPMediaPickerController to video with AVMutableVideoComposition, but it gives error and doesn’t work.

picking audio like this :

    func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
    
    if let musicUrl: NSURL = mediaItemCollection.items.first?.assetURL as NSURL? {
        musicURL = musicUrl as URL
        print("Music URL in did select \(musicUrl) !!!")
    }
    
}

and mixing audio and video like this :

func testMerge(url: URL) {
    
    let firstVideo = AVAsset(url: url)
    var audioAsset = AVAsset(url: musicURL!)
    
    print("Music URL\(musicURL)")

    let firstVideoTrack = firstVideo.tracks(withMediaType: AVMediaType.video).first
    
    let mixComposition = AVMutableComposition()
    
    let audioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
    let firstTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) 
    
    do {
        try firstTrack?.insertTimeRange(CMTimeRange(start: .zero, duration: firstVideo.duration), of: firstVideoTrack!, at: .zero)
        
        if musicURL.isFileURL {
            try audioTrack?.insertTimeRange(CMTimeRange(start: .zero, duration: firstVideo.duration), of: audioAsset.tracks(withMediaType: AVMediaType.audio).first, at: .zero)
        }
        
    } catch {
        debugPrint("Can't get track from the Video URL!")
    }
    
    let mainInstruction = AVMutableVideoCompositionInstruction()
    mainInstruction.timeRange = CMTimeRangeMake(start: .zero, duration: firstVideo.duration)
    
    let firstLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: firstTrack!)
        
    let scale = CGAffineTransform(scaleX: 1, y: 1)
    firstLayerInstruction.setTransform(scale, at: .zero)
    
    mainInstruction.layerInstructions = [firstLayerInstruction]
    
let mainCompositionInstruction = AVMutableVideoComposition()
mainCompositionInstruction.instructions = [mainInstruction]
mainCompositionInstruction.frameDuration = CMTimeMake(value: 1, timescale: 30)
mainCompositionInstruction.renderSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)

guard let documentDirectory = FileManager.default.urls (
    for: .documentDirectory,
    in: .userDomainMask).first else { print("ERROR"); return }

let url = documentDirectory.appendingPathComponent("mergeVideo-\(arc4random() % 10000).mp4")

guard let assetExport = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) else {print("ERROR"); return }

assetExport.videoComposition = mainCompositionInstruction
assetExport.outputFileType = .mp4
assetExport.shouldOptimizeForNetworkUse = true
assetExport.outputURL = url
assetExport.exportAsynchronously {
    switch assetExport.status {
            case  AVAssetExportSessionStatus.failed:
                print("failed \(String(describing: assetExport.error))")
            case AVAssetExportSessionStatus.cancelled:
                print("cancelled \(String(describing: assetExport.error))")
            case AVAssetExportSessionStatus.completed:
                print("Completed")
                print(url)
                
            default:
                print("unknown")
            }
    
    DispatchQueue.main.async {
        self.exportDidFinish(assetExport)
    }
}
    
}

if i play that audio with AVAudioPlayer then it is playing but if i add in video it gives error {Error Domain=NSOSStatusErrorDomain Code=-12109 "(null)"}, NSLocalizedFailureReason=The operation is not supported for this media., NSLocalizedDescription=Operation Stopped}

any help or tip would be really appreciated, thank you.

Solution

If i add audio file to project directory and then access it with bundle url then it works fine it just doesn’t work with audio picked through MPMediaPicker.

This is how i was able to make it work :

stored it as AVPlayerItem in variable like this

var playerItem: AVPlayerItem!        

func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
    
    if let musicUrl: NSURL = mediaItemCollection.items.first?.assetURL as NSURL? {
        musicURL = musicUrl as URL
        print("Music URL in did select \(musicUrl) !!!")
    }
    
    playerItem = AVPlayerItem(url: musicURL)
}

then in mergeVideo function accessed it’s track like this

        do {
        try firstTrack?.insertTimeRange(CMTimeRange(start: .zero, duration: durationTrack.duration), of: firstVideoTrack!, at: .zero)
        try secondTrack?.insertTimeRange(CMTimeRange(start: .zero, duration: durationTrack.duration), of: secondVideoTrack!, at: .zero)
        
        if playerItem != nil {
            let sourceAudio = playerItem.asset.tracks(withMediaType: .audio).first
            
            let audioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
            try audioTrack?.insertTimeRange(CMTimeRange(start: .zero, duration: durationTrack.duration), of: sourceAudio!, at: .zero)
        }
        
    } catch {
        debugPrint("Can't get track from the Video URL!")
    }

Answered By – ramesh sanghar

Answer Checked By – Gilberto Lyons (BugsFixing Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *