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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With