了解AVPlayer对象什么时候可以播放
我试图播放一个MP3
文件,从以前的UIView
(存储在一个NSURL *fileURL
variables)传递给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方法,但在所有情况下都不起作用。
为了补充,当状态属性不是有用的,我通过查看AVPlayer
的currentItem
(这是一个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
播放器。 我不认为这会解决你的问题,但更好。