start playing audio from a background task via AVAudioPlayer in Xcode

ios, ios7, iphone, objective-c

Solution

From what I've learned after writing an player App, it seems that you can not start playing audio when your App is already in background for longer than X seconds, even if you have configured everything right.

To fix it, you have to use background task wisely. The most important thing is that you must NOT call `endBackgroundTask` immediately after `playSoundFile`. Delay it for about 5-10 seconds.

Here is how I managed doing it for iOS6 and iOS7.

1, Add audio UIBackgroundModes in plist.

2, Set audio session category:

3, Create an AVQueuePlayer in the main thread and enqueue an audio asset before the App enter background.

*For continues playing in background (like playing a playlist)*

4, Make sure the audio queue in AVQueuePlayer never become empty, otherwise your App will be suspended when it finishes playing the last asset.

5, If 5 seconds is not enough for you to get the next asset you can employ background task to delay the suspend. When the asset is ready you should call `endBackgroundTask` after 10 seconds.

Problem

I am trying to start playing a sound from a background task via an AVAudioPlayer that is instantiated then, so here's what I've got. For readability I cut out all user selected values. ``` - (void)restartHandler { bg = 0; bg = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [[UIApplication sharedApplication] endBackgroundTask:bg]; }]; tim = [NSTimer timerWithTimeInterval:.1 target:self selector:@selector(upd) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:tim forMode:NSRunLoopCommonModes]; } - (void)upd { if ([[NSDate date] timeIntervalSinceDate:startDate] >= difference) { [self playSoundFile]; [tim invalidate]; [[UIApplication sharedApplication] endBackgroundTask:bg]; } } - (void)playSoundFile { NSError *sessionError = nil; [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&sessionError]; // new player object _player = [[AVQueuePlayer alloc] init]; [_player insertItem:[AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:@"Manamana" withExtension:@"m4a"]] afterItem:nil]; [_player setVolume:.7]; [_player play]; NSLog(@"Testing"); } ``` Explanation: `bg`, `tim`, `startDate`, `difference` and `_player` are globally declared variables. I call `- (void)restartHandler` from a user-fired method and inside it start a 10Hz repeating timer for `- (void)upd`. When a pre-set difference is reached, `- (void)playSoundFile` gets called and initiates and starts the player. The testing NSLog at the end of the method gets called. Now the strange thing is if I call `- (void)playSoundFile` when the app is active, everything works out just fine, but if I start it from the background task it just won't play any sound. Edit So I tried using different threads at runtime and as it seems, if the Player is not instantiated on the Main Thread this same problem will appear. I also tried using `AVPlayer` and `AVAudioPlayer` where the `AVAudioPlayer`'s `.playing` property did return YES and still no sound was playing.

Original source