fix(player): a layer that opens by itself takes no history entry
The auto-skip countdown and the up-next card pushed an entry without a user gesture (Chrome's Back button skips those), and one pushed under the open shortcuts sheet sat above it, so Back closed the hidden layer.
This commit is contained in:
@@ -308,6 +308,45 @@ describe("leaving an item that was played through", () => {
|
|||||||
act(() => void el.dispatchEvent(new Event("ended")));
|
act(() => void el.dispatchEvent(new Event("ended")));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A layer the player opens BY ITSELF — the auto-skip countdown, the up-next card — takes no history
|
||||||
|
// entry. Chrome's Back button skips an entry pushed without a user gesture, and one pushed under an
|
||||||
|
// open dialog (the shortcuts sheet) sat above it, so Back closed the hidden layer, not the dialog.
|
||||||
|
const overlayPushes = (push: { mock: { calls: unknown[][] } }) =>
|
||||||
|
push.mock.calls.filter(([state]) => (state as { _ov?: number } | null)?._ov != null);
|
||||||
|
|
||||||
|
it("gives the auto-skip countdown no history entry", async () => {
|
||||||
|
prefs({ autoSkipIntro: true, autoSkipDelay: 5 });
|
||||||
|
const withIntro = {
|
||||||
|
...item("A"),
|
||||||
|
markers: [{ type: "intro", start_s: 100, end_s: 160, final: false }],
|
||||||
|
} as unknown as PlexItemDetail;
|
||||||
|
mount({ details: { A: withIntro }, sessions: { A: () => Promise.resolve(direct("A")) } });
|
||||||
|
await playingAt("/media/A", 50);
|
||||||
|
await tick();
|
||||||
|
const push = vi.spyOn(window.history, "pushState");
|
||||||
|
await playingAt("/media/A", 110); // inside the intro: the countdown starts
|
||||||
|
await waitFor(() => expect(screen.getByText("plex.player.skipIntro")).toBeTruthy());
|
||||||
|
await tick();
|
||||||
|
expect(overlayPushes(push)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gives the up-next card no history entry", async () => {
|
||||||
|
prefs({ upNext: true });
|
||||||
|
mount({
|
||||||
|
details: { A: item("A"), B: item("B") },
|
||||||
|
sessions: { A: () => Promise.resolve(direct("A")), B: () => new Promise(() => {}) },
|
||||||
|
queue: ["A", "B"],
|
||||||
|
});
|
||||||
|
vi.spyOn(api, "plexSetState").mockResolvedValue(undefined as never);
|
||||||
|
await playingAt("/media/A", 2995);
|
||||||
|
await tick();
|
||||||
|
const push = vi.spyOn(window.history, "pushState");
|
||||||
|
endMedia();
|
||||||
|
await screen.findByText("Title B"); // the card names the next item
|
||||||
|
await tick();
|
||||||
|
expect(overlayPushes(push)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
it("settles it as watched when it ended, so Prev does not reopen it at its last seconds", async () => {
|
it("settles it as watched when it ended, so Prev does not reopen it at its last seconds", async () => {
|
||||||
prefs({ upNext: false });
|
prefs({ upNext: false });
|
||||||
const { qc, progress } = mount({
|
const { qc, progress } = mount({
|
||||||
|
|||||||
@@ -856,9 +856,10 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
|
|||||||
// The ONE Back cascade for the player's own ways of saying "back": the on-screen arrow, ⌫ and
|
// 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
|
// 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
|
// 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
|
// the layers the viewer OPENED one per press, the one opened last first (each holds its own history
|
||||||
// BackClose below). The shortcuts sheet is not in it: a Modal above us, with its own Escape layer
|
// entry, see BackClose below); the ones that open by themselves have none, so Back leaves past
|
||||||
// and Back-to-close.
|
// them. The shortcuts sheet is not in it: a Modal above us, with its own Escape layer and
|
||||||
|
// Back-to-close. Leaving closes whatever is still up first (`useHistorySubview`'s back).
|
||||||
// - The on-screen arrow is a click on "leave", not a cancel, so it does not stop a countdown.
|
// - 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.
|
// - Escape in fullscreen leaves fullscreen (the browser's own Escape) and not the player.
|
||||||
const back = useCallback(
|
const back = useCallback(
|
||||||
@@ -873,7 +874,6 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
|
|||||||
// The mouse/browser Back closes the layer whose history entry it popped (see BackClose below).
|
// The mouse/browser Back closes the layer whose history entry it popped (see BackClose below).
|
||||||
const closeStats = useCallback(() => setStatsOpen(false), []);
|
const closeStats = useCallback(() => setStatsOpen(false), []);
|
||||||
const closeAnyPanel = useCallback(() => setPanel("none"), []);
|
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
|
// Tell the shared cheat sheet which keys are live here, and whether ⇧← / ⇧→ apply (they step
|
||||||
// episodes, so they are listed only inside a series).
|
// episodes, so they are listed only inside a series).
|
||||||
@@ -1225,12 +1225,14 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Each layer over the video claims its OWN history entry while it is up, so the browser/mouse
|
{/* Each layer the viewer opens over the video claims its OWN history entry while it is up, so
|
||||||
Back closes the one opened last and the next Back the one under it — one shared entry was
|
the browser/mouse Back closes the one opened last and the next Back the one under it — one
|
||||||
spent on the first layer, and the second Back left the player with the rest still up. */}
|
shared entry was spent on the first layer, and the second Back left the player with the rest
|
||||||
|
still up. The auto-skip countdown and the up-next card open BY THEMSELVES and claim none:
|
||||||
|
Chrome's Back button skips an entry pushed without a user gesture, and one pushed while the
|
||||||
|
shortcuts sheet was open sat above the sheet's, so Back closed the hidden layer. */}
|
||||||
{statsOpen && <BackClose onBack={closeStats} />}
|
{statsOpen && <BackClose onBack={closeStats} />}
|
||||||
{panel !== "none" && <BackClose onBack={closeAnyPanel} />}
|
{panel !== "none" && panel !== "upNext" && <BackClose onBack={closeAnyPanel} />}
|
||||||
{skipProgress != null && <BackClose onBack={cancelSkip} />}
|
|
||||||
|
|
||||||
{/* Top bar */}
|
{/* Top bar */}
|
||||||
<div
|
<div
|
||||||
|
|||||||
Reference in New Issue
Block a user