0

I'm using the following code to concatenate multiple AVURLAssets:

AVMutableComposition * movie = [AVMutableComposition composition];

CMTime offset = kCMTimeZero;

for (AVURLAsset * asset in assets) {
    AVMutableCompositionTrack *compositionVideoTrack = [movie addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    AVMutableCompositionTrack *compositionAudioTrack = [movie addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

    AVAssetTrack *assetVideoTrack = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
    AVAssetTrack *assetAudioTrack = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject;

    CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
    NSError * error = nil;

    if (![compositionVideoTrack insertTimeRange: timeRange ofTrack: assetVideoTrack atTime: offset error: &error]) {
        NSLog(@"Error adding video track - %@", error);
    }
    if (![compositionAudioTrack insertTimeRange: timeRange ofTrack: assetAudioTrack atTime: offset error: &error]) {
        NSLog(@"Error adding audio track - %@", error);
    }

    offset = CMTimeAdd(offset, asset.duration);
}

The resultant composition plays through to the combined duration of all the original assets, and the audio plays correctly, but only the video from the first asset plays, then pauses on its final frame.

Any thoughts on what I've done wrong?

The ordering of the original assets is irrelevant - the first video and all the audio plays.

Ashley Mills
  • 50,474
  • 16
  • 129
  • 160

2 Answers2

1

You need to create an AVVideoComposition with instances of AVVideoCompositionInstructions. Check out this sample code.

The code you would be interested in would be something of this nature:

AVMutableVideoCompositionLayerInstruction *videoCompositionLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableVideoTrack];
[self.layerInstructions addObject:videoCompositionLayerInstruction];
        AVMutableVideoCompositionInstruction *passThroughInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
        passThroughInstruction.timeRange = trackTimeRange;
        passThroughInstruction.layerInstructions = @[videoCompositionLayerInstruction];
[self.compositionInstructions addObject:passThroughInstruction];
Jonathan
  • 291
  • 2
  • 6
  • I'm not sure where you got that idea from, but it's the wrong answer (see my own solution). Thanks for trying though. – Ashley Mills Jul 31 '15 at 09:57
  • Your solution does work. I was thinking about a more scalable solution in which you want to use multiple composition tracks instead of inserting into the same composition track. Using the AVMutableVideoCompositionLayerInstructions, AVMutableVideoCompositionInstruction, and AVVideoComposition allows you to perform more editing operations such as transitions, overlays, etc. Glad you found something that works for you though. – Jonathan Jul 31 '15 at 15:25
  • Johnathan, How exactly are you performing a loop here? I used the code vut the video is not looping, playing only once.. – Roi Mulia Nov 29 '18 at 08:51
0

OK, it was my mistake. I'd put the addMutableTrackWithMediaType: inside the for loop. Silly me. Fixed as below and it works like a charm!

I'll leave this here just in case anyone else has the same problem.

AVMutableComposition * movie = [AVMutableComposition composition];
AVMutableCompositionTrack *compositionVideoTrack = [movie addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *compositionAudioTrack = [movie addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

CMTime offset = kCMTimeZero;

for (AVURLAsset * asset in assets) {

    AVAssetTrack *assetVideoTrack = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
    AVAssetTrack *assetAudioTrack = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject;

    CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
    NSError * error = nil;

    if (![compositionVideoTrack insertTimeRange: timeRange ofTrack: assetVideoTrack atTime: offset error: &error]) {
        NSLog(@"Error adding video track - %@", error);
    }
    if (![compositionAudioTrack insertTimeRange: timeRange ofTrack: assetAudioTrack atTime: offset error: &error]) {
        NSLog(@"Error adding audio track - %@", error);
    }

    offset = CMTimeAdd(offset, asset.duration);
}
Ashley Mills
  • 50,474
  • 16
  • 129
  • 160