Search code examples
swiftxcodeuicollectionviewuicollectionviewcellavplayer

Trying to pause video when the UICollectionViewCell is out of view


I'm populating a collection view with URLs loaded from Firebase. But I can't get the video to pause when I exit the view or scroll the collection view up. When I go back using the navigation controller, I can still hear the video playing in the background. Then when I enter the view again, the video starts playing, but the first one never finished.

This is my view controller. Any tips? thank you! I'm still new to Swift, so please excuse any ignorance on my end.

import UIKit
import AVKit
import AVFoundation
import Firebase
import FirebaseDatabase
import SDWebImage

class ComedyViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {

    var avPlayer = AVPlayer()
    var avPlayerLayer = AVPlayerLayer()

    @IBOutlet weak var comedyCollectionView: UICollectionView!

    var comedyVideoArray = [ComedyModel]()

    var comedyDBRef: DatabaseReference! {
        return Database.database().reference().child("comedy")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        loadComedyDB()
    }


    func loadComedyDB() {
        comedyDBRef.observe(DataEventType.value, with: { (snapshot) in

            if snapshot.childrenCount > 0 {
                self.comedyVideoArray.removeAll()

                for comedyData in snapshot.children.allObjects as! [DataSnapshot] {
                    let comedyObject = comedyData.value as? [String: AnyObject]
                    let comedyPostTitle = comedyObject?["title"]
                    let comedyPostDescription = comedyObject?["description"]
                    let comedyArticleLink = comedyObject?["link"]
                    let comedyVideoUrl = comedyObject?["url"]
                    let allComedyData = ComedyModel(title: comedyPostTitle as! String?, description: comedyPostDescription as! String?, link: comedyArticleLink as! String?, url: comedyVideoUrl as! String?)

                    self.comedyVideoArray.append(allComedyData)

                    self.comedyCollectionView.reloadData()
                }
            }
        })
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        print(comedyVideoArray.count)
        return comedyVideoArray.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cellB = comedyCollectionView.dequeueReusableCell(withReuseIdentifier: "comedyCell", for: indexPath) as! ComedyCollectionViewCell
        let data = comedyVideoArray[indexPath.row]
        let item = AVPlayerItem(url: URL(string: data.url!)!)

        self.avPlayer = AVPlayer(playerItem: item)
        self.avPlayer.actionAtItemEnd = .none

        self.avPlayerLayer = AVPlayerLayer(player: self.avPlayer)
        self.avPlayerLayer.videoGravity = .resizeAspectFill
        self.avPlayerLayer.frame = CGRect(x: 0, y: 0, width: cellB.frame.size.width, height: cellB.frame.size.height / 2)

        cellB.videoView.layer.addSublayer(self.avPlayerLayer)

        self.avPlayer.play()
        //cellB.videoView.sd_setImage(with: URL(string: data.link!), placeholderImage: UIImage(named: "1"))

        return cellB
    }

    func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {

        if collectionView == self.comedyCollectionView {
            self.avPlayer.pause()
        }
    }
}

Solution

  • There're several issues:

    1. UICollectionView calls cellForItemAt before it shows new cell which means that old cell is still visible. Now, when you dequeue new cell, you are changing your pointer avPlayer to the new instance of AVPlayer so that when you're pausing the video you're actually calling pause() on a new video. The old one continues to play (in a cell that is still visible at the time).

    2. You're adding an instance of AVPlayer to the cell each time UICollectionView calls cellForItemAt. But UICollectionView tries to reuse those cells which are no longer visible so you can end up with a lot of AVPlayers in each cell. To avoid that you should check out how to use prepareForReuse method on cells. I also suggest you to create AVPlayer in cell itself (by subclassing the UICollectionViewCell) and then set its playerItem in cellForItemAt method. This way you could pause currently playing video in your didEndDisplaying method like this (just an example):

      func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        if let comedyCell = cell as? ComedyCollectionViewCell {
          comedyCell.avPlayer.pause()
        } 
      }