Basically what I am trying to do is cache the cell and have the video keep playing. When the user scroll back to the cell, the video should just show from where it was playing.
The problem is that the player gets removed and the cell ends up on a random cell instead of its designated area.
You will need to have two videos for this to work, I downloaded the videos off of here https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4 I just saved the same video twice under two different names.
How to replicate the problem: Tap the first cell, then scroll all the way down and then scroll back up, you will notice the video starts appearing all over the place. I just want the video to appear in its proper location and no where else.
Here is a link for the code: Code link
class ViewController: UIViewController {
private var collectionView: UICollectionView!
private var videosURLs: [String] = [
"ElephantsDream2", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream",
"ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream",
"ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream",
"ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream"
]
var cacheItem = [String: (cell: CustomCell, player: AVPlayer)]()
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
}
private func setupCollectionView() {
collectionView = UICollectionView(frame: .zero, collectionViewLayout: ColumnFlowLayout())
view.addSubview(collectionView)
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "cell")
collectionView.dataSource = self
collectionView.delegate = self
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? CustomCell else { return }
let item = videosURLs[indexPath.row]
let viewModel = PlayerViewModel(fileName: item)
cell.setupPlayerView(viewModel.player)
cacheItem[item] = (cell, viewModel.player)
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
videosURLs.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let item = videosURLs[indexPath.row]
if let cachedItem = cacheItem[item], indexPath.row == 0 {
print(indexPath)
print(item)
cachedItem.cell.setUpFromCache(cachedItem.player)
return cachedItem.cell
} else {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? CustomCell else { return UICollectionViewCell() }
cell.contentView.backgroundColor = .orange
let url = Bundle.main.url(forResource: item, withExtension: "mp4")
cell.playerItem = AVPlayerItem(url: url!)
return cell
}
}
}
class CustomCell: UICollectionViewCell {
private var cancelBag: Set<AnyCancellable> = []
private(set) var playerView: PlayerView?
var playerItem: AVPlayerItem?
override var reuseIdentifier: String?{
"cell"
}
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
override func prepareForReuse() {
super.prepareForReuse()
playerView = nil
playerView?.removeFromSuperview()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
layer.cornerRadius = 8
clipsToBounds = true
}
func setUpFromCache(_ player: AVPlayer) {
playerView?.player = player
}
func setupPlayerView(_ player: AVPlayer) {
if self.playerView == nil {
self.playerView = PlayerView(player: player, gravity: .aspectFill)
contentView.addSubview(playerView!)
playerView?.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
playerView!.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
playerView!.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
playerView!.topAnchor.constraint(equalTo: contentView.topAnchor),
playerView!.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
playerView?.player?.play()
NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime).sink { [weak self] notification in
if let p = notification.object as? AVPlayerItem, p == player.currentItem {
self?.playerView?.removeFromSuperview()
guard let self = self else { return }
NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
}
}.store(in: &cancelBag)
} else {
playerView?.player?.pause()
playerView?.removeFromSuperview()
playerView = nil
}
}
}