diff --git a/public/chat.html b/public/chat.html index b067747..62a9e0e 100644 --- a/public/chat.html +++ b/public/chat.html @@ -110,7 +110,9 @@ -
Start Live Caption
+
+ Start Live Caption +
diff --git a/public/images/portrait.png b/public/images/portrait.png index 813a343..14b589d 100644 Binary files a/public/images/portrait.png and b/public/images/portrait.png differ diff --git a/public/js/chat.js b/public/js/chat.js index c42ff56..cff7d8f 100644 --- a/public/js/chat.js +++ b/public/js/chat.js @@ -21,6 +21,10 @@ const chatInput = document.querySelector(".compose input"); const pipVideo = document.getElementById("remote-video"); const remoteVideo = $("#remote-video"); 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, @@ -45,19 +49,19 @@ var VideoChat = { }) .then((stream) => { VideoChat.onMediaStream(stream); - $("#local-video-text").text("Drag Me"); - setTimeout(() => $("#local-video-text").fadeOut(), 5000); + 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); }); }, - // The onMediaStream function receives the media stream as an argument. onMediaStream: function (stream) { logIt("onMediaStream"); VideoChat.localStream = stream; @@ -105,8 +109,7 @@ var VideoChat = { // Set up a callback to run when we have the ephemeral token to use Twilio's TURN server. startCall: function (event) { - logIt("startCall"); - logIt(">>> Sending token request..."); + logIt("startCall >>> Sending token request..."); VideoChat.socket.on("token", VideoChat.onToken(VideoChat.createOffer)); VideoChat.socket.emit("token", roomHash); }, @@ -170,20 +173,6 @@ var VideoChat = { case "disconnected": case "failed": logIt("failed/disconnected"); - // dataChanel.close(); - // logIt("Refreshing page..."); - // function reloadScript(id) { - // var $el = $("#" + id); - // $("#" + id).replaceWith( - // '' - // ); - // } - // reloadScript("chatJS"); - // alert("reloding"); location.reload(); break; case "closed": @@ -324,6 +313,7 @@ var VideoChat = { }, }; +// Get name of browser session using user agent function getBrowserName() { var name = "Unknown"; if (window.navigator.userAgent.indexOf("MSIE") !== -1) { @@ -339,40 +329,47 @@ function getBrowserName() { return name; } +// Basic logging class wrapper function logIt(message, error) { console.log(message); } +// Called when socket receives message that room is full function chatRoomFull() { alert( "Chat room is full. Check to make sure you don't have multiple open tabs, or try with a new room link" ); + // Exit room and redirect window.location.href = "/pickname"; } +// Reposition local video to top left of remote video function rePositionLocalVideo() { + // Get position of remote video var bounds = remoteVideo.position(); bounds.top += 10; bounds.left += 10; + // Set position of local video $("#moveable").css(bounds); } +// Reposition captions to bottom of video function rePositionCaptions() { + // Get remote video position var bounds = remoteVideo.position(); bounds.top -= 10; bounds.top = bounds.top + remoteVideo.height() - 1 * captionText.height(); + // Reposition captions captionText.css(bounds); } +// Called when window is resized function windowResized() { rePositionLocalVideo(); rePositionCaptions(); } -// // Fullscreen -// - // function openFullscreen() { // try { // // var elem = document.getElementById("remote-video"); @@ -415,50 +412,40 @@ function windowResized() { // } // setTimeout(windowResized, 1000); // } - -// // End Fullscreen -// -// // Mute microphone -// - function muteMicrophone() { var audioTrack = null; + // Get audio track to mute VideoChat.peerConnection.getSenders().find(function (s) { if (s.track.kind === "audio") { audioTrack = s.track; } }); - isMuted = !audioTrack.enabled; audioTrack.enabled = isMuted; isMuted = !isMuted; - - const micIcon = document.getElementById("mic-icon"); - const micText = document.getElementById("mic-text"); + // select mic button and mic button text + const micButtonIcon = document.getElementById("mic-icon"); + const micButtonText = document.getElementById("mic-text"); + // Update mute button text and icon if (isMuted) { - micIcon.classList.remove("fa-microphone"); - micIcon.classList.add("fa-microphone-slash"); - micText.innerText = "Unmute"; + micButtonIcon.classList.remove("fa-microphone"); + micButtonIcon.classList.add("fa-microphone-slash"); + micButtonText.innerText = "Unmute"; } else { - micIcon.classList.add("fa-microphone"); - micIcon.classList.remove("fa-microphone-slash"); - micText.innerText = "Mute"; + micButtonIcon.classList.add("fa-microphone"); + micButtonIcon.classList.remove("fa-microphone-slash"); + micButtonText.innerText = "Mute"; } } - -// // End Mute microphone -// -// // Pause Video -// - function pauseVideo() { var videoTrack = null; + // Get video track to pause VideoChat.peerConnection.getSenders().find(function (s) { if (s.track.kind === "video") { videoTrack = s.track; @@ -467,27 +454,23 @@ function pauseVideo() { videoIsPaused = !videoTrack.enabled; videoTrack.enabled = videoIsPaused; videoIsPaused = !videoIsPaused; - const micIcon = document.getElementById("video-icon"); - const micText = document.getElementById("video-text"); + // select video button and video button text + const videoButtonIcon = document.getElementById("video-icon"); + const videoButtonText = document.getElementById("video-text"); + // update pause button icon and text if (videoIsPaused) { - micIcon.classList.remove("fa-video"); - micIcon.classList.add("fa-video-slash"); - micText.innerText = "Unpause Video"; + videoButtonIcon.classList.remove("fa-video"); + videoButtonIcon.classList.add("fa-video-slash"); + videoButtonText.innerText = "Unpause Video"; } else { - micIcon.classList.add("fa-video"); - micIcon.classList.remove("fa-video-slash"); - micText.innerText = "Pause Video"; + videoButtonIcon.classList.add("fa-video"); + videoButtonIcon.classList.remove("fa-video-slash"); + videoButtonText.innerText = "Pause Video"; } } - -// // End pause Video -// -// // Swap camera / screen share -// - function swap() { if (!VideoChat.connected) { alert("You must join a call before you can share your screen."); @@ -500,20 +483,13 @@ function swap() { Snackbar.show({ text: "Please allow screen share. Click the middle of the picture above and then press share.", - // actionText: "Show Me How", width: "400px", pos: "bottom-center", actionTextColor: "#616161", duration: 50000, - - // onActionClick: function (element) { - // window.open( - // "https://getacclaim.zendesk.com/hc/en-us/articles/360001547832-Setting-the-default-camera-on-your-browser", - // "_blank" - // ); - // }, }); + // Request screen share navigator.mediaDevices .getDisplayMedia({ video: true, @@ -565,38 +541,42 @@ function switchStreamHelper(stream) { } VideoChat.localStream = videoTrack; VideoChat.localVideo.srcObject = stream; + // Unpause video on swap if (videoIsPaused) { pauseVideo(); } } - -// // End swap camera / screen share -// -// // Live caption -// - +// Request captions from other user, toggles state function requestToggleCaptions() { + // Handle requesting captions before connected if (!VideoChat.connected) { alert("You must be connected to a peer to use Live Caption"); return; } if (receivingCaptions) { captionText.text("").fadeOut(); - $("#caption-text").text("Start Live Caption"); + captionButtontext.text("Start Live Caption"); receivingCaptions = false; } else { - alert( - "This is an experimental feature. Live transcription requires the other user to have chrome." - ); - $("#caption-text").text("End Live Caption"); + Snackbar.show({ + text: + "This is an experimental feature. Live caption requires the other user to be using Chrome", + width: "400px", + pos: "bottom-center", + actionTextColor: "#616161", + duration: 10000, + }); + captionButtontext.text("End Live Caption"); receivingCaptions = true; } + // Send request to get captions over data channel dataChanel.send("tog:"); } +// Start/stop sending captions to other user function toggleSendCaptions() { if (sendingCaptions) { sendingCaptions = false; @@ -607,29 +587,26 @@ function toggleSendCaptions() { } } +// Start speech recognition function startSpeech() { try { var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; - var recognition = new SpeechRecognition(); - VideoChat.recognition = recognition; + VideoChat.recognition = new SpeechRecognition(); } catch (e) { sendingCaptions = false; logIt(e); logIt("error importing speech library"); + // Alert other user that they cannon use live caption dataChanel.send("cap:notusingchrome"); return; } - - // If false, the recording will stop after a few seconds of silence. - // When true, the silence period is longer (about 15 seconds), - // allowing us to keep recording even when the user pauses. - recognition.continuous = true; - recognition.interimResults = true; // recognition.maxAlternatives = 3; - + VideoChat.recognition.continuous = true; + // Show results that aren't final + VideoChat.recognition.interimResults = true; var finalTranscript; - recognition.onresult = (event) => { + VideoChat.recognition.onresult = (event) => { let interimTranscript = ""; for (let i = event.resultIndex, len = event.results.length; i < len; i++) { var transcript = event.results[i][0].transcript; @@ -638,6 +615,8 @@ function startSpeech() { } else { interimTranscript += transcript; var charsToKeep = interimTranscript.length % 100; + // Send captions over data chanel, + // subtracting as many complete 100 char slices from start dataChanel.send( "cap:" + interimTranscript.substring(interimTranscript.length - charsToKeep) @@ -645,117 +624,105 @@ function startSpeech() { } } }; - - recognition.onstart = function () { - console.log("recording on"); - }; - - recognition.onspeechend = function () { - console.log("on speech end"); - }; - - recognition.onerror = function (event) { - if (event.error === "no-speech") { - console.log("no speech detected"); - } - }; - - recognition.onend = function () { - console.log("on end"); - console.log(sendingCaptions); + VideoChat.recognition.onend = function () { + logIt("on speech recording end"); + // Restart speech recognition if user has not stopped it if (sendingCaptions) { startSpeech(); } else { VideoChat.recognition.stop(); } }; - - recognition.start(); + VideoChat.recognition.start(); } +// Recieve captions over datachannel function recieveCaptions(captions) { if (receivingCaptions) { captionText.text("").fadeIn(); } else { captionText.text("").fadeOut(); } + // Other user is not using chrome if (captions === "notusingchrome") { alert( "Other caller must be using chrome for this feature to work. Live Caption turned off." ); receivingCaptions = false; captionText.text("").fadeOut(); - $("#caption-text").text("Start Live Caption"); + captionButtontext.text("Start Live Caption"); return; } captionText.text(captions); rePositionCaptions(); } - -// // End Live caption -// -// -// Chat -// +// Text Chat +// Add text message to chat screen on page +function addMessageToScreen(msg) { + $(".chat-messages").append( + '
' + + msg + + "
" + ); +} +// Listen for enter press on chat input chatInput.addEventListener("keypress", function (event) { if (event.keyCode === 13) { + // Prevent page refresh on enter event.preventDefault(); var msg = chatInput.value; + // Prevent cross site scripting msg = msg.replace(//g, ">"); + // Make links clickable + msg = msg.autoLink(); + // Send message over data channel dataChanel.send("mes:" + msg); - $(".chat-messages").append( - '
' + - msg.autoLink() + - "
" - ); - $("#chat-zone").scrollTop($("#chat-zone")[0].scrollHeight); + // Add message to screen + addMessageToScreen(msg); + // Auto scroll chat down + chatZone.scrollTop(chatZone[0].scrollHeight); + // Clear chat input chatInput.value = ""; } }); +// Called when a message is recieved over the dataChannel function handleRecieveMessage(msg) { - msg = msg.replace(//g, ">"); - $(".chat-messages").append( - '
' + - msg.autoLink() + - "
" - ); - $("#chat-zone").scrollTop($("#chat-zone")[0].scrollHeight); - if ($("#entire-chat").is(":hidden")) { + // Add message to screen + addMessageToScreen(msg); + // Auto scroll chat down + chatZone.scrollTop(chatZone[0].scrollHeight); + // Show chat if hidden + if (entireChat.is(":hidden")) { toggleChat(); } } +// Show and hide chat function toggleChat() { - var entireChat = $("#entire-chat"); var chatIcon = document.getElementById("chat-icon"); var chatText = $("#chat-text"); if (entireChat.is(":visible")) { entireChat.fadeOut(); + // Update show chat buttton chatText.text("Show Chat"); chatIcon.classList.remove("fa-comment-slash"); chatIcon.classList.add("fa-comment"); } else { entireChat.fadeIn(); + // Update show chat buttton chatText.text("Hide Chat"); chatIcon.classList.remove("fa-comment"); chatIcon.classList.add("fa-comment-slash"); } } +// End Text chat -// -// End chat -// - -// //Picture in picture -// - function togglePictureInPicture() { - // pipVideo.webkitSetPresentationMode("picture-in-picture"); if ( "pictureInPictureEnabled" in document || pipVideo.webkitSetPresentationMode @@ -782,27 +749,17 @@ function togglePictureInPicture() { ); } } -// pipVideo.addEventListener("enterpictureinpicture", () => { -// $("#pip-text").text("Exit Picture in Picture"); -// alert("enter pip"); -// }); -// -// pipVideo.addEventListener("leavepictureinpicture", () => { -// $("#pip-text").text("Enter Picture in Picture"); -// alert("exit pip"); -// }); -// //Picture in picture -// function startUp() { + // Redirect mobile browsers if ( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent ) ) { alert( - "Zipcall is not currently supported on mobile. Please try again on dekstop." + "Zipcall is not currently supported on mobile. Please try again on desktop." ); window.location.href = "/notsupported"; } @@ -814,23 +771,25 @@ function startUp() { window.location.href = "/notsupported"; } + // Set tab title document.title = "Zipcall - " + url.substring(url.lastIndexOf("/") + 1); // get webcam on load VideoChat.requestMediaStream(); + // Captions hidden by default captionText.text("").fadeOut(); + // Make local video draggable $("#moveable").draggable({ containment: "window" }); + // Hide button labels on load $(".HoverState").hide(); - $("#entire-chat").hide(); + // Text chat hidden by default + entireChat.hide(); - // // Show hide button labels on hover - // - $(document).ready(function () { $(".hoverButton").mouseover(function () { $(".HoverState").hide(); @@ -841,16 +800,8 @@ function startUp() { }); }); - // - // End show hide button labels on hover - // - - // // Fade out / show UI on mouse move - // - var timedelay = 1; - function delayCheck() { if (timedelay === 5) { $(".multi-button").fadeOut(); @@ -859,7 +810,6 @@ function startUp() { } timedelay = timedelay + 1; } - $(document).mousemove(function () { $(".multi-button").fadeIn(); $("#header").fadeIn(); @@ -869,10 +819,6 @@ function startUp() { }); _delay = setInterval(delayCheck, 500); - // - // End Fade out / show UI on mouse move - // - // Show accept webcam snackbar Snackbar.show({ text: "Please allow microphone and webcam access", @@ -889,9 +835,13 @@ function startUp() { }, }); + // Set caption text on start captionText.text("Waiting for other user to join...").fadeIn(); + + // Reposition captions on start rePositionCaptions(); + // On change media devices refresh page and switch to system default navigator.mediaDevices.ondevicechange = () => window.location.reload(); }