12

I have an app mostly based around Core Bluetooth. When something specific happens, the app is woken up using Core Bluetooth background modes and it fires off an alarm, however I can't get the alarm working when the app is not in the foreground.

I have an Alarm Singleton class which initialises AVAudioPlayer like this:

NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
                    pathForResource:soundName
                             ofType:@"caf"]];

self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
[self.player prepareToPlay];
self.player.numberOfLoops = -1;
[self.player setVolume:1.0];

NSLog(@"%@", self.player);

This is the method that is called when my alarm code is called:

-(void)startAlert
{
    NSLog(@"%s", __FUNCTION__);

    playing = YES;
    [self.player play];
    NSLog(@"%i", self.player.playing);

    if (vibrate) {
        [self vibratePattern];
    }
}

Now when the app is in the foreground, self.player.playing returns 1 however when the app is in the background self.player.playing returns 0. Why would this be? All the code is being called, so the app is awake and functioning. The vibrate works perfectly which uses AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);

Any idea why this sound won't play?

Thanks

sunkehappy
  • 8,970
  • 5
  • 44
  • 65
Darren
  • 10,182
  • 20
  • 95
  • 162
  • Have you tested with device? – Bhavin Apr 29 '14 at 06:32
  • Yes of course. I need the device for the Bluetooth to function. – Darren Apr 29 '14 at 13:28
  • In this answer there is all information that you need.. http://stackoverflow.com/questions/22591421/ios-background-audio-not-playing/22777813#22777813 – Ashish Kakkad May 02 '14 at 11:52
  • That was my original question a few weeks ago and did not get a working answer :( – Darren May 02 '14 at 12:01
  • Pass error arguments in important code and check for return values. I am pretty sure AudioSession returns error. – pronebird May 03 '14 at 23:50
  • AudioSession is fine as when I call startAlert in the foreground it plays. If I put into background then call it, the player is still the same object but doesn't play. – Darren May 04 '14 at 08:12
  • This is https://stackoverflow.com/a/44681664/7889432 my answer may be helpful for you, or you can using lib https://github.com/teodorpatras/Jukebox – Giang Jun 21 '17 at 16:39

7 Answers7

14

Apple has a nice Technical Q&A article about this in its documentation (see also Playing and Recording Background Audio).

I think one big thing missing is that you haven't activated the Audio Background Mode in the Xcode settings: enter image description here

Maybe also adding [self.player prepareToPlay] in your alert method is helpful.

Michael Dorner
  • 17,587
  • 13
  • 87
  • 117
9

I have an App than also needs background audio but my App works with the App background mode "Voice over IP" as it needs to record sometimes. I play background audio telling the App singleton I need to play audio in background:

UIBackgroundTaskIdentifier newTaskId = UIBackgroundTaskInvalid;
if([thePlayer play]){
    newTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];   
}

EDIT: You must call [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL]; before your app goes to background. In my app, it is at the same time you start playing, in yours, if the player might be started in background, you should do:

- (void)applicationDidEnterBackground:(UIApplication *)application{
  // You should retain newTaskId to check for background tasks and finish them 
  newTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];   
}
Pablo Romeu
  • 2,113
  • 1
  • 13
  • 15
  • I'm afraid this doesn't work. if([player play]) returns FALSE – Darren May 02 '14 at 11:40
  • Ok, edited my answer, maybe the problem is you are not starting to play before going to background – Pablo Romeu May 03 '14 at 08:46
  • I don't want to start playing though until needed. – Darren May 04 '14 at 08:13
  • This still doesn't work. If I start playing the alert and then go to background, it keeps playing fine. But if I start to play while in background it doesn't work although the code is called fine. – Darren May 05 '14 at 07:53
  • Maybe the problem is you must play before you go to background: "When the UIBackgroundModes key contains the audio value, the system’s media frameworks automatically prevent the corresponding app from being suspended when it moves to the background. As long as it is playing audio or video content or recording audio content, the app continues to run in the background. However, if recording or playback stops, the system suspends the app."https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html – Pablo Romeu May 05 '14 at 09:06
  • I guess it's not possible to do what I want then :( – Darren May 05 '14 at 09:57
  • 1
    You can! Start a sound in `applicationDidEnterBackground:` when but set the volume to 0. Then you will be able to play a sound later... – Pablo Romeu May 05 '14 at 20:36
  • 1
    That's an idea. How would that affect the battery though if it's playing on silent for 3-4 hours? – Darren May 05 '14 at 21:02
  • I think you cannot do that, because iOS will immediately move your App to suspended mode if you stop... – Pablo Romeu May 06 '14 at 05:55
3

Apple docs

Either enable audio support from the Background modes section of the Capabilities tab

Or enable this support by including the UIBackgroundModes key with the audio value in your app’s Info.plist file

zeeawan
  • 6,667
  • 2
  • 50
  • 56
2

I take it you have the audio background mode specified for the app. Even so, I'm not sure you can set an audio session to be active while in the background. You need to have activated it before going into the background. You may also need to play some silent audio to keep this active, but this is seems like bad practice (it may drain the battery). Looking at the docs for notifications there seems to be a way to have a local notification play an audio sample that's included in your bundle, which seems to be what you want to do, so maybe that's the way to go.

Lewis Gordon
  • 1,711
  • 14
  • 19
  • Yes I have background audio in the plist. I originally used notifications, however I need it to also make sound when the phone is on silent. Is there no other way around this? – Darren Apr 27 '14 at 19:56
  • Hmmm, making a sound when the phone is on silent doesn't seem like it would be supported by a standard call. – Lewis Gordon Apr 27 '14 at 20:40
  • The method used above does play when on silent. There's many app that do this kind of thing. – Darren Apr 28 '14 at 05:52
  • Yes the audio session will play in the background, even when on silent mode. All my music apps do exactly that. Please see my answer above regarding the background wake up method call question. – Kaveh Vejdani Apr 30 '14 at 22:34
1

You need to enable your app to handle audiosession interruptions (and ended interruptions) while in the background. Apps handle audio interruptions through notification center:

  1. First, register your app with the notification center:

    - (void) registerForMediaPlayerNotifications {
    
        [notificationCenter addObserver : self
                                selector: @selector (handle_iPodLibraryChanged:)
                                    name: MPMediaLibraryDidChangeNotification
                                  object: musicPlayer];
    
        [[MPMediaLibrary defaultMediaLibrary] beginGeneratingLibraryChangeNotifications];
    
    }
    
  2. Now save player state when interruption begins:

    - (void) audioPlayerBeginInterruption: player {
    
        NSLog (@"Interrupted. The system has paused audio playback.");
    
        if (playing) {
            playing = NO;
            interruptedOnPlayback = YES;
        }
    }
    
  3. And reactivate audio session and resume playback when interruption ends:

    -(void) audioPlayerEndInterruption: player {
    
        NSLog (@"Interruption ended. Resuming audio playback.");
    
        [[AVAudioSession sharedInstance] setActive: YES error: nil];
    
        if (interruptedOnPlayback) {
            [appSoundPlayer prepareToPlay];
            [appSoundPlayer play];
            playing = YES;
            interruptedOnPlayback = NO;
        }
    }
    

Here's Apple's sample code with full implementation of what you're trying to achieve:

https://developer.apple.com/library/ios/samplecode/AddMusic/Introduction/Intro.html#//apple_ref/doc/uid/DTS40008845

Bista
  • 7,869
  • 3
  • 27
  • 55
Kaveh Vejdani
  • 224
  • 1
  • 6
  • Hi, startAlert is called, because the app has been woken by the Bluetooth background modes. So how do I call the audioPlayerEndInterruption? Or is that called by the delegate when I attempt to `play` the first time? – Darren May 01 '14 at 09:53
  • 1. If startAlert is called and player is not playing, it means your audioPlayer state and context was lost when the app was suspended, before you got a chance to save it. 2. audioPlayerEndInterruption is called by notification center when your app is woken from suspension, which is why your audio playing app needs to register for notification center so it can handle audio interruptions. 3. Now when the interruption ends, you can reactivate your audio session with : [[AVAudioSession sharedInstance] setActive: YES error: nil]; I will edit my main answer in a few minutes with detailed code. – Kaveh Vejdani May 02 '14 at 03:27
  • Thanks. I don't seem to be using AVAudioSession anywhere, just AVAudioPlayer. What is mediaPlayer defined as above? – Darren May 02 '14 at 11:15
  • Ok, it seems these notifications are for `MPMusicPlayerController` which I am not using as I am not playing media from the users library. – Darren May 02 '14 at 11:28
  • Didn't know you're using the library. Edited my answer (step 1) accordingly. It's there in the Apple's sample code too. Hope you get it to work finally :) – Kaveh Vejdani May 02 '14 at 21:19
  • BTW, apologies for the screwed up code block formatting. Neither ctrl-K, cmd-K or {} are working for me. Whatever I do, the end result comes out random. – Kaveh Vejdani May 03 '14 at 22:41
  • This has nothing to do with the original problem. Besides that you offer to listen to iPod library changes, what's the point? – pronebird May 03 '14 at 23:43
  • Yeh this makes no different sorry. The problem seems to come from wanting to start the play in the background. – Darren May 05 '14 at 07:46
1

Try this :

[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];

link

Try this http://www.sagorin.org/ios-playing-audio-in-background-audio/

Community
  • 1
  • 1
tilak
  • 4,589
  • 6
  • 34
  • 45
1

I was also facing the same problem, but i was only facing it only during initial time when i was trying to play a sound while app was in background, once the app comes in foreground and i play the sound once than it works in background also.

So as soon as app is launched/ login is successful in my case, i was running this code:

[self startRingcall];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self stopRingcall];
    });
- (void)startRingcall
{
    if( self.audioPlayer )
        [self.audioPlayer stop];
    NSURL* musicFile = [NSURL fileURLWithPath:[[[UILayer sharedInstance] getResourceBundle] pathForResource:@"meetcalling" ofType:@"caf"]];
    NSError *error;
    self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:musicFile error:&error];
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
    [[AVAudioSession sharedInstance] setActive: YES error: nil];
    if (error != nil) {
        NSLog(@"meetringer sound error -> %@",error.localizedDescription);
    }
    self.audioPlayer.volume = 0;
    [self.audioPlayer play];
    self.audioPlayer.numberOfLoops = 1;
}

- (void)stopRingcall
{
    if( self.audioPlayer )
        [self.audioPlayer stop];
    self.audioPlayer = nil;
}