initial commit

Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
ale
2025-09-16 01:54:29 +02:00
padre 9d013a7c87
commit 6d1dd42e6d
Se han modificado 44 ficheros con 1719 adiciones y 11509 borrados

Ver fichero

@@ -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>
)
}