fix(player): the browser Back peels each layer over the Plex video, one press at a time

All the layers shared one history entry, so the first Back spent it on one layer and the second
left the player with the stats readout (or the countdown) still up. Each layer now holds its own
entry; Back closes the one opened last.
This commit is contained in:
2026-10-07 23:16:38 +02:00
parent 891dc628f3
commit 2aa3ac6e85
2 changed files with 49 additions and 21 deletions
+17 -12
View File
@@ -855,15 +855,16 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
closePanel("upNext");
}
// The ONE Back cascade, for every way of saying "back": the on-screen arrow, ⌫, Escape, and the
// mouse/browser Back (history popstate, through BackClose below). It peels off what is layered
// over the video — the auto-skip countdown, then the open panel, then the stats readout — and only
// then leaves the player. The shortcuts sheet is not in it: a Modal above us, with its own Escape
// layer and Back-to-close.
// The ONE Back cascade for the player's own ways of saying "back": the on-screen arrow, ⌫ and
// Escape. It peels off what is layered over the video — the auto-skip countdown, then the open
// panel, then the stats readout — and only then leaves the player. The mouse/browser Back peels
// the same layers one per press, the one opened last first (each holds its own history entry, see
// BackClose below). The shortcuts sheet is not in it: a Modal above us, with its own Escape layer
// and Back-to-close.
// - The on-screen arrow is a click on "leave", not a cancel, so it does not stop a countdown.
// - Escape in fullscreen leaves fullscreen (the browser's own Escape) and not the player.
const back = useCallback(
(from: "arrow" | "key" | "history" | "escape") => {
(from: "arrow" | "key" | "escape") => {
if (from !== "arrow" && skipProgressRef.current != null) cancelAutoSkipRef.current();
else if (panelRef.current !== "none") setPanel("none");
else if (statsOpenRef.current) setStatsOpen(false);
@@ -871,7 +872,10 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
},
[onClose, panelRef, statsOpenRef, skipProgressRef, cancelAutoSkipRef],
);
const backFromHistory = useCallback(() => back("history"), [back]);
// The mouse/browser Back closes the layer whose history entry it popped (see BackClose below).
const closeStats = useCallback(() => setStatsOpen(false), []);
const closeAnyPanel = useCallback(() => setPanel("none"), []);
const cancelSkip = useCallback(() => cancelAutoSkipRef.current(), [cancelAutoSkipRef]);
// Tell the shared cheat sheet which keys are live here, and whether ⇧← / ⇧→ apply (they step
// episodes, so they are listed only inside a series).
@@ -1223,11 +1227,12 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
)}
</div>
{/* While a panel/menu is open or an auto-skip countdown is running, take over the browser/mouse
Back button (history popstate) so it closes/cancels that instead of dropping to the grid. */}
{(panel !== "none" || statsOpen || skipProgress != null) && (
<BackClose onBack={backFromHistory} />
)}
{/* Each layer over the video claims its OWN history entry while it is up, so the browser/mouse
Back closes the one opened last and the next Back the one under it — one shared entry was
spent on the first layer, and the second Back left the player with the rest still up. */}
{statsOpen && <BackClose onBack={closeStats} />}
{panel !== "none" && <BackClose onBack={closeAnyPanel} />}
{skipProgress != null && <BackClose onBack={cancelSkip} />}
{/* Top bar */}
<div