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

AVPlayer的AVPlayerItemDidPlayToEndTime通知重复触发问题咨询

解决CollectionView Cell中AVPlayer通知重复触发的问题

哥们,我帮你捋捋这个头疼的问题——你遇到的是AVPlayerItemDidPlayToEndTime通知重复触发,导致两个视频同时重启的坑,本质是通知的注册/移除逻辑不严谨加上Cell复用的隐患在搞鬼,咱们一步步解决:

问题根源拆解

  1. 通知监听范围太宽:你在awakeFromNib里注册通知时,object: player?.currentItem大概率是nil(因为此时Cell还没绑定具体的视频资源),这会导致你的监听捕获所有AVPlayerItem的结束通知,不管哪个Cell的视频播完,所有注册过的监听都会触发。
  2. 未处理Cell复用的通知残留:CollectionView的Cell是复用池机制,如果旧Cell的通知监听没被移除,当它被复用时,新的Player又会注册一次通知,旧监听还在后台生效,就会出现重复触发。
  3. 回调逻辑未做校验:你闭包里的逻辑直接操作self?.player,没有判断触发通知的是不是当前Cell的PlayerItem,导致误操作其他Cell的播放器。

修复方案(代码示例)

推荐用PlayerItem绑定+复用清理的方式,把通知的生命周期和PlayerItem绑定,同时严格处理Cell复用:

class VideoCollectionViewCell: UICollectionViewCell {
    var player: AVPlayer?
    private var currentPlayerItem: AVPlayerItem? {
        willSet {
            // 更换PlayerItem前,先移除旧的通知监听
            if let oldItem = currentPlayerItem {
                NotificationCenter.default.removeObserver(self, 
                    name: .AVPlayerItemDidPlayToEndTime, 
                    object: oldItem)
            }
        }
        didSet {
            // 绑定新PlayerItem后,注册对应通知
            if let newItem = currentPlayerItem {
                NotificationCenter.default.addObserver(self, 
                    selector: #selector(playerItemDidFinish(_:)), 
                    name: .AVPlayerItemDidPlayToEndTime, 
                    object: newItem)
            }
        }
    }

    // Cell复用前强制清理资源,避免残留
    override func prepareForReuse() {
        super.prepareForReuse()
        player?.pause()
        currentPlayerItem = nil
        player = nil
    }

    // 用Selector代替闭包,更便于监听的管理和移除
    @objc private func playerItemDidFinish(_ notification: Notification) {
        // 校验触发通知的是当前Cell的PlayerItem,避免误操作
        guard let triggeredItem = notification.object as? AVPlayerItem, 
              triggeredItem == currentPlayerItem else {
            return
        }
        // 只重启当前Cell的视频
        player?.seek(to: .zero)
        // 如果需要自动重播可以加这行:player?.play()
    }

    // 对外暴露的视频配置方法,统一管理Player和PlayerItem
    func setupVideo(with url: URL) {
        let newItem = AVPlayerItem(url: url)
        currentPlayerItem = newItem
        player = AVPlayer(playerItem: newItem)
        // 这里可以添加PlayerLayer到Cell的视图上
    }
}

关键优化点说明

  • 绑定PlayerItem生命周期:通过currentPlayerItem的willSet和didSet自动处理通知的移除/注册,确保每个PlayerItem只对应一个监听。
  • 复用强制清理:在prepareForReuse里彻底销毁旧的Player、PlayerItem和通知监听,避免复用残留。
  • 回调校验:在通知回调里判断触发源是不是当前Cell的PlayerItem,从根源上杜绝误操作。

如果你坚持要用闭包形式监听通知,记得要保存闭包的引用,在清理时手动移除:

private var endNotificationObserver: NSObjectProtocol?

func setupVideo(with url: URL) {
    // 先移除旧的闭包监听
    if let observer = endNotificationObserver {
        NotificationCenter.default.removeObserver(observer)
    }
    let newItem = AVPlayerItem(url: url)
    player = AVPlayer(playerItem: newItem)
    // 保存闭包引用,方便后续移除
    endNotificationObserver = NotificationCenter.default.addObserver(
        forName: .AVPlayerItemDidPlayToEndTime,
        object: newItem,
        queue: .main) { [weak self] _ in
            guard let self = self else { return }
            self.player?.seek(to: .zero)
        }
}

override func prepareForReuse() {
    super.prepareForReuse()
    if let observer = endNotificationObserver {
        NotificationCenter.default.removeObserver(observer)
    }
    endNotificationObserver = nil
    // 其他清理操作
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:36:49