// 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( '