Files
iptvnator/libs/ui/playback/src/lib/player-controls/controls-format.utils.ts
T
aa6ee85d3f feat(player-controls): add shared engine-agnostic controls layer (#1148)
* feat(player-controls): shared engine-agnostic controls layer (flag off)

Introduce a shared, engine-agnostic player-controls layer in libs/ui/playback
as pure additive library code with no consumers yet.

- Contract (player-controls.model.ts): PlayerControlsCapabilities,
  PlayerControlsState, and PlayerControlsCommands make up the
  PlayerController interface every engine adapter implements.
- Single presentation component (app-player-controls): one controls UI
  binding purely to a PlayerController, with focused helpers for
  visibility auto-hide, volume, fullscreen (built-in DOM path), menus,
  keyboard shortcuts, seek/volume feedback, and the controls surface.
- Web-video adapter (web-video-controls.adapter.ts + host directive):
  drives the contract from an HTMLVideoElement, including optional
  HLS.js quality/audio-track integration and series episode navigation.
- Feature flag WEB_PLAYER_SHARED_CONTROLS (web-player-controls.flag.ts)
  defaults to OFF; no player component consumes the new layer yet, so
  runtime behavior is unchanged.
- docs/architecture/player-controls-contract.md documents the target
  architecture (later PRs add the embedded-MPV adapter, immersive
  overlay, and host-supplied fullscreen delegate).

Review-driven hardening: the web-video adapter now holds the host
series-navigation signal reactively (updates after setContext are
reflected); the shared controls template is fully localized via
ngx-translate (reusing the EMBEDDED_MPV.PLAYER.* keys); single click on
the viewport toggles play/pause deferred so a double-click still
fullscreens; keyboard shortcuts are shadow-DOM-safe (composedPath) and
guard against duplicate-instance double-execution (defaultPrevented);
and hidden controls now also disable shortcuts.

Test coverage extended per Codecov patch report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(player-controls): harden shared controls foundation

* fix(player-controls): restore detached adapter state

* fix(player-controls): harden keyboard and adapter state

* fix(player-controls): refresh readiness and hide timers

* fix(player-controls): keep controls root inside surface

* fix(player-controls): hide seek controls for live streams

* fix(player-controls): harden multi-engine control state

* fix(player-controls): respect runtime interaction availability

* fix(player-controls): gate loading toggles and localize mute

* fix(player-controls): harden surface and error states

* fix(player-controls): preserve volume and cursor state

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: 4gray <serega05@gmail.com>
2026-07-16 12:56:30 +02:00

44 lines
1.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export function formatTime(value: number | null | undefined): string {
const safeValue = Math.max(0, Math.floor(value ?? 0));
const hours = Math.floor(safeValue / 3600);
const minutes = Math.floor((safeValue % 3600) / 60);
const seconds = safeValue % 60;
if (hours > 0) {
return `${hours}:${String(minutes).padStart(2, '0')}:${String(
seconds
).padStart(2, '0')}`;
}
return `${minutes}:${String(seconds).padStart(2, '0')}`;
}
export function volumeIcon(value: number): string {
if (value <= 0) {
return 'volume_off';
}
return value < 0.5 ? 'volume_down' : 'volume_up';
}
export function volumeLabel(value: number): string {
return `Volume ${Math.round(value * 100)}%`;
}
/** Rounds and formats a playback rate as e.g. `1.5×` for tooltips. */
export function speedLabel(speed: number): string {
const value = Math.round(speed * 100) / 100;
return `${value}×`;
}
export function readStoredVolume(): number {
const rawValue = Number(localStorage.getItem('volume') ?? '1');
if (Number.isNaN(rawValue)) {
return 1;
}
return Math.max(0, Math.min(1, rawValue));
}
export function persistVolume(value: number): void {
localStorage.setItem('volume', String(value));
}