Files
iptvnator/docs/superpowers/specs/2026-07-16-videojs-shared-controls-design.md
T
4grayandLars Emig 4e572c60ca feat(videojs): add feature-flagged shared controls (#1195)
* feat(videojs): add feature-flagged shared controls

Rebuild the Video.js shared-controls integration on the current player lifecycle with Tech rebinds, source-scoped tracks, reset ordering, volume preservation, diagnostics gating, and default-off compatibility.

Credits and supersedes the stacked implementation proposed in #1153.

Co-authored-by: Lars Emig <lars.emig@pickware.de>

* fix(videojs): harden MPEG-TS reset lifecycle

---------

Co-authored-by: Lars Emig <lars.emig@pickware.de>
2026-07-16 23:08:18 +02:00

175 lines
6.5 KiB
Markdown

# Video.js Shared Controls Replacement Design
## Context
PR #1153 proposed wiring Video.js into the shared `app-player-controls`
layer. Its Video.js-specific ideas remain useful, but the stacked PR also
contains superseded embedded-MPV and HTML5 work and cannot be merged directly.
The replacement is built on the current `master`, after:
- #1193 established frame-copy embedded MPV with shared controls.
- #1194 established the current web-player controls contract, authoritative
live/VOD metadata, diagnostic interaction gating, and source-owned cleanup.
The shared-controls feature flag remains disabled by default. Flag-off behavior
must stay unchanged.
## Goals
- Render the shared controls over Video.js when the feature flag is enabled.
- Preserve the native Video.js chrome and legacy series navigation when it is
disabled.
- Expose Video.js audio and subtitle tracks through the common adapter.
- Rebind all DOM-backed state when Video.js replaces its Tech `<video>`.
- Keep diagnostics usable by disabling player interaction and exiting only the
fullscreen session owned by the Video.js shell.
- Reduce `vjs-player.component.ts` below the 400-line hard limit.
## Non-goals
- Enable shared web controls by default.
- Add Video.js quality selection to the generic controls contract.
- Add generic aspect-ratio or PiP commands.
- Generalize the HTML5 text-track helper before a second implementation proves
the abstractions are identical.
## Key lifecycle decision
Video.js 8.23.4 replaces the Tech `<video>` after `player.reset()`. A one-time
adapter attachment is therefore invalid.
The replacement must treat the Tech video as a session-owned resource:
1. Acquire it from `player.tech({ IWillNotUseThisInPlugins: true }).el()`.
2. Detach listeners and the adapter from the previous element.
3. Attach them to the current element.
4. Repeat after every `playerreset`.
5. Destroy the bridge/session before `player.dispose()`.
Raw MPEG-TS startup must wait for `playerreset`, but Video.js does not attach a
request token to that event and can defer `reset()` behind a pending
`play()`. The component therefore pauses first, coalesces reset-required source
changes, and calls `reset()` only after `player.paused()` is true. Every
`playerreset` rebinds the current Tech and applies the latest desired source,
so a late reset cannot restore a superseded source.
## Component boundaries
### `vjs-player.types.ts`
Owns the focused Video.js API types used by the component and collaborators.
### `vjs-video-element-session.ts`
Owns native Tech video listeners used in both flag states:
- `loadeddata` / `playing` clear playback diagnostics.
- `ended` emits the playback-ended output.
### `vjs-player-reset-coordinator.ts`
Owns pause-before-reset ordering, coalescing, actual-volume snapshots, reset
volume-change suppression, and whether the desired source is already applied
to the current Tech. This also prevents a pre-ready `playerreset` from starting
raw MPEG-TS twice.
- Rebind detaches the old video before attaching the new one.
### `vjs-audio-tracks.ts`
Owns:
- legacy Video.js audio-menu logging/setup;
- stable per-source IDs for shared controls;
- valid selection and exact track-list listener cleanup.
Invalid or stale IDs are no-ops.
### `vjs-text-tracks.ts`
Uses `player.textTracks()` because VHS remote subtitles are represented there.
It:
- exposes only `captions` and `subtitles`;
- assigns stable per-source IDs with a `WeakMap`;
- treats `-1` as explicit off;
- keeps explicit off through preference and track-list events;
- suppresses the selected default track while global captions are disabled;
- disables non-selected tracks so VHS does not keep multiple tracks active.
### `vjs-mpegts-session.ts`
Owns raw MPEG-TS create/attach/load/play, VOD duration normalization,
diagnostics, listener cleanup, and idempotent teardown.
### `vjs-player-controls.bridge.ts`
Owns the component-scoped `WebVideoControlsAdapter` binding:
- attaches to the current Tech video;
- rebinds only when the Tech element changes;
- delegates audio/subtitle access to the track collaborators;
- uses `player.duration()` as the corrected duration source;
- clears source-specific IDs before source replacement;
- refreshes live/VOD and caption inputs;
- detaches exactly once on destroy.
## Interaction ownership
When shared controls are enabled:
- the template does not expose native `controls`;
- Video.js receives `controls: false`;
- Video.js `userActions.click`, `doubleClick`, and `hotkeys` are disabled;
- Video.js spatial navigation is disabled;
- the stable `.vjs-player-shell` is the shared interaction/fullscreen surface;
- `showControls` and `shortcutsEnabled` follow `interactionEnabled`;
- a diagnostic exits fullscreen only when that shell owns
`document.fullscreenElement`.
When the flag is disabled, existing Video.js options, chrome, plugins, and
legacy series navigation remain unchanged.
## Source transitions
### Normal Video.js source
1. Clear source-specific bridge/track state.
2. Destroy any raw MPEG-TS session.
3. Call `player.src(newSource)`.
4. Activate the new bridge source and refresh classification.
### Raw MPEG-TS source or empty source
1. Clear source-specific bridge/track state.
2. Destroy the current raw MPEG-TS session.
3. Record the latest desired source and request one coalesced reset.
4. Snapshot the actual Video.js volume and pause active playback.
5. Call `player.reset()` only after `player.paused()` is true.
6. On any `playerreset`, suppress/reset Video.js's temporary volume=1 change,
restore the snapshot, and rebind the current Tech video.
7. Apply only the latest desired source and start mpegts.js when applicable.
If authoritative `isLive` metadata changes for an active raw MPEG-TS URL, the
same reset flow restarts mpegts.js with the corrected live/VOD mode and duration
listener policy.
## Testing strategy
- Focused unit suites for video-session, audio tracks, text tracks, MPEG-TS,
and the controls bridge.
- Component integration tests for both flag states, fullscreen/diagnostic
gating, reset ordering, and output forwarding.
- Host tests that pass `interactionEnabled` and `showCaptions` to both
Video.js template branches.
- Existing `ui-playback` tests/lint, workspace typecheck, coverage, full lint,
Web E2E, and Electron E2E before merge.
## Attribution
The replacement preserves the useful Video.js-specific direction from Lars
Emig's #1153, especially the audio-track extraction and default-off dual-chrome
model, while rebuilding it against the current lifecycle and controls
architecture.