了解AVPlayer对象什么时候可以播放

我试图播放一个MP3文件,从以前的UIView (存储在一个NSURL *fileURLvariables)传递给UIView

我正在初始化一个AVPlayer

 player = [AVPlayer playerWithURL:fileURL]; NSLog(@"Player created:%d",player.status); 

NSLog打印Player created:0,我想这意味着它还没有准备好播放呢。

当我点击播放UIButton ,我运行的代码是:

 -(IBAction)playButtonClicked { NSLog(@"Clicked Play. MP3:%@",[fileURL absoluteString]); if(([player status] == AVPlayerStatusReadyToPlay) && !isPlaying) // if(!isPlaying) { [player play]; NSLog(@"Playing:%@ with %d",[fileURL absoluteString], player.status); isPlaying = YES; } else if(isPlaying) { [player pause]; NSLog(@"Pausing:%@",[fileURL absoluteString]); isPlaying = NO; } else { NSLog(@"Error in player??"); } } 

当我运行这个,我总是得到Error in player?? 在控制台。 但是,如果我replace检查AVPlayer是否准备好播放的if条件,用简单的if(!isPlaying) …,然后音乐播放第二次我点击播放UIButton

控制台日志是:

 Clicked Play. MP3:http://www.nimh.nih.gov/audio/neurogenesis.mp3 Playing:http://www.nimh.nih.gov/audio/neurogenesis.mp3 **with 0** Clicked Play. MP3:http://www.nimh.nih.gov/audio/neurogenesis.mp3 Pausing:http://www.nimh.nih.gov/audio/neurogenesis.mp3 Clicked Play. MP3:http://www.nimh.nih.gov/audio/neurogenesis.mp3 2011-03-23 11:06:43.674 Podcasts[2050:207] Playing:http://www.nimh.nih.gov/audio/neurogenesis.mp3 **with 1** 

我看到,第二次, player.status似乎持有1,我猜是AVPlayerReadyToPlay

第一次点击播放UIButton时,我能做些什么来正常播放? (即,我怎样才能确保AVPlayer不只是创build,而且还准备玩?)

您正在播放远程文件。 AVPlayer可能需要一些时间来缓冲足够的数据并准备好播放文件(参见AV Foundation编程指南 )

但是您似乎没有等待播放器准备就绪,然后点按播放button。 我想要的是禁用此button,只有当播放器准备就绪时才启用。

使用KVO,可以通知玩家状态的变化:

 playButton.enabled = NO; player = [AVPlayer playerWithURL:fileURL]; [player addObserver:self forKeyPath:@"status" options:0 context:nil]; 

状态变化时会调用这个方法:

 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == player && [keyPath isEqualToString:@"status"]) { if (player.status == AVPlayerStatusReadyToPlay) { playButton.enabled = YES; } else if (player.status == AVPlayerStatusFailed) { // something went wrong. player.error should contain some information } } } 

试图弄清AVPlayer的状态,我遇到了很多麻烦。 status属性似乎并不总是非常有帮助,当我试图处理audio会话中断时,这导致无尽的挫折感。 有时候, AVPlayer告诉我,它已经准备好了(用AVPlayerStatusReadyToPlay ),但实际上并不是这样。 我使用了Jilouc的KVO方法,但在所有情况下都不起作用。

为了补充,当状态属性不是有用的,我通过查看AVPlayercurrentItem (这是一个AVPlayerItem )的loadedTimeRanges属性来查询AVPlayer已经加载的stream的数量。

这有点令人困惑,但是看起来像这样:

 NSValue *val = [[[audioPlayer currentItem] loadedTimeRanges] objectAtIndex:0]; CMTimeRange timeRange; [val getValue:&timeRange]; CMTime duration = timeRange.duration; float timeLoaded = (float) duration.value / (float) duration.timescale; if (0 == timeLoaded) { // AVPlayer not actually ready to play } else { // AVPlayer is ready to play } 

经过很多研究并尝试了很多方法,我注意到AVPlayer对象准备播放时status观察者通常并不是更好的 ,因为对象可以准备好播放,但这并不意味着它会立即播放。

知道这个更好的主意是与loadedTimeRanges

对于注册观察员

 [playerClip addObserver:self forKeyPath:@"currentItem.loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; 

听观察者

 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == playerClip && [keyPath isEqualToString:@"currentItem.loadedTimeRanges"]) { NSArray *timeRanges = (NSArray*)[change objectForKey:NSKeyValueChangeNewKey]; if (timeRanges && [timeRanges count]) { CMTimeRange timerange=[[timeRanges objectAtIndex:0]CMTimeRangeValue]; float currentBufferDuration = CMTimeGetSeconds(CMTimeAdd(timerange.start, timerange.duration)); CMTime duration = playerClip.currentItem.asset.duration; float seconds = CMTimeGetSeconds(duration); //I think that 2 seconds is enough to know if you're ready or not if (currentBufferDuration > 2 || currentBufferDuration == seconds) { // Ready to play. Your logic here } } else { [[[UIAlertView alloc] initWithTitle:@"Alert!" message:@"Error trying to play the clip. Please try again" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil] show]; } } } 

为了移除观察者(dealloc,viewWillDissapear或注册观察者之前)它的一个好地方被调用

 - (void)removeObserverForTimesRanges { @try { [playerClip removeObserver:self forKeyPath:@"currentItem.loadedTimeRanges"]; } @catch(id anException){ NSLog(@"excepcion remove observer == %@. Remove previously or never added observer.",anException); //do nothing, obviously it wasn't attached because an exception was thrown } } 

我有没有得到任何callback的问题。

原来这取决于你如何创buildstream。 在我的情况下,我使用了一个playerItem来初始化,因此我不得不将观察者添加到该项目。

例如:

 - (void) setup { ... self.playerItem = [AVPlayerItem playerItemWithAsset:asset]; self.player = [AVPlayer playerWithPlayerItem:self.playerItem]; ... // add callback [self.player.currentItem addObserver:self forKeyPath:@"status" options:0 context:nil]; } // the callback method - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSLog(@"[VideoView] player status: %i", self.player.status); if (object == self.player.currentItem && [keyPath isEqualToString:@"status"]) { if (self.player.currentItem.status == AVPlayerStatusReadyToPlay) { //do stuff } } } // cleanup or it will crash -(void)dealloc { [self.player.currentItem removeObserver:self forKeyPath:@"status"]; } 

基于Tim Camber的回答 ,这里是我使用的Swift函数:

 private func isPlayerReady(_ player:AVPlayer?) -> Bool { guard let player = player else { return false } let ready = player.status == .readyToPlay let timeRange = player.currentItem?.loadedTimeRanges.first as? CMTimeRange guard let duration = timeRange?.duration else { return false } // Fail when loadedTimeRanges is empty let timeLoaded = Int(duration.value) / Int(duration.timescale) // value/timescale = seconds let loaded = timeLoaded > 0 return ready && loaded } 

或者,作为一个扩展

 extension AVPlayer { var ready:Bool { let timeRange = currentItem?.loadedTimeRanges.first as? CMTimeRange guard let duration = timeRange?.duration else { return false } let timeLoaded = Int(duration.value) / Int(duration.timescale) // value/timescale = seconds let loaded = timeLoaded > 0 return status == .readyToPlay && loaded } } 

检查玩家的currentItem的状态:

 if (player.currentItem.status == AVPlayerItemStatusReadyToPlay) 
 private var playbackLikelyToKeepUpContext = 0 

对于注册观察员

 avPlayer.addObserver(self, forKeyPath: "currentItem.playbackLikelyToKeepUp", options: .new, context: &playbackLikelyToKeepUpContext) 

听观察者

  override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if context == &playbackLikelyToKeepUpContext { if avPlayer.currentItem!.isPlaybackLikelyToKeepUp { // loadingIndicatorView.stopAnimating() or something else } else { // loadingIndicatorView.startAnimating() or something else } } } 

去除观察者

 deinit { avPlayer.removeObserver(self, forKeyPath: "currentItem.playbackLikelyToKeepUp") } 

代码中的关键点是实例属性isPlaybackLikelyToKeepUp。

我认为它更好地分配使用initWithUrl播放器。 我不认为这会解决你的问题,但更好。