How to cache an HLS video while playing it

Hi, I'm working on an app where a user can scroll through a feed of short videos (a bit like TikTok). I do some pre-fetching of videos a couple positions ahead of the user's scroll position, so the video can start playing as soon as he or she scrolls to the video.

Currently, I just pre-fetch by initializing a few AVPlayers. However, I'd like to add a better caching system.

I'm looking for the best way to:
  1. get videos to start playing as possible, while

  2. making sure to minimize re-downloading of videos if a user scrolls away from a video and back to it.

Is there a way that I can cache the contents of an AVPlayer that has loaded an HLS video?

Alternatively, I've explored using AVAssetDownloadTask to download the HLS videos. My issue is that I can't download the full video and then play it - I need to start playing the video as soon as the user scrolls to it, even if it's not done downloading.

Is there a way to start an HLS download with an AVAssetDownloadTask, and then start playing the video while it continues downloading?

Thank you!

Replies

Correction:

1. get videos to start playing as soon as possible


If the videos can be reasonably cached in RAM then we would recommend that. Regularly caching video to disk contributes to NAND wear, so it should be used for video that the user wants to watch while offline.

As long as the AVPlayerItem remains as the active item on the AVPlayer, it should retain its buffers. You may want to set AVPlayer.actionAtItemEnd to AVPlayerActionAtItemEndPause to avoid having the player eject the item when it reaches the end.

Also note AVPlayerItem.preferredForwardBufferDuration, which can be used to limit the amount of read-ahead for a playerItem that is not currently being played.
Got it, thank you! To clarify, are you suggesting that I just keep my AVPlayers in memory?

That's actually what I'm currently doing, but this seems problematic in terms of memory - a user might quickly scroll through 50 videos. I was actually told last year at an in-person WWDC lab that it was a bad idea to keep 50 AVPlayers in memory.

I added some cleanup logic to remove AVPlayers as the user scrolls away, but this means that videos need to be re-downloaded as if the user scrolls back.

How would I cache more than ~8 of my 10-second videos, if memory is too limited but disk is contributes to NAND wear?
Yes, it is a bad idea to try to keep that many players in memory. There is no really good way to cache more than, say, 4-6 videos in memory at once.

You can experiment with removing the AVPlayerLayer until the video is about to be played; that may reduce overall resource usage.

You can also reduce memory buffering by restricting the HLS variant chosen by the player to a lower bitrate until the user actually begins to play it, using preferredPeakBitRate or preferredMaximumResolution.

But most likely the best approach will come down to finding clever ways to cache fewer videos while still maintaining a responsive UI.
Sounds good, thanks!
@porges have you done prefetch or cache of video's initial part? Could you please suggest what approach you are using to prefetch the next videos or cache the playing part of the current video for further use when the user will scroll to that video again?
I initialize an AVPlayer for the user's current scroll position, the position above the user's current position, and the 3 positions below the user's current scroll position. Initializing the AVPlayer automatically loads the first HLS chunk.

I just keep references to those 5 AVPlayers. So if the user scrolls down one position then scrolls back, I don't need to re-initialize the AVPlayer. But if the user scrolls far away and then scrolls back, I do need to re-load the video.
Also, for the currently playing AVPlayer, I set player.preferredForwardBufferDuration = 0. This tells the system to use the default logic for how far ahead to load while playing. For the other players, I set player.preferredForwardBufferDuration = 1. This tells the players to just load one HLS chunk (generally 6-10 seconds).

@porges would you be willing to share some of how you accomplished this or if you found any other way to solve this. I have a similar setup, using prefetch in collectionview but finding videos are loading super slow or even not loaded when going back to it. I have tried a number of approaches but most seem to fall short. Either it will load ahead but be glitchy UI jumping(threading issue) or it will swipe very fast but I get a nice black screen before the video shows. I am trying to load m3u8 videos from cloudinary and I thought this would help but it seems the performance is still pretty weak. Any help would be greatly appreciated!

@DBott23 Please try ** AVQueuePlayer** instead of maintaining multiple AVPlayer instances of your own. I have observed the performance is slightly better in that scenario, although it is not completely smooth.