ios - MĂșltiples videos con AVPlayer
objective-c avcomposition (3)
Actualización - https://youtu.be/7QlaO7WxjGg
Aquí está su respuesta utilizando una vista de colección como ejemplo, que se reproducirá de 8 en una (tenga en cuenta que no es necesario ningún tipo de gestión de memoria, puede usar ARC):
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = (UICollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifier forIndexPath:indexPath];
// Enumerate array of visible cells'' indexPaths to find a match
if ([self.collectionView.indexPathsForVisibleItems
indexOfObjectPassingTest:^BOOL(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
return (obj.item == indexPath.item);
}]) dispatch_async(dispatch_get_main_queue(), ^{
[self drawLayerForPlayerForCell:cell atIndexPath:indexPath];
});
return cell;
}
- (void)drawPosterFrameForCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
[self.imageManager requestImageForAsset:AppDelegate.sharedAppDelegate.assetsFetchResults[indexPath.item]
targetSize:AssetGridThumbnailSize
contentMode:PHImageContentModeAspectFill
options:nil
resultHandler:^(UIImage *result, NSDictionary *info) {
cell.contentView.layer.contents = (__bridge id)result.CGImage;
}];
}
- (void)drawLayerForPlayerForCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
cell.contentView.layer.sublayers = nil;
[self.imageManager requestPlayerItemForVideo:(PHAsset *)self.assetsFetchResults[indexPath.item] options:nil resultHandler:^(AVPlayerItem * _Nullable playerItem, NSDictionary * _Nullable info) {
dispatch_sync(dispatch_get_main_queue(), ^{
if([[info objectForKey:PHImageResultIsInCloudKey] boolValue]) {
[self drawPosterFrameForCell:cell atIndexPath:indexPath];
} else {
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:[AVPlayer playerWithPlayerItem:playerItem]];
[playerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[playerLayer setBorderColor:[UIColor whiteColor].CGColor];
[playerLayer setBorderWidth:1.0f];
[playerLayer setFrame:cell.contentView.layer.bounds];
[cell.contentView.layer addSublayer:playerLayer];
[playerLayer.player play];
}
});
}];
}
El método drawPosterFrameForCell coloca una imagen donde no se puede reproducir un video porque está almacenado en iCloud, y no en el dispositivo.
De todos modos, este es el punto de partida; una vez que comprenda cómo funciona esto, puede hacer todas las cosas que desee, sin ninguno de los fallos técnicos, en cuanto a la memoria, que describió.
Estoy desarrollando una aplicación de iOS para iPad que necesita reproducir videos en alguna parte de la pantalla. Tengo varios archivos de video que deben reproducirse uno detrás de otro en un orden que no se da en tiempo de compilación. Debe verse como si fuera solo un video en reproducción. Está bien que, al pasar de un video al siguiente, se produzca una demora en la que se muestre el último o el primer fotograma de los dos videos, pero no debería haber pantallas parpadeantes o blancas sin contenido. Los videos no contienen audio. Es importante tener en cuenta el uso de la memoria. Los videos tienen una resolución muy alta y se pueden reproducir varias secuencias de video una al lado de la otra al mismo tiempo.
Para obtener esto, he intentado algunas soluciones por ahora. Se enumeran a continuación:
1. AVPlayer con AVComposition con todos los videos que contiene
En esta solución, tengo un AVPlayer que se usará solo en AVPlayerItem creado utilizando una composición AV que contiene todos los videos colocados uno al lado del otro. Cuando voy a videos específicos, busco el momento en la composición en la que comienza el siguiente video. El problema con esta solución es que al buscar el reproductor rápidamente se mostrarán algunos de los cuadros que está buscando, lo que no es aceptable. Parece que no hay forma de saltar directamente a un tiempo específico en la composición. Traté de resolver esto haciendo una imagen del último fotograma en el video que acaba de terminar, luego lo muestro frente al AVPLayer mientras busco, y finalmente lo elimino después de que se hizo la búsqueda. Estoy creando la imagen usando AVAssetImageGenerator, pero por alguna razón la calidad de la imagen no es la misma que la del video, por lo que hay cambios notables al mostrar y ocultar la imagen sobre el video. Otro problema es que AVPlayer usa mucha memoria porque un solo AVPlayerItem contiene todos los videos.
2. AVPlayer con múltiples AVPlayerItems
Esta solución utiliza un AVPlayerItem para cada video y reemplaza el elemento de AVPlayer cuando se cambia a un nuevo video. El problema con esto es que al cambiar el elemento de un AVPlayer, se mostrará una pantalla en blanco durante un breve período de tiempo mientras se carga el nuevo elemento. Para solucionar esto, la solución es colocar una imagen en el frente con el último cuadro mientras se puede usar la carga, pero aún así el problema es que la calidad de la imagen y el video es diferente y notable.
3. Dos AVPlayers uno encima del otro que se turnan para jugar AVPlayerItem
La siguiente solución que probé fue tener dos AVPlayer uno encima del otro que tomarían turnos para jugar AVPlayerItems. Entonces, cuando el jugador esté listo para jugar, permanecerá en el último cuadro del video. El otro AVPlayer se colocará al frente (con su elemento establecido en nulo, por lo que es transparente), y el siguiente AVPlayerItem se insertará en ese AVPlayer. Tan pronto como se cargue, comenzará a reproducirse y la ilusión de una transacción fluida entre los dos videos estará intacta. El problema con esta solución es el uso de la memoria. En algunos casos, necesito reproducir dos videos en la pantalla al mismo tiempo, lo que resultará en 4 AVPlayers con un AVPlayerItem cargado al mismo tiempo. Esto es simplemente demasiada memoria ya que los videos pueden tener una resolución muy alta.
¿Alguien tiene algunas ideas, sugerencias, comentarios o algo relacionado con el problema general y las soluciones probadas publicadas anteriormente?
Entonces el proyecto ahora está en vivo en la App Store y es hora de volver a este hilo y compartir mis hallazgos y revelar lo que terminé haciendo.
Lo que no funcionó
La primera opción en la que utilicé una gran AVComposition con todos los videos no era lo suficientemente buena ya que no había forma de saltar a un tiempo específico en la composición sin tener un pequeño problema de depuración. Además, tuve un problema al pausar el video exactamente entre dos videos en la composición, ya que la API no podía proporcionar una garantía de fotograma para la pausa.
El tercero de tener dos AVPlayers y dejarlos tomar turnos funcionó muy bien en la práctica. Especialmente en iPad 4 o iPhone 5. Los dispositivos con menor cantidad de RAM eran un problema, ya que tener varios videos en la memoria al mismo tiempo consumía demasiada memoria. Especialmente porque tuve que lidiar con videos de muy alta resolución.
Lo que terminé haciendo
Bueno, a la izquierda estaba la opción número 2. Crear un AVPlayerItem para un video cuando sea necesario y alimentarlo al AVPlayer. Lo bueno de esta solución fue el consumo de memoria. Al crear los AVPlayerItems de forma lenta y descartarlos en el momento en que ya no los necesitaban, el consumo de memoria se reducía al mínimo, lo que era muy importante para admitir dispositivos más antiguos con RAM limitada. El problema con esta solución era que al pasar de un video a otro, aparecía una pantalla en blanco en un momento rápido mientras el siguiente video se cargaba en la memoria. Mi idea de arreglar esto fue poner una imagen detrás del AVPlayer que se mostraría cuando el jugador estaba almacenando. Sabía que necesitaba imágenes perfectas de píxel a píxel con el video, así que capturé imágenes que eran copias exactas del último y primer fotograma de los videos. Esta solución funcionó muy bien en la práctica.
El problema con esta solución
Sin embargo, tuve el problema de que la posición de la imagen dentro de UIImageView no era la misma que la posición del video dentro del AVPlayer si el video / imagen no era su tamaño original o un módulo 4 de escala de eso. En otras palabras, tuve un problema con la forma en que se manejaban los medios pixeles con UIImageView y AVPlayer. No parecía ser de la misma manera.
Cómo lo arreglé
Probé muchas cosas desde que mi aplicación usaba los videos de forma interactiva y se mostraban en diferentes tamaños. Intenté cambiar el filtro de ampliación y minificationFilter de AVPlayerLayer y CALayer para usar el mismo algoritmo pero realmente no cambié nada. Al final terminé creando una aplicación para iPad que automáticamente podía tomar capturas de pantalla de los videos en todos los tamaños que necesitaba y luego usar la imagen correcta cuando el video se escalaba a un cierto tamaño. Esto dio imágenes que eran pixeles perfectas en todos los tamaños que mostraba un video específico. No es una cadena de herramientas perfecta, pero el resultado fue perfecto.
Reflexión final
La razón principal por la que este problema de posición fue muy visible para mí (y por lo tanto muy importante de resolver), fue porque el contenido de video que mi aplicación está reproduciendo son animaciones dibujadas, donde mucho el contenido está en una posición fija y solo una parte del la imagen se está moviendo. Si todo el contenido se mueve solo un píxel, da un fallo muy visible y desagradable. En WWDC este año discutí este problema con un ingeniero de Apple que es un experto en AVFoundation. Cuando le presenté el problema, su sugerencia básicamente fue ir con la opción 3, pero le expliqué que eso no era posible debido al consumo de memoria y que ya probé esa solución. En ese sentido, dijo que elegí la solución correcta y me pidió que presentara un informe de error para el posicionamiento de UIImage / AVPlayer cuando el video se escala.
Es posible que ya haya analizado esto, pero ¿ha revisado la Documentation AVQueuePlayer
Está diseñado para reproducir AVPlayerItems
en una cola y es una subclase directa de AVPlayer, así que simplemente úsela de la misma manera. Lo configuraste de la siguiente manera:
AVPlayerItem *firstItem = [AVPlayerItem playerItemWithURL: firstItemURL];
AVPlayerItem *secondItem = [AVPlayerItem playerItemWithURL: secondItemURL];
AVQueuePlayer *player = [AVQueuePlayer queuePlayerWithItems:[NSArray arrayWithObjects:firstItem, secondItem, nil]];
[player play];
Si desea agregar nuevos elementos a la cola en tiempo de ejecución simplemente use este método:
[player insertItem:thirdPlayerItem afterItem:firstPlayerItem];
No he probado para ver si esto reduce el problema de parpadeo que ha mencionado, pero parece que este sería el camino a seguir.