fix(player): a Restart of the paused film plays the new session; a key on faded bands only shows them

The same-film restart read the paused state, restarted the session without play, and resumed the
outgoing stream beside it. Space or Enter on a control the bands had faded from pressed it unseen;
now it only brings them back, and Space plays or pauses.
This commit is contained in:
2026-09-27 21:56:23 +02:00
parent 26697472cf
commit 5ab6778b97
3 changed files with 120 additions and 6 deletions
@@ -273,6 +273,46 @@ describe("keyboard focus in the bands", () => {
expect(bandHidden()).toBe(false);
});
it("a Space on a control the bands faded from only brings them back, and plays or pauses", async () => {
mount({ details: { A: item("A") } });
await playingAt("/media/A", 100);
vi.useFakeTimers();
video()._paused = true;
act(() => void video().play());
const mute = screen.getByLabelText("plex.player.mute");
fireEvent.keyDown(document.body, { key: "Tab" });
act(() => mute.focus());
fireEvent.keyDown(mute, { key: "ArrowUp" });
act(() => vi.advanceTimersByTime(10_500));
expect(bandHidden()).toBe(true);
const pauses = pause.mock.calls.length;
// prevented: the unseen Mute is not pressed on keyup
expect(fireEvent.keyDown(mute, { key: " " })).toBe(false);
expect(bandHidden()).toBe(false);
expect(pause.mock.calls.length).toBe(pauses + 1);
// the bands are up again: now Space is the focused control's own
expect(fireEvent.keyDown(mute, { key: " " })).toBe(true);
});
it("an Enter on a control the bands faded from only brings them back", async () => {
mount({ details: { A: item("A") } });
await playingAt("/media/A", 100);
vi.useFakeTimers();
video()._paused = true;
act(() => void video().play());
const mute = screen.getByLabelText("plex.player.mute");
fireEvent.keyDown(document.body, { key: "Tab" });
act(() => mute.focus());
fireEvent.keyDown(mute, { key: "ArrowUp" });
act(() => vi.advanceTimersByTime(10_500));
expect(bandHidden()).toBe(true);
const pauses = pause.mock.calls.length;
expect(fireEvent.keyDown(mute, { key: "Enter" })).toBe(false);
expect(bandHidden()).toBe(false);
expect(pause.mock.calls.length).toBe(pauses);
expect(video().paused).toBe(false);
});
it("a tap leaving a tapped button for the picture shows the hidden bands", async () => {
mount({ details: { A: item("A") } });
await playingAt("/media/A", 100);
@@ -423,6 +463,55 @@ describe("a collection sibling in the info overlay", () => {
);
});
describe("a Restart of the paused film whose session starts past 0", () => {
it("plays the new session, and never the one it replaces", { timeout: 10000 }, async () => {
const self: PlexItemDetail = {
...item("A"),
cast: [],
genres: [],
directors: [],
collections: [
{
id: "c1",
title: "Saga",
source: "plex",
items: [
{
id: "A",
type: "movie",
title: "Title A",
thumb: "",
status: "in_progress",
position_seconds: 1500,
duration_seconds: 3000,
},
],
},
],
} as unknown as PlexItemDetail;
const { session } = mount({ details: { A: self } });
// A session opened at the resume point: 0 is outside it, so a restart asks for a new one.
session.mockImplementation((id: string) =>
Promise.resolve({ ...direct(id), start_s: 1500 } as Awaited<
ReturnType<typeof api.plexSession>
>),
);
await playingAt("/media/A", 100);
const asked = session.mock.calls.length;
act(() => void video().pause());
fireEvent.click(screen.getByLabelText("plex.player.more"));
fireEvent.click(await menuItem("plex.player.infoBtn"));
const restart = await screen.findByRole("button", { name: "card.restart" }, { timeout: 5000 });
const plays = play.mock.calls.length;
fireEvent.click(restart);
await waitFor(() => expect(session.mock.calls.length).toBe(asked + 1));
expect(lastStart(session)).toEqual(["A", 0]);
expect(play.mock.calls.length).toBe(plays); // the outgoing stream stays paused
act(() => void video().dispatchEvent(new Event("loadedmetadata")));
expect(play.mock.calls.length).toBe(plays + 1); // and the new one plays
});
});
describe("the C5 row", () => {
it("has no Stop, Restart or ±10 s buttons, and keeps the rare tools behind the ⋯", async () => {
mount({ details: { A: item("A") } });