From c1105194129f3b9d66723147af8cc0ba00935cff Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Fri, 18 Sep 2026 21:12:00 +0200 Subject: [PATCH] fix(playback): make the fullscreen channel panel discoverable and calmer to close (#1619) * fix(playback): make the fullscreen channel panel discoverable and calmer to close The left-edge hot zone was an invisible 28px strip with nothing telling the user where the list lived, so "hovering the left side" rarely reached it while `C` always worked. Mouse movement over the stage now reveals a slim edge hint tab (CSS chevron, fades after 2.5 s idle, lit while the pointer rests in the zone), the zone grows to 40px (48px coarse), and a click or tap on the edge opens at once without the dwell. Closing follows the pointer more honestly: the mouse-leave grace grows from 420 ms to 1 s, and it applies only once the pointer has engaged with the panel, so a `C`-opened list no longer closes while the mouse merely roams over the video under the user's typing. Clicks inside the panel never close it. Found on the way: closing with Escape while the mouse still rests on the edge reopened the panel 160 ms later, because Chromium synthesizes a `pointerenter` on the hot zone the aside slid away from. An explicit close now re-arms the zone only on the next real pointer move. Verified live over CDP on HTML5, Video.js, ArtPlayer and Embedded MPV frame-copy; the hover mechanism itself was sound on every engine. Co-Authored-By: Claude Fable 5.1 * fix(playback): open the panel only on a primary click begun on the edge Review follow-up. The hot zone opened on every pointerup, so a drag released over the edge, a right or middle click and a pen barrel button all opened the panel; now pointerdown records the primary pointer and pointerup must match it (a leave or cancel forgets the press). The synthetic pointerenter that follows an explicit close no longer arms the hint either: the edge stays clear until the next real pointer move, which arms it and starts the dwell. Co-Authored-By: Claude Fable 5.1 --------- Co-authored-by: Claude Fable 5.1 --- .../playback-fullscreen-panel-edge-hint.md | 6 + AGENTS.md | 14 +- CLAUDE.md | 2 +- docs/architecture/player-controls-contract.md | 50 ++++-- .../fullscreen-channel-panel-state.spec.ts | 111 ++++++++++++ .../fullscreen-channel-panel-state.ts | 114 ++++++++++-- .../fullscreen-channel-panel.component.html | 22 ++- .../fullscreen-channel-panel.component.scss | 61 ++++++- ...fullscreen-channel-panel.component.spec.ts | 167 +++++++++++++++++- .../fullscreen-channel-panel.component.ts | 85 +++++++-- 10 files changed, 574 insertions(+), 58 deletions(-) create mode 100644 .changes/playback-fullscreen-panel-edge-hint.md 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 `