当应用程序从后台回来时,Swift iOS -AVPlayer视频冻结/暂停

Lan*_*ria 5 avfoundation nsnotificationcenter ios avplayer swift

我在我的应用程序的登录页面上循环播放视频.我按照这个Youtube教程让它在视图控制器中运行循环视频

问题是当应用程序进入后台时,如果我不立即回来,当我回来时,视频会被冻结.

根据应该发生的Apple Docs.

我试图使用NotificationCenter,Notification.Name.UIApplicationWillResignActive但是没有用.

应用程序从后台返回后,如何让视频继续播放?

var player: AVPlayer!
var playerLayer: AVPlayerLayer!

override func viewDidLoad() {
        super.viewDidLoad()

        configurePlayer()
}


@objc fileprivate func configurePlayer(){

        let url = Bundle.main.url(forResource: "myVideo", withExtension: ".mov")

        player = AVPlayer.init(url: url!)
        playerLayer = AVPlayerLayer(player: player!)
        playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
        playerLayer.frame = view.layer.frame


        player.actionAtItemEnd = AVPlayerActionAtItemEnd.none

        player.play()

        view.layer.insertSublayer(playerLayer, at: 0)

        NotificationCenter.default.addObserver(self, selector: #selector(playerItemReachedEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)

        NotificationCenter.default.addObserver(self, selector: #selector(playerItemReachedEnd), name: Notification.Name.UIApplicationWillResignActive, object: player.currentItem)

    }

@objc fileprivate func playerItemReachedEnd(){
        player.seek(to: kCMTimeZero)
    }
Run Code Online (Sandbox Code Playgroud)

Lan*_*ria 13

根据Apple Docs播放视频并将应用程序发送到后台时,播放器会自动暂停:

在此输入图像描述

他们要做的是AVPlayerLayer在应用程序进入后台时删除(设置为nil),然后在前台进行重新初始化时:

在此输入图像描述

他们说,来处理这个最好的办法是在applicationDidEnterBackgroundapplicationDidBecomeActive:

在此输入图像描述

我使用NSNotification来监听背景和前景事件,并设置函数来暂停播放器并将playerLayer设置为nil(均为背景事件),然后重新初始化playerLayer并为前景事件播放播放器.这些是我使用的通知.UIApplicationWillEnterForeground.UIApplicationDidEnterBackground

我发现的是,出于某种原因,如果你长按主页按钮,弹出的屏幕显示"我可以帮你什么",如果你再次按下主页按钮返回到你的应用视频将被冻结,并使用上面的2通知不会阻止它.我发现这个问题唯一的办法就是使用通知.UIApplicationWillResignActive.UIApplicationDidBecomeActive.如果您不在上述通知中添加这些内容,那么您的视频将在长按按住主页按钮后冻结.我发现阻止所有冻结方案的最佳方法是使用所有4个通知.

我必须做的两件事与我上面的代码不同的是将player和playerLayer类变量设置为选项而不是隐式展开的选项,我还在AVPlayer类中添加了一个扩展,以检查它是否在iOS 9或更低版本中播放.在iOS 10及更高版本中,存在内置方法.timeControlStatus AVPlayer计时器状态

我上面的代码:

var player: AVPlayer?
var playerLayer: AVPlayerLayer?
Run Code Online (Sandbox Code Playgroud)

添加扩展到AVPlayer以检查 iOS 9或更低版本中AVPlayer的状态:

import AVFoundation

extension AVPlayer{

    var isPlaying: Bool{
        return rate != 0 && error == nil
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是完整的代码:

var player: AVPlayer?
var playerLayer: AVPlayerLayer? //must be optional because it will get set to nil on background event

override func viewDidLoad() {
    super.viewDidLoad()

    // background event
    NotificationCenter.default.addObserver(self, selector: #selector(setPlayerLayerToNil), name: .UIApplicationDidEnterBackground, object: nil)

    // foreground event
    NotificationCenter.default.addObserver(self, selector: #selector(reinitializePlayerLayer), name: .UIApplicationWillEnterForeground, object: nil)

   // add these 2 notifications to prevent freeze on long Home button press and back
    NotificationCenter.default.addObserver(self, selector: #selector(setPlayerLayerToNil), name: Notification.Name.UIApplicationWillResignActive, object: nil)

    NotificationCenter.default.addObserver(self, selector: #selector(reinitializePlayerLayer), name: Notification.Name.UIApplicationDidBecomeActive, object: nil)

    configurePlayer()
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // this is also for the long Home button press
    if let player = player{
        if #available(iOS 10.0, *) {
            if player.timeControlStatus == .paused{
                player.play()
            }
        } else {
            if player.isPlaying == false{
                player.play()
            }
        }
    }
}

@objc fileprivate func configurePlayer(){

    let url = Bundle.main.url(forResource: "myVideo", withExtension: ".mov")

    player = AVPlayer.init(url: url!)
    playerLayer = AVPlayerLayer(player: player!)
    playerLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
    playerLayer?.frame = view.layer.frame

    player?.actionAtItemEnd = AVPlayerActionAtItemEnd.none

    player?.play()

    view.layer.insertSublayer(playerLayer!, at: 0)

    NotificationCenter.default.addObserver(self, selector: #selector(playerItemReachedEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
}

@objc fileprivate func playerItemReachedEnd(){
     // this works like a rewind button. It starts the player over from the beginning
     player?.seek(to: kCMTimeZero)
}

 // background event
@objc fileprivate func setPlayerLayerToNil(){
    // first pause the player before setting the playerLayer to nil. The pause works similar to a stop button
    player?.pause()
    playerLayer = nil
}

 // foreground event
@objc fileprivate func reinitializePlayerLayer(){

    if let player = player{

        playerLayer = AVPlayerLayer(player: player)

        if #available(iOS 10.0, *) {
            if player.timeControlStatus == .paused{
                player.play()
            }
        } else {
            // if app is running on iOS 9 or lower
            if player.isPlaying == false{
                player.play()
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

不要忘记将isPlaying扩展添加到AVPlayer


Tun*_*opi 6

接受的答案对我不起作用。我的“欢迎”视频在某些情况下会随机暂停。


背景: 由于当应用程序“resignsActive”或进入“后台”时,player和playerLayer对象不会被销毁(可以通过调用各自的通知时观察它们的状态来验证)我推测
设置这些对象为零,然后在进入后台或前景时重新初始化它们是有点不必要的。

我仅在播放器对象进入前台时再次播放它。

var player: AVPlayer?
var playerLayer: AVPlayerLayer?
Run Code Online (Sandbox Code Playgroud)

在 ViewDidLoad 中,我配置我的播放器对象。

override func viewDidLoad() {
  configurePlayer()
}
Run Code Online (Sandbox Code Playgroud)

configurePlayer()函数定义如下

private func configurePlayer() {
  guard let URL = Bundle.main.url(forResource: "welcome", withExtension: ".mp4") else { return }

  player = AVPlayer.init(url: URL)
  playerLayer = AVPlayerLayer(player: player)
  playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
  playerLayer?.frame = view.layer.frame

  player?.actionAtItemEnd = AVPlayerActionAtItemEnd.none
  playItem()

  setupPlayNotificationItems()
}
Run Code Online (Sandbox Code Playgroud)

这是辅助函数的实现

private func setupPlayNotificationItems() {
  NotificationCenter.default.addObserver(self,
                                        selector: #selector(restartPlayerItem),
                                        name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
                                        object: player?.currentItem)
  NotificationCenter.default.addObserver(self,
                                        selector: #selector(playItem),
                                        name: .UIApplicationWillEnterForeground,
                                        object: nil)
}

@objc private func playItem() {
  // If you please, you can also restart the video here
  restartPlayerItem()

  player?.play()

  if let playerlayer = playerLayer {
    view.layer.insertSublayer(playerlayer, at: 0)
  }
}

@objc func restartPlayerItem() {
  player?.seek(to: kCMTimeZero)
}
Run Code Online (Sandbox Code Playgroud)