Reputation: 16841
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.
Upvotes: 2
Views: 4445
Reputation: 561
Enabling background audio playback requires two steps, well explained in the official documentation:
In addition, enabling background video playback is a simple setting as of iOS 15:
player.audiovisualBackgroundPlaybackPolicy = .continuesIfPossible
You do not have to manually attach and detach the player layer anymore nowadays.
Upvotes: 3
Reputation: 4207
In swift it could be like this. Works on iOS 12 and above
private func setupPlayInBackground() {
NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main, using: { [weak self] _ in
self?.playerLayer?.player = nil
})
NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main, using: { [weak self] _ in
self?.playerLayer?.player = self?.player
})
// Add controls to lock screen if needed
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.isEnabled = true
commandCenter.pauseCommand.isEnabled = true
commandCenter.playCommand.addTarget { [weak self] _ -> MPRemoteCommandHandlerStatus in
self?.player?.play()
return MPRemoteCommandHandlerStatus.success
}
commandCenter.pauseCommand.addTarget { [weak self] _ -> MPRemoteCommandHandlerStatus in
self?.player?.pause()
return MPRemoteCommandHandlerStatus.success
}
}
Upvotes: 0
Reputation: 16841
After some searching I figured out my issue:
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;
}
Upvotes: 3