Here is an example how to play video inside collectionView cells without scrolling lag.
First of all we must prepare everything that we need to play video inside init method of cell without adding item or asset then we can create our asset or item when video url or path is assigned asynchronously.
The second important thing is resize your videos to cell size multiplied by 2 (for example my cell size is (w:50, h:50) and I resized my videos to (w:100, h:100) to get good quality).
In this example I'll use AVPlayerLooper
to auto loop videos so you can change it with AVPlayer
if you need.
Example CollectionViewCell class :
import UIKit
import AVKit
class ExampleCell: UICollectionViewCell {
public var isPlaying: Bool = false
public var videolink: URL? = nil {
didSet {
guard let link = videolink, oldValue != link else { return }
loadVideoUsingURL(link)
}
}
private var queuePlayer = AVQueuePlayer()
private var playerLayer = AVPlayerLayer()
private var looperPlayer: AVPlayerLooper?
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
queuePlayer.volume = 0.0
queuePlayer.actionAtItemEnd = .none
playerLayer.videoGravity = .resizeAspect
playerLayer.name = "videoLoopLayer"
playerLayer.cornerRadius = 5.0
playerLayer.masksToBounds = true
contentView.layer.addSublayer(playerLayer)
playerLayer.player = queuePlayer
}
override func layoutSubviews() {
super.layoutSubviews()
/// Resize video layer based on new frame
playerLayer.frame = CGRect(origin: .zero, size: CGSize(width: frame.width, height: frame.width))
}
private func loadVideoUsingURL(_ url: URL) {
/// Load asset in background thread to avoid lagging
DispatchQueue.global(qos: .background).async {
let asset = AVURLAsset(url: url)
/// Load needed values asynchronously
asset.loadValuesAsynchronously(forKeys: ["duration", "playable"]) {
/// UI actions should executed on the main thread
DispatchQueue.main.async { [weak self] in
guard let `self` = self else { return }
let item = AVPlayerItem(asset: asset)
if self.queuePlayer.currentItem != item {
self.queuePlayer.replaceCurrentItem(with: item)
self.looperPlayer = AVPlayerLooper(player: self.queuePlayer, templateItem: item)
}
}
}
}
}
public func startPlaying() {
queuePlayer.play()
isPlaying = true
}
public func stopPlaying() {
queuePlayer.pause()
isPlaying = false
}
}
And collectionView should be implemented like this:
Instantiate your cell
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ExampleCell.self),
for: indexPath) as? ExampleCell else {
return UICollectionViewCell()
}
cell.videolink = videoFileURL
return cell
}
Start playing video when collectionView will display the cell and stop it when it end to display
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let videoCell = cell as? ExampleCell else { return }
videoCell.startPlaying()
}
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let videoCell = cell as? ExampleCell else { return }
videoCell.stopPlaying()
}
Handle playing status in scrolling
// TODO: write logic to stop the video before it begins scrolling
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
let cells = collectionView.visibleCells.compactMap({ $0 as? ExampleCell })
cells.forEach { videoCell in
if videoCell.isPlaying {
videoCell.stopPlaying()
}
}
}
// TODO: write logic to start the video after it ends scrolling
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
guard !decelerate else { return }
let cells = collectionView.visibleCells.compactMap({ $0 as? ExampleCell })
cells.forEach { videoCell in
if !videoCell.isPlaying && canPlayVideos {
videoCell.startPlaying()
}
}
}
// TODO: write logic to start the video after it ends scrolling (programmatically)
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let cells = collectionView.visibleCells.compactMap({ $0 as? ExampleCell })
cells.forEach { videoCell in
// TODO: write logic to start the video after it ends scrolling
if !videoCell.isPlaying && canPlayVideos {
videoCell.startPlaying()
}
}
}