Swiss Developer Immigrated to Germany

Playing Multiple Overlapping Audio Tracks in Background

I wanted to play music in the background on my iPhone. To make the transition between two songs smooth and nice they should overlap and fade.  To achieve this I used the AVPlayer class: created an instance of it and started playing it. After some time it starts fading out, creates a new AVPlayer instance with a new song and starts playing again. This works perfectly well as long as my app runs in foreground. But when it is in background. It just fades out the old AVPlayer instance but does not start playing the new AVPlayer instance.

So I looked around what I was doing wrong. Along that I figured out that I’m doing this completely wrong. Using multiple AVPlayer instances was a bad idea. Instead a AVMutableComposition should be used. That’s designed for exactly that purpose. Maybe I’m not the only one struggling with this, so I put up this blogpost about it.

The idea is that you have one AVPlayer that plays one AVComposition which contains multiple tracks. I will demonstrate this here on a sample app. That app takes the first 20 songs from your iPod library. And plays every first five seconds of these songs after each other with a nice fade out when the next one begins.

Get the first 20 songs from your iPod library:

MPMediaQuery *query = [MPMediaQuery songsQuery];
NSArray *searchResult = [[query items] subarrayWithRange:NSMakeRange(0, 20)];

Then for every MPMediaItem in the searchResult we add a new AVMutableCompositionTrack to a AVMutableComposition.

AVMutableCompositionTrack *track;
track  = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

Into this track we put the track we get from the iPod Library. Important here is that we create an AVURLAsset with the AVURLAssetPreferPreciseDurationAndTimingKey option. Otherwise it will fail when adding the track from the iPod library to the track in the AVComposition.

Now we have the the full track in our AVComposition. So we cut out the part we don’t want in there.

NSURL *url = [item valueForProperty:MPMediaItemPropertyAssetURL];
NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
                                                    forKey:AVURLAssetPreferPreciseDurationAndTimingKey];
AVAsset *asset = [AVURLAsset URLAssetWithURL:url options:options];
AVAssetTrack *iPodTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
CMTimeRange iPodTrackTimeRange = iPodTrack.timeRange;
NSError *error = nil;
BOOL success = [track insertTimeRange:iPodTrackTimeRange
                              ofTrack:iPodTrack
                               atTime:CMTimeMakeWithSeconds(insertTime, 1)
                                error:&error];
if (!success) {
    NSLog(@"Error inserting new track: %@",error);
    continue;
}

And for every track added, we add a fade out property at the end.

AVMutableAudioMixInputParameters *params;
params =[AVMutableAudioMixInputParameters audioMixInputParameters];
[params setVolumeRampFromStartVolume:1.0
                         toEndVolume:0.0
                           timeRange:CMTimeRangeMake(CMTimeMakeWithSeconds(insertTime + playTime, 1),
                                                     CMTimeMakeWithSeconds(fadeTime, 1))];

[params setTrackID:[track trackID]];
[allAudioParams addObject:params];

Now when this is done for every track, we create a AVPlayerItem with the AVComposition and play it with an AVPlayer. We also apply the AudioMix to the player item to make the fadeout work.

AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:composition];
self.player = [[AVPlayer alloc] initWithPlayerItem:playerItem];

//add the audio mix for fade outs
AVMutableAudioMix * fadeOutMix = [AVMutableAudioMix audioMix];
[fadeOutMix setInputParameters:allAudioParams];
[playerItem setAudioMix:fadeOutMix];

[self.player play];
[sender setTitle:@"Pause"
        forState:UIControlStateNormal];

If this was too fast and not clear. It might be easier to see it in a working Xcode project. So I set up an small Xcode project which you can simply open and run to see what happens.

For many other AVFoundation related problems and questions, take a look at http://www.subfurther.com/blog/ that blog is a great resource of information!

Comments on: "Playing Multiple Overlapping Audio Tracks in Background" (1)

  1. Dude, this is awesome. Almost exactly the example I was looking for! Thanks!!