Files
iptvnator/docs/superpowers/specs/2026-07-17-web-player-shared-controls-setting-design.md
T
4gray beb62db314 feat(settings): add shared web player controls toggle (#1198)
* docs(playback): design shared controls setting

* docs(playback): plan shared controls setting

* feat(settings): persist shared web controls preference

* test(settings): harden shared controls normalization coverage

* feat(settings): expose shared web controls toggle

* fix(settings): label shared controls toggle

* feat(playback): resolve shared controls from settings

* test(playback): cover shared controls setting

* docs(playback): document shared controls preference

* fix(playback): await settings before host creation

* fix(settings): normalize shared controls updates
2026-07-17 13:38:06 +02:00

8.4 KiB

Web-Player Shared Controls Setting Design

Context

The shared app-player-controls presentation is already integrated with four playback paths:

  • Embedded MPV frame-copy uses it unconditionally because its video is rendered into a DOM canvas.
  • HTML5, Video.js, and ArtPlayer can use it behind the default-off WEB_PLAYER_SHARED_CONTROLS rollout token.

The web-player token currently resolves from the compile-time WEB_PLAYER_SHARED_CONTROLS_ENABLED = false constant. Enabling the integrations therefore requires a code change, rebuild, and application restart. The integrations are ready for opt-in testing, so the rollout choice should be a persisted user setting instead.

The Settings route does not keep an active player mounted. The setting can therefore be applied when the next playback session is created; changing an already-running engine or preserving an active playback position is not required.

Goals

  • Add an experimental Playback checkbox for opting HTML5, Video.js, and ArtPlayer into the shared controls.
  • Persist the choice in the canonical SettingsStore.
  • Apply the saved choice to the next web-player session without restarting or reloading the application.
  • Keep the controls mode immutable for the lifetime of a player session.
  • Preserve default-off behavior and compatibility with existing stored settings.
  • Keep Embedded MPV and external-player behavior unchanged.

Non-goals

  • Make shared web controls the default.
  • Change an active player between legacy and shared controls.
  • Add separate preferences for HTML5, Video.js, and ArtPlayer.
  • Make Embedded MPV native-view use a DOM overlay.
  • Allow frame-copy Embedded MPV to fall back to the legacy controls dock.
  • Control the native UI of external MPV or VLC processes.

Embedded MPV Semantics

The new checkbox is intentionally web-player-specific.

Embedded MPV has two rendering paths:

  • Frame-copy: video is uploaded to a renderer canvas. It always uses app-player-controls; a second controls toggle would only duplicate an already-required part of this engine.
  • Native-view: video is hosted in a platform-native surface. DOM controls cannot reliably stack above that surface, so the compositor-safe legacy dock remains required.

The existing embeddedMpvFrameCopy setting continues to select the rendering engine and continues to require an application restart. It is independent from the new web-player controls preference.

External MPV and VLC processes continue to own their own controls UI.

Considered Approaches

1. Conditional global web-player preference

Selected.

One setting controls HTML5, Video.js, and ArtPlayer together. Its checkbox is shown only while one of those players is selected. This matches the existing single rollout token, keeps the Settings UI relevant to the selected player, and avoids suggesting that the option changes Embedded MPV or external players.

2. Always-visible "shared controls where available" preference

Rejected because it would be a no-op for frame-copy Embedded MPV, unsupported for native-view, and outside the app for external players. The resulting conditional semantics would be harder to explain than a web-player-specific label.

3. Separate preference for every playback engine

Rejected because the three web integrations deliberately share one rollout contract. Per-engine preferences would add settings and test combinations without a current product requirement.

Settings Contract

Add an optional boolean to the shared settings interface:

webPlayerSharedControls?: boolean;

The canonical default is false. Loading an older settings object without the field must normalize to false, and saving settings must write the complete resolved value back to storage.

The settings form adds a webPlayerSharedControls control with a false default. Form hydration, serialization, reset behavior, backup/export, and the existing Save workflow continue to use the normal complete Settings object. This is renderer-only state; Electron main-process behavior and preload IPC do not need a new command.

Runtime Data Flow

The existing WEB_PLAYER_SHARED_CONTROLS injection token remains the immutable, test-overridable session seam used by the three engine components. The compile-time constant remains only as the default-off fallback for direct component use and focused tests.

Normal runtime resolution moves to WebPlayerViewComponent:

  1. SettingsStore loads webPlayerSharedControls, defaulting to false.
  2. The Settings page updates and persists it through the existing Save action.
  3. Leaving Settings destroys that route; no player is active during the change.
  4. The next WebPlayerViewComponent resolves a component-scoped boolean snapshot from SettingsStore.
  5. HTML5, Video.js, or ArtPlayer receives that snapshot through WEB_PLAYER_SHARED_CONTROLS and constructs exactly one controls system.
  6. The snapshot does not change until that player host is destroyed and a new playback session is created.

This preserves the construction-time requirements of Video.js and ArtPlayer: vendor controls, hotkeys, gestures, plugins, source bridges, and the shared adapter are configured atomically. A reactive boolean must not flip only the template while leaving the engine configured for the other mode.

Embedded MPV does not consume the setting. Its component continues to choose shared controls for engine === 'frame-copy' and the legacy dock for the native-view engine.

Settings UI

Add one standard setting-item to the Playback section, using the existing Material checkbox and layout styles.

  • Label meaning: Use unified controls for web players (experimental).
  • Description meaning: use IPTVnator's common controls in HTML5, Video.js, and ArtPlayer.
  • Visibility: selected player is videojs, html5, or artplayer.
  • Hidden for Embedded MPV, external MPV, and VLC.
  • Add a stable test selector to the row and checkbox.
  • Add the label and description keys to every locale file so the i18n key sets remain aligned.

No new colors, spacing rules, or one-off UI components are required.

Failure and Compatibility Behavior

  • Missing or malformed non-boolean stored values resolve to false.
  • A storage write failure is reported through the existing SettingsStore.updateSettings() rejection and must not require player-specific recovery.
  • Legacy mode remains the safe fallback whenever the setting cannot be resolved.
  • Direct engine tests can continue overriding WEB_PLAYER_SHARED_CONTROLS with true or false.
  • The setting does not relax Electron sandboxing and does not alter the frame-copy engine opt-in.

Testing Strategy

Shared interfaces and settings store

  • Default is false.
  • Older stored settings without the field load as false.
  • A stored true value is restored.
  • updateSettings() persists the complete resolved value.

Settings form and UI

  • Form creation and serialization include the new field.
  • The checkbox hydrates from stored settings and is included in the Save payload.
  • The row is visible for HTML5, Video.js, and ArtPlayer.
  • The row is hidden for Embedded MPV, external MPV, and VLC.
  • Translation key parity remains valid across all locales.

Playback host

  • A newly created HTML5, Video.js, or ArtPlayer session receives the saved boolean snapshot.
  • false preserves the existing vendor/native UI.
  • true mounts the shared controls path.
  • Embedded MPV selection ignores the web-player preference.
  • One session never renders or initializes both controls systems.

Validation

  • Run focused Settings form/component and SettingsStore tests.
  • Run the complete ui-playback test target because all three web engines share the rollout seam.
  • Run affected lint targets and the workspace typecheck.
  • Run the settings E2E flow for checkbox visibility, persistence, and use after returning to playback.
  • Run the closest Electron playback smoke/E2E coverage because the same Settings surface is used in the desktop app.

Documentation Impact

Update docs/architecture/player-controls-contract.md and the shared-controls sections of AGENTS.md and CLAUDE.md to replace the compile-time-only default-off rollout description with the persisted experimental preference. The Embedded MPV architecture documentation only needs clarification if its existing distinction between frame-copy shared controls and the native-view legacy dock would otherwise become ambiguous.