@@ -2,13 +2,16 @@ import { useSocket } from '@/context/SocketProvider';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import peer from '@/service/peer';
|
||||
import CallIcon from '@mui/icons-material/Call';
|
||||
import VideoCallIcon from '@mui/icons-material/VideoCall';
|
||||
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);
|
||||
@@ -16,36 +19,51 @@ const RoomPage = () => {
|
||||
const [isVideoOnHold, setIsVideoOnHold] = useState(false);
|
||||
const [callButton, setCallButton] = useState(true);
|
||||
const [isSendButtonVisible, setIsSendButtonVisible] = useState(true);
|
||||
const [isConnecting, setIsConnecting] = useState(false);
|
||||
|
||||
const handleUserJoined = useCallback(({ email, id }) => {
|
||||
//! console.log(`Email ${email} joined the room!`);
|
||||
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);
|
||||
//! console.log(`incoming call from ${from} with offer ${offer}`);
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: true,
|
||||
video: true
|
||||
});
|
||||
setMyStream(stream);
|
||||
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 });
|
||||
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(() => {
|
||||
for (const track of myStream.getTracks()) {
|
||||
peer.peer.addTrack(track, myStream);
|
||||
if (myStream) {
|
||||
for (const track of myStream.getTracks()) {
|
||||
peer.peer.addTrack(track, myStream);
|
||||
}
|
||||
setIsSendButtonVisible(false);
|
||||
}
|
||||
setIsSendButtonVisible(false);
|
||||
}, [myStream]);
|
||||
|
||||
const handleCallAccepted = useCallback(({ from, ans }) => {
|
||||
peer.setLocalDescription(ans);
|
||||
//! console.log("Call Accepted");
|
||||
|
||||
console.log("Call Accepted");
|
||||
setIsConnecting(false);
|
||||
sendStreams();
|
||||
}, [sendStreams]);
|
||||
|
||||
@@ -54,7 +72,6 @@ const RoomPage = () => {
|
||||
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 });
|
||||
@@ -66,23 +83,23 @@ const RoomPage = () => {
|
||||
|
||||
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);
|
||||
@@ -90,21 +107,21 @@ const RoomPage = () => {
|
||||
|
||||
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,
|
||||
handleIncomingCall,
|
||||
handleCallAccepted,
|
||||
handleNegoNeededIncoming,
|
||||
handleNegoFinal
|
||||
]);
|
||||
|
||||
}, [
|
||||
socket,
|
||||
handleUserJoined,
|
||||
handleUserLeft,
|
||||
handleIncomingCall,
|
||||
handleCallAccepted,
|
||||
handleNegoNeededIncoming,
|
||||
handleNegoFinal
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
socket.on("call:end", ({ from }) => {
|
||||
@@ -118,6 +135,9 @@ const RoomPage = () => {
|
||||
|
||||
setRemoteStream(null);
|
||||
setRemoteSocketId(null);
|
||||
setCallButton(true);
|
||||
setIsSendButtonVisible(true);
|
||||
setIsConnecting(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -126,7 +146,6 @@ const RoomPage = () => {
|
||||
}
|
||||
}, [remoteSocketId, myStream, socket]);
|
||||
|
||||
//* for disappearing call button
|
||||
useEffect(() => {
|
||||
socket.on("call:initiated", ({ from }) => {
|
||||
if (from === remoteSocketId) {
|
||||
@@ -139,46 +158,50 @@ const RoomPage = () => {
|
||||
}
|
||||
}, [socket, remoteSocketId]);
|
||||
|
||||
|
||||
const handleCallUser = useCallback(async () => {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: true,
|
||||
video: true
|
||||
});
|
||||
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 (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);
|
||||
}
|
||||
|
||||
if (isVideoOnHold) {
|
||||
const videoTracks = stream.getVideoTracks();
|
||||
videoTracks.forEach(track => track.enabled = false);
|
||||
}
|
||||
|
||||
//! create offer
|
||||
const offer = await peer.getOffer();
|
||||
//* send offer to remote user
|
||||
socket.emit("user:call", { to: remoteSocketId, offer })
|
||||
// set my stream
|
||||
setMyStream(stream);
|
||||
|
||||
//* hide the call button
|
||||
setCallButton(false);
|
||||
|
||||
//* Inform the remote user to hide their "CALL" button
|
||||
socket.emit("call:initiated", { to: remoteSocketId });
|
||||
}, [remoteSocketId, socket, isAudioMute, isVideoOnHold, callButton]);
|
||||
|
||||
}, [remoteSocketId, socket, isAudioMute, isVideoOnHold]);
|
||||
|
||||
const handleToggleAudio = () => {
|
||||
peer.toggleAudio();
|
||||
setIsAudioMute(!isAudioMute);
|
||||
if (myStream) {
|
||||
const audioTracks = myStream.getAudioTracks();
|
||||
audioTracks.forEach(track => track.enabled = !track.enabled);
|
||||
setIsAudioMute(!isAudioMute);
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleVideo = () => {
|
||||
peer.toggleVideo();
|
||||
setIsVideoOnHold(!isVideoOnHold);
|
||||
if (myStream) {
|
||||
const videoTracks = myStream.getVideoTracks();
|
||||
videoTracks.forEach(track => track.enabled = !track.enabled);
|
||||
setIsVideoOnHold(!isVideoOnHold);
|
||||
}
|
||||
}
|
||||
|
||||
const handleEndCall = useCallback(() => {
|
||||
@@ -190,6 +213,9 @@ const RoomPage = () => {
|
||||
}
|
||||
|
||||
setRemoteStream(null);
|
||||
setCallButton(true);
|
||||
setIsSendButtonVisible(true);
|
||||
setIsConnecting(false);
|
||||
|
||||
if (remoteSocketId) {
|
||||
socket.emit("call:end", { to: remoteSocketId });
|
||||
@@ -197,59 +223,168 @@ const RoomPage = () => {
|
||||
setRemoteSocketId(null);
|
||||
}, [myStream, remoteSocketId, socket]);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { slug } = router.query;
|
||||
const handleGoBack = () => {
|
||||
handleEndCall();
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='flex flex-col items-center justify-center w-screen h-screen overflow-hidden'>
|
||||
<title>Room No. {slug}</title>
|
||||
<h1 className='absolute top-0 left-0 text-5xl
|
||||
text-center font-josefin tracking-tighter mt-5 ml-5 mmd:text-xl mxs:text-sm'>Video
|
||||
<VideoCallIcon sx={{ fontSize: 50, color: 'rgb(30,220,30)' }} />
|
||||
Peers
|
||||
</h1>
|
||||
<h4 className='font-bold text-xl md:text-2xl
|
||||
mmd:text-sm mt-5 mb-4 msm:max-w-[100px] text-center'>
|
||||
{remoteSocketId ? "Connected With Remote User!" : "No One In Room"}
|
||||
</h4>
|
||||
{(remoteStream && remoteSocketId && isSendButtonVisible) &&
|
||||
<button className='bg-green-500 hover:bg-green-600' onClick={sendStreams}>
|
||||
Send Stream
|
||||
</button>
|
||||
}
|
||||
{(remoteSocketId && callButton) &&
|
||||
(
|
||||
<button className='text-xl bg-green-500 hover:bg-green-600 rounded-3xl'
|
||||
onClick={handleCallUser}
|
||||
style={{ display: !remoteStream ? 'block' : 'none' }}>
|
||||
Call <CallIcon fontSize='medium' className=' animate-pulse scale-125' />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
<div className="flex flex-col w-full items-center justify-center overflow-hidden">
|
||||
{
|
||||
myStream &&
|
||||
<VideoPlayer stream={myStream} name={"My Stream"} isAudioMute={isAudioMute} />
|
||||
}
|
||||
{
|
||||
remoteStream &&
|
||||
<VideoPlayer stream={remoteStream} name={"Remote Stream"} isAudioMute={isAudioMute} />
|
||||
}
|
||||
<div className='min-h-screen bg-gradient-to-br from-gray-900 via-blue-900 to-indigo-900 relative overflow-hidden'>
|
||||
<title>Room {slug} - VideoPeersJS</title>
|
||||
|
||||
{/* Background decorative elements */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute top-1/4 -left-4 w-72 h-72 bg-blue-500 rounded-full mix-blend-multiply filter blur-xl opacity-10 animate-pulse"></div>
|
||||
<div className="absolute bottom-1/4 -right-4 w-72 h-72 bg-purple-500 rounded-full mix-blend-multiply filter blur-xl opacity-10 animate-pulse delay-1000"></div>
|
||||
</div>
|
||||
{myStream && remoteStream && !isSendButtonVisible &&
|
||||
(
|
||||
<CallHandleButtons
|
||||
isAudioMute={isAudioMute}
|
||||
isVideoOnHold={isVideoOnHold}
|
||||
onToggleAudio={handleToggleAudio}
|
||||
onToggleVideo={handleToggleVideo}
|
||||
onEndCall={handleEndCall}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<motion.header
|
||||
className="absolute top-0 left-0 right-0 z-20 p-6"
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<motion.button
|
||||
onClick={handleGoBack}
|
||||
className="flex items-center space-x-2 px-4 py-2 bg-white/10 backdrop-blur-sm rounded-xl text-white hover:bg-white/20 transition-all duration-200"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
<span>Back</span>
|
||||
</motion.button>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Video className="h-6 w-6 text-indigo-400" />
|
||||
<h1 className="text-xl font-bold text-white">
|
||||
{process.env.NEXT_PUBLIC_APP_NAME || 'VideoPeersJS'}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-2 px-4 py-2 bg-white/10 backdrop-blur-sm rounded-xl text-white">
|
||||
<Users className="h-4 w-4" />
|
||||
<span className="text-sm">Room {slug}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.header>
|
||||
|
||||
{/* Connection Status */}
|
||||
<motion.div
|
||||
className="absolute top-24 left-1/2 transform -translate-x-1/2 z-20"
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
>
|
||||
<div className={`px-6 py-3 rounded-full backdrop-blur-sm text-white text-center ${
|
||||
remoteSocketId ? 'bg-green-500/20 border border-green-400/30' : 'bg-orange-500/20 border border-orange-400/30'
|
||||
}`}>
|
||||
<p className="text-sm font-medium">
|
||||
{isConnecting ? (
|
||||
<span className="flex items-center space-x-2">
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent"></div>
|
||||
<span>Connecting...</span>
|
||||
</span>
|
||||
) : remoteSocketId ? (
|
||||
"🟢 Connected with remote user"
|
||||
) : (
|
||||
"🟡 Waiting for someone to join..."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Video Container */}
|
||||
<div className="relative h-screen flex items-center justify-center p-6 pt-32">
|
||||
{/* Remote Stream (Main) */}
|
||||
{remoteStream ? (
|
||||
<VideoPlayer
|
||||
stream={remoteStream}
|
||||
name={"Remote Stream"}
|
||||
isAudioMute={false}
|
||||
/>
|
||||
) : (
|
||||
<motion.div
|
||||
className="w-full max-w-4xl aspect-video bg-gradient-to-br from-gray-800 to-gray-900 rounded-2xl border-2 border-white/10 flex items-center justify-center"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<div className="text-center text-white/60">
|
||||
<Users className="h-24 w-24 mx-auto mb-4 opacity-40" />
|
||||
<p className="text-xl font-medium mb-2">Waiting for remote video...</p>
|
||||
<p className="text-sm">Share this room ID with someone to start a call</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* My Stream (Picture-in-Picture) */}
|
||||
{myStream && (
|
||||
<VideoPlayer
|
||||
stream={myStream}
|
||||
name={"My Stream"}
|
||||
isAudioMute={isAudioMute}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 z-30">
|
||||
{remoteStream && isSendButtonVisible && (
|
||||
<motion.button
|
||||
onClick={sendStreams}
|
||||
className="mb-4 flex items-center space-x-2 px-6 py-3 bg-green-500 hover:bg-green-600 text-white rounded-xl font-semibold transition-all duration-200"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
>
|
||||
<Send className="h-5 w-5" />
|
||||
<span>Send Stream</span>
|
||||
</motion.button>
|
||||
)}
|
||||
|
||||
{remoteSocketId && callButton && !remoteStream && (
|
||||
<motion.button
|
||||
onClick={handleCallUser}
|
||||
disabled={isConnecting}
|
||||
className="flex items-center space-x-2 px-8 py-4 bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white rounded-2xl font-semibold text-lg shadow-lg disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200"
|
||||
whileHover={{ scale: isConnecting ? 1 : 1.05 }}
|
||||
whileTap={{ scale: isConnecting ? 1 : 0.95 }}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
>
|
||||
{isConnecting ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-2 border-white border-t-transparent"></div>
|
||||
<span>Connecting...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Phone className="h-6 w-6" />
|
||||
<span>Start Call</span>
|
||||
</>
|
||||
)}
|
||||
</motion.button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Call Control Buttons */}
|
||||
{myStream && remoteStream && !isSendButtonVisible && (
|
||||
<CallHandleButtons
|
||||
isAudioMute={isAudioMute}
|
||||
isVideoOnHold={isVideoOnHold}
|
||||
onToggleAudio={handleToggleAudio}
|
||||
onToggleVideo={handleToggleVideo}
|
||||
onEndCall={handleEndCall}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Referencia en una nueva incidencia
Block a user