Próbuję odtworzyć MP3
plik, który jest przekazywany UIView
z poprzedniego UIView
(przechowywany w NSURL *fileURL
zmiennej).
Inicjalizuję za AVPlayer
pomocą:
player = [AVPlayer playerWithURL:fileURL];
NSLog(@"Player created:%d",player.status);
Te NSLog
odciski Player created:0,
który Pomyślałem oznacza, że nie jest jeszcze gotowy do gry.
Kiedy klikam przycisk odtwarzania UIButton
, uruchamiany kod to:
-(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??");
}
}
Kiedy to uruchamiam, zawsze wchodzę Error in player??
do konsoli. Jeśli jednak zmienię if
warunek, który sprawdza, czy AVPlayer
jest gotowy do odtwarzania, prostym if(!isPlaying)
..., wtedy muzyka jest odtwarzana DRUGI RAZ, kiedy klikam grę UIButton
.
Dziennik konsoli to:
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**
Widzę, że DRUGI CZAS player.status
wydaje się mieć 1, co, jak sądzę, jest AVPlayerReadyToPlay
.
Co mogę zrobić, aby gra działała prawidłowo po pierwszym kliknięciu UIButton
? (tj. jak mogę się upewnić, że AVPlayer
plik nie jest tylko utworzony, ale także gotowy do gry?)
źródło
player.currentItem.status
wynika, żeplayer.status
nie jest. Nie wiem, jakie są różnice.Miałem wiele problemów z ustaleniem statusu pliku
AVPlayer
.status
Nieruchomość nie zawsze wydają się być strasznie pomocny, a to doprowadziło do niekończącej się frustracji, kiedy próbuje obsłużyć dźwiękowych przerw sesji. CzasamiAVPlayer
mówili mi, że jest gotowy do gry (zAVPlayerStatusReadyToPlay
), podczas gdy tak naprawdę nie wyglądał. Użyłem metody KVO firmy Jilouc, ale nie działała we wszystkich przypadkach.Aby uzupełnić, gdy właściwość status nie była użyteczna, zapytałem o ilość strumienia załadowanego przez AVPlayer, patrząc na
loadedTimeRanges
właściwośćAVPlayer
'scurrentItem
(która jestAVPlayerItem
).To trochę zagmatwane, ale tak to wygląda:
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 }
źródło
timeLoaded
jest) z CMTime: CMTimeGetSecondsAVPlayer
wydaje się ustawiaćstatus == AVPlayerStatusReadyToPlay
zbyt wcześnie, gdy naprawdę nie jest gotowy do gry. Aby to zadziałało, możeszNSTimer
na przykład opakować powyższy kod w wywołanie.Szybkie rozwiązanie
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) }
W ten sposób możesz również unieważnić obserwatora
self.observer.invalidate()
Ważne: musisz zachować zmienną obserwatora, w przeciwnym razie zostanie ona zwolniona, a changeHandler nie będzie już wywoływany. Dlatego nie definiuj obserwatora jako zmiennej funkcji, ale definiuj ją jako zmienną instancji, tak jak w podanym przykładzie.
Ta składnia obserwatora wartości klucza jest nowa w języku Swift 4.
Więcej informacji można znaleźć tutaj: https://github.com/ole/whats-new-in-swift-4/blob/master/Whats-new-in-Swift-4.playground/Pages/Key%20paths.xcplaygroundpage/ Contents.swift
źródło
Po wielu badaniach i wypróbowaniu wielu sposobów zauważyłem, że zwykle
status
obserwator nie jest lepszy, aby wiedzieć, kiedyAVPlayer
obiekt jest gotowy do gry , ponieważ obiekt może być gotowy do gry, ale nie oznacza to, że będzie odtwarzany natychmiast.Lepszy pomysł, aby wiedzieć, że to jest z
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 } }
źródło
currentBufferDuration < 2
private var playbackLikelyToKeepUpContext = 0
Dla obserwatora rejestru
avPlayer.addObserver(self, forKeyPath: "currentItem.playbackLikelyToKeepUp", options: .new, context: &playbackLikelyToKeepUpContext)
Posłuchaj obserwatora
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 } } }
Aby usunąć obserwatora
deinit { avPlayer.removeObserver(self, forKeyPath: "currentItem.playbackLikelyToKeepUp") }
Kluczowym punktem w kodzie jest właściwość instancji isPlaybackLikelyToKeepUp.
źródło
forKeyPath: #keyPath(AVPlayer.currentItem.isPlaybackLikelyToKeepUp)
Na podstawie odpowiedzi Tima Cambera , oto funkcja Swift, której używam:
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 }
Lub jako rozszerzenie
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 } }
źródło
AVPlayerItemNewAccessLogEntry
iwAVPlayerItemDidPlayToEndTime
moim projekcie. Afaik to działa.loadedTimeRanges
.Miałem problemy z nieotrzymaniem żadnych oddzwonień.
Okazuje się, że zależy to od tego, jak utworzysz strumień. W moim przypadku użyłem playerItem do zainicjowania, więc zamiast tego musiałem dodać obserwatora do elementu.
Na przykład:
- (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"]; }
źródło
AVPlayerItemStatusReadyToPlay
?Sprawdź status currentItem gracza:
if (player.currentItem.status == AVPlayerItemStatusReadyToPlay)
źródło
AVPlayerItemStatusUnkown
. Dopiero za jakiś czas będzie mógł wiedzieć, czy tak jest,AVPlayerItemStatusReadyToPlay
czyAVPlayerItemStatusFailed
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!! } }
źródło
Odpowiedź @ JoshBernfeld nie działa dla mnie. Nie pewny dlaczego. Obserwował
playerItem.observe(\.status
. Musiałem obserwowaćplayer?.observe(\.currentItem?.status
. Wygląda na to, że to to samo,playerItem status
własność.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 } }
po zakończeniu korzystania z zestawu odtwarzacza
playerStatusObserver = nil
źródło