27

My background audio works fine almost all the time. Screen locked, or mute switch on. But when the user has the application in the background, and it receives a call, even if the user doesn't answer the call, the background audio does not resumes after the interruption ends.

The Music app properly resumes background audio if it was interrupted.

Am I missing some property or do I need to have a callback or set a background task to continue background audio execution after an interruption? Or this is something that we can't do?

carlos
  • 2,624
  • 4
  • 26
  • 36

6 Answers6

22

We are resuming our audio after an outbound call and inbound call while the app is in the background.

We play audio with AVAudioPlayer and listen to the AVAudioSessionInterruptionNotification. Apple automatically pauses the AVAudioPlayer for you on an interruption and when you tell it to resume after you receive the interruption is over, Apple will set your session active again. See Table 3-2 from Handling Audio Interruptions on recommendations if you are using other types of audio technologies.

Subscribe to the notification:

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAudioSessionEvent:) name:AVAudioSessionInterruptionNotification object:nil];

Handle the notification:

- (void) onAudioSessionEvent: (NSNotification *) notification
{
    //Check the type of notification, especially if you are sending multiple AVAudioSession events here
    if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
        NSLog(@"Interruption notification received!");

        //Check to see if it was a Begin interruption
        if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
            NSLog(@"Interruption began!");

        } else {
            NSLog(@"Interruption ended!");
            //Resume your audio
        }
    }
}
Michael M. Myers
  • 2,495
  • 26
  • 22
  • 2
    It works when the app plays the audio in foreground. If app is background it is not resume. But If I comes to foreground and then working. @Michale Myers can you please give suggestion? – jailani Mar 13 '14 at 13:28
  • @jai [Do you have the capability to play audio in the background setup?](http://stackoverflow.com/questions/10429204/how-to-handle-background-audio-playing-while-ios-device-is-locked-or-on-another) – Michael M. Myers Mar 13 '14 at 23:04
  • My app runs in the back ground well. but how to check my app is still running after the screen locked? because my device doesn't get locked when my application running in the background... – jailani Mar 14 '14 at 05:32
  • 1
    This is the problem I am facing http://stackoverflow.com/questions/22400345/playing-music-at-back-ground-avaudiosessioninterruptionnotification-not-fired please give suggestion – jailani Mar 14 '14 at 09:12
  • 1
    With SoloAmbient, I no longer receive this notification even after I register it correctly. With PlayAndRecord, I only receive the entrance notification but not the exit one. This mechanism is totally broken for me. By the way, I use AVAudioSession, but the underlying tech is AU. But I think half a year ago this worked. It must have been broken somewhere along iOS 8.1.x. – kakyo Mar 12 '15 at 20:36
  • This worked beautifully for me under iOS 8.1 using AVQueuePlayer inside a Cordova plugin. Thank you! – Nosgoroth Dec 15 '15 at 09:15
6

In my experience I found I had to "wait a second or two" before attempting to reopen the audio device. I think that after the OS switches back to your app from the call, the phone app is still shutting down.

something like this when you return to foreground after knowing your audio session has been stopped:

            dispatch_time_t restartTime = dispatch_time(DISPATCH_TIME_NOW, 
                                                      1.5LL * NSEC_PER_SEC);

            dispatch_after(restartTime, dispatch_get_global_queue(0, 0), ^{ 
                [audioSystem restart]; 
            });
Tony Million
  • 4,296
  • 24
  • 24
  • This only happens when the app is in the background, and background audio services is up. If the call is received while the app is running and playing audio, it resumes playback after the call. – carlos Feb 28 '12 at 14:23
  • 1
    Ah, I understand now. Bad news, Apps cannot reopen the audio device while they are in the background, the user must manually bring the app to the foreground before you can reopen the audio device! – Tony Million Feb 28 '12 at 14:25
  • That's what I thought. I little lame, the music app successfully restores playback after a call. – carlos Feb 28 '12 at 14:46
2

I am using Xamarin so this is C# but the following code is working for me. I first set that my AppDelegate implements the AVAudioSession delegate methods by including IAVAudioSessionDelegate

public partial class AppDelegate: UIApplicationDelegate, IAVAudioSessionDelegate

I added a variable to the AppDelegate class for the audio session

public static AVAudioSession AudioSession;

In the override of the FinishedLaunching method:

AudioSession = AVAudioSession.SharedInstance();
AudioSession.Delegate = this;
error = AudioSession.SetCategory( AVAudioSessionCategory.Playback, AVAudioSessionCategoryOptions.DuckOthers );
AudioSession.SetMode( new NSString( "AVAudioSessionModeMoviePlayback" ), out error );

The two relevant delegate methods in AppDelegate.cs are:

[Export( "beginInterruption" )]
public void BeginInterruption()
{
        PlayerViewController.BeginSessionInterruption();
}
[Export( "endInterruptionWithFlags:" )]
public void EndInterruption( AVAudioSessionInterruptionFlags flags )
{  // ignore the flags so we're not dependent on the interrupting event saying that we can resume

 PlayerViewController.ResumeAfterSessionInterruption();
 }

My AppDelegate also has an override of OnActivated to enable the video tracks if it's a video asset, and an override of DidEnterBackground to disable the media's video tracks but still play the audio.

In the PlayerViewController.BeginSessionInterruption() method, I can't look at Player.Rate to see if the player was running at the time, because the interrupting alarm or phone call has already paused the player. From the "Responding to Interruptions" section of Apple's Audio Session Programming Guide, with my emphasis added:

  1. Your app is active, playing back audio.
  2. A phone call arrives. The system activates the phone app’s audio session.
  3. The system deactivates your audio session. At this point, * playback in your app has stopped *.
  4. The system invokes your interruption listener callback function indicating that your session has been deactivated. ...

My PlayerViewController's Play button has a Paused property to toggle between Play and Pause and draw the appropriate button image. So instead of checking Player.Rate, I look to see if the button's Paused property is false:

 public void BeginSessionInterruption()
        {
 PlayerWasRunningOnInterruption = !btnPlay.Paused;
 TogglePlayPause( true );  // put the Play button into the Paused state to agree with the player being stopped
public void ResumeAfterSessionInterruption()
        {
        NSError error;
AppDelegate.AudioSession.SetActive( true, AVAudioSessionSetActiveOptions.NotifyOthersOnDeactivation, out error );  // always reactivate the audio session
    if ( PlayerWasRunningOnInterruption )
                {
// rewind a little bit
// call btnPlayClick to resume the playback as if the user pressed the Play button
            }
    }
Sham Dhiman
  • 1,348
  • 1
  • 21
  • 59
Gary Z
  • 155
  • 2
  • 6
1

Look at:

https://developer.apple.com/documentation/avfoundation/avaudiosession#//apple_ref/doc/uid/TP40008240-CH1-DontLinkElementID_3

https://developer.apple.com/documentation/avfoundation/avaudiosession/responding_to_audio_session_interruptions

Code:

private func setupAudioSession() {
    do {

        try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: .mixWithOthers)
        try AVAudioSession.sharedInstance().setActive(true)

        setupAudioNotifications()

    } catch {
        print(error)
    }
}

private func setupAudioNotifications() {
    NotificationCenter.default.addObserver(self, selector: #selector(handleInterruption), name: .AVAudioSessionInterruption, object: nil)
}

@objc func handleInterruption(notification: Notification) {

    guard let userInfo = notification.userInfo,
        let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
        let type = AVAudioSessionInterruptionType(rawValue: typeValue) else {
            return
    }

    if type == .began {
        // Interruption began, take appropriate actions
        Player.shared.stop()

    } else if type == .ended {
        if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
            let options = AVAudioSessionInterruptionOptions(rawValue: optionsValue)
            if options.contains(.shouldResume) {
                // Interruption Ended - playback should resume
                Player.shared.start()
            } else {
                // Interruption Ended - playback should NOT resume
            }
        }
    }
}
Max
  • 636
  • 3
  • 13
  • 28
  • can you please help me it " Interruption Ended - playback should NOT resume"? – Yogesh Patel Mar 01 '21 at 17:00
  • Good example. Just note: when the App is not currently playing any audio, this handler is not being called. One is responsable to restart all the Audio required. – ZAY Sep 24 '21 at 09:24
0

if you only detect the call status you can use CTCallCenter, but if you want to detect the interruption (include the call interruption),you can use AVAudioSessionInterruptionNotification, and if you want to support background you should add code like this:

  [[AVAudioSession sharedInstance] setActive:YES error:nil];
  [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];

before you resume play music in background.hope this can help.

iCrany
  • 115
  • 10
0

Only two options works for me:

  1. reload AVPlayer or AVAudioPlayer after interruption end

    func interruptionNotification(_ notification: Notification) {
    guard let type = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? UInt,
      let interruption = AVAudioSessionInterruptionType(rawValue: type) else {
        return
    }
    if interruption == .ended && playerWasPlayingBeforeInterruption {
      player.replaceCurrentItem(with: AVPlayerItem(url: radioStation.url))
      play()
    }
    

    }

  2. use

AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: .mixWithOthers)

Vitalii Gozhenko
  • 9,220
  • 2
  • 48
  • 66