Search code examples
iosswiftcollectionview

How to play one video at a time in a collectionView?


I am building an application similar to TikTok, where the main feed is a UICollectionView with a video in each cell. My problem is that multiple videos will play simultaneously, and I only want one video to play at a time, based on whether or not the cell is in view. I also want the video to pause if the cell is out of view.

Each cell takes up the entire screen, and the collectionView has paging enabled.

If you have ever used Instagram or TikTok, I want the functionality to be the same. Video only plays when cell comes into view, and video pauses when cell leaves the view. Let me know if you need any more code or information and thank you!

Here is the relevant code in the FeedController:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! FeedCell
    cell.delegate = self

    cell.post = posts[indexPath.row]

    if let videoUrl = posts[indexPath.row].videoUrl { 
      player = AVPlayer(url: videoUrl)
      playerLayer = AVPlayerLayer(player: player)

      playerLayer?.zPosition = -1
      playerLayer?.frame = cell.contentView.frame
      playerLayer?.videoGravity = .resizeAspectFill
      cell.contentView.layer.addSublayer(playerLayer!)
      player?.play()

      cell.playerLayer = playerLayer
      cell.player = player
    }

    return cell
}

Here is the relevant code for the FeedCell:

class FeedCell: UICollectionViewCell {

  var post: Post?

  var playerLayer: AVPlayerLayer?
  var player: AVPlayer?

  override func prepareForReuse() {
      super.prepareForReuse()
      playerLayer?.removeFromSuperlayer()
      player?.pause()
  }

}

Solution

  • You can easily achieve this using the collectionView(:willDisplay:forItemAt:) and collectionView(:didEndDisplaying:forItemAt:) delegate methods.

    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        (cell as? FeedCell).player?.play()
    }
    
    func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        (cell as? FeedCell).player?.pause()
    }
    

    Note: The didEndDisplaying will be called only after your cell goes off entirely from the screen.