Compruebe el estado de reproducción de AVPlayer

Respuestas:

57

Para recibir una notificación por llegar al final de un artículo (a través de Apple ):

[[NSNotificationCenter defaultCenter] 
      addObserver:<self>
      selector:@selector(<#The selector name#>)
      name:AVPlayerItemDidPlayToEndTimeNotification 
      object:<#A player item#>];

Y para rastrear la reproducción puedes:

"seguir los cambios en la posición de la cabeza lectora en un objeto AVPlayer" mediante el uso de addPeriodicTimeObserverForInterval: cola: usingBlock: o addBoundaryTimeObserverForTimes: cola: usingBlock: .

El ejemplo es de Apple:

// Assume a property: @property (retain) id playerObserver;

Float64 durationSeconds = CMTimeGetSeconds([<#An asset#> duration]);
CMTime firstThird = CMTimeMakeWithSeconds(durationSeconds/3.0, 1);
CMTime secondThird = CMTimeMakeWithSeconds(durationSeconds*2.0/3.0, 1);
NSArray *times = [NSArray arrayWithObjects:[NSValue valueWithCMTime:firstThird], [NSValue valueWithCMTime:secondThird], nil];

self.playerObserver = [<#A player#> addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
    // Passing NULL for the queue specifies the main queue.

    NSString *timeDescription = (NSString *)CMTimeCopyDescription(NULL, [self.player currentTime]);
    NSLog(@"Passed a boundary at %@", timeDescription);
    [timeDescription release];
}];
Todd Hopkinson
fuente
Podría lanzar una bandera booleana para verificar el estado de la reproducción.
moonman239
Las notificaciones pueden causar problemas si cambia el elemento del jugador, por ejemplo, con -replaceCurrentItemWithPlayerItem:, y no lo maneja correctamente. Una forma más confiable para mí es rastrear AVPlayerel estado usando KVO. Vea mi respuesta a continuación para obtener más detalles: stackoverflow.com/a/34321993/3160561
maxkonovalov
2
¿También necesita notificación de error? AVPlayerItemFailedToPlayToEndTimeNotification
ToolmakerSteve
332

Puedes saber que está jugando usando:

AVPlayer *player = ...
if ((player.rate != 0) && (player.error == nil)) {
    // player is playing
}

Extensión Swift 3:

extension AVPlayer {
    var isPlaying: Bool {
        return rate != 0 && error == nil
    }
}
maz
fuente
29
No necesariamente, no maneja situaciones en las que el jugador se detiene debido a un error en el archivo. Algo que descubrí por las malas ...
Dermot
7
Como dijo Dermot, si intentas jugar algo mientras estás en modo avión, la tasa de AVPlayer aún se establece en 1.0, ya que implica la intención de jugar.
phi
4
El AVPlayer tiene una propiedad de error, solo verifique que no sea nulo y que la tasa no sea 0 :)
James Campbell
23
Tenga en cuenta que la respuesta se ha actualizado para evitar el escenario @Dermot. Entonces este realmente funciona.
jomafer
2
No funcionará cuando se reproduzcan secuencias de video al revés ( - [AVPlayer setRate:-1.0]), porque -1.0es menor que 0.
Julian F. Weinert
49

En iOS10, hay una propiedad incorporada para esto ahora: timeControlStatus

Por ejemplo, esta función reproduce o pausa el avPlayer según su estado y actualiza el botón de reproducción / pausa de manera apropiada.

@IBAction func btnPlayPauseTap(_ sender: Any) {
    if aPlayer.timeControlStatus == .playing {
        aPlayer.pause()
        btnPlay.setImage(UIImage(named: "control-play"), for: .normal)
    } else if aPlayer.timeControlStatus == .paused {
        aPlayer.play()
        btnPlay.setImage(UIImage(named: "control-pause"), for: .normal)
    }
}

En cuanto a su segunda pregunta, para saber si avPlayer llegó al final, lo más fácil sería configurar una notificación.

NotificationCenter.default.addObserver(self, selector: #selector(self.didPlayToEnd), name: .AVPlayerItemDidPlayToEndTime, object: nil)

Cuando llega al final, por ejemplo, puede rebobinar hasta el principio del video y restablecer el botón Pausa a Reproducir.

@objc func didPlayToEnd() {
    aPlayer.seek(to: CMTimeMakeWithSeconds(0, 1))
    btnPlay.setImage(UIImage(named: "control-play"), for: .normal)
}

Estos ejemplos son útiles si está creando sus propios controles, pero si usa un AVPlayerViewController, los controles vienen integrados.

Travis M.
fuente
Fácil de usar para iOS 10+
technerd
2
@objc func didPlayToEnd () para Swift 4.2
Sean Stayns
21

rateNO es la forma de comprobar si se está reproduciendo un vídeo (podría bloquearse). De documentación de rate:

Indica la velocidad de reproducción deseada; 0.0 significa "en pausa", 1.0 indica el deseo de jugar al ritmo natural del elemento actual.

Palabras clave "deseo de reproducir": una tasa de 1.0no significa que el video se esté reproduciendo.

La solución desde iOS 10.0 es usar lo AVPlayerTimeControlStatusque se puede observar en la AVPlayer timeControlStatuspropiedad.

La solución anterior a iOS 10.0 (9.0, 8.0, etc.) es desarrollar su propia solución. Una tasa de 0.0significa que el video está en pausa. Cuando rate != 0.0significa que el video se está reproduciendo o está bloqueado.

Puede averiguar la diferencia observando el tiempo del jugador a través de: func addPeriodicTimeObserver(forInterval interval: CMTime, queue: DispatchQueue?, using block: @escaping (CMTime) -> Void) -> Any

El bloque devuelve el tiempo del jugador actual CMTime, por lo que una comparación de lastTime(el tiempo que se recibió por última vez del bloque) y currentTime(el tiempo que el bloque acaba de informar) dirá si el jugador está jugando o está estancado. Por ejemplo, si lastTime == currentTimey rate != 0.0, entonces el jugador se ha estancado.

Como han señalado otros, averiguar si la reproducción ha finalizado se indica mediante AVPlayerItemDidPlayToEndTimeNotification.

kgaidis
fuente
18

Una alternativa más confiable NSNotificationes agregarse como observador a la ratepropiedad del jugador .

[self.player addObserver:self
              forKeyPath:@"rate"
                 options:NSKeyValueObservingOptionNew
                 context:NULL];

Luego, verifique si el nuevo valor para la tasa observada es cero, lo que significa que la reproducción se ha detenido por algún motivo, como llegar al final o estancarse debido a que el búfer está vacío.

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change
                       context:(void *)context {
    if ([keyPath isEqualToString:@"rate"]) {
        float rate = [change[NSKeyValueChangeNewKey] floatValue];
        if (rate == 0.0) {
            // Playback stopped
        } else if (rate == 1.0) {
            // Normal playback
        } else if (rate == -1.0) {
            // Reverse playback
        }
    }
}

Por rate == 0.0ejemplo, para saber qué causó exactamente la detención de la reproducción, puede realizar las siguientes comprobaciones:

if (self.player.error != nil) {
    // Playback failed
}
if (CMTimeGetSeconds(self.player.currentTime) >=
    CMTimeGetSeconds(self.player.currentItem.duration)) {
    // Playback reached end
} else if (!self.player.currentItem.playbackLikelyToKeepUp) {
    // Not ready to play, wait until enough data is loaded
}

Y no olvide hacer que su reproductor se detenga cuando llegue al final:

self.player.actionAtItemEnd = AVPlayerActionAtItemEndPause;

maxkonovalov
fuente
Creo que también necesito notificación de no llegar al final - AVPlayerItemFailedToPlayToEndTimeNotification. Si ocurre este error, nunca llegará a la hora de finalización. O leyendo la respuesta de maz, agregue a su código una marca de verificación player.error != nil, en cuyo caso la reproducción ha "terminado" debido a un error.
ToolmakerSteve
Por cierto, ¿qué le pareció "no confiable" sobre el uso de NSNotification AVPlayerItemDidPlayToEndTimeNotification?
ToolmakerSteve
ToolmakerSteve, gracias por su nota, agregó la verificación de errores a mi respuesta. Para notificaciones no confiables: me encontré con el problema mientras implementaba vistas de reproductor repetidas y reutilizables en la vista de colección, y KVO dejó las cosas más claras, ya que permitió mantenerlo todo más local sin que las notificaciones de los diferentes jugadores interfirieran entre sí.
maxkonovalov
17

Para Swift :

AVPlayer :

let player = AVPlayer(URL: NSURL(string: "http://www.sample.com/movie.mov"))
if (player.rate != 0 && player.error == nil) {
   println("playing")
}

Actualización : la
player.rate > 0condición cambió a player.rate != 0porque si el video se reproduce al revés puede ser negativo gracias a Julian por señalarlo.
Nota : Esto puede verse igual que la respuesta anterior (Maz) pero en Swift '! Player.error' me estaba dando un error de compilador, por lo que debe verificar el error usando 'player.error == nil' en Swift. (Porque la propiedad de error no es de tipo 'Bool')

AVAudioPlayer:

if let theAudioPlayer =  appDelegate.audioPlayer {
   if (theAudioPlayer.playing) {
       // playing
   }
}

AVQueuePlayer:

if let theAudioQueuePlayer =  appDelegate.audioPlayerQueue {
   if (theAudioQueuePlayer.rate != 0 && theAudioQueuePlayer.error == nil) {
       // playing
   }
}
Aks
fuente
2
AVPlayer! = AVAudioPlayer
quemeful
2
Advertencias: todo lo mencionado anteriormente en la respuesta que apareció dos años antes de esta.
Dan Rosenstark
1
@Yar, sé que también voté a favor de la respuesta anterior, pero esto es para Swift y en Swift '! Player.error' no funcionaba para mí, solo está permitido para tipos bool, así que agregué la respuesta, voté a favor de tu comentario por error para responder usando esta aplicación de desbordamiento de pila :)
Aks
Mi preocupación es que no sé qué tan bien funciona realmente el error player.error que no es nil.
Dan Rosenstark
1
No funcionará cuando se reproduzcan secuencias de video al revés ( player.rate = -1.0), porque -1.0 es menor que 0.
Julian F. Weinert
9

Extensión rápida basada en la respuesta de maz

extension AVPlayer {

    var isPlaying: Bool {
        return ((rate != 0) && (error == nil))
    }
}
Mark Bridges
fuente
6

La versión Swift de la respuesta de maxkonovalov es esta:

player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.New, context: nil)

y

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    if keyPath == "rate" {
        if let rate = change?[NSKeyValueChangeNewKey] as? Float {
            if rate == 0.0 {
                print("playback stopped")
            }
            if rate == 1.0 {
                print("normal playback")
            }
            if rate == -1.0 {
                print("reverse playback")
            }
        }
    }
}

¡Gracias maxkonovalov!

Sr. Stanev
fuente
Hola Sr. Stanev, gracias por su respuesta. Esto no me funciona (¿no imprime nada en la consola?)
Cesare
No importa, funciona, ¡mi jugador lo era nil! Pero necesito saber cuándo comienza la vista (no termina). ¿Alguna forma de hacer esto?
Cesare
3

Actualmente, con swift 5, la forma más fácil de comprobar si el reproductor está reproduciendo o en pausa es comprobar la variable .timeControlStatus .

player.timeControlStatus == .paused
player.timeControlStatus == .playing
azemi
fuente
1

La respuesta es el objetivo C

if (player.timeControlStatus == AVPlayerTimeControlStatusPlaying) {
    //player is playing
}
else if (player.timeControlStatus == AVPlayerTimeControlStatusPaused) {
    //player is pause
}
else if (player.timeControlStatus == AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate) {
    //player is waiting to play
}
iOS Lifee
fuente
0
player.timeControlStatus == AVPlayer.TimeControlStatus.playing
Noda Hikaru
fuente
1
Esta respuesta proporcionada puede ser correcta, pero podría beneficiarse de una explicación . Las respuestas de solo código no se consideran respuestas "buenas". Aquí hay algunas pautas para ¿Cómo escribo una buena respuesta? . De revisión .
MyNameIsCaleb