Merge pull request #1 from questo-ai/fixes

Fixes
This commit is contained in:
Taichi Kato 2020-05-31 11:32:11 +08:00 committed by GitHub
commit f392a13b56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 125 additions and 70 deletions

View File

@ -1,7 +1,6 @@
// Vars // Vars
var isMuted; var isMuted;
var videoIsPaused; var videoIsPaused;
var dataChanel = null;
const browserName = getBrowserName(); const browserName = getBrowserName();
const url = window.location.href; const url = window.location.href;
const roomHash = url.substring(url.lastIndexOf("/") + 1).toLowerCase(); const roomHash = url.substring(url.lastIndexOf("/") + 1).toLowerCase();
@ -27,13 +26,18 @@ const captionButtontext = $("#caption-button-text");
const entireChat = $("#entire-chat"); const entireChat = $("#entire-chat");
const chatZone = $("#chat-zone"); const chatZone = $("#chat-zone");
var dataChannel = new Map();
var VideoChat = { var VideoChat = {
connected: [], nickname: undefined,
videoEnabled: true,
audioEnabled: true,
connected: new Map(),
localICECandidates: {}, localICECandidates: {},
socket: io(), socket: io(),
remoteVideoWrapper: document.getElementById("wrapper"), remoteVideoWrapper: document.getElementById("wrapper"),
localVideo: document.getElementById("local-video"), localVideo: document.getElementById("local-video"),
peerConnections: {}, peerConnections: new Map(),
recognition: undefined, recognition: undefined,
// Call to getUserMedia (provided by adapter.js for cross browser compatibility) // Call to getUserMedia (provided by adapter.js for cross browser compatibility)
@ -91,6 +95,16 @@ var VideoChat = {
Snackbar.close(); Snackbar.close();
}, },
}); });
VideoChat.nickname = prompt("Please enter a nickname", "");
while (VideoChat.nickname == null || VideoChat.nickname.trim() === "") {
VideoChat.nickname = prompt(
"Nickname cannot be empty. Please enter a nickname",
""
);
}
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);
@ -107,7 +121,6 @@ var VideoChat = {
); );
}, },
call: function (uuid, room) { call: function (uuid, room) {
logIt("Initiating call with " + uuid); logIt("Initiating call with " + uuid);
VideoChat.socket.on( VideoChat.socket.on(
@ -127,29 +140,36 @@ var VideoChat = {
console.log("establishing connection to", uuid); console.log("establishing connection to", uuid);
// Initialise localICEcandidates for node uuid with empty array // Initialise localICEcandidates for node uuid with empty array
VideoChat.localICECandidates[uuid] = []; VideoChat.localICECandidates[uuid] = [];
VideoChat.connected[uuid] = false; VideoChat.connected.set(uuid, false);
// Set up a new RTCPeerConnection using the token's iceServers. // Set up a new RTCPeerConnection using the token's iceServers.
VideoChat.peerConnections[uuid] = new RTCPeerConnection({ VideoChat.peerConnections.set(
iceServers: token.iceServers, uuid,
}); new RTCPeerConnection({
iceServers: token.iceServers,
})
);
// Add the local video stream to the peerConnection. // Add the local video stream to the peerConnection.
VideoChat.localStream.getTracks().forEach(function (track) { VideoChat.localStream.getTracks().forEach(function (track) {
VideoChat.peerConnections[uuid].addTrack(track, VideoChat.localStream); VideoChat.peerConnections
.get(uuid)
.addTrack(track, VideoChat.localStream);
}); });
// Add general purpose data channel to peer connection, // Add general purpose data channel to peer connection,
// used for text chats, captions, and toggling sending captions // used for text chats, captions, and toggling sending captions
dataChanel = VideoChat.peerConnections[uuid].createDataChannel("chat", { dataChannel.set(
negotiated: true, uuid,
// both peers must have same id VideoChat.peerConnections.get(uuid).createDataChannel("chat", {
id: 0, negotiated: true,
}); // both peers must have same id
id: 0,
})
);
// Called when dataChannel is successfully opened // Called when dataChannel is successfully opened
dataChanel.onopen = function (event) { dataChannel.get(uuid).onopen = function (event) {
logIt("dataChannel opened"); logIt("dataChannel opened");
}; };
// Handle different dataChannel types // Handle different dataChannel types
dataChanel.onmessage = function (event) { dataChannel.get(uuid).onmessage = function (event) {
const receivedData = event.data; const receivedData = event.data;
// First 4 chars represent data type // First 4 chars represent data type
const dataType = receivedData.substring(0, 4); const dataType = receivedData.substring(0, 4);
@ -165,18 +185,18 @@ var VideoChat = {
// 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.peerConnections[uuid].onicecandidate = function (u) { VideoChat.peerConnections.get(uuid).onicecandidate = function (u) {
VideoChat.onIceCandidate(u, uuid); VideoChat.onIceCandidate(u, uuid);
}; };
VideoChat.peerConnections[uuid].onaddstream = function (u) { VideoChat.peerConnections.get(uuid).onaddstream = function (u) {
VideoChat.onAddStream(u, uuid); VideoChat.onAddStream(u, uuid);
}; };
// Called when there is a change in connection state // Called when there is a change in connection state
VideoChat.peerConnections[uuid].oniceconnectionstatechange = function ( VideoChat.peerConnections.get(
event uuid
) { ).oniceconnectionstatechange = function (event) {
switch (VideoChat.peerConnections[uuid].iceConnectionState) { switch (VideoChat.peerConnections.get(uuid).iceConnectionState) {
case "connected": case "connected":
logIt("connected"); logIt("connected");
// Once connected we no longer have a need for the signaling server, so disconnect // Once connected we no longer have a need for the signaling server, so disconnect
@ -206,7 +226,7 @@ var VideoChat = {
logIt( logIt(
`<<< Received local ICE candidate from STUN/TURN server (${event.candidate.address}) associated with UUID (${uuid})` `<<< Received local ICE candidate from STUN/TURN server (${event.candidate.address}) associated with UUID (${uuid})`
); );
if (VideoChat.connected[uuid]) { if (VideoChat.connected.get(uuid)) {
logIt(`>>> Sending local ICE candidate (${event.candidate.address})`); logIt(`>>> Sending local ICE candidate (${event.candidate.address})`);
VideoChat.socket.emit( VideoChat.socket.emit(
"candidate", "candidate",
@ -233,18 +253,18 @@ var VideoChat = {
logIt( logIt(
`onCandidate <<< Received remote ICE candidate (${rtcCandidate.address} - ${rtcCandidate.relatedAddress})` `onCandidate <<< Received remote ICE candidate (${rtcCandidate.address} - ${rtcCandidate.relatedAddress})`
); );
VideoChat.peerConnections[uuid].addIceCandidate(rtcCandidate); VideoChat.peerConnections.get(uuid).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 (uuid) { createOffer: function (uuid) {
logIt(`createOffer to ${uuid} >>> Creating offer...`); logIt(`createOffer to ${uuid} >>> Creating offer...`);
VideoChat.peerConnections[uuid].createOffer( VideoChat.peerConnections.get(uuid).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
// and send it over the socket connection to initiate the peerConnection // and send it over the socket connection to initiate the peerConnection
// on the other side. // on the other side.
VideoChat.peerConnections[uuid].setLocalDescription(offer); VideoChat.peerConnections.get(uuid).setLocalDescription(offer);
VideoChat.socket.emit("offer", JSON.stringify(offer), roomHash, uuid); VideoChat.socket.emit("offer", JSON.stringify(offer), roomHash, uuid);
}, },
function (err) { function (err) {
@ -263,10 +283,10 @@ var VideoChat = {
logIt("createAnswer"); logIt("createAnswer");
rtcOffer = new RTCSessionDescription(JSON.parse(offer)); rtcOffer = new RTCSessionDescription(JSON.parse(offer));
logIt(`>>> Creating answer to ${uuid}`); logIt(`>>> Creating answer to ${uuid}`);
VideoChat.peerConnections[uuid].setRemoteDescription(rtcOffer); VideoChat.peerConnections.get(uuid).setRemoteDescription(rtcOffer);
VideoChat.peerConnections[uuid].createAnswer( VideoChat.peerConnections.get(uuid).createAnswer(
function (answer) { function (answer) {
VideoChat.peerConnections[uuid].setLocalDescription(answer); VideoChat.peerConnections.get(uuid).setLocalDescription(answer);
VideoChat.socket.emit("answer", JSON.stringify(answer), roomHash, uuid); VideoChat.socket.emit("answer", JSON.stringify(answer), roomHash, uuid);
}, },
function (err) { function (err) {
@ -295,7 +315,7 @@ var VideoChat = {
logIt(`onAnswer <<< Received answer from ${uuid}`); logIt(`onAnswer <<< Received answer from ${uuid}`);
var rtcAnswer = new RTCSessionDescription(JSON.parse(answer)); var rtcAnswer = new RTCSessionDescription(JSON.parse(answer));
// Set remote description of RTCSession // Set remote description of RTCSession
VideoChat.peerConnections[uuid].setRemoteDescription(rtcAnswer); VideoChat.peerConnections.get(uuid).setRemoteDescription(rtcAnswer);
// The caller now knows that the callee is ready to accept new ICE candidates, so sending the buffer over // The caller now knows that the callee is ready to accept new ICE candidates, so sending the buffer over
VideoChat.localICECandidates[uuid].forEach((candidate) => { VideoChat.localICECandidates[uuid].forEach((candidate) => {
logIt(`>>> Sending local ICE candidate (${candidate.address})`); logIt(`>>> Sending local ICE candidate (${candidate.address})`);
@ -311,8 +331,7 @@ var VideoChat = {
// Called when a stream is added to the peer connection // Called when a stream is added to the peer connection
onAddStream: function (event, uuid) { onAddStream: function (event, uuid) {
logIt( logIt("onAddStream <<< Received new stream from remote. Adding it...");
"onAddStream <<< Received new stream from remote. Adding it...");
// Create new remote video source in wrapper // Create new remote video source in wrapper
// Create a <video> node // Create a <video> node
var node = document.createElement("video"); var node = document.createElement("video");
@ -327,7 +346,7 @@ var VideoChat = {
// Remove the loading gif from video // Remove the loading gif from video
VideoChat.remoteVideoWrapper.lastChild.style.background = "none"; VideoChat.remoteVideoWrapper.lastChild.style.background = "none";
// Update connection status // Update connection status
VideoChat.connected[uuid] = true; VideoChat.connected.set(uuid, true);
// Hide caption status text // Hide caption status text
captionText.fadeOut(); captionText.fadeOut();
// Reposition local video after a second, as there is often a delay // Reposition local video after a second, as there is often a delay
@ -411,6 +430,27 @@ function windowResized() {
rePositionCaptions(); rePositionCaptions();
} }
// Checks if connected to at least one peer
function isConnected() {
var connected = false;
// No way to 'break' forEach -> we go through all anyway
VideoChat.connected.forEach(function (value, key, map) {
if (value) {
connected = true;
}
});
return connected;
}
function sendToAllDataChannels(message) {
// key is UUID, value is dataChannel object
dataChannel.forEach(function (value, key, map) {
value.send(message);
});
}
// Fullscreen // Fullscreen
// function openFullscreen() { // function openFullscreen() {
// try { // try {
@ -458,21 +498,23 @@ function windowResized() {
// Mute microphone // Mute microphone
function muteMicrophone() { function muteMicrophone() {
VideoChat.audioEnabled = !VideoChat.audioEnabled;
var audioTrack = null; var audioTrack = null;
// Get audio track to mute
VideoChat.peerConnections.first.getSenders().find(function (s) { VideoChat.peerConnections.forEach(function (value, key, map) {
if (s.track.kind === "audio") { value.getSenders().find(function (s) {
audioTrack = s.track; if (s.track.kind === "audio") {
} audioTrack = s.track;
}
});
audioTrack.enabled = VideoChat.audioEnabled;
}); });
isMuted = !audioTrack.enabled;
audioTrack.enabled = isMuted;
isMuted = !isMuted;
// select mic button and mic button text // select mic button and mic button text
const micButtonIcon = document.getElementById("mic-icon"); const micButtonIcon = document.getElementById("mic-icon");
const micButtonText = document.getElementById("mic-text"); const micButtonText = document.getElementById("mic-text");
// Update mute button text and icon // Update mute button text and icon
if (isMuted) { if (!VideoChat.audioEnabled) {
micButtonIcon.classList.remove("fa-microphone"); micButtonIcon.classList.remove("fa-microphone");
micButtonIcon.classList.add("fa-microphone-slash"); micButtonIcon.classList.add("fa-microphone-slash");
micButtonText.innerText = "Unmute"; micButtonText.innerText = "Unmute";
@ -486,21 +528,25 @@ function muteMicrophone() {
// Pause Video // Pause Video
function pauseVideo() { function pauseVideo() {
var videoTrack = null; VideoChat.videoEnabled = !VideoChat.videoEnabled;
// Get video track to pause
VideoChat.peerConnection.getSenders().find(function (s) { // Communicate pause to all the peers' video tracks
if (s.track.kind === "video") { VideoChat.peerConnections.forEach(function (value, key, map) {
videoTrack = s.track; console.log("pausing video for ", key);
} value.getSenders().find(function (s) {
if (s.track.kind === "video") {
console.log("found video track");
videoTrack = s.track;
}
});
videoTrack.enabled = VideoChat.videoEnabled;
}); });
videoIsPaused = !videoTrack.enabled;
videoTrack.enabled = videoIsPaused;
videoIsPaused = !videoIsPaused;
// select video button and video button text // select video button and video button text
const videoButtonIcon = document.getElementById("video-icon"); const videoButtonIcon = document.getElementById("video-icon");
const videoButtonText = document.getElementById("video-text"); const videoButtonText = document.getElementById("video-text");
// update pause button icon and text // update pause button icon and text
if (videoIsPaused) { if (!VideoChat.videoEnabled) {
localVideoText.text("Video is paused"); localVideoText.text("Video is paused");
localVideoText.show(); localVideoText.show();
videoButtonIcon.classList.remove("fa-video"); videoButtonIcon.classList.remove("fa-video");
@ -518,11 +564,12 @@ function pauseVideo() {
// Swap camera / screen share // Swap camera / screen share
function swap() { function swap() {
// Handle swap video before video call is connected // Handle swap video before video call is connected by checking that there's at least one peer connected
if (!VideoChat.connected) { if (!isConnected()) {
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 // 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");
@ -590,21 +637,27 @@ function switchStreamHelper(stream) {
videoTrack.onended = function () { videoTrack.onended = function () {
swap(); swap();
}; };
if (VideoChat.connected) {
// Find sender // Swap video for every peer connection
const sender = VideoChat.peerConnection.getSenders().find(function (s) { VideoChat.connected.forEach(function (value, key, map) {
// make sure tack types match // Just to be safe, check if connected before swapping video channel
return s.track.kind === videoTrack.kind; if (VideoChat.connected.get(key)) {
}); const sender = VideoChat.peerConnections
// Replace sender track .get(key)
sender.replaceTrack(videoTrack); .getSenders()
} .find(function (s) {
return s.track.kind === videoTrack.kind;
});
sender.replaceTrack(videoTrack);
}
});
// Update local video stream // Update local video stream
VideoChat.localStream = videoTrack; VideoChat.localStream = videoTrack;
// Update local video object // Update local video object
VideoChat.localVideo.srcObject = stream; VideoChat.localVideo.srcObject = stream;
// Unpause video on swap // Unpause video on swap
if (videoIsPaused) { if (!VideoChat.videoEnabled) {
pauseVideo(); pauseVideo();
} }
} }
@ -614,7 +667,7 @@ function switchStreamHelper(stream) {
// Request captions from other user, toggles state // Request captions from other user, toggles state
function requestToggleCaptions() { function requestToggleCaptions() {
// Handle requesting captions before connected // Handle requesting captions before connected
if (!VideoChat.connected) { if (!isConnected()) {
alert("You must be connected to a peer to use Live Caption"); alert("You must be connected to a peer to use Live Caption");
return; return;
} }
@ -635,7 +688,7 @@ function requestToggleCaptions() {
receivingCaptions = true; receivingCaptions = true;
} }
// Send request to get captions over data channel // Send request to get captions over data channel
dataChanel.send("tog:"); sendToAllDataChannels("tog:");
} }
// Start/stop sending captions to other user // Start/stop sending captions to other user
@ -661,7 +714,7 @@ function startSpeech() {
logIt(e); logIt(e);
logIt("error importing speech library"); logIt("error importing speech library");
// Alert other user that they cannon use live caption // Alert other user that they cannon use live caption
dataChanel.send("cap:notusingchrome"); sendToAllDataChannels("cap:notusingchrome");
return; return;
} }
// recognition.maxAlternatives = 3; // recognition.maxAlternatives = 3;
@ -673,6 +726,7 @@ function startSpeech() {
let interimTranscript = ""; let interimTranscript = "";
for (let i = event.resultIndex, len = event.results.length; i < len; i++) { for (let i = event.resultIndex, len = event.results.length; i < len; i++) {
var transcript = event.results[i][0].transcript; var transcript = event.results[i][0].transcript;
console.log(transcript);
if (event.results[i].isFinal) { if (event.results[i].isFinal) {
finalTranscript += transcript; finalTranscript += transcript;
} else { } else {
@ -680,7 +734,7 @@ function startSpeech() {
var charsToKeep = interimTranscript.length % 100; var charsToKeep = interimTranscript.length % 100;
// Send captions over data chanel, // Send captions over data chanel,
// subtracting as many complete 100 char slices from start // subtracting as many complete 100 char slices from start
dataChanel.send( sendToAllDataChannels(
"cap:" + "cap:" +
interimTranscript.substring(interimTranscript.length - charsToKeep) interimTranscript.substring(interimTranscript.length - charsToKeep)
); );
@ -757,6 +811,7 @@ function recieveCaptions(captions) {
// Text Chat // Text Chat
// Add text message to chat screen on page // Add text message to chat screen on page
function addMessageToScreen(msg, isOwnMessage) { function addMessageToScreen(msg, isOwnMessage) {
// If nickname is undefined or null, user didn't input a nickname
if (isOwnMessage) { if (isOwnMessage) {
$(".chat-messages").append( $(".chat-messages").append(
'<div class="message-item customer cssanimation fadeInBottom"><div class="message-bloc"><div class="message">' + '<div class="message-item customer cssanimation fadeInBottom"><div class="message-bloc"><div class="message">' +
@ -783,7 +838,7 @@ chatInput.addEventListener("keypress", function (event) {
// Make links clickable // Make links clickable
msg = msg.autoLink(); msg = msg.autoLink();
// Send message over data channel // Send message over data channel
dataChanel.send("mes:" + msg); sendToAllDataChannels("mes:" + VideoChat.nickname + ": " + msg);
// Add message to screen // Add message to screen
addMessageToScreen(msg, true); addMessageToScreen(msg, true);
// Auto scroll chat down // Auto scroll chat down