From dfd5004a5ed3bf6dc625789da0de02bcf96b513c Mon Sep 17 00:00:00 2001 From: mtvpls Date: Mon, 8 Dec 2025 00:53:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=88=87=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/play/page.tsx | 57 ++++++++++++++++++++++ src/hooks/usePlaySync.ts | 101 +++++++++++++++++++++++++++++++++++---- 2 files changed, 149 insertions(+), 9 deletions(-) diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx index cde61f3..363ed2e 100644 --- a/src/app/play/page.tsx +++ b/src/app/play/page.tsx @@ -1594,6 +1594,63 @@ function PlayPageClient() { initSkipConfig(); }, []); + // 监听 URL 参数变化,处理换源和换视频(用于房员跟随房主操作) + useEffect(() => { + const urlSource = searchParams.get('source'); + const urlId = searchParams.get('id'); + + // 只在URL参数存在且与当前状态不同时才处理 + if (urlSource && urlId && (urlSource !== currentSource || urlId !== currentId)) { + console.log('[PlayPage] Detected source/id change from URL:', { + urlSource, + urlId, + currentSource, + currentId + }); + + // 检查新的source和id是否在可用源列表中 + const targetSource = availableSources.find( + (source) => source.source === urlSource && source.id === urlId + ); + + if (targetSource) { + console.log('[PlayPage] Found matching source in available sources, updating...'); + + // 记录当前播放进度 + const currentPlayTime = artPlayerRef.current?.currentTime || 0; + + // 获取URL中的episode参数 + const episodeParam = searchParams.get('episode'); + const targetEpisode = episodeParam ? parseInt(episodeParam, 10) - 1 : 0; + + // 更新视频源信息 + setCurrentSource(urlSource); + setCurrentId(urlId); + setVideoTitle(targetSource.title); + setVideoYear(targetSource.year); + setVideoCover(targetSource.poster); + setVideoDoubanId(targetSource.douban_id || 0); + setDetail(targetSource); + + // 更新集数 + if (targetEpisode >= 0 && targetEpisode < targetSource.episodes.length) { + setCurrentEpisodeIndex(targetEpisode); + + // 如果是同一集,保存播放进度以便恢复 + if (targetEpisode === currentEpisodeIndex && currentPlayTime > 1) { + resumeTimeRef.current = currentPlayTime; + } else { + resumeTimeRef.current = null; + } + } + } else { + console.log('[PlayPage] Source not found in available sources, reloading page...'); + // 如果新源不在可用列表中,强制刷新页面重新加载 + window.location.reload(); + } + } + }, [searchParams, currentSource, currentId, availableSources, currentEpisodeIndex]); + // 处理换源 const handleSourceChange = async ( newSource: string, diff --git a/src/hooks/usePlaySync.ts b/src/hooks/usePlaySync.ts index 55d9a66..df3f0ad 100644 --- a/src/hooks/usePlaySync.ts +++ b/src/hooks/usePlaySync.ts @@ -248,8 +248,8 @@ export function usePlaySync({ const url = `/play?${params.toString()}`; console.log('[PlaySync] Member redirecting to:', url); - // 使用 window.location.href 强制完整刷新页面,确保播放器重新加载 - window.location.href = url; + // 使用 router.push 进行导航,支持在同一页面更新参数 + router.push(url); }; socket.on('play:update', handlePlayUpdate); @@ -367,18 +367,45 @@ export function usePlaySync({ }; }, [socket, currentRoom, artPlayerRef, watchRoom, broadcastPlayState, isInRoom, playerReady]); + // 使用ref跟踪上一次的值,用于检测真正的变化 + const lastBroadcastRef = useRef<{ + videoId: string; + source: string; + episode: number; + } | null>(null); + // 房主:监听视频/集数/源变化并广播 useEffect(() => { - if (!isOwner || !socket || !currentRoom || !isInRoom || !watchRoom) return; + if (!isOwner || !socket || !currentRoom || !isInRoom || !watchRoom) { + // 如果不是房主或不在房间,重置跟踪 + lastBroadcastRef.current = null; + return; + } if (!videoId || !videoUrl) return; - console.log('[PlaySync] Video params changed, will broadcast after delay:', { + const currentState = { videoId, - currentSource, - currentEpisode + source: currentSource, + episode: currentEpisode || 1, + }; + + // 检查是否需要广播 + const shouldBroadcast = !lastBroadcastRef.current || + lastBroadcastRef.current.videoId !== currentState.videoId || + lastBroadcastRef.current.source !== currentState.source || + lastBroadcastRef.current.episode !== currentState.episode; + + if (!shouldBroadcast) { + console.log('[PlaySync] No change detected, skipping broadcast'); + return; + } + + console.log('[PlaySync] Detected change, will broadcast:', { + from: lastBroadcastRef.current, + to: currentState }); - // 延迟广播,避免初始化时触发 + // 延迟广播,确保页面已经稳定 const timer = setTimeout(() => { const state: PlayState = { type: 'play', @@ -395,10 +422,66 @@ export function usePlaySync({ console.log('[PlaySync] Broadcasting play:change:', state); watchRoom.changeVideo(state); - }, 1000); // 1秒延迟,给页面足够时间初始化 + + // 更新跟踪值 + lastBroadcastRef.current = currentState; + }, 500); // 减少延迟到500ms return () => clearTimeout(timer); - }, [isOwner, socket, currentRoom, isInRoom, watchRoom, videoId, currentEpisode, currentSource]); + }, [isOwner, socket, currentRoom, isInRoom, watchRoom, videoId, currentEpisode, currentSource, videoUrl, videoName, videoYear, searchTitle, artPlayerRef]); + + // 房主:加入房间时立即广播当前播放状态 + const lastRoomStateRef = useRef<{ isOwner: boolean; roomId: string | null }>({ isOwner: false, roomId: null }); + + useEffect(() => { + const currentRoomId = currentRoom?.id || null; + const prevRoomState = lastRoomStateRef.current; + + // 检测是否刚成为房主或刚加入房间 + const justBecameOwner = !prevRoomState.isOwner && isOwner; + const justJoinedRoom = !prevRoomState.roomId && currentRoomId; + + // 更新ref + lastRoomStateRef.current = { isOwner, roomId: currentRoomId }; + + if (!isOwner || !socket || !currentRoom || !isInRoom || !watchRoom) return; + if (!videoId || !videoUrl) return; + if (!justBecameOwner && !justJoinedRoom) return; + + console.log('[PlaySync] Owner joined room, broadcasting current state immediately:', { + justBecameOwner, + justJoinedRoom + }); + + // 立即广播当前状态 + const state: PlayState = { + type: 'play', + url: videoUrl, + currentTime: artPlayerRef.current?.currentTime || 0, + isPlaying: artPlayerRef.current?.playing || false, + videoId, + videoName, + videoYear, + searchTitle, + episode: currentEpisode, + source: currentSource, + }; + + // 短暂延迟确保房间连接已稳定 + const timer = setTimeout(() => { + console.log('[PlaySync] Broadcasting play:change on room join:', state); + watchRoom.changeVideo(state); + + // 同时更新跟踪值,避免立即重复广播 + lastBroadcastRef.current = { + videoId, + source: currentSource, + episode: currentEpisode || 1, + }; + }, 300); + + return () => clearTimeout(timer); + }, [isOwner, currentRoom, socket, isInRoom, watchRoom, videoId, videoUrl, videoName, videoYear, searchTitle, currentEpisode, currentSource, artPlayerRef]); return { isInRoom,