在UITableView中更改AVPlayer的playerItem

我有一个UITableView包含滚动时播放的video数量。 当tableView中的单元格被重新使用时,我只为每一行实例化一个AVPlayer 。 当一个单元格被重新使用时,我只需通过调用[self.player replaceCurrentItemWithPlayerItem:newItem];来更改单元格玩家的[self.player replaceCurrentItemWithPlayerItem:newItem]; 。 这是目前正在间接调用tableView:cellForRowAtIndexPath 。 当向下滚动时,重新使用时会有明显的滞后。 通过一个消除的过程,我已经得出结论,滞后是由replaceCurrentItemWithPlayerItem ,甚至开始播放之前造成的。 当删除这一行代码(防止玩家获得一个新的video),滞后消失。

我试图解决它:

我有一个自定义的UITableViewCell播放这些video,我已经创build了一个方法来初始化来自对象的新信息。 IE,在cellForRowAtIndexPath:我调用[cell initializeNewObject:newObject]; 执行以下方法:

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

当运行这个,我得到了相同的结果,如果我完全删除了更换项目的呼叫。 显然,这个函数不能被线程化。 我并不完全确定我对此的期望。 我会想象我需要一个干净的AVPlayer这个工作copy ,但search了一下之后,我发现了几个意见,指出replaceCurrentItemWithPlayerItem: 不能在一个单独的线程中调用,这对我来说没有任何意义。 我知道UI元素永远不应该在主/ UI线程之外的其他线程中处理,但我永远不会想象replaceCurrentItemWithPlayerItem属于这个类别。

我正在寻找一种方法来改变AVPlayer的项目没有滞后,但找不到任何。 我希望我已经理解了这个函数的线程是错误的,有人会纠正我。

编辑:我已经被告知,这个调用已经线程化,而这不应该真的发生。 但是,我没有看到其他的解释..以下是我的cellForRowAtIndexPath: 它在一个自定义的UITableView ,委托设置为self (所以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 ,但是滞后(这是在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; } 

滞后在每次完全相同的地方发生。 当快速滚动时,我们可以看到,当最底部的单元格的顶部到达屏幕的中心时,会出现短暂的滞后。 我想这对你来说并不意味着什么,但是很显然,每次都在同一个地方发生滞后,即当一个新的小区出队时。 改变AVPlayer的playerItem之后,我什么都不做。 之后我才开始播放video。 replaceCurrentItemWithPlayerItem: 正在引起这个明显的滞后。 该文档指出,它正在改变另一个线程中的项目,但在该方法中的东西是举起我的用户界面。

我被告知使用仪器中的Time Profiler来了解它是什么,但我不知道如何做到这一点。 通过运行分析器,并在一段时间滚动, 这是结果 (图片) 。 每个图组的高峰是我谈论的滞后。 一个极端的高峰是(我想)当我滚动到底部,并点击状态栏滚动到顶部。 我不知道如何解释结果。 我search堆栈replace ,并find了混蛋。 这是从Main Thread (在顶部)这里调用的,这是有道理的,具有50ms的“运行时间”,这是我不知道想到的。 图的相关比例是约1分半钟的时间。 相比之下,当再次注释单线并运行Time Profiler时,graphics的峰值明显较低(最高峰值为13%,相比图像上的峰值可能在60-70%左右)。

我不知道该找什么

如果你做一些基本的分析,我认为你可以缩小这个问题的范围。 对我来说,我有一个类似的问题,其中replaceCurrentItemWithPlayerItem阻塞UI线程。 我通过检查我的代码来找出哪条线需要时间来解决这个问题。 对于我来说,AVAsset的加载需要时间。 所以我使用loadValuesAsynchronouslyForKeys方法来解决我的问题。

因此你可以尝试以下方法:

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

在您的CustomCell.m中,您应该使用缩略图来显示特定单元格video的图像,您可以将缩略图添加到单元格内的button,然后在CustomCell.h中构build一个自定义委托,当您点击创buildbutton类似于:

 @class CustomCell; @protocol CustomCellDelegate <NSObject> - (void)userTappedPlayButtonOnCell:(CustomCell *)cell; @end 

也在你的.h添加button的动作:

 @interface CustomCell : UITableViewCell @property (strong, nonatomic) IBOutlet UIButton *playVideoButton; - (IBAction)didTappedPlayVideo; - (void)settupVideoWithURL:(NSString *)stringURL; - (void)removeVideoAndShowPlaceholderImage:(UIImage *)placeholder; @end 

也在“didTappedPlayVideo”你做下一个:

 - (void)didTappedPlayVideo{ [self.delegate userTappedPlayButtonOnCell:self]; } 

方法: settupVideoWithURL用于初始化你的播放器和方法: removeVideoAndShowPlaceholderImage将停止video,并使AVPlayerItem和AVPlayer零。

现在,当用户点击一个单元格时,将callback函数发送到具有tableView的UIViewController,并在委托方法中执行下一个操作:

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

PS:

在较旧的设备上(几乎所有的设备都在新的iPad Pro之前)没有被指示有多个AVPlayer实例。 也希望这块代码可以指导或帮助你在你的追求:)

您应该使用时间分析器来分析您的应用程序,以查看滞后实际发生的位置。

replaceCurrentItemWithPlayerItem:的文档清楚地声明它是asynchronous执行的,所以它不应该是主队列跳跃的源头。

项目replace发生asynchronous; 观察currentItem属性以找出replace将何时发生。

文档在这里

如果你仍然不知道,发表更多的代码,特别是至less你的cellForRowAtIndexPath方法。

UPDATE

根据下面的@ joey的评论,这个答案的以前的内容不再有效。 该文档不再声明该方法是asynchronous的,所以它可能不是。