首页 文章

设计应用程序获取多个远程视频并使用AVQueuePlayer

提问于
浏览
4

我写了一个iPhone和iPad应用程序,应该流动,例如4个视频通过HTTP . 必须在前一个视频开始时请求每个视频,因此我不能在开始时请求所有视频 . 例外情况是我可以在开始时请求第二个视频(让我们称之为内容) . 我们打电话给其他视频广告 .

我有一个每个广告或视频的网址,它返回一些包含实际内容网址的XML说明 . (实际上它是主视频中的JSON,但没关系 . )

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

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

然后我在玩家和我创造的任何物品上观察各种事物 . 首先是当前项目的变化:

-(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 . 如果我们转换为广告,代码也会停止FFing .

我还观察了已创建的任何项目的状态 . 如果状态已变为可播放任何项目,则在主线程上类似地调用下面的方法:

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

这基本上暂停了视频并显示了UI(显示了一个进度条,显示了已下载的视频数量),以便向用户提供有关视频停止原因的反馈 . 然后它会在一秒钟之后尝试重新启动视频 .

我在iPhone 3G上看到的最糟糕的错误与完全失速有关 . 有时我得到主视频的AVPlayerItemStatusFailed(错误消息无益 - 只是“未知错误” - AVFoundationErrorDomain错误-11800)所以用户退出停滞的视频并再次尝试,但似乎一旦玩家处于此状态,它不会播放任何视频,即使它之前播放的视频 . 然而,这是一个全新的AVQueuePlayer - 我在每组视频完成后退出我的VideoController,或者当用户厌倦了它们时 .

视频也可以进入“无广告”模式 . 主视频播放,但之后没有广告显示,并且对于所有后续视频,没有广告显示 .

我知道这对你来说是一个很难的问题,但是我已被要求提出问题,所以我要继续前进 .

你能看到我做错的事吗?

更一般地说,您是否可以在这样的情况下提供有关如何使用AVQueuePlayer的简要教程,其中创建AVQueuePlayer时不知道所有视频URL? ABQueuePlayer和AVPlayer的作用和注意事项是什么?

您能否就AVQueuePlayer处理低带宽情况提出建议?是什么导致它以不可重启的方式停止?

你有什么想法在主线程上必须做什么,什么不是?

3 回答

  • 1

    答案是必须在主线程上调用 AVQueuePlayer 的所有方法(不仅仅是属性) . 此外,必须在主线程上启动和停止KVO观察 .

    如果你不这样做,即使只是一个小案例,你的玩家最终会进入一个拒绝玩的状态 . 更糟糕的是,支持 AVQueuePlayer 的mediaserverd进程也处于这种状态,因此即使开始播放新视频并重新创建 AVQueuePlayer 也无效 .

  • 1

    解决:

    崩溃的原因是在主线程上调用它:

    currentDuration = CMTimeGetSeconds([[myAVQueuePlayer currentItem] duration]);
    

    我的解决方案是:

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

    我遇到了同样的问题(由于mediaserverd崩溃,音频播放偶尔在后台停止)...我试图在主线程中对AVQueuePlayer执行所有调用/属性但是它没有完全解决这个问题 .

    所以我在developers.apple.com开了一个TSI,答案是:

    1)导致此崩溃的原因是,它是我们的代码中的某些内容,还是它已知错误并且很快就会被修复?>>媒体服务器崩溃确实发生了,当它们发生时我们依赖开发人员报告来追踪问题和解决它们 . AVFoundation和队列播放器依赖于大量其他AV和Core Audio代码来完成它所做的事情,因此我们需要使用可重现的测试用例来分析导致此特定崩溃的原因 . 我个人之前没有看过这个崩溃日志,但是其他用户报告了这个并且看起来与5.1有关 . 我建议尽快提交错误,记录您对此问题的具体情况 . 您可以将错误ID发送给我,我可以将其升级到我们的AVFoundation和Core Audio工程团队 . 2)在这种情况下,我能做些什么(除了我现在正在做的事情)以防止iOS冻结应用程序? 3)如果mediaserverd崩溃了,它如何重新启动,我怎么才能得到通知?>>我写了一个关于处理媒体服务器崩溃的一般方法的问答 . 您可以侦听kAudioSessionProperty_ServerDied属性 . https://developer.apple.com/library/ios/#qa/qa1749/_index.html让我强调这是一个绑带,任何崩溃都被视为我们方面的错误,我们希望尽快修复它们 . 请尽快提交错误 . 谢谢 . 问候,Edward Edward A. ::音频和视频工程师开发人员技术支持全球开发者关系Apple Inc. *

    希望这会有所帮助...或者至少阻止你如何解决这个问题:-)

相关问题