mirror of
https://github.com/ianramzy/decentralized-video-chat.git
synced 2024-11-23 10:39:20 +08:00
finish refactor
This commit is contained in:
parent
cfa063c422
commit
5b4b397418
@ -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."
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user