Зная, когда AVPlayer объект готов к воспроизведению
я пытаюсь играть MP3
файл, который передается в UIView
из (хранящиеся в NSURL *fileURL
переменной).
я инициализации AVPlayer
С:
player = [AVPlayer playerWithURL:fileURL];
NSLog(@"Player created:%d",player.status);
The NSLog
печать Player created:0,
что я понял, означает, что он еще не готов играть.
когда я нажимаю на play 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??
в консоли.
Если я, однако, заменить if
условие, которое проверяет, если AVPlayer
готов играть, с простым if(!isPlaying)
..., затем музыка играет во второй раз, когда я нажимаю на play 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
.
что я могу сделать, чтобы игра работала правильно при первом нажатии на play UIButton
?
(т. е., как я могу убедиться, что AVPlayer
не просто создан, но и готов к играть?)
9 ответов:
вы играете удаленный файл. Это может занять некоторое время
AVPlayer
чтобы буферизировать достаточное количество данных и быть готовым к воспроизведению файла (см. руководство по программированию AV Foundation)но вы, кажется, не ждете, пока игрок будет готов, прежде чем нажать кнопку воспроизведения. Я бы отключил эту кнопку и включил ее только тогда, когда игрок готов.
используя 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
свойство не всегда казалось очень полезным, и это привело к бесконечному разочарованию, когда я пытался обрабатывать прерывания аудиосеанса. ИногдаAVPlayer
сказал мне, что он был готов играть (сAVPlayerStatusReadyToPlay
) когда это на самом деле не казалось. Я использовал метод кво Джилоука, но он не работал во всех случаях.чтобы дополнить, когда свойство status не было полезно, я запросил количество потока, который загрузил AVPlayer, посмотрев на
loadedTimeRanges
свойстваAVPlayer
' scurrentItem
(который являетсяAVPlayerItem
).все это немного запутанно, но вот как это выглядит:
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 }
после исследования много и попробовать много способов, я заметил, что обычно
status
наблюдатель не лучше знать действительно, когдаAVPlayer
объект готов к игре, потому что объект может быть готов к игре, но это не значит, что он будет играть сразу.лучшая идея для знать это с
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 или перед регистрацией наблюдателя) его хорошие места для called
- (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 } }
Быстрое Решение
var observer: NSKeyValueObservation? func prepareToPlay() { let url = <#Asset URL#> // Create asset to be played asset = AVAsset(url: url) let assetKeys = [ "playable", "hasProtectedContent" ] // Create a new AVPlayerItem with the asset and an // array of asset keys to be automatically loaded playerItem = AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: assetKeys) // Register as an observer of the player item's status property self.observer = playerItem.observe(\.status, options: [.new, .old], changeHandler: { (playerItem, change) in if playerItem.status == .readyToPlay { //Do your work here } }) // Associate the player item with the player player = AVPlayer(playerItem: playerItem) }
также вы можете сделать недействительным наблюдателя таким образом
self.observer.invalidate()
важно: вы должны сохранить переменную наблюдателя, иначе она будет освобождена, и обработчик изменений больше не будет вызываться. Поэтому не определяйте наблюдателя как переменную функции, а определите его как переменную экземпляра, как в данном примере.
этот синтаксис наблюдателя ключевых значений является новым для Swift 4.
для получения дополнительной информации см. здесь https://github.com/ole/whats-new-in-swift-4/blob/master/Whats-new-in-Swift-4.playground/Pages/Key%20paths.xcplaygroundpage/Contents.swift
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.
У меня были проблемы с не получением каких-либо обратных вызовов.
оказывается, это зависит от того, как вы создаете поток. В моем случае я использовал 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"]; }
на основе Тим Камбер ответ, вот функция 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 } }