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