AVPlayer无法播放本地已保存视频文件的问题求助
问题分析与修复方案
先给你揪出两个核心问题,这就是本地视频黑屏+警告的根源:
1. 致命逻辑错误:文件存在判断完全颠倒了
看你本地播放函数里的这段代码:
if (MediaUtils.fileExists(filePath: filepath)) { Logging.logError("Playing video failed, file not found: \(filepath)") return false }
这相当于文件存在的时候,你直接报错说找不到文件,然后返回false终止播放流程!完全搞反了判断逻辑!正确的应该是:如果文件不存在才报错返回,存在的话继续执行。
2. AVPlayerViewController使用违规:同时做了addSubview和present
你在代码里同时执行了:
view.addSubview(playerViewController.view) vc.present(playerViewController, animated: true)
这是绝对不允许的——一个视图控制器要么被present出来(作为独立的模态控制器),要么被添加为另一个控制器的子视图(作为容器控制器的一部分),不能同时操作。这就是你看到Unbalanced calls to begin/end appearance transitions警告的直接原因,也会导致视图渲染异常(比如黑屏)。
顺便说一句,你那个网络播放的函数其实也有这个问题,只是网络视频加载有延迟,没立刻触发明显的黑屏,建议也一起修正。
3. 如何获取播放错误信息?
要捕获player.play()的错误,你需要监听AVPlayerItem的状态变化或者相关通知:
- 监听
AVPlayerItemFailedToPlayToEndTimeNotification通知,捕获播放失败的错误 - 通过KVO监听
AVPlayerItem的status属性,获取准备状态和错误信息
修正后的完整代码示例
方式一:用present模态播放本地视频(推荐,符合系统播放器的交互逻辑)
static func playVideo(from filename: String, vc: UIViewController) -> Bool { let filepath = videosDirectoryPath + filename // 修正后的文件存在判断 guard MediaUtils.fileExists(filePath: filepath) else { Logging.logError("Playing video failed, file not found: \(filepath)") return false } let fileURL = URL(fileURLWithPath: filepath) let playerItem = AVPlayerItem(url: fileURL) // 监听播放失败通知 NotificationCenter.default.addObserver(forName: .AVPlayerItemFailedToPlayToEndTime, object: playerItem, queue: .main) { notification in if let error = notification.userInfo?[AVPlayerItemFailedToPlayToEndTimeErrorKey] as? Error { Logging.logError("Video playback failed with error: \(error.localizedDescription)") } } // KVO监听playerItem状态(可选,但能拿到更详细的准备状态) playerItem.addObserver(self, forKeyPath: "status", options: [.new], context: nil) let player = AVPlayer(playerItem: playerItem) let playerViewController = AVPlayerViewController() playerViewController.player = player vc.present(playerViewController, animated: true) { player.play() } return true } // 记得在合适的时机移除观察者(比如所在控制器的deinit方法) deinit { NotificationCenter.default.removeObserver(self) // 如果用了KVO,也要移除对应的观察者 playerItem?.removeObserver(self, forKeyPath: "status") } // KVO回调方法,处理playerItem状态变化 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard keyPath == "status", let playerItem = object as? AVPlayerItem else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } switch playerItem.status { case .failed: if let error = playerItem.error { Logging.logError("Player item failed with error: \(error.localizedDescription)") } case .readyToPlay: Logging.logDebug("Player item is ready to play") case .unknown: Logging.logDebug("Player item status is unknown (loading...)") @unknown default: break } }
方式二:将播放器视图添加为子视图(适合嵌入到自定义界面)
如果你需要把播放器嵌入到现有视图中,不要用present,而是遵循容器视图控制器的规范:
static func playVideo(from filename: String, vc: UIViewController, view: UIView) -> Bool { let filepath = videosDirectoryPath + filename guard MediaUtils.fileExists(filePath: filepath) else { Logging.logError("Playing video failed, file not found: \(filepath)") return false } let fileURL = URL(fileURLWithPath: filepath) let playerItem = AVPlayerItem(url: fileURL) // 添加错误监听(同上) NotificationCenter.default.addObserver(forName: .AVPlayerItemFailedToPlayToEndTime, object: playerItem, queue: .main) { notification in if let error = notification.userInfo?[AVPlayerItemFailedToPlayToEndTimeErrorKey] as? Error { Logging.logError("Video playback failed with error: \(error.localizedDescription)") } } let player = AVPlayer(playerItem: playerItem) let playerViewController = AVPlayerViewController() playerViewController.player = player // 设置播放器视图的frame和自适应规则 playerViewController.view.frame = view.bounds playerViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] // 必须调用容器VC的方法,这是规范 vc.addChild(playerViewController) view.addSubview(playerViewController.view) playerViewController.didMove(toParent: vc) player.play() return true }
最后总结
- 先修正文件存在的判断逻辑,这是本地视频根本没机会播放的首要原因
- 二选一使用present或addSubview的方式展示AVPlayerViewController,不要同时操作
- 通过监听AVPlayerItem的通知或KVO来获取详细的播放错误信息
内容的提问来源于stack exchange,提问作者rysv
相关产品推荐
相关产品推荐

