diff --git a/.changes/playback-fullscreen-panel-edge-hint.md b/.changes/playback-fullscreen-panel-edge-hint.md new file mode 100644 index 000000000..61f60c7bd --- /dev/null +++ b/.changes/playback-fullscreen-panel-edge-hint.md @@ -0,0 +1,6 @@ +--- +type: fix +area: playback +--- + +The fullscreen channel list is easier to find and calmer to use: moving the mouse shows a small tab on the left edge, a click on that edge opens the list at once, hover still opens it after a short rest, and the list now stays open for a full second after the mouse leaves. Opened with `C`, it no longer closes while the mouse only wanders over the video. diff --git a/AGENTS.md b/AGENTS.md index c8b5c0de4..b89dbd57c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -576,12 +576,16 @@ unchanged. Contract: `docs/architecture/m3u-playlist-module.md` first unknown probe and confirmed native/unsupported results withhold it. A live host provides `FULLSCREEN_CHANNEL_PANEL` (`panelTemplate` + optional `panelTitle`) and the panel slides that list over the video: left-edge hover - dwell, a touch tap on that edge, or `C`. The hot zone stays mounted above the - scrim and below the panel during opening, so a delayed paint cannot turn + dwell, a click or tap on that edge, or `C`. The hot zone stays mounted above + the scrim and below the panel during opening, so a delayed paint cannot turn stationary hover into a synthetic leave. Nothing is drawn while it is closed - (no handle), the hot zone stops above the controls bar, and scrim/Escape/ - mouse-leave close it — while a CDK overlay opened from the list counts as - the panel, so hover keeps it open and Escape closes the overlay first. The + and the pointer rests — mouse movement over the stage reveals a slim edge + hint tab that fades after 2.5 s idle — the hot zone stops above the controls + bar, and scrim/Escape/mouse-leave close it, mouse-leave after 1 s and only + once the pointer has been inside the panel (a `C`-opened panel survives the + mouse roaming over the video) — while a CDK overlay opened from the list + counts as the panel, so hover keeps it open and Escape closes the overlay + first. The header is one row (search whose placeholder carries the host title, plus close) and the list stays mounted per fullscreen session. `Settings.fullscreenChannelPanel` (default on) gates it, offered only for the diff --git a/CLAUDE.md b/CLAUDE.md index 0168e6922..db568b50f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1303,7 +1303,7 @@ engine` (restart required) or helper: `apps/electron-backend/native/helper/`; canonical packaging/runtime contracts: `docs/architecture/embedded-mpv-native.md` and `tools/embedded-mpv/README.md`. -- Shared player-controls layer: `libs/ui/playback/src/lib/player-controls/` exports the engine-neutral `PlayerController` contract, standalone `app-player-controls`, a generic web-video adapter/helper, and component-scoped `WEB_PLAYER_SHARED_CONTROLS` rollout token. Its subtitle menu carries capability-gated advanced subtitle support (#1408): external subtitle file loading, a ±0.5 s timing-offset row, and size/color styling persisted in the shared `subtitleStyle` localStorage key. HTML5/ArtPlayer implement it through the neutral source bridge (`.srt`/`.vtt` via a DOM file picker with encoding detection, native `TextTrack` rendering, `::cue` styling, delay only while the loaded file is the selected track; picks are source-generation-guarded and engine deselection precedes external track activation); the canonical style shape and clamp/normalize rules are shared with the main process via `@iptvnator/shared/interfaces` (`subtitle-style.util.ts`). Embedded MPV frame-copy implements it through new helper protocol commands (`sub-add`/`sub-delay`/`sub-scale`/`sub-color`, main-process file dialog, ASS supported, delay for all tracks). Video.js shared mode, vendor-chrome paths, native-view, and the Linux out-of-process path advertise no such capability and render no UI. Contract details: `docs/architecture/player-controls-contract.md` ("Advanced subtitle support"). Shared controls include a per-session quality menu (Auto + “1080p”-style levels via `setQualityLevel`; `AUTO_QUALITY_LEVEL_ID` restores ABR): the capability derives from the manifest — advertised only when the source exposes >1 video rendition (multi-variant HLS via hls.js `nextLevel`/`manualLevel`, DASH via Shaka variant tracks pinned to the active variant's exact audio stream (`audioId`, language fallback) with ABR toggled off for manual picks, Video.js via videojs-contrib-quality-levels) — so single-bitrate VOD and raw MPEG-TS never show it, nothing persists to Settings, and Embedded MPV/external players report the capability false. In fullscreen, `app-player-controls` shows a pointer-transparent media-title overlay at the top while controls are revealed (`mediaTitle` input: movie/channel/series name, plus an `S01E03` second line for episodes; series names flow from the detail views through `PortalInlinePlayerComponent.seriesTitle` and `WebPlayerViewComponent.mediaTitle`). Persisted `Settings.webPlayerSharedControls` is default-ON (absent stored values coerce with `!== false` in every normalization site; only an explicit false — the Settings > Playback checkbox — opts out to the legacy vendor chrome), and its checkbox appears only when HTML5, Video.js, or ArtPlayer is selected. The shared surface has explicit touch semantics (`ControlsSurface.wasTouchInteraction`): viewport taps toggle overlay visibility instead of pausing, the volume popover opens on tap instead of hover, coarse pointers get a taller scrub strip, and at container widths ≤640px the bar reflows to two rows (full-width timeline above transport + an end-aligned, wrapping actions cluster with 40px buttons whose panels remain unclipped). Only keyboard-originated focus pins the bar open: Chromium also focuses a clicked `