finish refactor

This commit is contained in:
Ian Ramzy 2020-04-06 00:17:40 -04:00
parent cfa063c422
commit 5b4b397418
1 changed files with 68 additions and 40 deletions

View File

@ -18,7 +18,7 @@ const isWebRTCSupported =
// Element vars // Element vars
const chatInput = document.querySelector(".compose input"); const chatInput = document.querySelector(".compose input");
const pipVideo = document.getElementById("remote-video"); const remoteVideoVanilla = document.getElementById("remote-video");
const remoteVideo = $("#remote-video"); const remoteVideo = $("#remote-video");
const captionText = $("#remote-video-text"); const captionText = $("#remote-video-text");
const localVideoText = $("#local-video-text"); const localVideoText = $("#local-video-text");
@ -62,10 +62,12 @@ var VideoChat = {
}); });
}, },
// Called when a video stream is added to VideoChat
onMediaStream: function (stream) { onMediaStream: function (stream) {
logIt("onMediaStream"); logIt("onMediaStream");
VideoChat.localStream = stream; VideoChat.localStream = stream;
// Add the stream as video's srcObject. // Add the stream as video's srcObject.
// Now that we have webcam video sorted, prompt user to share URL
Snackbar.show({ Snackbar.show({
text: "Share this URL with a friend to get started!", text: "Share this URL with a friend to get started!",
actionText: "Copy Link", actionText: "Copy Link",
@ -75,6 +77,8 @@ var VideoChat = {
duration: 500000, duration: 500000,
backgroundColor: "#16171a", backgroundColor: "#16171a",
onActionClick: function (element) { 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; var copyContent = window.location.href;
$('<input id="some-element">') $('<input id="some-element">')
.val(copyContent) .val(copyContent)
@ -83,12 +87,13 @@ var VideoChat = {
document.execCommand("copy"); document.execCommand("copy");
var toRemove = document.querySelector("#some-element"); var toRemove = document.querySelector("#some-element");
toRemove.parentNode.removeChild(toRemove); toRemove.parentNode.removeChild(toRemove);
$(element).css("opacity", 0); //Set opacity of element to 0 to close Snackbar Snackbar.close();
}, },
}); });
VideoChat.localVideo.srcObject = stream; VideoChat.localVideo.srcObject = stream;
// Now we're ready to join the chat room. // Now we're ready to join the chat room.
VideoChat.socket.emit("join", roomHash); VideoChat.socket.emit("join", roomHash);
// Add listeners to the websocket
VideoChat.socket.on("full", chatRoomFull); VideoChat.socket.on("full", chatRoomFull);
VideoChat.socket.on("offer", VideoChat.onOffer); VideoChat.socket.on("offer", VideoChat.onOffer);
VideoChat.socket.on("ready", VideoChat.readyToCall); VideoChat.socket.on("ready", VideoChat.readyToCall);
@ -101,6 +106,7 @@ var VideoChat = {
// When we are ready to call, enable the Call button. // When we are ready to call, enable the Call button.
readyToCall: function (event) { readyToCall: function (event) {
logIt("readyToCall"); logIt("readyToCall");
// First to join call will most likely initiate call
if (VideoChat.willInitiateCall) { if (VideoChat.willInitiateCall) {
logIt("Initiating call"); logIt("Initiating call");
VideoChat.startCall(); VideoChat.startCall();
@ -127,20 +133,23 @@ var VideoChat = {
VideoChat.localStream.getTracks().forEach(function (track) { VideoChat.localStream.getTracks().forEach(function (track) {
VideoChat.peerConnection.addTrack(track, VideoChat.localStream); VideoChat.peerConnection.addTrack(track, VideoChat.localStream);
}); });
// Add general purpose data channel to peer connection,
// used for text chats, captions, and toggling sending captions
dataChanel = VideoChat.peerConnection.createDataChannel("chat", { dataChanel = VideoChat.peerConnection.createDataChannel("chat", {
negotiated: true, negotiated: true,
// both peers must have same id
id: 0, id: 0,
}); });
// Called when dataChannel is successfully opened
dataChanel.onopen = function (event) { dataChanel.onopen = function (event) {
logIt("dataChannel opened"); logIt("dataChannel opened");
}; };
// Handle different dataChannel types
dataChanel.onmessage = function (event) { dataChanel.onmessage = function (event) {
var recievedData = event.data; const receivedData = event.data;
var dataType = recievedData.substring(0, 4); // First 4 chars represent data type
var cleanedMessage = recievedData.slice(4); const dataType = receivedData.substring(0, 4);
const cleanedMessage = receivedData.slice(4);
if (dataType === "mes:") { if (dataType === "mes:") {
handleRecieveMessage(cleanedMessage); handleRecieveMessage(cleanedMessage);
} else if (dataType === "cap:") { } else if (dataType === "cap:") {
@ -149,30 +158,29 @@ var VideoChat = {
toggleSendCaptions(); toggleSendCaptions();
} }
}; };
// Set up callbacks for the connection generating iceCandidates or // Set up callbacks for the connection generating iceCandidates or
// receiving the remote media stream. // receiving the remote media stream.
VideoChat.peerConnection.onicecandidate = VideoChat.onIceCandidate; VideoChat.peerConnection.onicecandidate = VideoChat.onIceCandidate;
VideoChat.peerConnection.onaddstream = VideoChat.onAddStream; VideoChat.peerConnection.onaddstream = VideoChat.onAddStream;
// Set up listeners on the socket for candidates or answers being passed // Set up listeners on the socket
// over the socket connection.
VideoChat.socket.on("candidate", VideoChat.onCandidate); VideoChat.socket.on("candidate", VideoChat.onCandidate);
VideoChat.socket.on("answer", VideoChat.onAnswer); VideoChat.socket.on("answer", VideoChat.onAnswer);
VideoChat.socket.on("requestToggleCaptions", () => toggleSendCaptions()); VideoChat.socket.on("requestToggleCaptions", () => toggleSendCaptions());
VideoChat.socket.on("recieveCaptions", (captions) => VideoChat.socket.on("recieveCaptions", (captions) =>
recieveCaptions(captions) recieveCaptions(captions)
); );
// Called when there is a change in connection state
VideoChat.peerConnection.oniceconnectionstatechange = function (event) { VideoChat.peerConnection.oniceconnectionstatechange = function (event) {
switch (VideoChat.peerConnection.iceConnectionState) { switch (VideoChat.peerConnection.iceConnectionState) {
case "connected": case "connected":
logIt("connected"); logIt("connected");
// Once connected we no longer have a need for the signaling server, so disconnect
VideoChat.socket.disconnect(); VideoChat.socket.disconnect();
break; break;
case "disconnected": case "disconnected":
case "failed": case "failed":
logIt("failed/disconnected"); logIt("failed/disconnected");
// Refresh page if connection is lost
location.reload(); location.reload();
break; break;
case "closed": case "closed":
@ -180,7 +188,6 @@ var VideoChat = {
break; break;
} }
}; };
callback(); callback();
}; };
}, },
@ -212,19 +219,18 @@ var VideoChat = {
// When receiving a candidate over the socket, turn it back into a real // When receiving a candidate over the socket, turn it back into a real
// RTCIceCandidate and add it to the peerConnection. // RTCIceCandidate and add it to the peerConnection.
onCandidate: function (candidate) { onCandidate: function (candidate) {
$("#remote-video-text").text("Found other user... connecting"); // Update caption text
logIt("onCandidate"); captionText.text("Found other user... connecting");
rtcCandidate = new RTCIceCandidate(JSON.parse(candidate)); rtcCandidate = new RTCIceCandidate(JSON.parse(candidate));
logIt( logIt(
`<<< Received remote ICE candidate (${rtcCandidate.address} - ${rtcCandidate.relatedAddress})` `onCandidate <<< Received remote ICE candidate (${rtcCandidate.address} - ${rtcCandidate.relatedAddress})`
); );
VideoChat.peerConnection.addIceCandidate(rtcCandidate); VideoChat.peerConnection.addIceCandidate(rtcCandidate);
}, },
// Create an offer that contains the media capabilities of the browser. // Create an offer that contains the media capabilities of the browser.
createOffer: function () { createOffer: function () {
logIt("createOffer"); logIt("createOffer >>> Creating offer...");
logIt(">>> Creating offer...");
VideoChat.peerConnection.createOffer( VideoChat.peerConnection.createOffer(
function (offer) { function (offer) {
// If the offer is created successfully, set it as the local description // If the offer is created successfully, set it as the local description
@ -267,8 +273,7 @@ var VideoChat = {
// When a browser receives an offer, set up a callback to be run when the // When a browser receives an offer, set up a callback to be run when the
// ephemeral token is returned from Twilio. // ephemeral token is returned from Twilio.
onOffer: function (offer) { onOffer: function (offer) {
logIt("onOffer"); logIt("onOffer <<< Received offer");
logIt("<<< Received offer");
VideoChat.socket.on( VideoChat.socket.on(
"token", "token",
VideoChat.onToken(VideoChat.createAnswer(offer)) VideoChat.onToken(VideoChat.createAnswer(offer))
@ -278,30 +283,35 @@ var VideoChat = {
// When an answer is received, add it to the peerConnection as the remote description. // When an answer is received, add it to the peerConnection as the remote description.
onAnswer: function (answer) { onAnswer: function (answer) {
logIt("onAnswer"); logIt("onAnswer <<< Received answer");
logIt("<<< Received answer");
var rtcAnswer = new RTCSessionDescription(JSON.parse(answer)); var rtcAnswer = new RTCSessionDescription(JSON.parse(answer));
// Set remote description of RTCSession
VideoChat.peerConnection.setRemoteDescription(rtcAnswer); VideoChat.peerConnection.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) => { VideoChat.localICECandidates.forEach((candidate) => {
// The caller now knows that the callee is ready to accept new ICE candidates, so sending the buffer over
logIt(`>>> Sending local ICE candidate (${candidate.address})`); logIt(`>>> Sending local ICE candidate (${candidate.address})`);
// Send ice candidate over websocket
VideoChat.socket.emit("candidate", JSON.stringify(candidate), roomHash); VideoChat.socket.emit("candidate", JSON.stringify(candidate), roomHash);
}); });
// Reset the buffer of local ICE candidates. This is not really needed, but it's good practice // Reset the buffer of local ICE candidates. This is not really needed, but it's good practice
VideoChat.localICECandidates = []; VideoChat.localICECandidates = [];
}, },
// Called when a stream is added to the peer connection
onAddStream: function (event) { onAddStream: function (event) {
logIt("onAddStream"); logIt("onAddStream <<< Received new stream from remote. Adding it...");
logIt("<<< Received new stream from remote. Adding it..."); // Update remote video source
VideoChat.remoteVideo.srcObject = event.stream; VideoChat.remoteVideo.srcObject = event.stream;
// Close the initial share url snackbar
Snackbar.close(); Snackbar.close();
// Remove the loading gif from video
VideoChat.remoteVideo.style.background = "none"; VideoChat.remoteVideo.style.background = "none";
// Update connection status
VideoChat.connected = true; VideoChat.connected = true;
// Hide caption status text
captionText.fadeOut(); captionText.fadeOut();
$("#local-video-text").fadeOut(); // Reposition local video repeatedly, as there is often a delay
// between adding a stream and the height of the video div changing
var timesRun = 0; var timesRun = 0;
var interval = setInterval(function () { var interval = setInterval(function () {
timesRun += 1; timesRun += 1;
@ -472,12 +482,15 @@ function pauseVideo() {
// Swap camera / screen share // Swap camera / screen share
function swap() { function swap() {
// Handle swap video before video call is connected
if (!VideoChat.connected) { if (!VideoChat.connected) {
alert("You must join a call before you can share your screen."); alert("You must join a call before you can share your screen.");
return; return;
} }
// Store swap button icon and text
const swapIcon = document.getElementById("swap-icon"); const swapIcon = document.getElementById("swap-icon");
const swapText = document.getElementById("swap-text"); const swapText = document.getElementById("swap-text");
// If mode is camera then switch to screen share
if (mode === "camera") { if (mode === "camera") {
// Show accept screenshare snackbar // Show accept screenshare snackbar
Snackbar.show({ Snackbar.show({
@ -488,22 +501,22 @@ function swap() {
actionTextColor: "#616161", actionTextColor: "#616161",
duration: 50000, duration: 50000,
}); });
// Request screen share, note we dont want to capture audio
// Request screen share // as we already have the stream from the Webcam
navigator.mediaDevices navigator.mediaDevices
.getDisplayMedia({ .getDisplayMedia({
video: true, video: true,
audio: false, audio: false,
}) })
.then(function (stream) { .then(function (stream) {
// Close allow screenshare snackbar
Snackbar.close(); Snackbar.close();
// Change display mode
mode = "screen"; mode = "screen";
// Update swap button icon and text
swapIcon.classList.remove("fa-desktop"); swapIcon.classList.remove("fa-desktop");
swapIcon.classList.add("fa-camera"); swapIcon.classList.add("fa-camera");
swapText.innerText = "Share Webcam"; swapText.innerText = "Share Webcam";
if (videoIsPaused) {
pauseVideo();
}
switchStreamHelper(stream); switchStreamHelper(stream);
}) })
.catch(function (err) { .catch(function (err) {
@ -511,15 +524,20 @@ function swap() {
logIt("Error sharing screen"); logIt("Error sharing screen");
Snackbar.close(); Snackbar.close();
}); });
// If mode is screenshare then switch to webcam
} else { } else {
// Stop the screen share track
VideoChat.localVideo.srcObject.getTracks().forEach((track) => track.stop()); VideoChat.localVideo.srcObject.getTracks().forEach((track) => track.stop());
// Get webcam input
navigator.mediaDevices navigator.mediaDevices
.getUserMedia({ .getUserMedia({
video: true, video: true,
audio: true, audio: true,
}) })
.then(function (stream) { .then(function (stream) {
// Change display mode
mode = "camera"; mode = "camera";
// Update swap button icon and text
swapIcon.classList.remove("fa-camera"); swapIcon.classList.remove("fa-camera");
swapIcon.classList.add("fa-desktop"); swapIcon.classList.add("fa-desktop");
swapText.innerText = "Share Screen"; swapText.innerText = "Share Screen";
@ -528,18 +546,26 @@ function swap() {
} }
} }
// Swap current video track with passed in stream
function switchStreamHelper(stream) { function switchStreamHelper(stream) {
// Get current video track
let videoTrack = stream.getVideoTracks()[0]; let videoTrack = stream.getVideoTracks()[0];
// Add listen for if the current track swaps, swap back
videoTrack.onended = function () { videoTrack.onended = function () {
swap(); swap();
}; };
if (VideoChat.connected) { if (VideoChat.connected) {
// Find sender
const sender = VideoChat.peerConnection.getSenders().find(function (s) { const sender = VideoChat.peerConnection.getSenders().find(function (s) {
// make sure tack types match
return s.track.kind === videoTrack.kind; return s.track.kind === videoTrack.kind;
}); });
// Replace sender track
sender.replaceTrack(videoTrack); sender.replaceTrack(videoTrack);
} }
// Update local video stream
VideoChat.localStream = videoTrack; VideoChat.localStream = videoTrack;
// Update local video object
VideoChat.localVideo.srcObject = stream; VideoChat.localVideo.srcObject = stream;
// Unpause video on swap // Unpause video on swap
if (videoIsPaused) { if (videoIsPaused) {
@ -725,19 +751,21 @@ function toggleChat() {
function togglePictureInPicture() { function togglePictureInPicture() {
if ( if (
"pictureInPictureEnabled" in document || "pictureInPictureEnabled" in document ||
pipVideo.webkitSetPresentationMode remoteVideoVanilla.webkitSetPresentationMode
) { ) {
if (document.pictureInPictureElement) { if (document.pictureInPictureElement) {
document.exitPictureInPicture().catch((error) => { document.exitPictureInPicture().catch((error) => {
logIt("Error exiting pip."); logIt("Error exiting pip.");
logIt(error); logIt(error);
}); });
} else if (pipVideo.webkitPresentationMode === "inline") { } else if (remoteVideoVanilla.webkitPresentationMode === "inline") {
pipVideo.webkitSetPresentationMode("picture-in-picture"); remoteVideoVanilla.webkitSetPresentationMode("picture-in-picture");
} else if (pipVideo.webkitPresentationMode === "picture-in-picture") { } else if (
pipVideo.webkitSetPresentationMode("inline"); remoteVideoVanilla.webkitPresentationMode === "picture-in-picture"
) {
remoteVideoVanilla.webkitSetPresentationMode("inline");
} else { } else {
pipVideo.requestPictureInPicture().catch((error) => { remoteVideoVanilla.requestPictureInPicture().catch((error) => {
alert( alert(
"You must be connected to another person to enter picture in picture." "You must be connected to another person to enter picture in picture."
); );