# Video Adaptive HLS video served from R2 through the colo cache, with sprite-sheet scrubbing. ## Prepare Needs `ffmpeg`/`ffprobe` (libx264, AAC) and `cwebp`. ```sh bun run video:prepare --source /path/to/video.mov --id intro --version v1 [--captions subs.srt --lang en] ``` Writes `.artifacts/videos///`: `master.m3u8`, one fMP4 rendition per rung (`/index.m3u8`, `init.mp4`, `NNNNN.m4s`, 4 s segments, 2 s keyframes, no upscaling), `poster.jpg`, optional `captions/.vtt` (converted from SRT or VTT), and `scrub/` (256x144 tiles, 10x10 per WebP sheet, one tile per second, at most 1000 tiles). ## Upload ```sh bun run video:upload --directory .artifacts/videos/intro/v1 [--bucket gitfor] [--local] ``` Objects land in the `BUCKET` R2 binding under `videos///`. Media goes first and `master.m3u8` last; an already-published version is refused. Versions are immutable, so ship changes as a new version. ## Delivery `src/server/videos.ts` serves `/videos///`, only for the prepared layout. Responses are `immutable` and stored in the Workers cache on the first full fetch; later requests (including `Range` and `If-None-Match`) are answered from cache without touching R2. ## Player ```tsx ``` `src/components/VideoPlayer.tsx` uses hls.js (lazy-loaded) or native HLS on Safari. Hovering the timeline shows a sprite thumbnail; dragging fills the frame with sprite frames and seeks on release.