feat(playlists): keep the player queue live across tabs

Track the playing item by id instead of a frozen index, deriving the index from the
live queue so the N / M counter and prev/next neighbours stay correct when the playlist
changes underneath (e.g. an item removed in another tab) without disrupting playback of
the current video. Refetch the playlist list/detail on window focus so such changes flow
in, updating the player's queue length in near-real-time.
This commit is contained in:
npeter83 2026-06-15 16:27:28 +02:00
parent bd085fc88f
commit 639d761bef
2 changed files with 20 additions and 6 deletions

View file

@ -203,8 +203,15 @@ export default function PlayerModal({
}) {
const { t } = useTranslation();
const qc = useQueryClient();
// The active item is queue[index] (falls back to the single opened video).
const [index, setIndex] = useState(startIndex ?? 0);
// Track the playing item by id (not a frozen index) so it survives the queue changing
// under us — e.g. an item removed in another tab. The index is derived from the *live*
// queue, so the N / M counter and prev/next neighbours stay correct without disrupting
// playback of the current video.
const [playingId, setPlayingId] = useState<string>(
queue?.[startIndex ?? 0]?.id ?? video.id
);
let index = queue ? queue.findIndex((v) => v.id === playingId) : 0;
if (index < 0) index = 0;
const active = queue && queue[index] ? queue[index] : video;
const hasQueue = !!queue && queue.length > 1;
// startAt only applies to the item we opened with; advanced items resume their own pos.
@ -369,7 +376,7 @@ export default function PlayerModal({
autoMarkedRef.current = true;
setWatched(true);
}
if (queue && index < queue.length - 1) setIndex(index + 1);
if (queue && index < queue.length - 1) setPlayingId(queue[index + 1].id);
}
},
},
@ -418,7 +425,7 @@ export default function PlayerModal({
{hasQueue && (
<div className="flex items-center justify-center gap-5 px-4 py-2 border-b border-border text-sm">
<button
onClick={() => setIndex((i) => Math.max(0, i - 1))}
onClick={() => index > 0 && setPlayingId(queue![index - 1].id)}
disabled={index === 0}
className="inline-flex items-center gap-1.5 text-muted enabled:hover:text-fg disabled:opacity-40 transition"
>
@ -428,7 +435,7 @@ export default function PlayerModal({
{index + 1} / {queue!.length}
</span>
<button
onClick={() => setIndex((i) => Math.min(queue!.length - 1, i + 1))}
onClick={() => index < queue!.length - 1 && setPlayingId(queue![index + 1].id)}
disabled={index === queue!.length - 1}
className="inline-flex items-center gap-1.5 text-muted enabled:hover:text-fg disabled:opacity-40 transition"
>