HLS VOD playback for browser (supplements MP4 proxy) #170

Open
zgaetano wants to merge 0 commits from feat/hls-vod-playback into main
Owner

Moves browser playback of recorded assets to HLS, retiring the MP4 range-stitching path for VOD. The MP4 proxy is kept (Premiere panel + downloads).

Why

The MP4 /video path streams the proxy through Node and stitches around a RustFS ranged-GET bug (streams from byte 0, drops bytes — wasteful per seek). HLS segments are small whole-file GETs, so the bug doesn't apply, and hls.js is already wired in the player for live.

Changes

  • worker/hls.js: remuxToHls() stream-copies the proxy MP4 → fMP4 HLS (playlist.m3u8 + init.mp4 + segment_*.m4s) via the existing segmentToHls, uploads to hls/<id>/, sets assets.hls_s3_key. hlsWorker backfills from an existing proxy.
  • proxy.js: generate HLS inline after the MP4 upload (local file, no re-download/re-encode); best-effort/non-fatal.
  • worker/index.js: register hls worker wherever proxy runs.
  • mam-api: GET /assets/:id/hls/:file serves playlist/init/segments as whole-object GETs (strict filename validation, no Range). /stream prefers hls_s3_key (type: 'hls'). reprocess?type=hls backfills. Migration 025 adds assets.hls_s3_key.
  • Frontend unchanged (hls.js already handles type: 'hls').

Verified live (zampp1)

  • Migration 025 applied; reprocess?type=hls → worker remuxed → hls_s3_key set
  • /streamtype: hls
  • playlist.m3u8 → 200 application/vnd.apple.mpegurl (valid HLS v7 fMP4)
  • init.mp4 + segment_00000.m4s200 whole-file (video/mp4), no 206/Range
  • Path-traversal + bad-filename → 400

Notes

  • New captures/uploads get HLS automatically; existing assets fall back to MP4 /video until backfilled via reprocess?type=hls (a bulk backfill could be a follow-up).
  • Single rendition (no ABR ladder) — out of scope for now.

🤖 Generated with Claude Code

Moves browser playback of recorded assets to **HLS**, retiring the MP4 range-stitching path for VOD. The MP4 proxy is **kept** (Premiere panel + downloads). ## Why The MP4 `/video` path streams the proxy through Node and stitches around a RustFS ranged-GET bug (streams from byte 0, drops bytes — wasteful per seek). HLS segments are small **whole-file GETs**, so the bug doesn't apply, and `hls.js` is already wired in the player for live. ## Changes - `worker/hls.js`: `remuxToHls()` stream-copies the proxy MP4 → fMP4 HLS (`playlist.m3u8` + `init.mp4` + `segment_*.m4s`) via the existing `segmentToHls`, uploads to `hls/<id>/`, sets `assets.hls_s3_key`. `hlsWorker` backfills from an existing proxy. - `proxy.js`: generate HLS inline after the MP4 upload (local file, no re-download/re-encode); best-effort/non-fatal. - `worker/index.js`: register `hls` worker wherever `proxy` runs. - `mam-api`: `GET /assets/:id/hls/:file` serves playlist/init/segments as whole-object GETs (strict filename validation, no Range). `/stream` prefers `hls_s3_key` (`type: 'hls'`). `reprocess?type=hls` backfills. Migration 025 adds `assets.hls_s3_key`. - Frontend unchanged (hls.js already handles `type: 'hls'`). ## Verified live (zampp1) - Migration 025 applied; `reprocess?type=hls` → worker remuxed → `hls_s3_key` set - `/stream` → `type: hls` - `playlist.m3u8` → 200 `application/vnd.apple.mpegurl` (valid HLS v7 fMP4) - `init.mp4` + `segment_00000.m4s` → **200 whole-file** (`video/mp4`), no 206/Range - Path-traversal + bad-filename → 400 ## Notes - New captures/uploads get HLS automatically; existing assets fall back to MP4 `/video` until backfilled via `reprocess?type=hls` (a bulk backfill could be a follow-up). - Single rendition (no ABR ladder) — out of scope for now. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
zgaetano added 2 commits 2026-05-29 16:22:03 -04:00
Browser playback of recorded assets moves to HLS, retiring the MP4
range-stitching path for VOD. MP4 proxy is kept for the Premiere panel.

- worker/hls.js: remuxToHls() stream-copies the proxy MP4 → fMP4 HLS
  (playlist.m3u8 + init.mp4 + segment_*.m4s) via existing segmentToHls,
  uploads to hls/<id>/, sets assets.hls_s3_key. hlsWorker backfills from
  an existing proxy.
- proxy.js: generate HLS inline after the MP4 upload (local file, no
  re-download, no re-encode); best-effort/non-fatal.
- worker/index.js: register 'hls' worker wherever 'proxy' runs.
- mam-api: GET /assets/:id/hls/:file serves playlist/init/segments as
  whole-object GETs (no Range → sidesteps RustFS bug), strict filename
  validation. /stream prefers hls_s3_key (type:'hls'). reprocess?type=hls
  backfills. Migration 025 adds assets.hls_s3_key.
- Frontend unchanged: hls.js path already handles type:'hls'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This branch is already included in the target branch. There is nothing to merge.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/hls-vod-playback:feat/hls-vod-playback
git checkout feat/hls-vod-playback

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git checkout main
git merge --no-ff feat/hls-vod-playback
git checkout feat/hls-vod-playback
git rebase main
git checkout main
git merge --ff-only feat/hls-vod-playback
git checkout feat/hls-vod-playback
git rebase main
git checkout main
git merge --no-ff feat/hls-vod-playback
git checkout main
git merge --squash feat/hls-vod-playback
git checkout main
git merge --ff-only feat/hls-vod-playback
git checkout main
git merge feat/hls-vod-playback
git push origin main
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: WildDragonLLC/dragonflight#170
No description provided.