decentralized-video-chat/public/js/chat.js

942 lines
30 KiB
JavaScript
Raw Normal View History

2020-03-30 12:03:11 +08:00
// Vars
2020-04-03 05:19:58 +08:00
var isMuted;
var videoIsPaused;
2020-04-03 01:11:17 +08:00
var dataChanel = null;
2020-03-30 12:03:11 +08:00
const browserName = getBrowserName();
const url = window.location.href;
2020-03-31 02:16:08 +08:00
const roomHash = url.substring(url.lastIndexOf("/") + 1).toLowerCase();
2020-03-30 12:03:11 +08:00
var mode = "camera";
// var isFullscreen = false;
2020-03-30 12:03:11 +08:00
var sendingCaptions = false;
var receivingCaptions = false;
const isWebRTCSupported =
2020-03-31 02:16:08 +08:00
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia ||
window.RTCPeerConnection;
2020-03-25 14:02:06 +08:00
2020-03-30 12:03:11 +08:00
// Element vars
2020-03-31 12:18:15 +08:00
const chatInput = document.querySelector(".compose input");
2020-04-06 12:17:40 +08:00
const remoteVideoVanilla = document.getElementById("remote-video");
2020-03-31 23:51:36 +08:00
const remoteVideo = $("#remote-video");
const captionText = $("#remote-video-text");
2020-04-06 11:15:20 +08:00
const localVideoText = $("#local-video-text");
const captionButtontext = $("#caption-button-text");
const entireChat = $("#entire-chat");
const chatZone = $("#chat-zone");
2020-03-22 05:23:46 +08:00
var VideoChat = {
2020-03-31 02:16:08 +08:00
connected: false,
willInitiateCall: false,
localICECandidates: [],
socket: io(),
remoteVideo: document.getElementById("remote-video"),
localVideo: document.getElementById("local-video"),
recognition: undefined,
// 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
// accepted callback to the onMediaStream function, otherwise callback to the
// noMediaStream function.
requestMediaStream: function (event) {
logIt("requestMediaStream");
rePositionLocalVideo();
navigator.mediaDevices
.getUserMedia({
video: true,
audio: true,
})
.then((stream) => {
VideoChat.onMediaStream(stream);
2020-04-06 11:15:20 +08:00
localVideoText.text("Drag Me");
setTimeout(() => localVideoText.fadeOut(), 5000);
2020-03-31 02:16:08 +08:00
})
.catch((error) => {
logIt(error);
logIt(
"Failed to get local webcam video, check webcam privacy settings"
2020-03-22 05:23:46 +08:00
);
2020-04-06 11:15:20 +08:00
// Keep trying to get user media
2020-03-31 02:16:08 +08:00
setTimeout(VideoChat.requestMediaStream, 1000);
});
},
2020-04-06 12:17:40 +08:00
// Called when a video stream is added to VideoChat
2020-03-31 02:16:08 +08:00
onMediaStream: function (stream) {
logIt("onMediaStream");
VideoChat.localStream = stream;
// Add the stream as video's srcObject.
2020-04-06 12:17:40 +08:00
// Now that we have webcam video sorted, prompt user to share URL
2020-03-31 02:16:08 +08:00
Snackbar.show({
2020-04-07 01:15:58 +08:00
text: "Here is the join link for your call: " + url,
2020-03-31 02:16:08 +08:00
actionText: "Copy Link",
2020-04-07 01:15:58 +08:00
width: "750px",
2020-03-31 02:16:08 +08:00
pos: "top-center",
2020-04-04 12:21:03 +08:00
actionTextColor: "#616161",
2020-03-31 02:16:08 +08:00
duration: 500000,
backgroundColor: "#16171a",
onActionClick: function (element) {
2020-04-06 12:17:40 +08:00
// 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
2020-03-31 02:16:08 +08:00
var copyContent = window.location.href;
$('<input id="some-element">')
.val(copyContent)
.appendTo("body")
.select();
document.execCommand("copy");
var toRemove = document.querySelector("#some-element");
toRemove.parentNode.removeChild(toRemove);
2020-04-06 12:17:40 +08:00
Snackbar.close();
2020-03-31 02:16:08 +08:00
},
});
VideoChat.localVideo.srcObject = stream;
// Now we're ready to join the chat room.
VideoChat.socket.emit("join", roomHash);
2020-04-06 12:17:40 +08:00
// Add listeners to the websocket
2020-03-31 02:16:08 +08:00
VideoChat.socket.on("full", chatRoomFull);
VideoChat.socket.on("offer", VideoChat.onOffer);
VideoChat.socket.on("ready", VideoChat.readyToCall);
VideoChat.socket.on(
"willInitiateCall",
() => (VideoChat.willInitiateCall = true)
);
},
// When we are ready to call, enable the Call button.
readyToCall: function (event) {
logIt("readyToCall");
2020-04-06 12:17:40 +08:00
// First to join call will most likely initiate call
2020-03-31 02:16:08 +08:00
if (VideoChat.willInitiateCall) {
logIt("Initiating call");
VideoChat.startCall();
}
},
// Set up a callback to run when we have the ephemeral token to use Twilio's TURN server.
startCall: function (event) {
2020-04-06 11:15:20 +08:00
logIt("startCall >>> Sending token request...");
2020-03-31 02:16:08 +08:00
VideoChat.socket.on("token", VideoChat.onToken(VideoChat.createOffer));
VideoChat.socket.emit("token", roomHash);
},
// When we receive the ephemeral token back from the server.
onToken: function (callback) {
logIt("onToken");
return function (token) {
logIt("<<< Received token");
// Set up a new RTCPeerConnection using the token's iceServers.
VideoChat.peerConnection = new RTCPeerConnection({
iceServers: token.iceServers,
});
// Add the local video stream to the peerConnection.
VideoChat.localStream.getTracks().forEach(function (track) {
VideoChat.peerConnection.addTrack(track, VideoChat.localStream);
});
2020-04-06 12:17:40 +08:00
// Add general purpose data channel to peer connection,
// used for text chats, captions, and toggling sending captions
2020-04-03 01:11:17 +08:00
dataChanel = VideoChat.peerConnection.createDataChannel("chat", {
negotiated: true,
2020-04-06 12:17:40 +08:00
// both peers must have same id
2020-04-03 01:11:17 +08:00
id: 0,
});
2020-04-06 12:17:40 +08:00
// Called when dataChannel is successfully opened
2020-04-03 01:11:17 +08:00
dataChanel.onopen = function (event) {
logIt("dataChannel opened");
};
2020-04-06 12:17:40 +08:00
// Handle different dataChannel types
2020-04-03 01:11:17 +08:00
dataChanel.onmessage = function (event) {
2020-04-06 12:17:40 +08:00
const receivedData = event.data;
// First 4 chars represent data type
const dataType = receivedData.substring(0, 4);
const cleanedMessage = receivedData.slice(4);
if (dataType === "mes:") {
handleRecieveMessage(cleanedMessage);
} else if (dataType === "cap:") {
recieveCaptions(cleanedMessage);
} else if (dataType === "tog:") {
toggleSendCaptions();
}
2020-04-03 01:11:17 +08:00
};
2020-03-31 02:16:08 +08:00
// Set up callbacks for the connection generating iceCandidates or
// receiving the remote media stream.
VideoChat.peerConnection.onicecandidate = VideoChat.onIceCandidate;
VideoChat.peerConnection.onaddstream = VideoChat.onAddStream;
2020-04-06 12:17:40 +08:00
// Set up listeners on the socket
2020-03-31 02:16:08 +08:00
VideoChat.socket.on("candidate", VideoChat.onCandidate);
VideoChat.socket.on("answer", VideoChat.onAnswer);
VideoChat.socket.on("requestToggleCaptions", () => toggleSendCaptions());
VideoChat.socket.on("recieveCaptions", (captions) =>
recieveCaptions(captions)
);
2020-04-06 12:17:40 +08:00
// Called when there is a change in connection state
VideoChat.peerConnection.oniceconnectionstatechange = function (event) {
switch (VideoChat.peerConnection.iceConnectionState) {
case "connected":
logIt("connected");
2020-04-06 12:17:40 +08:00
// Once connected we no longer have a need for the signaling server, so disconnect
2020-04-03 00:26:13 +08:00
VideoChat.socket.disconnect();
break;
case "disconnected":
case "failed":
logIt("failed/disconnected");
2020-04-06 12:17:40 +08:00
// Refresh page if connection is lost
location.reload();
break;
case "closed":
logIt("closed");
break;
}
};
2020-03-31 02:16:08 +08:00
callback();
};
},
// When the peerConnection generates an ice candidate, send it over the socket to the peer.
onIceCandidate: function (event) {
logIt("onIceCandidate");
if (event.candidate) {
logIt(
`<<< Received local ICE candidate from STUN/TURN server (${event.candidate.address})`
);
if (VideoChat.connected) {
logIt(`>>> Sending local ICE candidate (${event.candidate.address})`);
VideoChat.socket.emit(
"candidate",
JSON.stringify(event.candidate),
roomHash
);
} else {
// If we are not 'connected' to the other peer, we are buffering the local ICE candidates.
// This most likely is happening on the "caller" side.
// The peer may not have created the RTCPeerConnection yet, so we are waiting for the 'answer'
// to arrive. This will signal that the peer is ready to receive signaling.
VideoChat.localICECandidates.push(event.candidate);
}
}
},
// When receiving a candidate over the socket, turn it back into a real
// RTCIceCandidate and add it to the peerConnection.
onCandidate: function (candidate) {
2020-04-06 12:17:40 +08:00
// Update caption text
captionText.text("Found other user... connecting");
2020-03-31 02:16:08 +08:00
rtcCandidate = new RTCIceCandidate(JSON.parse(candidate));
logIt(
2020-04-06 12:17:40 +08:00
`onCandidate <<< Received remote ICE candidate (${rtcCandidate.address} - ${rtcCandidate.relatedAddress})`
2020-03-31 02:16:08 +08:00
);
VideoChat.peerConnection.addIceCandidate(rtcCandidate);
},
// Create an offer that contains the media capabilities of the browser.
createOffer: function () {
2020-04-06 12:17:40 +08:00
logIt("createOffer >>> Creating offer...");
2020-03-31 02:16:08 +08:00
VideoChat.peerConnection.createOffer(
function (offer) {
// If the offer is created successfully, set it as the local description
// and send it over the socket connection to initiate the peerConnection
// on the other side.
VideoChat.peerConnection.setLocalDescription(offer);
VideoChat.socket.emit("offer", JSON.stringify(offer), roomHash);
},
function (err) {
logIt("failed offer creation");
logIt(err, true);
}
);
},
// Create an answer with the media capabilities that both browsers share.
// This function is called with the offer from the originating browser, which
// needs to be parsed into an RTCSessionDescription and added as the remote
// description to the peerConnection object. Then the answer is created in the
// same manner as the offer and sent over the socket.
createAnswer: function (offer) {
logIt("createAnswer");
return function () {
logIt(">>> Creating answer...");
rtcOffer = new RTCSessionDescription(JSON.parse(offer));
VideoChat.peerConnection.setRemoteDescription(rtcOffer);
VideoChat.peerConnection.createAnswer(
function (answer) {
VideoChat.peerConnection.setLocalDescription(answer);
VideoChat.socket.emit("answer", JSON.stringify(answer), roomHash);
},
function (err) {
logIt("Failed answer creation.");
logIt(err, true);
}
);
};
},
// When a browser receives an offer, set up a callback to be run when the
// ephemeral token is returned from Twilio.
onOffer: function (offer) {
2020-04-06 12:17:40 +08:00
logIt("onOffer <<< Received offer");
2020-03-31 02:16:08 +08:00
VideoChat.socket.on(
"token",
VideoChat.onToken(VideoChat.createAnswer(offer))
);
VideoChat.socket.emit("token", roomHash);
},
// When an answer is received, add it to the peerConnection as the remote description.
onAnswer: function (answer) {
2020-04-06 12:17:40 +08:00
logIt("onAnswer <<< Received answer");
2020-03-31 02:16:08 +08:00
var rtcAnswer = new RTCSessionDescription(JSON.parse(answer));
2020-04-06 12:17:40 +08:00
// Set remote description of RTCSession
2020-03-31 02:16:08 +08:00
VideoChat.peerConnection.setRemoteDescription(rtcAnswer);
2020-04-06 12:17:40 +08:00
// The caller now knows that the callee is ready to accept new ICE candidates, so sending the buffer over
2020-03-31 02:16:08 +08:00
VideoChat.localICECandidates.forEach((candidate) => {
logIt(`>>> Sending local ICE candidate (${candidate.address})`);
2020-04-06 12:17:40 +08:00
// Send ice candidate over websocket
2020-03-31 02:16:08 +08:00
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
VideoChat.localICECandidates = [];
},
2020-04-06 12:17:40 +08:00
// Called when a stream is added to the peer connection
2020-03-31 02:16:08 +08:00
onAddStream: function (event) {
2020-04-06 12:17:40 +08:00
logIt("onAddStream <<< Received new stream from remote. Adding it...");
// Update remote video source
2020-03-31 02:16:08 +08:00
VideoChat.remoteVideo.srcObject = event.stream;
2020-04-06 12:17:40 +08:00
// Close the initial share url snackbar
2020-03-31 02:16:08 +08:00
Snackbar.close();
2020-04-06 12:17:40 +08:00
// Remove the loading gif from video
2020-03-31 02:16:08 +08:00
VideoChat.remoteVideo.style.background = "none";
2020-04-06 12:17:40 +08:00
// Update connection status
2020-03-31 02:16:08 +08:00
VideoChat.connected = true;
2020-04-06 12:17:40 +08:00
// Hide caption status text
2020-04-01 00:12:37 +08:00
captionText.fadeOut();
// Reposition local video after a second, as there is often a delay
2020-04-06 12:17:40 +08:00
// between adding a stream and the height of the video div changing
setTimeout(() => rePositionLocalVideo(), 500);
// var timesRun = 0;
// var interval = setInterval(function () {
// timesRun += 1;
// if (timesRun === 10) {
// clearInterval(interval);
// }
// rePositionLocalVideo();
// }, 300);
2020-03-31 02:16:08 +08:00
},
2020-03-30 12:03:11 +08:00
};
2020-04-06 11:15:20 +08:00
// Get name of browser session using user agent
2020-03-30 12:03:11 +08:00
function getBrowserName() {
2020-03-31 02:16:08 +08:00
var name = "Unknown";
if (window.navigator.userAgent.indexOf("MSIE") !== -1) {
} else if (window.navigator.userAgent.indexOf("Firefox") !== -1) {
name = "Firefox";
} else if (window.navigator.userAgent.indexOf("Opera") !== -1) {
name = "Opera";
} else if (window.navigator.userAgent.indexOf("Chrome") !== -1) {
name = "Chrome";
} else if (window.navigator.userAgent.indexOf("Safari") !== -1) {
name = "Safari";
}
return name;
2020-03-30 12:03:11 +08:00
}
2020-03-22 05:23:46 +08:00
2020-04-06 11:15:20 +08:00
// Basic logging class wrapper
2020-03-30 12:03:11 +08:00
function logIt(message, error) {
2020-03-31 02:16:08 +08:00
console.log(message);
2020-03-30 12:03:11 +08:00
}
2020-04-06 11:15:20 +08:00
// Called when socket receives message that room is full
2020-03-30 12:03:11 +08:00
function chatRoomFull() {
2020-03-31 02:16:08 +08:00
alert(
"Chat room is full. Check to make sure you don't have multiple open tabs, or try with a new room link"
);
2020-04-06 11:15:20 +08:00
// Exit room and redirect
2020-04-07 06:28:42 +08:00
window.location.href = "/newcall";
2020-03-30 12:03:11 +08:00
}
2020-04-06 11:15:20 +08:00
// Reposition local video to top left of remote video
2020-03-30 12:03:11 +08:00
function rePositionLocalVideo() {
2020-04-06 11:15:20 +08:00
// Get position of remote video
2020-03-31 23:51:36 +08:00
var bounds = remoteVideo.position();
let localVideo = $("#local-video");
2020-04-12 20:43:04 +08:00
if (
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
)
) {
bounds.top = $(window).height() * 0.7;
bounds.left += 10;
} else {
bounds.top += 10;
bounds.left += 10;
}
2020-04-06 11:15:20 +08:00
// Set position of local video
2020-03-31 02:16:08 +08:00
$("#moveable").css(bounds);
2020-03-30 12:03:11 +08:00
}
2020-04-06 11:15:20 +08:00
// Reposition captions to bottom of video
2020-03-31 23:51:36 +08:00
function rePositionCaptions() {
2020-04-06 11:15:20 +08:00
// Get remote video position
2020-03-31 23:51:36 +08:00
var bounds = remoteVideo.position();
bounds.top -= 10;
bounds.top = bounds.top + remoteVideo.height() - 1 * captionText.height();
2020-04-06 11:15:20 +08:00
// Reposition captions
2020-03-31 23:51:36 +08:00
captionText.css(bounds);
}
2020-04-06 11:15:20 +08:00
// Called when window is resized
2020-03-31 23:51:36 +08:00
function windowResized() {
rePositionLocalVideo();
rePositionCaptions();
}
2020-03-30 12:03:11 +08:00
// Fullscreen
// function openFullscreen() {
// try {
// // var elem = document.getElementById("remote-video");
// var elem = document.getElementById("body");
// if (!isFullscreen) {
// VideoChat.remoteVideo.classList.add("fullscreen");
// isFullscreen = true;
// if (elem.requestFullscreen) {
// elem.requestFullscreen();
// } else if (elem.mozRequestFullScreen) {
// /* Firefox */
// elem.mozRequestFullScreen();
// } else if (elem.webkitRequestFullscreen) {
// /* Chrome, Safari and Opera */
//
// elem.webkitRequestFullscreen();
// setTimeout(windowResized, 1000);
// } else if (elem.msRequestFullscreen) {
// /* IE/Edge */
// elem.msRequestFullscreen();
// }
// } else {
// isFullscreen = false;
// VideoChat.remoteVideo.classList.remove("fullscreen");
// if (document.exitFullscreen) {
// document.exitFullscreen();
// } else if (document.mozCancelFullScreen) {
// /* Firefox */
// document.mozCancelFullScreen();
// } else if (document.webkitExitFullscreen) {
// /* Chrome, Safari and Opera */
// document.webkitExitFullscreen();
// } else if (document.msExitFullscreen) {
// /* IE/Edge */
// document.msExitFullscreen();
// }
// }
// } catch (e) {
// logIt(e);
// }
// setTimeout(windowResized, 1000);
// }
2020-03-30 12:03:11 +08:00
// End Fullscreen
// Mute microphone
function muteMicrophone() {
2020-04-03 05:19:58 +08:00
var audioTrack = null;
2020-04-06 11:15:20 +08:00
// Get audio track to mute
2020-04-03 05:19:58 +08:00
VideoChat.peerConnection.getSenders().find(function (s) {
if (s.track.kind === "audio") {
audioTrack = s.track;
}
});
isMuted = !audioTrack.enabled;
audioTrack.enabled = isMuted;
isMuted = !isMuted;
2020-04-06 11:15:20 +08:00
// 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
2020-04-03 05:19:58 +08:00
if (isMuted) {
2020-04-06 11:15:20 +08:00
micButtonIcon.classList.remove("fa-microphone");
micButtonIcon.classList.add("fa-microphone-slash");
micButtonText.innerText = "Unmute";
2020-03-31 02:16:08 +08:00
} else {
2020-04-06 11:15:20 +08:00
micButtonIcon.classList.add("fa-microphone");
micButtonIcon.classList.remove("fa-microphone-slash");
micButtonText.innerText = "Mute";
2020-03-31 02:16:08 +08:00
}
}
2020-03-30 12:03:11 +08:00
// End Mute microphone
// Pause Video
2020-03-25 14:02:06 +08:00
function pauseVideo() {
2020-04-03 05:19:58 +08:00
var videoTrack = null;
2020-04-06 11:15:20 +08:00
// Get video track to pause
2020-04-03 05:19:58 +08:00
VideoChat.peerConnection.getSenders().find(function (s) {
if (s.track.kind === "video") {
videoTrack = s.track;
}
});
videoIsPaused = !videoTrack.enabled;
videoTrack.enabled = videoIsPaused;
videoIsPaused = !videoIsPaused;
2020-04-06 11:15:20 +08:00
// 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
2020-04-03 05:19:58 +08:00
if (videoIsPaused) {
localVideoText.text("Video is paused");
localVideoText.show();
2020-04-06 11:15:20 +08:00
videoButtonIcon.classList.remove("fa-video");
videoButtonIcon.classList.add("fa-video-slash");
videoButtonText.innerText = "Unpause Video";
2020-03-31 02:16:08 +08:00
} else {
localVideoText.text("Video unpaused");
setTimeout(() => localVideoText.fadeOut(), 2000);
2020-04-06 11:15:20 +08:00
videoButtonIcon.classList.add("fa-video");
videoButtonIcon.classList.remove("fa-video-slash");
videoButtonText.innerText = "Pause Video";
2020-03-31 02:16:08 +08:00
}
2020-03-25 14:02:06 +08:00
}
2020-03-30 12:03:11 +08:00
// End pause Video
2020-03-28 02:06:07 +08:00
2020-03-30 12:03:11 +08:00
// Swap camera / screen share
function swap() {
2020-04-06 12:17:40 +08:00
// Handle swap video before video call is connected
2020-03-31 02:16:08 +08:00
if (!VideoChat.connected) {
alert("You must join a call before you can share your screen.");
return;
}
2020-04-06 12:17:40 +08:00
// Store swap button icon and text
2020-03-31 02:16:08 +08:00
const swapIcon = document.getElementById("swap-icon");
const swapText = document.getElementById("swap-text");
2020-04-06 12:17:40 +08:00
// If mode is camera then switch to screen share
2020-03-31 02:16:08 +08:00
if (mode === "camera") {
// Show accept screenshare snackbar
Snackbar.show({
text:
"Please allow screen share. Click the middle of the picture above and then press share.",
2020-04-04 12:21:03 +08:00
width: "400px",
pos: "bottom-center",
2020-04-04 12:21:03 +08:00
actionTextColor: "#616161",
duration: 50000,
});
2020-04-06 12:17:40 +08:00
// Request screen share, note we dont want to capture audio
// as we already have the stream from the Webcam
2020-03-31 02:16:08 +08:00
navigator.mediaDevices
.getDisplayMedia({
video: true,
audio: false,
2020-03-31 02:16:08 +08:00
})
.then(function (stream) {
2020-04-06 12:17:40 +08:00
// Close allow screenshare snackbar
Snackbar.close();
2020-04-06 12:17:40 +08:00
// Change display mode
2020-03-31 02:16:08 +08:00
mode = "screen";
2020-04-06 12:17:40 +08:00
// Update swap button icon and text
2020-03-31 02:16:08 +08:00
swapIcon.classList.remove("fa-desktop");
swapIcon.classList.add("fa-camera");
swapText.innerText = "Share Webcam";
switchStreamHelper(stream);
2020-04-04 12:21:03 +08:00
})
.catch(function (err) {
logIt(err);
logIt("Error sharing screen");
Snackbar.close();
2020-03-31 02:16:08 +08:00
});
2020-04-06 12:17:40 +08:00
// If mode is screenshare then switch to webcam
2020-03-31 02:16:08 +08:00
} else {
2020-04-06 12:17:40 +08:00
// Stop the screen share track
2020-03-31 02:16:08 +08:00
VideoChat.localVideo.srcObject.getTracks().forEach((track) => track.stop());
2020-04-06 12:17:40 +08:00
// Get webcam input
2020-03-31 02:16:08 +08:00
navigator.mediaDevices
.getUserMedia({
video: true,
audio: true,
})
.then(function (stream) {
2020-04-06 12:17:40 +08:00
// Change display mode
2020-03-31 02:16:08 +08:00
mode = "camera";
2020-04-06 12:17:40 +08:00
// Update swap button icon and text
2020-03-31 02:16:08 +08:00
swapIcon.classList.remove("fa-camera");
swapIcon.classList.add("fa-desktop");
swapText.innerText = "Share Screen";
switchStreamHelper(stream);
});
}
}
2020-04-06 12:17:40 +08:00
// Swap current video track with passed in stream
function switchStreamHelper(stream) {
2020-04-06 12:17:40 +08:00
// Get current video track
2020-03-31 02:16:08 +08:00
let videoTrack = stream.getVideoTracks()[0];
2020-04-06 12:17:40 +08:00
// Add listen for if the current track swaps, swap back
videoTrack.onended = function () {
swap();
};
2020-03-31 02:16:08 +08:00
if (VideoChat.connected) {
2020-04-06 12:17:40 +08:00
// Find sender
2020-04-03 05:19:58 +08:00
const sender = VideoChat.peerConnection.getSenders().find(function (s) {
2020-04-06 12:17:40 +08:00
// make sure tack types match
2020-03-31 02:16:08 +08:00
return s.track.kind === videoTrack.kind;
});
2020-04-06 12:17:40 +08:00
// Replace sender track
2020-03-31 02:16:08 +08:00
sender.replaceTrack(videoTrack);
}
2020-04-06 12:17:40 +08:00
// Update local video stream
2020-03-31 02:16:08 +08:00
VideoChat.localStream = videoTrack;
2020-04-06 12:17:40 +08:00
// Update local video object
2020-03-31 02:16:08 +08:00
VideoChat.localVideo.srcObject = stream;
2020-04-06 11:15:20 +08:00
// Unpause video on swap
if (videoIsPaused) {
pauseVideo();
}
}
2020-03-30 12:03:11 +08:00
// End swap camera / screen share
2020-03-30 12:03:11 +08:00
// Live caption
2020-04-06 11:15:20 +08:00
// Request captions from other user, toggles state
2020-03-29 10:47:24 +08:00
function requestToggleCaptions() {
2020-04-06 11:15:20 +08:00
// Handle requesting captions before connected
2020-03-31 02:16:08 +08:00
if (!VideoChat.connected) {
alert("You must be connected to a peer to use Live Caption");
return;
}
if (receivingCaptions) {
2020-04-01 00:12:37 +08:00
captionText.text("").fadeOut();
2020-04-06 11:15:20 +08:00
captionButtontext.text("Start Live Caption");
2020-03-31 02:16:08 +08:00
receivingCaptions = false;
} else {
2020-04-06 11:15:20 +08:00
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");
2020-03-31 02:16:08 +08:00
receivingCaptions = true;
}
2020-04-06 11:15:20 +08:00
// Send request to get captions over data channel
dataChanel.send("tog:");
2020-03-29 10:47:24 +08:00
}
2020-04-06 11:15:20 +08:00
// Start/stop sending captions to other user
2020-03-29 10:47:24 +08:00
function toggleSendCaptions() {
2020-03-31 02:16:08 +08:00
if (sendingCaptions) {
sendingCaptions = false;
VideoChat.recognition.stop();
} else {
startSpeech();
sendingCaptions = true;
}
2020-03-29 10:47:24 +08:00
}
2020-04-06 11:15:20 +08:00
// Start speech recognition
2020-03-29 10:47:24 +08:00
function startSpeech() {
2020-03-31 02:16:08 +08:00
try {
var SpeechRecognition =
window.SpeechRecognition || window.webkitSpeechRecognition;
2020-04-06 11:15:20 +08:00
VideoChat.recognition = new SpeechRecognition();
2020-04-07 23:10:21 +08:00
// VideoChat.recognition.lang = "en";
2020-03-31 02:16:08 +08:00
} catch (e) {
sendingCaptions = false;
logIt(e);
logIt("error importing speech library");
2020-04-06 11:15:20 +08:00
// Alert other user that they cannon use live caption
dataChanel.send("cap:notusingchrome");
2020-03-31 02:16:08 +08:00
return;
}
// recognition.maxAlternatives = 3;
2020-04-06 11:15:20 +08:00
VideoChat.recognition.continuous = true;
// Show results that aren't final
VideoChat.recognition.interimResults = true;
2020-03-31 02:16:08 +08:00
var finalTranscript;
2020-04-06 11:15:20 +08:00
VideoChat.recognition.onresult = (event) => {
2020-03-31 02:16:08 +08:00
let interimTranscript = "";
for (let i = event.resultIndex, len = event.results.length; i < len; i++) {
2020-04-04 11:31:07 +08:00
var transcript = event.results[i][0].transcript;
2020-03-31 02:16:08 +08:00
if (event.results[i].isFinal) {
finalTranscript += transcript;
} else {
interimTranscript += transcript;
2020-04-06 01:30:57 +08:00
var charsToKeep = interimTranscript.length % 100;
2020-04-06 11:15:20 +08:00
// Send captions over data chanel,
// subtracting as many complete 100 char slices from start
2020-04-06 01:30:57 +08:00
dataChanel.send(
"cap:" +
interimTranscript.substring(interimTranscript.length - charsToKeep)
);
2020-03-31 02:16:08 +08:00
}
2020-03-29 10:47:24 +08:00
}
2020-03-31 02:16:08 +08:00
};
2020-04-06 11:15:20 +08:00
VideoChat.recognition.onend = function () {
logIt("on speech recording end");
// Restart speech recognition if user has not stopped it
2020-03-31 02:16:08 +08:00
if (sendingCaptions) {
startSpeech();
} else {
VideoChat.recognition.stop();
}
};
2020-04-06 11:15:20 +08:00
VideoChat.recognition.start();
2020-03-29 10:47:24 +08:00
}
2020-04-06 11:15:20 +08:00
// Recieve captions over datachannel
2020-03-30 12:03:11 +08:00
function recieveCaptions(captions) {
2020-04-06 01:30:57 +08:00
if (receivingCaptions) {
captionText.text("").fadeIn();
} else {
2020-04-01 00:12:37 +08:00
captionText.text("").fadeOut();
2020-03-31 02:16:08 +08:00
}
2020-04-06 11:15:20 +08:00
// Other user is not using chrome
2020-03-31 02:16:08 +08:00
if (captions === "notusingchrome") {
2020-04-06 01:30:57 +08:00
alert(
"Other caller must be using chrome for this feature to work. Live Caption turned off."
);
2020-03-31 02:16:08 +08:00
receivingCaptions = false;
2020-04-01 00:12:37 +08:00
captionText.text("").fadeOut();
2020-04-06 11:15:20 +08:00
captionButtontext.text("Start Live Caption");
2020-03-31 02:16:08 +08:00
return;
}
2020-04-06 01:30:57 +08:00
captionText.text(captions);
2020-03-31 23:51:36 +08:00
rePositionCaptions();
2020-03-30 12:03:11 +08:00
}
// End Live caption
2020-04-07 23:10:21 +08:00
// Translation
// function translate(text) {
// let fromLang = "en";
// let toLang = "zh";
// // let text = "hello how are you?";
// const API_KEY = "APIKEYHERE";
// let gurl = `https://translation.googleapis.com/language/translate/v2?key=${API_KEY}`;
// gurl += "&q=" + encodeURI(text);
// gurl += `&source=${fromLang}`;
// gurl += `&target=${toLang}`;
// fetch(gurl, {
// method: "GET",
// headers: {
// "Content-Type": "application/json",
// Accept: "application/json",
// },
// })
// .then((res) => res.json())
// .then((response) => {
// // console.log("response from google: ", response);
// // return response["data"]["translations"][0]["translatedText"];
// logIt(response);
// var translatedText =
// response["data"]["translations"][0]["translatedText"];
// console.log(translatedText);
// dataChanel.send("cap:" + translatedText);
// })
// .catch((error) => {
// console.log("There was an error with the translation request: ", error);
// });
// }
// End Translation
2020-04-06 11:15:20 +08:00
// Text Chat
// Add text message to chat screen on page
2020-04-09 01:50:01 +08:00
function addMessageToScreen(msg, isOwnMessage) {
if (isOwnMessage) {
$(".chat-messages").append(
'<div class="message-item customer cssanimation fadeInBottom"><div class="message-bloc"><div class="message">' +
msg +
"</div></div></div>"
);
} else {
$(".chat-messages").append(
'<div class="message-item moderator cssanimation fadeInBottom"><div class="message-bloc"><div class="message">' +
msg +
"</div></div></div>"
);
}
2020-04-06 11:15:20 +08:00
}
2020-03-30 12:03:11 +08:00
2020-04-06 11:15:20 +08:00
// Listen for enter press on chat input
2020-03-31 02:16:08 +08:00
chatInput.addEventListener("keypress", function (event) {
if (event.keyCode === 13) {
2020-04-06 11:15:20 +08:00
// Prevent page refresh on enter
2020-03-31 02:16:08 +08:00
event.preventDefault();
2020-04-06 01:25:29 +08:00
var msg = chatInput.value;
2020-04-06 11:15:20 +08:00
// Prevent cross site scripting
2020-04-06 01:25:29 +08:00
msg = msg.replace(/</g, "&lt;").replace(/>/g, "&gt;");
2020-04-06 11:15:20 +08:00
// Make links clickable
msg = msg.autoLink();
// Send message over data channel
2020-04-06 01:25:29 +08:00
dataChanel.send("mes:" + msg);
2020-04-06 11:15:20 +08:00
// Add message to screen
2020-04-09 01:50:01 +08:00
addMessageToScreen(msg, true);
2020-04-06 11:15:20 +08:00
// Auto scroll chat down
chatZone.scrollTop(chatZone[0].scrollHeight);
// Clear chat input
2020-03-31 02:16:08 +08:00
chatInput.value = "";
}
});
2020-04-06 11:15:20 +08:00
// Called when a message is recieved over the dataChannel
function handleRecieveMessage(msg) {
2020-04-06 11:15:20 +08:00
// Add message to screen
2020-04-09 01:50:01 +08:00
addMessageToScreen(msg, false);
2020-04-06 11:15:20 +08:00
// Auto scroll chat down
chatZone.scrollTop(chatZone[0].scrollHeight);
// Show chat if hidden
if (entireChat.is(":hidden")) {
2020-03-31 02:16:08 +08:00
toggleChat();
}
2020-04-03 01:11:17 +08:00
}
2020-03-30 10:41:15 +08:00
2020-04-06 11:15:20 +08:00
// Show and hide chat
2020-03-30 10:41:15 +08:00
function toggleChat() {
2020-03-31 02:16:08 +08:00
var chatIcon = document.getElementById("chat-icon");
var chatText = $("#chat-text");
if (entireChat.is(":visible")) {
entireChat.fadeOut();
2020-04-06 11:15:20 +08:00
// Update show chat buttton
2020-03-31 02:16:08 +08:00
chatText.text("Show Chat");
chatIcon.classList.remove("fa-comment-slash");
chatIcon.classList.add("fa-comment");
} else {
entireChat.fadeIn();
2020-04-06 11:15:20 +08:00
// Update show chat buttton
2020-03-31 02:16:08 +08:00
chatText.text("Hide Chat");
chatIcon.classList.remove("fa-comment");
chatIcon.classList.add("fa-comment-slash");
}
2020-03-30 10:41:15 +08:00
}
2020-04-06 11:15:20 +08:00
// End Text chat
2020-03-30 09:46:29 +08:00
//Picture in picture
function togglePictureInPicture() {
if (
"pictureInPictureEnabled" in document ||
2020-04-06 12:17:40 +08:00
remoteVideoVanilla.webkitSetPresentationMode
) {
2020-03-31 02:16:08 +08:00
if (document.pictureInPictureElement) {
document.exitPictureInPicture().catch((error) => {
logIt("Error exiting pip.");
logIt(error);
});
2020-04-06 12:17:40 +08:00
} else if (remoteVideoVanilla.webkitPresentationMode === "inline") {
remoteVideoVanilla.webkitSetPresentationMode("picture-in-picture");
} else if (
remoteVideoVanilla.webkitPresentationMode === "picture-in-picture"
) {
remoteVideoVanilla.webkitSetPresentationMode("inline");
2020-03-30 09:46:29 +08:00
} else {
2020-04-06 12:17:40 +08:00
remoteVideoVanilla.requestPictureInPicture().catch((error) => {
2020-04-03 03:00:25 +08:00
alert(
2020-04-03 04:06:02 +08:00
"You must be connected to another person to enter picture in picture."
2020-04-03 03:00:25 +08:00
);
2020-03-31 02:16:08 +08:00
});
2020-03-30 09:46:29 +08:00
}
2020-03-31 02:16:08 +08:00
} else {
alert(
"Picture in picture is not supported in your browser. Consider using Chrome or Safari."
2020-03-31 02:16:08 +08:00
);
}
2020-03-30 09:46:29 +08:00
}
//Picture in picture
2020-03-30 12:03:11 +08:00
function startUp() {
// Try and detect in-app browsers and redirect
var ua = navigator.userAgent || navigator.vendor || window.opera;
if (
DetectRTC.isMobileDevice &&
(ua.indexOf("FBAN") > -1 ||
ua.indexOf("FBAV") > -1 ||
ua.indexOf("Instagram") > -1)
) {
if (DetectRTC.osName === "iOS") {
window.location.href = "/notsupportedios";
} else {
window.location.href = "/notsupported";
}
}
// Redirect all iOS browsers that are not Safari
if (DetectRTC.isMobileDevice) {
if (DetectRTC.osName === "iOS" && !DetectRTC.browser.isSafari) {
window.location.href = "/notsupportedios";
}
}
if (!isWebRTCSupported || browserName === "MSIE") {
2020-04-06 02:28:50 +08:00
window.location.href = "/notsupported";
2020-03-31 02:16:08 +08:00
}
2020-03-30 12:03:11 +08:00
2020-04-06 11:15:20 +08:00
// Set tab title
2020-04-03 09:24:22 +08:00
document.title = "Zipcall - " + url.substring(url.lastIndexOf("/") + 1);
2020-03-30 12:03:11 +08:00
2020-03-31 02:16:08 +08:00
// get webcam on load
VideoChat.requestMediaStream();
2020-03-30 12:03:11 +08:00
2020-04-06 11:15:20 +08:00
// Captions hidden by default
2020-04-01 00:12:37 +08:00
captionText.text("").fadeOut();
2020-03-30 12:03:11 +08:00
2020-04-06 11:15:20 +08:00
// Make local video draggable
2020-03-31 02:16:08 +08:00
$("#moveable").draggable({ containment: "window" });
2020-03-30 12:03:11 +08:00
2020-04-06 11:15:20 +08:00
// Hide button labels on load
2020-03-31 02:16:08 +08:00
$(".HoverState").hide();
2020-03-30 12:03:11 +08:00
2020-04-06 11:15:20 +08:00
// Text chat hidden by default
entireChat.hide();
2020-03-30 12:03:11 +08:00
2020-03-31 02:16:08 +08:00
// Show hide button labels on hover
$(document).ready(function () {
$(".hoverButton").mouseover(function () {
$(".HoverState").hide();
$(this).next().show();
2020-03-30 12:03:11 +08:00
});
2020-03-31 02:16:08 +08:00
$(".hoverButton").mouseout(function () {
$(".HoverState").hide();
});
});
2020-03-30 12:03:11 +08:00
2020-03-31 02:16:08 +08:00
// Fade out / show UI on mouse move
var timedelay = 1;
function delayCheck() {
if (timedelay === 5) {
// $(".multi-button").fadeOut();
$("#header").fadeOut();
2020-03-31 02:16:08 +08:00
timedelay = 1;
2020-03-30 12:03:11 +08:00
}
2020-03-31 02:16:08 +08:00
timedelay = timedelay + 1;
}
$(document).mousemove(function () {
$(".multi-button").fadeIn();
$("#header").fadeIn();
$(".multi-button").style = "";
2020-03-31 02:16:08 +08:00
timedelay = 1;
clearInterval(_delay);
2020-03-30 12:03:11 +08:00
_delay = setInterval(delayCheck, 500);
2020-03-31 02:16:08 +08:00
});
_delay = setInterval(delayCheck, 500);
// Show accept webcam snackbar
Snackbar.show({
text: "Please allow microphone and webcam access",
actionText: "Show Me How",
width: "455px",
pos: "top-right",
2020-04-04 12:21:03 +08:00
actionTextColor: "#616161",
2020-03-31 02:16:08 +08:00
duration: 50000,
onActionClick: function (element) {
window.open(
"https://getacclaim.zendesk.com/hc/en-us/articles/360001547832-Setting-the-default-camera-on-your-browser",
"_blank"
);
},
});
2020-04-06 01:30:57 +08:00
2020-04-06 11:15:20 +08:00
// Set caption text on start
2020-04-06 01:30:57 +08:00
captionText.text("Waiting for other user to join...").fadeIn();
2020-04-06 11:15:20 +08:00
// Reposition captions on start
2020-04-06 01:30:57 +08:00
rePositionCaptions();
2020-04-06 01:39:11 +08:00
2020-04-06 11:15:20 +08:00
// On change media devices refresh page and switch to system default
2020-04-06 01:39:11 +08:00
navigator.mediaDevices.ondevicechange = () => window.location.reload();
2020-03-30 12:03:11 +08:00
}
2020-03-30 02:59:59 +08:00
2020-03-30 12:03:11 +08:00
startUp();