Я пытаюсь воспроизвести MP3
файл, который передается UIView
из предыдущего UIView
(хранится в NSURL *fileURL
переменной).
Я инициализирую 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??
в консоль. Однако, если я заменю if
условие, которое проверяет AVPlayer
готовность к воспроизведению, простым 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
он не просто создан, но и готов к игре?)
player.currentItem.status
верно, когдаplayer.status
нет. Не уверен, в чем разница.У меня было много проблем, пытаясь определить статус файла
AVPlayer
.status
Собственность не всегда , кажется, очень полезно, и это привело к бесконечному разочарованию , когда я пытался обрабатывать аудио - сеансы прерывания. ИногдаAVPlayer
мне говорили, что он готов к игре,AVPlayerStatusReadyToPlay
хотя на самом деле это не так. Я использовал метод KVO Жилука, но он работал не во всех случаях.В дополнении, когда свойство статуса не является полезным, я оспаривал сумму потока , что AVPlayer был загружен, глядя на
loadedTimeRanges
свойствеAVPlayer
«сcurrentItem
(который является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 }
источник
timeLoaded
есть) из CMTime: CMTimeGetSecondsAVPlayer
кажется, устанавливаетсяstatus == AVPlayerStatusReadyToPlay
слишком рано, когда он еще не готов к игре. Чтобы это работало, вы можете, например, обернуть приведенный выше код вNSTimer
вызов.Быстрое решение
var observer: NSKeyValueObservation? func prepareToPlay() { let url = <#Asset URL#> // Create asset to be played let 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 let 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()
Важно: вы должны сохранить переменную наблюдателя, иначе она будет освобождена, и changeHandler больше не будет вызываться. Поэтому не определяйте наблюдателя как функциональную переменную, а определяйте его как переменную экземпляра, как в данном примере.
Этот синтаксис наблюдателя значения ключа является новым в 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
источник
После большого исследования и опробования многих способов я заметил, что обычно
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]; } } }
- (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 } }
источник
currentBufferDuration < 2
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.
источник
forKeyPath: #keyPath(AVPlayer.currentItem.isPlaybackLikelyToKeepUp)
Основываясь на ответе Тима Камбера , я использую функцию 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 } }
источник
AVPlayerItemNewAccessLogEntry
иAVPlayerItemDidPlayToEndTime
в своем проекте. Афаик работает.loadedTimeRanges
.У меня были проблемы с тем, что я не получал обратных вызовов.
Оказывается, это зависит от того, как вы создаете поток. В моем случае я использовал 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"]; }
источник
AVPlayerItemStatusReadyToPlay
?Проверьте статус текущего элемента игрока:
if (player.currentItem.status == AVPlayerItemStatusReadyToPlay)
источник
AVPlayerItemStatusUnkown
. Только через какое - то время, он будет иметь возможность знать , если онAVPlayerItemStatusReadyToPlay
илиAVPlayerItemStatusFailed
Swift 4:
var player:AVPlayer! override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReadyToPlay(notification:)), name: .AVPlayerItemNewAccessLogEntry, object: player?.currentItem) } @objc func playerItemDidReadyToPlay(notification: Notification) { if let _ = notification.object as? AVPlayerItem { // player is ready to play now!! } }
источник
Ответ @JoshBernfeld не сработал для меня. Не знаю почему. Он заметил
playerItem.observe(\.status
. Я должен был наблюдатьplayer?.observe(\.currentItem?.status
. Похоже, это одно и то же,playerItem status
собственность.var playerStatusObserver: NSKeyValueObservation? player?.automaticallyWaitsToMinimizeStalling = false // starts faster playerStatusObserver = player?.observe(\.currentItem?.status, options: [.new, .old]) { (player, change) in switch (player.status) { case .readyToPlay: // here is where it's ready to play so play player DispatchQueue.main.async { [weak self] in self?.player?.play() } case .failed, .unknown: print("Media Failed to Play") @unknown default: break } }
когда вы закончите использовать набор плеера
playerStatusObserver = nil
источник