ios audio-streaming dropbox avplayer avqueueplayer

ios - AVPlayer "congela" la aplicación al inicio del almacenamiento en búfer de una transmisión de audio



audio-streaming dropbox (3)

Estoy usando una subclase de AVQueuePlayer y cuando agrego un nuevo AVPlayerItem con una URL de transmisión, la aplicación se congela durante uno o dos segundos. Al congelarme quiero decir que no responde a los toques en la interfaz de usuario. Además, si ya tengo una canción en reproducción y luego agrego otra a la cola, AVQueuePlayer comienza a cargar automáticamente la canción mientras todavía está transmitiendo la primera. Esto hace que la aplicación no responda a los toques en la IU durante dos segundos, al igual que cuando se agrega la primera canción, pero la canción todavía se está reproduciendo. Entonces eso significa que AVQueuePlayer está haciendo algo en el hilo principal que está causando el aparente "congelamiento".

Estoy usando insertItem:afterItem: para agregar mi AVPlayerItem . Probé y me aseguré de que este fuera el método que causaba el retraso. Quizás podría ser algo que AVPlayerItem hace cuando AVPlayerItem lo activa al momento de agregarlo a la cola.

Debo señalar que estoy usando Dropbox API v1 beta para obtener la URL de transmisión mediante esta llamada a método:

[[self restClient] loadStreamableURLForFile:metadata.path];

Luego, cuando reciba la URL de la AVQueuePlayer envío a AVQueuePlayer siguiente manera:

[self.player insertItem:[AVPlayerItem playerItemWithURL:url] afterItem:nil];

Entonces mi pregunta es: ¿cómo puedo evitar esto? ¿Debería realizar la precarga de una transmisión de audio por mi cuenta sin la ayuda de AVPlayer ? Si es así, ¿cómo hago esto?

Gracias.


Bump, ya que esta es una pregunta altamente calificada y preguntas similares en línea tienen respuestas obsoletas o no son geniales. La idea general es bastante directa con AVKit y AVFoundation , lo que significa que ya no dependerá de bibliotecas de terceros. El único problema es que requirió algunos ajustes y unió las piezas.

AVFoundation de AVFoundation Player() con url aparentemente no es segura para subprocesos, o mejor dicho, no está destinada a ser. Lo que significa que, sin importar cómo lo inicialices en un subproceso en segundo plano, los atributos del jugador se UITableView en la cola principal, lo que provocará bloqueos en la UI, especialmente en UITableView y UICollectionViews . Para resolver este problema, Apple proporcionó AVAsset que toma una URL y ayuda a cargar los atributos de los medios como pista, reproducción, duración, etc. y puede hacerlo de forma asíncrona, y la mejor parte es que este proceso de carga es cancelable (a diferencia de otros hilos de fondo de cola de despacho). donde terminar una tarea puede no ser tan sencillo). Esto significa que no hay necesidad de preocuparse por la persistencia de los subprocesos zombi en segundo plano a medida que se desplaza rápidamente en una vista de tabla o de colección, finalmente acumulando en la memoria un montón de objetos no utilizados. Esta característica cancelable es excelente y nos permite cancelar cualquier carga asíncrona AVAsset persistente si está en progreso, pero solo durante la eliminación de la cola de la celda. El proceso de carga asincrónica puede invocarse mediante el método loadValuesAsynchronously y puede cancelarse (según lo desee) en cualquier momento posterior (si todavía está en curso).

No se olvide de manejar de manera correcta los resultados de loadValuesAsynchronously . En Swift (3/4) , así es como cargarías un video de forma asíncrona y manejarías situaciones si el proceso asíncrono falla (debido a redes lentas, etc.) -

TL; DR

PARA JUGAR UN VIDEO

let asset = AVAsset(url: URL(string: self.YOUR_URL_STRING)) let keys: [String] = ["playable"] var player: AVPlayer! asset.loadValuesAsynchronously(forKeys: keys, completionHandler: { var error: NSError? = nil let status = asset.statusOfValue(forKey: "playable", error: &error) switch status { case .loaded: DispatchQueue.main.async { let item = AVPlayerItem(asset: asset) self.player = AVPlayer(playerItem: item) let playerLayer = AVPlayerLayer(player: self.player) playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill playerLayer.frame = self.YOUR_VIDEOS_UIVIEW.bounds self.YOUR_VIDEOS_UIVIEW.layer.addSublayer(playerLayer) self.player.isMuted = true self.player.play() } break case .failed: DispatchQueue.main.async { //do something, show alert, put a placeholder image etc. } break case .cancelled: DispatchQueue.main.async { //do something, show alert, put a placeholder image etc. } break default: break } })

NOTA :

En función de lo que tu aplicación quiera lograr, es posible que aún tengas que hacer algunos ajustes para sintonizarla para obtener un desplazamiento más suave en una UITableView o UICollectionView . También es posible que necesite implementar una cierta cantidad de KVO en las propiedades de AVPlayerItem para que funcione y hay muchas publicaciones aquí en SO que tratan AVPlayerItem KVO en detalle.

PARA BUCLE A TRAVÉS DE ACTIVOS (video loops / GIF)

Para reproducir un video, puede usar el mismo método anterior e introducir AVPlayerLooper . Aquí hay un código de muestra para reproducir un video (o tal vez un video corto en estilo GIF). Tenga en cuenta el uso de la clave de duration que se requiere para nuestro video loop.

let asset = AVAsset(url: URL(string: self.YOUR_URL_STRING)) let keys: [String] = ["playable","duration"] var player: AVPlayer! var playerLooper: AVPlayerLooper! asset.loadValuesAsynchronously(forKeys: keys, completionHandler: { var error: NSError? = nil let status = asset.statusOfValue(forKey: "duration", error: &error) switch status { case .loaded: DispatchQueue.main.async { let playerItem = AVPlayerItem(asset: asset) self.player = AVQueuePlayer() let playerLayer = AVPlayerLayer(player: self.player) //define Timerange for the loop using asset.duration let duration = playerItem.asset.duration let start = CMTime(seconds: duration.seconds * 0, preferredTimescale: duration.timescale) let end = CMTime(seconds: duration.seconds * 1, preferredTimescale: duration.timescale) let timeRange = CMTimeRange(start: start, end: end) self.playerLooper = AVPlayerLooper(player: self.player as! AVQueuePlayer, templateItem: playerItem, timeRange: timeRange) playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill playerLayer.frame = self.YOUR_VIDEOS_UIVIEW.bounds self.YOUR_VIDEOS_UIVIEW.layer.addSublayer(playerLayer) self.player.isMuted = true self.player.play() } break case .failed: DispatchQueue.main.async { //do something, show alert, put a placeholder image etc. } break case .cancelled: DispatchQueue.main.async { //do something, show alert, put a placeholder image etc. } break default: break } })

EDITAR : Según la documentation , AVPlayerLooper requiere que la propiedad de duration del activo esté completamente cargada para poder recorrer los videos. Además, el timeRange: timeRange con el timeRange: timeRange inicial y final en la inicialización de AVPlayerLooper es realmente opcional si desea un bucle infinito. También me he dado cuenta desde que AVPlayerLooper esta respuesta que AVPlayerLooper tiene una AVPlayerLooper solo un 70-80% en la AVAsset bucles de videos, especialmente si su AVAsset necesita transmitir el video desde una URL. Para resolver este problema, existe un enfoque totalmente diferente (aunque simple) para reproducir un video:

//this will loop the video since this is a Gif let interval = CMTime(value: 1, timescale: 2) self.timeObserverToken = self.player?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main, using: { (progressTime) in if let totalDuration = self.player?.currentItem?.duration{ if progressTime == totalDuration{ self.player?.seek(to: kCMTimeZero) self.player?.play() } } })


La respuesta de Gigisommo para Swift 3 incluye los comentarios de los comentarios:

let asset = AVAsset(url: url) let keys: [String] = ["playable"] asset.loadValuesAsynchronously(forKeys: keys) { DispatchQueue.main.async { let item = AVPlayerItem(asset: asset) self.playerCtrl.player = AVPlayer(playerItem: item) } }


No use playerItemWithURL es sincronización. Cuando reciba la respuesta con la URL, intente esto:

AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil]; NSArray *keys = @[@"playable"]; [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() { [self.player insertItem:[AVPlayerItem playerItemWithAsset:asset] afterItem:nil]; }];