Attempting to merge multiple videos with AVMutableComposition, the tracks are gathering correctly in the avassettrack. However, they simply overlap and only the second video is displayed. Also the length of the output is whatever the second video length is.
here is the merge func:
public func mergeCapturedVideos(completion: @escaping (_ completedMovieURL: URL) -> Void) {
let mixComposition = AVMutableComposition()
let movieAssets: [AVAsset] = self.capturedMovieURLs.map({ AVAsset(url: $0) })
var insertTime: CMTime = CMTime.zero
if movieAssets.count == self.capturedMovieURLs.count {
for movieAsset in movieAssets {
do {
if let compositionVideoTrack: AVMutableCompositionTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) {
let tracks: [AVAssetTrack] = movieAsset.tracks(withMediaType: .video)
let assetTrack: AVAssetTrack = tracks[0] as AVAssetTrack
try compositionVideoTrack.insertTimeRange(CMTimeRange(start: .zero, duration: movieAsset.duration), of: assetTrack, at: insertTime)
print("1 \(insertTime)")
insertTime = CMTimeAdd(insertTime, movieAsset.duration)
print("2 \(insertTime)")
}
} catch let error as NSError {
print("Error merging movies: \(error)")
}
print("MIX: \(mixComposition)")
}
let completeMovieURL: URL = self.capturedMovieURLs[0]
if let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHEVC1920x1080WithAlpha) {
exporter.outputURL = completeMovieURL
exporter.outputFileType = .mp4
exporter.exportAsynchronously {
if let url = exporter.outputURL {
completion(url)
} else if let error = exporter.error {
print("Merge exporter error: \(error)")
}
}
}
}
}
This is what the "MIX: " print statement is outputting:
MIX: <AVMutableComposition: 0x2815670a0 tracks = (
"<AVMutableCompositionTrack: 0x281567ea0 trackID = 1, mediaType = vide, editCount = 1>",
"<AVMutableCompositionTrack: 0x281567da0 trackID = 2, mediaType = vide, editCount = 2>"
)>
As you can see, the tracks are being added properly, so it must be a issue with the insertTime var. But that is also printing the expected output:
1 CMTime(value: 0, timescale: 1, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
2 CMTime(value: 2121, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
1 CMTime(value: 2121, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
2 CMTime(value: 3560, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
As shown, the insertTime var is properly adding the duration of each track on every loop.
This is leaving me very puzzled, why is the output not a singular movie with each video?
Are you sure it is the second movie displaying only and not the first ?
I believe what is happening is this:
let completeMovieURL: URL = self.capturedMovieURLs[0]if let url = exporter.outputURLthis will always be valid as you provided it the url of a valid videoAVAssetExportSession statusand only oncompletedyou should call your completion handlerThat explains the issue of why you see 1 video, but why is the merge failing, I think this is due to 2 reasons and it is not because of your time ranges:
AVMutableCompositionTrackfor each video when actually you should reuse it and keep adding new videos to it. Initialize this outside the loopAVAssetExportPresetused isAVAssetExportPresetHEVC1920x1080WithAlpha- I recommend not using this unless you want to configure some instructions to support this format, better to go withAVAssetExportPresetHighestQualityif you want to keep things simpleHere are some updates I made to your existing code with my comments above and hopefully you should get the output you want