From dd306bcebba609f7722ddecf4b5dea6234d8ffdc Mon Sep 17 00:00:00 2001 From: mtvpls Date: Tue, 3 Feb 2026 21:21:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A7=82=E5=BD=B1=E5=AE=A4we?= =?UTF-8?q?blive=E8=B7=B3=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/watch-room/page.tsx | 23 ++++++++++-- src/app/web-live/page.tsx | 75 ++++++++++++++++++++++++++++++++++--- 2 files changed, 89 insertions(+), 9 deletions(-) diff --git a/src/app/watch-room/page.tsx b/src/app/watch-room/page.tsx index cad5a12..a824fb6 100644 --- a/src/app/watch-room/page.tsx +++ b/src/app/watch-room/page.tsx @@ -154,7 +154,6 @@ export default function WatchRoomPage() { if (!currentRoom || isOwner) return; const handlePlayChange = (state: any) => { - console.log('[WatchRoom] Member following owner - play:change event'); if (state.type === 'play') { const params = new URLSearchParams({ id: state.videoId, @@ -171,9 +170,17 @@ export default function WatchRoomPage() { }; const handleLiveChange = (state: any) => { - console.log('[WatchRoom] Member following owner - live:change event'); if (state.type === 'live') { - router.push(`/live?id=${state.channelId}`); + // 判断是否为 weblive 格式(channelUrl 包含 platform:roomId) + if (state.channelUrl && state.channelUrl.includes(':')) { + // weblive 格式,导航到 web-live 页面 + // channelId 是 sourceKey,channelUrl 是 platform:roomId + const [platform, roomId] = state.channelUrl.split(':'); + router.push(`/web-live?platform=${platform}&roomId=${roomId}`); + } else { + // 普通 live 格式,导航到 live 页面 + router.push(`/live?id=${state.channelId}`); + } } }; @@ -264,7 +271,15 @@ export default function WatchRoomPage() { if (state.searchTitle) params.set('stitle', state.searchTitle); router.push(`/play?${params.toString()}`); } else if (state.type === 'live') { - router.push(`/live?id=${state.channelId}`); + // 判断是否为 weblive 格式(channelUrl 包含 platform:roomId) + if (state.channelUrl && state.channelUrl.includes(':')) { + // weblive 格式,导航到 web-live 页面 + const [platform, roomId] = state.channelUrl.split(':'); + router.push(`/web-live?platform=${platform}&roomId=${roomId}`); + } else { + // 普通 live 格式,导航到 live 页面 + router.push(`/live?id=${state.channelId}`); + } } }} className="px-6 py-2 bg-white text-blue-600 font-medium rounded-lg hover:bg-white/90 transition-colors whitespace-nowrap" diff --git a/src/app/web-live/page.tsx b/src/app/web-live/page.tsx index 1a9dceb..01c7af2 100644 --- a/src/app/web-live/page.tsx +++ b/src/app/web-live/page.tsx @@ -1,17 +1,21 @@ 'use client'; import { AlertTriangle,Radio } from 'lucide-react'; +import { useRouter, useSearchParams } from 'next/navigation'; import { useEffect, useRef, useState } from 'react'; import { useWebLiveSync } from '@/hooks/useWebLiveSync'; import PageLayout from '@/components/PageLayout'; +import { useWatchRoomContextSafe } from '@/components/WatchRoomProvider'; let Artplayer: any = null; let Hls: any = null; let flvjs: any = null; export default function WebLivePage() { + const searchParams = useSearchParams(); + const router = useRouter(); const artRef = useRef(null); const artPlayerRef = useRef(null); const [loading, setLoading] = useState(true); @@ -28,6 +32,8 @@ export default function WebLivePage() { const [errorMessage, setErrorMessage] = useState(null); const [selectedPlatform, setSelectedPlatform] = useState(null); const [isWebLiveEnabled, setIsWebLiveEnabled] = useState(null); + const [librariesLoaded, setLibrariesLoaded] = useState(false); + const hasAutoLoadedRef = useRef(false); // 防止重复自动加载 // 观影室同步功能 const webLiveSync = useWebLiveSync({ @@ -52,9 +58,14 @@ export default function WebLivePage() { document.head.appendChild(meta); if (typeof window !== 'undefined') { - import('artplayer').then(mod => { Artplayer = mod.default; }); - import('hls.js').then(mod => { Hls = mod.default; }); - import('flv.js').then(mod => { flvjs = mod.default; }); + // 异步加载所有必需的库 + Promise.all([ + import('artplayer').then(mod => { Artplayer = mod.default; }), + import('hls.js').then(mod => { Hls = mod.default; }), + import('flv.js').then(mod => { flvjs = mod.default; }) + ]).then(() => { + setLibrariesLoaded(true); + }); // 检查网络直播功能是否启用 const runtimeConfig = (window as any).RUNTIME_CONFIG; @@ -93,6 +104,41 @@ export default function WebLivePage() { } }; + // 当 sources 加载完成后,检查 URL 参数并自动加载对应的频道 + useEffect(() => { + if (!sources || sources.length === 0) return; + if (!librariesLoaded) return; // 等待库加载完成 + + // 直接从 searchParams 读取,而不是从 useState + const needLoadPlatform = searchParams.get('platform'); + const needLoadRoomId = searchParams.get('roomId'); + + if (!needLoadPlatform || !needLoadRoomId) { + hasAutoLoadedRef.current = false; // 重置标志 + return; + } + + // 检查是否已经加载了这个频道 + if (currentSource?.platform === needLoadPlatform && currentSource?.roomId === needLoadRoomId) { + return; + } + + // 防止重复加载 + if (hasAutoLoadedRef.current) { + return; + } + + hasAutoLoadedRef.current = true; + + // 查找匹配的 source + const foundSource = sources.find(s => s.platform === needLoadPlatform && s.roomId === needLoadRoomId); + if (foundSource) { + handleSourceClick(foundSource); + } else { + hasAutoLoadedRef.current = false; // 重置标志以便重试 + } + }, [sources, librariesLoaded, searchParams]); + function m3u8Loader(video: HTMLVideoElement, url: string) { if (!Hls) return; const hls = new Hls({ debug: false, enableWorker: true, lowLatencyMode: true }); @@ -212,12 +258,31 @@ export default function WebLivePage() { setIsVideoLoading(true); setErrorMessage(null); setStreamInfo(null); + + // 更新 URL 参数 + const newSearchParams = new URLSearchParams(searchParams.toString()); + newSearchParams.set('platform', source.platform); + newSearchParams.set('roomId', source.roomId); + router.replace(`/web-live?${newSearchParams.toString()}`); + try { const res = await fetch(`/api/web-live/stream?platform=${source.platform}&roomId=${source.roomId}`); if (res.ok) { const data = await res.json(); - setVideoUrl(data.url); - setOriginalVideoUrl(data.originalUrl || data.url); + + // 等待 DOM 渲染完成后再设置 videoUrl + const waitForDom = () => { + if (artRef.current) { + setVideoUrl(data.url); + setOriginalVideoUrl(data.originalUrl || data.url); + } else { + requestAnimationFrame(waitForDom); + } + }; + + // 使用 requestAnimationFrame 等待下一帧 + requestAnimationFrame(waitForDom); + // 保存主播信息 if (data.name || data.title) { setStreamInfo({