AVQueuePlayer的预缓冲

时间:2010-11-18 18:15:05

标签: iphone objective-c avfoundation avplayer

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

我知道文档中没有任何内容可以暗示这一点,我主要询问是否有人观察过这种行为。

6 个答案:

答案 0 :(得分:20)

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

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

然而,AVPlayerItem's loadedTimeRanges属性似乎与缓冲更相关。

在那个数组上做KVO有点棘手 - 数组对象本身不会改变,只有它的项目才会改变 - 所以我每隔一段时间就打印出它的内容。

我发现队列的第一个项目中有几秒钟,第二个项目的loadedTimeRanges会显示一个新的CMTimeRange,其中包含开始时间0和一些小的持续时间。上一个项目继续播放时,持续时间可以增加到60秒左右。

简短回答: AVQueuePlayer会在播放当前的AVPlayerItem时缓冲下一个{{1}}。

答案 1 :(得分:6)

从iOS 5开始,似乎AVQueuePlayer不再预先缓冲。它确实预先缓冲了iOS 4中的下一首曲目。

我不确定为什么会有变化,或者它是否故意考虑到苹果公司或只是一个错误。无论如何,这是一个痛苦,我将向他们提交一个关于它的错误。

答案 2 :(得分:5)

找到一个工作!!!经过数小时的搜索和测试失败后,我找到了适用于iOS 5及更高版本的解决方案。

这将播放文件,两者之间没有任何延迟。如果你想在文件之间切换,不是很方便,但肯定会把所有东西都放在一起。在添加AVURLAsset时,仍然可以跟踪每个文件的开始位置,保留CMTime变量,如:

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

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

编辑:在http://www.developers-life.com/avplayer-looping-video-without-hiccupdelays.html上找到,但正如Stefan Kendall在评论中提到的,它现在是一个垃圾网站。以为我会留下链接以便归因,但不要访问它!

答案 3 :(得分:2)

我一直在使用服务器提供的电影来试验AVQueuePlayer。在这个特定的测试中,我设置了一个queuePlayer,其中AVPlayerItems用URL初始化为两个不同类型的不同视频资产。一个是.mp4文件(渐进式下载),另一个是.m3u8 http-streaming文件。我必须说,它确实有点时髦。

根据我对文件排队的顺序,我会得到完全不同的行为。如果我从.mp4文件开始,当nextItem启动播放器(.m3u8文件)时,AVPlayerLayer变为空白和静音。如果我更改顺序并从m3u8文件开始,AVPlayerLayer将变为空白,但第二个文件中的音频可以正常播放。

根据我目前所见,我的印象是AVQueuePlayer在当前AVPlayerItem播放完毕之前 NOT 开始下载下一个AVPlayerItem。但我可能错了。

继续我想......

编辑:好的,所以我做了一些测试,似乎下一个项目在最后一个项目完成播放之前开始下载。见下面的帖子。中风“不”......

Edit2:在这里添加我的解释,因为评论让我没有格式化选项。 (如果这是处理答案更新的一种不好的方法,那么最好的做法是受欢迎的)

无论如何,我遍历了我的itemsArray,使用KVO添加了对status属性的观察......

[playerItem addObserver: self forKeyPath:@"status" options: 0 context: NULL];

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

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

现在我可以记录AVPlayerItem的状态更改,结果是在当前项目结束播放之前,状态更改 AVPlayerItemStatusReadyToPlay 会记录队列中的下一个AVPlayerItem。

我的结论是,行中的下一个项目在当前项目结束之前开始下载。

然而! 我使用AVPlayerItems创建一个数组,用于创建我的播放器。在AVAssets上阅读AVFoundation文档,我得到的印象是这些文件是异步下载它们的资产,但我仍然不确定这些下载是由IAPlayerItem启动的。当我将.m3u8文件添加到队列时,我仍然有一些时髦的行为,这让我想知道这些资产是否在创建时开始下载(虽然看起来有点奇怪)。

答案 4 :(得分:0)

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

  

注意:不知道这是对的,但它对我来说非常适合,没有任何问题。如果有人知道更多或想要改进代码以获得更好的结果,那么欢迎更改我的答案。

在下面的代码中,第一个视频采用正常的缓冲时间。当前视频完成后,下一个视频将自动开始,您可以左右滑动以移动下一个和上一个视频。

按照以下步骤操作。

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

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

答案 5 :(得分:0)

您可以使用三个AVPlayers。首先是上一个,第二是当前,最后一个是下一个。当前播放时,播放最后一个播放器作为预加载并观察其状态。当预加载播放器的状态变为AVPlayerStatusReadyToPlay时,将其停止并调用下一行: [player.playerItem cancelPendingSeeks]; [player.playerItem.asset cancelLoading];

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