weblive观影室
This commit is contained in:
@@ -3,6 +3,8 @@
|
|||||||
import { AlertTriangle,Radio } from 'lucide-react';
|
import { AlertTriangle,Radio } from 'lucide-react';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { useWebLiveSync } from '@/hooks/useWebLiveSync';
|
||||||
|
|
||||||
import PageLayout from '@/components/PageLayout';
|
import PageLayout from '@/components/PageLayout';
|
||||||
|
|
||||||
let Artplayer: any = null;
|
let Artplayer: any = null;
|
||||||
@@ -27,6 +29,22 @@ export default function WebLivePage() {
|
|||||||
const [selectedPlatform, setSelectedPlatform] = useState<string | null>(null);
|
const [selectedPlatform, setSelectedPlatform] = useState<string | null>(null);
|
||||||
const [isWebLiveEnabled, setIsWebLiveEnabled] = useState<boolean | null>(null);
|
const [isWebLiveEnabled, setIsWebLiveEnabled] = useState<boolean | null>(null);
|
||||||
|
|
||||||
|
// 观影室同步功能
|
||||||
|
const webLiveSync = useWebLiveSync({
|
||||||
|
currentSourceKey: currentSource?.key || '',
|
||||||
|
currentSourceName: currentSource?.name || '',
|
||||||
|
currentSourcePlatform: currentSource?.platform || '',
|
||||||
|
currentSourceRoomId: currentSource?.roomId || '',
|
||||||
|
onSourceChange: (sourceKey, platform, roomId) => {
|
||||||
|
// 房员接收到直播源切换指令
|
||||||
|
if (!sources || !Array.isArray(sources)) return;
|
||||||
|
const source = sources.find(s => s.key === sourceKey);
|
||||||
|
if (source) {
|
||||||
|
handleSourceClick(source);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const meta = document.createElement('meta');
|
const meta = document.createElement('meta');
|
||||||
meta.name = 'referrer';
|
meta.name = 'referrer';
|
||||||
@@ -419,7 +437,7 @@ export default function WebLivePage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 外部播放器按钮 */}
|
{/* 外部播放器按钮 */}
|
||||||
{currentSource && (
|
{currentSource && !webLiveSync.isInRoom && (
|
||||||
<div className='mt-3 px-2 lg:flex-shrink-0 flex justify-end'>
|
<div className='mt-3 px-2 lg:flex-shrink-0 flex justify-end'>
|
||||||
<div className='bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm rounded-lg p-2 border border-gray-200/50 dark:border-gray-700/50 w-full lg:w-auto overflow-x-auto'>
|
<div className='bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm rounded-lg p-2 border border-gray-200/50 dark:border-gray-700/50 w-full lg:w-auto overflow-x-auto'>
|
||||||
<div className='flex gap-1.5 justify-end lg:flex-wrap items-center'>
|
<div className='flex gap-1.5 justify-end lg:flex-wrap items-center'>
|
||||||
@@ -653,7 +671,8 @@ export default function WebLivePage() {
|
|||||||
<button
|
<button
|
||||||
key={source.key}
|
key={source.key}
|
||||||
onClick={() => handleSourceClick(source)}
|
onClick={() => handleSourceClick(source)}
|
||||||
className={`w-full p-3 rounded-lg text-left transition-all duration-200 ${isActive ? 'bg-green-100 dark:bg-green-900/30 border border-green-300 dark:border-green-700' : 'hover:bg-gray-100 dark:hover:bg-gray-700'}`}
|
disabled={webLiveSync.shouldDisableControls}
|
||||||
|
className={`w-full p-3 rounded-lg text-left transition-all duration-200 ${isActive ? 'bg-green-100 dark:bg-green-900/30 border border-green-300 dark:border-green-700' : 'hover:bg-gray-100 dark:hover:bg-gray-700'} ${webLiveSync.shouldDisableControls ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||||
>
|
>
|
||||||
<div className='flex items-center gap-3'>
|
<div className='flex items-center gap-3'>
|
||||||
<div className='w-10 h-10 bg-gray-300 dark:bg-gray-700 rounded-lg flex items-center justify-center flex-shrink-0'>
|
<div className='w-10 h-10 bg-gray-300 dark:bg-gray-700 rounded-lg flex items-center justify-center flex-shrink-0'>
|
||||||
@@ -688,7 +707,8 @@ export default function WebLivePage() {
|
|||||||
<button
|
<button
|
||||||
key={platform}
|
key={platform}
|
||||||
onClick={() => handlePlatformClick(platform)}
|
onClick={() => handlePlatformClick(platform)}
|
||||||
className='w-full flex items-start gap-3 px-2 py-3 rounded-lg bg-gray-200/50 dark:bg-white/10 hover:bg-gray-300/50 dark:hover:bg-white/20 transition-all duration-200 cursor-pointer'
|
disabled={webLiveSync.shouldDisableControls}
|
||||||
|
className={`w-full flex items-start gap-3 px-2 py-3 rounded-lg bg-gray-200/50 dark:bg-white/10 hover:bg-gray-300/50 dark:hover:bg-white/20 transition-all duration-200 cursor-pointer ${webLiveSync.shouldDisableControls ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||||
>
|
>
|
||||||
<div className='w-12 h-12 bg-gray-200 dark:bg-gray-600 rounded-lg flex items-center justify-center flex-shrink-0 overflow-hidden'>
|
<div className='w-12 h-12 bg-gray-200 dark:bg-gray-600 rounded-lg flex items-center justify-center flex-shrink-0 overflow-hidden'>
|
||||||
{platform === 'huya' ? (
|
{platform === 'huya' ? (
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// React Hook for Web Live Page Synchronization
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
import { useWatchRoomContextSafe } from '@/components/WatchRoomProvider';
|
||||||
|
|
||||||
|
import type { LiveState } from '@/types/watch-room';
|
||||||
|
|
||||||
|
interface UseWebLiveSyncOptions {
|
||||||
|
currentSourceKey: string;
|
||||||
|
currentSourceName: string;
|
||||||
|
currentSourcePlatform: string;
|
||||||
|
currentSourceRoomId: string;
|
||||||
|
onSourceChange?: (sourceKey: string, platform: string, roomId: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useWebLiveSync({
|
||||||
|
currentSourceKey,
|
||||||
|
currentSourceName,
|
||||||
|
currentSourcePlatform,
|
||||||
|
currentSourceRoomId,
|
||||||
|
onSourceChange,
|
||||||
|
}: UseWebLiveSyncOptions) {
|
||||||
|
const watchRoom = useWatchRoomContextSafe();
|
||||||
|
const syncingRef = useRef(false); // 防止循环同步
|
||||||
|
|
||||||
|
// 检查是否在房间内
|
||||||
|
const isInRoom = !!(watchRoom && watchRoom.currentRoom);
|
||||||
|
const isOwner = watchRoom?.isOwner || false;
|
||||||
|
const currentRoom = watchRoom?.currentRoom;
|
||||||
|
const socket = watchRoom?.socket;
|
||||||
|
|
||||||
|
// 房主:广播直播源切换
|
||||||
|
const broadcastSourceChange = useCallback(() => {
|
||||||
|
if (!isOwner || !socket || syncingRef.current || !watchRoom) return;
|
||||||
|
|
||||||
|
if (!currentSourceKey || !currentSourceName || !currentSourcePlatform || !currentSourceRoomId) return;
|
||||||
|
|
||||||
|
// 使用 channelId 存储 sourceKey,channelUrl 存储 platform:roomId
|
||||||
|
const state: LiveState = {
|
||||||
|
type: 'live',
|
||||||
|
channelId: currentSourceKey,
|
||||||
|
channelName: currentSourceName,
|
||||||
|
channelUrl: `${currentSourcePlatform}:${currentSourceRoomId}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[WebLiveSync] Broadcasting source change:', state);
|
||||||
|
watchRoom.changeLiveChannel(state);
|
||||||
|
}, [isOwner, socket, currentSourceKey, currentSourceName, currentSourcePlatform, currentSourceRoomId, watchRoom]);
|
||||||
|
|
||||||
|
// 房员:接收并同步房主的直播源切换
|
||||||
|
useEffect(() => {
|
||||||
|
if (!socket || !currentRoom || isOwner || !isInRoom) return;
|
||||||
|
|
||||||
|
const handleLiveChange = (state: LiveState) => {
|
||||||
|
if (syncingRef.current) return;
|
||||||
|
|
||||||
|
console.log('[WebLiveSync] Received source change:', state);
|
||||||
|
syncingRef.current = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 解析 channelUrl 获取 platform 和 roomId
|
||||||
|
const [platform, roomId] = state.channelUrl.split(':');
|
||||||
|
|
||||||
|
// 调用回调函数来切换直播源
|
||||||
|
if (onSourceChange && platform && roomId) {
|
||||||
|
onSourceChange(state.channelId, platform, roomId);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setTimeout(() => {
|
||||||
|
syncingRef.current = false;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.on('live:change', handleLiveChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.off('live:change', handleLiveChange);
|
||||||
|
};
|
||||||
|
}, [socket, currentRoom, isOwner, onSourceChange, isInRoom]);
|
||||||
|
|
||||||
|
// 房主:当直播源改变时自动广播
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOwner || !currentSourceKey || !isInRoom) return;
|
||||||
|
|
||||||
|
// 防止初始化时广播
|
||||||
|
if (syncingRef.current) return;
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
broadcastSourceChange();
|
||||||
|
}, 500); // 延迟广播,避免频繁触发
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [isOwner, currentSourceKey, currentSourcePlatform, currentSourceRoomId, broadcastSourceChange, isInRoom]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isInRoom,
|
||||||
|
isOwner,
|
||||||
|
shouldDisableControls: isInRoom && !isOwner, // 房员禁用直播源切换
|
||||||
|
broadcastSourceChange, // 导出供手动调用
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user