From 37b90354337efb0257bd27da22adf789e8fd71d7 Mon Sep 17 00:00:00 2001 From: Taichi Kato Date: Mon, 1 Jun 2020 12:30:23 +0800 Subject: [PATCH 01/10] Updated CSS to adopt better to mobile screens --- public/css/chat.css | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/public/css/chat.css b/public/css/chat.css index aef5584..0a746cc 100644 --- a/public/css/chat.css +++ b/public/css/chat.css @@ -111,7 +111,9 @@ a { #wrapper { display: flex; - flex-direction: column; + /* flex: 1 1 20em; */ + /* flex-basis: 30%; */ + flex-direction: row; align-items: center; flex-wrap: wrap; justify-content: center; @@ -122,8 +124,8 @@ a { left: 50%; -ms-transform: translate(-50%, -50%); transform: translate(-50%, -50%); - width: 65%; - max-height: 100%; + width: 100%; + max-height: 90vh; max-width: 100%; } @@ -146,7 +148,7 @@ a { } #remote-video:first-child:nth-last-child(1) { /* -or- li:only-child { */ - max-height: 80vh; +width: 80vw; } /* two items */ @@ -415,7 +417,22 @@ button:hover { margin: 0; line-height: 2rem; } - + #wrapper { + display: flex; + flex-direction: row; + align-items: center; + flex-wrap: wrap; + justify-content: center; + padding: 0; + margin: 0; + position: absolute; + top: 50%; + left: 50%; + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, calc(-50% - 3rem)); + max-height: 90%; + max-width: 100%; + } #remote-video { /* width: 75vw; height: calc((16/9) * 75vw); */ From 003fca03990111c08e8f0c4bf8d76a812cf1dc4e Mon Sep 17 00:00:00 2001 From: Khush Jammu Date: Mon, 1 Jun 2020 12:45:10 +0800 Subject: [PATCH 02/10] add stub for bitrate downscaling --- public/js/chat.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/public/js/chat.js b/public/js/chat.js index 8634978..49838d9 100644 --- a/public/js/chat.js +++ b/public/js/chat.js @@ -499,6 +499,12 @@ function sendToAllDataChannels(message) { // } // End Fullscreen + +// Downscale own stream +function downscaleStreams() { + // To be implemented +} + // Mute microphone function muteMicrophone() { var audioTrack = null; From f72d77494e2050ffbffc5f007a43018fbb2e3b22 Mon Sep 17 00:00:00 2001 From: Arya Vohra Date: Tue, 2 Jun 2020 10:52:54 +0800 Subject: [PATCH 03/10] Testing bitrate/res downscaling on n>4 --- public/js/chat.js | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/public/js/chat.js b/public/js/chat.js index 49838d9..eb52b4e 100644 --- a/public/js/chat.js +++ b/public/js/chat.js @@ -225,6 +225,15 @@ var VideoChat = { break; } }; + + + // Downscale send resolution and bitrate if num in room > 4 + if (VideoChat.peerConnections.size > 3) { + for (var pc in VideoChat.peerConnections) { + downscaleStream(pc); + } + } + callback(uuid); }; }, @@ -500,9 +509,25 @@ function sendToAllDataChannels(message) { // End Fullscreen -// Downscale own stream -function downscaleStreams() { - // To be implemented +// Downscale single stream +async function downscaleStream(pc) { + height = 480; + rate = 800000; + try { + do { + h = height; + r = rate; + const [sender] = pc.getSenders(); + const ratio = sender.track.getSettings().height / height; + const params = sender.getParameters(); + if (!params.encodings) params.encodings = [{}]; // Firefox workaround! + params.encodings[0].scaleResolutionDownBy = Math.max(ratio, 1); + params.encodings[0].maxBitrate = rate; + await sender.setParameters(params); + } while (h != height); + } catch (e) { + logIt(e); + } } // Mute microphone From 38ead5c785bcdd87cb43338287f3eb31d0a50628 Mon Sep 17 00:00:00 2001 From: Arya Vohra Date: Tue, 2 Jun 2020 11:33:15 +0800 Subject: [PATCH 04/10] Downscaling to 360p for larger calls working --- public/css/chat.css | 2 +- public/js/chat.js | 79 +++++++++++++++++++++++++++------------------ public/landing.html | 15 ++++----- 3 files changed, 55 insertions(+), 41 deletions(-) diff --git a/public/css/chat.css b/public/css/chat.css index cc49ab6..3c80d13 100644 --- a/public/css/chat.css +++ b/public/css/chat.css @@ -145,7 +145,7 @@ a { padding: 10px; } #remote-video:first-child:nth-last-child(1) { -/* -or- li:only-child { */ + /* -or- li:only-child { */ max-height: 80vh; } diff --git a/public/js/chat.js b/public/js/chat.js index eb52b4e..3404d46 100644 --- a/public/js/chat.js +++ b/public/js/chat.js @@ -204,7 +204,9 @@ var VideoChat = { break; case "disconnected": logIt("disconnected - UUID " + uuid); - VideoChat.remoteVideoWrapper.removeChild(document.querySelectorAll(`[uuid="${uuid}"]`)[0]); + VideoChat.remoteVideoWrapper.removeChild( + document.querySelectorAll(`[uuid="${uuid}"]`)[0] + ); VideoChat.connected.delete(uuid); VideoChat.peerConnections.delete(uuid); dataChannel.delete(uuid); @@ -225,15 +227,6 @@ var VideoChat = { break; } }; - - - // Downscale send resolution and bitrate if num in room > 4 - if (VideoChat.peerConnections.size > 3) { - for (var pc in VideoChat.peerConnections) { - downscaleStream(pc); - } - } - callback(uuid); }; }, @@ -370,6 +363,12 @@ var VideoChat = { VideoChat.connected.set(uuid, true); // Hide caption status text captionText.fadeOut(); + // Downscale send resolution and bitrate if num in room > 4 + if (VideoChat.peerConnections.size > 3) { + VideoChat.peerConnections.forEach(function (value, key, map) { + downscaleStream(value); + }); + } // Reposition local video after a second, as there is often a delay // between adding a stream and the height of the video div changing setTimeout(() => rePositionLocalVideo(), 500); @@ -508,16 +507,18 @@ function sendToAllDataChannels(message) { // } // End Fullscreen - // Downscale single stream -async function downscaleStream(pc) { - height = 480; +async function downscaleStream(pc, applying = false) { + height = 240; rate = 800000; + if (applying) return; try { + applying = true; do { h = height; - r = rate; - const [sender] = pc.getSenders(); + const sender = pc.getSenders().find(function (s) { + return s.track.kind === "video"; + }); const ratio = sender.track.getSettings().height / height; const params = sender.getParameters(); if (!params.encodings) params.encodings = [{}]; // Firefox workaround! @@ -527,6 +528,8 @@ async function downscaleStream(pc) { } while (h != height); } catch (e) { logIt(e); + } finally { + applying = false; } } @@ -542,7 +545,7 @@ function muteMicrophone() { }); audioTrack.enabled = VideoChat.audioEnabled; }); - + // select mic button and mic button text const micButtonIcon = document.getElementById("mic-icon"); const micButtonText = document.getElementById("mic-text"); @@ -894,15 +897,16 @@ function uuidToColor(uuid) { // Using uuid to generate random. unique pastel color var hash = 0; for (var i = 0; i < uuid.length; i++) { - hash = uuid.charCodeAt(i) + ((hash << 5) - hash); - hash = hash & hash; + hash = uuid.charCodeAt(i) + ((hash << 5) - hash); + hash = hash & hash; } var hue = Math.abs(hash % 360); - console.log(hue); // Ensure color is not similar to other colors - var availColors = Array.from({length: 18}, (x,i) => i*20); - VideoChat.peerColors.forEach(function(value, key, map) {availColors[Math.floor(value/20)] = null}); - if (availColors[Math.floor(hue/20)] == null) { + var availColors = Array.from({ length: 18 }, (x, i) => i * 20); + VideoChat.peerColors.forEach(function (value, key, map) { + availColors[Math.floor(value / 20)] = null; + }); + if (availColors[Math.floor(hue / 20)] == null) { for (var i = 0; i < availColors.length; i++) { if (availColors[i] != null) { hue = (hue % 20) + availColors[i]; @@ -917,7 +921,9 @@ function uuidToColor(uuid) { // Sets the border color of uuid's stream function setStreamColor(uuid) { const color = uuidToColor(uuid); - document.querySelectorAll(`[uuid="${uuid}"]`)[0].style.border = `3px solid ${color}`; + document.querySelectorAll( + `[uuid="${uuid}"]` + )[0].style.border = `3px solid ${color}`; VideoChat.peerColors[uuid] = color; } @@ -952,18 +958,27 @@ function togglePictureInPicture() { logIt("Error exiting pip."); logIt(error); }); - } else if (VideoChat.remoteVideoWrapper.lastChild.webkitPresentationMode === "inline") { - VideoChat.remoteVideoWrapper.lastChild.webkitSetPresentationMode("picture-in-picture"); } else if ( - VideoChat.remoteVideoWrapper.lastChild.webkitPresentationMode === "picture-in-picture" + VideoChat.remoteVideoWrapper.lastChild.webkitPresentationMode === "inline" ) { - VideoChat.remoteVideoWrapper.lastChild.webkitSetPresentationMode("inline"); + VideoChat.remoteVideoWrapper.lastChild.webkitSetPresentationMode( + "picture-in-picture" + ); + } else if ( + VideoChat.remoteVideoWrapper.lastChild.webkitPresentationMode === + "picture-in-picture" + ) { + VideoChat.remoteVideoWrapper.lastChild.webkitSetPresentationMode( + "inline" + ); } else { - VideoChat.remoteVideoWrapper.lastChild.requestPictureInPicture().catch((error) => { - alert( - "You must be connected to another person to enter picture in picture." - ); - }); + VideoChat.remoteVideoWrapper.lastChild + .requestPictureInPicture() + .catch((error) => { + alert( + "You must be connected to another person to enter picture in picture." + ); + }); } } else { alert( diff --git a/public/landing.html b/public/landing.html index 7e1b62d..693a094 100755 --- a/public/landing.html +++ b/public/landing.html @@ -67,9 +67,9 @@ class="mt-0 mb-32 reveal-from-bottom" data-reveal-delay="300" > - Simple, Secure, and Fast. Peer to peer group video - calling provides quality and latency simply not - available with traditional technology. + Simple, Secure, and Fast. Peer to peer group video calling + provides quality and latency simply not available with + traditional technology.

@@ -248,8 +247,8 @@

Total Privacy and Security

Zipcall is built privacy first. Each chat is single use, - and end to end state of the art encryption means your calls - are exactly that. Your calls. + and end to end state of the art encryption means your + calls are exactly that. Your calls.

From fb3c28b6aa3b081739f102a4e725cc21c595f4ef Mon Sep 17 00:00:00 2001 From: Khush Jammu Date: Wed, 3 Jun 2020 17:44:05 +0800 Subject: [PATCH 05/10] add reloading for erronous disconnects --- public/js/chat.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/public/js/chat.js b/public/js/chat.js index 3404d46..4c41278 100644 --- a/public/js/chat.js +++ b/public/js/chat.js @@ -203,6 +203,12 @@ var VideoChat = { logIt("connected"); break; case "disconnected": + // First possibility: we disconnected from the peer + if (VideoChat.socket.connect().connected === false) { + location.reload(); + } + + // Second possibility: the peer disconnected from us logIt("disconnected - UUID " + uuid); VideoChat.remoteVideoWrapper.removeChild( document.querySelectorAll(`[uuid="${uuid}"]`)[0] From 604099e097d71af53415ff716fd984ed1b517a8e Mon Sep 17 00:00:00 2001 From: Khush Jammu Date: Wed, 3 Jun 2020 17:50:45 +0800 Subject: [PATCH 06/10] fix property in disconnect --- public/js/chat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/chat.js b/public/js/chat.js index 4c41278..87c882e 100644 --- a/public/js/chat.js +++ b/public/js/chat.js @@ -204,7 +204,7 @@ var VideoChat = { break; case "disconnected": // First possibility: we disconnected from the peer - if (VideoChat.socket.connect().connected === false) { + if (VideoChat.socket.connected === false) { location.reload(); } From 04c9f14d084e7d7e9d8b65b6e42668df854afa15 Mon Sep 17 00:00:00 2001 From: Arya Vohra Date: Wed, 3 Jun 2020 17:53:55 +0800 Subject: [PATCH 07/10] Color fix and removed redundant definition of borderColor --- public/js/chat.js | 69 +++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/public/js/chat.js b/public/js/chat.js index 3404d46..7feaebd 100644 --- a/public/js/chat.js +++ b/public/js/chat.js @@ -32,7 +32,6 @@ var dataChannel = new Map(); var VideoChat = { videoEnabled: true, audioEnabled: true, - borderColor: `hsl(${Math.floor(Math.random() * 360)}, 70%, 80%)`, connected: new Map(), localICECandidates: {}, socket: io(), @@ -364,11 +363,11 @@ var VideoChat = { // Hide caption status text captionText.fadeOut(); // Downscale send resolution and bitrate if num in room > 4 - if (VideoChat.peerConnections.size > 3) { - VideoChat.peerConnections.forEach(function (value, key, map) { - downscaleStream(value); - }); - } + // if (VideoChat.peerConnections.size > 3) { + // VideoChat.peerConnections.forEach(function (value, key, map) { + // downscaleStream(value); + // }); + // } // Reposition local video after a second, as there is often a delay // between adding a stream and the height of the video div changing setTimeout(() => rePositionLocalVideo(), 500); @@ -508,30 +507,30 @@ function sendToAllDataChannels(message) { // End Fullscreen // Downscale single stream -async function downscaleStream(pc, applying = false) { - height = 240; - rate = 800000; - if (applying) return; - try { - applying = true; - do { - h = height; - const sender = pc.getSenders().find(function (s) { - return s.track.kind === "video"; - }); - const ratio = sender.track.getSettings().height / height; - const params = sender.getParameters(); - if (!params.encodings) params.encodings = [{}]; // Firefox workaround! - params.encodings[0].scaleResolutionDownBy = Math.max(ratio, 1); - params.encodings[0].maxBitrate = rate; - await sender.setParameters(params); - } while (h != height); - } catch (e) { - logIt(e); - } finally { - applying = false; - } -} +// async function downscaleStream(pc, applying = false) { +// height = 240; +// rate = 800000; +// if (applying) return; +// try { +// applying = true; +// do { +// h = height; +// const sender = pc.getSenders().find(function (s) { +// return s.track.kind === "video"; +// }); +// const ratio = sender.track.getSettings().height / height; +// const params = sender.getParameters(); +// if (!params.encodings) params.encodings = [{}]; // Firefox workaround! +// params.encodings[0].scaleResolutionDownBy = Math.max(ratio, 1); +// params.encodings[0].maxBitrate = rate; +// await sender.setParameters(params); +// } while (h != height); +// } catch (e) { +// logIt(e); +// } finally { +// applying = false; +// } +// } // Mute microphone function muteMicrophone() { @@ -902,20 +901,20 @@ function uuidToColor(uuid) { } var hue = Math.abs(hash % 360); // Ensure color is not similar to other colors - var availColors = Array.from({ length: 18 }, (x, i) => i * 20); + var availColors = Array.from({ length: 9 }, (x, i) => i * 40); VideoChat.peerColors.forEach(function (value, key, map) { - availColors[Math.floor(value / 20)] = null; + availColors[Math.floor(value / 40)] = null; }); - if (availColors[Math.floor(hue / 20)] == null) { + if (availColors[Math.floor(hue / 40)] == null) { for (var i = 0; i < availColors.length; i++) { if (availColors[i] != null) { - hue = (hue % 20) + availColors[i]; + hue = (hue % 40) + availColors[i]; availColors[i] = null; break; } } } - return `hsl(${hue},100%,70%)`; + return `hsl(${hue},100%,60%)`; } // Sets the border color of uuid's stream From 5bddd4463dbdd31ca7e093166b9dcd5537dd601d Mon Sep 17 00:00:00 2001 From: Arya Vohra Date: Thu, 4 Jun 2020 12:49:34 +0800 Subject: [PATCH 08/10] Generating colors at the right rtime --- public/js/chat.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/js/chat.js b/public/js/chat.js index df4812d..0c528af 100644 --- a/public/js/chat.js +++ b/public/js/chat.js @@ -37,10 +37,10 @@ var VideoChat = { socket: io(), remoteVideoWrapper: document.getElementById("wrapper"), localVideo: document.getElementById("local-video"), + peerColors: new Map(), peerConnections: new Map(), recognition: undefined, borderColor: undefined, - peerColors: new Map(), // 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 @@ -98,17 +98,17 @@ var VideoChat = { }, }); - VideoChat.borderColor = uuidToColor(VideoChat.socket.id); VideoChat.localVideo.srcObject = stream; - VideoChat.localVideo.style.border = `3px solid ${VideoChat.borderColor}`; // Now we're ready to join the chat room. VideoChat.socket.emit("join", roomHash); + VideoChat.borderColor = uuidToColor(VideoChat.socket.id); + VideoChat.localVideo.style.border = `3px solid ${VideoChat.borderColor}`; // Add listeners to the websocket VideoChat.socket.on("full", chatRoomFull); VideoChat.socket.on("offer", VideoChat.onOffer); - VideoChat.socket.on("willInitiateCall", VideoChat.call); + VideoChat.socket.on("initiateCall", VideoChat.call); // Set up listeners on the socket VideoChat.socket.on("candidate", VideoChat.onCandidate); From 84716b21616f0ec6532b5f4d44c2f93c0b6bf828 Mon Sep 17 00:00:00 2001 From: Arya Vohra Date: Thu, 4 Jun 2020 13:00:28 +0800 Subject: [PATCH 09/10] Change listener name --- server.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server.js b/server.js index de98ade..a82b726 100644 --- a/server.js +++ b/server.js @@ -8,6 +8,7 @@ var twillioAccountSID = var twilio = require("twilio")(twillioAccountSID, twillioAuthToken); var express = require("express"); var app = express(); +const fs = require('fs'); var http = require("http").createServer(app); var io = require("socket.io")(http); var path = require("path"); @@ -80,9 +81,9 @@ io.on("connection", function (socket) { socket.join(room); } else if (numClients < 5) { socket.join(room); - // When the client is not the first to join the room, all clients are ready. - logIt("Broadcasting ready message", room); - socket.broadcast.to(room).emit("willInitiateCall", socket.id, room); + logIt("Broadcasting request to connect with new peer...", room); + // Emits message to ask peers in room to connect with joining peer + socket.broadcast.to(room).emit("initiateCall", socket.id, room); } else { logIt( "room already full with " + numClients + " people in the room.", @@ -132,7 +133,7 @@ io.on("connection", function (socket) { }); // Listen for Heroku port, otherwise just use 3000 -var port = process.env.PORT || 3000; +var port = process.env.PORT || 443; http.listen(port, function () { console.log("http://localhost:" + port); }); From 9ccb1b138ea28784c6193656f48889576ac675fa Mon Sep 17 00:00:00 2001 From: Khush Jammu Date: Thu, 4 Jun 2020 15:41:28 +0800 Subject: [PATCH 10/10] overhaul disconnect handling --- public/js/chat.js | 46 +++++++++++++++++++++++++++++----------------- server.js | 5 +++++ 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/public/js/chat.js b/public/js/chat.js index 0c528af..b3b8224 100644 --- a/public/js/chat.js +++ b/public/js/chat.js @@ -106,11 +106,10 @@ var VideoChat = { VideoChat.localVideo.style.border = `3px solid ${VideoChat.borderColor}`; // Add listeners to the websocket + VideoChat.socket.on("leave", VideoChat.onLeave); VideoChat.socket.on("full", chatRoomFull); VideoChat.socket.on("offer", VideoChat.onOffer); VideoChat.socket.on("initiateCall", VideoChat.call); - - // Set up listeners on the socket VideoChat.socket.on("candidate", VideoChat.onCandidate); VideoChat.socket.on("answer", VideoChat.onAnswer); VideoChat.socket.on("requestToggleCaptions", () => toggleSendCaptions()); @@ -119,6 +118,7 @@ var VideoChat = { ); }, + // Initiate a call with newly joined peer call: function (uuid, room) { logIt(`call >>> Initiating call with ${uuid}...`); VideoChat.socket.on( @@ -130,6 +130,24 @@ var VideoChat = { VideoChat.socket.emit("token", roomHash, uuid); }, + // Handle a peer leaving the room + onLeave: function(uuid) { + logIt("disconnected - UUID " + uuid); + // Remove video element + VideoChat.remoteVideoWrapper.removeChild( + document.querySelectorAll(`[uuid="${uuid}"]`)[0] + ); + + // Delete connection & metadata + VideoChat.connected.delete(uuid); + VideoChat.peerConnections.delete(uuid); + dataChannel.delete(uuid); + + if (VideoChat.peerConnections.size === 0) { + displayWaitingCaption(); + } + }, + establishConnection: function (correctUuid, callback) { return function (token, uuid) { if (correctUuid != uuid) { @@ -202,23 +220,12 @@ var VideoChat = { logIt("connected"); break; case "disconnected": - // First possibility: we disconnected from the peer - if (VideoChat.socket.connected === false) { + // Remove UUID if connection to server is intact + if (VideoChat.socket.connected) { + VideoChat.onLeave(uuid); + } else { location.reload(); } - - // Second possibility: the peer disconnected from us - logIt("disconnected - UUID " + uuid); - VideoChat.remoteVideoWrapper.removeChild( - document.querySelectorAll(`[uuid="${uuid}"]`)[0] - ); - VideoChat.connected.delete(uuid); - VideoChat.peerConnections.delete(uuid); - dataChannel.delete(uuid); - - if (VideoChat.peerConnections.size === 0) { - displayWaitingCaption(); - } break; case "failed": logIt("failed"); @@ -1001,6 +1008,11 @@ function displayWaitingCaption() { rePositionCaptions(); } +window.onbeforeunload = function () { + VideoChat.socket.emit("leave", roomHash); + return null; +}; + function startUp() { // Try and detect in-app browsers and redirect var ua = navigator.userAgent || navigator.vendor || window.opera; diff --git a/server.js b/server.js index a82b726..84c4d00 100644 --- a/server.js +++ b/server.js @@ -93,6 +93,11 @@ io.on("connection", function (socket) { } }); + socket.on("leave", function (room) { + logIt("A client has left the room", room); + socket.broadcast.to(room).emit("leave", socket.id); + }); + // When receiving the token message, use the Twilio REST API to request an // token to get ephemeral credentials to use the TURN server. socket.on("token", function (room, uuid) {