mirror of
https://github.com/zoriya/v10.git
synced 2026-08-15 18:34:22 +00:00
225 lines
12 KiB
Markdown
225 lines
12 KiB
Markdown
---
|
|
status: implemented
|
|
date: 2026-05-20
|
|
definition: sketched
|
|
---
|
|
|
|
# Engine-adapter integration
|
|
|
|
The engine's external-driving contract: `shareSignals` exposes the
|
|
composition's writable + readonly signal refs to a consumer callback at
|
|
setup time, and `SimpleHlsMediaMixin` is the canonical adapter that
|
|
maps a WHATWG HTMLMediaElement-shaped API onto those refs. The
|
|
*audience* for this feature is adapter authors and contributors who
|
|
need to drive the engine from outside — not end users, who see the
|
|
adapter's API only through whatever wraps it (e.g.,
|
|
`packages/core`'s `SimpleHlsMedia` class).
|
|
|
|
The feature ships as a *pair*: the framework-level `shareSignals`
|
|
mechanism + the canonical mixin. New adapter shapes (React hooks, RN
|
|
bridges, etc.) would compose on top of `shareSignals` independently of
|
|
the mixin.
|
|
|
|
## Status
|
|
|
|
- **Composition:** `createSimpleHlsEngine` (HLS VoD); `shareSignals`
|
|
composed last so other behaviors' setups have run by the time the
|
|
callback fires
|
|
- **Definition depth:** sketched — capability surface and the
|
|
adapter-rationale open question both documented
|
|
|
|
## Phases of complexity
|
|
|
|
| Phase | What | Notes |
|
|
|---|---|---|
|
|
| Writable signal refs via `onSignalsReady` | `shareSignals` captures `Signal<T>` / `ReadonlySignal<T>` refs into a consumer-supplied callback at setup time. Generic over composition shape (`makeShareSignals<S, C>()`) | Per-slot read/write intent is expressed at the use site (callers type captured refs as `Signal<T>` or `ReadonlySignal<T>`). Composed last in the engine so initial state writes are visible to the consumer |
|
|
| Mixin adapter pattern | `SimpleHlsMediaMixin` is the canonical consumer: function-of-base-class structure (mix into any base), captures refs once in `onSignalsReady`, exposes a WHATWG HTMLMediaElement-shaped API mapping each setter/method to engine writes | Downstream use: `class SimpleHlsMedia extends SimpleHlsMediaMixin(HTMLVideoElementHost) {}` in `packages/core/src/dom/media/simple-hls/` |
|
|
| Media element binding | `attach(el)` writes `context.mediaElement`; `detach()` clears it. **Engine persists across attach/detach cycles** — only `src` reassignment or explicit `destroy()` tears it down | Re-attach to a different element is supported. The engine is the durable state holder; `mediaElement` is a context slot |
|
|
| Source assignment via destroy + recreate | Adapter's `set src` destroys the current engine and creates a fresh one, re-applies any explicit preload, re-attaches `mediaElement` to the new engine, and writes the new `{ url }` | Bypasses the in-place source-replacement path. Rationale not documented in code — see Open questions and [source-replacement.md](./source-replacement.md) |
|
|
| Preload reflection | `set preload(value)` writes W3C values to `state.preload`; clearing (`preload = ''`) doesn't patch the current engine but is re-applied on the next src change. Pre-attach src + preload combinations are supported | Extended preload values flow through state but don't reach the DOM (per [`preload-modes`](./preload-modes.md)'s sticky-extended-values semantics) |
|
|
| Programmatic `play()` with retry | `play()` writes `state.loadActivated = true` (co-writer with `trackLoadTriggers`'s DOM listener path) before invoking native play. **Defensive retry:** if native play rejects with "no supported sources" while src is pending, wait for `loadstart` (MSE attaches blob URL) and retry once | The retry handles MSE pipeline timing — adapter doesn't know exactly when MSE setup attaches the blob URL. Listener canceled on src change |
|
|
|
|
## What's not implemented
|
|
|
|
- **Reactive change-notification surface** — `onSignalsReady` fires
|
|
once at setup. Consumers wanting to react to state changes from
|
|
outside the engine must keep refs and subscribe via SPF primitives
|
|
(`effect()`, signal `subscribe()`). The adapter doesn't expose
|
|
curated `onPlay` / `onSrcChange` / `onError` callbacks.
|
|
- **Multiple engine instances per adapter** — one engine per adapter
|
|
instance. No built-in pattern for multi-engine scenarios
|
|
(picture-in-picture with two streams, A/B testing).
|
|
- **Non-HTMLMediaElement adapter shapes** — React-friendly hooks,
|
|
React Native bridges, etc. would compose on top of `shareSignals`
|
|
independently. Today the canonical adapter is HTMLMediaElement-
|
|
shaped via the mixin. No bracketed candidate features tracked yet;
|
|
add when concrete need surfaces.
|
|
- **Curated state / error introspection** — consumers can read
|
|
`signals.state.*.get()` directly, but there's no adapter-level
|
|
"current playback state" / "current error" shape that doesn't
|
|
require knowing the engine's signal map.
|
|
|
|
## Implementation surface
|
|
|
|
**Composition:** `packages/spf/src/playback/engines/hls/engine.ts` —
|
|
`shareSignals` is the last behavior in the composition. Instantiated
|
|
once at module load:
|
|
|
|
```ts
|
|
const shareSignals = makeShareSignals<SimpleHlsEngineState, SimpleHlsEngineContext>();
|
|
|
|
// ...
|
|
|
|
return createComposition(
|
|
[
|
|
// ... all other behaviors ...
|
|
shareSignals,
|
|
],
|
|
{ config, initialState }
|
|
);
|
|
```
|
|
|
|
**Behavior factory:**
|
|
|
|
| Export | File | Role |
|
|
|---|---|---|
|
|
| `makeShareSignals<S, C>()` | `packages/spf/src/core/composition/share-signals.ts` | Generic behavior factory. Returns a `Behavior<StateSignals<S>, ContextSignals<C>, ShareSignalsConfig<S, C>>` whose setup invokes `config.onSignalsReady?.({ state, context })` |
|
|
| `ShareSignalsConfig<S, C>` | same | Config interface carrying the `onSignalsReady` callback |
|
|
|
|
**Canonical adapter:**
|
|
|
|
| Export | File | Role |
|
|
|---|---|---|
|
|
| `SimpleHlsMediaMixin<Base>` | `packages/spf/src/playback/engines/hls/adapter.ts` | Function-of-base-class mixin. Captures refs in `onSignalsReady`, exposes WHATWG HTMLMediaElement-shaped API |
|
|
| `SimpleHlsMediaElement` | same | Standalone subclass: `SimpleHlsMediaMixin(class {})`. Bare-bones reference instance |
|
|
| `SimpleHlsMediaProps` / `SimpleHlsMediaAPI` | same | The adapter's public-facing shape |
|
|
|
|
**Adapter ↔ engine state/context map:**
|
|
|
|
| Adapter call | Engine write |
|
|
|---|---|
|
|
| `attach(el)` | `context.mediaElement.set(el)` |
|
|
| `detach()` | `context.mediaElement.set(undefined)` |
|
|
| `destroy()` | `engine.destroy()` |
|
|
| `set src(value)` | `engine.destroy()` → new engine → `state.presentation.set({ url: value })` |
|
|
| `set preload(value)` | `state.preload.set(value)` (W3C values only; pre-empties stay engine-local) |
|
|
| `play()` | `state.loadActivated.set(true)` → native `play()` with `loadstart` retry on "no supported sources" |
|
|
|
|
**Downstream consumer:** `packages/core/src/dom/media/simple-hls/index.ts`:
|
|
|
|
```ts
|
|
export class SimpleHlsMedia extends SimpleHlsMediaMixin(HTMLVideoElementHost) {}
|
|
```
|
|
|
|
This is the canonical end consumer — used wherever the HTML player
|
|
expects an HTMLMediaElement-shaped object backed by SPF.
|
|
|
|
## Config surface
|
|
|
|
```ts
|
|
// ShareSignalsConfig<S, C>
|
|
{
|
|
onSignalsReady?: (signals: {
|
|
state: StateSignals<S>;
|
|
context: ContextSignals<C>;
|
|
}) => void;
|
|
}
|
|
```
|
|
|
|
The HLS engine config (`SimpleHlsEngineConfig`) extends
|
|
`ShareSignalsConfig<SimpleHlsEngineState, SimpleHlsEngineContext>`, so
|
|
`onSignalsReady` is part of the engine's config surface.
|
|
|
|
`SimpleHlsMediaMixin`'s constructor takes optional `config` and threads
|
|
it through to every engine instance (including the ones created on
|
|
each `set src`).
|
|
|
|
## Verification
|
|
|
|
- **Unit tests:**
|
|
- `packages/spf/src/playback/engines/hls/tests/adapter.test.ts` —
|
|
extensive coverage: src assignment / re-assignment / clear,
|
|
engine recreation on src change, mediaElement preservation across
|
|
src changes, play retry on `loadstart`, preload propagation,
|
|
attach/detach lifecycle
|
|
- `packages/spf/src/playback/engines/hls/tests/engine.test.ts`
|
|
→ "allows patching state and owners from outside" — direct
|
|
engine-level write surface (bypasses the mixin)
|
|
- `packages/spf/src/core/composition/tests/share-signals.test.ts`
|
|
— the behavior itself
|
|
- **Downstream usage:**
|
|
- `packages/core/src/dom/media/simple-hls/index.ts` —
|
|
`SimpleHlsMedia` consumer
|
|
- **Walkthrough:**
|
|
- `packages/spf/docs/hls-engine.md` Stage 10 — high-level coverage
|
|
of the pattern
|
|
|
|
## Open questions
|
|
|
|
- **Destroy-recreate vs in-place source replacement.** The canonical
|
|
adapter destroys + recreates the engine on every `src` change, even
|
|
though the engine's behaviors support in-place
|
|
`state.presentation` overwrite (validated by
|
|
[`source-replacement`](./source-replacement.md)'s test). The
|
|
rationale isn't documented in the code or commit history. Possible
|
|
motivations: stricter isolation between sources; simpler reasoning
|
|
per-engine; guarding against latent cleanup-cascade bugs. Worth
|
|
resolving when the cost of either choice surfaces.
|
|
- **Callback timing semantics.** `shareSignals`'s JSDoc explicitly
|
|
notes the callback fires while other behaviors are still in setup;
|
|
reads inside the callback may yield only initial-seed values. The
|
|
documented use is "capture refs, use later." Is read-at-setup-time
|
|
ever a supported case, or always discouraged?
|
|
- **Mixin base-class genericity.** `SimpleHlsMediaMixin<Base extends Constructor<any>>`
|
|
accepts any base; today's only documented consumer is
|
|
`HTMLVideoElementHost`. Other bases are structurally allowed but
|
|
not exercised — if usage broadens, the contract may need
|
|
tightening.
|
|
|
|
## Related features
|
|
|
|
- **preload-modes** — adapter `set preload(value)` and `play()` are
|
|
external writers on `state.preload` and `state.loadActivated`
|
|
respectively. The adapter's preload-clearing semantics (clear
|
|
`#preload` but don't patch the current engine) interact with
|
|
`preload-modes`'s sticky-extended-values rule.
|
|
- **source-replacement** — adapter `set src` is the canonical
|
|
user-facing entry into source replacement. The adapter's
|
|
destroy-recreate path bypasses the in-place reactor cascade — see
|
|
[`source-replacement.md`](./source-replacement.md) for the in-place
|
|
contract and the same open question.
|
|
- **mse-mms-pipeline** — adapter `attach(el)` binds the element MS
|
|
attaches to. The engine handles the rest of the MS lifecycle via
|
|
the resolved/unresolved cascade.
|
|
- **audio-playback** / **subtitles** / **video-abr** /
|
|
**buffer-management** — all driven by engine state the adapter
|
|
writes through. The adapter doesn't expose these features' surfaces
|
|
directly; consumers read engine state via the captured signal refs.
|
|
|
|
## 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 with an
|
|
alternative adapter shape. The variant ships an independent
|
|
`SimpleHlsAudioOnlyMediaElement` adapter (via
|
|
`SimpleHlsAudioOnlyMediaMixin`) parallel to `SimpleHlsMediaElement`;
|
|
the `shareSignals` mechanism + mixin pattern compose unchanged. The
|
|
consumer-facing API matches the WHATWG `HTMLMediaElement` surface.
|
|
- **[`video-only-mode-override`](../use-cases/video-only-mode-override.md)**
|
|
*(coarse)* — Phase 1 baseline constituent on the inverse axis.
|
|
Ships an independent `SimpleVideoOnlyHlsMediaElement`-style
|
|
adapter parallel to `SimpleHlsMediaElement`. Same `shareSignals`
|
|
pattern; consumer-facing API differs from both default and
|
|
audio-only-mode-override.
|
|
|
|
## See also
|
|
|
|
- [clusters.md § Engine lifecycle](./clusters.md#engine-lifecycle)
|
|
- [packages/spf/docs/hls-engine.md § Stage 10](../../../../packages/spf/docs/hls-engine.md)
|
|
— `shareSignals` and the adapter pattern walkthrough
|
|
- [conventions/signals.md](../conventions/signals.md) — per-slot
|
|
`Signal<T>` / `ReadonlySignal<T>` intent (relevant for how consumers
|
|
type captured refs at the use site)
|
|
- `packages/core/src/dom/media/simple-hls/index.ts` — canonical
|
|
downstream consumer
|