import { useEffect, useState, useRef } from "react"; import { createWebRTCService, type Participant, type ChatMessage, } from "../lib/webrtc"; let webrtcServiceInstance: ReturnType | null = null; let isInitializing = false; export const useWebRTC = (roomId?: string, autoJoin = false) => { const callbacksRegisteredRef = useRef(false); const hasJoinedRoomRef = useRef(false); const [localStream, setLocalStream] = useState(null); const [participants, setParticipants] = useState([]); const [isAudioMuted, setIsAudioMuted] = useState(false); const [isVideoMuted, setIsVideoMuted] = useState(false); const [chatMessages, setChatMessages] = useState([]); const [isConnected, setIsConnected] = useState(false); const [isInitialized, setIsInitialized] = useState(false); useEffect(() => { // Создаем сервис только один раз (синглтон) if (!webrtcServiceInstance) { webrtcServiceInstance = createWebRTCService({}); } // Инициализируем состояние из существующего сервиса const existingStream = webrtcServiceInstance.getLocalStream(); if (existingStream) { console.log("[useWebRTC] Initializing with existing local stream"); setLocalStream(existingStream); setIsInitialized(true); } const existingParticipants = webrtcServiceInstance.getParticipants(); console.log("[useWebRTC] Component mounted, existing participants:", existingParticipants.length); if (existingParticipants.length > 0) { console.log("[useWebRTC] Initializing with participants:", existingParticipants.map(p => p.id)); setParticipants(existingParticipants); } const existingMessages = webrtcServiceInstance.getChatMessages(); if (existingMessages.length > 0) { console.log("[useWebRTC] Initializing with existing messages:", existingMessages.length); setChatMessages(existingMessages); } // Добавляем коллбэки только один раз для этого компонента if (callbacksRegisteredRef.current) { return; } callbacksRegisteredRef.current = true; const removeCallbacks = webrtcServiceInstance.addCallbacks({ onLocalStreamReady: (stream) => { console.log("[useWebRTC] Local stream ready"); setLocalStream(stream); setIsInitialized(true); }, onRemoteStreamReady: (participantId, stream) => { console.log("[useWebRTC] onRemoteStreamReady called for:", participantId); setParticipants((prev) => { const existing = prev.find((p) => p.id === participantId); if (existing) { // Если поток уже тот же самый, не обновляем if (existing.stream === stream) { console.log("[useWebRTC] Stream already set for:", participantId); return prev; } console.log("[useWebRTC] Updating stream for existing participant:", participantId); return prev.map((p) => p.id === participantId ? { ...p, stream } : p ); } else { console.log("[useWebRTC] Adding new participant with stream:", participantId); return [...prev, { id: participantId, stream }]; } }); }, onRoomParticipants: () => { setIsConnected(true); }, onParticipantJoined: (participant) => { console.log("[useWebRTC] onParticipantJoined called for:", participant.id); setParticipants((prev) => { if (prev.find((p) => p.id === participant.id)) { console.log("[useWebRTC] Participant already in list, skipping"); return prev; } console.log("[useWebRTC] Adding participant to state"); return [...prev, participant]; }); }, onParticipantLeft: (participantId) => { setParticipants((prev) => prev.filter((p) => p.id !== participantId)); }, onParticipantAudioToggle: (participantId, isEnabled) => { console.log(`[useWebRTC] Audio toggle for ${participantId}: ${isEnabled}`); setParticipants((prev) => { const participant = prev.find((p) => p.id === participantId); const newMutedState = !isEnabled; // Только обновляем, если значение действительно изменилось if (participant && participant.isMuted === newMutedState) { return prev; } return prev.map((p) => p.id === participantId ? { ...p, isMuted: newMutedState } : p ); }); }, onParticipantVideoToggle: (participantId, isEnabled) => { console.log(`[useWebRTC] Video toggle for ${participantId}: ${isEnabled}`); setParticipants((prev) => { const participant = prev.find((p) => p.id === participantId); const newVideoOffState = !isEnabled; // Только обновляем, если значение действительно изменилось if (participant && participant.isVideoOff === newVideoOffState) { return prev; } return prev.map((p) => p.id === participantId ? { ...p, isVideoOff: newVideoOffState } : p ); }); }, onParticipantSpeakingChange: (participantId, isSpeaking) => { setParticipants((prev) => { const participant = prev.find((p) => p.id === participantId); // Только обновляем, если значение действительно изменилось if (participant && participant.isSpeaking === isSpeaking) { return prev; } return prev.map((p) => p.id === participantId ? { ...p, isSpeaking } : p ); }); }, onChatMessage: (message) => { console.log("[useWebRTC] onChatMessage called:", message); setChatMessages((prev) => [...prev, message]); }, onError: (error) => { console.error("[useWebRTC] Error:", error); }, }); const initWebRTC = async () => { if (!webrtcServiceInstance || isInitializing) { return; } // Проверяем, есть ли уже localStream if (webrtcServiceInstance.getLocalStream()) { setIsInitialized(true); return; } try { isInitializing = true; const stream = await webrtcServiceInstance.initializeLocalStream(); // Даже если stream === null (пользователь отказался от разрешений), // считаем инициализацию завершенной if (stream === null) { console.log("[useWebRTC] Initialized without local stream (user denied permissions)"); setIsInitialized(true); } } catch (error) { console.error("[useWebRTC] Initialization error:", error); // Даже при ошибке разрешаем продолжить setIsInitialized(true); } finally { isInitializing = false; } }; initWebRTC(); // Cleanup при размонтировании компонента return () => { callbacksRegisteredRef.current = false; removeCallbacks(); }; }, []); // Пустой массив зависимостей - эффект срабатывает только при монтировании // Отдельный эффект для присоединения к комнате // ВАЖНО: Присоединяемся только ПОСЛЕ инициализации localStream! useEffect(() => { if ( !webrtcServiceInstance || !autoJoin || !roomId || hasJoinedRoomRef.current || !isInitialized ) { return; } const joinRoomAsync = async () => { await webrtcServiceInstance!.joinRoom(roomId); hasJoinedRoomRef.current = true; }; joinRoomAsync(); }, [roomId, autoJoin, isInitialized]); const toggleAudio = () => { if (!webrtcServiceInstance) return; const newState = webrtcServiceInstance.toggleAudio(); setIsAudioMuted(!newState); }; const toggleVideo = () => { if (!webrtcServiceInstance) return; const newState = webrtcServiceInstance.toggleVideo(); setIsVideoMuted(!newState); }; const sendMessage = (content: string) => { if (!webrtcServiceInstance) return; webrtcServiceInstance.sendChatMessage(content); }; const joinRoom = async (roomId: string) => { if (!webrtcServiceInstance) return; await webrtcServiceInstance.joinRoom(roomId); setIsConnected(true); }; const leaveRoom = () => { if (!webrtcServiceInstance) return; webrtcServiceInstance.leaveRoom(); setIsConnected(false); setParticipants([]); }; const updateSpeakingState = (isSpeaking: boolean) => { if (!webrtcServiceInstance) return; webrtcServiceInstance.updateSpeakingState(isSpeaking); }; return { localStream, participants, isAudioMuted, isVideoMuted, chatMessages, isConnected, isInitialized, currentUserId: webrtcServiceInstance?.getCurrentUserId() || "", toggleAudio, toggleVideo, sendMessage, updateSpeakingState, joinRoom, leaveRoom, }; };