Ich versuche, eine MP3
Datei abzuspielen , die an UIView
eine frühere UIView
(in einer NSURL *fileURL
Variablen gespeichert ) übergeben wird.
Ich initialisiere ein AVPlayer
mit:
player = [AVPlayer playerWithURL:fileURL];
NSLog(@"Player created:%d",player.status);
Die NSLog
Drucke, Player created:0,
die ich mir vorgestellt habe, bedeuten, dass es noch nicht spielbereit ist.
Wenn ich auf die Wiedergabe UIButton
klicke, lautet der von mir ausgeführte Code:
-(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??");
}
}
Wenn ich das starte, steige ich immer Error in player??
in die Konsole. Wenn ich jedoch die if
Bedingung, die prüft, ob AVPlayer
sie spielbereit ist, durch eine einfache if(!isPlaying)
... ersetze , wird die Musik das ZWEITE MAL abgespielt , wenn ich auf die Wiedergabe klicke UIButton
.
Das Konsolenprotokoll lautet:
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**
Ich sehe, dass das ZWEITE MAL player.status
1 zu halten scheint, was ich vermute AVPlayerReadyToPlay
.
Was kann ich tun, damit das Spielen beim ersten Klicken richtig funktioniert UIButton
? (dh wie kann ich sicherstellen, dass das AVPlayer
nicht nur erstellt, sondern auch spielbereit ist?)
quelle
player.currentItem.status
ist genau, wennplayer.status
nicht. Ich bin mir nicht sicher, was die Unterschiede sind.Ich hatte große Probleme, den Status eines herauszufinden
AVPlayer
. Dasstatus
Anwesen schien nicht immer furchtbar hilfreich zu sein, und dies führte zu endloser Frustration, als ich versuchte, mit Unterbrechungen der Audiositzung umzugehen. ManchmalAVPlayer
sagte mir das, es sei bereit zu spielen,AVPlayerStatusReadyToPlay
wenn es nicht wirklich zu sein schien. Ich habe die KVO-Methode von Jilouc verwendet, aber sie hat nicht in allen Fällen funktioniert.Als Ergänzung dazu habe ich, wenn die Statuseigenschaft nicht nützlich war, die Menge des Streams abgefragt, den der AVPlayer geladen hatte, indem ich mir die
loadedTimeRanges
Eigenschaft derAVPlayer
's angesehen habecurrentItem
(die eine istAVPlayerItem
).Es ist alles ein wenig verwirrend, aber so sieht es aus:
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 }
quelle
timeLoaded
ist es , was ist) aus CMTime herauszuholen: CMTimeGetSecondsAVPlayer
scheintstatus == AVPlayerStatusReadyToPlay
zu früh zu setzen, wenn es nicht wirklich spielbereit ist. Damit dies funktioniert, können Sie beispielsweise den obigen Code in einenNSTimer
Aufruf einschließen.Schnelle Lösung
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) }
Auf diese Weise können Sie auch den Beobachter ungültig machen
self.observer.invalidate()
Wichtig: Sie müssen die Beobachtervariable beibehalten, da sonst die Zuordnung aufgehoben wird und der changeHandler nicht mehr aufgerufen wird. Definieren Sie den Beobachter also nicht als Funktionsvariable, sondern als Instanzvariable wie im angegebenen Beispiel.
Diese Schlüsselwert-Beobachter-Syntax ist neu in Swift 4.
Weitere Informationen finden Sie hier https://github.com/ole/whats-new-in-swift-4/blob/master/Whats-new-in-Swift-4.playground/Pages/Key%20paths.xcplaygroundpage/ Contents.swift
quelle
Nachdem ich viel recherchiert und viele Möglichkeiten ausprobiert habe, habe ich festgestellt, dass der
status
BeobachterAVPlayer
normalerweise nicht besser weiß, wann das Objekt spielbereit ist , da das Objekt spielbereit sein kann, aber dies bedeutet nicht, dass es sofort abgespielt wird.Die bessere Idee, dies zu wissen, ist mit
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 } }
quelle
currentBufferDuration < 2
private var playbackLikelyToKeepUpContext = 0
Für Registerbeobachter
avPlayer.addObserver(self, forKeyPath: "currentItem.playbackLikelyToKeepUp", options: .new, context: &playbackLikelyToKeepUpContext)
Hören Sie dem Beobachter zu
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 } } }
Zum Entfernen des Beobachters
deinit { avPlayer.removeObserver(self, forKeyPath: "currentItem.playbackLikelyToKeepUp") }
Der Schlüsselpunkt im Code ist die Instanzeigenschaft isPlaybackLikelyToKeepUp.
quelle
forKeyPath: #keyPath(AVPlayer.currentItem.isPlaybackLikelyToKeepUp)
Basierend auf der Antwort von Tim Camber ist hier die Swift-Funktion, die ich benutze:
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 }
Oder als Erweiterung
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 } }
quelle
AVPlayerItemNewAccessLogEntry
undAVPlayerItemDidPlayToEndTime
in meinem Projekt an. Afaik es funktioniert.loadedTimeRanges
.Ich hatte Probleme, keine Rückrufe zu erhalten.
Es stellt sich heraus, dass es davon abhängt, wie Sie den Stream erstellen. In meinem Fall habe ich ein playerItem zum Initialisieren verwendet und musste stattdessen den Beobachter zum Element hinzufügen.
Zum Beispiel:
- (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"]; }
quelle
AVPlayerItemStatusReadyToPlay
?Überprüfen Sie den Status des aktuellen Elements des Players:
if (player.currentItem.status == AVPlayerItemStatusReadyToPlay)
quelle
AVPlayerItemStatusUnkown
. Erst nach einiger Zeit wird es wissen können, ob es istAVPlayerItemStatusReadyToPlay
oderAVPlayerItemStatusFailed
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!! } }
quelle
@ JoshBernfelds Antwort hat bei mir nicht funktioniert. Nicht sicher warum. Er beobachtete
playerItem.observe(\.status
. Ich musste beobachtenplayer?.observe(\.currentItem?.status
. Scheint, als wären sie dasselbe, dasplayerItem status
Eigentum.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 } }
Wenn Sie mit dem Player-Set fertig sind
playerStatusObserver = nil
quelle