web-dev-qa-db-fra.com

Changement de playerItem of AVPlayer dans UITableView

J'ai une UITableView contenant un certain nombre de vidéos à lire lors du défilement. Au fur et à mesure que les cellules de la table sont réutilisées, je n’instancie qu’une seule variable AVPlayer pour chaque ligne. Quand une cellule est réutilisée, je change simplement la PlayerItem du lecteur de la cellule en appelant [self.player replaceCurrentItemWithPlayerItem:newItem];. Ceci est actuellement appelé indirectement dans tableView:cellForRowAtIndexPath. Lorsque vous faites défiler l'écran vers le bas, il y a un décalage notable lorsque la réutilisation se produit. Avec un processus d'élimination, j'ai conclu que le décalage est causé par replaceCurrentItemWithPlayerItem, avant même qu'il ne commence à jouer. Lorsque vous supprimez cette seule ligne de code (empêchant le lecteur d’obtenir une nouvelle vidéo), le retard disparaît.

Ce que j'ai essayé de résoudre:

J'ai une coutume UITableViewCell pour la lecture de ces vidéos et j'ai créé une méthode pour les initialiser avec les nouvelles informations d'un objet. I.E, dans cellForRowAtIndexPath: j’appelle [cell initializeNewObject:newObject]; pour appliquer la méthode suivante:

//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;
        }
    }/*...*/
}

Lors de l'exécution de cette opération, j'obtiens le même résultat que si je supprimais complètement l'appel pour remplacer l'élément. Apparemment, cette fonction ne peut pas être enfilée. Je ne suis pas tout à fait sûr de ce que j'attendais de cela. J'imagine qu'il me faut une copy propre de la AVPlayer pour que cela fonctionne, mais après une recherche un peu, j'ai trouvé plusieurs commentaires indiquant que replaceCurrentItemWithPlayerItem: peut pas être appelé dans un fil séparé, ce qui n'a aucun sens pour moi . Je sais que les éléments d'interface utilisateur ne doivent jamais être gérés dans d'autres threads que principal/UI-thread, mais je n'aurais jamais imaginé que replaceCurrentItemWithPlayerItem tombe dans cette catégorie.

Je cherche maintenant un moyen de changer l'item d'une AVPlayer sans décalage, mais je ne peux pas en trouver. J'espère que j'ai mal compris le filetage de cette fonction et que quelqu'un me corrigera ..

EDIT: Je viens d’être informé que cet appel est déjà threadé et que cela ne devrait pas se produire réellement. Cependant, je ne vois aucune autre explication. Ci-dessous se trouve mon cellForRowAtIndexPath:. C'est à l'intérieur d'une coutume UITableView avec des délégués définis à self (donc 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;
}

La initializeNewObject qui "fonctionne" actuellement, même en retard (c'est à l'intérieur 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;
}

Le décalage se produit exactement au même endroit à chaque fois. Lors du défilement rapide, nous pouvons constater que le court décalage survient lorsque la partie supérieure de la cellule située au bas de l'écran atteint le centre de l'écran. Je suppose que cela ne vous dit rien, mais il est clair que le décalage se produit exactement au même endroit, à savoir quand une nouvelle cellule est en train de retirer de la file d'attente. Après avoir changé le playerItem de la AVPlayer, je fais rien . Je ne commence à jouer la vidéo que plus tard. replaceCurrentItemWithPlayerItem: est est la cause de ce décalage perceptible. La documentation indique qu'il modifie l'élément dans un autre thread, mais quelque chose dans cette méthode bloque mon interface utilisateur.

On m'a dit d'utiliser Time Profiler dans Instruments pour découvrir ce que c'est, mais je ne sais pas comment faire. En exécutant le profileur et en faisant défiler de temps en temps,voici le résultat (image) . Les pics de chaque groupe de graphes correspondent au retard dont je parle. Le seul sommet extrême est (je pense) lorsque j'avais fait défiler vers le bas et tapé sur la barre d'état pour faire défiler vers le haut. Je ne sais pas comment interpréter le résultat. J'ai cherché replace dans la pile et j'ai trouvé le bâtard. Il est appelé ici à partir de Main Thread (en haut), ce qui est logique, avec un "temps d'exécution" de 50 ms, auquel je n'ai aucune idée à penser. La proportion pertinente du graphique est d'une durée d'environ 1 minute et demie. En comparaison, lorsque vous commentez à nouveau cette ligne et que Time Profiler est de nouveau affiché, les pics du graphique sont nettement inférieurs (pic le plus élevé à 13% , comparé à ce qui est sur l'image, qui est probablement autour de 60-70%).

Je ne sais pas quoi chercher ..

39
Sti

Si vous faites un niveau de base de profilage, je pense que vous pouvez réduire le problème. Pour moi, j'ai eu un problème similaire où replaceCurrentItemWithPlayerItem bloquait le thread d'interface utilisateur. Je l'ai résolu en examinant mon code pour savoir quelle ligne prenait du temps. Pour moi, le chargement de AVAsset prenait du temps. J'ai donc utilisé la méthode loadValuesAsynchronouslyForKeys d'AVAsset pour résoudre mon problème. 

Vous pouvez donc essayer ce qui suit:

-(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;
}
1
manishg

Vous devez profiler votre application à l'aide du profileur de temps pour voir où se produit réellement le retard.

La documentation de replaceCurrentItemWithPlayerItem: indique clairement qu'il s'exécute de manière asynchrone. Par conséquent, il ne devrait pas être la source de la rapidité de votre file d'attente principale.

Le remplacement d'article a lieu de manière asynchrone; observez la propriété currentItem pour savoir quand le remplacement aura lieu/aura eu lieu.

Documentation ici

Si vous ne parvenez toujours pas à comprendre, postez plus de code, et en particulier au moins votre méthode cellForRowAtIndexPath. </ S>

UPDATE

Selon le commentaire de @ joey ci-dessous, le contenu précédent de cette réponse n'est plus valide. Le documentation n'indique plus que la méthode est asynchrone, il est donc possible que ce ne soit pas le cas.

0
Dima

Dans votre CustomCell.m, vous devez utiliser les vignettes pour afficher une image pour la vidéo de cellule spécifique; vous pouvez ajouter la vignette à un bouton de votre cellule, puis créer un délégué personnalisé dans CustomCell.h à appeler lorsque vous appuyez sur le nouveau bouton créé quelque chose comme:

@class CustomCell;

@protocol CustomCellDelegate <NSObject>

- (void)userTappedPlayButtonOnCell:(CustomCell *)cell;

@end

également dans votre .h, ajoutez l'action pour le bouton:

@interface CustomCell : UITableViewCell

@property (strong, nonatomic) IBOutlet UIButton *playVideoButton;

 - (IBAction)didTappedPlayVideo;
 - (void)settupVideoWithURL:(NSString *)stringURL;
 - (void)removeVideoAndShowPlaceholderImage:(UIImage *)placeholder;

@end

aussi dans "didTappedPlayVideo" vous faites le suivant:

- (void)didTappedPlayVideo{

[self.delegate userTappedPlayButtonOnCell:self];

}

la méthode: settupVideoWithURL est utilisée pour lancer votre lecteur et la méthode: removeVideoAndShowPlaceholderImagearrête la vidéo et rend l'AVPlayerItem et l'AVPlayer nil.

Maintenant, lorsque l'utilisateur appuie sur une cellule, vous renvoyez l'appel à UIViewController où vous avez la table, et dans la méthode déléguée, vous devez effectuer les opérations suivantes:

    - (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;
    }

P.S .:

Sur les appareils plus anciens (presque tous jusqu’au nouvel iPad Pro), il n’est pas indiqué que plusieurs instances de AVPlayer. Espèrent également que ces fragments de code pourront vous guider ou vous aider dans votre quête :)

0
Laur Stefan