Pre-buffering para AVQueuePlayer

¿Alguien sabe si AVQueuePlayer comienza a almacenar en búfer el próximo AVPlayerItem cuando el elemento actual está por terminar de reproducirse?

Sé que no hay nada en los documentos que sugiera esto, estoy preguntando sobre todo si alguien ha observado este tipo de comportamiento o no.

Bien, AVQueuePlayer este problema nuevamente y escribí un código para verificar AVQueuePlayer .

La respuesta de jollyCocoa me indicó la dirección correcta al sugerir observar la propiedad de estado en AVPlayerItem . Sin embargo, la documentación no parece señalar que esta propiedad (y su valor AVPlayerItemStatusReadyToPlay en particular) podría estar relacionada con el almacenamiento en búfer.

Sin embargo, la propiedad AVPlayerItem's loadedTimeRanges parece estar más relacionada con el almacenamiento en búfer.

Hacer KVO en esa matriz fue un poco más complicado: el objeto de matriz no cambia, solo lo hacen los artículos, así que recurrí a imprimir su contenido cada segundo.

Lo que descubrí es que unos segundos en el primer elemento de la cola, loadedTimeRanges para el segundo elemento muestra un nuevo CMTimeRange con hora de inicio 0 y una duración pequeña. La duración puede boost hasta 60 o más segundos mientras el elemento anterior continúa reproduciéndose.

Respuesta corta: AVQueuePlayer almacenará en búfer el siguiente AVPlayerItem mientras reproduce el actual.

A partir de iOS 5, parece que AVQueuePlayer ya no almacena previamente los buffers. Pre-buffer la siguiente pista en iOS 4.

No estoy seguro de por qué ha habido un cambio o si es deliberado por parte de Apple o simplemente un error. En cualquier caso, es un dolor y les enviaré un error al respecto.

Encontré un trabajo alrededor !!! Después de muchas horas de búsqueda y pruebas fallidas, encontré una solución que funciona en iOS 5 y superior.

Esto reproducirá los archivos sin demora en el medio. No es tan conveniente si desea cambiar de un archivo a otro, pero sin duda lo preparará todo para usted. Todavía es posible realizar un seguimiento de dónde comienza cada archivo, al agregar AVURLAsset, mantenga la variable CMTime, algo así como:

 NSValue *toJumpTo = [NSValue valueWithCMTime:composition.duration]; [array addObject:toJumpTo]; 

y luego simplemente revise el arreglo, verificando el valor de tiempo actual y comparándolo usando CMTimeCompare.

Editar: se encontró en http://www.developers-life.com/avplayer-looping-video-without-hiccupdelays.html pero como Stefan Kendall mencionó en los comentarios, ahora es un sitio de spam. ¡Pensé en dejar el enlace para fines de atribución, pero no lo visite!

He estado experimentando con AVQueuePlayer usando películas proporcionadas por un servidor. En esta prueba en particular, configuré un QueuePlayer con AVPlayerItems inicializado con URL para dos activos de video diferentes de diferentes tipos. Uno es un archivo .mp4 (descarga progresiva) y el otro un archivo http-streaming de .m3u8. Funciona un poco funky, debo decir.

Dependiendo del orden en el que hago cola en los archivos, obtengo un comportamiento bastante diferente. Si comienzo con el archivo .mp4, el AVPlayerLayer se queda en blanco y en silencio cuando el nextItem inicia el reproductor (el archivo .m3u8). Si cambio el orden y comienzo con el archivo m3u8, AVPlayerLayer queda en blanco pero el audio del segundo archivo se reproduce bien.

Mi impresión, por lo que he visto hasta ahora, es que AVQueuePlayer NO comienza a descargar el siguiente AVPlayerItem antes de que el AVPlayerItem actual haya terminado de reproducirse. Pero podría estar equivocado.

Para continuar, supongo …

Editar: Ok, entonces hice algunas pruebas más y parece que los siguientes elementos comienzan a descargarse antes de que el último elemento termine de reproducirse. Ver la publicación a continuación. Golpee el “NO” …

Edit2: Agregando mi explicación aquí en su lugar, ya que el comentario me dejó sin opciones de formateo. (Se recomienda la mejor práctica para esto, si esta es una mala forma de manejar las actualizaciones de respuesta)

De todos modos, itere a través de mis elementos Array añadiendo observación de la propiedad de estado usando KVO …

 [playerItem addObserver: self forKeyPath:@"status" options: 0 context: NULL]; 

También configuré mi controlador como el observador de AVPlayerItem Notification AVPlayerItemDidPlayToEndTimeNotification :

 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(playerItemDidReachEnd:) name: AVPlayerItemDidPlayToEndTimeNotification object: nil]; 

Ahora podría registrar el cambio de estado de AVPlayerItem, y resulta que el siguiente AVPlayerItem en la cola se registra con un estado de cambio AVPlayerItemStatusReadyToPlay antes de que el elemento actual termine la reproducción.

Mi conclusión es que el siguiente elemento en la línea comienza a descargarse antes de que termine el elemento actual.

¡Sin embargo! Creé una matriz con AVPlayerItems que uso para crear mi reproductor. Al leer los documentos de AVFoundation en AVAssets, me da la impresión de que descargan sus activos de forma asíncrona, pero todavía no estoy seguro de cuándo estas descargas están siendo iniciadas por IAPlayerItem. Todavía tengo un poco de comportamiento funky cuando agrego el archivo .m3u8 a la cola, lo que me hace preguntarme si estos activos comienzan a descargarse en el momento de la creación (sin embargo, me parece un poco extraño).

Hice una demostración simple para usted y para otras personas que desean reducir el tiempo de almacenamiento en búfer de cada video desde la URL.

NOTA: No sé si esto es correcto o no, pero funciona perfecto para mí sin ningún problema. Si alguien sabe más o desea mejorar el código para obtener mejores resultados, bienvenido a cambiar mi respuesta.

En el siguiente código, el primer video toma el tiempo de almacenamiento en búfer normal. El siguiente video se iniciará automáticamente cuando finalice el video actual y podrá deslizar el dedo hacia la izquierda y hacia la derecha para mover el video siguiente y anterior.

Sigue los pasos a continuación.

1) En el archivo videoPlayerVC.h .

 #import  #import  @interface videoPlayerVC : UIViewController { AVPlayerViewController *avPlyrViewController; AVPlayerItem *currentAVPlyrItem; int currentVideoNO; // For current video track. NSMutableArray *arrOfAVPItems; NSMutableArray *arrOfPlyer; } 

2) En el archivo videoPlayerVC.m

Aquí puede comenzar a reproducir video haciendo clic en el botón. A continuación se muestra el método de acción del botón.

 -(void)clickOnPlayVideosImage:(UIButton *)sender { // In my App. all video URLs is up to 15 second. currentVideoNO = 0; NSMutableArray *arrForVideoURL = [[NSMutableArray alloc]initWithObjects: [NSURL URLWithString:@"http://videos/url1.mov"], [NSURL URLWithString:@"http://videos/url2.mov"], [NSURL URLWithString:@"http://videos/url3.mov"], [NSURL URLWithString:@"http://videos/url3.mov"], [NSURL URLWithString:@"http://videos/url4.mov"], [NSURL URLWithString:@"http://videos/url5.mov"], nil]; AVPlayerItem *thePlayerItemA = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:0]]; AVPlayerItem *thePlayerItemB = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:1]]; AVPlayerItem *thePlayerItemC = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:2]]; AVPlayerItem *thePlayerItemD = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:3]]; AVPlayerItem *thePlayerItemE = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:4]]; AVPlayerItem *thePlayerItemF = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:5]]; if(arrOfAVPItems.count > 0) [arrOfAVPItems removeAllObjects]; arrOfAVPItems = nil; if(arrOfPlyer.count > 0) [arrOfPlyer removeAllObjects]; arrOfPlyer = nil; arrOfPlyer = [[NSMutableArray alloc] init]; arrOfAVPItems = [NSMutableArray arrayWithObjects:thePlayerItemA, thePlayerItemB, thePlayerItemC, thePlayerItemD, thePlayerItemE, thePlayerItemF, nil]; // Add All items in the Array for(AVPlayerItem *myPlyrItem in arrOfAVPItems) { AVPlayer *videoPlayerNext = [AVPlayer playerWithPlayerItem:myPlyrItem]; /// Add item in the player [videoPlayerNext play]; [videoPlayerNext pause]; [arrOfPlyer addObject:videoPlayerNext]; /// Make Array of "AVPlayer" just reduce buffering of each video. } avPlyrViewController = [AVPlayerViewController new]; avPlyrViewController.delegate = self; avPlyrViewController.player = (AVPlayer *)arrOfPlyer[0]; // Add first player from the Array. avPlyrViewController.showsPlaybackControls = YES; avPlyrViewController.allowsPictureInPicturePlayback = YES; avPlyrViewController.videoGravity = AVLayerVideoGravityResizeAspect; [self presentViewController:avPlyrViewController animated:YES completion:^{ [self performSelector:@selector(playVideos) withObject:nil afterDelay:1];/// call method after one second for play video. UISwipeGestureRecognizer * swipeleft=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeleftToNextVideo:)]; swipeleft.direction=UISwipeGestureRecognizerDirectionLeft; [avPlyrViewController.view addGestureRecognizer:swipeleft]; // Add left swipe for move on next video UISwipeGestureRecognizer * swiperight=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeleftToPerviousVideo:)]; swiperight.direction=UISwipeGestureRecognizerDirectionRight; [avPlyrViewController.view addGestureRecognizer:swiperight]; // Add right swipe for move on previous video }]; } 

Ahora escribe el código del método playVideos .

 #pragma mark - AVPlayer Methods - -(void)playVideos { [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:currentAVPlyrItem]; // remove current "AVPlayerItem" from the notification observe. if(arrOfAVPItems.count > 0) { currentAVPlyrItem = (AVPlayerItem *)arrOfAVPItems[currentVideoNO]; // Add "AVPlayerItem" from the array. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:currentAVPlyrItem]; // Add notification observer to indication current "AVPlayerItem" is finish. // pause and nil previous player if available. [avPlyrViewController.player pause]; avPlyrViewController.player = nil; avPlyrViewController.player = (AVPlayer *)arrOfPlyer[currentVideoNO]; // add new player from the array [avPlyrViewController.player.currentItem seekToTime:kCMTimeZero]; // set for start video on initial position. [avPlyrViewController.player play]; // Play video } } 

Observador de notificación para indicar que el video ha finalizado.

 - (void)playerItemDidReachEnd:(NSNotification *)notification { NSLog(@"IT REACHED THE END"); [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:currentAVPlyrItem]; // remove current "AVPlayerItem" from the notification observe. [self swipeleftToNextVideo:nil]; // Call method for next video. } 

Método para reproducir el siguiente video

 -(void)swipeleftToNextVideo:(UISwipeGestureRecognizer*)gestureRecognizer { currentVideoNO++; if(currentVideoNO > (arrOfAVPItems.count -1)) currentVideoNO = 0; NSLog(@"current - %d and last - %d", currentVideoNO, (int)(arrOfAVPItems.count -1)); [self playVideos]; } 

Método para jugar video anterior.

 -(void)swipeleftToPerviousVideo:(UISwipeGestureRecognizer*)gestureRecognizer { currentVideoNO--; if(currentVideoNO < 0) currentVideoNO = (int)(arrOfAVPItems.count -1); NSLog(@"current - %d and last - %d", currentVideoNO, (int)(arrOfAVPItems.count -1)); [self playVideos]; } 

Sigue estos pasos. Si tiene alguna duda, haga un comentario por favor.