5

I’m trying add inline videos to my UITableViewCells a la Instagram, Twitter, Vine…etc. I’m testing out the UI with a local video file using AVPlayerController and a custom cell (see sample code below). I wait for the status of the AVPlayer to be ReadyToPlay and then play the video.

The issue is that when scrolling on the tableView the UI freezes for a fraction of section whenever a video cell is loaded which makes the app seem clunky. This effect is made worse when there are multiple video cells in a row. Any thoughts and help would be greatly appreciated

TableView Code:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

     let cell = tableView.dequeueReusableCellWithIdentifier("videoCell", forIndexPath: indexPath) as! CustomCell

     //Set up video player if cell doesn't already have one
     if(cell.videoPlayerController == nil){
         cell.videoPlayerController = AVPlayerViewController()
         cell.videoPlayerController.view.frame.size.width = cell.mediaView.frame.width
         cell.videoPlayerController.view.frame.size.height = cell.mediaView.frame.height 
         cell.videoPlayerController.view.center = cell.mediaView.center
         cell.mediaView.addSubview(cell.videoPlayerController.view) 
     }

     //Remove old observers on cell.videoPlayer if they exist
     if(cell.videoObserverSet){
         cell.videoPlayer.removeObserver(cell, forKeyPath: "status")
     }


     let localVideoUrl: NSURL = NSBundle.mainBundle().URLForResource("videoTest", withExtension: "m4v")!
     cell.videoPlayer = AVPlayer(URL:localVideoUrl)
     cell.videoPlayer.addObserver(cell, forKeyPath:"status", options:NSKeyValueObservingOptions.New, context = nil)
     cell.videoObserverSet = true         

     cell.videoPlayerController.player = cell.videoPlayer


     return cell
}

Custom Cell Observer Code:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {

    if(keyPath=="status"){
        print("status changed on cell video!");

        if(self.videoPlayer.status == AVPlayerStatus.ReadyToPlay){

            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                self.videoPlayer.play()
            })
        }
    }
}

I also tried loading an AVAsset version of the video using the loadValuesAsynchronouslyForKeys(["playable"]) but that didn't help with the UI block either.

What can I do to get the silky smooth video playback and scrolling of Instagram?

Eugene
  • 1,377
  • 2
  • 18
  • 22
  • Run it under the Time Profiler in Instruments. Set the sample interval to 40 µs (which appears to be the minimum) to see if you can get some idea of what it's doing during the glitch. Then you can try to move whatever that is off of the main thread. – rob mayoff Feb 19 '16 at 03:45
  • Thanks I'll definitely look into that – Eugene Feb 19 '16 at 05:36

1 Answers1

5

Another Update Highly recommend using Facebook's Async Display Kit ASVideoNode as a drop in replacement for AVPlayer. You'll get the silky smooth Instagram tableview performance while loading and playing videos. I think AVPlayer runs a few processes on the main thread and there's no way to get around them cause it's happening at a relatively low level.

Update: Solved

I wasn't able to directly tackle the issue, but I used to some tricks and assumptions and to get the performance I wanted.

The assumption was that there's no way to avoid the video taking a certain amount of time to load on the main thread (unless you're instagram and you possess some AsyncDisplayKit magic, but I didn't want to make that jump), so instead of trying to remove the UI block, try to hide it instead.

I hid the UIBlock by implementing a simple isScrolling check on on my tableView

func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {

    if(self.isScrolling){
        if(!decelerate){
            self.isScrolling = false
            self.tableView.reloadData()
        }
    }
}

func scrollViewDidEndDecelerating(scrollView: UIScrollView) {

    if(self.isScrolling){
        self.isScrolling = false
        self.tableView.reloadData()
    }
}

func scrollViewWillBeginDragging(scrollView: UIScrollView) {
    self.isScrolling = true
}

And then just made sure not to load the video cells when tableView is scrolling or a video is already playing, and reload the the tableView when the scrolling stops.

if(!isScrolling && cell.videoPlayer == nil){

    //Load video cell here
}

But make sure to remove the videoPlayer, and stop listening to replay notifications once the videoCell is not on the screen anymore.

func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {

    if(tableView.indexPathsForVisibleRows?.indexOf(indexPath) == nil){

        if (cell.videoPlayer != nil && murmur.video_url != nil){

            if(cell.videoPlayer.rate != 0){

                cell.videoPlayer.removeObserver(cell, forKeyPath: "status")
                cell.videoPlayer = nil;
                cell.videoPlayerController.view.alpha = 0;
            }
        }
    }
}

With these changes I was able to achieve a smooth UI with multiple (2) video cells on the screen at a time. Hope this helps and let me know if you have any specific questions or if what I described wasn't clear.

Eugene
  • 1,377
  • 2
  • 18
  • 22
  • So you actually didn't solve this, what you instead did was to only show the video player when the scrolling has stopped, which basically still blocks the UI (?) –  Feb 01 '17 at 14:26
  • Yeah, it was a hack where we only played videos when the scrolling had stopped. Once a video is already playing you can keep scrolling no problem, but we only start playing videos when they're on the screen and scrolling has stopped. The last time I checked I believe Twitter implemented a similar strategy in their iOS app. It's one of the drawbacks of using AVPlayer.. But I can highly recommend Facebook's AsyncDisplayKit that has an ASVideoNode object that's a stand in replacement for AVPlayer and that was built from the ground up not to block the main thread. Great performance. – Eugene Feb 02 '17 at 18:04
  • thanks for the answer. however I tried the AsyncDisplayKit and it fails totally. It is just a wrapper around AVPlayer and nothing else new about it and also very limited. I think the only way to go about this is to implement a completly own player, but that takes way too long time to do unfortunately.. –  Feb 02 '17 at 18:07