I've made an iPhone app to be used while exercising. It plays a bell tone to indicate that you (the user) should switch from one step of your exercise routine to the next. I've designed the app so that you can listen to music on the iPod while using the app, and I want the tone to play sufficiently audibly over the music. I've gotten this to work...sort of...
When the music is loud, it's hard to hear the tone. My ideal solution is something similar to the way the iPod app handles an incoming text message or email. The music volume lowers, the sound plays, then the music volume fades back in.
Here are the approaches that I've tried so far:
I've used
AudioServicesPlaySystemSound
to play the sound.I initialized the sound like this:
CFBundleRef mainBundle = CFBundleGetMainBundle(); soundFileURLRef = CFBundleCopyResourceURL(mainBundle, CFSTR ("bell"), CFSTR ("wav"), NULL); AudioServicesCreateSystemSoundID (soundFileURLRef, &soundFileID);
And I play the sound at the appropriate time using:
AudioServicesPlaySystemSound (self.soundFileID);
This plays the sound fine, but it is too hard to hear over loud music. On to attempt 2...
I tried to lower the iPod volume, play the sound, and then return the volume to its previous level.
If there's 1 second left in the current step, start lowering the volume:
if ([self.intervalSet currentStepTimeRemainingInSeconds] == 1) { self.volumeIncrement = originalVolume/5.0f; [NSTimer scheduledTimerWithTimeInterval:0.5f target:self selector:@selector(fadeVolumeOut:) userInfo:[NSNumber numberWithInt:1] repeats:NO]; }
Here's the
fadeVolumeOut:
method:- (void) fadeVolumeOut:(NSTimer *)timer { if (!TARGET_IPHONE_SIMULATOR) { NSNumber *volumeStep = (NSNumber *)[timer userInfo]; int vStep = [(NSNumber *)volumeStep intValue]; float volume = [[MPMusicPlayerController iPodMusicPlayer] volume]; volume = volume - self.volumeIncrement; if (volume < 0.0f) { volume = 0.0f; } [[MPMusicPlayerController iPodMusicPlayer] setVolume:volume]; if (vStep < 5) { vStep = vStep + 1; [NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(fadeVolumeOut:) userInfo:[NSNumber numberWithInt:vStep] repeats:NO]; } } }
Then, when the step ends, play the alert sound and fade the volume back in:
[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(alertAndFadeVolumeIn) userInfo:nil repeats:NO];
Here's the
alertAndFadeVolumeIn
method:- (void) alertAndFadeVolumeIn { [self alert]; [NSTimer scheduledTimerWithTimeInterval:0.25f target:self selector:@selector(fadeVolumeIn:) userInfo:[NSNumber numberWithInt:1] repeats:NO]; }
And
fadeVolumeIn:
is basically the opposite offadeVolumeOut:
above.This works, the volume fades out, the sound plays, and the volume fades back in. The problem is that the tone volume is lowered by the same amount as the iPod, so it doesn't make it any easier to hear over the music.
I switched to
AVAudioSession
to play the sound, and set up the session so that the iPod music will continue to play while the app is in use. Here's how I'm initializing the session:AVAudioSession *session = [AVAudioSession sharedInstance]; [session setCategory:AVAudioSessionCategoryPlayback error:nil]; OSStatus propertySetError = 0; UInt32 allowMixing = true; propertySetError = AudioSessionSetProperty ( kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof (allowMixing), &allowMixing ); NSError *activationError = nil; [session setActive:YES error:&activationError]; NSString *audioFile = [[NSBundle mainBundle] pathForResource:@"bell" ofType:@"wav"]; player = [[AVAudioPlayer alloc] initWithContentsOfURL: [NSURL fileURLWithPath:audioFile] error:NULL];
To play the sound, I call
[self.player play]
at the appropriate time. Again, the tone volume lowers along with the iPod volume, and the tone is not any easier to hear.I tried putting
[[MPMusicPlayerController applicationMusicPlayer] setVolume:1.0f];
right before the alert sound plays. This had mixed results. The first time the sound plays at full volume as I had hoped, but subsequent times the volume is much lower. Also, the music doesn't fade out smoothly. It seems likeiPodMusicPlayer
andapplicationMusicPlayer
are sharing the same volume. Is this a result of using[AVAudioSession sharedInstance];
? Is there another way to initialize anAVAudioSession
?Next, I tried using
AVAudioSession
"ducking":OSStatus propertySetError = 0; UInt32 allowDucking = true; propertySetError = AudioSessionSetProperty ( kAudioSessionProperty_OtherMixableAudioShouldDuck, sizeof (allowDucking), &allowDucking );
Unfortunately, the iPod music "ducks" when the audio session is activated, which is as soon as the viewController is loaded the way I had things.
Finally, I changed my code so that the audio session is activated one second before the step ends, the sound is played when the step ends, and one second later, the session is deactivated. I've removed all of my fading in and out code at this point. Here are the relevant code snippets:
if ([self.intervalSet currentStepTimeRemainingInSeconds] == 1) { AVAudioSession *session = [AVAudioSession sharedInstance]; [session setActive:YES error:nil]; }
...
if (self.shouldRing) { [self.player play]; [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(stopSound) userInfo:nil repeats:NO]; }
...
- (void) stopSound { [self.player stop]; AVAudioSession *session = [AVAudioSession sharedInstance]; [session setActive:NO error:nil]; }
This takes me to the point where I'm really stuck. This works perfectly after the first step ends. The iPod volume ducks, the sound plays loudly, and the iPod volume fades back in a second later. However, the second time a step ends the sound plays slightly less loudly, the third time it's barely audible, and the fourth time it's silent. Then, the fifth time, it plays loudly again and the cycle repeats.
On top of that, activating and deactivating seem to be pretty heavy operations and they cause the timer to stutter slightly.
Has anyone tried to do something similar? Is there a preferred approach to playing a sound over the iPod music?