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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBDkEXP4htyXTCZUwMLphd
This commit is contained in:
Simon
2026-09-05 17:06:32 +00:00
parent acfffb3a91
commit 59f7c33ebd

View File

@@ -23,6 +23,7 @@ App.player = App.player || {};
historyPushed: false, historyPushed: false,
idleTimer: null, idleTimer: null,
originEl: null, originEl: null,
hudHovered: false, // mouse resting on the controls (desktop)
attemptToken: 0 // bumps on every open()/format switch to void stale async callbacks attemptToken: 0 // bumps on every open()/format switch to void stale async callbacks
}; };
@@ -145,6 +146,11 @@ App.player = App.player || {};
clearIdleTimer(); clearIdleTimer();
cp.idleTimer = setTimeout(() => { cp.idleTimer = setTimeout(() => {
cp.idleTimer = null; 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'); if (cp.container) cp.container.classList.add('cp-hud-idle');
}, HUD_IDLE_MS); }, HUD_IDLE_MS);
} }
@@ -449,6 +455,64 @@ App.player = App.player || {};
addCleanup(destroy); 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) // Keyboard shortcuts (desktop)
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@@ -840,6 +904,7 @@ App.player = App.player || {};
}); });
} }
bindGestures(cp.video); bindGestures(cp.video);
bindHoverHud();
bindKeyboard(cp.video); bindKeyboard(cp.video);
bindClose(); bindClose();
bindBufferingIndicator(cp.video); bindBufferingIndicator(cp.video);