import { useSocket } from '@/context/SocketProvider'; import { useRouter } from 'next/router'; import React, { useCallback, useEffect, useState } from 'react' import peer from '@/service/peer'; import { motion } from 'framer-motion'; import { Video, Phone, Users, Send, ArrowLeft } from 'lucide-react'; import VideoPlayer from '@/components/VideoPlayer'; import CallHandleButtons from '@/components/CallHandleButtons'; const RoomPage = () => { const socket = useSocket(); const router = useRouter(); const { slug } = router.query; const [remoteSocketId, setRemoteSocketId] = useState(null); const [myStream, setMyStream] = useState(null); const [remoteStream, setRemoteStream] = useState(null); const [isAudioMute, setIsAudioMute] = useState(false); const [isVideoOnHold, setIsVideoOnHold] = useState(false); const [callButton, setCallButton] = useState(true); const [isSendButtonVisible, setIsSendButtonVisible] = useState(true); const [isConnecting, setIsConnecting] = useState(false); const [hasJoinedRoom, setHasJoinedRoom] = useState(false); // Check if user came from lobby (has proper session) or direct navigation useEffect(() => { // Check if user has a valid session or email stored (you might want to implement proper session management) const hasValidSession = localStorage.getItem('userEmail') || document.referrer.includes('/'); if (!hasValidSession && typeof window !== 'undefined') { // User navigated directly to room page without going through lobby console.warn('Direct navigation to room detected, redirecting to lobby'); router.push('/'); return; } // If user has a session, mark as joined setHasJoinedRoom(true); }, [router]); // Store user email when they join from lobby useEffect(() => { socket.on("room:join", (data) => { if (typeof window !== 'undefined') { localStorage.setItem('userEmail', data.email); localStorage.setItem('currentRoom', data.room); } }); // Listen for socket errors and provide user feedback socket.on("error", (error) => { console.error('Socket error:', error); alert(`Connection Error: ${error.message || 'Something went wrong. Please try again.'}`); // Redirect to lobby on error router.push('/'); }); return () => { socket.off("room:join"); socket.off("error"); }; }, [socket, router]); const handleUserJoined = useCallback(({ email, id }) => { console.log(`User ${email} joined the room!`); setRemoteSocketId(id); }, []); const handleUserLeft = useCallback(({ email }) => { console.log(`User ${email} left the room`); setRemoteSocketId(null); setRemoteStream(null); }, []); const handleIncomingCall = useCallback(async ({ from, offer }) => { setRemoteSocketId(from); setIsConnecting(true); try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }); setMyStream(stream); const ans = await peer.getAnswer(offer); socket.emit("call:accepted", { to: from, ans }); } catch (error) { console.error('Error handling incoming call:', error); setIsConnecting(false); } }, [socket]); const sendStreams = useCallback(() => { if (myStream) { for (const track of myStream.getTracks()) { peer.peer.addTrack(track, myStream); } setIsSendButtonVisible(false); } }, [myStream]); const handleCallAccepted = useCallback(({ from, ans }) => { peer.setLocalDescription(ans); console.log("Call Accepted"); setIsConnecting(false); sendStreams(); }, [sendStreams]); const handleNegoNeededIncoming = useCallback(async ({ from, offer }) => { const ans = await peer.getAnswer(offer); socket.emit("peer:nego:done", { to: from, ans }); }, [socket]); const handleNegoNeeded = useCallback(async () => { const offer = await peer.getOffer(); socket.emit("peer:nego:needed", { offer, to: remoteSocketId }); }, [remoteSocketId, socket]); const handleNegoFinal = useCallback(async ({ ans }) => { await peer.setLocalDescription(ans); }, []) useEffect(() => { peer.peer.addEventListener('negotiationneeded', handleNegoNeeded); return () => { peer.peer.removeEventListener('negotiationneeded', handleNegoNeeded); } }, [handleNegoNeeded]); useEffect(() => { peer.peer.addEventListener('track', async ev => { const remoteStream = ev.streams; console.log("GOT TRACKS!"); setRemoteStream(remoteStream[0]); setIsConnecting(false); }) }, []) useEffect(() => { socket.on("user:joined", handleUserJoined); socket.on("user:left", handleUserLeft); socket.on("incoming:call", handleIncomingCall); socket.on("call:accepted", handleCallAccepted); socket.on("peer:nego:needed", handleNegoNeededIncoming); socket.on("peer:nego:final", handleNegoFinal); return () => { socket.off("user:joined", handleUserJoined); socket.off("user:left", handleUserLeft); socket.off("incoming:call", handleIncomingCall); socket.off("call:accepted", handleCallAccepted); socket.off("peer:nego:needed", handleNegoNeededIncoming); socket.off("peer:nego:final", handleNegoFinal); }; }, [ socket, handleUserJoined, handleUserLeft, handleIncomingCall, handleCallAccepted, handleNegoNeededIncoming, handleNegoFinal ]); useEffect(() => { socket.on("call:end", ({ from }) => { if (from === remoteSocketId) { peer.peer.close(); if (myStream) { myStream.getTracks().forEach(track => track.stop()); setMyStream(null); } setRemoteStream(null); setRemoteSocketId(null); setCallButton(true); setIsSendButtonVisible(true); setIsConnecting(false); } }); return () => { socket.off("call:end"); } }, [remoteSocketId, myStream, socket]); useEffect(() => { socket.on("call:initiated", ({ from }) => { if (from === remoteSocketId) { setCallButton(false); } }); return () => { socket.off("call:initiated"); } }, [socket, remoteSocketId]); const handleCallUser = useCallback(async () => { setIsConnecting(true); try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }); if (isAudioMute) { const audioTracks = stream.getAudioTracks(); audioTracks.forEach(track => track.enabled = false); } if (isVideoOnHold) { const videoTracks = stream.getVideoTracks(); videoTracks.forEach(track => track.enabled = false); } const offer = await peer.getOffer(); socket.emit("user:call", { to: remoteSocketId, offer }) setMyStream(stream); setCallButton(false); socket.emit("call:initiated", { to: remoteSocketId }); } catch (error) { console.error('Error starting call:', error); setIsConnecting(false); } }, [remoteSocketId, socket, isAudioMute, isVideoOnHold]); const handleToggleAudio = () => { if (myStream) { const audioTracks = myStream.getAudioTracks(); audioTracks.forEach(track => track.enabled = !track.enabled); setIsAudioMute(!isAudioMute); } }; const handleToggleVideo = () => { if (myStream) { const videoTracks = myStream.getVideoTracks(); videoTracks.forEach(track => track.enabled = !track.enabled); setIsVideoOnHold(!isVideoOnHold); } } const handleEndCall = useCallback(() => { peer.peer.close(); if (myStream) { myStream.getTracks().forEach(track => track.stop()); setMyStream(null); } setRemoteStream(null); setCallButton(true); setIsSendButtonVisible(true); setIsConnecting(false); if (remoteSocketId) { socket.emit("call:end", { to: remoteSocketId }); } setRemoteSocketId(null); }, [myStream, remoteSocketId, socket]); const handleGoBack = () => { handleEndCall(); // Clear session data when leaving room if (typeof window !== 'undefined') { localStorage.removeItem('userEmail'); localStorage.removeItem('currentRoom'); } router.push('/'); }; return ( <> {!hasJoinedRoom ? (
Verifying access...
{isConnecting ? ( Connecting... ) : remoteSocketId ? ( "🟢 Connected with remote user" ) : ( "🟡 Waiting for someone to join..." )}
Waiting for remote video...
Share this room ID with someone to start a call