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

Swift中AVPlayer准备播放时KVO观察者未触发问题排查

解决AVPlayer KVO监听playbackLikelyToKeepUp无回调的问题

你遇到的observeValue从未触发的问题,大概率是链式KVO监听的逻辑漏洞,或是AVPlayer/AVPlayerItem的配置问题导致的。我帮你拆解几个核心排查点和解决方法:

1. 链式KVO路径的陷阱:currentItem为nil时无法监听子属性

你直接监听"currentItem.playbackLikelyToKeepUp",但如果一开始player.currentItem是nil,KVO不会自动追踪后续currentItem的子属性变化。正确的做法是先监听currentItem的存在状态,再给非nil的currentItem添加子属性监听:

// 定义两个独立的context区分不同监听
var currentItemContext = 0
var playbackLikelyToKeepUpContext = 0

// 先监听currentItem的变化
if let video = card.pageImageVideoController.controllers[0] as? VideoController {
    video.player.addObserver(self, forKeyPath: "currentItem", options: [.new, .old], context: &currentItemContext)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    guard let videoController = topCard?.pageImageVideoController.controllers[0] as? VideoController else { return }

    if context == &currentItemContext {
        // 移除旧currentItem的监听(防止内存泄漏)
        if let oldItem = change?[.oldKey] as? AVPlayerItem {
            oldItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp", context: &playbackLikelyToKeepUpContext)
        }
        // 给新的currentItem添加playback状态监听
        if let newItem = change?[.newKey] as? AVPlayerItem {
            newItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: &playbackLikelyToKeepUpContext)
        }
    } else if context == &playbackLikelyToKeepUpContext {
        if let item = object as? AVPlayerItem, item.isPlaybackLikelyToKeepUp {
            print("ready")
        } else {
            print("not ready")
        }
    } else {
        // 务必调用父类实现,处理其他未匹配的KVO
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }
}

2. 先确认AVPlayerItem是否被正确赋值

如果player.currentItem始终是nil,任何监听都不会触发。在添加KVO前加个打印排查:

if let video = card.pageImageVideoController.controllers[0] as? VideoController {
    print("Current AVPlayerItem: \(video.player.currentItem)")
    // 后续监听代码
}

如果输出为nil,检查是否调用过player.replaceCurrentItem(with: yourAVPlayerItem)来关联视频资源。

3. 确保观察者对象未被提前释放

如果你的self(观察者)被提前销毁(比如ViewController被pop但未移除监听),observeValue自然不会触发。同时务必在对象销毁前移除所有KVO监听,避免崩溃:

deinit {
    if let video = card.pageImageVideoController.controllers[0] as? VideoController {
        video.player.removeObserver(self, forKeyPath: "currentItem", context: &currentItemContext)
        if let item = video.player.currentItem {
            item.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp", context: &playbackLikelyToKeepUpContext)
        }
    }
}

额外推荐:用Combine替代KVO(更简洁安全)

现在AVFoundation推荐用Combine框架监听状态,避免KVO的繁琐和潜在问题:

import Combine

var cancellables = Set<AnyCancellable>()

if let video = card.pageImageVideoController.controllers[0] as? VideoController {
    video.player.publisher(for: \.currentItem)
        .switchToLatest { item in
            // 若currentItem为nil,默认返回false
            item?.publisher(for: \.isPlaybackLikelyToKeepUp) ?? Just(false).eraseToAnyPublisher()
        }
        .sink { isReady in
            isReady ? print("ready") : print("not ready")
        }
        .store(in: &cancellables)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:18:53