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

如何设置UICollectionView的AVPlayer列表仅播放当前选中cell的视频

问题核心原因

你当前的逻辑是ClipsCollectionViewCell的configure方法调用时就自动播放视频,只要cell被加载到内存就会触发播放,所以会出现多个cell同时播放的问题。我们只需要把播放控制权收回到视图控制器,只让当前屏幕完全可见的cell播放即可。

第一步:修改 ClipsCollectionViewCell 代码

  1. 先新增存储属性避免重复添加播放器图层和重复监听通知:
class ClipsCollectionViewCell: UICollectionViewCell {
    // 原有属性保持不变
    private var playerLayer: AVPlayerLayer? // 新增:存储播放器图层
    // ... 其他原有属性
}
  1. 修改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() 不要自动播放
}
  1. 新增公开的播放、暂停方法:
// 新增:播放视频
public func playVideo() {
    player?.seek(to: .zero)
    player?.play()
}

// 新增:暂停视频
public func pauseVideo() {
    player?.pause()
}
  1. 完善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 代码

  1. 新增控制当前播放的私有方法:
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()
}
  1. 实现UIScrollView代理方法,滑动结束后切换播放:
// 手动滑动结束触发
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    playCurrentVisibleCell()
}

// 代码控制滚动结束触发(如果有代码调用setContentOffset的场景需要加这个)
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
    playCurrentVisibleCell()
}
  1. 首次加载数据完成后自动播放第一个视频,找到fetchClips里的group.notify代码块,修改如下:
group.notify(queue: .main) {
    self.sortData()
    self.collectionView?.reloadData()
    // 新增:首次加载完成后播放第一个视频
    DispatchQueue.main.async {
        self.playCurrentVisibleCell()
    }
}

优化建议

如果你的视频数量很多,可以进一步优化内存:滑动过程中预加载下一个视频的资源,离开屏幕的cell直接释放播放器资源,减少内存占用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 14:54:08