mirror of
https://github.com/zoriya/v10.git
synced 2026-08-14 09:59:44 +00:00
204 lines
12 KiB
Markdown
204 lines
12 KiB
Markdown
---
|
|
status: implemented
|
|
date: 2026-05-20
|
|
definition: sketched
|
|
---
|
|
|
|
# Preload modes
|
|
|
|
The engine's loading-semantics implementation: how `preload="none|metadata|auto"`
|
|
plus user / programmatic activation determine when the engine fetches the
|
|
manifest, sets up MSE, and starts segment loading. Together, the
|
|
`syncPreload` and `trackLoadTriggers` behaviors model loading behavior
|
|
similar to native `HTMLMediaElement` playback — `state.preload` is the
|
|
mode, `state.loadActivated` is the override that fires on
|
|
`play` / `seeking` (or programmatic intent), and downstream gates read
|
|
both.
|
|
|
|
This doc captures the **capability surface**: what works, what doesn't,
|
|
which behaviors / slots implement it, and how downstream features gate
|
|
on it.
|
|
|
|
## Status
|
|
|
|
- **Composition:** `createSimpleHlsEngine` (HLS VoD)
|
|
- **Definition depth:** sketched — capability surface and implementation
|
|
footprint documented; the extended-preload-value mechanism is a
|
|
forward-compatibility hook with no shipped consumer yet
|
|
|
|
## Phases of complexity
|
|
|
|
What's implemented today, organized as capability slices around the
|
|
engine's loading-semantics contract.
|
|
|
|
| Phase | What | Notes |
|
|
|---|---|---|
|
|
| W3C preload mode honoring | Engine respects `preload="none" \| "metadata" \| "auto"` via the `isBlockingPreload` predicate that downstream gates consume (`resolvePresentation`, the per-type segment-loading FSM). `'none'` is the strictest gate — blocks manifest fetch and all segment loading until activation. `'metadata'` resolves manifests + sets up MSE + fetches init segments only. `'auto'` runs the full pipeline | `isBlockingPreload` in `media/utils/preload.ts` is the core predicate |
|
|
| Bidirectional DOM ↔ state sync | `state.preload` and `mediaElement.preload` stay synchronized for W3C values. Effects registered read-before-write so a freshly mounted `<video>` element's `preload` attribute wins on attach. All writes are deduped to break echo loops between the two sides | `syncPreload`; load-bearing ordering documented in the behavior |
|
|
| Extended preload values | External writes of non-W3C values (e.g. `'canplay'`) are sticky on `state.preload`: the DOM-side read won't overwrite them, the state-side write won't push them to the DOM | Consumer-extension point; no shipped consumer reads extended values today |
|
|
| Default backfill | `state.preload` is never `undefined` in steady state. When neither the DOM attribute nor external code supplies a value, backfilled from `config.defaultPreload` (default `'metadata'`, matching the HTMLMediaElement element default) | Resolves the empty-state ambiguity at most one place: `syncPreload`'s read effect |
|
|
| DOM-triggered load activation | DOM `play` and `seeking` events on the attached media element flip `state.loadActivated` to true. Immediate-true on entry if `!el.paused \|\| el.seeking` — covers autoplay, native-controls play, and direct-DOM-`play()` paths the engine wasn't the source of | `trackLoadTriggers`; mirrors native HTMLMediaElement preload-override semantics |
|
|
| Programmatic load activation | External code writes `state.loadActivated = true` directly — the canonical path in `@videojs/spf/hls` is `SimpleHlsMediaMixin.play()`, which signals playback intent before invoking the element's `.play()`. Co-writer with `trackLoadTriggers`; same downstream effect | Multi-writer with intentionally orthogonal decision domains: DOM-event-driven vs programmatic-intent-driven |
|
|
| Per-source reset | `state.loadActivated` resets to `false` when source identity changes (URL or `mediaElement` swap, including direct in-place replacement with no `undefined` intermediate). New source starts at its appropriate preload mode regardless of the previous source's activation history | Sticky-per-source-identity; not sticky-per-engine |
|
|
|
|
## What's not implemented
|
|
|
|
- **Granular preload between `'metadata'` and `'auto'`** — the extended-
|
|
preload-value mechanism can carry slot values like `'firstSegment'` or
|
|
`'firstFrame'`, but no downstream consumer reads them. Extended-value
|
|
support is a forward-compatibility hook today, not a shipped capability.
|
|
- **`loadActivated` re-deactivation within source** — sticky-true within
|
|
a source identity. There's no way to "go back to preload-only" mid-
|
|
source (e.g., to pause-and-evict on backgrounded tabs). A new source
|
|
reset is the only way to clear the slot.
|
|
- **Activation triggers beyond `play` / `seeking`** — `loadedmetadata`,
|
|
`canplay`, time-threshold crossings, viewport intersection, and similar
|
|
hooks aren't wired in. Adding one is local to `trackLoadTriggers`.
|
|
- **External-state-write race during attach** — `syncPreload`'s read
|
|
effect runs before the write on `mediaElement` swap, so a freshly
|
|
attached element with a W3C `preload` attribute wins on attach. The
|
|
read uses `peek` on `state.preload` to avoid re-triggering on external
|
|
writes; external writes still go through but are queued behind the
|
|
attach-time DOM read.
|
|
|
|
## Implementation surface
|
|
|
|
**Composition:** `packages/spf/src/playback/engines/hls/engine.ts` — both
|
|
behaviors are composed at the top of the pipeline, before
|
|
`resolvePresentation`. `syncPreload` is in the runtime-agnostic
|
|
`playback/behaviors/` (operates against `MediaElementLike`);
|
|
`trackLoadTriggers` is in `playback/behaviors/dom/` (consumes
|
|
`HTMLMediaElement` events directly).
|
|
|
|
**Behaviors:**
|
|
|
|
| Behavior | File | Responsibility |
|
|
|---|---|---|
|
|
| `syncPreload` | `packages/spf/src/playback/behaviors/sync-preload.ts` | Bidirectional `state.preload` ↔ `mediaElement.preload` sync; sticky extended values; W3C-only DOM writes; deduped to break echo loops |
|
|
| `trackLoadTriggers` | `packages/spf/src/playback/behaviors/dom/track-load-triggers.ts` | 3-state slot-driven FSM (`'preconditions-unmet' ⟷ 'monitoring' ⟷ 'load-active'`) writing `state.loadActivated = true` on `play` / `seeking` |
|
|
|
|
**Helpers:** `packages/spf/src/media/utils/preload.ts`
|
|
|
|
| Export | Role |
|
|
|---|---|
|
|
| `StandardPreload` | `'auto' \| 'metadata' \| 'none'` — the W3C `<video>`/`<audio>` `preload` value type |
|
|
| `isStandardPreload(value)` | Discriminator separating W3C values from extended values |
|
|
| `DEFAULT_PRELOAD` | `'metadata'` — matches the HTMLMediaElement element default |
|
|
| `isBlockingPreload(preload, defaultPreload?)` | Returns `true` iff `(preload \|\| defaultPreload) === 'none'`. Consumed by `resolvePresentation` and segment-loading gates |
|
|
|
|
**State slots:**
|
|
|
|
- `state.preload` — multi-writer:
|
|
- **External / extended-value writer** (sticky) — external code may write
|
|
extended values (e.g. `'canplay'`); `syncPreload`'s read effect leaves
|
|
them alone, and its write effect doesn't push them to the DOM.
|
|
- **`syncPreload` read effect (DOM-driven, W3C-only)** — copies
|
|
`mediaElement.preload` into `state.preload` on `mediaElement` swap or
|
|
`presentation.url` change, gated on `isStandardPreload` and on
|
|
`state.preload` not already holding an extended value.
|
|
- **`syncPreload` default-backfill** — when neither DOM nor external
|
|
code has supplied a value, backfills from `config.defaultPreload`.
|
|
- `state.loadActivated` — multi-writer:
|
|
- **`trackLoadTriggers`** writes `true` from DOM `play` / `seeking`
|
|
listeners, plus immediate-true on entry if `!el.paused || el.seeking`.
|
|
Writes `false` on source-identity reset.
|
|
- **Adapter / external code** writes `true` directly to signal
|
|
programmatic intent (canonical path: `SimpleHlsMediaMixin.play()`).
|
|
- `state.presentation` — read-only by these behaviors; the URL field is
|
|
used by both to detect source identity changes (`syncPreload` for
|
|
attach-time DOM-read re-firing; `trackLoadTriggers` for the per-source
|
|
reset).
|
|
|
|
**Context slots:** `context.mediaElement` (read-only). Both behaviors
|
|
re-fire on element swap.
|
|
|
|
## Config surface
|
|
|
|
```ts
|
|
{
|
|
defaultPreload?: StandardPreload; // default 'metadata' — backfill for
|
|
// state.preload AND fallback for
|
|
// isBlockingPreload's gate predicate
|
|
}
|
|
```
|
|
|
|
`defaultPreload` is consumed by both `syncPreload` (state backfill) and
|
|
`resolvePresentation` (gate fallback) — keeping them in agreement is the
|
|
point of having a single config knob.
|
|
|
|
## Verification
|
|
|
|
- **Unit tests:**
|
|
- `packages/spf/src/playback/behaviors/tests/sync-preload.test.ts` —
|
|
bidirectional sync, sticky extended values, DOM-attribute-wins-on-
|
|
attach ordering, dedupe behavior
|
|
- `packages/spf/src/playback/behaviors/dom/tests/track-load-triggers.test.ts`
|
|
— FSM transitions, `play` / `seeking` triggering, immediate-true on
|
|
entry, per-source reset, multi-writer coexistence with external writes
|
|
- **Sandbox:**
|
|
- `apps/sandbox/src/spf-segment-loading/` — main SPF demo; exercises
|
|
preload-aware loading end-to-end
|
|
- `apps/sandbox/src/simple-hls-html/` / `simple-hls-react/` — adapter
|
|
integration paths showing how `SimpleHlsMediaMixin.play()` writes
|
|
`loadActivated` programmatically alongside the DOM path
|
|
|
|
## Open questions
|
|
|
|
- **Triggers beyond `play` / `seeking`.** Should `loadedmetadata`,
|
|
`canplay`, or viewport-intersection hooks contribute to
|
|
`loadActivated`? `trackLoadTriggers` is the natural home, but each
|
|
trigger has different consumer semantics worth working through.
|
|
- **Extended preload value adoption.** No shipped consumer reads
|
|
extended values today. The sticky-write mechanism is a
|
|
forward-compatibility hook; the first consumer (e.g., a
|
|
`'firstSegment'` mode that fetches one segment then stops) would
|
|
also need a corresponding gate in `resolvePresentation` / the
|
|
segment-load FSM.
|
|
- **Multi-writer factoring on `loadActivated`.** Today: DOM listener
|
|
(`trackLoadTriggers`) + programmatic adapter (`SimpleHlsMediaMixin.play()`).
|
|
Two writers, one slot, intentionally orthogonal. As the multi-writer
|
|
pattern accumulates data points (`selectedTextTrackId` is the other
|
|
current concordant slot; `selectedAudioTrackId` is the proposed
|
|
third for `multi-language-audio`), a shared coordination primitive
|
|
may emerge.
|
|
- **Re-deactivation policy.** Sticky-true today; no
|
|
in-source-identity reset. A future "pause + evict" policy on
|
|
backgrounded tabs would push on this — likely a separate writer to
|
|
`loadActivated = false` from a yet-to-be-named lifecycle behavior.
|
|
|
|
## Related features
|
|
|
|
- **mse-mms-pipeline** — gated indirectly through `resolvePresentation`.
|
|
`setupMediaSource` only fires once the presentation is fully resolved,
|
|
which only happens once the preload gate opens.
|
|
- **video-abr** — same: gated indirectly via presentation resolution.
|
|
- **subtitles** — `loadTextTrackSegments` uses the same preload-aware
|
|
load-mode FSM as audio/video (`'dormant' / 'metadata-only' / 'full-range'`).
|
|
- **multi-language-audio** *(coarse)* — would inherit the same gating
|
|
shape for any new audio behaviors.
|
|
- **buffer-management** — the canonical *consumer* of this feature's
|
|
gate state. The 4-state load-mode FSM (`'preconditions-unmet'` /
|
|
`'dormant'` / `'metadata-only'` / `'full-range'`) lives in
|
|
`loadVideoSegments` / `loadAudioSegments` / `loadTextTrackSegments`
|
|
and maps directly to `(preload, loadActivated)`. Forward-buffer +
|
|
back-buffer plans also build on top of the gates this feature
|
|
produces.
|
|
- **source-replacement** — the per-source `loadActivated` reset is one
|
|
piece of the broader source-replacement cleanup cascade. New
|
|
behaviors that join the engine and gate on resolved presentation must
|
|
honor that cascade or in-place source replacement breaks silently.
|
|
|
|
## See also
|
|
|
|
- [conventions/behaviors.md](../conventions/behaviors.md) — behavior
|
|
shape; slot-driven FSM pattern (`trackLoadTriggers` is a canonical
|
|
instance)
|
|
- [conventions/signals.md](../conventions/signals.md) — multi-writer
|
|
slot conventions (`loadActivated` is the second concordant data point
|
|
alongside `selectedTextTrackId`)
|
|
- [clusters.md § Gating / prerequisite chains](./clusters.md#gating--prerequisite-chains)
|
|
— cross-cluster pattern this feature exemplifies
|
|
- [packages/spf/docs/hls-engine.md](../../../../packages/spf/docs/hls-engine.md)
|
|
— engine composition walkthrough (Stage 1 covers `syncPreload` and
|
|
`trackLoadTriggers`)
|