Iphone 基于AVQueuePlayer的多远程视频采集应用程序设计
我已经编写了一个iPhone和iPad应用程序,可以通过HTTP传输例如4个视频。每个视频必须在前一个视频开始时请求,因此我不能在开始时请求所有视频。例外情况是,我可以在开始时请求第二个视频(我们称之为内容)。让我们把其他的视频称为广告 我为每个广告或视频都有一个URL,它返回一些包含实际内容URL的XML描述。(实际上,在主视频中是JSON,但没关系。) 当XML返回时,我找到内容URL并为其创建一个AVPlayerItem。当第一个广告返回时,我用它制作AVQueuePlayer。如果那时主视频已经返回,我为它制作一个AVPlayerItem,并在第一个广告之后将其插入队列播放器 有两种格式可用于广告和视频,MP4和3GP。如果设备不在wifi上,或者是低端设备(例如iPhone 3G和iPad第二代),我会选择3GP 然后我观察玩家身上的各种东西以及我创建的任何物品。首先,当前项目正在更改:Iphone 基于AVQueuePlayer的多远程视频采集应用程序设计,iphone,ios,Iphone,Ios,我已经编写了一个iPhone和iPad应用程序,可以通过HTTP传输例如4个视频。每个视频必须在前一个视频开始时请求,因此我不能在开始时请求所有视频。例外情况是,我可以在开始时请求第二个视频(我们称之为内容)。让我们把其他的视频称为广告 我为每个广告或视频都有一个URL,它返回一些包含实际内容URL的XML描述。(实际上,在主视频中是JSON,但没关系。) 当XML返回时,我找到内容URL并为其创建一个AVPlayerItem。当第一个广告返回时,我用它制作AVQueuePlayer。如果那时主
-(void)playerCurrentItemChanged:(NSString*)aPath ofPlayer:(AVQueuePlayer*)aQueuePlayer change:(NSDictionary*)aChange {
if ([aPath isEqualToString:kCurrentItemKey]) {
if (!_quitting) {
[self performSelectorOnMainThread:@selector(queuePlayerCurrentItemChanged) withObject:nil waitUntilDone:NO];
}
}
}
注意,我在主线程上调用了另一个方法,因为它在文档中说,AVPlayer的非原子属性应该以这种方式使用
此方法如下所示:
-(void)queuePlayerCurrentItemChanged {
NSAssert([NSThread isMainThread], @"FIX ME! VideoController method called using a thread other than main!");
AVPlayerItem* playerItem = _queuePlayer.currentItem;
if (playerItem) {
VideoItem* videoItem = [self findVideoItemFromPlayerItem:playerItem];
[self getReadyToPlay:videoItem];
// don't continue to FF if it's an ad
if (videoItem.isAd && (_queuePlayer.rate > 1.0)) {
_queuePlayer.rate = 1.0;
}
}
else {
NSLog(@"queuePlayerCurrentItemChanged to nil!!!");
}
}
VideoItem是我自己的类,它包装了一个AVPlayerItem,并添加了我需要跟踪的额外数据
基本上,getReadyToPlay根据是广告(不允许FF)还是主视频来设置UI。如果我们转换到广告,代码也会停止
我还观察已创建的任何项目的状态。如果任何项目的状态已变为可播放状态,则在主线程上类似地调用以下方法:
-(void)queuePlayerItemStatusPlayable:(AVPlayerItem*)aPlayerItem {
NSAssert([NSThread isMainThread], @"FIX ME! VideoController method called using a thread other than main!");
VideoItem* videoItem = [self findVideoItemFromPlayerItem:aPlayerItem];
NSLog(@"queuePlayerItemStatusPlayable for item %@",videoItem);
if (_queuePlayer.currentItem != aPlayerItem) {
NSLog(@" but playable status is for non current item %@, ignoring",videoItem);
return;
}
if (_videoItemIndex == 0) {
NSLog(@" and playable status is for item 0 - call getReadyToPlay");
[self getReadyToPlay:videoItem]; // the first item doesn't get a current item notification so do this here
}
[self playIfReady]; //pausenotadvance (do this every time now)
}
如果这是第一个广告,我必须调用getReadyToPlay,因为对于这个播放器,从来没有当前的项目通知。我以前也只为这个项目调用playIfReady,但现在我为任何当前项目调用它,以避免延迟
类似地,如果项目的状态为AVPlayerItemStatusFailed,则调用此代码:
-(void)queuePlayerItemStatusFailed:(AVPlayerItem*)aPlayerItem {
NSAssert([NSThread isMainThread], @"FIX ME! VideoController method called using a thread other than main!");
VideoItem* videoItem = [self findVideoItemFromPlayerItem:aPlayerItem];
if (videoItem.isAd) {
// this seems to be the only notification that we've run out of video when reachability is off
if (appDelegate._networkStatus == NotReachable) {
NSLog(@"AVPlayerItemStatusFailed when not reachable, show low bandwidth UI");
if (!_isPaused && !_lowBandwidthUIShowing) {
[_queuePlayer pause];;
[self showLowBandwidthUI];
}
return;
}
NSError* error = aPlayerItem.error;
if (aPlayerItem == _queuePlayer.currentItem) {
NSLog(@"AVPlayerStatusFailed playing currently playing ad with error: %@",error.localizedDescription);
if (videoItem.isLast) {
NSLog(@"Failed to play last ad, quitting");
[self playEnded];
}
else {
NSLog(@"Not the last ad, advance");
[_queuePlayer advanceToNextItem];
}
}
else {
NSLog(@"Error - AVPlayerStatusFailed on non-playing ad with error: %@",error.localizedDescription);
[_queuePlayer removeItem:aPlayerItem];
}
}
else {
// This is can be an invalid URL in the main video JSON or really bad network
// Assuming invalid URLS are pretty rare by the time we're in the app store, blame the network
// Whatever, give up because it's the main video
NSError* error = aPlayerItem.error;
if (appDelegate._networkStatus == ReachableViaWiFi) {
if (!_alertShowing) {
NSLog(@"Error - AVPlayerStatusFailed playing main video, bad JSON? : error %@",error.localizedDescription);
[self showServerAlertAndExit];
}
}
else {
if (!_alertShowing) {
NSLog(@"Error - AVPlayerStatusFailed playing main video, bandwidth? : error %@",error.localizedDescription);
[self showNetworkAlertAndExit];
}
}
}
return;
}
当另一个广告出现时,我要么将其添加到播放器的末尾,要么替换当前项目。我只在当前项为零时才执行后一种操作,这意味着播放机已完成当前视频片段并停止等待更多:
-(void) addNewItemToVideoPlayer:(AVPlayerItem*)aPlayerItem {
NSAssert([NSThread isMainThread], @"FIX ME! VideoController method called using a thread other than main!");
if (_queuePlayer.currentItem == nil) {
NSLog(@" CCV replaced nil current item, player %@",_queuePlayer);
[_queuePlayer replaceCurrentItemWithPlayerItem:aPlayerItem];
if (!_isPaused)
[_queuePlayer play];
}
else if ([_queuePlayer canInsertItem:aPlayerItem afterItem:((VideoItem*)[_videoItems objectAtIndex:_indexLastCued]).avPlayerItem]) {
NSLog(@" CCV inserted item after valid current item, player %@",_queuePlayer);
[_queuePlayer insertItem:aPlayerItem afterItem:((VideoItem*)[_videoItems objectAtIndex:_indexLastCued]).avPlayerItem];
}
}
这段代码似乎在模拟器/wifi上运行得很好,可能还有高端设备
在缓慢的3G网络上,iPhone 3G显示出各种不太可重复的缺陷
我确实观察到播放可能会继续,并将其称为主线:
-(void)queuePlayerLikelyToKeepUp:(AVPlayerItem*)aPlayerItem {
NSAssert([NSThread isMainThread], @"FIX ME! VideoController method called using a thread other than main!");
if (aPlayerItem.playbackLikelyToKeepUp == NO) {
if (!_isPaused) {
[_queuePlayer pause];
[self showLowBandwidthUI];
[self performSelector:@selector(restartVideo) withObject:nil afterDelay:1.0]; // delay and try again
}
}
else {
// if we forced the showing of UI due to low bandwidth, fade it out, remove spinner
if (_lowBandwidthUIShowing) {
[self hideLowBandwidthUI];
[_queuePlayer play];
}
}
}
currentDuration = CMTimeGetSeconds([[myAVQueuePlayer currentItem] duration]);
这基本上会暂停视频并显示UI(它会显示一个进度条,显示下载了多少视频,以便向用户反馈视频暂停的原因)。然后,它尝试在一秒钟后重新启动视频
我在iPhone3G上看到的最糟糕的错误是完全失速。有时我会收到主视频的AVPlayerItemStatusFailed(错误消息没有帮助-只是“未知错误”-AVFoundationErrorDomain错误-11800),因此用户退出暂停的视频并重试,但随后似乎播放器一旦处于此状态,就不会播放任何视频,即使是以前播放过的视频。然而,这是一个全新的AVQueuePlayer——在每一组视频完成后,或者当用户对它们感到厌烦时,我退出我的视频控制器
视频也可以进入“无广告”模式。主要视频播放,但随后没有广告播放,所有后续视频都没有广告播放
我知道这是一个很难回答的问题,如果不是不可能的话,但我被要求问这个问题,所以我会继续
你能看出我做错了什么吗
更一般地说,您能否简单介绍一下如何在这样的情况下使用AVQueuePlayer,即创建AVQueuePlayer时,所有视频URL都未知?ABQueuePlayer和AVPlayer的行为和不行为是什么
你能给我一些关于如何使用AVQueuePlayer处理低带宽情况的建议吗?是什么导致它以不可重启的方式暂停
你有没有想过在主线程上应该做什么,不应该做什么?答案是必须在主线程上调用
AVQueuePlayer
的所有方法(不仅仅是属性)。此外,必须在主线程上启动和停止KVO
如果你不这样做,即使是在一个很小的情况下,你的玩家最终也会进入一种拒绝游戏的状态。更糟糕的是,支持
AVQueuePlayer
的mediaserverd进程也处于这种状态,因此即使开始播放新视频并重新创建AVQueuePlayer
,也无法工作。我遇到了同样的问题(由于mediaserverd崩溃,音频播放偶尔会在后台停止)。。。我尝试在主线程中对AVQueuePlayer执行所有调用/属性,但它没有完全解决问题
因此,我在developers.apple.com上打开了一个TSI,答案如下:
1) 是什么导致了这次崩溃,是我们的代码中有什么东西,还是
知道错误,它很快就会被修复?>>媒体服务器
崩溃确实会发生,当崩溃发生时,我们依靠开发人员报告
跟踪问题并解决它们。AVFoundation和queue player位于
大量其他AV和核心音频代码来完成它的功能和
因此,我们需要分析是什么导致了这次特定的碰撞
媒体服务器我们需要一份带有可复制测试的错误报告
案例我个人以前没有看过这个崩溃日志,但其他用户
已经报告了这一点,它似乎与5.1有关。我确实建议归档
在尽可能快地记录您的
这一问题的具体案例。你可以给我发bug ID,我可以
将其上报给我们的AVF基金会和核心音频工程团队
2) 我能做什么(除了我正在做的事之外)
NSError* error = nil;
if ( [[[myAVQueuePlayer currentItem] asset] statusOfValueForKey:@"duration" error:&error] == AVKeyValueStatusLoaded ) {
currentDuration = CMTimeGetSeconds([[myAVQueuePlayer currentItem] duration]);
} else {
[[[myAVQueuePlayer currentItem] asset] loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:@"duration"] completionHandler:^{
currentDuration = CMTimeGetSeconds([[myAVQueuePlayer currentItem] duration]);
}];
}