Playing videos in UICollectionViewCell. Video is playing fine but have scroll performance on collectionView
Asked Answered
E

2

6

I explored other answers on community and tried this -

    player = AVPlayer()
    player.volume = 0.0
    player.actionAtItemEnd = AVPlayerActionAtItemEnd.None
    addPlayerLayer()

    let asset = AVAsset.init(URL: videoURL)
    asset.loadValuesAsynchronouslyForKeys(["duration", "playable"]) {

        dispatch_async(dispatch_get_main_queue(), { () -> Void in
                let item = AVPlayerItem.init(asset: asset)
                self.player.replaceCurrentItemWithPlayerItem(item)
                self.player.play()
        })
    }

But whenever there is slow network, there is noticeable lag/hiccups while scrolling collectionView. Am I doing something wrong? Please help

Esquiline answered 24/1, 2017 at 12:43 Comment(5)
Possible duplicate of AVPlayerViewController in UICollectionViewCell bug?Militarist
It's not duplicate, its about scrolling performance of collectionView while playing videos.Esquiline
How did you do it ? i have a bunch of videos from server that need to play all at once in collection view horizontally ..Ainsworth
@FaridAlHaddad Finally, I ended up with playing the visible videos first then while scrolling I play videos on scroll stop(only those videos which are visible). This does not have lags. If this strategy will work for your requirement and you need help with code, do let me know, I can post an answer to this question.Esquiline
Yes this will do the job for me, can you please show me how you did that ? in addition, i think this way everytime you scroll the video will load again from the internet consuming a lot of data, do you know how can i handle the cache of the videos ? thank you so michAinsworth
P
3

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:

  1. 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
    }
    
  2. 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()
    }
    
  3. 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()
           }
       }
    }
    
Pape answered 30/11, 2021 at 13:29 Comment(0)
P
2

Please look into Facebook's AsyncDisplayKit (the engine behind Facebook and Instagram feeds) you can render video for the most part on background threads using their AVideoNode.

OR:

player = AVPlayer()
player.volume = 0.0
player.actionAtItemEnd = AVPlayerActionAtItemEnd.None
addPlayerLayer()
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), {}
    let asset = AVAsset.init(URL: videoURL)
    asset.loadValuesAsynchronouslyForKeys(["duration", "playable"]) {
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
                let item = AVPlayerItem.init(asset: asset)
                self.player.replaceCurrentItemWithPlayerItem(item)
                self.player.play()
        })
    }
})
Parsons answered 24/1, 2017 at 13:7 Comment(1)
I tried both your code and facebook library, none of them solve scrolling lag problem.Esquiline

© 2022 - 2024 — McMap. All rights reserved.