--- status: implemented date: 2026-05-20 definition: sketched --- # Audio playback Today's single-rendition audio playback in the HLS engine. The engine parses audio renditions from the multivariant playlist, picks one via the default picker at source load, fetches its media playlist, sets up an audio `SourceBuffer`, and loads segments. The selected rendition is locked for the lifetime of the source — mid-stream switching belongs to [`multi-language-audio`](./multi-language-audio.md) (coarse, not yet implemented). Symmetric with `video-abr` (video) and `subtitles` (text) in the per-track-type capability set. Most of the implementation surface is *owned architecturally* by other features (`mse-mms-pipeline`, `buffer-management`, `source-replacement`); this doc captures what's audio-specific. ## Status - **Composition:** `createSimpleHlsEngine` (HLS VoD) - **Definition depth:** sketched — capability surface documented; language-aware selection exists in `media/primitives/` but isn't wired (see *What's not implemented*) ## Phases of complexity Capability slices around today's audio playback contract. | Phase | What | Notes | |---|---|---| | Audio rendition recognition | `parseMultivariantPlaylist` surfaces audio renditions from `#EXT-X-MEDIA:TYPE=AUDIO` lines with `language`, `name`, `default`, `autoselect`, `channels`, `codecs`, `uri` | Engine consumes at most one today; parsing handles multiple — the foundation `multi-language-audio` builds on | | Default audio rendition selection | `selectAudioTrack` runs the default picker once on `'presentation-resolved'` entry. Default picker today is `pickFirstTrackId` (first track in the audio selection set) — language-unaware | Config-overridable via `SelectAudioTrackConfig.picker`. The language-aware `pickAudioTrack` exists in `media/primitives/` but isn't wired — see *What's not implemented* | | Audio media playlist resolution | `resolveAudioTrack` (sibling of resolveVideoTrack / resolveTextTrack, shared `setupTrackResolution` helper) fetches the selected rendition's media playlist on entry; aborts on source un-resolve via state-bound `AbortController` | Same shape as video / text resolution | | Audio SourceBuffer + actor setup | `setupAudioBufferActors` creates the audio `SourceBuffer` + `SourceBufferActor` + `SegmentLoaderActor`. Uses plain `fetchStream` (no bandwidth sampling — there's no audio ABR yet) | Owned architecturally by `mse-mms-pipeline`; the Firefox `mozHasAudio` cross-type invariant lives in that feature's documentation | | Audio segment loading | `loadAudioSegments` dispatches the per-type 4-state segment-load FSM (`'preconditions-unmet' / 'dormant' / 'metadata-only' / 'full-range'`) consuming `(preload, loadActivated)`. Same gate behavior as video | Owned architecturally by `buffer-management` | | Source-change clearance | On source un-resolve, `selectedAudioTrackId` clears, audio actors tear down, in-flight playlist fetch aborts. New source's audio rendition picked fresh on the next resolve | Owned architecturally by `source-replacement` | ## What's not implemented - **Multi-rendition recognition + programmatic selection + mid-stream switching** — covered by [`multi-language-audio`](./multi-language-audio.md) (partial, sketched — Tier 1 + most of Tier 2 implemented). Default selection now uses `pickAudioTrack`'s three-tier picker (`preferredAudioLanguage` → `DEFAULT=YES` → first-track), so `preferredAudioLanguage` config takes effect. Tier 2 programmatic selection via `userAudioTrackSelection` filter and same-codec mid-stream switching with next-segment-boundary flush also implemented. Persistence and A/V sync policy refinements deferred. - **Audio ABR** — covered by [audio-abr](./audio-abr.md). `setupAudioBufferActors` uses plain `fetchStream`; the bandwidth-sampling `createTrackedFetch` isn't wired into the audio fetch path. The sampling primitive exists (used by video); audio needs a parallel `switchAudioQuality` behavior to consume it. - **Channels-aware selection (5.1 / surround)** — covered by [5.1-surround-selection](./5.1-surround-selection.md). Audio tracks carry a `channels` field surfaced by the parser, but it's not yet used for selection or capability filtering. - **Audio-only composition optimizations** — the engine tolerates audio-only sources today (basic coverage via `engine.test.ts` "handles audio-only stream"), but the default engine doesn't *explicitly* compose for audio-only contexts (no shorter buffer targets, no subtracted video-related work). The explicit variant ships via the [audio-only-mode-override](../use-cases/audio-only-mode-override.md) use case (Phase 1 landed); detect-from-parser routing in the default engine to supplant the tolerance is future work tracked there. - **A/V sync handling** — delegated entirely to the browser's MSE pipeline. The engine doesn't intervene on audio/video sync. ## Implementation surface **Composition:** `packages/spf/src/playback/engines/hls/engine.ts` — audio behaviors composed alongside video and text: ```ts // Track selection (reads config for initial preferences). selectAudioTrack, selectTextTrack, // Resolve selected tracks (fetch media playlists) resolveVideoTrack, resolveAudioTrack, resolveTextTrack, // ... // MSE setup // ... setupAudioBufferActors, // Segment loading // ... loadAudioSegments, ``` **Behaviors:** | Behavior | File | Responsibility | |---|---|---| | `selectAudioTrack` | `packages/spf/src/playback/behaviors/select-tracks.ts` | Default audio rendition selection on source load. Lifecycle-only; mutually exclusive with `switchAudioTrack` | | `switchAudioTrack` | `packages/spf/src/playback/behaviors/dom/switch-audio-track.ts` | Filter-reactive slot owner + mid-stream flush dispatcher. **Owned architecturally by [`multi-language-audio`](./multi-language-audio.md)** — composed in both `createSimpleHlsEngine` and `createHlsAudioOnlyEngine` today | | `resolveAudioTrack` | `packages/spf/src/playback/behaviors/resolve-track.ts` | Fetches the selected audio media playlist | | `setupAudioBufferActors` | `packages/spf/src/playback/behaviors/dom/setup-buffer-actors.ts` | Audio SourceBuffer + actor setup. **Owned architecturally by `mse-mms-pipeline`** | | `loadAudioSegments` | `packages/spf/src/playback/behaviors/dom/load-segments.ts` | Audio segment loading dispatcher. **Owned architecturally by `buffer-management`** | **Helpers:** | Helper | File | Status | |---|---|---| | `pickAudioTrack(presentation, config)` | `packages/spf/src/media/primitives/select-tracks.ts` | **Default picker today** — language-aware three-tier (`preferredAudioLanguage` → `DEFAULT=YES` → first-track). Wired by [`multi-language-audio`](./multi-language-audio.md) Tier 1 | | `pickFirstTrackId(presentation, 'audio')` | `packages/spf/src/media/primitives/select-tracks.ts` | Simple first-track fallback (still available for callers overriding via `SelectAudioTrackConfig.picker`) | **State slots:** - `selectedAudioTrackId` — single-writer. Owner depends on which audio- selection behavior is composed: `selectAudioTrack` (lifecycle-only) or `switchAudioTrack` (filter-reactive + mid-stream flush; the variant composed in `createSimpleHlsEngine` and `createHlsAudioOnlyEngine` today). Becomes `switchAudioQuality`'s responsibility when [audio-abr](./audio-abr.md) lands (extends `switchAudioTrack`). - `userAudioTrackSelection` — added by [`multi-language-audio`](./multi-language-audio.md). Consumer-driven `Partial` filter narrowing the audio candidate set before `switchAudioTrack`'s picker runs. - Reads `presentation` (audio renditions surface in `presentation.selectionSets`) **Manifest parsing:** `parseMultivariantPlaylist` (`packages/spf/src/media/hls/parse-multivariant.ts`) extracts audio renditions from `#EXT-X-MEDIA:TYPE=AUDIO` lines. ## Config surface ```ts { preferredAudioLanguage?: string; // Exposed but inert with the // default picker; see What's // not implemented } ``` Plus the behavior-level override: ```ts // SelectAudioTrackConfig (consumed via composition config) { picker?: TrackPicker; // Override default // pickFirstTrackId } ``` Consumers wanting language-aware selection today must override `picker` — e.g., to use `pickAudioTrack` from `@videojs/spf` primitives. ## Verification - **Unit tests:** - `packages/spf/src/playback/engines/hls/tests/engine.test.ts` → "orchestrates complete pipeline" — asserts `selectedAudioTrackId` defined, audio track resolved, audio buffer actor present - `packages/spf/src/playback/engines/hls/tests/engine.test.ts` → "handles audio-only stream" — basic audio-only coverage - `packages/spf/src/playback/behaviors/dom/tests/setup-buffer-actors.test.ts` — per-type audio setup - **Coverage gap:** no dedicated `select-tracks.test.ts` coverage specifically asserting audio picker behavior (the default `pickFirstTrackId` path is exercised through the orchestration tests; the `pickAudioTrack` primitive has its own unit tests in `media/primitives/tests/`) - **Sandbox:** - `apps/sandbox/src/spf-segment-loading/` — exercises audio playback as part of HLS playback ## Open questions - **Audio-only composition guarantees.** The engine *tolerates* audio-only sources today, but is it *designed* for them? The [audio-only-mode-override](../use-cases/audio-only-mode-override.md) use case (Phase 1 landed) supplies the explicit variant; whether the default engine routes to it for audio-only sources via detect-from-parser is future work tracked there. - **Channels exposure vs use.** The parser surfaces `channels` on audio tracks but no selection logic consumes it. Stays inert until [5.1-surround-selection](./5.1-surround-selection.md) wires it. ## Related features - **[multi-language-audio](./multi-language-audio.md)** *(partial, sketched)* — the extension covering multi-rendition surfacing (free via parser), language-aware default selection (wires `pickAudioTrack`), programmatic selection via `userAudioTrackSelection` filter, and same-codec mid-stream switching via next-segment-boundary flush. Today's `audio-playback` is the baseline it builds on. - **subtitles** — parallel structure (default picker + per-type segment loading; same `setupTrackResolution` helper). Text-track selection is user opt-in by default; audio's default selection always picks something (or nothing if no audio renditions). - **video-abr** — video equivalent. Video has ABR overlay; audio doesn't (yet — see [audio-abr](./audio-abr.md)). - **mse-mms-pipeline** — owns audio SourceBuffer setup (`setupAudioBufferActors`). The Firefox `mozHasAudio` cross-type invariant lives in that feature's documentation. - **buffer-management** — owns audio segment loading (`loadAudioSegments`). Same 4-state FSM as video / text. - **source-replacement** — audio actors and `selectedAudioTrackId` tear down via the resolved/unresolved cascade. - **preload-modes** — gates audio loading via the same FSM that gates video and text. - **[audio-abr](./audio-abr.md)** — bandwidth sampling + quality switching for audio. Parallel sibling of video-abr on the audio axis; consumes audio-playback's single-rendition baseline + (when it lands) multi-language-audio's rendition-group machinery. - **[5.1-surround-selection](./5.1-surround-selection.md)** — channels-aware codec-change selection. Consumer of capability- probing on the audio channel-count axis. Wires the `channels` field surfaced by the parser into selection logic. - **[audio-only-mode-override](../use-cases/audio-only-mode-override.md)** *(use case; Phase 1 landed)* — engine variant optimized for audio-only delivery; covers both truly-audio-only sources and mixed sources delivered as audio-only. - **capability-probing** *(candidate)* — narrows the audio candidate set selection runs over; unsupported codecs filtered upstream of the `selectAudioTrack` picker. ## Use cases that compose this feature - **[`audio-only-mode-override`](../use-cases/audio-only-mode-override.md)** *(partial — Phase 1 landed)* — Phase 1 baseline constituent. The audio-only delivery variant composes this feature's rendition selection, media playlist resolution, and segment loading as-is via `createHlsAudioOnlyEngine`. ## See also - [presentation-modeling.md](../presentation-modeling.md) — architectural deep-dive on the format-neutral data shape and parser interface that surfaces audio renditions (the `parsePresentation` contract this feature's recognition phase relies on) - [multi-language-audio.md](./multi-language-audio.md) — future extension on top of this feature - [clusters.md § Track & variant registry](./clusters.md#track--variant-registry) - [conventions/behaviors.md](../conventions/behaviors.md) — per-type specialization pattern (`setupTrackResolution`) - [packages/spf/docs/hls-engine.md](../../../../packages/spf/docs/hls-engine.md) — engine composition walkthrough (Stage 2: track selection; Stage 3: track resolution)