在 UITableView 中更改 AVPlayer 的 playerItem

Posted

技术标签:

【中文标题】在 UITableView 中更改 AVPlayer 的 playerItem【英文标题】:Changing playerItem of AVPlayer in UITableView 【发布时间】:2014-08-17 18:11:42 【问题描述】:

我有一个UITableView,其中包含许多滚动时要播放的视频。由于 tableView 中的单元格被重复使用,我只为每一行实例化一个AVPlayer。当一个单元被重新使用时,我只需通过调用[self.player replaceCurrentItemWithPlayerItem:newItem]; 来更改单元播放器的PlayerItem。这目前在tableView:cellForRowAtIndexPath 内部被间接调用。向下滚动时,重用发生时有明显的滞后。通过消除过程,我得出结论,延迟是由replaceCurrentItemWithPlayerItem 引起的,甚至还没有开始播放。删除这一行代码(阻止播放器获取新视频)时,滞后消失了。

我尝试解决的问题:

我有一个自定义UITableViewCell 用于播放这些视频,并且我在其中创建了一个方法来使用来自对象的新信息进行初始化。 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 才能工作,但经过一番搜索,我发现几个 cmets 声明 replaceCurrentItemWithPlayerItem: 可以 not 在单独的线程中调用,这对我来说毫无意义。我知道 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 后,我什么都不做。我直到稍后才开始播放视频。 replaceCurrentItemWithPlayerItem: 造成了这种明显的滞后。文档声明它正在更改另一个线程中的项目,但该方法中的 something 阻碍了我的 UI。

有人告诉我在 Instruments 中使用 Time Profiler 来找出它是什么,但我不知道该怎么做。通过运行分析器并偶尔滚动一次,this is the result(image)。每个图组的峰值是我正在谈论的滞后。一个极端的峰值是(我认为)当我滚动到底部并点击状态栏滚动到顶部时。我不知道如何解释结果。我在堆栈中搜索replace,找到了那个混蛋。它是从Main Thread(在顶部)中调用的,这是有道理的,“运行时间”为 50 毫秒,我不知道怎么想。该图的相关比例是大约1分半钟的时间跨度。 相比之下,当注释掉该单行并再次运行 Time Profiler 时,图表的峰值显着降低(最高峰值为 13%,而图像上的峰值可能约为 60-70%)。

我不知道要找什么..

【问题讨论】:

我也有同样的问题。你修好了没有? @user1561346 No.. :/ 我决定只在滚动停止时才开始播放。这样,我可以滚动浏览许多视频(带有正确的缩略图)而无需加载视频,并且仅在滚动停止时加载。不是我想要的,但比滞后更好。如果您发现了什么,请告诉我! @Sti - 你如何获取和设置视频缩略图? 不确定 replaceCurrentItemWithPlayerItem 本身就是问题所在。我尝试保留所有 AVPlayer(每个单元 1 个)并简单地为每个单元交换 AVPlayerLayer 的播放器。因此,永远不要调用 replaceCurrentItemWithPlayerItem,因为每个 AVPlayer 只被赋予一个 AVPlayerItem。即使将 AVPlayerLayer 的播放器替换为另一个现有的 AVPlayer,延迟仍然存在并发生。 @Sti 你找到解决方案了吗?我也遇到了同样的问题。你能帮忙吗? 【参考方案1】:

如果您进行一些基本级别的分析,我认为您可以缩小问题范围。对我来说,我遇到了类似的问题,replaceCurrentItemWithPlayerItem 阻塞了 UI 线程。我通过检查我的代码来找出哪一行需要时间来解决它。对我来说,加载 AVAsset 需要时间。所以我使用了 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;

【讨论】:

感谢您的回复,但是,如果您阅读了我在赏金工具提示中指出的内容“在 AVAsset、AVPlayer 和 AVPlayerItem 上执行 KVO 并没有帮助解决 replaceCurrentItemWithPlayerItem 发生的延迟。一个解决方案这种顺利或替代的方法是受欢迎的。”我已经尝试了上述所有方法,甚至更进一步,它没有帮助:(【参考方案2】:

在您的 CustomCell.m 中,您应该使用缩略图来显示特定单元格视频的图像,您可以将缩略图添加到单元格内的按钮中,然后在 CustomCell.h 中构建一个自定义委托,当您点击时将调用该委托在新创建的按钮上类似:

@class CustomCell;

@protocol CustomCellDelegate <NSObject>

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

@end

还在您的 .h 中添加按钮的操作:

@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将停止视频并使 AVPlayerItem 和 AVPlayer 为零。

现在,当用户点击一个单元格时,您将调用发送回您拥有 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;
    

附注:

在较旧的设备上(几乎所有设备,直到新的 iPad Pro)都没有表示有多个AVPlayer 实例。 也希望这段代码可以指导或帮助您完成任务:)

【讨论】:

【参考方案3】:

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

replaceCurrentItemWithPlayerItem: 的文档明确指出它是异步执行的,因此它不应该是主队列跳动的根源。

项目替换异步发生;观察 currentItem 属性来找出替换将/确实发生的时间。

Documentation here

如果您仍然无法弄清楚,请发布更多代码,尤其是至少您的 cellForRowAtIndexPath 方法。

更新

根据下面@joey 的评论,此答案的先前内容不再有效。 documentation 不再声明该方法是异步的,所以它可能不是。

【讨论】:

谢谢!但是replaceCurrentItemWithPlayerItem 是我滞后的唯一合乎逻辑的解释。我似乎找不到它。有关更多信息,请参阅更新的问题! (附图片链接) 再试一次,检查左侧的Invert Call Tree,去掉“replace”这个词上的过滤器。 我现在这样做了,并将结果限制为“时间”,只反映滚动时的一秒钟。我不确定如何解释这一点。它是否说“某事”需要 10 毫秒才能解决? 10ms 听起来并不多,但我想当整个屏幕滚动并突然停止超过 1 FPS 单位的任何时间段时,它会很明显。结果:i.imgur.com/YPmPazv.png 我扩展了最上面的项目objc_msgSend。通过进一步扩展其中的[AVPlayerItem..]-line,我通过与原始图像中所示相同的方式回到主线程。都是 1ms/1.1%.. @Dima 您的链接不再有效。这里的文档:developer.apple.com/library/ios/documentation/AVFoundation/…:说“项目替换立即发生,项目成为玩家的 currentItem。”并建议替换不是异步的。 @Dima 我看到你正在积极回答关于 SO 的问题。我给你赏金,因为我没有得到任何解决方案。

以上是关于在 UITableView 中更改 AVPlayer 的 playerItem的主要内容,如果未能解决你的问题,请参考以下文章

在uiscrollview中使用更改按钮实现uitableview

在 UITableView 中单击时更改 UIButton 颜色和文本

UITableView 部分标题更改当前标题的样式

在 UITableView 中点击时更改单元格的颜色

在同一 UITableView 的单元格中更改 UITextFields 之间的焦点

在iOS中点击时如何更改UITableView Header背景颜色