Tuesday, October 6, 2009

Dev Diary: preventing iPhone sleep

I've recently added a "clock radio" alarm feature to some of my iPhone radio apps. As part of this process, I had to figure out a reasonable work-around for Apple's restriction of not allowing 3rd party apps to run in the background.

Unfortunately, the best that can be done for an app that wants to behave as an alarm clock is to require the user of the app to not exit the app. While this seems fairly straightforward, it does introduce another problem. When on battery power, most users will not want the screen of the iPhone to be on while they are using the alarm feature, so normal behavior would be for a user to set the alarm and then "lock" the iPhone screen. And there's the problem - the iPhone OS has a feature where if it is running on battery power, it will suspend any running app if the iPhone screen is locked, except under one condition. If the app is playing audio with the audio session property of kAudioSessionCategory_MediaPlayback, the app will be allowed to continue to run indefinitely while the iPhone screen is locked.

So, if you have an alarm clock feature in an app, this would seem to imply that you must keep the iPhone plugged into a charger, otherwise the app will be suspended a few minutes after you lock the screen and the alarm timer will never get triggered. Fortunately there is another way around this problem and it only uses a minor amount of battery power. If you have your app continuously play a silent audio file while the screen is locked, then this will prevent the OS from suspending the app.

Here's a rough example of how you can implement this using a few lines of code and the AVAudioPlayer class.

AVAudioPlayer *silentPlayer;
NSString *soundFile =[NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] resourcePath], SILENT_SOUND_FILE];
NSData *data = [NSData dataWithContentsOfFile:soundFile];

if (data){
NSError *outError;
silentPlayer = [[AVAudioPlayer alloc] initWithData:data error:&outError];
// trigger some error

silentPlayer.delegate = self;
silentPlayer.volume = 0.0;
[silentPlayer play];

Then, since I am specifying the class using this silentPlayer is its delegate, I also implement the following method to be notified when the silent file has finished playing. In this case, I want the silent file to loop, so I simply restart it.

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
// When the sound stops, wait 1 second and play it again - forever!
[NSThread sleepForTimeInterval:1.0f];
[player play];

Obviously, this isn't a complete implementation, but should give you the basics to get started with preventing the iPhone from sleeping. In my particular case, when I want the silent file to stop playing, I simply remove the delegate for the silentPlayer and then tell it to stop:

silentPlayer.delegate = nil;
[silentPlayer stop];

This way, when the silentPlayer stops playing, the audioPlayerDidFinishPlaying method will not be called again, so the silent file will no longer be looping.

All content copyright © 2009  Brian Stormont, unless otherwise noted.   All rights reserved.