AVPlayer在缓冲音频流的开始时“冻结”应用程序

时间:2011-10-09 03:14:48

标签: ios audio-streaming dropbox avplayer avqueueplayer

我正在使用AVQueuePlayer的子类,当我使用流式网址添加新的AVPlayerItem时,该应用会冻结大约一两秒钟。通过冻结我的意思是它不响应UI上的触摸。此外,如果我已经播放了一首歌曲,然后将另一首歌曲添加到队列中,AVQueuePlayer会自动开始预加载该歌曲,同时它仍在播放第一首歌曲。这使得应用程序不会响应UI上的触摸两秒钟,就像添加第一首歌但歌曲仍在播放时一样。所以这意味着AVQueuePlayer正在主线程中做一些导致明显“冻结”的事情。

我正在使用insertItem:afterItem:添加AVPlayerItem。我测试并确保这是造成延迟的方法。也许它可能是AVPlayerItem在将AVQueuePlayer添加到队列时被[[self restClient] loadStreamableURLForFile:metadata.path]; 激活时所做的事情。

必须指出我使用Dropbox API v1 beta通过此方法调用获取流式网址:

AVQueuePlayer

然后,当我收到流网址时,我将其发送到[self.player insertItem:[AVPlayerItem playerItemWithURL:url] afterItem:nil]; ,如下所示:

AVPlayer

所以我的问题是:我该如何避免这种情况? 我是否应该在没有{{1}}帮助的情况下自行预加载音频流?如果是这样,我该怎么做?

感谢。

3 个答案:

答案 0 :(得分:69)

不要使用playerItemWithURL它的同步。 当您收到带有网址的回复时,请尝试以下操作:

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

[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() {
    [self.player insertItem:[AVPlayerItem playerItemWithAsset:asset] afterItem:nil];
}];

答案 1 :(得分:10)

Bump,因为这是一个评价很高的问题,在线的类似问题要么已经过时了,要么不是很好。使用AVKitAVFoundation,整个想法非常简单,这意味着不再依赖于第三方库。唯一的问题是它需要一些修修补补并将各个部分放在一起。

AVFoundation使用Player()进行url UITableView初始化显然不是线程安全的,或者说它不是故意的。这意味着,无论您如何在后台线程中初始化,都会在主队列中加载播放器属性,导致UI冻结,特别是在UICollectionViewsAVAsset中。为了解决这个问题,Apple提供了cancellable来获取URL并协助加载媒体属性,如曲目,播放,持续时间等,并且可以异步执行,最好的部分是此加载过程是可取消的(与其他不同)调度队列后台线程,其中结束任务可能不是那么直接)。这意味着,当您在表视图或集合视图上快速滚动时,无需担心后台中存在延迟的僵尸线程,最终会在内存中堆积大量未使用的对象。这个AVAsset功能非常棒,并允许我们取消任何延迟loadValuesAsynchronously异步加载(如果它正在进行中)但仅在单元格出列时。异步加载过程可以由loadValuesAsynchronously方法调用,并且可以在以后任何时候(如果仍在进行中)被随意取消。

请勿使用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 } }) 的结果忘记异常处理。在 Swift(3/4)中,如果异步处理失败(由于网络速度慢等),您将如何异步加载视频并处理情况 -

<强> TL; DR

播放视频

UITableView

注意

根据您的应用想要实现的目标,您可能仍需要进行一些修改以进行调整,以便在UICollectionViewAVPlayerItem中进行更顺畅的滚动。您可能还需要在AVPlayerItem属性上实施一定数量的KVO才能使其发挥作用,并且此处有大量帖子详细讨论AVPlayerLooper KVO。

通过资产循环(视频循环/ GIF)

要循环播放视频,您可以使用上述相同的方法并引入duration。这是一个循环视频的示例代码(或者可能是GIF风格的短视频)。 注意使用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 } }) 密钥,这是我们的视频循环所必需的。

AVPlayerLooper

编辑:根据documentationduration要求资源的timeRange: timeRange属性完全加载,以便能够循环播放视频。此外,如果您想要无限循环,AVPlayerLooper初始化中AVPlayerLooper的开始和结束时间范围实际上是可选的。我已经意识到,自从我发布此答案后,AVAsset在循环视频中的准确率仅为70-80%,尤其是当您//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() } } }) 需要从网址流式传输视频时。为了解决这个问题,有一种完全不同(但很简单)的方法来循环播放视频 -

> nvm use v5.12.0

答案 2 :(得分:5)

Gigisommo对 Swift 3 的回答,包括评论的反馈:

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)

        }

}