2

I have been trying to add some videos into my tableView using the AVPlayer which I placed into the TableView Cell.

But the UI freezes while the video is being loaded.

This is my cellforRow at indexpath.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCel {


    var tableCell : UITableViewCell? = nil

    if (tableCell == nil){

        tableCell = tableView.dequeueReusableCell(withIdentifier: "cell")
    }

    let view = tableCell?.viewWithTag(9999)
    tableCell?.tag = indexPath.row


    DispatchQueue.main.async {
         if (tableCell?.tag == indexPath.row){
                let player = self.cache.object(forKey: indexPath.row as AnyObject) as! AVPlayer
                let playerLayer = AVPlayerLayer(player: player)
                playerLayer.frame = (view?.bounds)!
                view?.layer.addSublayer(playerLayer)
                player.play()

            }

//            tableView.reloadData()
    }

 return tableCell!
}

This is how I added the videos to the cache.

    for i in 0...10{

        var videoURL : URL? = nil

        if (i%2 == 0){

            videoURL = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
        }else{

            videoURL = URL(string: "http://techslides.com/demos/sample-videos/small.mp4")

        }
        let player = AVPlayer(url: videoURL!)

        arr?.append(player)

        self.cache.setObject(player, forKey: i as AnyObject)
        print("count = \(arr?.count)")
    }

What would be the best method to have this resolved?

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Pm Abi
  • 225
  • 5
  • 8

1 Answers1

1

Thankfully, you're in luck. iOS is optimized for just these types of issues. Apple engineers have gone to great lengths to provide APIs to help you achieve buttery smooth scrolling and load your content at the same time.

Note: Solving this issue requires offloading tasks to different threads on the CPU. It can get complicated, and there's a lot to learn about it. I strongly recommend you read Apple's documentation.

First, know that the cellForRowAtIndexPath: method should generally call your data source on the main thread. Meaning the call you have to DispatchQueue.main.async is not appropriately used in this scenario.

Instead, what you'll need to do is offload your AVPlayer caching to a separate, background thread and then come back into the main thread to update the cell's layer content. Additionally, you shouldn't be doing that in cellForRowAtIndexPath:. You'll likely need to subclass a UITableViewCell and write your own loading sequence within that - that way, you can quickly init your custom cell, hand it back to cellForRowAtIndexPath: (which can then continue along merrily), and keep loading things and updating from within the cell subclass.

To setup your AVPlayer in a background thread, do this:

DispatchQueue.global(qos: .background).async {
    // Background thread
    // Do your AVPlayer work here

    // When you need to update the UI, switch back out to the main thread
    DispatchQueue.main.async {
        // Main thread
        // Do your UI updates here
    }
}

You may want to look at the quality of service value you pass in when you create your background thread. The userInitiated QOS may be better for this specific case because the videos are being loaded as a result of a user-initiated action.

As for the actual caching of the videos. I am not too familiar with AVPlayer or AVPlayerItem, but from my understanding the more metadata (i.e. duration, tracks, etc.) you can give to AVPlayer before trying to render the video in a layer, the faster it goes (see this related StackOverflow Question).

Additionally, you'll probably want to check out the newly introduced APIs in iOS 10 that all you to pre-fetch cells in a table view. If you take advantage of the pre-fetch optimizations, you could likely load everything you need for AVPlayer in the background and well before the cells are ever requested for display in cellForRowAtIndexPath:.

Anyhow, hope you can get started from there and solve the issue. Definitely take the time to read the documentation on AVKit, GCD, and UITableView (specifically the new pre-fetching APIs).

Sam Spencer
  • 8,492
  • 12
  • 76
  • 133
  • 1
    Thank you Samuel, I can see that there is a huge performance improvement in the tableCell scrolling. I believe I may need to do some tinkering in the caching part too. – Pm Abi Jul 11 '17 at 06:38
  • 2
    In case of fast scrolling, the TableView cell may go out of user's view while the background thread may still be executing. If I am doing a `DispatchQueue.global` from within the `UITableViewCell` subclass, how am I supposed to know when the cell is being dequeued so as to cancel the Async process? any Idea? Otherwise it could become a memory leak case.... – Anjan Biswas Jan 17 '18 at 22:50