From 3586a1285cae0e43df34315507dcfd4efbe7fcae Mon Sep 17 00:00:00 2001 From: pb-coding Date: Fri, 13 Oct 2023 11:37:51 +0200 Subject: [PATCH] wip testing voice and video --- .gitignore | 1 + src/App.tsx | 45 -------- src/components/SessionManager.tsx | 2 + src/components/VoiceChat.tsx | 178 ++++++++++++++++++++++++++++++ src/main.tsx | 7 +- 5 files changed, 182 insertions(+), 51 deletions(-) create mode 100644 src/components/VoiceChat.tsx diff --git a/.gitignore b/.gitignore index 1cac559..7d70bca 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +cert # Editor directories and files .vscode/* diff --git a/src/App.tsx b/src/App.tsx index c471762..845c36b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,5 @@ import { useState, useEffect } from "react"; import { socket } from "./socket"; -import SimplePeer from "simple-peer"; import GameCanvas from "./components/GameCanvas"; import { Footer } from "./components/Footer"; @@ -54,50 +53,6 @@ export default function App() { socket.on("clients-in-session", onClientsInRoomUpdate); socket.on("game-update", onGameUpdate); - const peers = new Map(); - - navigator.mediaDevices - .getUserMedia({ audio: true }) - .then((stream) => { - socket.on("initiate-voice-chat", (data) => { - const { to } = data; - console.log("Initiating voice chat with", to); - - const peer = new SimplePeer({ initiator: true, stream }); - - peers.set(to, peer); - - peer.on("signal", (signalData) => { - socket.emit("signal", { to, signalData }); - }); - }); - - socket.on("signal", (data) => { - const { from, signalData } = data; - let peer = peers.get(from); - - if (!peer) { - peer = new SimplePeer({ initiator: false, stream }); - peers.set(from, peer); - - peer.on("signal", (signalData) => { - socket.emit("signal", { to: from, signalData }); - }); - - peer.on("stream", (remoteStream) => { - const audioEl = new Audio(); - audioEl.srcObject = remoteStream; - audioEl.play(); - }); - } - - peer.signal(signalData); - }); - }) - .catch((err) => { - console.log("Error getting user media", err); - }); - return () => { socket.off("connect", onConnect); socket.off("disconnect", onDisconnect); diff --git a/src/components/SessionManager.tsx b/src/components/SessionManager.tsx index 5056c99..94fb82a 100644 --- a/src/components/SessionManager.tsx +++ b/src/components/SessionManager.tsx @@ -3,6 +3,7 @@ import { socket } from "../socket"; import { ConnectedIndicator, DisconnectedIndicator } from "./Indicators"; import Button from "../global/Button"; +import VoiceChat from "./VoiceChat"; type SessionManagerProps = { isConnected: boolean; @@ -100,6 +101,7 @@ export const SessionManager: FC = ({ Game Server: {isConnected ? : } + diff --git a/src/components/VoiceChat.tsx b/src/components/VoiceChat.tsx new file mode 100644 index 0000000..a1833cf --- /dev/null +++ b/src/components/VoiceChat.tsx @@ -0,0 +1,178 @@ +import { FC, useEffect, useState, useRef } from "react"; +import { socket } from "../socket"; + +type VoiceChatProps = { + session: string; +}; + +const VoiceChat: FC = ({ session }) => { + const servers = { + iceServers: [ + { + urls: [ + "stun:stun1.l.google.com:19302", + "stun:stun2.l.google.com:19302", + ], + }, + ], + iceCandidatePoolSize: 10, + }; + + const localVideoRef = useRef(null); + const remoteVideoRef = useRef(null); + + const localStreamRef = useRef(null); + const remoteStreamRef = useRef(new MediaStream()); + + const pc = useRef(new RTCPeerConnection(servers)); + const sessionRef = useRef(session); + + useEffect(() => { + sessionRef.current = session; + }, [session]); + + const [isWebcamSetup, setIsWebcamSetup] = useState(false); + + // Setup media sources + const setupWebcam = async () => { + localStreamRef.current = await navigator.mediaDevices.getUserMedia({ + video: true, + audio: true, + }); + + if (localVideoRef.current) { + localVideoRef.current.srcObject = localStreamRef.current; + } + + localStreamRef.current.getTracks().forEach((track) => { + if (!localStreamRef.current) return; + pc.current.addTrack(track, localStreamRef.current); + }); + }; + + // Create an offer + const createOffer = async () => { + const offerDescription = await pc.current.createOffer(); + await pc.current.setLocalDescription(offerDescription); + console.log("session (create offer):", session); + socket.emit("create-offer", { offerDescription, sessionName: session }); + }; + + // Answer the call + const answerCall = async (offer: RTCSessionDescriptionInit) => { + console.log("Answer call triggered"); + + await pc.current.setRemoteDescription(new RTCSessionDescription(offer)); + + const answerDescription = await pc.current.createAnswer(); + await pc.current.setLocalDescription(answerDescription); + + console.log("session (answer call):", sessionRef.current); + + socket.emit("answer-call", { + answerDescription, + sessionName: sessionRef.current, + }); + }; + + useEffect(() => { + if (localVideoRef.current && localStreamRef.current) { + localVideoRef.current.srcObject = localStreamRef.current; + } + + if (remoteVideoRef.current) { + remoteVideoRef.current.srcObject = remoteStreamRef.current; + } + + socket.on("offer-made", async (offer) => { + console.log("Offer received:", offer); + answerCall(offer); + }); + + socket.on("answer-made", async (answer) => { + console.log("Answer received:", answer); + await pc.current.setRemoteDescription(new RTCSessionDescription(answer)); + console.log("set remote description", pc.current); + console.log("set"); + }); + + socket.on("add-ice-candidate", (candidate) => { + console.log("Ice candidate received:", candidate); + pc.current.addIceCandidate(new RTCIceCandidate(candidate)); + }); + + pc.current.onicecandidate = (event) => { + if (event.candidate) { + const candidate = event.candidate.toJSON(); + console.log("Emit ICE-candidate:", candidate); + socket.emit("ice-candidate", { + candidate, + sessionName: sessionRef.current, + }); + } + }; + + pc.current.ontrack = (event) => { + event.streams[0].getTracks().forEach((track) => { + remoteStreamRef.current.addTrack(track); + }); + console.log("remote stream", remoteStreamRef.current); + if (remoteVideoRef.current) { + console.log("setting remote video ref"); + remoteVideoRef.current.srcObject = remoteStreamRef.current; + } + }; + }, []); + + const startWebcam = async () => { + await setupWebcam(); + setIsWebcamSetup(true); + }; + + const initiateCall = async () => { + if (isWebcamSetup) { + // Only allow calls if the webcam is set up + await createOffer(); + } else { + console.log("Please set up your webcam first."); + } + }; + + return ( +
+

Webcam

+
+ +

Local Stream

+ +
+ +

Remote Stream

+ +
+
+ + +

2. Create a new Call

+ + +

3. Join a Call

+

Answer the call from a different browser window or device

+ + + + +

4. Hangup

+ + +
+ ); +}; + +export default VoiceChat; diff --git a/src/main.tsx b/src/main.tsx index 966f17a..eb0a604 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,5 @@ -import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App.tsx"; import "./index.css"; -ReactDOM.createRoot(document.getElementById("root")!).render( - - - -); +ReactDOM.createRoot(document.getElementById("root")!).render();