mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
refactor: Rename SPF background-looping-video into background-video (#1731)
This commit is contained in:
@@ -133,11 +133,11 @@ actual use.
|
||||
consolidate into a single use-case doc with a *Variant-decision signal
|
||||
source* section covering both paths. The Case-1 feature doc does not
|
||||
exist separately in those cases. Not every use case has a Case-1
|
||||
sibling — `background-looping-video` has constituent features but no
|
||||
sibling — `background-video` has constituent features but no
|
||||
single Case-1 axis-counterpart.
|
||||
|
||||
- **Conflating sibling use cases** — e.g., `video-only-mode-override` and
|
||||
`background-looping-video` both touch the video-only delivery
|
||||
`background-video` both touch the video-only delivery
|
||||
composition but address different delivery scenarios (video-without-audio
|
||||
delivery vs Mux background-video product). Check at Step 5: distinct
|
||||
customer story? distinct composition specifics? If yes, separate docs
|
||||
@@ -207,7 +207,7 @@ Common shapes:
|
||||
video-only-mode-override) → constituent features include the parallel
|
||||
Case-1 source-shape feature plus the baseline playback / buffer /
|
||||
selection features.
|
||||
- Product-scenario use cases (background-looping-video, shorts-player) →
|
||||
- Product-scenario use cases (background-video, shorts-player) →
|
||||
constituent features include multiple capability features the variant
|
||||
assembles.
|
||||
|
||||
@@ -404,7 +404,7 @@ phases in, ask: is this really one use case, or is the variant actually
|
||||
a decomposition into multiple use cases? Heuristic: a slice belongs in
|
||||
its own doc if it has (a) a distinct customer story, (b) distinct
|
||||
composition specifics, or (c) independent timeline. Worked example:
|
||||
`video-only-mode-override` vs `background-looping-video` — both
|
||||
`video-only-mode-override` vs `background-video` — both
|
||||
exercise video-only delivery composition, distinct customer
|
||||
stories, distinct composition specifics.
|
||||
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Sandbox — SPF Background Looping Video</title>
|
||||
<title>Sandbox — SPF Background Video</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
@@ -223,7 +223,7 @@
|
||||
<div class="scrim"></div>
|
||||
|
||||
<div class="hero">
|
||||
<h1>Background Looping Video</h1>
|
||||
<h1>Background Video</h1>
|
||||
<p>
|
||||
Subtractive engine variant — audio, text tracks, ABR, and preload monitoring are composed out; a max-resolution
|
||||
picker is composed in. The SPF foundation for <code><mux-background-video></code>.
|
||||
+8
-8
@@ -1,9 +1,9 @@
|
||||
import '@app/styles.css';
|
||||
|
||||
// SPF Background Looping Video — sandbox demo
|
||||
// http://localhost:5173/spf-background-looping-video/
|
||||
// SPF Background Video — sandbox demo
|
||||
// http://localhost:5173/spf-background-video/
|
||||
//
|
||||
// Drives `BackgroundLoopingVideoMediaElement`. The diagnostic strip
|
||||
// Drives `BackgroundVideoMediaElement`. The diagnostic strip
|
||||
// surfaces three signals reviewers should verify:
|
||||
// - loadActivated is true from frame 0 (no preload-gate or play-event needed)
|
||||
// - the picker honors `maxResolution` (defaults to the highest variant)
|
||||
@@ -19,8 +19,8 @@ import '@app/styles.css';
|
||||
|
||||
import { SOURCES } from '@app/shared/sources';
|
||||
import { effect, snapshot } from '@videojs/spf';
|
||||
import type { BackgroundLoopingVideoEngineState } from '@videojs/spf/background-looping-video';
|
||||
import { BackgroundLoopingVideoMediaElement } from '@videojs/spf/background-looping-video';
|
||||
import type { BackgroundVideoEngineState } from '@videojs/spf/background-video';
|
||||
import { BackgroundVideoMediaElement } from '@videojs/spf/background-video';
|
||||
|
||||
// ── DOM refs ──────────────────────────────────────────────────────────────────
|
||||
const video = document.getElementById('bg-video') as HTMLVideoElement;
|
||||
@@ -50,7 +50,7 @@ for (const id of HLS_SOURCE_IDS) {
|
||||
}
|
||||
|
||||
// ── Renditions ────────────────────────────────────────────────────────────────
|
||||
type MaybePresentation = BackgroundLoopingVideoEngineState['presentation'];
|
||||
type MaybePresentation = BackgroundVideoEngineState['presentation'];
|
||||
|
||||
function videoTracksOf(presentation: MaybePresentation) {
|
||||
return presentation?.selectionSets?.find((s) => s.type === 'video')?.switchingSets[0]?.tracks ?? [];
|
||||
@@ -75,7 +75,7 @@ function trackDimensions(track: VideoTrack): { w: number; h: number } {
|
||||
// ── Adapter lifecycle ─────────────────────────────────────────────────────────
|
||||
let currentSourceId: keyof typeof SOURCES = DEFAULT_ID;
|
||||
let currentMaxResolution: string | undefined;
|
||||
let adapter!: BackgroundLoopingVideoMediaElement;
|
||||
let adapter!: BackgroundVideoMediaElement;
|
||||
let stopDiag: () => void = () => {};
|
||||
|
||||
function rebuildAdapter(): void {
|
||||
@@ -84,7 +84,7 @@ function rebuildAdapter(): void {
|
||||
video.pause();
|
||||
adapter?.destroy();
|
||||
|
||||
adapter = new BackgroundLoopingVideoMediaElement({ config: { maxResolution: currentMaxResolution } });
|
||||
adapter = new BackgroundVideoMediaElement({ config: { maxResolution: currentMaxResolution } });
|
||||
// src before attach: the engine starts resolving the presentation before
|
||||
// play() (called inside attach) runs, so no teardown races the play promise.
|
||||
adapter.src = SOURCES[currentSourceId].url;
|
||||
@@ -270,7 +270,7 @@ snapshot between operations rather than awaiting `send()` directly.
|
||||
candidate (audio-only-tuned buffer-management behavior).
|
||||
- **[`video-only-mode-override`](../use-cases/video-only-mode-override.md)**
|
||||
*(coarse)* — Phase 1 baseline constituent (used as-is). Loop-
|
||||
friendly buffer fetching is the peer `[background-looping-
|
||||
friendly buffer fetching is the peer `[background-
|
||||
video]` use case's concern, not this one's; video-only-mode-
|
||||
override's Phase 3 may surface other defaults but not
|
||||
loop-around.
|
||||
|
||||
@@ -92,7 +92,7 @@ A feature's *implementation shape* falls along a spectrum:
|
||||
|---|---|---|---|
|
||||
| **Policy** | Pure config / function variation consumed by an existing behavior. No new behaviors | Inside an existing behavior | *(no documented examples yet — shape: a height cap consumed by a `selectQuality` config)* |
|
||||
| **Middle pattern** | A new state-producing behavior monitors an external signal and updates state; existing consumer behaviors update to respect that state. Heavier than pure policy but lighter than composition | New behavior + targeted edits to consumers | *(no documented examples yet — shapes: `ResizeObserver` → cap state → switching; CDN-tracking → selection state → failover; buffer-state monitor → ended signal; `initPTS` detection → offset state → append)* |
|
||||
| **Composition** | A different composed engine. Alternative compositions assemble a variant via one or more composition mechanisms (subtract / add / alternative-implementation / alternative-default-configuration), in any combination, to handle different *modes* or *delivery scenarios*. See [`use-cases/README.md`](../use-cases/README.md) for the full doc-type, mechanism taxonomy, and decomposition rubric | At the Adapter level, on initial conditions | *(no instance docs yet; candidates in [`use-cases/README.md`](../use-cases/README.md) Index — `audio-only-mode-override`, `video-only-mode-override`, `background-looping-video`)* |
|
||||
| **Composition** | A different composed engine. Alternative compositions assemble a variant via one or more composition mechanisms (subtract / add / alternative-implementation / alternative-default-configuration), in any combination, to handle different *modes* or *delivery scenarios*. See [`use-cases/README.md`](../use-cases/README.md) for the full doc-type, mechanism taxonomy, and decomposition rubric | At the Adapter level, on initial conditions | *(no instance docs yet; candidates in [`use-cases/README.md`](../use-cases/README.md) Index — `audio-only-mode-override`, `video-only-mode-override`, `background-video`)* |
|
||||
|
||||
Composition is bounded to **modes** and **delivery scenarios** — see [`use-cases/README.md`](../use-cases/README.md) for the doc-type that captures composition variants. Most "feels like composition" items actually fit the middle pattern.
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ Initially empty; populated as docs land. Candidates flagged in source material (
|
||||
|
||||
- [`audio-only-mode-override`](./audio-only-mode-override.md) *(partial — Phase 1 landed)* — audio-only delivery. Covers both truly-audio-only HLS sources and mixed-manifest sources delivered as audio-only via the same shared engine factory (`createHlsAudioOnlyEngine`). Subsumes what Notion originally framed as separate epics #4a (Basic Audio-only) and #4b (Audio-only Mode Override).
|
||||
- [`video-only-mode-override`](./video-only-mode-override.md) *(coarse)* — video-only delivery. Inverse-axis sibling of [`audio-only-mode-override`](./audio-only-mode-override.md); same shape. Subsumes Notion epics NEW-A (Basic Video-only) and NEW-B (Video-only Composition).
|
||||
- `[background-looping-video]` — Mux's background-video product scenario: loop + autoplay-muted + GPU/thermal-aware caps + likely silent-video delivery. **Distinct from `video-only-mode-override`** despite shared Mux consumer context; both may share constituent features but address different delivery scenarios. *[GitHub #873](https://github.com/videojs/v10/issues/873); [`mux-background-video`](https://github.com/muxinc/mux-background-video) prior art.*
|
||||
- `[background-video]` — Mux's background-video product scenario: loop + autoplay-muted + GPU/thermal-aware caps + likely silent-video delivery. **Distinct from `video-only-mode-override`** despite shared Mux consumer context; both may share constituent features but address different delivery scenarios. *[GitHub #873](https://github.com/videojs/v10/issues/873); [`mux-background-video`](https://github.com/muxinc/mux-background-video) prior art.*
|
||||
- Further candidates surfaced in source material but not yet scoped: picture-in-picture, short-form / shorts-player, audio-podcast mode, cast/remote-display compositions, ambient/decorative video.
|
||||
|
||||
## See also
|
||||
|
||||
@@ -213,7 +213,7 @@ artifact; how the variant is signaled is orthogonal.
|
||||
|
||||
- **Adapter shape proliferation.** Each use-case composition that gets its own
|
||||
adapter multiplies the adapter surface. As more use cases land
|
||||
(background-looping-video, video-only-mode-override, audio-podcast mode,
|
||||
(background-video, video-only-mode-override, audio-podcast mode,
|
||||
etc.), the inventory of `SimpleXHlsMediaElement` classes grows. Worth flagging
|
||||
as a registry-level concern; may surface a future "use-case adapter factory"
|
||||
pattern.
|
||||
@@ -401,7 +401,7 @@ configuration drives end-of-stream correctly with no per-type changes.
|
||||
- **[`video-only-mode-override`](./video-only-mode-override.md)** *(coarse)* —
|
||||
inverse-axis sibling. Same shape, video-side instead of audio-side: subtract
|
||||
audio behaviors from any source to deliver video-only. Distinct from
|
||||
`[background-looping-video]` despite shared constituent features.
|
||||
`[background-video]` despite shared constituent features.
|
||||
|
||||
## See also
|
||||
|
||||
|
||||
+7
-7
@@ -4,7 +4,7 @@ date: 2026-05-22
|
||||
definition: technical
|
||||
---
|
||||
|
||||
# Background-looping video
|
||||
# Background video
|
||||
|
||||
Engine variant for silent, autoplay-looping video on ambient/decorative
|
||||
surfaces (hero backgrounds, GIF-replacement loops, editorial previews).
|
||||
@@ -18,7 +18,7 @@ demonstrates SPF composability by *removing* behaviors from the standard
|
||||
playback engine rather than adding new logic.
|
||||
|
||||
Distinct from [`video-only-mode-override`](./video-only-mode-override.md):
|
||||
both subtract audio from mixed-source manifests, but background-looping-
|
||||
both subtract audio from mixed-source manifests, but background-
|
||||
video also commits to single-rendition playback, loop semantics,
|
||||
autoplay-via-initial-state, and a product-shaped adapter — `mode-override`
|
||||
is the narrower "deliver video-only despite mixed source" use case.
|
||||
@@ -26,9 +26,9 @@ is the narrower "deliver video-only despite mixed source" use case.
|
||||
## Status
|
||||
|
||||
Phase 1 implemented ([#1586](https://github.com/videojs/v10/issues/1586)):
|
||||
`createBackgroundLoopingVideoEngine`, `BackgroundLoopingVideoMediaElement`,
|
||||
`createBackgroundVideoEngine`, `BackgroundVideoMediaElement`,
|
||||
and the `pickMaxResolutionVideoTrack` primitive ship under
|
||||
`@videojs/spf/background-looping-video`. Phases 2-3 (decorator composition
|
||||
`@videojs/spf/background-video`. Phases 2-3 (decorator composition
|
||||
of audio and preload) and Phase 4 (Video.js component shell — out of SPF
|
||||
scope) stay coarser.
|
||||
|
||||
@@ -82,7 +82,7 @@ tree-shakes out the ABR code path."* — exactly the affordance Phase 1 wants.
|
||||
Independent adapter parallel to `SimpleHlsMediaElement`:
|
||||
|
||||
```ts
|
||||
const bgPlayer = new BackgroundLoopingVideoMediaElement({ picker: maxResolutionPicker });
|
||||
const bgPlayer = new BackgroundVideoMediaElement({ picker: maxResolutionPicker });
|
||||
bgPlayer.src = sourceUrl;
|
||||
bgPlayer.loop = true; // native HTMLMediaElement.loop
|
||||
bgPlayer.muted = true; // browser autoplay policy
|
||||
@@ -97,7 +97,7 @@ Video.js component shell) live above the SPF engine.
|
||||
|
||||
## Variant-decision signal source
|
||||
|
||||
**Adapter-upfront.** Selecting `BackgroundLoopingVideoMediaElement` *is*
|
||||
**Adapter-upfront.** Selecting `BackgroundVideoMediaElement` *is*
|
||||
the variant choice — no parser detection, no runtime config branch. Same
|
||||
resolution as [`video-only-mode-override`](./video-only-mode-override.md)
|
||||
and [`audio-only-mode-override`](./audio-only-mode-override.md): Case-2
|
||||
@@ -135,7 +135,7 @@ Phase 2 (decorations TBD): **[`audio-playback`](../features/audio-playback.md)**
|
||||
- **Sampling-strip alt-impl Path A vs B.** Likely Path B per [`README.md` § Implementation note](./README.md#implementation-note-customizing-behaviors-for-use-cases).
|
||||
- **GPU/thermal-aware quality caps boundary.** Engine-variant (compose a thermal-aware behavior) or adapter (cap the picker candidate set). Likely engine-variant given the product context.
|
||||
|
||||
Resolved Phase 1: ~~picker location~~ (`pickMaxResolutionVideoTrack` ships in [`media/primitives/select-tracks.ts`](../../../../packages/spf/src/media/primitives/select-tracks.ts) next to `pickFirstTrackId`); ~~adapter naming~~ (`BackgroundLoopingVideoMediaElement`; product-shell naming `<mux-background-video>` lives in the adapter layer).
|
||||
Resolved Phase 1: ~~picker location~~ (`pickMaxResolutionVideoTrack` ships in [`media/primitives/select-tracks.ts`](../../../../packages/spf/src/media/primitives/select-tracks.ts) next to `pickFirstTrackId`); ~~adapter naming~~ (`BackgroundVideoMediaElement`; product-shell naming `<mux-background-video>` lives in the adapter layer).
|
||||
|
||||
## See also
|
||||
|
||||
@@ -21,7 +21,7 @@ epic NEW-B "Video-only Composition" — delivery-mode choice) into a single
|
||||
composition with two variant-decision-source paths
|
||||
(see *Variant-decision signal source* below).
|
||||
|
||||
[`background-looping-video`](./README.md#index) *(forward-ref; not yet
|
||||
[`background-video`](./README.md#index) *(forward-ref; not yet
|
||||
documented)* is a related-but-distinct Mux product scenario that may
|
||||
compose this use case as part of a broader assembly (loop +
|
||||
autoplay-muted + GPU/thermal-aware caps). See
|
||||
@@ -47,7 +47,7 @@ for product context.
|
||||
same engine factory and adapter, distinguished only by which signal
|
||||
sources the variant decision.
|
||||
- **Mux relevance:** [GitHub #873 (mux-background-video)](https://github.com/videojs/v10/issues/873)
|
||||
is product-adjacent — distinct use case (`background-looping-video`) that
|
||||
is product-adjacent — distinct use case (`background-video`) that
|
||||
shares constituent features but addresses a different delivery scenario.
|
||||
|
||||
## Target delivery context
|
||||
@@ -125,7 +125,7 @@ variant-decision behavior is needed.
|
||||
be adapter-layer rather than engine-variant — boundary question.
|
||||
- **GPU/thermal-aware quality caps** — for low-attention consumer surfaces
|
||||
(ambient video, background tabs), cap video quality based on GPU/thermal
|
||||
signals. Likely shared with `background-looping-video`'s broader scenario.
|
||||
signals. Likely shared with `background-video`'s broader scenario.
|
||||
- **Buffer-target tuning** — depends on consumer surface. Background/ambient
|
||||
video may want shorter buffers (lower priority); foreground muted-autoplay
|
||||
may want normal buffers. Probably consumer-policy rather than engine
|
||||
@@ -152,7 +152,7 @@ Phase 1 baseline:
|
||||
Phase 1 verification covers this.
|
||||
- **[`buffer-management`](../features/buffer-management.md)** — used as-is in
|
||||
Phase 1; Phase 3 surfaces alternative defaults / candidate Path-B behaviors
|
||||
(loop-friendly buffer fetching is `background-looping-video`'s concern,
|
||||
(loop-friendly buffer fetching is `background-video`'s concern,
|
||||
not this use case's).
|
||||
|
||||
Phase 2 (when relevant):
|
||||
@@ -225,11 +225,11 @@ implementation pass landed the shared-factory pattern (see that doc's
|
||||
- **Adapter shape proliferation.** Each use-case composition that gets its
|
||||
own adapter multiplies the adapter surface. Shared concern with
|
||||
`audio-only-mode-override` and forthcoming use cases.
|
||||
- **Relationship with `background-looping-video`.** Distinct use case (Mux's
|
||||
- **Relationship with `background-video`.** Distinct use case (Mux's
|
||||
background-video product scenario); shared constituent features but
|
||||
different delivery scenario. Background-looping composes loop + autoplay-
|
||||
different delivery scenario. Background composes loop + autoplay-
|
||||
muted + GPU/thermal-aware caps + likely silent-video delivery; this use
|
||||
case is the narrower "deliver video-only" piece. Background-looping-video
|
||||
case is the narrower "deliver video-only" piece. Background-video
|
||||
may compose this use case's engine factory as one of its building blocks,
|
||||
or share constituents at the feature level — cross-link as peer use cases.
|
||||
- **Test coverage for mixed-source video-only delivery.** New test coverage
|
||||
@@ -265,8 +265,8 @@ implementation pass landed the shared-factory pattern (see that doc's
|
||||
muted-default behavior) or adapter level (adapter sets `muted = true` on
|
||||
the underlying media element). Likely adapter.
|
||||
- **GPU/thermal-aware quality caps boundary.** Likely shared with
|
||||
`background-looping-video`; may belong at the use-case level there rather
|
||||
than here. Worth scoping in the background-looping-video doc when it
|
||||
`background-video`; may belong at the use-case level there rather
|
||||
than here. Worth scoping in the background-video doc when it
|
||||
lands.
|
||||
- **Mux-billing integration.** Same shape as audio-only-mode-override —
|
||||
billing context likely surfaces at adapter / consumer level.
|
||||
@@ -294,11 +294,11 @@ implementation pass landed the shared-factory pattern (see that doc's
|
||||
inverse-axis sibling. Same shape, audio-side instead of video-side. The
|
||||
joint resolution of the "engine variant factory shape" open question
|
||||
applies to both.
|
||||
- **`[background-looping-video]`** *(forward-ref; not yet documented)* —
|
||||
- **`[background-video]`** *(forward-ref; not yet documented)* —
|
||||
peer use case. Mux's background-video product scenario: loop +
|
||||
autoplay-muted + GPU/thermal-aware caps + likely silent-video delivery.
|
||||
**Distinct from this use case** despite shared Mux consumer context and
|
||||
overlapping constituent features. Background-looping-video may compose
|
||||
overlapping constituent features. Background-video may compose
|
||||
this use case's engine factory as one of its building blocks, or compose
|
||||
shared constituents directly — resolution depends on whether
|
||||
"use-case-composing-use-case" emerges as a pattern. Cross-link as peer.
|
||||
|
||||
@@ -33,10 +33,10 @@
|
||||
"development": "./dist/dev/hls.js",
|
||||
"default": "./dist/default/hls.js"
|
||||
},
|
||||
"./background-looping-video": {
|
||||
"types": "./dist/dev/background-looping-video.d.ts",
|
||||
"development": "./dist/dev/background-looping-video.js",
|
||||
"default": "./dist/default/background-looping-video.js"
|
||||
"./background-video": {
|
||||
"types": "./dist/dev/background-video.d.ts",
|
||||
"development": "./dist/dev/background-video.js",
|
||||
"default": "./dist/default/background-video.js"
|
||||
}
|
||||
},
|
||||
"main": "dist/default/index.js",
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
export type { BackgroundLoopingVideoMediaAPI, BackgroundLoopingVideoMediaProps } from './adapter';
|
||||
export {
|
||||
BackgroundLoopingVideoMediaElement,
|
||||
BackgroundLoopingVideoMediaMixin,
|
||||
backgroundLoopingVideoMediaDefaultProps,
|
||||
} from './adapter';
|
||||
export type {
|
||||
BackgroundLoopingVideoEngineConfig,
|
||||
BackgroundLoopingVideoEngineContext,
|
||||
BackgroundLoopingVideoEngineSignals,
|
||||
BackgroundLoopingVideoEngineState,
|
||||
} from './engine';
|
||||
export { createBackgroundLoopingVideoEngine } from './engine';
|
||||
+29
-29
@@ -7,14 +7,14 @@ import {
|
||||
} from '../../../media/primitives/select-tracks';
|
||||
import type { VideoSelectionSet } from '../../../media/types';
|
||||
import {
|
||||
type BackgroundLoopingVideoEngineConfig,
|
||||
type BackgroundLoopingVideoEngineContext,
|
||||
type BackgroundLoopingVideoEngineSignals,
|
||||
type BackgroundLoopingVideoEngineState,
|
||||
createBackgroundLoopingVideoEngine,
|
||||
type BackgroundVideoEngineConfig,
|
||||
type BackgroundVideoEngineContext,
|
||||
type BackgroundVideoEngineSignals,
|
||||
type BackgroundVideoEngineState,
|
||||
createBackgroundVideoEngine,
|
||||
} from './engine';
|
||||
|
||||
export interface BackgroundLoopingVideoMediaProps {
|
||||
export interface BackgroundVideoMediaProps {
|
||||
src: string;
|
||||
preload: '' | 'none' | 'metadata' | 'auto';
|
||||
loop: boolean;
|
||||
@@ -23,7 +23,7 @@ export interface BackgroundLoopingVideoMediaProps {
|
||||
maxResolution: string | number | undefined;
|
||||
}
|
||||
|
||||
export const backgroundLoopingVideoMediaDefaultProps: BackgroundLoopingVideoMediaProps = {
|
||||
export const backgroundVideoMediaDefaultProps: BackgroundVideoMediaProps = {
|
||||
src: '',
|
||||
preload: 'auto',
|
||||
loop: true,
|
||||
@@ -32,8 +32,8 @@ export const backgroundLoopingVideoMediaDefaultProps: BackgroundLoopingVideoMedi
|
||||
maxResolution: undefined,
|
||||
};
|
||||
|
||||
export interface BackgroundLoopingVideoMediaAPI extends BackgroundLoopingVideoMediaProps {
|
||||
readonly engine: Composition<BackgroundLoopingVideoEngineState, BackgroundLoopingVideoEngineContext>;
|
||||
export interface BackgroundVideoMediaAPI extends BackgroundVideoMediaProps {
|
||||
readonly engine: Composition<BackgroundVideoEngineState, BackgroundVideoEngineContext>;
|
||||
attach(mediaElement: HTMLMediaElement): void;
|
||||
detach(): void;
|
||||
destroy(): void;
|
||||
@@ -41,7 +41,7 @@ export interface BackgroundLoopingVideoMediaAPI extends BackgroundLoopingVideoMe
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixin that adds the background-looping-video SPF playback engine to any
|
||||
* Mixin that adds the background-video SPF playback engine to any
|
||||
* base class.
|
||||
*
|
||||
* Implements the WHATWG HTML media element contract (`src`, `preload`,
|
||||
@@ -53,7 +53,7 @@ export interface BackgroundLoopingVideoMediaAPI extends BackgroundLoopingVideoMe
|
||||
* passthroughs, all defaulting to `true` — the use case is silent
|
||||
* autoplay-looping video, so muted + autoplay satisfy browser autoplay
|
||||
* policies and loop is the defining behavior;
|
||||
* - drives the underlying engine with the background-looping-video
|
||||
* - drives the underlying engine with the background-video
|
||||
* composition (single-rendition, video-only, autoplay-from-construction).
|
||||
*
|
||||
* A new engine is created on every src assignment — this fully tears down
|
||||
@@ -63,22 +63,22 @@ export interface BackgroundLoopingVideoMediaAPI extends BackgroundLoopingVideoMe
|
||||
* automatically.
|
||||
*
|
||||
* @example
|
||||
* class BackgroundLoopingVideoMedia extends BackgroundLoopingVideoMediaMixin(HTMLVideoElementHost) {}
|
||||
* class BackgroundVideoMedia extends BackgroundVideoMediaMixin(HTMLVideoElementHost) {}
|
||||
*
|
||||
* const media = new BackgroundLoopingVideoMedia();
|
||||
* const media = new BackgroundVideoMedia();
|
||||
* media.attach(document.querySelector('video'));
|
||||
* media.src = 'https://stream.mux.com/abc123.m3u8';
|
||||
* media.play();
|
||||
*/
|
||||
export function BackgroundLoopingVideoMediaMixin<Base extends Constructor<any>>(BaseClass: Base) {
|
||||
class BackgroundLoopingVideoMediaImpl extends BaseClass {
|
||||
#engine: Composition<BackgroundLoopingVideoEngineState, BackgroundLoopingVideoEngineContext>;
|
||||
#config: BackgroundLoopingVideoEngineConfig;
|
||||
#signals!: BackgroundLoopingVideoEngineSignals;
|
||||
#preload: '' | 'none' | 'metadata' | 'auto' = backgroundLoopingVideoMediaDefaultProps.preload;
|
||||
#loop: boolean = backgroundLoopingVideoMediaDefaultProps.loop;
|
||||
#muted: boolean = backgroundLoopingVideoMediaDefaultProps.muted;
|
||||
#autoplay: boolean = backgroundLoopingVideoMediaDefaultProps.autoplay;
|
||||
export function BackgroundVideoMediaMixin<Base extends Constructor<any>>(BaseClass: Base) {
|
||||
class BackgroundVideoMediaImpl extends BaseClass {
|
||||
#engine: Composition<BackgroundVideoEngineState, BackgroundVideoEngineContext>;
|
||||
#config: BackgroundVideoEngineConfig;
|
||||
#signals!: BackgroundVideoEngineSignals;
|
||||
#preload: '' | 'none' | 'metadata' | 'auto' = backgroundVideoMediaDefaultProps.preload;
|
||||
#loop: boolean = backgroundVideoMediaDefaultProps.loop;
|
||||
#muted: boolean = backgroundVideoMediaDefaultProps.muted;
|
||||
#autoplay: boolean = backgroundVideoMediaDefaultProps.autoplay;
|
||||
#maxResolution: string | number | undefined;
|
||||
|
||||
/** Pending loadstart listener from a deferred play() retry, if any. */
|
||||
@@ -94,7 +94,7 @@ export function BackgroundLoopingVideoMediaMixin<Base extends Constructor<any>>(
|
||||
this.#engine = this.#createEngine();
|
||||
}
|
||||
|
||||
get engine(): Composition<BackgroundLoopingVideoEngineState, BackgroundLoopingVideoEngineContext> {
|
||||
get engine(): Composition<BackgroundVideoEngineState, BackgroundVideoEngineContext> {
|
||||
return this.#engine;
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ export function BackgroundLoopingVideoMediaMixin<Base extends Constructor<any>>(
|
||||
async play(): Promise<void> {
|
||||
const mediaElement = this.#signals.context.mediaElement.get();
|
||||
if (!mediaElement) {
|
||||
return Promise.reject(new Error('BackgroundLoopingVideoMediaElement: no media element attached'));
|
||||
return Promise.reject(new Error('BackgroundVideoMediaElement: no media element attached'));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -243,14 +243,14 @@ export function BackgroundLoopingVideoMediaMixin<Base extends Constructor<any>>(
|
||||
// Private
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
#createEngine(): Composition<BackgroundLoopingVideoEngineState, BackgroundLoopingVideoEngineContext> {
|
||||
#createEngine(): Composition<BackgroundVideoEngineState, BackgroundVideoEngineContext> {
|
||||
const adapterPicker: TrackPicker = (presentation) => {
|
||||
const videoSet = presentation.selectionSets?.find((s) => s.type === 'video') as VideoSelectionSet | undefined;
|
||||
const tracks = videoSet?.switchingSets[0]?.tracks ?? [];
|
||||
return pickTrackUnderPixelArea(tracks, maxResolutionToPixelArea(this.#maxResolution))?.id;
|
||||
};
|
||||
|
||||
return createBackgroundLoopingVideoEngine({
|
||||
return createBackgroundVideoEngine({
|
||||
picker: adapterPicker,
|
||||
...this.#config,
|
||||
onSignalsReady: (signals) => {
|
||||
@@ -267,8 +267,8 @@ export function BackgroundLoopingVideoMediaMixin<Base extends Constructor<any>>(
|
||||
}
|
||||
}
|
||||
|
||||
return BackgroundLoopingVideoMediaImpl as unknown as MixinReturn<Base, BackgroundLoopingVideoMediaAPI>;
|
||||
return BackgroundVideoMediaImpl as unknown as MixinReturn<Base, BackgroundVideoMediaAPI>;
|
||||
}
|
||||
|
||||
/** Standalone SPF background-looping-video adapter with no base class. */
|
||||
export class BackgroundLoopingVideoMediaElement extends BackgroundLoopingVideoMediaMixin(class {}) {}
|
||||
/** Standalone SPF background-video adapter with no base class. */
|
||||
export class BackgroundVideoMediaElement extends BackgroundVideoMediaMixin(class {}) {}
|
||||
+21
-21
@@ -23,11 +23,11 @@ import { resolveVideoTrack } from '../../behaviors/resolve-track';
|
||||
import { type SelectVideoTrackConfig, selectVideoTrack } from '../../behaviors/select-tracks';
|
||||
|
||||
// ============================================================================
|
||||
// Background-looping-video engine state & context
|
||||
// Background-video engine state & context
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* State shape for the background-looping-video playback engine.
|
||||
* State shape for the background-video playback engine.
|
||||
*
|
||||
* Narrower than `SimpleHlsEngineState`: audio/text track slots are absent
|
||||
* because their selection/resolution behaviors are subtracted. `bandwidthState`
|
||||
@@ -35,7 +35,7 @@ import { type SelectVideoTrackConfig, selectVideoTrack } from '../../behaviors/s
|
||||
* samples into it (wasted work in this variant — a Phase 3 alt-impl will skip
|
||||
* sampling).
|
||||
*/
|
||||
export interface BackgroundLoopingVideoEngineState {
|
||||
export interface BackgroundVideoEngineState {
|
||||
/**
|
||||
* The presentation being played. A caller writes `{ url }`;
|
||||
* `resolvePresentation` parses the manifest and populates the rest.
|
||||
@@ -47,9 +47,9 @@ export interface BackgroundLoopingVideoEngineState {
|
||||
}
|
||||
|
||||
/**
|
||||
* Context shape for the background-looping-video engine.
|
||||
* Context shape for the background-video engine.
|
||||
*/
|
||||
export interface BackgroundLoopingVideoEngineContext {
|
||||
export interface BackgroundVideoEngineContext {
|
||||
mediaElement?: HTMLMediaElement | undefined;
|
||||
mediaSource?: MediaSource;
|
||||
videoBufferActor?: SourceBufferActor;
|
||||
@@ -62,28 +62,28 @@ export interface BackgroundLoopingVideoEngineContext {
|
||||
* state (reads) without touching `composition.state` / `composition.context`
|
||||
* directly.
|
||||
*/
|
||||
export type BackgroundLoopingVideoEngineSignals = {
|
||||
state: StateSignals<BackgroundLoopingVideoEngineState>;
|
||||
context: ContextSignals<BackgroundLoopingVideoEngineContext>;
|
||||
export type BackgroundVideoEngineSignals = {
|
||||
state: StateSignals<BackgroundVideoEngineState>;
|
||||
context: ContextSignals<BackgroundVideoEngineContext>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Configuration for the background-looping-video engine.
|
||||
* Configuration for the background-video engine.
|
||||
*
|
||||
* Each option is consumed by the appropriate behavior — the engine itself
|
||||
* has no config beyond what its behaviors read. Compared to
|
||||
* `SimpleHlsEngineConfig`, audio/text/ABR/bandwidth/quality knobs are
|
||||
* dropped: the variant subtracts the behaviors that read them.
|
||||
*/
|
||||
export interface BackgroundLoopingVideoEngineConfig
|
||||
extends ShareSignalsConfig<BackgroundLoopingVideoEngineState, BackgroundLoopingVideoEngineContext> {
|
||||
export interface BackgroundVideoEngineConfig
|
||||
extends ShareSignalsConfig<BackgroundVideoEngineState, BackgroundVideoEngineContext> {
|
||||
/**
|
||||
* Track picker handed to `selectVideoTrack`. Default:
|
||||
* `pickHighestResolutionVideoTrack` — picks the highest-resolution variant on
|
||||
* presentation resolve and pins it for the session. Override for
|
||||
* mobile-aware or content-aware caps.
|
||||
*
|
||||
* Adapters (e.g. `BackgroundLoopingVideoMediaElement`) install their own
|
||||
* Adapters (e.g. `BackgroundVideoMediaElement`) install their own
|
||||
* picker; this default applies when the engine is constructed directly.
|
||||
*/
|
||||
picker?: TrackPicker<SelectVideoTrackConfig>;
|
||||
@@ -95,13 +95,13 @@ export interface BackgroundLoopingVideoEngineConfig
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Background-looping-video playback engine
|
||||
// Background-video playback engine
|
||||
// ============================================================================
|
||||
|
||||
const shareSignals = makeShareSignals<BackgroundLoopingVideoEngineState, BackgroundLoopingVideoEngineContext>();
|
||||
const shareSignals = makeShareSignals<BackgroundVideoEngineState, BackgroundVideoEngineContext>();
|
||||
|
||||
/**
|
||||
* Create a background-looping-video playback engine.
|
||||
* Create a background-video playback engine.
|
||||
*
|
||||
* Subtractive composition over the HLS engine baseline:
|
||||
* audio-side, text-side, ABR-driven, preload-monitoring, and play/seek
|
||||
@@ -113,12 +113,12 @@ const shareSignals = makeShareSignals<BackgroundLoopingVideoEngineState, Backgro
|
||||
* surfaces that should start loading the moment a src is set.
|
||||
*
|
||||
* Native `loop` / `muted` / `autoplay` are adapter concerns and live on
|
||||
* `BackgroundLoopingVideoMediaElement` rather than the engine.
|
||||
* `BackgroundVideoMediaElement` rather than the engine.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* let signals: BackgroundLoopingVideoEngineSignals;
|
||||
* const engine = createBackgroundLoopingVideoEngine({
|
||||
* let signals: BackgroundVideoEngineSignals;
|
||||
* const engine = createBackgroundVideoEngine({
|
||||
* onSignalsReady: (refs) => {
|
||||
* signals = refs;
|
||||
* },
|
||||
@@ -130,9 +130,9 @@ const shareSignals = makeShareSignals<BackgroundLoopingVideoEngineState, Backgro
|
||||
* await engine.destroy();
|
||||
* ```
|
||||
*/
|
||||
export function createBackgroundLoopingVideoEngine(
|
||||
config: BackgroundLoopingVideoEngineConfig = {}
|
||||
): Composition<BackgroundLoopingVideoEngineState, BackgroundLoopingVideoEngineContext> {
|
||||
export function createBackgroundVideoEngine(
|
||||
config: BackgroundVideoEngineConfig = {}
|
||||
): Composition<BackgroundVideoEngineState, BackgroundVideoEngineContext> {
|
||||
const finalConfig = {
|
||||
...config,
|
||||
picker: config.picker ?? pickHighestResolutionVideoTrack,
|
||||
@@ -0,0 +1,13 @@
|
||||
export type { BackgroundVideoMediaAPI, BackgroundVideoMediaProps } from './adapter';
|
||||
export {
|
||||
BackgroundVideoMediaElement,
|
||||
BackgroundVideoMediaMixin,
|
||||
backgroundVideoMediaDefaultProps,
|
||||
} from './adapter';
|
||||
export type {
|
||||
BackgroundVideoEngineConfig,
|
||||
BackgroundVideoEngineContext,
|
||||
BackgroundVideoEngineSignals,
|
||||
BackgroundVideoEngineState,
|
||||
} from './engine';
|
||||
export { createBackgroundVideoEngine } from './engine';
|
||||
+30
-30
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* BackgroundLoopingVideoMediaElement adapter tests.
|
||||
* BackgroundVideoMediaElement adapter tests.
|
||||
*
|
||||
* Covers the HTMLMediaElement-compatible contract for src, preload, loop,
|
||||
* muted, and play(). Adapter-shape parallels SimpleHlsMediaElement; the
|
||||
@@ -8,9 +8,9 @@
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { MaybeResolvedPresentation } from '../../../../media/types';
|
||||
import { BackgroundLoopingVideoMediaElement } from '../adapter';
|
||||
import { BackgroundVideoMediaElement } from '../adapter';
|
||||
|
||||
describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
describe('BackgroundVideoMediaElement', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
@@ -24,24 +24,24 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
|
||||
describe('src', () => {
|
||||
it('returns empty string before any src is set', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
expect(media.src).toBe('');
|
||||
});
|
||||
|
||||
it('reflects the set value synchronously', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
media.src = 'https://example.com/v.m3u8';
|
||||
expect(media.src).toBe('https://example.com/v.m3u8');
|
||||
});
|
||||
|
||||
it('synchronously updates engine presentation state when src is set', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
media.src = 'https://example.com/v.m3u8';
|
||||
expect(media.engine.state.presentation.get()?.url).toBe('https://example.com/v.m3u8');
|
||||
});
|
||||
|
||||
it('clears engine presentation state when src is set to empty string', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
media.src = 'https://example.com/v.m3u8';
|
||||
media.src = '';
|
||||
expect(media.engine.state.presentation.get()?.url).toBeFalsy();
|
||||
@@ -50,12 +50,12 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
|
||||
describe('attach / detach', () => {
|
||||
it('exposes the engine immediately (created at construction)', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
expect(media.engine).not.toBeNull();
|
||||
});
|
||||
|
||||
it('reuses the same engine instance across attach calls', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
const engineBefore = media.engine;
|
||||
media.attach(document.createElement('video'));
|
||||
media.attach(document.createElement('video'));
|
||||
@@ -63,7 +63,7 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
});
|
||||
|
||||
it('re-attaches the media element to the new engine when src changes', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
const el = document.createElement('video');
|
||||
media.attach(el);
|
||||
media.src = 'https://example.com/v1.m3u8';
|
||||
@@ -71,21 +71,21 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
});
|
||||
|
||||
it('sets mediaElement in context when attached', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
const el = document.createElement('video');
|
||||
media.attach(el);
|
||||
expect(media.engine.context.mediaElement.get()).toBe(el);
|
||||
});
|
||||
|
||||
it('clears mediaElement in context when detached', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
media.attach(document.createElement('video'));
|
||||
media.detach();
|
||||
expect(media.engine.context.mediaElement.get()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('detach does not destroy the engine', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
media.attach(document.createElement('video'));
|
||||
const spy = vi.spyOn(media.engine, 'destroy');
|
||||
media.detach();
|
||||
@@ -95,17 +95,17 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
|
||||
describe('loop / muted defaults', () => {
|
||||
it('defaults loop to true', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
expect(media.loop).toBe(true);
|
||||
});
|
||||
|
||||
it('defaults muted to true', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
expect(media.muted).toBe(true);
|
||||
});
|
||||
|
||||
it('applies loop / muted defaults to the media element on attach', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
const el = document.createElement('video');
|
||||
// start the element in the opposite state so we can confirm attach overrides it
|
||||
el.loop = false;
|
||||
@@ -117,7 +117,7 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
|
||||
// attach modifies native props; changing src doesn't
|
||||
it('preserves loop / muted to the preserved element on src change', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
const el = document.createElement('video');
|
||||
media.attach(el);
|
||||
el.loop = false;
|
||||
@@ -131,7 +131,7 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
// pins loop=true / muted=true for the autoplay-looping use case). These
|
||||
// assert functional setters — unskip when the setters are implemented.
|
||||
it.skip('mirrors loop changes onto the attached element', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
const el = document.createElement('video');
|
||||
media.attach(el);
|
||||
media.loop = false;
|
||||
@@ -140,7 +140,7 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
});
|
||||
|
||||
it.skip('mirrors muted changes onto the attached element', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
const el = document.createElement('video');
|
||||
media.attach(el);
|
||||
media.muted = false;
|
||||
@@ -149,7 +149,7 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
});
|
||||
|
||||
it.skip('stores loop / muted updates made before attach and applies them on attach', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
media.loop = false;
|
||||
media.muted = false;
|
||||
const el = document.createElement('video');
|
||||
@@ -161,7 +161,7 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
|
||||
describe('play()', () => {
|
||||
it('returns a Promise', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
media.attach(document.createElement('video'));
|
||||
const result = media.play();
|
||||
expect(result).toBeInstanceOf(Promise);
|
||||
@@ -169,14 +169,14 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
});
|
||||
|
||||
it('rejects when no media element is attached', async () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
await expect(media.play()).rejects.toThrow('no media element attached');
|
||||
});
|
||||
});
|
||||
|
||||
describe('destroy()', () => {
|
||||
it('destroys the underlying engine', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
const spy = vi.spyOn(media.engine, 'destroy');
|
||||
media.destroy();
|
||||
expect(spy).toHaveBeenCalledOnce();
|
||||
@@ -185,25 +185,25 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
|
||||
describe('maxResolution', () => {
|
||||
it('defaults to undefined', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
expect(media.maxResolution).toBeUndefined();
|
||||
});
|
||||
|
||||
it('reflects the value passed via constructor config', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement({
|
||||
const media = new BackgroundVideoMediaElement({
|
||||
config: { maxResolution: '720p' },
|
||||
});
|
||||
expect(media.maxResolution).toBe('720p');
|
||||
});
|
||||
|
||||
it('reflects setter writes', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
media.maxResolution = '1080p';
|
||||
expect(media.maxResolution).toBe('1080p');
|
||||
});
|
||||
|
||||
it('does not rebuild the engine on setter writes — closure picker reads the field live', () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
const firstEngine = media.engine;
|
||||
media.maxResolution = '720p';
|
||||
expect(media.engine).toBe(firstEngine);
|
||||
@@ -240,7 +240,7 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
it('the closure picker reads maxResolution at pick time', async () => {
|
||||
// Construct without a cap. If the closure captured at creation, the
|
||||
// pick would use `undefined` (→ 1440p). It uses the current field instead.
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
media.maxResolution = '720p';
|
||||
media.engine.state.presentation.set(presentationWithFourTracks());
|
||||
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
||||
@@ -249,7 +249,7 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
});
|
||||
|
||||
it('setter writes are reflected on the next presentation cycle', async () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement();
|
||||
const media = new BackgroundVideoMediaElement();
|
||||
media.engine.state.presentation.set(presentationWithFourTracks());
|
||||
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
||||
expect(media.engine.state.selectedVideoTrackId.get()).toBe('1440p');
|
||||
@@ -268,7 +268,7 @@ describe('BackgroundLoopingVideoMediaElement', () => {
|
||||
});
|
||||
|
||||
it('honors a user-supplied picker, overriding the closure default', async () => {
|
||||
const media = new BackgroundLoopingVideoMediaElement({
|
||||
const media = new BackgroundVideoMediaElement({
|
||||
config: { maxResolution: '720p', picker: () => '1440p' },
|
||||
});
|
||||
media.engine.state.presentation.set(presentationWithFourTracks());
|
||||
+9
-9
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* createBackgroundLoopingVideoEngine tests.
|
||||
* createBackgroundVideoEngine tests.
|
||||
*
|
||||
* The variant subtracts audio, text, ABR, and preload-monitoring behaviors
|
||||
* from the simple HLS engine, then seeds `loadActivated: true` so the
|
||||
@@ -10,13 +10,13 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { snapshot } from '../../../../core/signals/primitives';
|
||||
import type { MaybeResolvedPresentation } from '../../../../media/types';
|
||||
import { createBackgroundLoopingVideoEngine } from '../engine';
|
||||
import { createBackgroundVideoEngine } from '../engine';
|
||||
|
||||
vi.mock('../../../../media/dom/mse/append-segment', () => ({
|
||||
appendSegment: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
describe('createBackgroundLoopingVideoEngine', () => {
|
||||
describe('createBackgroundVideoEngine', () => {
|
||||
let originalFetch: typeof globalThis.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -28,7 +28,7 @@ describe('createBackgroundLoopingVideoEngine', () => {
|
||||
});
|
||||
|
||||
it('creates an engine with state, context, and destroy()', () => {
|
||||
const engine = createBackgroundLoopingVideoEngine();
|
||||
const engine = createBackgroundVideoEngine();
|
||||
|
||||
expect(engine.state).toBeDefined();
|
||||
expect(engine.context).toBeDefined();
|
||||
@@ -38,13 +38,13 @@ describe('createBackgroundLoopingVideoEngine', () => {
|
||||
});
|
||||
|
||||
it('seeds loadActivated: true so preload gates pass from frame 0', () => {
|
||||
const engine = createBackgroundLoopingVideoEngine();
|
||||
const engine = createBackgroundVideoEngine();
|
||||
expect(engine.state.loadActivated.get()).toBe(true);
|
||||
engine.destroy();
|
||||
});
|
||||
|
||||
it('omits subtracted state slots — no audio/text/userVideoTrackSelection signals', () => {
|
||||
const engine = createBackgroundLoopingVideoEngine();
|
||||
const engine = createBackgroundVideoEngine();
|
||||
const state = snapshot(engine.state) as Record<string, unknown>;
|
||||
|
||||
// selectedAudioTrackId is declared by calculatePresentationDuration so
|
||||
@@ -61,7 +61,7 @@ describe('createBackgroundLoopingVideoEngine', () => {
|
||||
});
|
||||
|
||||
it('omits subtracted context slots — no audio segment loader / text actors', () => {
|
||||
const engine = createBackgroundLoopingVideoEngine();
|
||||
const engine = createBackgroundVideoEngine();
|
||||
const context = snapshot(engine.context) as Record<string, unknown>;
|
||||
|
||||
// `audioBufferActor` IS declared by `endOfStream` (cross-type EOS
|
||||
@@ -79,7 +79,7 @@ describe('createBackgroundLoopingVideoEngine', () => {
|
||||
});
|
||||
|
||||
it('defaults the picker to pickHighestResolutionVideoTrack', async () => {
|
||||
const engine = createBackgroundLoopingVideoEngine();
|
||||
const engine = createBackgroundVideoEngine();
|
||||
|
||||
const presentation: MaybeResolvedPresentation = {
|
||||
id: 'p',
|
||||
@@ -136,7 +136,7 @@ describe('createBackgroundLoopingVideoEngine', () => {
|
||||
});
|
||||
|
||||
it('honors a custom picker override from config', async () => {
|
||||
const engine = createBackgroundLoopingVideoEngine({
|
||||
const engine = createBackgroundVideoEngine({
|
||||
picker: () => 'forced-pick',
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ const createConfig = (mode: PackageBuildMode): UserConfig => ({
|
||||
index: 'src/index.ts',
|
||||
dom: 'src/dom.ts',
|
||||
hls: 'src/playback/engines/hls/index.ts',
|
||||
'background-looping-video': 'src/playback/engines/background-looping-video/index.ts',
|
||||
'background-video': 'src/playback/engines/background-video/index.ts',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user