Saltar al contenido

El video Swift iOS -AVPlayer se congela / pausa cuando la aplicación vuelve desde el fondo

Solución:

Según Apple Docs, cuando se reproduce un video y la aplicación se envía a un segundo plano, el reproductor se pausa automáticamente:

ingrese la descripción de la imagen aquí

Lo que dicen hacer es quitar el AVPlayerLayer (establecido en nil) cuando la aplicación pasa al fondo y luego reinicializa cuando se trata del primer plano:

ingrese la descripción de la imagen aquí

Y la mejor manera que dicen de manejar esto es en el applicationDidEnterBackground y el applicationDidBecomeActive:

ingrese la descripción de la imagen aquí

Usé NSNotification para escuchar los eventos de fondo y primer plano y establecer funciones para pausar el reproductor y configurar el playerLayer en nil (ambos para el evento de fondo) y luego reinicializar el playerLayer y reproducir el reproductor para el evento de primer plano. Estas son las notificaciones que utilicé .UIApplicationWillEnterForeground y .UIApplicationDidEnterBackground

Lo que descubrí es que, por alguna razón, si mantienes presionado el botón Inicio y aparece la pantalla que dice “¿En qué puedo ayudarte?”, Si presionas el botón Inicio nuevamente para volver a tu app, el video se congelará y el uso de las 2 notificaciones anteriores no lo evitará. La única forma que encontré para prevenir esto es además utilizar las notificaciones .UIApplicationWillResignActive y .UIApplicationDidBecomeActive. Si no agrega estos además de las Notificaciones anteriores, su video se congelará en el botón Inicio, mantenga presionado y regrese. La mejor manera que he encontrado para evitar todos los escenarios congelados es usar las 4 notificaciones.

2 cosas que tuve que hacer de manera diferente a mi código anterior fue establecer las variables de clase player y playerLayer como opcionales en lugar de opcionales sin envolver implícitamente y también agregué una extensión a la clase AVPlayer para verificar si se está reproduciendo o no en iOS 9 o inferior . En iOS 10 y superior hay un método integrado .timeControlStatus Estado del temporizador de AVPlayer

mi código de arriba:

var player: AVPlayer?
var playerLayer: AVPlayerLayer?

Agregue una extensión a AVPlayer para verificar el estado de AVPlayer en iOS 9 o inferior:

import AVFoundation

extension AVPlayer{

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

Aquí está el código completo a continuación:

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: UIApplication.didEnterBackgroundNotification, object: nil)

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

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

    NotificationCenter.default.addObserver(self, selector: #selector(reinitializePlayerLayer), name: UIApplication.didBecomeActiveNotification, 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()
            }
        }
    }
}

NO OLVIDE AÑADIR EL isPlaying EXTENSIÓN AL AVPlayer

La respuesta aceptada no funcionó para mí. Mi video de “bienvenida” se detuvo aleatoriamente en ciertas ocasiones.

Esto es lo que hizo:

Fondo: Dado que los objetos player y playerLayer no se destruyen cuando la aplicación “resignsActive” o pasa al “fondo” (que se puede verificar observando sus estados cuando se llaman sus respectivas notificaciones) supuse que establecía cualquiera de estos objetos en nulo y luego, reinicializarlos al ingresar en segundo plano o en primer plano es un poco innecesario.

Solo vuelvo a reproducir el objeto del jugador cuando entra en primer plano.

var player: AVPlayer?
var playerLayer: AVPlayerLayer?

En ViewDidLoad, configuro mi objeto de reproductor.

override func viewDidLoad() {
  configurePlayer()
}

La función configurePlayer () se define a continuación

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

Y aquí están las implementaciones de funciones auxiliares.

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

Agregar observador

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

Eliminar observador

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

Métodos

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

Prueba este código

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *