// SPDX-License-Identifier: AGPL-3.0-or-later /* # # This file is part of FreedomBox. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # # This file based on example code from Janus Demos which is licensed as # follows. # # 2014-2022 Meetecho # # GPL-3 with OpenSSL exception # If you modify this Program, or any covered work, # by linking or combining it with OpenSSL # (or a modified version of that library), # containing parts covered by the terms of OpenSSL License, # the licensors of this Program grant you # additional permission to convey the resulting work. # Corresponding Source for a non-source form of such a combination # shall include the source code for the parts of openssl used # as well as that of the covered work. */ var janus = null; var sfutest = null; var opaqueId = "videoroomtest-"+Janus.randomString(12); var myroom = 1234; // Demo room if(getQueryStringValue("room") !== "") myroom = parseInt(getQueryStringValue("room")); var myusername = null; var myid = null; var mystream = null; // We use this other ID just to map our subscriptions to us var mypvtid = null; var remoteFeed = null; var feeds = {}, feedStreams = {}, subStreams = {}, slots = {}, mids = {}, subscriptions = {}; var localTracks = {}, localVideos = 0, remoteTracks = {}; var bitrateTimer = [], simulcastStarted = {}; var doSimulcast = (getQueryStringValue("simulcast") === "yes" || getQueryStringValue("simulcast") === "true"); var acodec = (getQueryStringValue("acodec") !== "" ? getQueryStringValue("acodec") : null); var vcodec = (getQueryStringValue("vcodec") !== "" ? getQueryStringValue("vcodec") : null); var subscriber_mode = (getQueryStringValue("subscriber-mode") === "yes" || getQueryStringValue("subscriber-mode") === "true"); $(document).ready(function() { // Initialize the library (all console debuggers enabled) Janus.init({debug: "all", callback: function() { // Use a button to start the video room $('#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, token: token, apisecret: apisecret, success: function() { // Attach to video room test plugin janus.attach( { plugin: "janus.plugin.videoroom", opaqueId: opaqueId, success: function(pluginHandle) { $('#details').remove(); sfutest = pluginHandle; Janus.log("Plugin attached! (" + sfutest.getPlugin() + ", id=" + sfutest.getId() + ")"); Janus.log(" -- This is a publisher/manager"); // Prepare the username registration $('#videojoin').removeClass('hide').show(); $('#registernow').removeClass('hide').show(); $('#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"); }, 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"); $("#videolocal").parent().parent().unblock(); if(!on) return; $('#publish').remove(); // This controls allows us to override the global room bitrate cap $('#bitrate').parent().parent().removeClass('hide').show(); $('#bitrate a').click(function() { var id = $(this).attr("id"); var bitrate = parseInt(id)*1000; if(bitrate === 0) { Janus.log("Not limiting bandwidth via REMB"); } else { Janus.log("Capping bandwidth to " + bitrate + " via REMB"); } $('#bitrateset').html($(this).html() + '').parent().removeClass('open'); sfutest.send({ message: { request: "configure", bitrate: bitrate }}); return false; }); }, 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 (publisher) :::", msg); var event = msg["videoroom"]; Janus.debug("Event: " + event); if(event != undefined && event != null) { if(event === "joined") { // Publisher/manager created, negotiate WebRTC and attach to existing feeds, if any myid = msg["id"]; mypvtid = msg["private_id"]; Janus.log("Successfully joined room " + msg["room"] + " with ID " + myid); if(subscriber_mode) { $('#videojoin').hide(); $('#videos').removeClass('hide').show(); } else { publishOwnFeed(true); } // Any new feed to attach to? if(msg["publishers"]) { var list = msg["publishers"]; Janus.debug("Got a list of available publishers/feeds:", list); var sources = null; for(var f in list) { var id = list[f]["id"]; var display = list[f]["display"]; var streams = list[f]["streams"]; for(var i in streams) { var stream = streams[i]; stream["id"] = id; stream["display"] = display; } feedStreams[id] = { id: id, display: display, streams: streams } Janus.debug(" >> [" + id + "] " + display + ":", streams); if(!sources) sources = []; sources.push(streams); } if(sources) subscribeTo(sources); } } else if(event === "destroyed") { // The room has been destroyed Janus.warn("The room has been destroyed!"); bootbox.alert("The room has been destroyed", function() { window.location.reload(); }); } else if(event === "event") { // Any info on our streams or a new feed to attach to? if(msg["streams"]) { var streams = msg["streams"]; for(var i in streams) { var stream = streams[i]; stream["id"] = myid; stream["display"] = myusername; } feedStreams[myid] = { id: myid, display: myusername, streams: streams } } else if(msg["publishers"]) { var list = msg["publishers"]; Janus.debug("Got a list of available publishers/feeds:", list); var sources = null; for(var f in list) { var id = list[f]["id"]; var display = list[f]["display"]; var streams = list[f]["streams"]; for(var i in streams) { var stream = streams[i]; stream["id"] = id; stream["display"] = display; } feedStreams[id] = { id: id, display: display, streams: streams } Janus.debug(" >> [" + id + "] " + display + ":", streams); if(!sources) sources = []; sources.push(streams); } if(sources) subscribeTo(sources); } else if(msg["leaving"]) { // One of the publishers has gone away? var leaving = msg["leaving"]; Janus.log("Publisher left: " + leaving); unsubscribeFrom(leaving); } else if(msg["unpublished"]) { // One of the publishers has unpublished? var unpublished = msg["unpublished"]; Janus.log("Publisher left: " + unpublished); if(unpublished === 'ok') { // That's us sfutest.hangup(); return; } unsubscribeFrom(unpublished); } else if(msg["error"]) { if(msg["error_code"] === 426) { // This is a "no such room" error: give a more meaningful description bootbox.alert( "

Apparently room " + myroom + " (the one this page uses as a test room) " + "does not exist...

Do you have an updated janus.plugin.videoroom.cfg " + "configuration file? If not, make sure you copy the details of room " + myroom + " " + "from that sample in your current configuration file, then restart Janus and try again." ); } else { bootbox.alert(msg["error"]); } } } } if(jsep) { Janus.debug("Handling SDP as well...", jsep); sfutest.handleRemoteJsep({ jsep: jsep }); // Check if any of the media we wanted to publish has // been rejected (e.g., wrong or unsupported codec) var audio = msg["audio_codec"]; if(mystream && mystream.getAudioTracks() && mystream.getAudioTracks().length > 0 && !audio) { // Audio has been rejected toastr.warning("Our audio stream has been rejected, viewers won't hear us"); } var video = msg["video_codec"]; if(mystream && mystream.getVideoTracks() && mystream.getVideoTracks().length > 0 && !video) { // Video has been rejected toastr.warning("Our video stream has been rejected, viewers won't see us"); // Hide the webcam video $('#myvideo').hide(); $('#videolocal').append( '

' + 'Video rejected, no webcam' + '
'); } } }, onlocaltrack: function(track, on) { Janus.debug(" ::: Got a local track event :::"); Janus.debug("Local track " + (on ? "added" : "removed") + ":", track); // We use the track ID as name of the element, but it may contain invalid characters var trackId = track.id.replace(/[{}]/g, ""); if(!on) { // Track removed, get rid of the stream and the rendering var stream = localTracks[trackId]; if(stream) { try { var tracks = stream.getTracks(); for(var i in tracks) { var mst = tracks[i]; if(mst) mst.stop(); } } catch(e) {} } if(track.kind === "video") { $('#myvideo' + trackId).remove(); localVideos--; if(localVideos === 0) { // No video, at least for now: show a placeholder if($('#videolocal .no-video-container').length === 0) { $('#videolocal').append( '
' + 'No webcam available' + '
'); } } } delete localTracks[trackId]; return; } // If we're here, a new track was added var stream = localTracks[trackId]; if(stream) { // We've been here already return; } $('#videos').removeClass('hide').show(); if($('#mute').length === 0) { // Add a 'mute' button $('#videolocal').append(''); $('#mute').click(toggleMute); // Add an 'unpublish' button $('#videolocal').append(''); $('#unpublish').click(unpublishOwnFeed); } 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($('#videolocal .no-video-container').length === 0) { $('#videolocal').append( '
' + 'No webcam available' + '
'); } } } else { // New video track: create a stream out of it localVideos++; $('#videolocal .no-video-container').remove(); stream = new MediaStream(); stream.addTrack(track.clone()); localTracks[trackId] = stream; Janus.log("Created local stream:", stream); Janus.log(stream.getTracks()); Janus.log(stream.getVideoTracks()); $('#videolocal').append('