// Vars var isMuted; var videoIsPaused; var dataChanel = null; const browserName = getBrowserName(); const url = window.location.href; const roomHash = url.substring(url.lastIndexOf("/") + 1).toLowerCase(); var mode = "camera"; // var isFullscreen = false; var sendingCaptions = false; var receivingCaptions = false; const isWebRTCSupported = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || window.RTCPeerConnection; // Element vars const chatInput = document.querySelector(".compose input"); const remoteVideoVanilla = document.getElementById("remote-video"); const remoteVideo = $("#remote-video"); const remoteVideosWrapper = $("#wrapper"); const captionText = $("#remote-video-text"); const localVideoText = $("#local-video-text"); const captionButtontext = $("#caption-button-text"); const entireChat = $("#entire-chat"); const chatZone = $("#chat-zone"); var VideoChat = { connected: false, counter: 0, willInitiateCall: false, localICECandidates: [], socket: io(), remoteVideoWrapper: document.getElementById("wrapper"), localVideo: document.getElementById("local-video"), peerConnections: {}, recognition: undefined, uuid: undefined, token: undefined, // Call to getUserMedia (provided by adapter.js for cross browser compatibility) // asking for access to both the video and audio streams. If the request is // accepted callback to the onMediaStream function, otherwise callback to the // noMediaStream function. requestMediaStream: function (event) { logIt("requestMediaStream"); rePositionLocalVideo(); navigator.mediaDevices .getUserMedia({ video: true, audio: true, }) .then((stream) => { VideoChat.onMediaStream(stream); localVideoText.text("Drag Me"); setTimeout(() => localVideoText.fadeOut(), 5000); }) .catch((error) => { logIt(error); logIt( "Failed to get local webcam video, check webcam privacy settings" ); // Keep trying to get user media setTimeout(VideoChat.requestMediaStream, 1000); }); }, // Called when a video stream is added to VideoChat onMediaStream: function (stream) { logIt("onMediaStream"); VideoChat.localStream = stream; // Add the stream as video's srcObject. // Now that we have webcam video sorted, prompt user to share URL Snackbar.show({ text: "Here is the join link for your call: " + url, actionText: "Copy Link", width: "750px", pos: "top-center", actionTextColor: "#616161", duration: 500000, backgroundColor: "#16171a", onActionClick: function (element) { // Copy url to clipboard, this is achieved by creating a temporary element, // adding the text we want to that element, selecting it, then deleting it var copyContent = window.location.href; $('') .val(copyContent) .appendTo("body") .select(); document.execCommand("copy"); var toRemove = document.querySelector("#some-element"); toRemove.parentNode.removeChild(toRemove); Snackbar.close(); }, }); VideoChat.localVideo.srcObject = stream; // Now we're ready to join the chat room. VideoChat.socket.emit("join", roomHash); // Receive its own uuid VideoChat.socket.on("uuid", (uuid) => (VideoChat.uuid = uuid)); VideoChat.socket.on("token", (token) => (VideoChat.token = token)); // Add listeners to the websocket VideoChat.socket.on("full", chatRoomFull); VideoChat.socket.on("offer", VideoChat.onOffer); VideoChat.socket.on("ready", VideoChat.readyToCall); VideoChat.socket.on("willInitiateCall", VideoChat.call) }, // When we are ready to call, enable the Call button. readyToCall: function (event) { logIt("readyToCall"); }, call: function (uuid, room) { logIt("Initiating call"); VideoChat.startCall(uuid); }, // Set up a callback to run when we have the ephemeral token to use Twilio's TURN server. startCall: function (uuid) { VideoChat.establishConnection(uuid); VideoChat.createOffer(uuid); }, establishConnection: function (uuid) { console.log("establishing connection to", uuid); // Set up a new RTCPeerConnection using the token's iceServers. VideoChat.peerConnections[uuid] = new RTCPeerConnection({ iceServers: VideoChat.token.iceServers, }); // Add the local video stream to the peerConnection. VideoChat.localStream.getTracks().forEach(function (track) { console.log("local stream: ", track); VideoChat.peerConnections[uuid].addTrack(track, VideoChat.localStream); }); // Add general purpose data channel to peer connection, // used for text chats, captions, and toggling sending captions dataChanel = VideoChat.peerConnections[uuid].createDataChannel("chat", { negotiated: true, // both peers must have same id id: 0, }); // Called when dataChannel is successfully opened dataChanel.onopen = function (event) { logIt("dataChannel opened"); }; // Handle different dataChannel types dataChanel.onmessage = function (event) { const receivedData = event.data; // First 4 chars represent data type const dataType = receivedData.substring(0, 4); const cleanedMessage = receivedData.slice(4); if (dataType === "mes:") { handleRecieveMessage(cleanedMessage); } else if (dataType === "cap:") { recieveCaptions(cleanedMessage); } else if (dataType === "tog:") { toggleSendCaptions(); } }; // Set up callbacks for the connection generating iceCandidates or // receiving the remote media stream. VideoChat.peerConnections[uuid].onicecandidate = VideoChat.onIceCandidate; VideoChat.peerConnections[uuid].onaddstream = VideoChat.onAddStream; // Set up listeners on the socket VideoChat.socket.on("candidate", VideoChat.onCandidate); VideoChat.socket.on("answer", VideoChat.onAnswer); VideoChat.socket.on("requestToggleCaptions", () => toggleSendCaptions()); VideoChat.socket.on("recieveCaptions", (captions) => recieveCaptions(captions) ); // Called when there is a change in connection state VideoChat.peerConnections[uuid].oniceconnectionstatechange = function (event) { switch (VideoChat.peerConnections[uuid].iceConnectionState) { case "connected": logIt("connected"); // Once connected we no longer have a need for the signaling server, so disconnect VideoChat.socket.off("token"); VideoChat.socket.off("offer"); break; case "disconnected": logIt("disconnected"); case "failed": logIt("failed"); // VideoChat.socket.connect // VideoChat.createOffer(); // Refresh page if connection has failed location.reload(); break; case "closed": logIt("closed"); break; } }; }, // When we receive the ephemeral token back from the server. // onToken: function (callback) { // logIt("onToken"); // return function (token, uuid) { // logIt("<<< Received token"); // console.log(token, uuid); // // Set up a new RTCPeerConnection using the token's iceServers. // VideoChat.peerConnections[uuid] = new RTCPeerConnection({ // iceServers: token.iceServers, // }); // // Add the local video stream to the peerConnection. // VideoChat.localStream.getTracks().forEach(function (track) { // VideoChat.peerConnections[uuid].addTrack(track, VideoChat.localStream); // }); // // Add general purpose data channel to peer connection, // // used for text chats, captions, and toggling sending captions // dataChanel = VideoChat.peerConnections[uuid].createDataChannel("chat", { // negotiated: true, // // both peers must have same id // id: 0, // }); // // Called when dataChannel is successfully opened // dataChanel.onopen = function (event) { // logIt("dataChannel opened"); // }; // // Handle different dataChannel types // dataChanel.onmessage = function (event) { // const receivedData = event.data; // // First 4 chars represent data type // const dataType = receivedData.substring(0, 4); // const cleanedMessage = receivedData.slice(4); // if (dataType === "mes:") { // handleRecieveMessage(cleanedMessage); // } else if (dataType === "cap:") { // recieveCaptions(cleanedMessage); // } else if (dataType === "tog:") { // toggleSendCaptions(); // } // }; // // Set up callbacks for the connection generating iceCandidates or // // receiving the remote media stream. // VideoChat.peerConnections[uuid].onicecandidate = VideoChat.onIceCandidate; // VideoChat.peerConnections[uuid].onaddstream = VideoChat.onAddStream; // // Set up listeners on the socket // VideoChat.socket.on("candidate", VideoChat.onCandidate); // VideoChat.socket.on("answer", VideoChat.onAnswer); // VideoChat.socket.on("requestToggleCaptions", () => toggleSendCaptions()); // VideoChat.socket.on("recieveCaptions", (captions) => // recieveCaptions(captions) // ); // // Called when there is a change in connection state // VideoChat.peerConnections[uuid].oniceconnectionstatechange = function (event) { // switch (VideoChat.peerConnections[uuid].iceConnectionState) { // case "connected": // logIt("connected"); // // Once connected we no longer have a need for the signaling server, so disconnect // VideoChat.socket.off("token"); // VideoChat.socket.off("offer"); // break; // case "disconnected": // logIt("disconnected"); // case "failed": // logIt("failed"); // // VideoChat.socket.connect // // VideoChat.createOffer(); // // Refresh page if connection has failed // location.reload(); // break; // case "closed": // logIt("closed"); // break; // } // }; // callback(); // }; // }, // When the peerConnection generates an ice candidate, send it over the socket to the peer. onIceCandidate: function (event) { logIt("onIceCandidate"); if (event.candidate) { logIt( `<<< Received local ICE candidate from STUN/TURN server (${event.candidate.address})` ); if (VideoChat.connected) { logIt(`>>> Sending local ICE candidate (${event.candidate.address})`); VideoChat.socket.emit( "candidate", JSON.stringify(event.candidate), roomHash ); } else { // If we are not 'connected' to the other peer, we are buffering the local ICE candidates. // This most likely is happening on the "caller" side. // The peer may not have created the RTCPeerConnection yet, so we are waiting for the 'answer' // to arrive. This will signal that the peer is ready to receive signaling. VideoChat.localICECandidates.push(event.candidate); } } }, // When receiving a candidate over the socket, turn it back into a real // RTCIceCandidate and add it to the peerConnection. onCandidate: function (candidate, uuid) { // Update caption text captionText.text("Found other user... connecting"); rtcCandidate = new RTCIceCandidate(JSON.parse(candidate)); logIt( `onCandidate <<< Received remote ICE candidate (${rtcCandidate.address} - ${rtcCandidate.relatedAddress})` ); VideoChat.peerConnections[uuid].addIceCandidate(rtcCandidate); }, // Create an offer that contains the media capabilities of the browser. createOffer: function (uuid) { console.log(">>> Creating offer to UUID: ", uuid, ". my UUID is", VideoChat.socket.id); VideoChat.peerConnections[uuid].createOffer( function (offer) { // If the offer is created successfully, set it as the local description // and send it over the socket connection to initiate the peerConnection // on the other side. VideoChat.peerConnections[uuid].setLocalDescription(offer); VideoChat.socket.emit("offer", JSON.stringify(offer), roomHash, uuid); }, function (err) { logIt("failed offer creation"); logIt(err, true); } ); }, // Create an answer with the media capabilities that both browsers share. // This function is called with the offer from the originating browser, which // needs to be parsed into an RTCSessionDescription and added as the remote // description to the peerConnection object. Then the answer is created in the // same manner as the offer and sent over the socket. createAnswer: function (offer, uuid) { logIt("createAnswer"); rtcOffer = new RTCSessionDescription(JSON.parse(offer)); VideoChat.peerConnections[uuid].setRemoteDescription(rtcOffer); VideoChat.peerConnections[uuid].createAnswer( function (answer) { VideoChat.peerConnections[uuid].setLocalDescription(answer); console.log(">>> Creating answer to UUID: ", uuid, ". my UUID is", VideoChat.socket.id); VideoChat.socket.emit("answer", JSON.stringify(answer), roomHash, uuid); }, function (err) { logIt("Failed answer creation."); logIt(err, true); } ); }, // When a browser receives an offer, set up a callback to be run when the // ephemeral token is returned from Twilio. onOffer: function (offer, uuid) { logIt("onOffer <<< Received offer"); VideoChat.establishConnection(uuid); VideoChat.createAnswer(offer, uuid); }, // When an answer is received, add it to the peerConnection as the remote description. onAnswer: function (answer, uuid) { console.log("onAnswer <<< Received answer", uuid, ". my UUID is", VideoChat.socket.id); // logIt("onAnswer <<< Received answer" + ""); var rtcAnswer = new RTCSessionDescription(JSON.parse(answer)); // Set remote description of RTCSession VideoChat.peerConnections[uuid].setRemoteDescription(rtcAnswer); // The caller now knows that the callee is ready to accept new ICE candidates, so sending the buffer over VideoChat.localICECandidates.forEach((candidate) => { logIt(`>>> Sending local ICE candidate (${candidate.address})`); // Send ice candidate over websocket VideoChat.socket.emit("candidate", JSON.stringify(candidate), roomHash, uuid); }); // Reset the buffer of local ICE candidates. This is not really needed, but it's good practice VideoChat.localICECandidates = []; }, // Called when a stream is added to the peer connection onAddStream: function (event) { // if(VideoChat.counter > 4) { // VideoChat.socket.disconnect(); // }else{ // VideoChat.counter++; // } logIt("onAddStream <<< Received new stream from remote. Adding it..."); // Create new remote video source in wrapper // Create a