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.
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];
}];
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.
AVFoundation
Player()
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 UITableView
et 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()
}
}
})
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)
}
}