diff --git a/client/src/pages/room/[slug].jsx b/client/src/pages/room/[slug].jsx index 6b547c1..39d39e3 100644 --- a/client/src/pages/room/[slug].jsx +++ b/client/src/pages/room/[slug].jsx @@ -1,24 +1,99 @@ import { useSocket } from '@/context/SocketProvider'; import React, { useCallback, useEffect, useState } from 'react' import ReactPlayer from 'react-player'; +import peer from '@/service/peer'; const RoomPage = () => { const socket = useSocket(); const [remoteSocketId, setRemoteSocketId] = useState(null); const [myStream, setMyStream] = useState(null) + const [remoteStream, setRemoteStream] = useState(null) const handleUserJoined = useCallback(({ email, id }) => { console.log(`Email ${email} joined the room!`); setRemoteSocketId(id); + }, []); + + 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); + + const ans = await peer.getAnswer(offer); + socket.emit("call:accepted", { to: from, ans }); + }, [socket]); + + const sendStreams = useCallback(() => { + for (const track of myStream.getTracks()) { + peer.peer.addTrack(track, myStream); + } + }, [myStream]); + + const handleCallAccepted = useCallback(({ from, ans }) => { + peer.setLocalDescription(ans); + // console.log("Call Accepted"); + + 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]); + }) }, []) useEffect(() => { socket.on("user:joined", handleUserJoined); + 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("incoming:call", handleIncomingCall); + socket.off("call:accepted", handleCallAccepted); + socket.off("peer:nego:needed", handleNegoNeededIncoming); + socket.off("peer:nego:final", handleNegoFinal); }; - }, [socket, handleUserJoined]); + }, + [socket, + handleUserJoined, + handleIncomingCall, + handleCallAccepted, + handleNegoNeededIncoming, + handleNegoFinal + ]); const handleCallUser = useCallback(async () => { const stream = await navigator.mediaDevices.getUserMedia({ @@ -26,31 +101,59 @@ const RoomPage = () => { video: true }); + //! create offer + const offer = await peer.getOffer(); + //* send offer to remote user + socket.emit("user:call", { to: remoteSocketId, offer }) + // set my stream setMyStream(stream); - }, []) + }, [remoteSocketId, socket]); return (
-

RoomPage

-

{remoteSocketId ? "Connected" : "No One In Room"}

+

RoomPage

+

{remoteSocketId ? "Connected" : "No One In Room"}

+ {myStream && + + } {remoteSocketId && } - { - myStream && - <> -

My Stream

- - - } +
+ { + myStream && +
+

+ My Stream +

+ +
+ } + { + remoteStream && +
+

+ Remote Stream +

+ +
+ } +
) } diff --git a/client/src/service/peer.js b/client/src/service/peer.js new file mode 100644 index 0000000..5b83578 --- /dev/null +++ b/client/src/service/peer.js @@ -0,0 +1,39 @@ +class PeerService { + constructor() { + if (!this.peer) { + this.peer = new RTCPeerConnection({ + iceServers: [{ + urls: [ + "stun:stun.l.google.com:19302", + "stun:global.stun.twilio.com:3478", + ] + }] + }) + } + } + + setLocalDescription = async (ans) => { + if(this.peer){ + await this.peer.setRemoteDescription(new RTCSessionDescription(ans)); + } + } + + getAnswer = async (offer) => { + if(this.peer){ + await this.peer.setRemoteDescription(offer); + const ans = await this.peer.createAnswer(); + await this.peer.setLocalDescription(new RTCSessionDescription(ans)); + return ans; + } + } + + getOffer = async () => { + if (this.peer) { + const offer = await this.peer.createOffer(); + await this.peer.setLocalDescription(new RTCSessionDescription(offer)); + return offer; + } + } +} + +export default new PeerService(); \ No newline at end of file diff --git a/server/index.js b/server/index.js index b485a59..b41a737 100644 --- a/server/index.js +++ b/server/index.js @@ -1,4 +1,4 @@ -const { Server} = require('socket.io'); +const { Server } = require('socket.io'); const io = new Server(8080, { cors: true @@ -22,5 +22,22 @@ io.on("connection", (socket) => { // emits a 'room:joined' event back to the client // that just joined the room. io.to(socket.id).emit("room:join", data); - }) + }); + + socket.on("user:call", ({ to, offer }) => { + io.to(to).emit("incoming:call", { from: socket.id, offer }); + }); + + socket.on("call:accepted", ({ to, ans }) => { + io.to(to).emit("call:accepted", { from: socket.id, ans }); + }); + + socket.on("peer:nego:needed", ({ to, offer }) => { + io.to(to).emit("peer:nego:needed", { from: socket.id, offer }); + }); + + socket.on("peer:nego:done", ({ to, ans }) => { + io.to(to).emit("peer:nego:final", { from: socket.id, ans }); + }); + }) \ No newline at end of file