I try to play video in UITableViewCell
when a user clicks on play button in cell.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With