From 59f7c33ebd8aaec6765701eb841659e2f42ce13a Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 5 Sep 2026 17:06:32 +0000 Subject: [PATCH] Wake the player HUD on mouse movement (desktop) The fullscreen player only revealed its HUD on a tap, a key press or a transport action, so on a PC the controls stayed hidden while the mouse moved over the video -- the reels feed already woke its own HUD on mousemove, so this was the odd one out. Any mouse movement over the player now wakes it, and it stays up while the pointer rests on the top or bottom bar rather than fading out from under the cursor. Touch and pen are excluded on purpose: taps already wake the HUD, and a finger dragging for volume or a dismiss swipe isn't someone looking for the controls. The listeners go on the bars rather than .cp-hud, which is pointer-events:none so it never eats gestures over the video. Verified with synthesized mouse input in headless Chrome: idle 3.2s -> hidden; move over the video -> shown; still 3.2s -> hidden again; onto the controls -> shown and still shown after 3.4s resting there; back over the video and still -> hidden. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QBDkEXP4htyXTCZUwMLphd --- frontend/js/player.js | 65 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/frontend/js/player.js b/frontend/js/player.js index 1f4ed46..ad8b6b0 100644 --- a/frontend/js/player.js +++ b/frontend/js/player.js @@ -23,6 +23,7 @@ App.player = App.player || {}; historyPushed: false, idleTimer: null, originEl: null, + hudHovered: false, // mouse resting on the controls (desktop) attemptToken: 0 // bumps on every open()/format switch to void stale async callbacks }; @@ -145,6 +146,11 @@ App.player = App.player || {}; clearIdleTimer(); cp.idleTimer = setTimeout(() => { cp.idleTimer = null; + // Never fade out from under a mouse resting on the controls. + if (cp.hudHovered) { + scheduleHudHide(); + return; + } if (cp.container) cp.container.classList.add('cp-hud-idle'); }, HUD_IDLE_MS); } @@ -449,6 +455,64 @@ App.player = App.player || {}; addCleanup(destroy); } + // --------------------------------------------------------------------- + // Mouse presence (desktop): moving the mouse anywhere over the player + // brings the HUD back, and it stays up while the pointer rests on the + // controls. Touch input is deliberately excluded -- taps already wake the + // HUD, and a finger dragging for volume or a dismiss swipe shouldn't count + // as "the viewer went looking for the controls". + // --------------------------------------------------------------------- + function bindHoverHud() { + const container = cp.container; + if (!container) return; + const isMouse = (event) => !event.pointerType || event.pointerType === 'mouse'; + + let lastWake = 0; + const onMove = (event) => { + if (!isMouse(event)) return; + // wakeHud() re-arms the idle timer; once every 150ms is plenty and + // keeps a fast mouse from churning timers on every pixel. + const now = (window.performance && performance.now()) ? performance.now() : Date.now(); + if (now - lastWake < 150) return; + lastWake = now; + wakeHud(); + }; + const onEnterHud = (event) => { + if (!isMouse(event)) return; + cp.hudHovered = true; + wakeHud(); + }; + const onLeaveHud = (event) => { + if (!isMouse(event)) return; + cp.hudHovered = false; + scheduleHudHide(); + }; + const onLeaveContainer = (event) => { + if (!isMouse(event)) return; + cp.hudHovered = false; + }; + + container.addEventListener('pointermove', onMove); + container.addEventListener('pointerleave', onLeaveContainer); + // The bars, not .cp-hud itself: the HUD wrapper is pointer-events:none + // (so it never eats gestures over the video) and only its children are + // hit-testable. + const bars = [q('.cp-top-bar'), q('.cp-bottom-bar')].filter(Boolean); + bars.forEach((bar) => { + bar.addEventListener('pointerenter', onEnterHud); + bar.addEventListener('pointerleave', onLeaveHud); + }); + addCleanup(() => { + cp.hudHovered = false; + container.removeEventListener('pointermove', onMove); + container.removeEventListener('pointerleave', onLeaveContainer); + bars.forEach((bar) => { + bar.removeEventListener('pointerenter', onEnterHud); + bar.removeEventListener('pointerleave', onLeaveHud); + }); + }); + } + // --------------------------------------------------------------------- // Keyboard shortcuts (desktop) // --------------------------------------------------------------------- @@ -840,6 +904,7 @@ App.player = App.player || {}; }); } bindGestures(cp.video); + bindHoverHud(); bindKeyboard(cp.video); bindClose(); bindBufferingIndicator(cp.video);