T-HUB/docs/reports/streaming-playback-spec.md

3.1 KiB

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 path is trivial)

Effort

API endpoint + segment math: ~80 lines + tests. Player progressive fetcher: ~60 lines in video-player/viewer. Total: half a session.


Appendix: compute fabric design (2026-10-17)

Tiers

  1. Shared worker (moxexleyhp1) — the operator's box: 3090 CUDA (transcription sidecar live), NVENC + QuickSync transcodes. Serves ALL collectives as baseline capacity.
  2. Collective-owned workers — a collective registers additional boxes via the hub (collective-scoped worker token + Windows downloader + setup/connection instructions). Those pull only that collective's jobs — increasing its compute without touching others.
  3. LLM engines — collectives (or the operator) register LiteLLM/OpenAI-compatible endpoints as inference providers (per-collective model routing: a collective's AI features point at its own engines first, hub defaults second).

Building blocks (when implemented)

  • worker_registrations table: workspace_id (NULL = shared), type (transcode | llm), token_hash, endpoint_url / capabilities, last_seen_at, created_by
  • Poll/claim protocol scoped per registration (the produce_job_claim lane pattern extends)
  • Windows downloader + PowerShell installer + connection instructions (the ipfs3 failover pattern: token + instructions served from the collective's Workers settings page)
  • LLM engine registrations join the model routing for AI features (transcription, captions, search embeddings)