fix(player): the playing film restarts in place; arrows stay the volume; the bands' holds end
- The info overlay's collection strip listing the film itself: Play/Restart restart or resume it in place instead of pausing it on a frozen 0:00. - Up/Down on the closed ⋯ and tracks buttons stay the volume (openOnArrowKey: false). - A keyboard focus in the bands holds them for 10 s after the last key, not the whole film; a tap leaving a tapped button no longer re-shows the bands its own toggle then hid. - Next leaving under the focus hands it to its neighbour (the player keeps its focus). - The chosen audio / subtitle track wears the MenuCheck tick.
This commit is contained in:
@@ -4,6 +4,7 @@ import { createPortal } from "react-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { plexLang } from "../i18n";
|
||||
import { exposeVideoToBackgroundPlay } from "../lib/browserBackgroundPlay";
|
||||
import { useFocusKeeper } from "../lib/useFocusKeeper";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import Hls from "hls.js";
|
||||
import {
|
||||
@@ -82,6 +83,7 @@ import {
|
||||
} from "../lib/layerStack";
|
||||
import { Button } from "./ui/Button";
|
||||
import { IconButton } from "./ui/IconButton";
|
||||
import { MenuCheck } from "./ui/MenuCheck";
|
||||
import { PopoverItem, PopoverSurface } from "./ui/AnchoredPopover";
|
||||
import { closeThenAct, useAnchoredPopover } from "./ui/anchoredPopoverHooks";
|
||||
import { Slider } from "./ui/NativeControls";
|
||||
@@ -109,6 +111,10 @@ const BACK_TO_PREV_S = 3;
|
||||
const PLAYER_MENU =
|
||||
"rounded-xl border border-white/15 bg-media-chrome/95 text-sm text-white shadow-2xl";
|
||||
const PLAYER_ROW = "block w-full rounded-lg px-2 py-1.5 text-left hover:bg-white/10";
|
||||
// A track choice: the chosen one wears the tick in the leading slot (DESIGN "one of several"), which
|
||||
// the highlight's white text cannot wash out the way it did the accent-coloured label (round 2, P6).
|
||||
const TRACK_ROW =
|
||||
"flex w-full min-h-9 items-center gap-2 rounded-lg px-2 py-1.5 text-left hover:bg-white/10";
|
||||
const TAP_SKIP_S = 10;
|
||||
const DOUBLE_TAP_MS = 300;
|
||||
|
||||
@@ -149,6 +155,10 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
|
||||
// can't start a detached <video> playing audio in the background.
|
||||
const aliveRef = useRef(true);
|
||||
const wrapRef = useRef<HTMLDivElement>(null);
|
||||
// A control that goes while it holds the focus — Next onto the last item or a series finale —
|
||||
// hands it to its neighbour instead of <body> (round 2, P5). The player is portaled outside the
|
||||
// page scroller, whose keeper does not watch it.
|
||||
useFocusKeeper(wrapRef);
|
||||
const sessionStartRef = useRef(0); // absolute offset the current HLS/direct session begins at
|
||||
// Which loadSession call is the current one. Everything after its `await` checks it: a response
|
||||
// that lands after a newer call, an item change or the player's unmount is dropped — it used to
|
||||
@@ -291,6 +301,8 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
|
||||
},
|
||||
placement: "top-end",
|
||||
offset: 8,
|
||||
// ↑/↓ are the volume wherever the focus is in the player, the ⋯ included.
|
||||
openOnArrowKey: false,
|
||||
});
|
||||
const tracksPop = useAnchoredPopover({
|
||||
open: tracksOpen,
|
||||
@@ -298,6 +310,7 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
|
||||
placement: "top-end",
|
||||
offset: 8,
|
||||
fitHeight: true,
|
||||
openOnArrowKey: false,
|
||||
});
|
||||
const tracksFade = useScrollFade();
|
||||
// The ⋯ is both the menu's trigger and the tuning panel's dismiss exception and focus return.
|
||||
@@ -824,6 +837,14 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
|
||||
|
||||
const go = useCallback(
|
||||
(otherId: string | null | undefined, watchedWrite?: Promise<unknown>, fromZero = false) => {
|
||||
if (otherId && otherId === id) {
|
||||
// The item already playing (the info overlay's collection strip lists the film itself):
|
||||
// nothing to switch to. The switch below would pause it and stop trusting its clock, and
|
||||
// `setId` to the same id loads nothing — a frozen 0:00 (E28 review round 2, P1).
|
||||
if (fromZero) seekTo(0);
|
||||
videoRef.current?.play().catch(() => {});
|
||||
return;
|
||||
}
|
||||
if (otherId) {
|
||||
startAtZeroRef.current = fromZero ? otherId : null;
|
||||
// A played-through item settles as watched, with no checkpoint at the credits (see
|
||||
@@ -850,7 +871,7 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
|
||||
setId(otherId);
|
||||
}
|
||||
},
|
||||
[settleProgress, finishProgress],
|
||||
[id, seekTo, settleProgress, finishProgress],
|
||||
);
|
||||
|
||||
// Prev/next: from the playlist queue when playing one, else the item's own episode neighbours.
|
||||
@@ -1696,11 +1717,12 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
|
||||
checked={(audioOrd ?? 0) === a.ord}
|
||||
label={a.label}
|
||||
onSelect={() => changeAudio(a.ord)}
|
||||
className={`${PLAYER_ROW} truncate ${
|
||||
className={`${TRACK_ROW} ${
|
||||
(audioOrd ?? 0) === a.ord ? "text-accent" : "text-white"
|
||||
}`}
|
||||
>
|
||||
{a.label}
|
||||
<MenuCheck kind="one" on={(audioOrd ?? 0) === a.ord} />
|
||||
<span className="truncate">{a.label}</span>
|
||||
</PopoverItem>
|
||||
))}
|
||||
</div>
|
||||
@@ -1714,9 +1736,10 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
|
||||
checked={subOrd == null}
|
||||
label={t("plex.player.subOff")}
|
||||
onSelect={() => changeSubtitle(null)}
|
||||
className={`${PLAYER_ROW} ${subOrd == null ? "text-accent" : "text-white"}`}
|
||||
className={`${TRACK_ROW} ${subOrd == null ? "text-accent" : "text-white"}`}
|
||||
>
|
||||
{t("plex.player.subOff")}
|
||||
<MenuCheck kind="one" on={subOrd == null} />
|
||||
<span className="truncate">{t("plex.player.subOff")}</span>
|
||||
</PopoverItem>
|
||||
{textSubs.map((sub) => (
|
||||
<PopoverItem
|
||||
@@ -1726,11 +1749,12 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
|
||||
checked={subOrd === sub.ord}
|
||||
label={sub.label}
|
||||
onSelect={() => changeSubtitle(sub.ord)}
|
||||
className={`${PLAYER_ROW} truncate ${
|
||||
className={`${TRACK_ROW} ${
|
||||
subOrd === sub.ord ? "text-accent" : "text-white"
|
||||
}`}
|
||||
>
|
||||
{sub.label}
|
||||
<MenuCheck kind="one" on={subOrd === sub.ord} />
|
||||
<span className="truncate">{sub.label}</span>
|
||||
</PopoverItem>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user