Custom video loader caching technique

This commit brings significant improvements to the video cache feature.

Previously, the cache would merely download the video when requested, in
parallel with AVPlayer which also triggers a video download.

The video cache has been updated to tap into the AVPlayer loading
process, removing the download duplication.

Here is how that works:
1. The player requests an AVAsset from the cache.
2. The cache will return a cached asset if possible, or a special AVURLAsset with a custom `AVAssetResourceLoaderDelegate`.
3. The video player will start sending loading requests to this loader delegate.
4. Upon receiving the first request, the loader delegate begins to download the video data on the background.
5. Upon receiving these requests, the loader delegate will also record the requests, so that it can serve them once possible
6. The loader delegate keeps track of all video data chunks as it receives them from the download task, through the `URLSessionDataDelegate` and `URLSessionTaskDelegate` protocols
7. As it receives data, it checks all pending loading requests from the AVPlayer, and fulfills them as soon as possible
8. If the download fails (e.g. timeout errors, loss of connection), it attempts to restart the download.
9. If the download succeeds, it saves the video to the cache on disk.

Closes: https://github.com/damus-io/damus/issues/1717
Changelog-Added: Add video cache to save network bandwidth
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Link: 20240411004129.84436-4-daniel@daquino.me
Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
Daniel D’Aquino
2024-04-11 00:42:04 +00:00
committed by William Casarin
parent 26d2627a1c
commit ba494f94ab
2 changed files with 300 additions and 11 deletions

View File

@@ -26,6 +26,7 @@ func video_has_audio(player: AVPlayer) async -> Bool {
final class DamusVideoPlayerViewModel: ObservableObject {
private let url: URL
private let maybe_cached_av_asset: VideoCache.MaybeCachedAVAsset?
private let player_item: AVPlayerItem
let player: AVPlayer
fileprivate let controller: VideoController
@@ -65,8 +66,14 @@ final class DamusVideoPlayerViewModel: ObservableObject {
init(url: URL, video_size: Binding<CGSize?>, controller: VideoController, mute: Bool? = nil) {
self.url = url
player_item = AVPlayerItem(url: url)
let maybe_cached_av_asset = try? VideoCache.standard?.maybe_cached_asset_for(video_url: url)
if maybe_cached_av_asset == nil {
Log.info("Something went wrong when trying to load the video with the video cache. Gracefully downgrading to non-cache video loading", for: .storage)
}
self.maybe_cached_av_asset = maybe_cached_av_asset // Save this wrapped asset to avoid having the loader delegate garbage collected while we still need it.
player_item = AVPlayerItem(asset: self.maybe_cached_av_asset?.av_asset ?? AVURLAsset(url: url))
player = AVPlayer(playerItem: player_item)
player.automaticallyWaitsToMinimizeStalling = true
self.controller = controller
_video_size = video_size