You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无法播放Documents目录离线音频,AVPlayerItem状态为unknown求助

离线音频播放AVPlayerItem status=unknown问题排查

核心问题点

  • 资源未主动触发加载:创建AVAsset和AVPlayerItem后,未触发资源加载流程,AVPlayerItem默认不会自动加载本地资源的可播放状态,导致status一直停留在unknown。
  • 观察者绑定时机错误:在初始化AVPlayer之后才尝试给currentItem加观察者,若currentItem未完成初始化,观察者无法绑定成功,自然接收不到状态变化。
  • 可选值处理不严谨:currentItem as! Book强制解包存在崩溃风险;audioPlayer?.currentItem?.addObserver的可选链如果currentItem为nil,观察者根本不会被添加。

修正后的代码实现

优化音频路径获取函数

func getAudioUrl(book: Book) -> URL? {
    guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
        print("无法访问Documents目录")
        return nil
    }
    // 用格式化字符串避免拼接错误,更易维护
    let fileName = String(format: "test-audio-%ld.mp3", book.id)
    let fileUrl = documentsDirectory.appendingPathComponent(fileName)
    
    guard FileManager.default.fileExists(atPath: fileUrl.path) else {
        print("目标文件不存在:\(fileUrl.path)")
        return nil
    }
    return fileUrl
}

修正播放初始化流程

// 安全解包,避免强制类型转换崩溃
guard let currentBook = currentItem as? Book, 
      let fileURL = OfflineHandler.sharedInstance.getAudioUrl(book: currentBook) else {
    print("无法获取有效音频文件路径")
    return
}

let asset = AVAsset(url: fileURL)
// 异步加载资源的可播放状态,确保资源就绪后再初始化Player
asset.loadValuesAsynchronously(forKeys: ["playable", "duration"]) { [weak self] in
    guard let self = self else { return }
    var loadError: NSError?
    let playableStatus = asset.statusOfValue(forKey: "playable", error: &loadError)
    
    switch playableStatus {
    case .loaded:
        // 资源加载完成,先给PlayerItem加观察者,再关联到AVPlayer
        let avPlayerItem = AVPlayerItem(asset: asset)
        avPlayerItem.addObserver(self, forKeyPath: "status", options: [.new, .initial], context: nil)
        
        // 绑定播放完成通知
        NotificationCenter.default.addObserver(self, 
                                               selector: #selector(self.itemDidFinishPlaying(_:)), 
                                               name: .AVPlayerItemDidPlayToEndTime, 
                                               object: avPlayerItem)
        
        self.audioPlayer = AVPlayer(playerItem: avPlayerItem)
        self.audioPlayer?.volume = 1.0
        self.audioPlayer?.automaticallyWaitsToMinimizeStalling = false
        
        self.updateCC()
        self.isReadyToPlay = true
        // 资源就绪后触发播放
        self.audioPlayer?.play()
    case .failed:
        print("资源加载失败:\(loadError?.localizedDescription ?? "未知错误")")
    case .cancelled:
        print("资源加载被取消")
    default:
        print("资源加载状态异常:\(playableStatus.rawValue)")
    }
}

完善观察者与内存管理

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    // 仅处理AVPlayerItem的status变化,避免干扰其他观察者
    guard let playerItem = object as? AVPlayerItem, keyPath == "status" else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
    }
    
    switch playerItem.status {
    case .readyToPlay:
        print("音频已准备就绪")
    case .failed:
        print("音频播放失败:\(playerItem.error?.localizedDescription ?? "未知错误")")
    case .unknown:
        print("音频状态未知,可能加载未完成")
    @unknown default:
        print("未处理的状态:\(playerItem.status.rawValue)")
    }
}

// 页面销毁时移除观察者和通知,避免内存泄漏
deinit {
    audioPlayer?.currentItem?.removeObserver(self, forKeyPath: "status")
    NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
}

内容的提问来源于stack exchange,提问作者Chris Hansen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 01:30:03