AVPlayer停止播放,不再恢复

在我的应用程序中,我必须播放存储在Web服务器上的audio文件。 我正在使用AVPlayer 。 我有所有的播放/暂停控制,所有的代表和观察员工作得很好。 在播放小audio文件时,一切都很好。

当一个长audio文件播放时,它也开始播放罚款,但几秒钟后AVPlayer暂停播放(最有可能缓冲)。 问题是它不能自行恢复。 它保持暂停状态,如果我再次手动按播放button它再次平稳播放。

我想知道为什么AVPlayer不能自动恢复,如何在不用用户再次按下播放button的情况下再次恢复audio? 谢谢。

是的,它停止,因为缓冲区是空的,所以它必须等待加载更多的video。 之后,你必须手动要求重新开始。 为了解决这个问题,我遵循了以下步骤:

1)检测:为了检测玩家何时停止,我使用KVO的速率属性值:

 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"rate"] ) { if (self.player.rate == 0 && CMTimeGetSeconds(self.playerItem.duration) != CMTimeGetSeconds(self.playerItem.currentTime) && self.videoPlaying) { [self continuePlaying]; } } } 

这种情况: CMTimeGetSeconds(self.playerItem.duration) != CMTimeGetSeconds(self.playerItem.currentTime)是检测到达video结束或停在中间

2)等待video加载 – 如果直接继续播放,则不会有足够的缓冲区继续播放而不会中断。 要知道什么时候开始,你必须观察从playerItem(这里我使用一个库来观察块,但我认为它的重点)的价值playbackLikelytoKeepUp

 -(void)continuePlaying { if (!self.playerItem.playbackLikelyToKeepUp) { self.loadingView.hidden = NO; __weak typeof(self) wSelf = self; self.playbackLikelyToKeepUpKVOToken = [self.playerItem addObserverForKeyPath:@keypath(_playerItem.playbackLikelyToKeepUp) block:^(id obj, NSDictionary *change) { __strong typeof(self) sSelf = wSelf; if(sSelf) { if (sSelf.playerItem.playbackLikelyToKeepUp) { [sSelf.playerItem removeObserverForKeyPath:@keypath(_playerItem.playbackLikelyToKeepUp) token:self.playbackLikelyToKeepUpKVOToken]; sSelf.playbackLikelyToKeepUpKVOToken = nil; [sSelf continuePlaying]; } } }]; } 

而就是这样! 问题解决了

编辑:顺便说一下,库使用的是libextobjc

我正在处理video文件,所以我的代码比你需要更多,但是下面的解决scheme应该暂停播放器,然后每隔0.5秒检查一次,看看我们的缓冲是否足够跟上。 如果是这样,它会重新启动播放器。 如果玩家没有重新启动而挂起超过10秒钟,我们停止玩家并向用户道歉。 这意味着你需要合适的观察员。 下面的代码对我来说工作得很好。

在.h文件或其他地方定义/初始化的属性:

 AVPlayer *player; int playerTryCount = -1; // this should get set to 0 when the AVPlayer starts playing NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; 

部分.m:

 - (AVPlayer *)initializePlayerFromURL:(NSURL *)movieURL { // create AVPlayer AVPlayerItem *videoItem = [AVPlayerItem playerItemWithURL:movieURL]; AVPlayer *videoPlayer = [AVPlayer playerWithPlayerItem:videoItem]; // add Observers [videoItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil]; [self startNotificationObservers]; // see method below // I observe a bunch of other stuff, but this is all you need for this to work return videoPlayer; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { // check that all conditions for a stuck player have been met if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) { if (self.player.currentItem.playbackLikelyToKeepUp == NO && CMTIME_COMPARE_INLINE(self.player.currentTime, >, kCMTimeZero) && CMTIME_COMPARE_INLINE(self.player.currentTime, !=, self.player.currentItem.duration)) { // if so, post the playerHanging notification [self.notificationCenter postNotificationName:PlayerHangingNotification object:self.videoPlayer]; } } } - (void)startNotificationObservers { [self.notificationCenter addObserver:self selector:@selector(playerContinue) name:PlayerContinueNotification object:nil]; [self.notificationCenter addObserver:self selector:@selector(playerHanging) name:PlayerHangingNotification object:nil]; } // playerHanging simply decides whether to wait 0.5 seconds or not // if so, it pauses the player and sends a playerContinue notification // if not, it puts us out of our misery - (void)playerHanging { if (playerTryCount <= 10) { playerTryCount += 1; [self.player pause]; // start an activity indicator / busy view [self.notificationCenter postNotificationName:PlayerContinueNotification object:self.player]; } else { // this code shouldn't actually execute, but I include it as dummyproofing [self stopPlaying]; // a method where I clean up the AVPlayer, // which is already paused // Here's where I'd put up an alertController or alertView // to say we're sorry but we just can't go on like this anymore } } // playerContinue does the actual waiting and restarting - (void)playerContinue { if (CMTIME_COMPARE_INLINE(self.player.currentTime, ==, self.player.currentItem.duration)) { // we've reached the end [self stopPlaying]; } else if (playerTryCount > 10) // stop trying [self stopPlaying]; // put up "sorry" alert } else if (playerTryCount == 0) { return; // protects against a race condition } else if (self.player.currentItem.playbackLikelyToKeepUp == YES) { // Here I stop/remove the activity indicator I put up in playerHanging playerTryCount = 0; [self.player play]; // continue from where we left off } else { // still hanging, not at end // create a 0.5-second delay to see if buffering catches up // then post another playerContinue notification to call this method again // in a manner that attempts to avoid any recursion or threading nightmares playerTryCount += 1; double delayInSeconds = 0.5; dispatch_time_t executeTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(executeTime, dispatch_get_main_queue(), ^{ // test playerTryCount again to protect against changes that might have happened during the 0.5 second delay if (playerTryCount > 0) { if (playerTryCount <= 10) { [self.notificationCenter postNotificationName:PlayerContinueNotification object:self.videoPlayer]; } else { [self stopPlaying]; // put up "sorry" alert } } }); } 

希望能帮助到你!

我有一个类似的问题。 我有一些我想玩的本地文件,configuration了AVPlayer并且调用了[player play],玩家在第0帧停止播放,直到我再次手动调用play时才会播放。 由于错误的解释,我接受的答案是不可能实现的,那么我只是试图推迟这个游戏,并且神奇地工作

 [self performSelector:@selector(startVideo) withObject:nil afterDelay:0.2]; -(void)startVideo{ [self.videoPlayer play]; } 

对于networkingvideo,我也有这个问题,我使用华莱士的答案解决它。

在创buildAVPlayer时添加一个观察者:

 [self.videoItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil]; - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { // check that all conditions for a stuck player have been met if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) { if (self.videoPlayer.currentItem.playbackLikelyToKeepUp == NO && CMTIME_COMPARE_INLINE(self.videoPlayer.currentTime, >, kCMTimeZero) && CMTIME_COMPARE_INLINE(self.videoPlayer.currentTime, !=, self.videoPlayer.currentItem.duration)) { NSLog(@"hanged"); [self performSelector:@selector(startVideo) withObject:nil afterDelay:0.2]; } } 

}

请记住在解散视图之前删除观察者

 [self.videoItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"] 

我觉得用AVPlayerItemPlaybackStalledNotification来检测停顿是一个更好的办法。

接受的答案给出了一个可能的解决scheme,但缺乏灵活性,也很难阅读。 这是更灵活的解决scheme。

添加观察员:

 //_player is instance of AVPlayer [_player.currentItem addObserver:self forKeyPath:@"status" options:0 context:nil]; [_player addObserver:self forKeyPath:@"rate" options:0 context:nil]; 

处理器:

 -(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context { if ([keyPath isEqualToString:@"status"]) { if (_player.status == AVPlayerStatusFailed) { //Possibly show error message or attempt replay from tart //Description from the docs: // Indicates that the player can no longer play AVPlayerItem instances because of an error. The error is described by // the value of the player's error property. } }else if ([keyPath isEqualToString:@"rate"]) { if (_player.rate == 0 && //if player rate dropped to 0 CMTIME_COMPARE_INLINE(_player.currentItem.currentTime, >, kCMTimeZero) && //if video was started CMTIME_COMPARE_INLINE(_player.currentItem.currentTime, <, _player.currentItem.duration) && //but not yet finished _isPlaying) { //instance variable to handle overall state (changed to YES when user triggers playback) [self handleStalled]; } } } 

魔法:

 -(void)handleStalled { NSLog(@"Handle stalled. Available: %lf", [self availableDuration]); if (_player.currentItem.playbackLikelyToKeepUp || // [self availableDuration] - CMTimeGetSeconds(_player.currentItem.currentTime) > 10.0) { [_player play]; } else { [self performSelector:@selector(handleStalled) withObject:nil afterDelay:0.5]; //try again } } 

“[self availableDuration]”是可选的,但您可以根据可用的video数量手动启动播放。 您可以更改代码检查是否有足够的video缓冲的频率。 如果您决定使用可选部分,请执行以下方法:

 - (NSTimeInterval) availableDuration { NSArray *loadedTimeRanges = [[_player currentItem] loadedTimeRanges]; CMTimeRange timeRange = [[loadedTimeRanges objectAtIndex:0] CMTimeRangeValue]; Float64 startSeconds = CMTimeGetSeconds(timeRange.start); Float64 durationSeconds = CMTimeGetSeconds(timeRange.duration); NSTimeInterval result = startSeconds + durationSeconds; return result; } 

不要忘记清理。 删除观察员:

 [_player.currentItem removeObserver:self forKeyPath:@"status"]; [_player removeObserver:self forKeyPath:@"rate"]; 

可能的未决呼叫处理停滞的video:

 [UIView cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleStalled) object:nil]; 

首先我观察播放停顿

 NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(playerStalled), name: AVPlayerItemPlaybackStalledNotification, object: videoPlayer.currentItem) 

然后我强制播放继续

 func playerStalled(note: NSNotification) { let playerItem = note.object as! AVPlayerItem if let player = playerItem.valueForKey("player") as? AVPlayer{ player.play() } } 

这可能不是最好的方式,但我使用它,直到我find更好的东西:)

在非常糟糕的networkingplaybackLikelyToKeepUp中,很可能是错误的。

使用kvo来观察playbackBufferEmpty缓冲空间是否更好,如果现有缓冲区数据可以用于回放,则更为敏感。如果值更改为true,则可以调用播放方法继续播放。