关于AVPlayerItem初始化时传入duration至automaticallyLoadedAssetKeys的疑问
duration到automaticallyLoadedAssetKeys的合理性与有效性保障 好问题!其实把duration传入automaticallyLoadedAssetKeys不仅是恰当的,反而是推荐的实践——我来给你拆解清楚为什么,以及怎么确保你拿到的duration是有效的。
为什么传入duration是恰当的?
首先得明确automaticallyLoadedAssetKeys的核心作用:它告诉AVPlayerItem,在自身状态变为ready to play之前,必须先预加载你指定的AVAsset属性键。
你看到文档里说duration在资源加载完成前会返回kCMTimeIndefinite,这是没有预加载duration时的情况:如果你没把duration加入这个数组,直接访问playerItem.duration,底层AVAsset可能还没完成时长的加载,就会返回这个“不确定”的时间值。
但如果你把duration加入automaticallyLoadedAssetKeys,AVPlayerItem会在标记为ready状态前,自动完成duration的异步加载。这时候当你拿到ready状态的信号时,duration已经是有效的、准确的时长值了,不会再出现kCMTimeIndefinite。
如何确保duration的有效性?
要确保拿到有效的duration,关键是等待AVPlayerItem进入readyToPlay状态后再访问duration,结合预加载的配置,具体步骤如下:
初始化时正确传入
duration键
以Swift为例:let videoAsset = AVURLAsset(url: yourVideoURL) // 将"duration"加入自动加载的键数组 let playerItem = AVPlayerItem(asset: videoAsset, automaticallyLoadedAssetKeys: ["duration"])Objective-C版本:
AVURLAsset *videoAsset = [[AVURLAsset alloc] initWithURL:yourVideoURL]; AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:videoAsset automaticallyLoadedAssetKeys:@[@"duration"]];监听AVPlayerItem的
status状态
只有当playerItem的status变为.readyToPlay时,你指定的所有automaticallyLoadedAssetKeys才会加载完成,这时候访问duration就是安全有效的。用Swift Combine的方式监听(iOS 13+):
var cancellables = Set<AnyCancellable>() playerItem.publisher(for: \.status) .receive(on: DispatchQueue.main) .sink { [weak self] status in guard let self = self else { return } switch status { case .readyToPlay: // 此时duration已加载完成,可安全访问 let validDuration = self.playerItem.duration if CMTIME_IS_VALID(validDuration) { // 处理有效时长,比如转为秒:CMTimeGetSeconds(validDuration) } case .failed(let error): print("加载失败:\(error.localizedDescription)") case .unknown: // 仍在加载中,等待即可 break @unknown default: break } } .store(in: &cancellables)如果用传统KVO监听(兼容更早系统):
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard keyPath == "status", let item = object as? AVPlayerItem else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } switch item.status { case .readyToPlay: let validDuration = item.duration // 处理有效时长 case .failed(let error): print("加载失败:\(error.localizedDescription)") default: break } } // 记得添加监听 playerItem.addObserver(self, forKeyPath: "status", options: [.new], context: nil) // 销毁时移除监听 deinit { playerItem.removeObserver(self, forKeyPath: "status") }额外补充:手动加载的备选方案
如果因为某些原因你不想用automaticallyLoadedAssetKeys,也可以直接通过AVAsset手动加载duration:videoAsset.loadValuesAsynchronously(forKeys: ["duration"]) { DispatchQueue.main.async { var error: NSError? let status = videoAsset.statusOfValue(forKey: "duration", error: &error) switch status { case .loaded: let validDuration = videoAsset.duration // 处理时长 case .failed(let err): print("加载失败:\(err.localizedDescription)") default: break } } }但这种方式需要你自己管理加载流程,不如让AVPlayerItem自动处理更贴合播放器的生命周期。
内容的提问来源于stack exchange,提问作者Dan Morrow

