Files
v10/internal/design/spf/use-cases/background-looping-video.md
T

11 KiB

status, date, definition
status date definition
draft 2026-05-22 technical

Background-looping video

Engine variant for silent, autoplay-looping video on ambient/decorative surfaces (hero backgrounds, GIF-replacement loops, editorial previews). The Case-2 Player feature per ../features/clusters.md § Feature classification axes; ground-truth source the Mux mux-background-video product adapted to Video.js 10. Phase 1 is the SPF foundation for <mux-background-video> under parent epic #873; the composition demonstrates SPF composability by removing behaviors from the standard playback engine rather than adding new logic.

Distinct from video-only-mode-override: both subtract audio from mixed-source manifests, but background-looping- 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.

Status

Phase 1 implemented (#1586): createBackgroundLoopingVideoEngine, BackgroundLoopingVideoMediaElement, and the pickMaxResolutionVideoTrack primitive ship under @videojs/spf/background-looping-video. Phases 2-3 (decorator composition of audio and preload) and Phase 4 (Video.js component shell — out of SPF scope) stay coarser.

Phases

Adopts the parent-epic #873 phase structure — phases map to discrete epic deliverables.

Phase What
1 — Composition + adapter (#1586) Subtractive composition removing audio, text, ABR, preload-monitoring, and play/seek-monitoring behaviors; adds selectVideoTrack with a max-resolution picker; seeds loadActivated: true; ships independent adapter parallel to SimpleHlsMediaElement. HLS multivariant source; native mediaElement.loop = true.
2 — withAudio() decoration Composes audio-side behaviors back in for surfaces needing audio (user-initiated unmute, audible ambient). Decorator shape TBD.
3 — withPreload() + optimizations Composes preload-state monitoring back in for lazy/viewport-gated tiles. Co-scoped with loop-around forward-buffer fetching, GPU/thermal-aware quality caps, and a sampling-strip alt-impl of loadVideoSegments.
4 — Video.js component Full <mux-background-video> integration. Out of SPF scope — adapter / consumer territory.

Composition specifics

Phase 1 uses three mechanisms — subtract, add, and alternative default configuration. Alternative-implementation buckets surface in Phase 3.

Subtracted

From createSimpleHlsEngine:

  • syncPreload, trackLoadTriggers — no preload-state monitoring or DOM play/seeking activation; replaced by loadActivated: true initial state.
  • selectAudioTrack, selectTextTrack, resolveAudioTrack, resolveTextTrack, setupAudioBufferActors, loadAudioSegments — no audio side.
  • syncTextTracks, setupTextTrackActors, loadTextTrackSegments — no text-track machinery.
  • switchVideoQuality — no ABR; commits to a single rendition for the session.

Added

selectVideoTrack (from select-tracks.ts) with a config-provided picker, defaulting to pickMaxResolutionVideoTrack. Per switchVideoQuality's docstring: "Composing selectVideoTrack alone tree-shakes out the ABR code path." — exactly the affordance Phase 1 wants.

Alternative default configurations

  • initialState.loadActivated: true (Phase 1) — seeds the composition into the post-preload-gate state from frame 0. Combined with the syncPreload / trackLoadTriggers subtractions, every downstream behavior that would otherwise gate on !isBlockingPreload(preload) || loadActivated sees loadActivated truthy from the start.
  • picker (Phase 1) — defaults to pickMaxResolutionVideoTrack; overridable for mobile/content-aware caps.
  • Back-buffer tuning (Phase 3 candidate) — larger back-buffer for gapless wrap-around (per buffer-management's BackBufferConfig.keepSegments).
  • GPU/thermal-aware quality caps (Phase 3 candidate) — shared concern with video-only-mode-override.

Alternative implementations (Phase 3 candidates)

  • Sampling-strip loadVideoSegments — the standard loader samples bandwidth into state.bandwidthState to feed ABR. With switchVideoQuality subtracted, no consumer reads it; sampling is harmless but wasted work. Per README.md § Implementation note, likely Path B (the sampling assumption is structurally tied to ABR).
  • Loop-around forward-buffer fetching — pre-fetch the wrap-around for gapless restart. See buffer-management "What's not implemented".

Customer-policy surface

Independent adapter parallel to SimpleHlsMediaElement:

const bgPlayer = new BackgroundLoopingVideoMediaElement({ picker: maxResolutionPicker });
bgPlayer.src = sourceUrl;
bgPlayer.loop = true;        // native HTMLMediaElement.loop
bgPlayer.muted = true;       // browser autoplay policy
bgPlayer.play();

Engine config: picker (TrackPicker, default max-resolution); later phases add withAudio() / withPreload() decorator hooks. Native loop / muted live on the underlying media element. Adapter-layer concerns (autoplay-muted defaults, loop policy refinements, GPU/thermal caps, the Video.js component shell) live above the SPF engine.

Variant-decision signal source

Adapter-upfront. Selecting BackgroundLoopingVideoMediaElement is the variant choice — no parser detection, no runtime config branch. Same resolution as video-only-mode-override and audio-only-mode-override: Case-2 use cases resolve via adapter choice.

Constituent features

Phase 1 baseline:

  • video-only-composition — used at the composition-mechanism level; same audio-side subtraction pattern as the Case-1 feature, driven by adapter choice instead of source-shape detection. Plus further subtractions (text, ABR, preload).
  • engine-adapter-integration — variant adapter parallels SimpleHlsMediaElement via the same SimpleHlsMediaMixin / shareSignals pattern.
  • mse-mms-pipeline — used as-is. Firefox mozHasAudio=false verification under subtractive-audio composition is joint Phase 1 scope with video-only-mode-override and the Case-1 video-only-composition feature.
  • buffer-management — as-is in Phase 1; Phase 3 surfaces back-buffer tuning and loop-around forward-buffer fetching (the "loop-around buffer fetching" candidate in that feature's What's not implemented directly targets this use case).
  • preload-modes — alternative initial state (loadActivated: true) plus subtraction of syncPreload + trackLoadTriggers. Semantic contract preserved; the variant just seeds the gate-passable state from composition time.

Subtracted (cross-link discipline):

  • video-abr — single rendition for the session.
  • multi-language-audio — audio fully subtracted.
  • subtitles — text fully subtracted; may resurface if a withCaptions()-style extension is scoped.

Phase 2 (decorations TBD): audio-playback, audio-abr.

Likely cross-cutting impact

  • Shared engine factory. Three use cases now want subtractive-audio composition (this, video-only-mode-override, Case-1 video-only-composition). Lean: shared factory at the subtractive-audio level, with this use case layering further subtractions (text, ABR, preload) and an initial-state override on top.
  • Firefox mozHasAudio verification. Joint scope with the two sibling cases — same mixed-source-with-audio-subtracted permutation.
  • Adapter proliferation. N+1 adapter parallel to SimpleHlsMediaElement; three adapters share the SimpleHlsMediaMixin / shareSignals pattern — cost is configuration surface, not architecture.
  • loadActivated: true initial-state pattern. Pioneered here. If a second use case wants the same shape, consider a shared withAutoLoad()-style helper or document treatment in preload-modes.

Open questions

  • Phase 2/3 decorator pattern shape. Decorator on the engine factory? Composable-feature abstraction? Engine-config flags re-including subtracted behaviors? Resolves when Phase 2/3 are scoped.
  • Shared engine factory. Joint with video-only-mode-override and Case-1 video-only-composition. Lean: shared, with this case composing further subtractions.
  • Sampling-strip alt-impl Path A vs B. Likely Path B per README.md § Implementation note.
  • 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 next to pickFirstTrackId); adapter naming (BackgroundLoopingVideoMediaElement; product-shell naming <mux-background-video> lives in the adapter layer).

See also