Estoy tratando de reproducir un MP3
archivo que se pasa a un UIView
anterior UIView
(almacenado en una NSURL *fileURL
variable).
Estoy inicializando un AVPlayer
con:
player = [AVPlayer playerWithURL:fileURL];
NSLog(@"Player created:%d",player.status);
Las NSLog
impresiones Player created:0,
que imaginé significan que aún no está listo para jugar.
Cuando hago clic en reproducir UIButton
, el código que ejecuto es:
-(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??");
}
}
Cuando ejecuto esto, siempre entro Error in player??
en la consola. Sin embargo, si sustituyo la if
condición que comprueba si AVPlayer
está listo para reproducirse, con un simple if(!isPlaying)
..., la música se reproduce la SEGUNDA VEZ que hago clic en reproducir UIButton
.
El registro de la consola es:
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**
Veo que la SEGUNDA VEZ player.status
parece contener 1, que supongo que es AVPlayerReadyToPlay
.
¿Qué puedo hacer para que la reproducción funcione correctamente la primera vez que hago clic en la reproducción UIButton
? (es decir, ¿cómo puedo asegurarme de que AVPlayer
no solo se haya creado, sino que también esté listo para jugar?)
fuente
player.currentItem.status
es exacto cuandoplayer.status
no lo es. No estoy seguro de cuáles son las diferencias.Tuve muchos problemas para tratar de averiguar el estado de un
AVPlayer
. Lastatus
propiedad no siempre pareció ser muy útil, y esto me llevó a una frustración infinita cuando intentaba manejar las interrupciones de la sesión de audio. A vecesAVPlayer
me decían que estaba listo para jugarAVPlayerStatusReadyToPlay
cuando en realidad no parecía estarlo. Usé el método KVO de Jilouc, pero no funcionó en todos los casos.Para complementar, cuando la propiedad de estado no estaba siendo útil, consulté la cantidad de flujo que el AVPlayer había cargado mirando la
loadedTimeRanges
propiedad deAVPlayer
'scurrentItem
(que es anAVPlayerItem
).Todo es un poco confuso, pero así es como se ve:
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 }
fuente
timeLoaded
es lo que es) de CMTime: CMTimeGetSecondsAVPlayer
parece establecersestatus == AVPlayerStatusReadyToPlay
demasiado pronto cuando no está listo para jugar realmente. Para que esto funcione, puede envolver el código anterior en laNSTimer
invocación, por ejemplo.Solución rápida
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) }
También puede invalidar al observador de esta manera.
self.observer.invalidate()
Importante: Debe mantener la variable de observador retenida, de lo contrario, se desasignará y ya no se llamará al changeHandler. Por lo tanto, no defina al observador como una variable de función, sino defínalo como una variable de instancia como en el ejemplo dado.
Esta sintaxis de observador de valores clave es nueva en Swift 4.
Para obtener más información, consulte aquí https://github.com/ole/whats-new-in-swift-4/blob/master/Whats-new-in-Swift-4.playground/Pages/Key%20paths.xcplaygroundpage/ Contenido.swift
fuente
Después de investigar mucho y probar muchas formas, he notado que normalmente el
status
observador no es el mejor para saber realmente cuándo elAVPlayer
objeto está listo para jugar , porque el objeto puede estar listo para jugar, pero esto no significa que se jugará inmediatamente.La mejor idea para saber esto es con
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 } }
fuente
currentBufferDuration < 2
private var playbackLikelyToKeepUpContext = 0
Para registrar observador
avPlayer.addObserver(self, forKeyPath: "currentItem.playbackLikelyToKeepUp", options: .new, context: &playbackLikelyToKeepUpContext)
Escucha al observador
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 } } }
Para quitar observador
deinit { avPlayer.removeObserver(self, forKeyPath: "currentItem.playbackLikelyToKeepUp") }
El punto clave en el código es la propiedad de instancia isPlaybackLikelyToKeepUp.
fuente
forKeyPath: #keyPath(AVPlayer.currentItem.isPlaybackLikelyToKeepUp)
Según la respuesta de Tim Camber , aquí está la función Swift que uso:
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 }
O, como una extensión
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 } }
fuente
AVPlayerItemNewAccessLogEntry
yAVPlayerItemDidPlayToEndTime
en mi proyecto. Afaik funciona.loadedTimeRanges
.Tuve problemas al no recibir ninguna devolución de llamada.
Resulta que depende de cómo crees la transmisión. En mi caso, utilicé un playerItem para inicializar y, por lo tanto, tuve que agregar el observador al elemento.
Por ejemplo:
- (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"]; }
fuente
AVPlayerItemStatusReadyToPlay
?Compruebe el estado del elemento actual del jugador:
if (player.currentItem.status == AVPlayerItemStatusReadyToPlay)
fuente
AVPlayerItemStatusUnkown
. Solo después de algún tiempo, podrá saber si esAVPlayerItemStatusReadyToPlay
oAVPlayerItemStatusFailed
Rápido 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!! } }
fuente
La respuesta de @JoshBernfeld no funcionó para mí. No estoy seguro de por qué. Observó
playerItem.observe(\.status
. Tuve que observarplayer?.observe(\.currentItem?.status
. Parece que son lo mismo, laplayerItem status
propiedad.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 } }
cuando haya terminado de usar el reproductor
playerStatusObserver = nil
fuente