views:

316

answers:

1

Hello all,

I've an app which uses the MPAudioPlayerController to access the iPod music library, and an AVAudioPlayer to overlay audio on top of the music. I've used this documentation as a guide. Specifically:

Finally, you can enhance a category to automatically lower the volume of other audio when your audio is playing. This could be used, for example, in an exercise application. Say the user is exercising along to their iPod when your application wants to overlay a verbal message—for instance, “You’ve been rowing for 10 minutes.” To ensure that the message from your application is intelligible, apply the kAudioSessionProperty_OtherMixableAudioShouldDuck property to the audio session. When ducking takes place, all other audio on the device—apart from phone audio—lowers in volume.

But I'm not seeing this behavior. In fact, what I see (or hear, rather) is that if I setup the AudioSession with kAudioSessionProperty_OtherMixableAudioShouldDuck set to true, the MPAudioPlayerController initial volume gets reduced, and if I then call pause (and then play again) on the MPAudioPlayerController the volume level gets increased to "normal" levels. Playing the AVAudioPlayer does not have any affect on the audio level...

So I've set up a simple test case to reproduce this.

In a ViewController header:

#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>

@interface MusicPlayerVolumeTestViewController : UIViewController <AVAudioPlayerDelegate>
{
    UIButton *musicButton;
    UIButton *soundButton;
    AVAudioPlayer *audioPlayer;
    MPMusicPlayerController *musicPlayerController;
}
@property (nonatomic, retain) IBOutlet UIButton *musicButton;
@property (nonatomic, retain) IBOutlet UIButton *soundButton;
@property (nonatomic, retain) MPMusicPlayerController *musicPlayerController;

- (IBAction)musicAction;
- (IBAction)soundAction;

@end

and in the implementation:

- (void)viewDidLoad
{
    [super viewDidLoad];

    //Setup our Audio Session
    OSStatus status = AudioSessionInitialize(NULL, NULL, NULL, NULL);    
    //We want our audio to play if the screen is locked or the mute switch is on
    UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
    status = AudioSessionSetProperty (kAudioSessionProperty_AudioCategory, sizeof (sessionCategory), &sessionCategory);
    //We want our audio to mix with other app's audio
    UInt32 shouldMix = true;
    status = AudioSessionSetProperty (kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof (shouldMix), &shouldMix);
    //Enable "ducking" of the iPod volume level while our sounds are playing
    UInt32 shouldDuck = true;
    AudioSessionSetProperty(kAudioSessionProperty_OtherMixableAudioShouldDuck, sizeof(shouldDuck), &shouldDuck);
    //Activate our audio session
    AudioSessionSetActive(YES);

    //Setup the Music Player to access the iPod music library
    self.musicPlayerController = [MPMusicPlayerController applicationMusicPlayer];
    [self.musicPlayerController setShuffleMode: MPMusicShuffleModeSongs];
    [self.musicPlayerController setRepeatMode: MPMusicRepeatModeNone];
    [self.musicPlayerController setQueueWithQuery:[MPMediaQuery songsQuery]];

    //Setup a AVAudioPlayer sound to overlay against the Music Player audio
    NSURL *soundURL = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:@"overlay" ofType:@"mp3"]];
    NSError *error = nil;
    audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error: &error];
    if (!audioPlayer)
    {
        NSLog(@"Could not create audio effect player: %@", [error localizedDescription]);
    }
    [audioPlayer prepareToPlay];
}

- (IBAction)musicAction
{
    if (self.musicPlayerController.playbackState == MPMusicPlaybackStatePlaying)
    {
        [self.musicPlayerController pause];
    }
    else if (self.musicPlayerController.playbackState == MPMusicPlaybackStateStopped
          || self.musicPlayerController.playbackState == MPMusicPlaybackStatePaused)
    {
        [self.musicPlayerController play];
    }
}

- (IBAction)soundAction
{
    if (audioPlayer.playing)
    {
        [audioPlayer pause];
    }
    else
    {
        [audioPlayer play];
    }
}

I've wired up a couple UIButtons. One for the musicAction (used for playing/pausing the MPMusicPlayerController) and one for the soundAction (used for playing/pausing the AVAudioPlayer).

As mentioned, If I tap the musicAction button, the music plays, but at a reduced volume level, and if I tap the soundAction button, the overlay plays, but has no affect on the volume of the MPMusicPlayerController. And, more bug-like, is that when I pause and then play the MPMusicPlayerController the volume of the music increases to the level it would have been if I did not setup the AudioSession.

I'm interested to know if anyone else has had this experience, and if so if you've found a work around (or can tell me that I'm doing something wrong). Otherwise, I guess I'm off to Radar.

Many thanks,

Levi

A: 

The documentation isn't clear about what the expected behavior of ducking is supposed to be, but what I've found is that if you set up your audio session for ducking, when you activate the audio session, it ducks, and when you deactivate it, it unducks. So you have to keep activating and deactivating the audio session to duck the music just for your sound cue.

One bug I found is that if you deactivate the audio session while inside the audioPlayerDidFinishPlaying:successfully: method of your AVAudioPlayerDelegate, the ducking isn't removed, and the volume is kept at the same level. I've already filed a Radar on this, but it doesn't hurt if other file similar ones. In fact it'll probably put some pressure on them to fix it.

What you're doing is starting the audio session and keeping it on, so it ducks immediately and stays ducked, so that it won't duck the music any further when you play a sound. The issue with the music volume going back up after pausing and playing the music sounds like a bug.

lucius