Estoy tratando de reproducir un MP3archivo que se pasa a un UIViewanterior UIView(almacenado en una NSURL *fileURLvariable).
Estoy inicializando un AVPlayercon:
player = [AVPlayer playerWithURL:fileURL];
NSLog(@"Player created:%d",player.status);
Las NSLogimpresiones 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 ifcondición que comprueba si AVPlayerestá 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.statusparece 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 AVPlayerno solo se haya creado, sino que también esté listo para jugar?)
fuente

player.currentItem.statuses exacto cuandoplayer.statusno lo es. No estoy seguro de cuáles son las diferencias.Tuve muchos problemas para tratar de averiguar el estado de un
AVPlayer. Lastatuspropiedad 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 vecesAVPlayerme decían que estaba listo para jugarAVPlayerStatusReadyToPlaycuando 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
loadedTimeRangespropiedad 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
timeLoadedes lo que es) de CMTime: CMTimeGetSecondsAVPlayerparece establecersestatus == AVPlayerStatusReadyToPlaydemasiado pronto cuando no está listo para jugar realmente. Para que esto funcione, puede envolver el código anterior en laNSTimerinvocació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
statusobservador no es el mejor para saber realmente cuándo elAVPlayerobjeto 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 < 2private var playbackLikelyToKeepUpContext = 0Para 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
AVPlayerItemNewAccessLogEntryyAVPlayerItemDidPlayToEndTimeen 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 esAVPlayerItemStatusReadyToPlayoAVPlayerItemStatusFailedRá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 statuspropiedad.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 = nilfuente