AVPlayer "gèle" l'application au début de la mise en tampon d'un flux audio

j'utilise une sous-classe de AVQueuePlayer et quand je ajouter de nouvelles AVPlayerItem avec une URL de streaming l'application gèle pendant environ une seconde ou deux. Par gel je veux dire qu'il ne répond pas aux touches sur L'UI. De plus, si j'ai déjà une chanson qui joue et que j'en ajoute une autre à la file d'attente, AVQueuePlayer démarre automatiquement le préchargement de la chanson pendant qu'elle diffuse la première. Cela rend l'application répond pas à la touche sur l'INTERFACE de deux secondes, comme lors de l'ajout de la première chanson, mais la chanson est toujours de la partie. Ce qui signifie que AVQueuePlayer fait quelque chose dans le thread principal qui provoque le "gel"apparent.

j'utilise insertItem:afterItem: ajouter ma AVPlayerItem. J'ai testé et je me suis assuré que c'était la méthode qui causait le retard. Peut-être que ça pourrait être quelque chose qui AVPlayerItem quand il est activé par AVQueuePlayer au moment de l'ajouter à la file d'attente.

je dois signaler que j'utilise L'API Dropbox v1 beta pour obtenir L'URL de diffusion en utilisant cette méthode appel:

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

puis quand je reçois L'URL du flux Je l'envoie à AVQueuePlayer comme suit:

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

alors ma question Est: Comment puis-je éviter cela? Devrais-je faire le préchargement d'un flux audio moi-même sans l'aide de AVPlayer? Si oui, comment dois-je faire?

Merci.

38
demandé sur pablasso 2011-10-09 07:14:47

3 réponses

Ne pas utiliser playerItemWithURL c'est la synchronisation. Lorsque vous recevez la réponse avec l'url essayez ceci:

AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
NSArray *keys = @[@"playable"];

[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() {
    [self.player insertItem:[AVPlayerItem playerItemWithAsset:asset] afterItem:nil];
}];
69
répondu Gigisommo 2016-03-23 12:37:28

Bump, puisqu'il s'agit d'une question hautement cotée et que des questions similaires en ligne ont des réponses périmées ou ne sont pas géniales. L'idée est assez simple avec AVKit et AVFoundation, ce qui signifie qu'il ne dépend plus des bibliothèques tierces. Le seul problème est qu'il a fallu bricoler et mettre les morceaux ensemble.

AVFoundationPlayer() initialisation url n'est apparemment pas sûr du fil, ou plutôt ce n'est pas censé l'être. Ce qui signifie, peu importe comment vous initialisez - le dans un thread de fond les attributs du lecteur vont être chargés dans la file d'attente principale provoquant des gels dans L'interface utilisateur, en particulier dans UITableViewet UICollectionViews. Pour résoudre ce problème Apple fourni AVAsset qui prend une URL et aide à charger les attributs média comme la piste, la lecture, la durée, etc. et peut le faire de manière asynchrone, avec une meilleure partie étant que ce processus de chargement est annulable (contrairement à d'autres threads de fond de la file D'attente D'Expédition où la fin d'une tâche peut ne pas être si simple transmettre.) Cela signifie, Il n'y a pas besoin de s'inquiéter de fils de zombies persistants dans le fond que vous faites défiler rapidement sur une vue de la table ou la vue de la collection, s'empilant finalement sur la mémoire avec un tas entier d'objets inutilisés. Ce cancellable caractéristique est grande, et nous permet d'annuler n'importe quelle AVAsset charge asynchrone si elle est en cours mais seulement pendant la période de repos cellulaire. Le chargement asynchrone processus peut être invoqué par l' loadValuesAsynchronously et peut être annulée (à volonté) à tout moment ultérieur (si elle est encore en vigueur) progrès.)

N'oubliez pas d'exception gérer correctement en utilisant les résultats de loadValuesAsynchronously. Swift (3/4), voici comment charger une vidéo de manière asynchrone et gérer les situations si le processus asynchrone échoue (en raison de réseaux lents, etc.) -

TL;DR

TO PLAY A 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
   }
})

NOTE:

basé sur ce que votre application veut réaliser vous pouvez encore avoir à faire une certaine quantité de bricoler pour l'accorder pour obtenir le rouleau plus lisse dans un UITableView ou UICollectionView. Vous pouvez également avoir besoin d'implémenter une certaine quantité de KVO sur le AVPlayerItem propriétés pour qu'il fonctionne et il y a beaucoup de posts ici AFIN de discuter AVPlayerItem KVOs en détail.


to LOOP THROUGH ASSETS (video loops/GIFs)

pour boucler une vidéo, vous pouvez utiliser la même méthode ci-dessus et introduire AVPlayerLooper. Voici un exemple de code pour boucler une vidéo (ou peut-être une courte vidéo dans GIF style.) Remarque:duration la clé qui est requise pour notre boucle vidéo.

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
   }
})

EDIT : documentation,AVPlayerLooper nécessite l' duration propriété de l'actif à être entièrement chargé afin d'être en mesure de boucler à travers des vidéos. Aussi, le timeRange: timeRange avec le début et la fin timerange dans le AVPlayerLooper initialisation est vraiment facultatif si vous voulez une boucle infinie. J'ai également réalisé depuis que j'ai posté cette réponse que AVPlayerLooper n'est que d'environ 70 à 80% de précision en boucle des vidéos, surtout si votre AVAsset doit diffuser la vidéo à partir d'une URL. Pour résoudre ce problème, il existe une approche totalement différente (mais simple) de la boucle d'une vidéo-

//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()
                                }
                            }
                        })
4
répondu Annjawn 2018-05-26 18:16:55

réponse de Gigisommo pour Swift 3 incluant les commentaires des commentaires:

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)

        }

}
3
répondu David Seek 2017-05-25 14:32:55