如何设置UICollectionView的AVPlayer列表仅播放当前选中cell的视频
问题核心原因
你当前的逻辑是ClipsCollectionViewCell的configure方法调用时就自动播放视频,只要cell被加载到内存就会触发播放,所以会出现多个cell同时播放的问题。我们只需要把播放控制权收回到视图控制器,只让当前屏幕完全可见的cell播放即可。
第一步:修改 ClipsCollectionViewCell 代码
- 先新增存储属性避免重复添加播放器图层和重复监听通知:
class ClipsCollectionViewCell: UICollectionViewCell { // 原有属性保持不变 private var playerLayer: AVPlayerLayer? // 新增:存储播放器图层 // ... 其他原有属性 }
- 修改
configureVideo方法,移除自动播放逻辑,避免重复添加图层:
private func configureVideo() { guard let model = model else { return } // 先移除旧的播放器图层和通知监听 playerLayer?.removeFromSuperlayer() if let currentItem = player?.currentItem { NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: currentItem) } guard let url = URL(string: model.videoFileURL) else { return } player = AVPlayer(url: url) let playerView = AVPlayerLayer() playerView.player = player playerView.frame = contentView.bounds playerView.videoGravity = .resizeAspectFill videoContainer.layer.addSublayer(playerView) self.playerLayer = playerView player?.volume = 1 // 注意:AVPlayer音量范围是0~1,你原来写的5是错误的 player?.actionAtItemEnd = .none NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd(notification:)), name: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem) // 删掉原来的 player?.play() 不要自动播放 }
- 新增公开的播放、暂停方法:
// 新增:播放视频 public func playVideo() { player?.seek(to: .zero) player?.play() } // 新增:暂停视频 public func pauseVideo() { player?.pause() }
- 完善
prepareForReuse方法,避免内存泄漏:
override func prepareForReuse() { super.prepareForReuse() usernameLabel.text = nil // 移除通知监听 if let currentItem = player?.currentItem { NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: currentItem) } playerLayer?.removeFromSuperlayer() player?.pause() player = nil }
第二步:修改 BetaClipsViewController 代码
- 新增控制当前播放的私有方法:
private func playCurrentVisibleCell() { guard let collectionView = collectionView else { return } // 暂停所有可见cell的播放 collectionView.visibleCells.forEach { cell in if let clipCell = cell as? ClipsCollectionViewCell { clipCell.pauseVideo() } } // 找到当前中心位置对应的indexPath,就是当前选中的分页 let centerPoint = CGPoint(x: collectionView.contentOffset.x + collectionView.bounds.width/2, y: collectionView.bounds.height/2) guard let currentIndexPath = collectionView.indexPathForItem(at: centerPoint), let currentCell = collectionView.cellForItem(at: currentIndexPath) as? ClipsCollectionViewCell else { return } // 播放当前cell的视频 currentCell.playVideo() }
- 实现UIScrollView代理方法,滑动结束后切换播放:
// 手动滑动结束触发 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { playCurrentVisibleCell() } // 代码控制滚动结束触发(如果有代码调用setContentOffset的场景需要加这个) func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { playCurrentVisibleCell() }
- 首次加载数据完成后自动播放第一个视频,找到
fetchClips里的group.notify代码块,修改如下:
group.notify(queue: .main) { self.sortData() self.collectionView?.reloadData() // 新增:首次加载完成后播放第一个视频 DispatchQueue.main.async { self.playCurrentVisibleCell() } }
优化建议
如果你的视频数量很多,可以进一步优化内存:滑动过程中预加载下一个视频的资源,离开屏幕的cell直接释放播放器资源,减少内存占用。
内容的提问来源于stack exchange,提问作者Globe
相关产品推荐
相关产品推荐

