// 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 echotest = null;
var opaqueId = "devicetest-" + Janus.randomString(12);
var localTracks = {},
localVideos = 0,
remoteTracks = {},
remoteVideos = 0;
var bitrateTimer = null;
var audioDeviceId = null;
var videoDeviceId = null;
var audioenabled = false;
var videoenabled = false;
var doSimulcast =
getQueryStringValue("simulcast") === "yes" ||
getQueryStringValue("simulcast") === "true";
var acodec =
getQueryStringValue("acodec") !== "" ? getQueryStringValue("acodec") : null;
var vcodec =
getQueryStringValue("vcodec") !== "" ? getQueryStringValue("vcodec") : null;
var vprofile =
getQueryStringValue("vprofile") !== ""
? getQueryStringValue("vprofile")
: null;
var simulcastStarted = false;
// Helper method to prepare a UI selection of the available devices
function initDevices(devices) {
$("#devices").removeClass("hide");
$("#devices").parent().removeClass("hide");
$("#choose-device").click(restartCapture);
let audio = $("#audio-device").val();
let video = $("#video-device").val();
$("#audio-device, #video-device").find("option").remove();
devices.forEach(function (device) {
let label = device.label;
if (!label || label === "") label = device.deviceId;
let option = $(
'"
);
if (device.kind === "audioinput") {
$("#audio-device").append(option);
} else if (device.kind === "videoinput") {
$("#video-device").append(option);
} else if (device.kind === "audiooutput") {
// Apparently only available from Chrome 49 on?
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId
// Definitely missing in Safari at the moment: https://bugs.webkit.org/show_bug.cgi?id=179415
$("#output-devices").removeClass("hide");
$("#audiooutput").append(
'' +
label +
""
);
$("#audiooutput a")
.unbind("click")
.click(function () {
$(".dropdown-toggle").dropdown("hide");
let deviceId = $(this).attr("id");
let label = $(this).text();
Janus.log(
"Trying to set device " +
deviceId +
" (" +
label +
") as sink for the output"
);
if ($("#peervideo1").length === 0) {
Janus.error("No remote video element available");
bootbox.alert("No remote video element available");
return false;
}
if (!$("#peervideo1").get(0).setSinkId) {
Janus.error("SetSinkId not supported");
bootbox.warn("SetSinkId not supported");
return false;
}
$("#peervideo1")
.get(0)
.setSinkId(deviceId)
.then(function () {
Janus.log("Audio output device attached:", deviceId);
$("#outputdeviceset").text(label).parent().removeClass("open");
})
.catch(function (error) {
Janus.error(error);
bootbox.alert(error);
});
return false;
});
}
});
$("#audio-device").val(audio);
$("#video-device").val(video);
$("#change-devices").click(function () {
// A different device has been selected: hangup the session, and set it up again
$("#audio-device, #video-device").attr("disabled", true);
$("#change-devices").attr("disabled", true);
restartCapture();
});
}
var firstOffer = true;
function restartCapture() {
let replaceAudio = $("#audio-device").val() !== audioDeviceId;
audioDeviceId = $("#audio-device").val();
let replaceVideo = $("#video-device").val() !== videoDeviceId;
videoDeviceId = $("#video-device").val();
Janus.warn(videoDeviceId);
let width = doSimulcast ? 1280 : 640;
let height = doSimulcast ? 720 : 480;
if (!firstOffer) {
if (!replaceAudio && !replaceVideo) {
// Nothing to do, reset devices controls
$("#audio-device, #video-device").removeAttr("disabled");
$("#change-devices").removeAttr("disabled");
return;
}
// Just replacing tracks, no need for a renegotiation
let tracks = [];
if (replaceAudio) {
tracks.push({
type: "audio",
mid: "0", // We assume mid 0 is audio
capture: { deviceId: { exact: audioDeviceId } },
});
}
if (replaceVideo) {
tracks.push({
type: "video",
mid: "1", // We assume mid 1 is video
capture: {
deviceId: { exact: videoDeviceId },
width: { ideal: width },
height: { ideal: height },
},
});
}
// We use the replaceTracks helper function, that will in turn
// call the WebRTC replaceTrack API with the info we requested,
// without the need to do any renegotiation on the PeerConnection
echotest.replaceTracks({
tracks: tracks,
error: function (err) {
bootbox.alert(err.message);
},
});
// Reset devices controls
$("#audio-device, #video-device").removeAttr("disabled");
$("#change-devices").removeAttr("disabled");
return;
}
// We're only now starting, create a new PeerConnection
firstOffer = false;
let body = { audio: true, video: true };
// We can try and force a specific codec, by telling the plugin what we'd prefer
// For simplicity, you can set it via a query string (e.g., ?vcodec=vp9)
if (acodec) body["audiocodec"] = acodec;
if (vcodec) body["videocodec"] = vcodec;
// For the codecs that support them (VP9 and H.264) you can specify a codec
// profile as well (e.g., ?vprofile=2 for VP9, or ?vprofile=42e01f for H.264)
if (vprofile) body["videoprofile"] = vprofile;
Janus.debug("Trying a createOffer too (audio/video sendrecv)");
echotest.createOffer({
// We provide a specific device ID for both audio and video
tracks: [
{
type: "audio",
capture: { deviceId: { exact: audioDeviceId } },
recv: true,
},
{
type: "video",
capture: {
deviceId: { exact: videoDeviceId },
width: { ideal: width },
height: { ideal: height },
},
recv: true,
simulcast: doSimulcast,
},
{ type: "data" }, // Let's negotiate data channels as well
],
success: function (jsep) {
Janus.debug("Got SDP!", jsep);
echotest.send({ message: body, jsep: jsep });
// Create a spinner waiting for the remote video
$("#videoright").html(
'
' +
'
' +
' Loading...' +
"
" +
"
"
);
},
error: function (error) {
Janus.error("WebRTC error:", error);
bootbox.alert("WebRTC error... " + error.message);
},
});
}
$(document).ready(function () {
// Initialize the library (all console debuggers enabled)
Janus.init({
debug: "all",
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 EchoTest plugin
janus.attach({
plugin: "janus.plugin.echotest",
opaqueId: opaqueId,
success: function (pluginHandle) {
$("#details").remove();
echotest = pluginHandle;
Janus.log(
"Plugin attached! (" +
echotest.getPlugin() +
", id=" +
echotest.getId() +
")"
);
// Enumerate devices: that's what we're here for
Janus.listDevices(initDevices);
// We wait for the user to select the first device before making a move
$("#start")
.removeAttr("disabled")
.html("Stop")
.click(function () {
$(this).attr("disabled", true);
if (bitrateTimer) clearInterval(bitrateTimer);
bitrateTimer = null;
janus.destroy();
});
},
error: function (error) {
console.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);
if (jsep) {
Janus.debug("Handling SDP as well...", jsep);
echotest.handleRemoteJsep({ jsep: jsep });
}
let result = msg["result"];
if (result) {
if (result === "done") {
// The plugin closed the echo test
bootbox.alert("The Echo Test is over");
$("video").remove();
$("#waitingvideo").remove();
audioenabled = true;
$("#toggleaudio")
.attr("disabled", true)
.html("Disable audio")
.removeClass("btn-success")
.addClass("btn-danger");
videoenabled = true;
$("#togglevideo")
.attr("disabled", true)
.html("Disable video")
.removeClass("btn-success")
.addClass("btn-danger");
$("#bitrate").attr("disabled", true);
$("#bitrateset").text("Bandwidth");
$("#curbitrate").addClass("hide");
if (bitrateTimer) clearInterval(bitrateTimer);
bitrateTimer = null;
$("#curres").addClass("hide");
$("#datasend").val("").attr("disabled", true);
$("#datarecv").val("");
$("#outputdeviceset").text("Output device");
return;
}
// Any loss?
let status = result["status"];
if (status === "slow_link") {
toastr.warning(
"Janus apparently missed many packets we sent, maybe we should reduce the bitrate",
"Packet loss?",
{ timeOut: 2000 }
);
}
}
// Is simulcast in place?
let substream = msg["substream"];
let temporal = msg["temporal"];
if (
(substream !== null && substream !== undefined) ||
(temporal !== null && temporal !== undefined)
) {
if (!simulcastStarted) {
simulcastStarted = true;
addSimulcastButtons(msg["videocodec"] === "vp8");
}
// We just received notice that there's been a switch, update the buttons
updateSimulcastButtons(substream, temporal);
}
},
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) 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();
let stream = new MediaStream([track]);
localTracks[trackId] = stream;
Janus.log("Created local stream:", stream);
$("#videoleft").append(
''
);
Janus.attachMediaStream(
$("#myvideo" + trackId).get(0),
stream
);
}
if (
echotest.webrtcStuff.pc.iceConnectionState !== "completed" &&
echotest.webrtcStuff.pc.iceConnectionState !== "connected"
) {
$("#videoleft")
.parent()
.block({
message: "Publishing...",
css: {
border: "none",
backgroundColor: "transparent",
color: "white",
},
});
}
// Reset devices controls
$("#audio-device, #video-device").removeAttr("disabled");
$("#change-devices").removeAttr("disabled");
},
onremotetrack: function (track, mid, on) {
Janus.debug(
"Remote track (mid=" +
mid +
") " +
(on ? "added" : "removed") +
":",
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 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