From f162788135939b594e57adeec06595d5dedf8adc Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sun, 30 Aug 2026 00:28:50 +0200 Subject: [PATCH] feat(plex): up-next end-card at episode end (E6 S4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At episode end, when a next item exists and the pref is on, show an up-next card (poster + title + ~10s countdown + Cancel) instead of the old instant jump, so binge stays in the viewer's control (D6-A). Countdown or Play now advances; Cancel/✕ dismisses and stays on the finished frame. Lives only on the play-to-the-end path — credits-auto-skip advances earlier and never reaches the card, so the two reconcile by construction (no double-advance). New pref 'upNext' (default on) with a Playback-tab toggle; the card closes on item change and joins the Back/Escape cascade. i18n HU/EN. --- frontend/src/components/PlexPlayer.tsx | 33 ++++- .../src/components/PlexPlayerSettings.tsx | 5 + frontend/src/components/UpNextCard.tsx | 128 ++++++++++++++++++ frontend/src/i18n/locales/en/plex.json | 6 + frontend/src/i18n/locales/hu/plex.json | 6 + frontend/src/lib/plexPlayerPrefs.ts | 2 + 6 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/UpNextCard.tsx diff --git a/frontend/src/components/PlexPlayer.tsx b/frontend/src/components/PlexPlayer.tsx index 7d9f0f9a..fabbe953 100644 --- a/frontend/src/components/PlexPlayer.tsx +++ b/frontend/src/components/PlexPlayer.tsx @@ -42,6 +42,7 @@ import { import { Ctrl, PanelHead, Skip10 } from "./plexPlayerControls"; import { PlexPlayerSettings } from "./PlexPlayerSettings"; import { PlexStatsOverlay } from "./PlexStatsOverlay"; +import { UpNextCard } from "./UpNextCard"; import { useAutoSkip, useControlsVisibility, @@ -177,6 +178,12 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) { const [statsOpen, setStatsOpen] = useState(false); const statsOpenRef = useRef(false); statsOpenRef.current = statsOpen; + // Up-next end-card: shown at episode end (with a next item) instead of an instant jump (S4). + const [upNextOpen, setUpNextOpen] = useState(false); + const upNextOpenRef = useRef(false); + upNextOpenRef.current = upNextOpen; + const upNextPrefRef = useRef(prefs.upNext); + upNextPrefRef.current = prefs.upNext; const now = useWallClock(); durationRef.current = duration; @@ -520,7 +527,11 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) { // (absRef 0) — its stray 'ended' must not mark anything watched or cascade another advance. if (absRef.current <= 0) return; api.plexSetState(id, "watched").catch(() => {}); - if (nextId) go(nextId); + // With a next item and the up-next pref on, surface the end-card (poster + countdown + Cancel) + // and let IT advance — binge stays in the viewer's control (S4/D6-A). The card only appears here, + // on play-to-the-end; credits-auto-skip advances earlier and this handler never fires for it. + if (nextId && upNextPrefRef.current) setUpNextOpen(true); + else if (nextId) go(nextId); // Nothing queued next → dismiss the player back to the previous page (same as Stop/Escape), // instead of stranding a black, ended