Skip to content

Adaptive bitrate (HLS) playback for large videos — GB-scale files buffer badly on a single 720p proxy #148

Description

@Rl0007

Problem

Multi-GB source videos take a long time to start and keep buffering during playback.

Today playback is a plain progressive download:

  • frontend/src/components/MediaPlayerDialog.tsx:123 renders <video src={mediaUrl}> against a presigned R2 URL. There is no adaptive streaming layer — the browser just range-requests one big MP4.
  • frontend/src/components/review/VideoPlayer.tsx / frontend/src/hooks/useVideoPlayer.ts do the same.
  • The only transcode we do is a single fixed rendition: vms/proxy.py_ffmpeg_proxy() produces one 720p H.264 CRF-28 MP4 with +faststart.

So there is exactly one quality level. A viewer on a slow connection has no lower rung to fall back to, and a viewer on a fast connection can't get better than 720p. If the proxy isn't ready yet, the player falls back to the multi-GB original, which is the worst case.

What we want

Bunny.net-style playback: one upload → an ABR ladder of renditions → an HLS manifest the player switches between automatically based on bandwidth, plus instant seek and a quality selector.

Proposed scope

Backend (vms/proxy.py)

  • Replace the single-output ffmpeg call with an ABR ladder, e.g. 240p / 360p / 480p / 720p / 1080p (skip rungs above the source resolution).
  • Emit HLS: per-rendition .m3u8 + segments and a master playlist, -hls_time 4 -hls_playlist_type vod, keyframe-aligned across rungs (-g/-force_key_frames) so switching is seamless.
  • Upload the segment tree to R2 under something like hls/<asset>/…; store the master playlist key on VMS Asset (new field alongside proxy_r2_key, e.g. hls_master_key) and keep proxy_status semantics.
  • Preserve the existing enqueue/progress/realtime flow (_publish_proxy_progress) — the ladder is longer, so per-rendition progress would help.

Delivery

  • HLS segments need signed access. Either presign each segment, or serve the manifest through a whitelisted endpoint that rewrites segment URLs with presigned links (mind expiry vs. long videos).

Frontend

  • Swap the raw <video> for an HLS-capable player (hls.js, with native HLS on Safari/iOS) in MediaPlayerDialog.tsx and review/VideoPlayer.tsx, driven from useVideoPlayer.ts so both share one implementation.
  • Auto quality by default + a manual quality selector.
  • Fall back to the existing progressive proxy URL when no HLS manifest exists (older assets), and never fall back to the raw multi-GB original.

Open questions

  • Do we transcode the ladder ourselves on the long queue (ffmpeg CPU cost on the bench box, hours per GB-scale file), or push transcoding to a service (Bunny Stream / Cloudflare Stream / Mux) and just store the playback ID?
  • Backfill: re-encode existing assets, or generate HLS lazily on first play?
  • Storage cost of keeping original + ladder in R2.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions