AVPlayer deja de reproducirse y no se reanuda de nuevo

En mi aplicación tengo que reproducir files de audio almacenados en un server web. Estoy usando AVPlayer para eso. Tengo todos los controles de reproducción / pausa y todos los delegates y observadores que funcionan perfectamente. Al reproducir pequeños files de audio todo funciona bien.

Cuando se reproduce un file de audio largo, también comienza a reproducirse bien, pero después de algunos segundos el AVPlayer pausa la reproducción (lo más probable es que se AVPlayer el búfer). El problema es que no se reanuda por sí mismo nuevamente. Se mantiene en un estado de pausa y si presiono manualmente el button de reproducción nuevamente, se reproduce suavemente nuevamente.

Quiero saber por qué AVPlayer no se reanuda automáticamente y ¿cómo puedo volver a reanudar el audio sin que el usuario vuelva a presionar el button Reproducir? Gracias.

Sí, se detiene porque el búfer está vacío, por lo que tiene que esperar para cargar más videos. Después de eso, tienes que pedir manualmente comenzar de nuevo. Para resolver el problema seguí estos pasos:

1) Detección: para detectar cuándo el jugador se detuvo utilizo el KVO con la propiedad de velocidad del valor:

 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"rate"] ) { if (self.player.rate == 0 && CMTimeGetSeconds(self.playerItem.duration) != CMTimeGetSeconds(self.playerItem.currentTime) && self.videoPlaying) { [self continuePlaying]; } } } 

Esta condición: CMTimeGetSeconds(self.playerItem.duration) != CMTimeGetSeconds(self.playerItem.currentTime) es detectar la diferencia entre llegar al final del video o detenerse en el medio

2) Espere a que se cargue el video: si continúa jugando directamente, no tendrá suficiente búfer para continuar jugando sin interrupción. Para saber cuándo comenzar, debes observar el valor de playbackLikelytoKeepUp desde el playerItem (aquí uso una biblioteca para observar con bloques pero creo que es el punto):

 -(void)continuePlaying { if (!self.playerItem.playbackLikelyToKeepUp) { self.loadingView.hidden = NO; __weak typeof(self) wSelf = self; self.playbackLikelyToKeepUpKVOToken = [self.playerItem addObserverForKeyPath:@keypath(_playerItem.playbackLikelyToKeepUp) block:^(id obj, NSDictionary *change) { __strong typeof(self) sSelf = wSelf; if(sSelf) { if (sSelf.playerItem.playbackLikelyToKeepUp) { [sSelf.playerItem removeObserverForKeyPath:@keypath(_playerItem.playbackLikelyToKeepUp) token:self.playbackLikelyToKeepUpKVOToken]; sSelf.playbackLikelyToKeepUpKVOToken = nil; [sSelf continuePlaying]; } } }]; } 

¡Y eso es! problema resuelto

Edit: Por cierto, la biblioteca utilizada es libextobjc

Estoy trabajando con files de video, así que hay más en mi código de lo que necesitas, pero la siguiente solución debe pausar al reproductor cuando cuelga, luego revisar cada 0.5 segundo para ver si hemos guardado el buffer lo suficiente como para continuar. Si es así, reinicia el reproductor. Si el jugador se cuelga durante más de 10 segundos sin reiniciar, detendremos al jugador y pediremos disculpas al usuario. Esto significa que necesita los observadores adecuados. El código a continuación funciona bastante bien para mí.

properties definidas / init'd en un file .h o en otro lugar:

 AVPlayer *player; int playerTryCount = -1; // this should get set to 0 when the AVPlayer starts playing NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; 

parcial .m:

 - (AVPlayer *)initializePlayerFromURL:(NSURL *)movieURL { // create AVPlayer AVPlayerItem *videoItem = [AVPlayerItem playerItemWithURL:movieURL]; AVPlayer *videoPlayer = [AVPlayer playerWithPlayerItem:videoItem]; // add Observers [videoItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil]; [self startNotificationObservers]; // see method below // I observe a bunch of other stuff, but this is all you need for this to work return videoPlayer; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { // check that all conditions for a stuck player have been met if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) { if (self.player.currentItem.playbackLikelyToKeepUp == NO && CMTIME_COMPARE_INLINE(self.player.currentTime, >, kCMTimeZero) && CMTIME_COMPARE_INLINE(self.player.currentTime, !=, self.player.currentItem.duration)) { // if so, post the playerHanging notification [self.notificationCenter postNotificationName:PlayerHangingNotification object:self.videoPlayer]; } } } - (void)startNotificationObservers { [self.notificationCenter addObserver:self selector:@selector(playerContinue) name:PlayerContinueNotification object:nil]; [self.notificationCenter addObserver:self selector:@selector(playerHanging) name:PlayerHangingNotification object:nil]; } // playerHanging simply decides whether to wait 0.5 seconds or not // if so, it pauses the player and sends a playerContinue notification // if not, it puts us out of our misery - (void)playerHanging { if (playerTryCount <= 10) { playerTryCount += 1; [self.player pause]; // start an activity indicator / busy view [self.notificationCenter postNotificationName:PlayerContinueNotification object:self.player]; } else { // this code shouldn't actually execute, but I include it as dummyproofing [self stopPlaying]; // a method where I clean up the AVPlayer, // which is already paused // Here's where I'd put up an alertController or alertView // to say we're sorry but we just can't go on like this anymore } } // playerContinue does the actual waiting and restarting - (void)playerContinue { if (CMTIME_COMPARE_INLINE(self.player.currentTime, ==, self.player.currentItem.duration)) { // we've reached the end [self stopPlaying]; } else if (playerTryCount > 10) // stop trying [self stopPlaying]; // put up "sorry" alert } else if (playerTryCount == 0) { return; // protects against a race condition } else if (self.player.currentItem.playbackLikelyToKeepUp == YES) { // Here I stop/remove the activity indicator I put up in playerHanging playerTryCount = 0; [self.player play]; // continue from where we left off } else { // still hanging, not at end // create a 0.5-second delay to see if buffering catches up // then post another playerContinue notification to call this method again // in a manner that attempts to avoid any recursion or threading nightmares playerTryCount += 1; double delayInSeconds = 0.5; dispatch_time_t executeTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(executeTime, dispatch_get_main_queue(), ^{ // test playerTryCount again to protect against changes that might have happened during the 0.5 second delay if (playerTryCount > 0) { if (playerTryCount <= 10) { [self.notificationCenter postNotificationName:PlayerContinueNotification object:self.videoPlayer]; } else { [self stopPlaying]; // put up "sorry" alert } } }); } 

¡Espero eso ayude!

Tuve un problema similar. Tenía algunos files locales que quería reproducir, configuré el AVPlayer y llamé [play player], el reproductor se detiene en el cuadro 0 y no volverá a jugar hasta que vuelva a llamar a la reproducción de forma manual. La respuesta aceptada fue imposible para mí de implementar debido a una explicación defectuosa, luego intenté retrasar la obra y mágicamente funcionó

 [self performSelector:@selector(startVideo) withObject:nil afterDelay:0.2]; -(void)startVideo{ [self.videoPlayer play]; } 

Para los videos web también tuve el problema, lo soluciono usando la respuesta de Wallace.

Al crear AVPlayer, agregue un observador:

 [self.videoItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil]; - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { // check that all conditions for a stuck player have been met if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) { if (self.videoPlayer.currentItem.playbackLikelyToKeepUp == NO && CMTIME_COMPARE_INLINE(self.videoPlayer.currentTime, >, kCMTimeZero) && CMTIME_COMPARE_INLINE(self.videoPlayer.currentTime, !=, self.videoPlayer.currentItem.duration)) { NSLog(@"hanged"); [self performSelector:@selector(startVideo) withObject:nil afterDelay:0.2]; } } 

}

Recuerde eliminar el observador antes de descartar la vista

 [self.videoItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"] 

Creo que usar AVPlayerItemPlaybackStalledNotification para detectar el estancamiento es una mejor manera.

La respuesta aceptada ofrece una posible solución al problema, pero carece de flexibilidad, también es difícil de leer. Aquí hay una solución más flexible.

Agregar observadores:

 //_player is instance of AVPlayer [_player.currentItem addObserver:self forKeyPath:@"status" options:0 context:nil]; [_player addObserver:self forKeyPath:@"rate" options:0 context:nil]; 

Entrenador de animales:

 -(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context { if ([keyPath isEqualToString:@"status"]) { if (_player.status == AVPlayerStatusFailed) { //Possibly show error message or attempt replay from tart //Description from the docs: // Indicates that the player can no longer play AVPlayerItem instances because of an error. The error is described by // the value of the player's error property. } }else if ([keyPath isEqualToString:@"rate"]) { if (_player.rate == 0 && //if player rate dropped to 0 CMTIME_COMPARE_INLINE(_player.currentItem.currentTime, >, kCMTimeZero) && //if video was started CMTIME_COMPARE_INLINE(_player.currentItem.currentTime, <, _player.currentItem.duration) && //but not yet finished _isPlaying) { //instance variable to handle overall state (changed to YES when user triggers playback) [self handleStalled]; } } } 

Magia:

 -(void)handleStalled { NSLog(@"Handle stalled. Available: %lf", [self availableDuration]); if (_player.currentItem.playbackLikelyToKeepUp || // [self availableDuration] - CMTimeGetSeconds(_player.currentItem.currentTime) > 10.0) { [_player play]; } else { [self performSelector:@selector(handleStalled) withObject:nil afterDelay:0.5]; //try again } } 

El "[self availableDuration]" es opcional, pero puede iniciar manualmente la reproducción según la cantidad de video disponible. Puede cambiar la frecuencia con la que el código verifica si hay suficiente video almacenado en el búfer. Si decide utilizar la parte opcional, aquí está la implementación del método:

 - (NSTimeInterval) availableDuration { NSArray *loadedTimeRanges = [[_player currentItem] loadedTimeRanges]; CMTimeRange timeRange = [[loadedTimeRanges objectAtIndex:0] CMTimeRangeValue]; Float64 startSeconds = CMTimeGetSeconds(timeRange.start); Float64 durationSeconds = CMTimeGetSeconds(timeRange.duration); NSTimeInterval result = startSeconds + durationSeconds; return result; } 

No te olvides de la limpieza. Eliminar observadores:

 [_player.currentItem removeObserver:self forKeyPath:@"status"]; [_player removeObserver:self forKeyPath:@"rate"]; 

Y posibles llamadas pendientes para manejar video estancado:

 [UIView cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleStalled) object:nil]; 

Primero observo para la pérdida de reproducción

 NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(playerStalled), name: AVPlayerItemPlaybackStalledNotification, object: videoPlayer.currentItem) 

Luego forzo la continuación de la reproducción

 func playerStalled(note: NSNotification) { let playerItem = note.object as! AVPlayerItem if let player = playerItem.valueForKey("player") as? AVPlayer{ player.play() } } 

Probablemente esta no sea la mejor manera de hacerlo, pero lo estoy usando hasta que encuentre algo mejor 🙂