import { useMemo } from "react"; import UserCamera from "./ui/UserCamera"; import UserDevicesControls from "./ui/UserDevicesControls"; import DraggableContainer from "./DraggableContainer"; import { useWebRTC } from "../hooks/useWebRTC"; import clsx from "clsx"; interface SessionUsersPanelProps { roomId: string; autoJoin?: boolean; mode?: "full" | "mini"; } function SessionUsersPanel({ roomId, autoJoin = false, mode = "full", }: SessionUsersPanelProps) { const { localStream, participants, isAudioMuted: isLocalAudioMuted, isVideoMuted: isLocalVideoMuted, toggleAudio, toggleVideo, updateSpeakingState, } = useWebRTC(roomId, autoJoin); const hasLocalStream = localStream !== null; // Callback для отправки состояния speaking const handleSpeakingChange = (isSpeaking: boolean) => { updateSpeakingState?.(isSpeaking); }; // Вычисляем количество камер для grid - мемоизируем для избежания лишних вычислений const activeCamerasCount = useMemo( () => (localStream ? 1 : 0) + participants.filter( (p) => p.stream != null && p.stream.getTracks().length > 0 ).length, [localStream, participants] ); // Определяем количество колонок в зависимости от количества камер // 1-2 камеры: 1 колонка (друг под другом), 3-4: 2 колонки, 5-9: 3 колонки, 10-16: 4 колонки, 17-25: 5 колонок const gridColumns = useMemo(() => { if (activeCamerasCount <= 2) return 1; if (activeCamerasCount <= 4) return 2; if (activeCamerasCount <= 9) return 3; if (activeCamerasCount <= 16) return 4; return 5; }, [activeCamerasCount]); // Вычисляем количество рядов для правильного расчета высоты const gridRows = useMemo( () => Math.ceil(activeCamerasCount / gridColumns), [activeCamerasCount, gridColumns] ); // Фильтруем участников с активными потоками - мемоизируем для избежания лишних фильтраций const activeParticipants = useMemo( () => participants.filter( (participant) => participant.stream != null && participant.stream.getTracks().length > 0 ), [participants] ); // Рендерим камеры const camerasContent = ( <> {/* Локальная камера пользователя - показываем только если есть разрешение */} {localStream && ( console.log("Toggle control")} onSpeakingChange={handleSpeakingChange} /> )} {/* Камеры удаленных участников - показываем только если есть поток с активными треками */} {activeParticipants.map((participant) => ( console.log(`Mute user ${participant.id}`)} onVideoOff={() => console.log(`Video off user ${participant.id}`)} onCanControl={() => console.log(`Can control user ${participant.id}`) } /> ))} ); // Для режима full используем DraggableContainer if (mode === "full") { return ( = 640 ? "bottom-right" : "top-right"} padding="1.111vw" className="z-[999] flex gap-4" > {camerasContent} ); } // Для режима mini используем flex-обертку для центрирования и внутри grid return (
{camerasContent}
); } export default SessionUsersPanel;