web-dev-qa-db-ja.com

Swift iOS -AVPlayerビデオは、アプリがバックグラウンドから戻ってきたときにフリーズ/一時停止します

アプリのログインページでビデオがループ再生されています。私はそれを機能させるためにこの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)
    }
5
Lance Samaria

Apple Docs によると、ビデオが再生され、アプリがバックグラウンドに送信されると、プレーヤーは自動的に一時停止されます。

enter image description here

彼らが言うことは、アプリがバックグラウンドに行くときにAVPlayerLayer(nilに設定)を削除し、フォアグラウンドになるとそれを再初期化することです。

enter image description here

そして、これを処理するために彼らが言う最良の方法は、applicationDidEnterBackgroundapplicationDidBecomeActiveです。

enter image description here

NSNotificationを使用して、バックグラウンドイベントとフォアグラウンドイベントをリッスンし、プレーヤーを一時停止してplayerLayerをnil(両方ともバックグラウンドイベント用)に設定する関数を設定してから、playerLayerを再初期化し、フォアグラウンドイベント用にプレーヤーを再生しました。これらは私が使用した通知です.UIApplicationWillEnterForeground.UIApplicationDidEnterBackground

どういうわけか、ホームボタンを長押しすると、ホームボタンをもう一度押して元の場所に戻ると、「何ができますか」というポップアップ画面が表示されることがわかりました。アプリのビデオはフリーズし、上記の2つの通知を使用してもそれを防ぐことはできません。これを防ぐために私が見つけた唯一の方法は、また通知.UIApplicationWillResignActiveおよび.UIApplicationDidBecomeActiveを使用することです。上記の通知に加えてこれらを追加しない場合、ビデオはホームボタンを長押しして元に戻すとフリーズします。すべての凍結シナリオを防ぐために私が見つけた最善の方法は、4つの通知すべてを使用することです。

上記のコードとは異なる2つのことは、playerクラスとplayerLayerクラス変数を暗黙的にアンラップされたオプションではなくオプションとして設定することでした。また、AVPlayerクラスに拡張機能を追加して、iOS9以下で再生されているかどうかを確認しました。 。 iOS 10以降には、組み込みのメソッド.timeControlStatusAVPlayerタイマーステータス があります。

上記の私のコード:

var player: AVPlayer?
var playerLayer: AVPlayerLayer?

AVPlayerに拡張機能を追加して AVPlayerの状態を確認してください iOS 9以下の場合:

import AVFoundation

extension AVPlayer{

    var isPlaying: Bool{
        return rate != 0 && error == nil
    }
}

以下に完成したコードを示します。

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()
            }
        }
    }
}

AVPlayerにisPlaying拡張機能を追加することを忘れないでください

13
Lance Samaria

受け入れられた答えは私にはうまくいきませんでした。私の「ウェルカム」ビデオは、特定の機会にランダムに一時停止しました。


これが何をしたかです:
Background:アプリが「resignsActive」または「background」に入ったときにplayerオブジェクトとplayerLayerオブジェクトが破棄されないため(それぞれの通知時に状態を観察することで確認できます)と呼ばれます)これらのオブジェクトのいずれかをnilに設定し、背景または前景に入るときにそれらを再初期化する必要は少しないと推測しました。

プレーヤーオブジェクトがフォアグラウンドに入るときにのみ、プレーヤーオブジェクトを再度再生します。

var player: AVPlayer?
var playerLayer: AVPlayerLayer?

ViewDidLoadで、プレーヤーオブジェクトを構成します。

override func viewDidLoad() {
  configurePlayer()
}

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()
}

そしてここにヘルパー関数の実装があります

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)
}
4
Tunscopi

オブザーバーを追加

func addPlayerNotifications() {
    NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidPlayToEnd), name: .AVPlayerItemDidPlayToEndTime, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForeground), name: .UIApplicationWillEnterForeground, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground), name: .UIApplicationDidEnterBackground, object: nil)
}

オブザーバーを削除する

func removePlayerNotifations() {
    NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
    NotificationCenter.default.removeObserver(self, name: .UIApplicationWillEnterForeground, object: nil)
    NotificationCenter.default.removeObserver(self, name: .UIApplicationDidEnterBackground, object: nil)
}

メソッド

// Player end.
@objc  func playerItemDidPlayToEnd(_ notification: Notification) {
    // Your Code.
    player.seek(to: kCMTimeZero)
}

//App enter in forground.
@objc func applicationWillEnterForeground(_ notification: Notification) {
      player.play()
}

//App enter in forground.
@objc func applicationDidEnterBackground(_ notification: Notification) {
      player.pause()
}

このコードを試してください

2
vp2698

Swift 4.で私のために働く簡単な解決策を見つけました。オーバーライドされたViewDidLoadで、アプリがバックグラウンドに入るときとフォアグラウンドに入るときのオブザーバーを作成しました。

NotificationCenter.default.addObserver(self, selector:#selector(VideoViewController.shutItDown), name: UIApplication.didEnterBackgroundNotification, object: UIApplication.shared)
NotificationCenter.default.addObserver(self, selector:#selector(VideoViewController.refresh), name: UIApplication.willEnterForegroundNotification, object: nil)

次に、クラスのオブザーバーによって呼び出される次のメソッドがあります。

@objc func refresh() {
    setupVideo()
}

@objc func shutItDown() {
    self.newLayer.removeFromSuperlayer()
}

ここで、newLayerは、VideoViewにサブレイヤーとして追加されるAVLayerです。冗長にするために、ビデオセットアップのコードを追加して、見た目が大きく異なっていてもすべてが理解できるようにしました。

private func setupVideo() {

    self.path = URL(fileURLWithPath: Bundle.main.path(forResource: "coined", ofType: "mov")!)
    self.player = AVPlayer(url: self.path)

    self.newLayer = AVPlayerLayer(player: self.player)
    self.newLayer.frame = self.videoView.frame
    self.videoView.layer.addSublayer(newLayer)
    self.newLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill

    self.player.play()

    self.videoView.bringSubviewToFront(continueButton)
    self.videoView.bringSubviewToFront(settingsButton)
}

この方法の「欠点」は、バックグラウンドからフォアグラウンドに移動するたびにビデオが再起動されることです。これは私の場合は受け入れられたものですが、あなたのものではないかもしれません。これは、バックグラウンドに移動するとAVLayerが削除され、フォアグラウンドに移動するたびにvideoViewに新しいAVLayerが配置されるためです。古いAVLayerの削除は、レンダリングスナップショットエラーを防ぐため、つまり「フリーズ」を克服するための基本でした。

0
TSonono