iosobjective-cavplayer

Prevent AVPlayer from pausing when it enters the background?


I have a simple app with an AVPlayer playing M3U8 content. Within the app, I do the following to allow background audio:

    NSError *audioError;
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&audioError];
    if (audioError == nil)
    {
        [[AVAudioSession sharedInstance] setActive:YES error:nil];
    }

When I hit the lock button on my phone, the audio pauses. If I hit the lock button again (to look at the lock screen) I see media controls and I can un-pause the content to hear the audio.

My question is, how do I prevent this auto-pause so it keeps playing when the lock button is first pressed? I tried something like the following:

- (id) init
{
    // ...
    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(handleEnteredBackground) name: UIApplicationDidEnterBackgroundNotification object: nil];
    // ...
}

- (void) handleEnteredBackground
{
    // [player play];
    [player performSelector:@selector(play) withObject:nil afterDelay:1];
}

But this didn't seem to work.


Solution

  • After some searching I figured out my issue:

    https://developer.apple.com/documentation/avfoundation/media_assets_playback_and_editing/creating_a_basic_video_player_ios_and_tvos/playing_audio_from_a_video_asset_in_the_background

    If you’re playing audio-only assets, such as MP3 or M4A files, your setup is complete and your app can play background audio. If you need to play the audio portion of a video asset, an additional step is required. If the player’s current item is displaying video on the device, playback of the AVPlayer instance is automatically paused when the app is sent to the background. If you want to continue playing audio, you disconnect the AVPlayer instance from the presentation when entering the background and reconnect it when returning to the foreground.

    Their (Swift) example code looks like so:

    func applicationDidEnterBackground(_ application: UIApplication) {
    
        // Disconnect the AVPlayer from the presentation when entering background
    
        // If presenting video with AVPlayerViewController
        playerViewController.player = nil
    
        // If presenting video with AVPlayerLayer
        playerLayer.player = nil
    }
    
    func applicationWillEnterForeground(_ application: UIApplication) {
        // Reconnect the AVPlayer to the presentation when returning to foreground
    
        // If presenting video with AVPlayerViewController
        playerViewController.player = player
    
        // If presenting video with AVPlayerLayer
        playerLayer.player = player
    }
    

    An Objective-C equivalent would be:

    - (void) applicationDidEnterBackground:(UIApplication*)application
    {
        playerViewController.player = nil;
    }
    - (void) applicationWillEnterForeground:(UIApplication*)application
    {
        playerViewController.player = player;
    }
    

    Or in my case the player was embedded into a View and was not controlled directly by the app delegate or the view controller, so I used the following:

    - (id) init
    {
        // ...
        [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(handleEnteredBackground) name: UIApplicationDidEnterBackgroundNotification object: nil];
        [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(handleEnteredForeground) name: UIApplicationDidBecomeActiveNotification object: nil];
        // ...
    }
    
    - (void) handleEnteredBackground
    {
        controller.player = nil;
    }
    
    - (void) handleEnteredForeground
    {
        controller.player = player;
    }