AVPlayer的AVPlayerItemDidPlayToEndTime通知重复触发问题咨询
解决CollectionView Cell中AVPlayer通知重复触发的问题
哥们,我帮你捋捋这个头疼的问题——你遇到的是AVPlayerItemDidPlayToEndTime通知重复触发,导致两个视频同时重启的坑,本质是通知的注册/移除逻辑不严谨加上Cell复用的隐患在搞鬼,咱们一步步解决:
问题根源拆解
- 通知监听范围太宽:你在
awakeFromNib里注册通知时,object: player?.currentItem大概率是nil(因为此时Cell还没绑定具体的视频资源),这会导致你的监听捕获所有AVPlayerItem的结束通知,不管哪个Cell的视频播完,所有注册过的监听都会触发。 - 未处理Cell复用的通知残留:CollectionView的Cell是复用池机制,如果旧Cell的通知监听没被移除,当它被复用时,新的Player又会注册一次通知,旧监听还在后台生效,就会出现重复触发。
- 回调逻辑未做校验:你闭包里的逻辑直接操作
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
相关产品推荐
相关产品推荐

