# Spec: progressive playback for encrypted media (the "decrypting…" lag fix) ## Problem Playing a Files video (SPENC2, client-sealed) waits for the WHOLE sealed file to download + decrypt before the first frame — length-dependent, brutal for 1-2 GB. ## Why it's fixable cleanly SPENC2 frames are already stored as INDEPENDENT 10 MiB AES-GCM segments (header 135 B + per-segment 4+12+ctLen). Every segment decrypts on its own, so any plaintext range can be served by fetching + decrypting only the covering segments. No re-encryption, no format change. ## Design 1. API: `GET /files/:id/range?offset=N&length=M` (auth = download route; direct/ objects only) → decrypts just the covering segments via `S3Storage.readDirectRange` (ranged R2 GET covering the needed segments, CEK unwrap from the same fetch, per-segment GCM), returns the plaintext slice + headers: X-Total-Size, X-Segment-Bytes. 16 MiB per-request cap. 2. Player integration (no Service Worker, no MSE): progressive blob — fetch [0, 16 MiB) → objectURL → play immediately (our encodes are +faststart, so a truncated mp4 plays head-first); background-fetch the remaining chunks, appending; when playback nears the fetched edge or the user seeks past it, await the covering chunk before continuing. On full fetch: swap in the complete blob (scrub-anywhere). 3. SPENC1 (server-sealed legacy): the single-stream GCM cannot be sliced — keep the whole-file fetch (these are the small/older items; new uploads are all SPENC2). ## Non-goals / later - True MSE/fMP4 streaming with a Service Worker (better scrub UX, bigger build) - Audio file streaming (same endpoint serves it; the