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

z31licg0  于 12个月前  发布在  iOS
关注(0)|答案(4)|浏览(180)

我使用的是AVQueuePlayer的子类,当我添加新的AVPlayerItem和流URL时,应用程序会冻结大约一两秒。冻结是指它不会对用户界面上的触摸做出响应。此外,如果我已经有一首歌在播放,然后又添加了另一首歌到队列中,AVQueuePlayer在播放第一首歌曲时会自动开始预加载歌曲。这会使应用程序在两秒钟内不响应用户界面上的触摸,就像添加第一首歌曲时一样,但歌曲仍然所以这意味着AVQueuePlayer正在主线程中做一些导致明显“冻结”的事情。
我正在使用insertItem:afterItem:来添加我的AVPlayerItem。我测试并确定这是导致延迟的方法。可能是AVPlayerItem在将其添加到队列时被AVQueuePlayer激活时所做的事情。
必须指出的是,我使用Dropbox API v1测试版通过以下方法调用来获取流URL:

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

字符串
然后,当我收到流URL,我发送它到AVQueuePlayer如下:

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


所以我的问题是:我该如何避免这种情况?我应该在没有AVPlayer的帮助下自己预加载音频流吗?如果是,我该怎么做?
谢谢.

9nvpjoqh

9nvpjoqh1#

不要使用playerItemWithURL,它是同步的。当你收到带有URL的响应时,试试这个:

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

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

字符串

i2byvkas

i2byvkas2#

碰撞,因为这是一个高度评价的问题和类似的问题在线要么有过时的答案或不是很好.整个想法是相当直接的与AVKitAVFoundation,这意味着没有更多的依赖于第三方库.唯一的问题是,它采取了一些修修补补,把碎片在一起.
使用url初始化AVFoundationPlayer()显然不是线程安全的,或者说它不应该是线程安全的。这意味着,无论你如何在后台线程中初始化它,播放器属性都会被加载到主队列中,导致UI冻结,特别是在UITableView s和UICollectionViews中。为了解决这个问题,Apple提供了AVAsset,它需要一个URL,并协助加载媒体属性,如轨道,播放,持续时间等,并可以异步进行,最好的部分是,这个加载过程是可取消的(不像其他Dispatch队列后台线程那样结束任务可能不那么直接)。这意味着,当您在表视图或集合视图上快速滚动时,无需担心后台中的延迟僵尸线程,cancellable的这个特性非常棒,它允许我们取消任何正在进行的AVAsset xmlc加载,但只能在信元出队期间取消。xmlc加载过程可以由loadValuesAsynchronously方法调用,并且可以在以后的任何时间(如果仍在进行中)取消(随意)。
别忘了使用loadValuesAsynchronously的结果正确处理异常。在**Swift(3/4)**中,你将如何异步加载视频,以及如何处理如果UVC进程失败(由于网络速度慢等原因)的情况-

TL;DR

播放视频

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

字符串

注意事项

根据你的应用想要实现的目标,你可能仍然需要做一些修改来调整它,以在UITableViewUICollectionView中获得更平滑的滚动。你可能还需要在AVPlayerItem属性上实现一些KVO,以便它工作,SO中有很多文章详细讨论了AVPlayerItem KVO。

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

要循环视频,您可以使用上面介绍的相同方法并引入AVPlayerLooper。这里有一个循环视频(或GIF风格的短视频)的示例代码。注意使用duration键,这是我们视频循环所需的。

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

编辑:根据documentationAVPlayerLooper需要完全加载资产的duration属性,以便能够循环播放视频。此外,如果你想要一个无限循环,在AVPlayerLooper初始化中带有开始和结束时间范围的timeRange: timeRange真的是可选的。自从我发布了这个答案,我也意识到AVPlayerLooper仅仅是关于70-80%的准确率在循环视频,特别是如果你的AVAsset需要从一个URL流视频。为了解决这个问题,有一个完全不同的(但简单的)方法来循环视频-

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

ao218c7q

ao218c7q3#

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)

        }

}

字符串

pkwftd7m

pkwftd7m4#

iOS 15+,请等待

Task {
    let asset = AVAsset(url: url)
    let playable = try await asset.load(.isPlayable)
    if playable {
        player = AVPlayer(playerItem: .init(asset: asset))
        player.play()
    }
}

字符串

相关问题