AVPlayer搭配CachingPlayerItem与Cache播放远程音频需点击两次问题咨询
问题根因
你遇到的首次播放失败、二次才能播的问题,核心是以下三个原因:
- 你强制关闭了
automaticallyWaitsToMinimizeStalling,远程未缓存资源首次加载时没有足够的初始缓冲,播放器会直接终止播放逻辑 - 你没有监听
AVPlayerItem的状态,刚把item塞进播放器就立刻调用play(),此时item还没完成资源元数据加载、没有进入可播放状态 - 你写死了缓存读取时的MIMEType和文件扩展名,如果Firebase返回的资源类型和你写的不匹配,也会导致首次加载解析失败
修复方案
步骤1:移除不必要的配置
删掉self.automaticallyWaitsToMinimizeStalling = false这行代码,这个配置只适合本地已缓存的资源播放场景,远程流媒体场景开启会直接导致播放失败。
步骤2:添加播放器状态监听
监听AVPlayerItem的status属性,等状态变为readyToPlay时再触发播放,不要直接调用play()。
步骤3:修正缓存读取的资源类型匹配逻辑
建议缓存时同时存储资源的MIMEType和扩展名,不要写死,避免和实际资源格式不兼容。
修正后的代码
import AVKit import Cache import Combine class AudioPlayer: AVPlayer, ObservableObject { private let diskConfig = DiskConfig(name: "DiskCache") private let memoryConfig = MemoryConfig(expiry: .never, countLimit: 10, totalCostLimit: 10) // 存储状态观察器,避免被释放 private var statusObserver: AnyCancellable? lazy var storage: Cache.Storage<String, Data>? = { return try? Cache.Storage(diskConfig: diskConfig, memoryConfig: memoryConfig, transformer: TransformerFactory.forData()) }() /// Plays audio either from the network if it's not cached or from the cache. func startPlayback(with url: URL) { // 先清空之前的观察器 statusObserver?.cancel() let playerItem: CachingPlayerItem do { let result = try storage!.entry(forKey: url.absoluteString) // 缓存命中,用本地数据初始化 playerItem = CachingPlayerItem(data: result.object, mimeType: "audio/mp4", fileExtension: "m4a") } catch { // 未命中缓存,用远程url初始化 playerItem = CachingPlayerItem(url: url) } playerItem.delegate = self replaceCurrentItem(with: playerItem) // 监听item状态,准备好再播放 statusObserver = playerItem.publisher(for: \.status) .receive(on: DispatchQueue.main) .sink { [weak self] status in guard let self = self else { return } if status == .readyToPlay { self.play() // 播放触发后取消观察,避免重复调用 self.statusObserver?.cancel() } else if status == .failed { // 可在这里添加播放失败的降级逻辑 print("播放失败,错误:\(playerItem.error?.localizedDescription ?? "未知错误")") } } } } extension AudioPlayer: CachingPlayerItemDelegate { func playerItem(_ playerItem: CachingPlayerItem, didFinishDownloadingData data: Data) { // 下载完成后存入缓存 storage?.async.setObject(data, forKey: playerItem.url.absoluteString, completion: { _ in }) print("缓存完成") } // 可选实现,处理下载错误 func playerItem(_ playerItem: CachingPlayerItem, didDownloadBytesSoFar bytesDownloaded: Int, outOf bytesExpected: Int) { // 可在这里更新下载进度UI } func playerItem(_ playerItem: CachingPlayerItem, didFinishDownloadingWithError error: Error) { print("资源下载失败:\(error.localizedDescription)") } }
内容的提问来源于stack exchange,提问作者gadha007
相关产品推荐
相关产品推荐

