Search code examples
iosswiftavfoundationavplayeravplayerlayer

Reuse AVPlayer's in TableView


Can anyone help in creating a pool of AVPlayer's to be reused in a tableView? So the idea is to have a pool of AVPlayer's, let's say 3, and continue to reuse them as the user scrolls through the table.

Here's a quick synopsis of the app and what we want to build:

  • Each video is an mp4 and is only 5 seconds long. So each cell will play a 5 second video and loop it.
  • All videos are local, they will be downloaded to disk before the table is even shown to the user. This will help in terms of smooth scroll performance of the tableView.

Right now I am creating too many AVPlayer's and not reusing them which is having a bad effect on performance such as scrolling is a bit choppy. Also, Apple does not allow an infinite amount of AVPlayer's to be created.

Any ideas? Thanks

Update 1:

import UIKit
import VIMVideoPlayer

var cache = NSCache<NSString, VIMVideoPlayerView>()

class FeedTableViewCell: UITableViewCell {
// MARK: - Properties

@IBOutlet weak var containerView: UIView!

static let reuseIdentifier = "FeedTableViewCell"

var video: Video? {
    didSet {
        if let cachedVideoPlayerView = cache.object(forKey: video!.preview!.remoteURL as NSString)  {
            // We have a cached video player view!
            containerView.addSubview(cachedVideoPlayerView)
        } else {
            // There is nothing cached.
            let previewURL = FileManager.applicationDocumentsDirectory.appendingPathComponent(video!.preview!.fileName!)
            let newVideoPlayer = VIMVideoPlayer()
            newVideoPlayer.setURL(previewURL)
            newVideoPlayer.isLooping = true
            newVideoPlayer.isMuted = true
            newVideoPlayer.disableAirplay()

            let newVideoPlayerView = VIMVideoPlayerView()
            newVideoPlayerView.frame = contentView.bounds
            newVideoPlayerView.delegate = self
            newVideoPlayerView.setVideoFillMode(AVLayerVideoGravityResizeAspectFill)
            newVideoPlayerView.player = newVideoPlayer
            containerView.addSubview(newVideoPlayerView)
            cache.setObject(newVideoPlayerView, forKey: video!.preview!.remoteURL as NSString)
        }
    }
}

// MARK: - Life Cycle

override func awakeFromNib() {
    super.awakeFromNib()

    print("AWAKE FROM NIB CELL")
}

override func prepareForReuse() {
    super.prepareForReuse()

}
}

// MARK: - VIMVideoPlayerViewDelegate
extension FeedTableViewCell: VIMVideoPlayerViewDelegate {
func videoPlayerViewIsReady(toPlayVideo videoPlayerView: VIMVideoPlayerView!) {
    videoPlayerView.player.play()
}
}

Solution

  • Extending your idea of using a pool, instead of reinventing the wheel, why not leverage UITableViewCell's current implementation of reusability? At any given time, n 'reusable' UITableViewCells exist, acting as your pool.

    If each one of these cells contains a single AVPlayer subview, then the management is done for you by the table view. Therefore in the UITableViewCell's reuse identifier, stop the player (if needed), update its MP4 (from an in-memory cache ideally), and start it again.

    If needed, you can cache the position of the video when the cell disappears in order to make it seem like the video never stopped playing while scrolling was in progress.

    -- As a side note, theoretically this will work, but has not been tested with a live app. The obvious caveat is the size of the video being loaded into the AVPlayer on the main thread, while attempting to maintain 60fps.

    Edit:

    Please see https://stackoverflow.com/a/35514126/556479 for more info.