// We import the settings.js file to know which address we should contact
// to talk to Janus, and optionally which STUN/TURN servers should be
// used as well. Specifically, that file defines the "server" and
// "iceServers" properties we'll pass when creating the Janus session.
/* global iceServers:readonly, Janus:readonly, server:readonly */
var janus = null;
var videocall = null;
var opaqueId = "videocalltest-" + Janus.randomString(12);
var localTracks = {},
localVideos = 0,
remoteTracks = {},
remoteVideos = 0;
var bitrateTimer = null;
var audioenabled = false;
var videoenabled = false;
var myusername = null;
var yourusername = null;
var doSimulcast =
getQueryStringValue("simulcast") === "yes" ||
getQueryStringValue("simulcast") === "true";
var simulcastStarted = false;
$(document).ready(function () {
// Initialize the library (console debug enabled)
Janus.init({
debug: true,
callback: function () {
// Use a button to start the demo
$("#start").one("click", function () {
$(this).attr("disabled", true).unbind("click");
// Make sure the browser supports WebRTC
if (!Janus.isWebrtcSupported()) {
bootbox.alert("No WebRTC support... ");
return;
}
// Create session
janus = new Janus({
server: server,
iceServers: iceServers,
// Should the Janus API require authentication, you can specify either the API secret or user token here too
// token: "mytoken",
// or
// apisecret: "serversecret",
success: function () {
// Attach to VideoCall plugin
janus.attach({
plugin: "janus.plugin.videocall",
opaqueId: opaqueId,
success: function (pluginHandle) {
$("#details").remove();
videocall = pluginHandle;
Janus.log(
"Plugin attached! (" +
videocall.getPlugin() +
", id=" +
videocall.getId() +
")"
);
// Prepare the username registration
$("#videocall").removeClass("hide");
$("#login").removeClass("invisible");
$("#registernow").removeClass("hide");
$("#register").click(registerUsername);
$("#username").focus();
$("#start")
.removeAttr("disabled")
.html("Stop")
.click(function () {
$(this).attr("disabled", true);
janus.destroy();
});
},
error: function (error) {
Janus.error(" -- Error attaching plugin...", error);
bootbox.alert(" -- Error attaching plugin... " + error);
},
consentDialog: function (on) {
Janus.debug(
"Consent dialog should be " + (on ? "on" : "off") + " now"
);
if (on) {
// Darken screen and show hint
$.blockUI({
message: '
',
baseZ: 3001,
css: {
border: "none",
padding: "15px",
backgroundColor: "transparent",
color: "#aaa",
top: "10px",
left: "100px",
},
});
} else {
// Restore screen
$.unblockUI();
}
},
iceState: function (state) {
Janus.log("ICE state changed to " + state);
},
mediaState: function (medium, on, mid) {
Janus.log(
"Janus " +
(on ? "started" : "stopped") +
" receiving our " +
medium +
" (mid=" +
mid +
")"
);
},
webrtcState: function (on) {
Janus.log(
"Janus says our WebRTC PeerConnection is " +
(on ? "up" : "down") +
" now"
);
$("#videoleft").parent().unblock();
},
slowLink: function (uplink, lost, mid) {
Janus.warn(
"Janus reports problems " +
(uplink ? "sending" : "receiving") +
" packets on mid " +
mid +
" (" +
lost +
" lost packets)"
);
},
onmessage: function (msg, jsep) {
Janus.debug(" ::: Got a message :::", msg);
let result = msg["result"];
if (result) {
if (result["list"]) {
let list = result["list"];
Janus.debug("Got a list of registered peers:", list);
for (let mp in list) {
Janus.debug(" >> [" + list[mp] + "]");
}
} else if (result["event"]) {
let event = result["event"];
if (event === "registered") {
myusername = escapeXmlTags(result["username"]);
Janus.log(
"Successfully registered as " + myusername + "!"
);
$("#youok")
.removeClass("hide")
.html("Registered as '" + myusername + "'");
// Get a list of available peers, just for fun
videocall.send({ message: { request: "list" } });
// Enable buttons to call now
$("#phone").removeClass("invisible");
$("#call").unbind("click").click(doCall);
$("#peer").focus();
} else if (event === "calling") {
Janus.log("Waiting for the peer to answer...");
// TODO Any ringtone?
bootbox.alert("Waiting for the peer to answer...");
} else if (event === "incomingcall") {
Janus.log(
"Incoming call from " + result["username"] + "!"
);
yourusername = escapeXmlTags(result["username"]);
// Notify user
bootbox.hideAll();
bootbox.dialog({
message: "Incoming call from " + yourusername + "!",
title: "Incoming call",
closeButton: false,
buttons: {
success: {
label: "Answer",
className: "btn-success",
callback: function () {
$("#peer")
.val(result["username"])
.attr("disabled", true);
videocall.createAnswer({
jsep: jsep,
// We want bidirectional audio and video, if offered,
// plus data channels too if they were negotiated
tracks: [
{ type: "audio", capture: true, recv: true },
{ type: "video", capture: true, recv: true },
{ type: "data" },
],
success: function (jsep) {
Janus.debug("Got SDP!", jsep);
let body = { request: "accept" };
videocall.send({ message: body, jsep: jsep });
$("#peer").attr("disabled", true);
$("#call")
.removeAttr("disabled")
.html("Hangup")
.removeClass("btn-success")
.addClass("btn-danger")
.unbind("click")
.click(doHangup);
},
error: function (error) {
Janus.error("WebRTC error:", error);
bootbox.alert(
"WebRTC error... " + error.message
);
},
});
},
},
danger: {
label: "Decline",
className: "btn-danger",
callback: function () {
doHangup();
},
},
},
});
} else if (event === "accepted") {
bootbox.hideAll();
let peer = escapeXmlTags(result["username"]);
if (!peer) {
Janus.log("Call started!");
} else {
Janus.log(peer + " accepted the call!");
yourusername = peer;
}
// Video call can start
if (jsep) videocall.handleRemoteJsep({ jsep: jsep });
$("#call")
.removeAttr("disabled")
.html("Hangup")
.removeClass("btn-success")
.addClass("btn-danger")
.unbind("click")
.click(doHangup);
} else if (event === "update") {
// An 'update' event may be used to provide renegotiation attempts
if (jsep) {
if (jsep.type === "answer") {
videocall.handleRemoteJsep({ jsep: jsep });
} else {
videocall.createAnswer({
jsep: jsep,
// We want bidirectional audio and video, if offered,
// plus data channels too if they were negotiated
tracks: [
{ type: "audio", capture: true, recv: true },
{ type: "video", capture: true, recv: true },
{ type: "data" },
],
success: function (jsep) {
Janus.debug("Got SDP!", jsep);
let body = { request: "set" };
videocall.send({ message: body, jsep: jsep });
},
error: function (error) {
Janus.error("WebRTC error:", error);
bootbox.alert("WebRTC error... " + error.message);
},
});
}
}
} else if (event === "hangup") {
Janus.log(
"Call hung up by " +
result["username"] +
" (" +
result["reason"] +
")!"
);
// Reset status
bootbox.hideAll();
videocall.hangup();
$("#waitingvideo").remove();
$("#videos").addClass("hide");
$("#peer").removeAttr("disabled").val("");
$("#call")
.removeAttr("disabled")
.html("Call")
.removeClass("btn-danger")
.addClass("btn-success")
.unbind("click")
.click(doCall);
$("#toggleaudio").attr("disabled", true);
$("#togglevideo").attr("disabled", true);
$("#bitrate").attr("disabled", true);
$("#curbitrate").addClass("hide");
$("#curres").addClass("hide");
} else if (event === "simulcast") {
// Is simulcast in place?
let substream = result["substream"];
let temporal = result["temporal"];
if (
(substream !== null && substream !== undefined) ||
(temporal !== null && temporal !== undefined)
) {
if (!simulcastStarted) {
simulcastStarted = true;
addSimulcastButtons(result["videocodec"] === "vp8");
}
// We just received notice that there's been a switch, update the buttons
updateSimulcastButtons(substream, temporal);
}
}
}
} else {
// FIXME Error?
let error = msg["error"];
bootbox.alert(error);
if (error.indexOf("already taken") > 0) {
// FIXME Use status codes...
$("#username").removeAttr("disabled").val("");
$("#register")
.removeAttr("disabled")
.unbind("click")
.click(registerUsername);
}
// TODO Reset status
videocall.hangup();
$("#waitingvideo").remove();
$("#videos").addClass("hide");
$("#peer").removeAttr("disabled").val("");
$("#call")
.removeAttr("disabled")
.html("Call")
.removeClass("btn-danger")
.addClass("btn-success")
.unbind("click")
.click(doCall);
$("#toggleaudio").attr("disabled", true);
$("#togglevideo").attr("disabled", true);
$("#bitrate").attr("disabled", true);
$("#curbitrate").addClass("hide");
$("#curres").addClass("hide");
if (bitrateTimer) clearInterval(bitrateTimer);
bitrateTimer = null;
}
},
onlocaltrack: function (track, on) {
Janus.debug(
"Local track " + (on ? "added" : "removed") + ":",
track
);
// We use the track ID as name of the element, but it may contain invalid characters
let trackId = track.id.replace(/[{}]/g, "");
if (!on) {
// Track removed, get rid of the stream and the rendering
let stream = localTracks[trackId];
if (stream) {
try {
let tracks = stream.getTracks();
for (let i in tracks) {
let mst = tracks[i];
if (mst !== null && mst !== undefined) mst.stop();
}
// eslint-disable-next-line no-unused-vars
} catch (e) {}
}
if (track.kind === "video") {
$("#myvideo" + trackId).remove();
localVideos--;
if (localVideos === 0) {
// No video, at least for now: show a placeholder
if ($("#videoleft .no-video-container").length === 0) {
$("#videoleft").append(
'
' +
'' +
'No webcam available' +
"
"
);
}
}
}
delete localTracks[trackId];
return;
}
// If we're here, a new track was added
let stream = localTracks[trackId];
if (stream) {
// We've been here already
return;
}
if ($("#videoleft video").length === 0) {
$("#videos").removeClass("hide");
}
if (track.kind === "audio") {
// We ignore local audio tracks, they'd generate echo anyway
if (localVideos === 0) {
// No video, at least for now: show a placeholder
if ($("#videoleft .no-video-container").length === 0) {
$("#videoleft").append(
'
' +
'' +
'No webcam available' +
"
"
);
}
}
} else {
// New video track: create a stream out of it
localVideos++;
$("#videoleft .no-video-container").remove();
stream = new MediaStream([track]);
localTracks[trackId] = stream;
Janus.log("Created local stream:", stream);
$("#videoleft").append(
''
);
Janus.attachMediaStream(
$("#myvideo" + trackId).get(0),
stream
);
}
if (
videocall.webrtcStuff.pc.iceConnectionState !== "completed" &&
videocall.webrtcStuff.pc.iceConnectionState !== "connected"
) {
$("#videoleft")
.parent()
.block({
message: "Publishing...",
css: {
border: "none",
backgroundColor: "transparent",
color: "white",
},
});
}
},
onremotetrack: function (track, mid, on, metadata) {
Janus.debug(
"Remote track (mid=" +
mid +
") " +
(on ? "added" : "removed") +
(metadata ? " (" + metadata.reason + ") " : "") +
":",
track
);
if (!on) {
// Track removed, get rid of the stream and the rendering
$("#peervideo" + mid).remove();
if (track.kind === "video") {
remoteVideos--;
if (remoteVideos === 0) {
// No video, at least for now: show a placeholder
if ($("#videoright .no-video-container").length === 0) {
$("#videoright").append(
'
' +
'' +
'No remote video available' +
"
"
);
}
}
}
delete remoteTracks[mid];
return;
}
if ($("#peervideo" + mid).length > 0) return;
// If we're here, a new track was added
$("#spinner").remove();
let addButtons = false;
if (
$("#videoright audio").length === 0 &&
$("#videoright video").length === 0
) {
addButtons = true;
$("#videos").removeClass("hide");
}
if (track.kind === "audio") {
// New audio track: create a stream out of it, and use a hidden