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:
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:
Y la mejor manera que dicen de manejar esto es en el applicationDidEnterBackground
y el applicationDidBecomeActive
:
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