AVQueuePlayer的预缓冲

23

有没有人知道当当前 AVPlayerItem 即将完成播放时,AVQueuePlayer 是否开始缓冲下一个 AVPlayerItem

我知道文档中没有任何提示,主要是想问是否有人观察到这种行为或者没有。

6个回答

22

好的,我再次查看了这个问题,并编写了一些代码来检查AVQueuePlayer

jollyCocoa的答案指出了正确的方向,建议观察AVPlayerItem上的状态属性。然而,文档似乎没有指出此属性(特别是其AVPlayerItemStatusReadyToPlay值)可能与缓冲有关。

然而,AVPlayerItem的loadedTimeRanges属性似乎更与缓冲有关。

对该数组进行KVO有点棘手 - 数组对象本身不会改变,只有它的项会改变 - 所以我决定每秒打印一次它的内容。

我发现在队列的第一个项目中几秒钟后,第二个项目的loadedTimeRanges出现了一个新的CMTimeRange,其开始时间为0,持续时间很短。在前一个项目继续播放的同时,持续时间可以增加到60秒左右。

简短回答:AVQueuePlayer将在播放当前项目时缓冲下一个AVPlayerItem


1
加载 AVPlayerItem 不是导致播放器卡顿的原因;如果您不同意,请向我展示能够证明这一点的代码。我有一个可用的应用程序,它在一个集合视图单元格中为您 iPhone 上的每个视频资产加载一个 AVPlayerItem。您可以尽快滚动,每个视频的第一帧都将显示,就像您只请求了资产的单个图像一样。但是,仅当滚动时开始播放时,会出现播放卡顿。那是因为您将太多的东西放入一个线程和一个队列中。在单独的线程和队列上启动播放,卡顿就会消失。 - James Bush
@JamesBush 有趣的评论,你能详细阐述一下吗?可以附上一些代码吗? - StackGU

8

从iOS 5开始,AVQueuePlayer似乎不再预缓冲了。在iOS 4中,它会预先缓冲下一曲。

我不确定为什么会有变化,或者这是否是苹果故意的,还是一个错误。无论如何,这很麻烦,我将向他们提交一个错误报告。


如果您已经提交了错误报告,能否打开radar以便我们可以复制它? - Stelian Iancu
这个 bug 报告有任何进展吗?我的应用程序也受到了同样的 bug 影响,当 AVQueuePlayer 移动到下一个文件时,将两个本地存储的 mp4 文件排队会导致播放停滞。 - Kristian Evensen
@matt-gallagher,你有没有收到苹果关于AVQueuePlayer缺乏预缓冲的回复? - Nick Fishman

5

找到了一个解决方法!!! 经过多个小时的搜索和失败的测试,我找到了一个适用于iOS 5及以上版本的解决方案。

这将在文件之间没有任何延迟的情况下播放。如果您想在文件之间切换,这不是很方便,但肯定会为您整合所有内容。在添加AVURLAsset时仍然可以跟踪每个文件的起点,保留CMTime变量,类似于:

NSValue *toJumpTo = [NSValue valueWithCMTime:composition.duration];
[array addObject:toJumpTo];

然后只需遍历数组,检查当前时间值并使用CMTimeCompare进行比较。

编辑:在此页面上找到(该链接目前指向垃圾邮件)。


1
哇,那个AVComposition技巧正是我所需要的。干杯! :) - Goz
你可以使用存档链接替换该链接。 - ov1d1u

2
我一直在使用由服务器提供的电影进行AVQueuePlayer实验。在这个特定的测试中,我使用了两种不同类型的视频资源,分别是初始化为URL的AVPlayerItems的.mp4文件(渐进式下载)和.m3u8 http流媒体文件。它的表现有点奇怪,必须说。
取决于我排队文件的顺序,我会得到非常不同的行为。如果我从.mp4文件开始,当nextItem开始播放(.m3u8文件)时,AVPlayerLayer会变成空白和静音。如果我改变顺序并从m3u8文件开始,则AVPlayerLayer变为空白,但第二个文件的音频播放正常。
从我目前所见,我的印象是AVQueuePlayer在当前AVPlayerItem完成播放之前不会开始下载下一个AVPlayerItem。但我可能错了。
我想继续测试...
编辑:好吧,我做了更多的测试,似乎下一项在上一项完成播放之前就开始下载了。请参见下面的帖子。删掉“NOT”...
编辑2:由于评论框没有格式选项,我在此添加了我的解释。(如果这是处理答案更新的不良方式,请提供最佳实践方法)
无论如何,我通过KVO迭代了我的itemsArray,添加了对状态属性的观察...
[playerItem addObserver: self forKeyPath:@"status" options: 0 context: NULL];

我还将控制器设置为AVPlayerItem通知AVPlayerItemDidPlayToEndTimeNotification的观察者:

[[NSNotificationCenter defaultCenter] addObserver: self
                                         selector: @selector(playerItemDidReachEnd:) 
                                             name: AVPlayerItemDidPlayToEndTimeNotification 
                                           object: nil];

现在我可以记录AVPlayerItem的状态更改,结果发现队列中的下一个AVPlayerItem在当前项目结束播放之前被记录为状态更改为AVPlayerItemStatusReadyToPlay。
我的结论是,当前项目结束播放之前,下一项正在排队等待开始下载。
然而!我创建了一个带有AVPlayerItems的数组,用于创建我的播放器。阅读AVFoundation文档上关于AVAssets的内容,我得出这些资源会异步下载的印象,但我仍然不确定这些下载是由IAPlayerItem何时启动的。当我将.m3u8文件添加到队列中时,仍然存在一些奇怪的行为,这让我想知道这些资产是否在创建时开始下载(但这对我来说似乎有点奇怪)。

好的,所以我遍历items数组中的项,通过KVO添加观察。这个评论不允许格式化。我会在主帖中进行编辑! - jollyCocoa

0

你可以使用三个AVPlayer。第一个是上一个,第二个是当前的,最后一个是下一个。当当前播放时,将上一个播放器作为预加载并观察其状态。当预加载播放器的状态变为AVPlayerStatusReadyToPlay时,停止它并调用下面的代码:

[player.playerItem cancelPendingSeeks]; [player.playerItem.asset cancelLoading];

这些代码将防止预加载视频的所有数据。


0

我为您和其他想要减少每个URL视频缓冲时间的人制作了一个简单的演示。

注意:不知道这是否是正确的方法,但对我来说完美地工作,没有任何问题。如果有人知道更多或想改进代码以获得更好的结果,则欢迎更改我的答案。

在下面的代码中,第一个视频需要正常的缓冲时间。当当前视频完成时,下一个视频将自动开始,并且您可以向左或向右滑动以移动到下一个和上一个视频。

按照以下步骤操作。

1)在videoPlayerVC.h文件中。

#import <AVFoundation/AVFoundation.h>
#import <AVKit/AVKit.h>

@interface videoPlayerVC : UIViewController
{
    AVPlayerViewController *avPlyrViewController;
    AVPlayerItem *currentAVPlyrItem;

    int currentVideoNO;  // For current video track.
    NSMutableArray *arrOfAVPItems;
    NSMutableArray *arrOfPlyer;
}

2) 在videoPlayerVC.m文件中

在这里,您可以通过单击按钮开始播放视频。以下是按钮的自定义方法。

-(void)clickOnPlayVideosImage:(UIButton *)sender
{
   // In my App. all video URLs is up to 15 second.
    currentVideoNO = 0;
    NSMutableArray *arrForVideoURL = [[NSMutableArray alloc]initWithObjects:
                                      [NSURL URLWithString:@"http://videos/url1.mov"],
                                      [NSURL URLWithString:@"http://videos/url2.mov"],
                                      [NSURL URLWithString:@"http://videos/url3.mov"],
                                      [NSURL URLWithString:@"http://videos/url3.mov"],
                                      [NSURL URLWithString:@"http://videos/url4.mov"],
                                      [NSURL URLWithString:@"http://videos/url5.mov"], nil];

    AVPlayerItem *thePlayerItemA = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:0]];
    AVPlayerItem *thePlayerItemB = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:1]];
    AVPlayerItem *thePlayerItemC = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:2]];
    AVPlayerItem *thePlayerItemD = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:3]];
    AVPlayerItem *thePlayerItemE = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:4]];
    AVPlayerItem *thePlayerItemF = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:5]];

    if(arrOfAVPItems.count > 0)
       [arrOfAVPItems removeAllObjects];
    arrOfAVPItems = nil;

    if(arrOfPlyer.count > 0)
        [arrOfPlyer removeAllObjects];
    arrOfPlyer = nil;
    arrOfPlyer = [[NSMutableArray alloc] init];

    arrOfAVPItems = [NSMutableArray arrayWithObjects:thePlayerItemA, thePlayerItemB, thePlayerItemC, thePlayerItemD, thePlayerItemE, thePlayerItemF, nil]; // Add All items in the Array

    for(AVPlayerItem *myPlyrItem in arrOfAVPItems)
    {
        AVPlayer *videoPlayerNext = [AVPlayer playerWithPlayerItem:myPlyrItem]; /// Add item in the player
        [videoPlayerNext play]; 
        [videoPlayerNext pause];
        [arrOfPlyer addObject:videoPlayerNext]; /// Make Array of  "AVPlayer" just reduce buffering of each video. 
    }

    avPlyrViewController = [AVPlayerViewController new];
    avPlyrViewController.delegate = self;
    avPlyrViewController.player = (AVPlayer *)arrOfPlyer[0]; // Add first player from the Array.
    avPlyrViewController.showsPlaybackControls = YES;
    avPlyrViewController.allowsPictureInPicturePlayback = YES;
    avPlyrViewController.videoGravity = AVLayerVideoGravityResizeAspect;

    [self presentViewController:avPlyrViewController animated:YES completion:^{
        [self performSelector:@selector(playVideos) withObject:nil afterDelay:1];/// call method after one second for play video.

        UISwipeGestureRecognizer * swipeleft=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeleftToNextVideo:)];
        swipeleft.direction=UISwipeGestureRecognizerDirectionLeft;
        [avPlyrViewController.view addGestureRecognizer:swipeleft]; // Add left swipe for move on next video

        UISwipeGestureRecognizer * swiperight=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeleftToPerviousVideo:)];
        swiperight.direction=UISwipeGestureRecognizerDirectionRight;
        [avPlyrViewController.view addGestureRecognizer:swiperight]; // Add right swipe for move on previous video
    }];
}

现在编写playVideos方法的代码。
#pragma mark - AVPlayer Methods -

-(void)playVideos
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:currentAVPlyrItem]; // remove current "AVPlayerItem" from the notification observe.

    if(arrOfAVPItems.count > 0)
    {
        currentAVPlyrItem = (AVPlayerItem *)arrOfAVPItems[currentVideoNO]; // Add "AVPlayerItem" from the array.

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:currentAVPlyrItem]; // Add notification observer to indication current "AVPlayerItem" is finish.

        // pause and nil previous player if available.
        [avPlyrViewController.player pause];
        avPlyrViewController.player = nil;

        avPlyrViewController.player = (AVPlayer *)arrOfPlyer[currentVideoNO]; // add new player from the array
        [avPlyrViewController.player.currentItem seekToTime:kCMTimeZero]; // set for start video on initial position.
        [avPlyrViewController.player play]; // Play video

    }
}

通知观察器,用于指示视频已完成。

- (void)playerItemDidReachEnd:(NSNotification *)notification
{
    NSLog(@"IT REACHED THE END");
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:currentAVPlyrItem];  // remove current "AVPlayerItem" from the notification observe.

    [self swipeleftToNextVideo:nil]; // Call method for next video.
}

播放下一个视频的方法

-(void)swipeleftToNextVideo:(UISwipeGestureRecognizer*)gestureRecognizer
{
    currentVideoNO++;
    if(currentVideoNO > (arrOfAVPItems.count -1))
        currentVideoNO = 0;

    NSLog(@"current - %d and last - %d", currentVideoNO, (int)(arrOfAVPItems.count -1));

    [self playVideos];
}

播放上一个视频的方法。

-(void)swipeleftToPerviousVideo:(UISwipeGestureRecognizer*)gestureRecognizer
{
    currentVideoNO--;
    if(currentVideoNO < 0)
        currentVideoNO = (int)(arrOfAVPItems.count -1);

    NSLog(@"current - %d and last - %d", currentVideoNO, (int)(arrOfAVPItems.count -1));

    [self playVideos];
}

请按照以下步骤操作。如果有任何疑问,请在评论区留言。


请您提供一个使用AVQueuePlayer的示例代码。 - Mahesh Narla
1
哪一段代码可以减少缓冲时间(也称预加载视频)? - Tony Nguyen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接