--- status: draft date: 2026-03-11 --- # Architecture > **This document describes the current SPF codebase as a snapshot in time — not the target design.** The architecture, implementation details, and component boundaries documented here are highly tentative and subject to significant change. The initial implementation captured useful lessons (source buffer coordination, end-of-stream timing, streaming response bodies, etc.), but the underlying architecture, primitives, and structure are expected to be substantially reworked in the near term. See [primitives.md](primitives.md) for the forward-looking design. Internal structure of SPF. ## Overview ``` ┌─────────────────────────────────────────────────────────┐ │ core/ (DOM-free) │ │ state ─ actor ─ task ─ HLS parser ─ ABR ─ buffer math │ └─────────────────────────────────────────────────────────┘ │ ┌──────────────────────────▼──────────────────────────────┐ │ dom/ (browser) │ │ │ │ PlaybackEngine │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Reactors (features/) │ │ │ │ loadSegments · endOfStream · setupMediaSource │ │ │ │ setupSourceBuffers · qualitySwitching · ... │ │ │ └─────────────────┬────────────────────────────────┘ │ │ │ send messages │ │ ┌─────────────────▼──────────────┐ │ │ │ Actors │ │ │ │ SegmentLoaderActor │ │ │ │ SourceBufferActor (×2) │ │ │ └─────────────────┬──────────────┘ │ │ │ execute │ │ ┌─────────────────▼──────────────┐ │ │ │ MSE │ │ │ │ MediaSource · SourceBuffer │ │ │ └────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ ``` The `core/` layer holds SPF's framework primitives (composition, signals, tasks, actors, reactors) — runtime-agnostic, with no DOM or network dependencies. The `media/` layer holds runtime-agnostic HAS-domain logic (ABR, buffer math, HLS parsing, CMAF-HAM types). The `dom/` layer wires browser platform APIs (MSE, fetch, HTMLMediaElement) into both. --- ## Core Layer ### Reactive State (`core/state/create-state.ts`) A batched, subscription-based state container. All feature coordination flows through state. ```ts interface State { get(): S; patch(partial: Partial): void; flush(): void; subscribe(listener: Listener): () => void; } ``` **Key behavior:** - `patch()` defers via `queueMicrotask` — multiple synchronous patches are coalesced into one notification. - `flush()` drains the pending patch immediately. Call when downstream subscribers need to react before the next tick (e.g., ABR sampling). - Selector subscriptions fire only when the selected slice changes, using a custom equality function. ### Actor (`core/actors/actor.ts` + `core/tasks/task.ts`) An actor owns a `snapshot` (status + context) and serializes its own work via a runner. ```ts interface Actor { snapshot: ActorSnapshot; subscribe(listener: () => void): () => void; } ``` **Task** — wraps an async function with an `AbortController`. Abortable at any point. **SerialRunner** — executes tasks one at a time. Used by SourceBufferActor because the SourceBuffer API is inherently serial (one `appendBuffer` at a time). **ConcurrentRunner** — deduplicates by ID. Used where parallel work is safe but duplicate tasks are wasteful. ### HLS Parsing (`media/hls/`) Parses multivariant and media playlists into typed structures (`Presentation`, `Track`, `Segment`). URL resolution is handled separately in `resolve-url.ts`, making the parsers pure functions of text input. ### ABR (`media/abr/`) Two components: **EWMA** — fast/slow exponentially weighted moving average pair. The fast weight tracks recent conditions; the slow weight anchors against outliers. Exported estimate is the minimum of both (conservative). **Quality selection** (`quality-selection.ts`) — given a bandwidth estimate and a list of tracks sorted by bitrate, picks the highest track whose bitrate fits within the estimate. Upgrades are subject to a `minUpgradeInterval` gate (default 8 s) to prevent oscillation; downgrades are immediate. > **Zero-factor correction:** Raw EWMA starts near zero. The displayed/used estimate must apply `estimate / (1 - α^totalWeight)` to correct for the initialization bias. ### Buffer Math (`media/buffer/`) **Forward buffer** (`forward-buffer.ts`) — computes the target load window: `[currentTime, currentTime + forwardBufferDuration]`. Also computes the flush point (segments behind `currentTime - backBufferDuration`). **Back buffer** (`back-buffer.ts`) — computes the portion of the buffer to evict when the engine is under memory pressure. --- ## DOM Layer ### PlaybackEngine (`dom/playback-engine/hls-engine.ts`, built on `core/composition/create-composition.ts`) The orchestration hub. Initializes all features in a fixed order, wiring shared state, owners, and a single event stream. **Feature init order:** | Step | Feature | Purpose | |------|---------|---------| | 0a | `syncPreload` | Bidirectional sync of `mediaElement.preload` ↔ `state.preload` (backfilled to `'metadata'` by default) before any buffering decisions | | 0b | `trackPlaybackInitiated` | `play` event → `state.playbackInitiated = true` | | 1 | `resolvePresentation` | Fetch multivariant playlist, parse tracks | | 2 | `selectVideoTrack` / `selectAudioTrack` / `selectTextTrack` | Choose initial tracks | | 3 | `resolveTrack` | Fetch media playlist for each selected track | | 3.5 | `calculatePresentationDuration` | Derive duration from playlists | | 4 | `setupMediaSource` | Create `MediaSource`, attach to `