Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best place to add AVPlayer or MPMoviePlayerController in UITableViewCell?

I try to play video in UITableViewCell when a user clicks on play button in cell.

like image 309
Pankil Avatar asked Jun 07 '17 05:06

Pankil


2 Answers

Scene 1:

If you are willing to auto play video's like Facebook or Instagram in each cell then consider adding AVPlayer in cellForRowAtIndexPath.

AVPlayer will not allow you to change the url being played (AVURLAsset url) once created. So If you are using AVPlayer and providing ur own view to player I afraid creating AVPlayer instance every time in cellForRowAtIndexPath is the only solution.

On the other hand if you are planning to use AVPlayerViewController, you can create AVPlayer viewController instance in cell's awakeFromNib so you will have only one instance of player per cell and in cellForRowAtIndexPath you can pass different url each time. You can use prepareForReuse method of cell to pause the player and resetting all your player properties.

Scene 2:

If you are planning to play once user taps on button in cell, consider creating AVPlayer/AVPlayerViewController on button tap and play the video. Use prepareForReuse to reset the properties of ur player to ensure you don't play wrong video when u scroll and cell gets reused.

EDIT:

As OP has asked for some code snippet, providing skeleton code. This is not a copy paste code, idea is just to give idea of how to use AVPlayerViewController inside cell the code might have compiler issues.

//create a custom tableViewCell subclass

class ImageTableViewCell: UITableViewCell {
    @IBOutlet weak var carsImageView: UIImageView!
    var playerController : AVPlayerViewController? = nil
    var passedURL : URL! = nil

    override func awakeFromNib() {
        super.awakeFromNib()
        playerController = AVPlayerViewController()
        // Initialization code
    }

    func configCell(with url : URL) {
        //something like this
        self.passedURL = url
        //show video thumbnail with play button on it.
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        // Configure the view for the selected state
    }

    @IBAction func playOrPauseVideo(_ sender: UIButton) {
        let player = AVPlayer(url: url)
        playerController.player = player
        playerController?.showsPlaybackControls = true
        playerController.player.play()
        //add playerController view as subview to cell
    }

    override func prepareForReuse() {
        //this way once user scrolls player will pause
        self.playerController.player?.pause()
        self.playerController.player = nil
    }
}

The code above sets the PlayerController to nil in prepareForReuse() so when user scrolls the tableView and cell goes out of tableView Frame and gets reused, player will pause and will not retain the status. If you simply want to pause and replay when user scrolls back to the same cell u will have to save player somewhere outside the cell and find a way to map player instance to cell.

Finally in cellForRowAtIndexPath call,

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell : ImageTableViewCell = tableView.dequeueReusableCell(withIdentifier: "imageCell") as! ImageTableViewCell
    cell.configCell(with: URL(string: "https://google.com")!)
    ....
}

EDIT 2:

As OP has completely filped the question to play only one video at any point in time and pause other playing videos on tapping play button my earlier answer will no longer hold true.

Earlier answer allows user to play multiple video.

Here is a modified answer to make it work.

Modify your custom cell class as below.

protocol VideoPlayingCellProtocol : NSObjectProtocol {
    func playVideoForCell(with indexPath : IndexPath)
}

class ImageTableViewCell: UITableViewCell {
    @IBOutlet weak var carsImageView: UIImageView!
    var playerController : AVPlayerViewController? = nil
    var passedURL : URL! = nil
    var indexPath : IndexPath! = nil
    var delegate : VideoPlayingCellProtocol = nil

    override func awakeFromNib() {
        super.awakeFromNib()
        playerController = AVPlayerViewController()
        // Initialization code
    }

    func configCell(with url : URL,shouldPlay : Bool) {
        //something like this
        self.passedURL = url
        if shouldPlay == true {
            let player = AVPlayer(url: url)
            if self.playerController == nil {
                playerController = AVPlayerViewController()
            }
            playerController.player = player
            playerController?.showsPlaybackControls = true
            playerController.player.play()
        }
        else {
            if self.playerController != nil {
                self.playerController.player?.pause()
                self.playerController.player = nil
            }
            //show video thumbnail with play button on it.
        }
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

    @IBAction func playOrPauseVideo(_ sender: UIButton) {
        self.delegate.playVideoForCell(self.indexPath)
        //add playerController view as subview to cell
    }

    override func prepareForReuse() {
        //this way once user scrolls player will pause
        self.playerController.player?.pause()
        self.playerController.player = nil
    }
}

In your TableView VC create a property called

var currentlyPlayingIndexPath : IndexPath? = nil

Make your TableView VC to confirm VideoPlayingCellProtocol

extension ViewController : VideoPlayingCellProtocol {
    func playVideoForCell(with indexPath: IndexPath) {
    self.currentlyPlayingIndexPath = indexPath
    //reload tableView
    self.tableView.reloadRows(at: self.tableView.indexPathsForVisibleRows!, with: .none)
   }
}

Finally modify your cellForRowAtIndexPath as

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell : ImageTableViewCell = tableView.dequeueReusableCell(withIdentifier: "imageCell") as! ImageTableViewCell
        //delegate setting here which u missed
        cell.delegate = self
        //let the cell know its indexPath
        cell.indexPath = indexPath

            cell.configCell(with: URL(string: "https://google.com")!, shouldPlay: self.currentlyPlayingIndexPath == indexPath)
        ....
}

Thats all you need.

like image 189
Sandeep Bhandari Avatar answered Oct 03 '22 01:10

Sandeep Bhandari


I had a similar requirement as you and created global AVPlayer and AVPlayerLayer variables in my UITableViewController. I then attached them to a cell when a person selected the cell. From what I've seen, my scrolling is smooth, memory usage is minimal, and it also enforces that only one video is played at a time.

First step was to create the variables in my UITableViewController:

lazy var avPlayer : AVPlayer = {
    let player = AVPlayer()
    return player
}()

lazy var avPlayerLayer : AVPlayerLayer = {
    let layer = AVPlayerLayer(player: self.avPlayer)
    return layer
}()

I then created two functions in the UITableViewController to handle linking and removing the AVPlayerLayer from a cell as such.

private func removePlayerFromCell()
{
    if (self.avPlayerLayer.superlayer != nil)
    {
        self.avPlayerLayer.removeFromSuperlayer()
        self.avPlayer.pause()
        self.avPlayer.replaceCurrentItem(with: nil)
    }
}

private func attachPlayerToCell(indexPath : IndexPath)
{
    if let cell = tableView.cellForRow(at: indexPath)
    {
        self.removePlayerFromCell()

        //create url
       let url = ... 

        //create player item
        let playerItem = AVPlayerItem(url: url)
        self.avPlayer.replaceCurrentItem(with: playerItem)

        self.avPlayerLayer.frame = cell.contentView.frame
        cell.contentView.layer.addSublayer(self.avPlayerLayer)

        self.avPlayer.play()
    }
}

You can then call attachPlayerToCell() in didSelectRowAt().

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
    attachPlayerToCell(indexPath: indexPath)
}

Lastly, to ensure videos don't play during scrolling, use the table's UIScrollViewDelegate to stop playback:

override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    self.removePlayerFromCell()
}

Hope this helps and is clear.

like image 24
Jan-Michael Tressler Avatar answered Oct 01 '22 01:10

Jan-Michael Tressler