From 83840594a626861c1c12da0cea68c8b5842b4e2e Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Thu, 23 Oct 2025 09:50:51 -0600 Subject: [PATCH] watchparty goes to next episode with host --- .../internals/Backend/WatchPartyReporter.tsx | 118 ++++++++++++++++++ .../player/internals/WatchPartyResetter.tsx | 35 +++--- 2 files changed, 136 insertions(+), 17 deletions(-) diff --git a/src/components/player/internals/Backend/WatchPartyReporter.tsx b/src/components/player/internals/Backend/WatchPartyReporter.tsx index 61e1cd84..6f6eab02 100644 --- a/src/components/player/internals/Backend/WatchPartyReporter.tsx +++ b/src/components/player/internals/Backend/WatchPartyReporter.tsx @@ -1,5 +1,6 @@ import { t } from "i18next"; import { useEffect, useRef } from "react"; +import { useNavigate } from "react-router-dom"; import { getRoomStatuses, sendPlayerStatus } from "@/backend/player/status"; import { usePlayerStatusPolling } from "@/components/player/hooks/usePlayerStatusPolling"; @@ -24,6 +25,8 @@ export function WatchPartyReporter() { const lastReportTime = useRef(0); const lastReportedStateRef = useRef(""); const contentValidatedRef = useRef(false); + const hostEpisodeRef = useRef<{ seasonId?: number; episodeId?: number }>({}); + const navigate = useNavigate(); // Auth data const account = useAuthStore((s) => s.account); @@ -45,6 +48,7 @@ export function WatchPartyReporter() { useEffect(() => { if (!watchPartyEnabled) { contentValidatedRef.current = false; + hostEpisodeRef.current = {}; } }, [watchPartyEnabled]); @@ -93,6 +97,14 @@ export function WatchPartyReporter() { ? parseInt(meta.episode.tmdbId, 10) : undefined; + // Initialize host episode tracking + if (hostSeasonId && hostEpisodeId) { + hostEpisodeRef.current = { + seasonId: hostSeasonId, + episodeId: hostEpisodeId, + }; + } + // Validate episode match (if host has this info) if ( (hostSeasonId && @@ -144,6 +156,112 @@ export function WatchPartyReporter() { disable, ]); + // Monitor for episode changes from host and auto-navigate guests + useEffect(() => { + if ( + !watchPartyEnabled || + !roomCode || + isHost || + !meta?.tmdbId || + meta.type !== "show" + ) { + return; + } + + const checkForEpisodeChange = async () => { + try { + const roomData = await getRoomStatuses(backendUrl, account, roomCode); + const users = Object.values(roomData.users).flat(); + const hostUser = users.find((user) => user.isHost); + + if (!hostUser || hostUser.content.type !== "TV Show") return; + + const hostSeasonId = hostUser.content.seasonId; + const hostEpisodeId = hostUser.content.episodeId; + + // Initialize host episode ref on first check + if ( + !hostEpisodeRef.current.seasonId && + !hostEpisodeRef.current.episodeId + ) { + hostEpisodeRef.current = { + seasonId: hostSeasonId, + episodeId: hostEpisodeId, + }; + return; + } + + // Check if host has changed episodes + const hasHostChangedEpisode = + hostSeasonId !== hostEpisodeRef.current.seasonId || + hostEpisodeId !== hostEpisodeRef.current.episodeId; + + if (hasHostChangedEpisode && hostSeasonId && hostEpisodeId) { + // Update our reference + hostEpisodeRef.current = { + seasonId: hostSeasonId, + episodeId: hostEpisodeId, + }; + + // Check if we're already on the correct episode + const currentSeasonId = meta.season?.tmdbId + ? parseInt(meta.season.tmdbId, 10) + : undefined; + const currentEpisodeId = meta.episode?.tmdbId + ? parseInt(meta.episode.tmdbId, 10) + : undefined; + + if ( + currentSeasonId === hostSeasonId && + currentEpisodeId === hostEpisodeId + ) { + // Already on the correct episode + return; + } + + // Navigate to the new episode + // eslint-disable-next-line no-console + console.log("Host changed episode, following to new episode:", { + seasonId: hostSeasonId, + episodeId: hostEpisodeId, + }); + + const url = new URL( + `/media/tmdb-tv-${meta.tmdbId}/${hostSeasonId}/${hostEpisodeId}`, + window.location.origin, + ); + url.searchParams.set("watchparty", roomCode); + + // Reset content validation so it re-validates on the new episode + contentValidatedRef.current = false; + + navigate(url.pathname + url.search); + } + } catch (error) { + console.error("Failed to check for episode change:", error); + } + }; + + // Check every 3 seconds for episode changes + const interval = setInterval(checkForEpisodeChange, 3000); + + // Initial check + checkForEpisodeChange(); + + return () => clearInterval(interval); + }, [ + watchPartyEnabled, + roomCode, + isHost, + meta?.tmdbId, + meta?.season?.tmdbId, + meta?.episode?.tmdbId, + meta?.type, + backendUrl, + account, + navigate, + ]); + useEffect(() => { // Skip if watch party is not enabled if ( diff --git a/src/components/player/internals/WatchPartyResetter.tsx b/src/components/player/internals/WatchPartyResetter.tsx index b239b703..fb61ea38 100644 --- a/src/components/player/internals/WatchPartyResetter.tsx +++ b/src/components/player/internals/WatchPartyResetter.tsx @@ -11,41 +11,42 @@ export function WatchPartyResetter() { const meta = usePlayerStore((s) => s.meta); const { disable } = useWatchPartyStore(); - // Store the current meta to track changes - const previousMetaRef = useRef(null); + // Store the current base media to track changes + const previousBaseMediaRef = useRef(null); - // Memoize the metaId calculation - const metaId = useMemo(() => { + // Memoize the base media ID (without episode details for shows) + // This allows episode changes within the same show to keep the room active + const baseMediaId = useMemo(() => { if (!meta) return null; - return meta.type === "show" - ? `${meta.type}-${meta.tmdbId}-s${meta.season?.tmdbId || "0"}-e${meta.episode?.tmdbId || "0"}` - : `${meta.type}-${meta.tmdbId}`; + // For shows, only track the show ID, not the episode + // This allows episode navigation within the same show + return `${meta.type}-${meta.tmdbId}`; }, [meta]); useEffect(() => { - // If meta exists but has changed, reset watch party + // If base media has changed (different show/movie), reset watch party if ( - metaId && - previousMetaRef.current && - metaId !== previousMetaRef.current + baseMediaId && + previousBaseMediaRef.current && + baseMediaId !== previousBaseMediaRef.current ) { // eslint-disable-next-line no-console - console.log("Media changed, disabling watch party:", { - previous: previousMetaRef.current, - current: metaId, + console.log("Base media changed, disabling watch party:", { + previous: previousBaseMediaRef.current, + current: baseMediaId, }); disable(); } - // Update the ref with current meta - previousMetaRef.current = metaId; + // Update the ref with current base media + previousBaseMediaRef.current = baseMediaId; // Also reset when component unmounts (player exited) return () => { disable(); }; - }, [metaId, disable]); + }, [baseMediaId, disable]); return null; // This component doesn't render anything }