Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/102.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Iphone 基于AVQueuePlayer的多远程视频采集应用程序设计_Iphone_Ios - Fatal编程技术网

Iphone 基于AVQueuePlayer的多远程视频采集应用程序设计

Iphone 基于AVQueuePlayer的多远程视频采集应用程序设计,iphone,ios,Iphone,Ios,我已经编写了一个iPhone和iPad应用程序,可以通过HTTP传输例如4个视频。每个视频必须在前一个视频开始时请求,因此我不能在开始时请求所有视频。例外情况是,我可以在开始时请求第二个视频(我们称之为内容)。让我们把其他的视频称为广告 我为每个广告或视频都有一个URL,它返回一些包含实际内容URL的XML描述。(实际上,在主视频中是JSON,但没关系。) 当XML返回时,我找到内容URL并为其创建一个AVPlayerItem。当第一个广告返回时,我用它制作AVQueuePlayer。如果那时主

我已经编写了一个iPhone和iPad应用程序,可以通过HTTP传输例如4个视频。每个视频必须在前一个视频开始时请求,因此我不能在开始时请求所有视频。例外情况是,我可以在开始时请求第二个视频(我们称之为内容)。让我们把其他的视频称为广告

我为每个广告或视频都有一个URL,它返回一些包含实际内容URL的XML描述。(实际上,在主视频中是JSON,但没关系。)

当XML返回时,我找到内容URL并为其创建一个AVPlayerItem。当第一个广告返回时,我用它制作AVQueuePlayer。如果那时主视频已经返回,我为它制作一个AVPlayerItem,并在第一个广告之后将其插入队列播放器

有两种格式可用于广告和视频,MP4和3GP。如果设备不在wifi上,或者是低端设备(例如iPhone 3G和iPad第二代),我会选择3GP

然后我观察玩家身上的各种东西以及我创建的任何物品。首先,当前项目正在更改:

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