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

AVPlayer播放本地视频黑屏问题排查:代码报错咨询

问题分析与解决方案

先看你本地视频播放的代码,有两个关键问题直接导致了黑屏和警告:

1. 文件存在判断逻辑完全写反了

你这段代码里的判断:

if (MediaUtils.fileExists(filePath: filepath)) { 
    Logging.logError("Playing video failed, file not found: \(filepath)") 
    return false 
} 

意思是当文件存在的时候,反而输出“文件未找到”的错误日志并返回false,直接跳过了后续的播放逻辑!这就导致文件明明存在,但代码根本没执行播放的部分,自然会黑屏。正确的逻辑应该是判断文件不存在的时候才报错返回。

2. AVPlayerViewController的错误展示方式

你同时做了两件事:把playerViewController.view添加到传入的view作为子视图,又调用vc.present(playerViewController, animated: true)。这两种方式都是用来展示AVPlayerViewController的,同时使用会导致视图层级混乱,触发你看到的Unbalanced calls to begin/end appearance transitions警告,而且展示逻辑冲突。

你应该二选一:要么present模态展示,要么把playerViewController作为子控制器添加到当前VC(而不是只加view)。

修正后的本地视频播放代码

这里给出修正后的代码,同时解决上述两个问题:

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 player = AVPlayer(url: URL(fileURLWithPath: filepath)) 
    let playerViewController = AVPlayerViewController() 
    playerViewController.player = player 
    
    // 选择一种展示方式:要么模态present(和你网络播放逻辑一致),要么作为子控制器嵌入
    // 方式1:模态present(推荐)
    vc.present(playerViewController, animated: true) { 
        player.play() 
    } 
    
    // 方式2:如果要嵌入到指定view中,需要添加子控制器(不要只加view)
    // vc.addChild(playerViewController)
    // playerViewController.view.frame = view.bounds
    // view.addSubview(playerViewController.view)
    // playerViewController.didMove(toParent: vc)
    // player.play()
    
    return true 
}

如何获取player.play()的错误信息

要捕获AVPlayer的播放错误,可以通过两种方式实现:

方法1:监听AVPlayerItem的status状态

let playerItem = AVPlayerItem(url: URL(fileURLWithPath: filepath))
let player = AVPlayer(playerItem: playerItem)

// 添加状态监听
playerItem.addObserver(self, forKeyPath: "status", options: [.new, .initial], context: nil)

// 在observeValue方法中处理状态变化
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "status", let playerItem = object as? AVPlayerItem {
        switch playerItem.status {
        case .failed:
            if let error = playerItem.error {
                Logging.logError("Playback failed with error: \(error.localizedDescription)")
            }
        case .readyToPlay:
            Logging.logDebug("Player is ready to play")
        case .unknown:
            Logging.logDebug("Player status unknown")
        @unknown default:
            break
        }
    }
}

// 记得在合适时机移除监听,比如VC销毁时
// playerItem.removeObserver(self, forKeyPath: "status")

方法2:监听播放失败通知

NotificationCenter.default.addObserver(self, selector: #selector(handlePlaybackFailure(_:)), name: .AVPlayerItemFailedToPlayToEndTime, object: player.currentItem)

@objc func handlePlaybackFailure(_ notification: Notification) {
    if let error = notification.userInfo?[AVPlayerItemFailedToPlayToEndTimeErrorKey] as? Error {
        Logging.logError("Playback failed: \(error.localizedDescription)")
    }
}

// 记得在合适时机移除通知监听
// NotificationCenter.default.removeObserver(self, name: .AVPlayerItemFailedToPlayToEndTime, object: nil)

这样就能准确获取到播放失败的具体错误信息了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:03:58