feat(plex): sort direction, genre any/all, multi-person filters; fix back-nav + player stop
UAT follow-ups on the Plex filter epic: - Sort now has an asc/desc toggle (sort_dir), applied to any sort field. - Genre multi-select gains an Any/All mode (genre_mode: OR vs AND containment). - Director/actor/studio become multi-value: people AND (titles featuring all selected), studios OR; clicking them on the info page stacks (unions) instead of replacing, and the sidebar shows each as a removable 'Active' chip. - fix(history): clicking a metadata filter on the info page now pushes a fresh grid entry instead of history.back(), so browser Back returns to the info page rather than leaving the Plex module. - fix(player): fully tear down the <video> + hls on unmount and guard late play() calls, so backing out of a just-started video no longer leaves audio playing in the background. i18n en/hu/de (match any/all, sort direction).
This commit is contained in:
parent
eefd7e3abd
commit
1982cfa7b9
10 changed files with 160 additions and 60 deletions
|
|
@ -53,6 +53,9 @@ export default function PlexPlayer({ itemId, onClose }: Props) {
|
|||
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const hlsRef = useRef<Hls | null>(null);
|
||||
// False once unmounted, so a late play() (from MANIFEST_PARSED / loadedmetadata firing after Back)
|
||||
// can't start a detached <video> playing audio in the background.
|
||||
const aliveRef = useRef(true);
|
||||
const wrapRef = useRef<HTMLDivElement>(null);
|
||||
const sessionStartRef = useRef(0); // absolute offset the current HLS/direct session begins at
|
||||
const durationRef = useRef(0);
|
||||
|
|
@ -143,7 +146,7 @@ export default function PlexPlayer({ itemId, onClose }: Props) {
|
|||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||
enableSubs();
|
||||
setReady(true);
|
||||
video.play().catch(() => {});
|
||||
if (aliveRef.current) video.play().catch(() => {});
|
||||
});
|
||||
// A fatal HLS error (e.g. the remux session died / the file became unreadable) would
|
||||
// otherwise leave the spinner up forever — surface it instead.
|
||||
|
|
@ -157,7 +160,7 @@ export default function PlexPlayer({ itemId, onClose }: Props) {
|
|||
const onMeta = () => {
|
||||
if (sess.mode === "direct" && startAt > 0) video.currentTime = startAt;
|
||||
setReady(true);
|
||||
video.play().catch(() => {});
|
||||
if (aliveRef.current) video.play().catch(() => {});
|
||||
video.removeEventListener("loadedmetadata", onMeta);
|
||||
};
|
||||
video.addEventListener("loadedmetadata", onMeta);
|
||||
|
|
@ -179,6 +182,28 @@ export default function PlexPlayer({ itemId, onClose }: Props) {
|
|||
};
|
||||
}, [detail, loadSession]);
|
||||
|
||||
// Full media teardown on unmount (Back): stop + detach the <video> and destroy hls, so a late
|
||||
// play() can't leave audio playing on a detached element after leaving the player.
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
aliveRef.current = false;
|
||||
if (hlsRef.current) {
|
||||
hlsRef.current.destroy();
|
||||
hlsRef.current = null;
|
||||
}
|
||||
const v = videoRef.current;
|
||||
if (v) {
|
||||
try {
|
||||
v.pause();
|
||||
v.removeAttribute("src");
|
||||
v.load();
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// --- time tracking + progress checkpoints ----------------------------------------------------
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue