问题描述
iOS 14在后台播放视频中的音频: 应用进入背景时音频被暂停。甚至我在“功能”中打开了后台模式。 如果播放器正在播放,请按锁定按钮,然后发生
解决方法
如果您正在使用avplayer播放视频,并且还希望在应用程序处于后台时播放音频。
尝试以下代码,它可以在我的机器上运行:
- Xcode版本:12.0
- iOS版本:iOS 14
//first,observe the relative notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillResignActive:)
name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification object:nil];
//Add an observer to AVPlayerItem,this is the key point!!!
[playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
-(void)applicationWillResignActive:(NSNotification *)notification{
//1 make sure the category of AVAudioSession is AVAudioSessionCategoryPlayback
AVAudioSession *session=[AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
//2 This is important!!!
//set the avplayerlayer.player = nil.
mavPlayerLayer.player = nil;
}
//when app becomes active,we should add the player to the avplayerlayer.
-(void)applicationWillEnterForeground:(NSNotification *)notification{
// restore the avPlayerLayer.player
mavPlayerLayer.player = mavPlayer;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {
NSLog(@"playbackLikelyToKeepUp");
[mavPlayer play];
}
}
当然,不要忘记在info.plist文件中添加相关属性以支持后台播放。
,在等待苹果修复它的同时,我有一个临时解决方案:
-
请勿使用 UIApplication.didEnterBackgroundNotification 和 UIApplication.willEnterForegroundNotification
-
使用 UIApplicationWillResignActiveNotification 和 UIApplication.didBecomeActiveNotification
现在,在“更改应用程序”(向下滑动,将应用程序移至后台)的情况下,它可以正常运行。
但是,当应用程序通过按“锁定按钮”直接进入时不起作用->播放器将暂停音乐,用户必须单击lockScreen上的playButton才能继续(这使用户感到恼火)
-> ***我通过设置autoPlay(应用程序进入后台1次)解决了恼人的问题:
var didEnterBackground:Bool =假,canSkipPausing:Bool =真
//在timerDuration块中(timerDuration用于计算播放秒数):
if self.didEnterBackground && self.canSkipPausing == true {
//fix player auto-paused when enter background
self.canSkipPausing = false
if self.playerPlayer?.rate == 0.0 {
self.playerPlayer?.play()
}
}
//处理'didEnterBackground'和'canSkipPausing':
@objc
func applicationDidEnterBackground(_ notification: Notification) {
didEnterBackground = true
// canSkipPausing = false // remember don't set this here,set it before entering background
playerLayer?.player = nil
}
@objc
func applicationWillEnterForeground(_ notification: Notification) {
didEnterBackground = false
canSkipPausing = true
playerLayer?.player = playerPlayer
}
//通过这种技巧,当按下设备锁定按钮时,音乐仍然暂停了一点,并在0.5秒后再次自动播放,但至少用户不需要执行任何操作。
,我终于找到了解决方案。不确定这是iOS 14的bug还是仅是设计缺陷。根据Apple的文档(Special Considerations for Video Media),您需要在进入背景时禁用视频轨道,而在进入前景时将其重新启用。但是我认为他们直到iOS14才真正实施了此建议。这是我完整的示例。
import UIKit
import AVKit
class ViewController: UIViewController
{
var aPlayer: AVPlayer! = AVPlayer()
var playButton: UIButton!
var videoView: UIView!
var videoFrame: CGRect!
var active: Bool! = false
override func viewDidLoad()
{
super.viewDidLoad()
videoFrame = CGRect(x: 100,y: 100,width: 640,height: 480)
self.view.backgroundColor = .blue
// These notifications were send from AppDelegate accordingly.
NotificationCenter.default.addObserver(self,selector: #selector(fromApplicationWillResignActive),name: NSNotification.Name(rawValue: "ApplicationWillResignActive"),object: nil)
NotificationCenter.default.addObserver(self,selector: #selector(fromApplicaitonWillEnterForeground),name: NSNotification.Name(rawValue: "ApplicationWillEnterForeground"),object: nil)
setupUI()
}
func setupUI()
{
playButton = UIButton(type: .custom)
playButton.frame = CGRect(x: 50,y: 50,width: 100,height: 40)
playButton.setTitle("Play",for: .normal)
playButton.showsTouchWhenHighlighted = true
playButton.addTarget(self,action: #selector(playButtonPressed),for: .touchUpInside)
self.view.addSubview(playButton)
}
@objc func playButtonPressed(button: UIButton)
{
self.active = true
videoView = UIView(frame: videoFrame)
videoView.backgroundColor = .purple
videoView.frame = videoFrame
self.view.addSubview(videoView)
let videoURL: URL = (Bundle.main.resourceURL?.appendingPathComponent("myfavor1.mp4"))!
let item = AVPlayerItem(url: videoURL)
self.aPlayer.replaceCurrentItem(with: item)
let aLayerPlayer = AVPlayerLayer(player: self.aPlayer)
aLayerPlayer.frame = self.videoView.bounds
self.videoView.layer.addSublayer(aLayerPlayer)
self.aPlayer.play()
}
@objc func fromApplicationWillResignActive(aNotification: NSNotification)
{
if self.active
{
self.videoView.removeFromSuperview()
self.videoView = nil
// For iOS 14
let tracks = self.aPlayer.currentItem!.tracks
for playerItemTrack in tracks
{
if playerItemTrack.assetTrack!.hasMediaCharacteristic(AVMediaCharacteristic.visual)
{
// Disable the track.
playerItemTrack.isEnabled = false
}
}
}
}
@objc func fromApplicaitonWillEnterForeground(aNotification: NSNotification)
{
if self.active
{
self.videoView = UIView(frame: self.videoFrame)
self.videoView.backgroundColor = .purple
let aLayerPlayer = AVPlayerLayer(player: self.aPlayer)
aLayerPlayer.frame = self.videoView.bounds
self.videoView.layer.addSublayer(aLayerPlayer)
self.view.addSubview(self.videoView)
// For iOS 14
let tracks = self.aPlayer.currentItem!.tracks
for playerItemTrack in tracks
{
if playerItemTrack.assetTrack!.hasMediaCharacteristic(AVMediaCharacteristic.visual)
{
// Disable the track.
playerItemTrack.isEnabled = true
}
}
}
}
}