let localStream; let video; let peerConnection; let serverConnection; let uuid; let otherPeerJoined; const peerConnectionConfig = { iceServers: [ { urls: "stun:stun.stunprotocol.org:3478" }, { urls: "stun:stun.l.google.com:19302" }, ], }; async function pageReady() { uuid = createUUID(); video = document.getElementById("video"); serverConnection = new WebSocket( `wss://${window.location.hostname}/ws${window.location.pathname}` ); serverConnection.onmessage = gotMessageFromServer; const constraints = { video: confirm("Enable video?"), audio: true, }; if (!navigator.mediaDevices.getUserMedia) { alert("Your browser does not support getUserMedia API"); return; } try { const stream = await navigator.mediaDevices.getUserMedia(constraints); localStream = stream; video.srcObject = stream; } catch (error) { errorHandler(error); } if (otherPeerJoined) { start(true); } } function start(isCaller) { peerConnection = new RTCPeerConnection(peerConnectionConfig); peerConnection.onicecandidate = gotIceCandidate; peerConnection.ontrack = gotRemoteStream; for (const track of localStream.getTracks()) { peerConnection.addTrack(track, localStream); } if (isCaller) { peerConnection .createOffer() .then(createdDescription) .catch(errorHandler); } } function gotMessageFromServer(message) { if(message && message.data === "participants-ready") { otherPeerJoined = true; if (localStream && localStream.getTracks) { start(true); } return; } // if we do not have a peer connection yet if (!peerConnection){ // start as call receiver start(false); } // parse signal from received message const signal = JSON.parse(message.data); // Ignore messages from ourselves if (signal.uuid == uuid) { return; } if (signal.sdp) { peerConnection .setRemoteDescription(new RTCSessionDescription(signal.sdp)) .then(() => { // Only create answers in response to offers if (signal.sdp.type !== "offer"){ return; } peerConnection .createAnswer() .then(createdDescription) .catch(errorHandler); }) .catch(errorHandler); return; } if (signal.ice) { peerConnection .addIceCandidate(new RTCIceCandidate(signal.ice)) .catch(errorHandler); } } function gotIceCandidate(event) { if (event.candidate != null) { serverConnection.send( JSON.stringify({ ice: event.candidate, uuid: uuid }) ); } } function createdDescription(description) { peerConnection .setLocalDescription(description) .then(() => { serverConnection.send( JSON.stringify({ sdp: peerConnection.localDescription, uuid: uuid, }) ); }) .catch(errorHandler); } function gotRemoteStream(event) { video.srcObject = event.streams[0]; video.muted = false; video.removeAttribute("muted"); } function errorHandler(error) { console.log(error); } function createUUID() { if (crypto.randomUUID){ return crypto.randomUUID(); } function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } return `${s4() + s4()}-${s4()}-${s4()}-${s4()}-${s4() + s4() + s4()}`; }