refactor(media)!: extract media package from core (#1879)

This commit is contained in:
rahim
2026-07-28 21:14:50 -07:00
committed by GitHub
parent fa54f75d38
commit 75dcc6675b
346 changed files with 1570 additions and 1072 deletions
+6 -6
View File
@@ -14,21 +14,21 @@ Treating every media target as a complete `HTMLMediaElement` coupled player stat
## Decisions
- Define small capabilities for playback, source, seeking, volume, tracks, presentation modes, and related events instead of one mandatory browser-shaped contract.
- Keep core contracts structural and DOM-free. Minimal event, target, range, and track shapes allow browser objects to satisfy them without making browser types the abstraction boundary.
- Keep media contracts structural and DOM-free. Minimal event, target, range, and track shapes allow browser objects to satisfy them without making browser types the abstraction boundary.
- Require a feature to narrow to the capability it needs. The base player target cannot imply support for optional media behavior.
- Put native-element forwarding in DOM host classes, where platform behavior belongs, rather than in the core contract.
- Put native-element forwarding in DOM host classes, where platform behavior belongs, rather than in the shared contract.
- Expose engine ownership through `MediaEngineHost`; keep engine-specific state off the common media surface.
- Make engine destruction asynchronous so source or configuration replacement can wait for network and MediaSource cleanup.
- Keep HTML custom elements as adapters over core media hosts rather than a second media hierarchy.
- Keep HTML custom elements as adapters over media hosts rather than a second media hierarchy.
## Consequences
Custom media can implement only the capabilities it supports, store features reveal their real dependencies, and non-DOM adapters can share the same core contracts. The cost is explicit capability checks and more interfaces, which is preferable to an implicit, oversized platform contract.
Custom media can implement only the capabilities it supports, store features reveal their real dependencies, and non-DOM adapters can share the same media contracts. The cost is explicit capability checks and more interfaces, which is preferable to an implicit, oversized platform contract.
## Current sources of truth
- Contracts and type guards: `packages/core/src/core/media/`
- Browser hosts and engine-backed media: `packages/core/src/dom/media/`
- Contracts and type guards: `packages/media/src/core/`
- Browser hosts and engine-backed media: `packages/media/src/dom/`
- HTML element adapters: `packages/html/src/media/`
- Player feature consumers and tests: `packages/core/src/dom/store/features/`
- Public exports and generated API reference
@@ -33,7 +33,7 @@ the mixin.
| 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/` |
| 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/media/src/dom/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 in-place recycling | Adapter's `set src` overwrites `state.presentation` on its single recycled engine (`{ url }`, or `undefined` for empty src). Media element + engine-wide preload persist; no engine recreation, no signal re-capture | Drives the engine's in-place source-replacement cascade — see [source-replacement.md](./source-replacement.md). (The adapter previously destroyed + recreated the engine per assignment.) |
| 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) |
@@ -105,7 +105,7 @@ return createComposition(
| `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`:
**Downstream consumer:** `packages/media/src/dom/simple-hls/media.ts`:
```ts
export class SimpleHlsMedia extends SimpleHlsMediaMixin(HTMLVideoElementHost) {}
@@ -148,7 +148,7 @@ each `set src`).
- `packages/spf/src/core/composition/tests/share-signals.test.ts`
— the behavior itself
- **Downstream usage:**
- `packages/core/src/dom/media/simple-hls/index.ts`
- `packages/media/src/dom/simple-hls/media.ts`
`SimpleHlsMedia` consumer
- **Walkthrough:**
- `packages/spf/docs/hls-engine.md` Stage 10 — high-level coverage
@@ -220,5 +220,5 @@ each `set src`).
- [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
- `packages/media/src/dom/simple-hls/media.ts` — canonical
downstream consumer
@@ -299,19 +299,19 @@ parallel to the existing `simple-hls-video` pair.
Public re-export: `@videojs/spf/hls` — all of the above ship via
`packages/spf/src/playback/engines/hls/index.ts`.
**Core — media wrapper** (`packages/core/src/dom/media/simple-hls-audio-only/`):
**Core — media wrapper** (`packages/media/src/dom/simple-hls-audio-only/`):
| Export | File | Purpose |
|---|---|---|
| `SimpleHlsAudioOnlyMedia` | `index.ts` | Applies `SimpleHlsAudioOnlyMediaMixin` to `HTMLAudioElementHost` (audio host, not video — symmetric with `mux-audio`) |
Public re-export: `@videojs/core/dom/media/simple-hls-audio-only`.
Public re-export: `@videojs/media/dom/simple-hls-audio-only`.
**HTML — custom element** (`packages/html/src/media/simple-hls-audio-only/`):
| Export | File | Purpose |
|---|---|---|
| `SimpleHlsAudioOnly` | `media/simple-hls-audio-only/index.ts` | Applies `MediaAttachMixin` + `CustomMediaElement('audio', SimpleHlsAudioOnlyMedia)` |
| `SimpleHlsAudioOnly` | `media/simple-hls-audio-only/media.ts` | Applies `MediaAttachMixin` + `CustomMediaElement('audio', SimpleHlsAudioOnlyMedia)` |
| `SimpleHlsAudioOnlyElement` (tag `simple-hls-audio-only`) | `define/media/simple-hls-audio-only.ts` | Custom-element definition; registers `<simple-hls-audio-only>` via `safeDefine` |
CDN entry: `packages/html/src/cdn/media/simple-hls-audio-only.ts`
@@ -415,8 +415,8 @@ configuration drives end-of-stream correctly with no per-type changes.
- [`packages/spf/src/playback/engines/hls/adapter-audio-only.ts`](../../../../packages/spf/src/playback/engines/hls/adapter-audio-only.ts) — Phase 1 adapter
- [`packages/spf/src/playback/engines/hls/tests/engine-audio-only.test.ts`](../../../../packages/spf/src/playback/engines/hls/tests/engine-audio-only.test.ts) — Phase 1 engine integration tests
- [`packages/spf/src/playback/engines/hls/tests/adapter-audio-only.test.ts`](../../../../packages/spf/src/playback/engines/hls/tests/adapter-audio-only.test.ts) — Phase 1 adapter tests
- [`packages/core/src/dom/media/simple-hls-audio-only/index.ts`](../../../../packages/core/src/dom/media/simple-hls-audio-only/index.ts) — Phase 1 core media wrapper
- [`packages/html/src/media/simple-hls-audio-only/index.ts`](../../../../packages/html/src/media/simple-hls-audio-only/index.ts) — Phase 1 HTML custom element
- [`packages/react/src/media/simple-hls-audio-only/index.tsx`](../../../../packages/react/src/media/simple-hls-audio-only/index.tsx) — Phase 1 React component
- [`packages/media/src/dom/simple-hls-audio-only/media.ts`](../../../../packages/media/src/dom/simple-hls-audio-only/media.ts) — Phase 1 media wrapper
- [`packages/html/src/media/simple-hls-audio-only/media.ts`](../../../../packages/html/src/media/simple-hls-audio-only/media.ts) — Phase 1 HTML custom element
- [`packages/react/src/media/simple-hls-audio-only/media.tsx`](../../../../packages/react/src/media/simple-hls-audio-only/media.tsx) — Phase 1 React component
- [`apps/sandbox/templates/html-simple-hls-audio-only/`](../../../../apps/sandbox/templates/html-simple-hls-audio-only/) — Phase 1 HTML sandbox demo template
- [`apps/sandbox/templates/react-simple-hls-audio-only/`](../../../../apps/sandbox/templates/react-simple-hls-audio-only/) — Phase 1 React sandbox demo template