在UITableView中更改AVPlayer的playerItem

时间:2014-08-17 18:11:43

标签: ios objective-c multithreading cocoa-touch avplayer

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

我试图修复它:

我有一个用于播放这些视频的自定义UITableViewCell,我在这些视频中创建了一个方法,用来自对象的新信息进行初始化。 I.E,在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;
        }
    }/*...*/
}

运行时,我得到的结果与完全删除更换项目的调用相同。显然,这个功能不能被线程化。 我不完全确定我对此的期望。我想我需要一个干净的copy AVPlayer来实现这一点,但在搜索了一下后,我发现了一些评论,指出replaceCurrentItemWithPlayerItem:可以在一个单独的线程中调用,这对我没有意义。我知道UI元素永远不应该在除main / UI-thread之外的其他线程中处理,但我绝不会想象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: 导致此明显延迟。该文档声明它正在更改另一个线程中的项目,但该方法中的某些正在阻止我的UI。

我被告知在仪器中使用Time Profiler来查明它是什么,但我不知道该怎么做。通过运行探查器并偶尔滚动一次, this is the result(图像)。每个图组的峰值都是我所说的滞后。当我滚动到底部并点击状态栏滚动到顶部时,单个极端峰值(我认为)。我不知道如何解释结果。我在堆栈中搜索replace,找到了这个混蛋。它是在Main Thread内(在顶部)调用的,这是有道理的,运行时间为" 50毫秒,我不知道。图的相关比例是约1分半的时间跨度。 相比之下,当评论该单行并再次运行Time Profiler时,图形的峰值显着降低(最高峰值为13%,与图像上的最高值相比,可能在60-70左右) %)。

我不知道该找什么......

3 个答案:

答案 0 :(得分: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;
}

答案 1 :(得分:0)

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

replaceCurrentItemWithPlayerItem:的文档明确指出它是异步执行的,所以它不应该是主队列跳跃的来源。

  

项目替换是异步发生的;观察currentItem   找出何时更换将会/确实发生的财产。

Documentation here

如果您仍然无法弄明白,请发布更多代码,特别是至少使用cellForRowAtIndexPath方法。

<强>更新

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

答案 2 :(得分:0)

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

P.S:

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