ios - Cambio de PlayerItem de AVPlayer en UITableView
objective-c multithreading (3)
Tengo una UITableView
contiene una cantidad de videos para jugar cuando se desplaza. A medida que las celdas de tableView se reutilizan, solo AVPlayer
un AVPlayer
para cada fila, nunca. Cuando se reutiliza una celda, simplemente cambio el PlayerItem
del PlayerItem
de la célula llamando [self.player replaceCurrentItemWithPlayerItem:newItem];
. Esto se está llamando indirectamente en tableView:cellForRowAtIndexPath
. Cuando se desplaza hacia abajo, hay un retraso notable cuando ocurre la reutilización. Con un proceso de eliminación, he llegado a la conclusión de que el retraso es causado por replaceCurrentItemWithPlayerItem
, incluso antes de que empiece a reproducirse. Al eliminar esta única línea de código (lo que impide que el jugador obtenga un nuevo video), el retraso desaparece.
Lo que he tratado de arreglar:
Tengo una UITableViewCell
personalizada para reproducir estos videos, y he creado un método dentro de estos para inicializar con la nueva información de un objeto. IE, en cellForRowAtIndexPath:
[cell initializeNewObject:newObject];
para realizar el siguiente método:
//In CustomCell.m
-(void)initializeNewObject:(CustomObject*)newObject
{ /*...*/
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
AVPlayerItem *xPlayerItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:newObject.url]];
AVPlayer *dummy = self.player;
[dummy replaceCurrentItemWithPlayerItem:xPlayerItem];
dispatch_async(dispatch_get_main_queue(), ^{
self.player = dummy;
playerItem = xPlayerItem;
}
}/*...*/
}
Al ejecutar esto, obtengo el mismo resultado que si eliminara por completo la llamada para reemplazar el artículo. Aparentemente, esta función no se puede enhebrar. No estoy completamente seguro de lo que esperaba de esto. Me imagino que necesito una copy
limpia del AVPlayer
para que esto funcione, pero después de buscar un poco, encontré varios comentarios que dicen que replaceCurrentItemWithPlayerItem:
no se puede llamar en un hilo separado, lo que no tiene sentido para mí. Sé que los elementos de UI nunca deberían manejarse en otros hilos que el hilo principal / IU, pero nunca imaginaría que replaceCurrentItemWithPlayerItem
a esta categoría.
Ahora estoy buscando una manera de cambiar el elemento de un AVPlayer
sin el retraso, pero no puedo encontrar ninguno. Espero haber entendido mal el enrutamiento de esta función, y que alguien me corrija ...
EDITAR: Ahora me han informado que esta llamada ya está enhebrada, y que esto no debería suceder realmente. Sin embargo, no veo otra explicación. A continuación está mi cellForRowAtIndexPath:
Está dentro de una UITableView
personalizada con delegados configurados en self
(así que self == tableView
)
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = [self dequeueReusableCellWithIdentifier:kCellIdentifier];
if(!cell)
cell = [[[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil] objectAtIndex:0];
//Array ''data'' contains all objects to be shown. The objects each have titles, url''s etc.
CustomVideoObject *currentObject = [data objectAtIndex:indexPath.row];
//When a cell later starts playing, I store a pointer to the cell in this tableView named ''playing''
//After a quick scroll, the dequeueing cell might be the cell currently playing - resetting
if(playing == cell)
playing = nil;
//This call will insert the correct URL, title, etc for the new video, in the custom cell
[cell initializeNewObject:currentObject];
return cell;
}
initializeNewObject
que actualmente está "funcionando", sin embargo, está rezagado (esto está dentro de CustomCell.m
:
-(void)initializeNewObject:(CustomObject*)o
{
//If this cell is being dequeued/re-used, its player might still be playing the old file
[self.player pause];
self.currentObject = o;
/*
//Setting text-variables for title etc. Removed from this post, but by commenting them out in the code, nothing improves.
*/
//Replace the old playerItem in the cell''s player
NSURL *url = [NSURL URLWithString:self.currentObject.url];
AVAsset *newAsset = [AVAsset assetWithURL:url];
AVPlayerItem *newItem = [AVPlayerItem playerItemWithAsset:newAsset];
[self.player replaceCurrentItemWithPlayerItem:newItem];
//The last line above, replaceCurrentItemWithPlayerItem:, is the ''bad guy''.
//When commenting that one out, all lag is gone (of course, no videos will be playing either)
//The lag still occurs even if I never call [self.player play], which leads me
//to believe that nothing after this function can cause the lag
self.isPlaying = NO;
}
El retraso ocurre en el mismo lugar cada vez. Al desplazarnos hacia abajo podemos ver que el retraso corto ocurre cuando la parte superior de la celda inferior llega al centro de la pantalla. Supongo que esto no significa nada para ti, pero está claro que el desfase se está produciendo en el mismo lugar cada vez, es decir, cuando una nueva célula está desmantelando. Después de cambiar el playerItem del AVPlayer
, no hago nada . No empiezo a reproducir el video hasta más tarde. replaceCurrentItemWithPlayerItem:
está causando este retraso notable. La documentación indica que está cambiando el elemento en otro hilo, pero algo en ese método está deteniendo mi UI.
Me dijeron que use Time Profiler en Instruments para descubrir qué es, pero no tengo idea de cómo hacerlo. Al ejecutar el generador de perfiles y al desplazarse de vez en cuando, este es el resultado (imagen) . Los picos de cada grupo de gráficos es el rezago del que estoy hablando. El único pico extremo es (creo) cuando me desplacé hacia abajo y toqué la barra de estado para desplazarme a la parte superior. No sé cómo interpretar el resultado. Busqué en la pila para replace
y encontré al bastardo. Se llama aquí desde dentro de Main Thread
(en la parte superior), lo que tiene sentido, con un "tiempo de ejecución" de 50 ms, que no tengo ni idea de qué pensar. La proporción relevante del gráfico es de un intervalo de tiempo de aproximadamente 1 minuto y medio. En comparación, al comentar esa línea única y ejecutar Time Profiler nuevamente, los picos del gráfico son significativamente más bajos (el pico más alto al 13%, en comparación con lo que hay en la imagen, que probablemente sea alrededor del 60-70%).
No sé qué buscar ...
Debe perfilar su aplicación utilizando el perfil de tiempo para ver dónde se está produciendo el retraso.
La documentación para replaceCurrentItemWithPlayerItem:
indica claramente que se ejecuta de forma asíncrona, por lo que no debería ser la fuente de su salto de cola principal.
El reemplazo del artículo se produce de forma asíncrona; observe la propiedad currentItem para saber cuándo se producirá el reemplazo.
Si aún no puede resolverlo, publique más código y, en particular, al menos su método cellForRowAtIndexPath
.
ACTUALIZAR
Según el comentario de @ joey a continuación, el contenido anterior de esta respuesta ya no es válido. La documentation ya no establece que el método es asincrónico, por lo que puede no serlo.
En su CustomCell.m debe usar miniaturas para mostrar una imagen para el video celular específico, puede agregar la miniatura a un botón dentro de su celda y luego crear un delegado personalizado en CustomCell.h que se invocará cuando toque el nuevo botón creado algo así como:
@class CustomCell;
@protocol CustomCellDelegate <NSObject>
- (void)userTappedPlayButtonOnCell:(CustomCell *)cell;
@end
también en su su .h agregue la acción para el botón:
@interface CustomCell : UITableViewCell
@property (strong, nonatomic) IBOutlet UIButton *playVideoButton;
- (IBAction)didTappedPlayVideo;
- (void)settupVideoWithURL:(NSString *)stringURL;
- (void)removeVideoAndShowPlaceholderImage:(UIImage *)placeholder;
@end
también en "didTappedPlayVideo" haces lo siguiente:
- (void)didTappedPlayVideo{
[self.delegate userTappedPlayButtonOnCell:self];
}
el método: settupVideoWithURL
se usa para settupVideoWithURL
tu reproductor y el método: removeVideoAndShowPlaceholderImage
detendrá el video y hará que AVPlayerItem y AVPlayer sean nulos.
Ahora, cuando el usuario toca una celda, devuelve la llamada al UIViewController donde tienes el tableView, y en el método de delegado debes hacer lo siguiente:
- (void)userTappedPlayButtonOnCell:(CustomCell *)cell{
//check if this is the first time when the user plays a video
if(self.previousPlayedVideoIndexPath){
CustomCell *previousCell = (CustomCell *)[tableView cellForRowAtIndexPath:self.previousPlayedVideoIndexPath];
[previousCell removeVideoAndShowPlaceholderImage: [UIImage imageNamed:@"img.png"]];
}
[cell settupVideoWithURL:@"YOURvideoURL"];
self.previousPlayedVideoIndexPath = cell.indexPath;
}
PD:
En dispositivos más antiguos (casi todos hasta el nuevo iPad Pro) no está indicado tener múltiples instancias de AVPlayer
. También espero que estos fragmentos de código puedan guiarte o ayudarte en tu búsqueda :)
Si realiza un nivel básico de creación de perfiles, creo que puede reducir el problema. Para mí, tuve un problema similar donde replaceCurrentItemWithPlayerItem
estaba bloqueando el hilo de UI. Lo resolví examinando mi código para descubrir qué línea tomaba tiempo. Para mí, la carga de AVAsset estaba tomando tiempo. Así que utilicé el método loadValuesAsynchronouslyForKeys de AVAsset para resolver mi problema.
Por lo tanto, puedes probar lo siguiente:
-(void)initializeNewObject:(CustomObject*)o
{
//If this cell is being dequeued/re-used, its player might still be playing the old file
[self.player pause];
self.currentObject = o;
/*
//Setting text-variables for title etc. Removed from this post, but by commenting them out in the code, nothing improves.
*/
//Replace the old playerItem in the cell''s player
NSURL *url = [NSURL URLWithString:self.currentObject.url];
AVAsset *newAsset = [AVAsset assetWithURL:url];
[newAsset loadValuesAsynchronouslyForKeys:@[@"duration"] completionHandler:^{
AVPlayerItem *newItem = [AVPlayerItem playerItemWithAsset:newAsset];
[self.player replaceCurrentItemWithPlayerItem:newItem];
}];
//The last line above, replaceCurrentItemWithPlayerItem:, is the ''bad guy''.
//When commenting that one out, all lag is gone (of course, no videos will be playing either)
//The lag still occurs even if I never call [self.player play], which leads me
//to believe that nothing after this function can cause the lag
self.isPlaying = NO;
}