diff --git a/misc/zoneminder.service.in b/misc/zoneminder.service.in
index d1cfb36a0..21be2e433 100644
--- a/misc/zoneminder.service.in
+++ b/misc/zoneminder.service.in
@@ -3,8 +3,8 @@
[Unit]
Description=ZoneMinder CCTV recording and security system
-After=network.target mysqld.service httpd.service
-Requires=mysqld.service httpd.service
+After=network.target mysqld.service httpd.service janus.service
+Requires=mysqld.service httpd.service janus.service
[Service]
User=@WEB_USER@
diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp
index e638ed8c8..15a812f83 100644
--- a/src/zm_monitor.cpp
+++ b/src/zm_monitor.cpp
@@ -1100,12 +1100,13 @@ bool Monitor::connect() {
set_credentials(soap);
Debug(1, "ONVIF Endpoint: %s", proxyEvent.soap_endpoint);
if (proxyEvent.CreatePullPointSubscription(&request, response) != SOAP_OK) {
- Warning("Couldn't create subscription!");
+ Error("Couldn't create subscription! %s, %s", soap_fault_string(soap), soap_fault_detail(soap));
} else {
//Empty the stored messages
set_credentials(soap);
- if (proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) {
- Warning("Couldn't do initial event pull! %s", response.SubscriptionReference.Address);
+ if ((proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) &&
+ ( soap->error != SOAP_EOF)) { //SOAP_EOF could indicate no messages to pull.
+ Error("Couldn't do initial event pull! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap));
} else {
Debug(1, "Good Initial ONVIF Pull");
ONVIF_Healthy = TRUE;
@@ -1120,7 +1121,9 @@ bool Monitor::connect() {
#if HAVE_LIBCURL //janus setup. Depends on libcurl.
if (janus_enabled && (path.find("rtsp://") != std::string::npos)) {
if (add_to_janus() != 0) {
- Warning("Failed to add monitor stream to Janus!");
+ if (add_to_janus() != 0) {
+ Warning("Failed to add monitor stream to Janus!");
+ }
}
}
#endif
@@ -1794,8 +1797,10 @@ bool Monitor::Poll() {
set_credentials(soap);
int result = proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse);
if (result != SOAP_OK) {
- if (result != -1) //Ignore the timeout error
- Warning("Failed to get ONVIF messages! %i", result);
+ if (result != SOAP_EOF) { //Ignore the timeout error
+ Error("Failed to get ONVIF messages! %s", soap_fault_string(soap));
+ ONVIF_Healthy = FALSE;
+ }
} else {
Debug(1, "Got Good Response! %i", result);
for (auto msg : tev__PullMessagesResponse.wsnt__NotificationMessage) {
@@ -1814,6 +1819,7 @@ bool Monitor::Poll() {
if (!ONVIF_Trigger_State) {
Debug(1,"Triggered Event");
ONVIF_Trigger_State = TRUE;
+ std::this_thread::sleep_for (std::chrono::seconds(1)); //thread sleep
}
} else {
Debug(1, "Triggered off ONVIF");
@@ -1826,6 +1832,8 @@ bool Monitor::Poll() {
}
}
}
+ } else {
+ std::this_thread::sleep_for (std::chrono::seconds(1)); //thread sleep to avoid the busy loop.
}
#endif
return TRUE;
@@ -2109,11 +2117,14 @@ bool Monitor::Analyse() {
Debug(1, "Staying in %s", State_Strings[state].c_str());
}
if (state == ALARM) {
- last_alarm_count = analysis_image_count;
+ last_alarm_count = analysis_image_count;
} // This is needed so post_event_count counts after last alarmed frames while in ALARM not single alarmed frames while ALERT
- } else if (!score and (snap->score == 0)) { // snap->score means -1 which means didn't do motion detection so don't do state transition
+
+ // snap->score -1 means didn't do motion detection so don't do state transition
+ // In Nodect, we may still have a triggered event, so need this code to run to end the event.
+ } else if (!score and ((snap->score == 0) or (function == NODECT))) {
Debug(1, "!score %s", State_Strings[state].c_str());
- alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count
+ alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count
if (state == ALARM) {
Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count);
diff --git a/web/js/MonitorStream.js b/web/js/MonitorStream.js
index ef4d6db41..aaf5ac8fb 100644
--- a/web/js/MonitorStream.js
+++ b/web/js/MonitorStream.js
@@ -1,3 +1,5 @@
+var janus = null;
+var streaming = [];
function MonitorStream(monitorData) {
this.id = monitorData.id;
@@ -8,6 +10,7 @@ function MonitorStream(monitorData) {
this.url_to_zms = monitorData.url_to_zms;
this.width = monitorData.width;
this.height = monitorData.height;
+ this.janusEnabled = monitorData.janusEnabled;
this.scale = 100;
this.status = null;
this.alarmState = STATE_IDLE;
@@ -80,6 +83,17 @@ function MonitorStream(monitorData) {
const stream = this.getElement();
if (!stream) return;
+ if (this.janusEnabled) {
+ var id = parseInt(this.id);
+ var server = "http://" + window.location.hostname + ":8088/janus";
+ if (janus == null) {
+ Janus.init({debug: "all", callback: function() {
+ janus = new Janus({server: server}); //new Janus
+ }});
+ }
+ attachVideo(id);
+ return;
+ }
if (!stream.src) {
// Website Monitors won't have an img tag
console.log('No src for #liveStream'+this.id);
@@ -287,3 +301,68 @@ function MonitorStream(monitorData) {
this.streamCmdReq(this.streamCmdParms);
};
} // end function MonitorStream
+
+async function attachVideo(id) {
+ await waitUntil(() => janus.isConnected() )
+ janus.attach({
+ plugin: "janus.plugin.streaming",
+ opaqueId: "streamingtest-"+Janus.randomString(12),
+ success: function(pluginHandle) {
+ streaming[id] = pluginHandle;
+ var body = { "request": "watch", "id": id};
+ streaming[id].send({"message": body});
+ },
+ error: function(error) {
+ Janus.error(" -- Error attaching plugin... ", error);
+ },
+ onmessage: function(msg, jsep) {
+ Janus.debug(" ::: Got a message :::");
+ Janus.debug(msg);
+ var result = msg["result"];
+ if(result !== null && result !== undefined) {
+ if(result["status"] !== undefined && result["status"] !== null) {
+ var status = result["status"];
+ Janus.debug(status);
+ }
+ } else if(msg["error"] !== undefined && msg["error"] !== null) {
+ return;
+ }
+ if(jsep !== undefined && jsep !== null) {
+ Janus.debug("Handling SDP as well...");
+ Janus.debug(jsep);
+ // Offer from the plugin, let's answer
+ streaming[id].createAnswer({
+ jsep: jsep,
+ // We want recvonly audio/video and, if negotiated, datachannels
+ media: { audioSend: false, videoSend: false, data: true },
+ success: function(jsep) {
+ Janus.debug("Got SDP!");
+ Janus.debug(jsep);
+ var body = { "request": "start"};
+ streaming[id].send({"message": body, "jsep": jsep});
+ },
+ error: function(error) {
+ Janus.error("WebRTC error:", error);
+ }
+ });
+ }
+ }, //onmessage function
+ onremotestream: function(ourstream) {
+ Janus.debug(" ::: Got a remote track :::");
+ Janus.debug(ourstream);
+ Janus.attachMediaStream(document.getElementById("liveStream" + id), ourstream);
+ }
+ }); // janus.attach
+} //function attachVideo
+
+const waitUntil = (condition) => {
+ return new Promise((resolve) => {
+ let interval = setInterval(() => {
+ if (!condition()) {
+ return;
+ }
+ clearInterval(interval);
+ resolve();
+ }, 100);
+ });
+}
diff --git a/web/js/janus.js b/web/js/janus.js
deleted file mode 100644
index 177bcf2f7..000000000
--- a/web/js/janus.js
+++ /dev/null
@@ -1,3649 +0,0 @@
-"use strict";
-
-/*
- The MIT License (MIT)
-
- Copyright (c) 2016 Meetecho
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the "Software"),
- to deal in the Software without restriction, including without limitation
- the rights to use, copy, modify, merge, publish, distribute, sublicense,
- and/or sell copies of the Software, and to permit persons to whom the
- Software is furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- OTHER DEALINGS IN THE SOFTWARE.
- */
-
-// List of sessions
-Janus.sessions = {};
-
-Janus.isExtensionEnabled = function() {
- if(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
- // No need for the extension, getDisplayMedia is supported
- return true;
- }
- if(window.navigator.userAgent.match('Chrome')) {
- var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10);
- var maxver = 33;
- if(window.navigator.userAgent.match('Linux'))
- maxver = 35; // "known" crash in chrome 34 and 35 on linux
- if(chromever >= 26 && chromever <= maxver) {
- // Older versions of Chrome don't support this extension-based approach, so lie
- return true;
- }
- return Janus.extension.isInstalled();
- } else {
- // Firefox and others, no need for the extension (but this doesn't mean it will work)
- return true;
- }
-};
-
-var defaultExtension = {
- // Screensharing Chrome Extension ID
- extensionId: 'hapfgfdkleiggjjpfpenajgdnfckjpaj',
- isInstalled: function() { return document.querySelector('#janus-extension-installed') !== null; },
- getScreen: function (callback) {
- var pending = window.setTimeout(function () {
- var error = new Error('NavigatorUserMediaError');
- error.name = 'The required Chrome extension is not installed: click here to install it. (NOTE: this will need you to refresh the page)';
- return callback(error);
- }, 1000);
- this.cache[pending] = callback;
- window.postMessage({ type: 'janusGetScreen', id: pending }, '*');
- },
- init: function () {
- var cache = {};
- this.cache = cache;
- // Wait for events from the Chrome Extension
- window.addEventListener('message', function (event) {
- if(event.origin != window.location.origin)
- return;
- if(event.data.type == 'janusGotScreen' && cache[event.data.id]) {
- var callback = cache[event.data.id];
- delete cache[event.data.id];
-
- if (event.data.sourceId === '') {
- // user canceled
- var error = new Error('NavigatorUserMediaError');
- error.name = 'You cancelled the request for permission, giving up...';
- callback(error);
- } else {
- callback(null, event.data.sourceId);
- }
- } else if (event.data.type == 'janusGetScreenPending') {
- console.log('clearing ', event.data.id);
- window.clearTimeout(event.data.id);
- }
- });
- }
-};
-
-Janus.useDefaultDependencies = function (deps) {
- var f = (deps && deps.fetch) || fetch;
- var p = (deps && deps.Promise) || Promise;
- var socketCls = (deps && deps.WebSocket) || WebSocket;
-
- return {
- newWebSocket: function(server, proto) { return new socketCls(server, proto); },
- extension: (deps && deps.extension) || defaultExtension,
- isArray: function(arr) { return Array.isArray(arr); },
- webRTCAdapter: (deps && deps.adapter) || adapter,
- httpAPICall: function(url, options) {
- var fetchOptions = {
- method: options.verb,
- headers: {
- 'Accept': 'application/json, text/plain, */*'
- },
- cache: 'no-cache'
- };
- if(options.verb === "POST") {
- fetchOptions.headers['Content-Type'] = 'application/json';
- }
- if(options.withCredentials !== undefined) {
- fetchOptions.credentials = options.withCredentials === true ? 'include' : (options.withCredentials ? options.withCredentials : 'omit');
- }
- if(options.body) {
- fetchOptions.body = JSON.stringify(options.body);
- }
-
- var fetching = f(url, fetchOptions).catch(function(error) {
- return p.reject({message: 'Probably a network error, is the server down?', error: error});
- });
-
- /*
- * fetch() does not natively support timeouts.
- * Work around this by starting a timeout manually, and racing it agains the fetch() to see which thing resolves first.
- */
-
- if(options.timeout) {
- var timeout = new p(function(resolve, reject) {
- var timerId = setTimeout(function() {
- clearTimeout(timerId);
- return reject({message: 'Request timed out', timeout: options.timeout});
- }, options.timeout);
- });
- fetching = p.race([fetching, timeout]);
- }
-
- fetching.then(function(response) {
- if(response.ok) {
- if(typeof(options.success) === typeof(Janus.noop)) {
- return response.json().then(function(parsed) {
- try {
- options.success(parsed);
- } catch(error) {
- Janus.error('Unhandled httpAPICall success callback error', error);
- }
- }, function(error) {
- return p.reject({message: 'Failed to parse response body', error: error, response: response});
- });
- }
- }
- else {
- return p.reject({message: 'API call failed', response: response});
- }
- }).catch(function(error) {
- if(typeof(options.error) === typeof(Janus.noop)) {
- options.error(error.message || '<< internal error >>', error);
- }
- });
-
- return fetching;
- }
- }
-};
-
-Janus.useOldDependencies = function (deps) {
- var jq = (deps && deps.jQuery) || jQuery;
- var socketCls = (deps && deps.WebSocket) || WebSocket;
- return {
- newWebSocket: function(server, proto) { return new socketCls(server, proto); },
- isArray: function(arr) { return jq.isArray(arr); },
- extension: (deps && deps.extension) || defaultExtension,
- webRTCAdapter: (deps && deps.adapter) || adapter,
- httpAPICall: function(url, options) {
- var payload = options.body !== undefined ? {
- contentType: 'application/json',
- data: JSON.stringify(options.body)
- } : {};
- var credentials = options.withCredentials !== undefined ? {xhrFields: {withCredentials: options.withCredentials}} : {};
-
- return jq.ajax(jq.extend(payload, credentials, {
- url: url,
- type: options.verb,
- cache: false,
- dataType: 'json',
- async: options.async,
- timeout: options.timeout,
- success: function(result) {
- if(typeof(options.success) === typeof(Janus.noop)) {
- options.success(result);
- }
- },
- error: function(xhr, status, err) {
- if(typeof(options.error) === typeof(Janus.noop)) {
- options.error(status, err);
- }
- }
- }));
- }
- };
-};
-
-Janus.noop = function() {};
-
-Janus.dataChanDefaultLabel = "JanusDataChannel";
-
-// Note: in the future we may want to change this, e.g., as was
-// attempted in https://github.com/meetecho/janus-gateway/issues/1670
-Janus.endOfCandidates = null;
-
-// Stop all tracks from a given stream
-Janus.stopAllTracks = function(stream) {
- try {
- // Try a MediaStreamTrack.stop() for each track
- var tracks = stream.getTracks();
- for(var mst of tracks) {
- Janus.log(mst);
- if(mst) {
- mst.stop();
- }
- }
- } catch(e) {
- // Do nothing if this fails
- }
-}
-
-// Initialization
-Janus.init = function(options) {
- options = options || {};
- options.callback = (typeof options.callback == "function") ? options.callback : Janus.noop;
- if(Janus.initDone) {
- // Already initialized
- options.callback();
- } else {
- if(typeof console.log == "undefined") {
- console.log = function() {};
- }
- // Console logging (all debugging disabled by default)
- Janus.trace = Janus.noop;
- Janus.debug = Janus.noop;
- Janus.vdebug = Janus.noop;
- Janus.log = Janus.noop;
- Janus.warn = Janus.noop;
- Janus.error = Janus.noop;
- if(options.debug === true || options.debug === "all") {
- // Enable all debugging levels
- Janus.trace = console.trace.bind(console);
- Janus.debug = console.debug.bind(console);
- Janus.vdebug = console.debug.bind(console);
- Janus.log = console.log.bind(console);
- Janus.warn = console.warn.bind(console);
- Janus.error = console.error.bind(console);
- } else if(Array.isArray(options.debug)) {
- for(var d of options.debug) {
- switch(d) {
- case "trace":
- Janus.trace = console.trace.bind(console);
- break;
- case "debug":
- Janus.debug = console.debug.bind(console);
- break;
- case "vdebug":
- Janus.vdebug = console.debug.bind(console);
- break;
- case "log":
- Janus.log = console.log.bind(console);
- break;
- case "warn":
- Janus.warn = console.warn.bind(console);
- break;
- case "error":
- Janus.error = console.error.bind(console);
- break;
- default:
- console.error("Unknown debugging option '" + d + "' (supported: 'trace', 'debug', 'vdebug', 'log', warn', 'error')");
- break;
- }
- }
- }
- Janus.log("Initializing library");
-
- var usedDependencies = options.dependencies || Janus.useDefaultDependencies();
- Janus.isArray = usedDependencies.isArray;
- Janus.webRTCAdapter = usedDependencies.webRTCAdapter;
- Janus.httpAPICall = usedDependencies.httpAPICall;
- Janus.newWebSocket = usedDependencies.newWebSocket;
- Janus.extension = usedDependencies.extension;
- Janus.extension.init();
-
- // Helper method to enumerate devices
- Janus.listDevices = function(callback, config) {
- callback = (typeof callback == "function") ? callback : Janus.noop;
- if (config == null) config = { audio: true, video: true };
- if(Janus.isGetUserMediaAvailable()) {
- navigator.mediaDevices.getUserMedia(config)
- .then(function(stream) {
- navigator.mediaDevices.enumerateDevices().then(function(devices) {
- Janus.debug(devices);
- callback(devices);
- // Get rid of the now useless stream
- Janus.stopAllTracks(stream)
- });
- })
- .catch(function(err) {
- Janus.error(err);
- callback([]);
- });
- } else {
- Janus.warn("navigator.mediaDevices unavailable");
- callback([]);
- }
- };
- // Helper methods to attach/reattach a stream to a video element (previously part of adapter.js)
- Janus.attachMediaStream = function(element, stream) {
- try {
- element.srcObject = stream;
- } catch (e) {
- try {
- element.src = URL.createObjectURL(stream);
- } catch (e) {
- Janus.error("Error attaching stream to element");
- }
- }
- };
- Janus.reattachMediaStream = function(to, from) {
- try {
- to.srcObject = from.srcObject;
- } catch (e) {
- try {
- to.src = from.src;
- } catch (e) {
- Janus.error("Error reattaching stream to element");
- }
- }
- };
- // Detect tab close: make sure we don't loose existing onbeforeunload handlers
- // (note: for iOS we need to subscribe to a different event, 'pagehide', see
- // https://gist.github.com/thehunmonkgroup/6bee8941a49b86be31a787fe8f4b8cfe)
- var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0;
- var eventName = iOS ? 'pagehide' : 'beforeunload';
- var oldOBF = window["on" + eventName];
- window.addEventListener(eventName, function() {
- Janus.log("Closing window");
- for(var s in Janus.sessions) {
- if(Janus.sessions[s] && Janus.sessions[s].destroyOnUnload) {
- Janus.log("Destroying session " + s);
- Janus.sessions[s].destroy({unload: true, notifyDestroyed: false});
- }
- }
- if(oldOBF && typeof oldOBF == "function") {
- oldOBF();
- }
- });
- // If this is a Safari Technology Preview, check if VP8 is supported
- Janus.safariVp8 = false;
- if(Janus.webRTCAdapter.browserDetails.browser === 'safari' &&
- Janus.webRTCAdapter.browserDetails.version >= 605) {
- // Let's see if RTCRtpSender.getCapabilities() is there
- if(RTCRtpSender && RTCRtpSender.getCapabilities && RTCRtpSender.getCapabilities("video") &&
- RTCRtpSender.getCapabilities("video").codecs && RTCRtpSender.getCapabilities("video").codecs.length) {
- for(var codec of RTCRtpSender.getCapabilities("video").codecs) {
- if(codec && codec.mimeType && codec.mimeType.toLowerCase() === "video/vp8") {
- Janus.safariVp8 = true;
- break;
- }
- }
- if(Janus.safariVp8) {
- Janus.log("This version of Safari supports VP8");
- } else {
- Janus.warn("This version of Safari does NOT support VP8: if you're using a Technology Preview, " +
- "try enabling the 'WebRTC VP8 codec' setting in the 'Experimental Features' Develop menu");
- }
- } else {
- // We do it in a very ugly way, as there's no alternative...
- // We create a PeerConnection to see if VP8 is in an offer
- var testpc = new RTCPeerConnection({});
- testpc.createOffer({offerToReceiveVideo: true}).then(function(offer) {
- Janus.safariVp8 = offer.sdp.indexOf("VP8") !== -1;
- if(Janus.safariVp8) {
- Janus.log("This version of Safari supports VP8");
- } else {
- Janus.warn("This version of Safari does NOT support VP8: if you're using a Technology Preview, " +
- "try enabling the 'WebRTC VP8 codec' setting in the 'Experimental Features' Develop menu");
- }
- testpc.close();
- testpc = null;
- });
- }
- }
- // Check if this browser supports Unified Plan and transceivers
- // Based on https://codepen.io/anon/pen/ZqLwWV?editors=0010
- Janus.unifiedPlan = false;
- if(Janus.webRTCAdapter.browserDetails.browser === 'firefox' &&
- Janus.webRTCAdapter.browserDetails.version >= 59) {
- // Firefox definitely does, starting from version 59
- Janus.unifiedPlan = true;
- } else if(Janus.webRTCAdapter.browserDetails.browser === 'chrome' &&
- Janus.webRTCAdapter.browserDetails.version >= 72) {
- // Chrome does, but it's only usable from version 72 on
- Janus.unifiedPlan = true;
- } else if(!window.RTCRtpTransceiver || !('currentDirection' in RTCRtpTransceiver.prototype)) {
- // Safari supports addTransceiver() but not Unified Plan when
- // currentDirection is not defined (see codepen above).
- Janus.unifiedPlan = false;
- } else {
- // Check if addTransceiver() throws an exception
- var tempPc = new RTCPeerConnection();
- try {
- tempPc.addTransceiver('audio');
- Janus.unifiedPlan = true;
- } catch (e) {}
- tempPc.close();
- }
- Janus.initDone = true;
- options.callback();
- }
-};
-
-// Helper method to check whether WebRTC is supported by this browser
-Janus.isWebrtcSupported = function() {
- return !!window.RTCPeerConnection;
-};
-// Helper method to check whether devices can be accessed by this browser (e.g., not possible via plain HTTP)
-Janus.isGetUserMediaAvailable = function() {
- return navigator.mediaDevices && navigator.mediaDevices.getUserMedia;
-};
-
-// Helper method to create random identifiers (e.g., transaction)
-Janus.randomString = function(len) {
- var charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
- var randomString = '';
- for (var i = 0; i < len; i++) {
- var randomPoz = Math.floor(Math.random() * charSet.length);
- randomString += charSet.substring(randomPoz,randomPoz+1);
- }
- return randomString;
-};
-
-function Janus(gatewayCallbacks) {
- gatewayCallbacks = gatewayCallbacks || {};
- gatewayCallbacks.success = (typeof gatewayCallbacks.success == "function") ? gatewayCallbacks.success : Janus.noop;
- gatewayCallbacks.error = (typeof gatewayCallbacks.error == "function") ? gatewayCallbacks.error : Janus.noop;
- gatewayCallbacks.destroyed = (typeof gatewayCallbacks.destroyed == "function") ? gatewayCallbacks.destroyed : Janus.noop;
- if(!Janus.initDone) {
- gatewayCallbacks.error("Library not initialized");
- return {};
- }
- if(!Janus.isWebrtcSupported()) {
- gatewayCallbacks.error("WebRTC not supported by this browser");
- return {};
- }
- Janus.log("Library initialized: " + Janus.initDone);
- if(!gatewayCallbacks.server) {
- gatewayCallbacks.error("Invalid server url");
- return {};
- }
- var websockets = false;
- var ws = null;
- var wsHandlers = {};
- var wsKeepaliveTimeoutId = null;
- var servers = null;
- var serversIndex = 0;
- var server = gatewayCallbacks.server;
- if(Janus.isArray(server)) {
- Janus.log("Multiple servers provided (" + server.length + "), will use the first that works");
- server = null;
- servers = gatewayCallbacks.server;
- Janus.debug(servers);
- } else {
- if(server.indexOf("ws") === 0) {
- websockets = true;
- Janus.log("Using WebSockets to contact Janus: " + server);
- } else {
- websockets = false;
- Janus.log("Using REST API to contact Janus: " + server);
- }
- }
- var iceServers = gatewayCallbacks.iceServers || [{urls: "stun:stun.l.google.com:19302"}];
- var iceTransportPolicy = gatewayCallbacks.iceTransportPolicy;
- var bundlePolicy = gatewayCallbacks.bundlePolicy;
- // Whether IPv6 candidates should be gathered
- var ipv6Support = (gatewayCallbacks.ipv6 === true);
- // Whether we should enable the withCredentials flag for XHR requests
- var withCredentials = false;
- if(gatewayCallbacks.withCredentials !== undefined && gatewayCallbacks.withCredentials !== null)
- withCredentials = gatewayCallbacks.withCredentials === true;
- // Optional max events
- var maxev = 10;
- if(gatewayCallbacks.max_poll_events !== undefined && gatewayCallbacks.max_poll_events !== null)
- maxev = gatewayCallbacks.max_poll_events;
- if(maxev < 1)
- maxev = 1;
- // Token to use (only if the token based authentication mechanism is enabled)
- var token = null;
- if(gatewayCallbacks.token !== undefined && gatewayCallbacks.token !== null)
- token = gatewayCallbacks.token;
- // API secret to use (only if the shared API secret is enabled)
- var apisecret = null;
- if(gatewayCallbacks.apisecret !== undefined && gatewayCallbacks.apisecret !== null)
- apisecret = gatewayCallbacks.apisecret;
- // Whether we should destroy this session when onbeforeunload is called
- this.destroyOnUnload = true;
- if(gatewayCallbacks.destroyOnUnload !== undefined && gatewayCallbacks.destroyOnUnload !== null)
- this.destroyOnUnload = (gatewayCallbacks.destroyOnUnload === true);
- // Some timeout-related values
- var keepAlivePeriod = 25000;
- if(gatewayCallbacks.keepAlivePeriod !== undefined && gatewayCallbacks.keepAlivePeriod !== null)
- keepAlivePeriod = gatewayCallbacks.keepAlivePeriod;
- if(isNaN(keepAlivePeriod))
- keepAlivePeriod = 25000;
- var longPollTimeout = 60000;
- if(gatewayCallbacks.longPollTimeout !== undefined && gatewayCallbacks.longPollTimeout !== null)
- longPollTimeout = gatewayCallbacks.longPollTimeout;
- if(isNaN(longPollTimeout))
- longPollTimeout = 60000;
-
- // overrides for default maxBitrate values for simulcasting
- function getMaxBitrates(simulcastMaxBitrates) {
- var maxBitrates = {
- high: 900000,
- medium: 300000,
- low: 100000,
- };
-
- if (simulcastMaxBitrates !== undefined && simulcastMaxBitrates !== null) {
- if (simulcastMaxBitrates.high)
- maxBitrates.high = simulcastMaxBitrates.high;
- if (simulcastMaxBitrates.medium)
- maxBitrates.medium = simulcastMaxBitrates.medium;
- if (simulcastMaxBitrates.low)
- maxBitrates.low = simulcastMaxBitrates.low;
- }
-
- return maxBitrates;
- }
-
- var connected = false;
- var sessionId = null;
- var pluginHandles = {};
- var that = this;
- var retries = 0;
- var transactions = {};
- createSession(gatewayCallbacks);
-
- // Public methods
- this.getServer = function() { return server; };
- this.isConnected = function() { return connected; };
- this.reconnect = function(callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- callbacks["reconnect"] = true;
- createSession(callbacks);
- };
- this.getSessionId = function() { return sessionId; };
- this.getInfo = function(callbacks) { getInfo(callbacks); };
- this.destroy = function(callbacks) { destroySession(callbacks); };
- this.attach = function(callbacks) { createHandle(callbacks); };
-
- function eventHandler() {
- if(sessionId == null)
- return;
- Janus.debug('Long poll...');
- if(!connected) {
- Janus.warn("Is the server down? (connected=false)");
- return;
- }
- var longpoll = server + "/" + sessionId + "?rid=" + new Date().getTime();
- if(maxev)
- longpoll = longpoll + "&maxev=" + maxev;
- if(token)
- longpoll = longpoll + "&token=" + encodeURIComponent(token);
- if(apisecret)
- longpoll = longpoll + "&apisecret=" + encodeURIComponent(apisecret);
- Janus.httpAPICall(longpoll, {
- verb: 'GET',
- withCredentials: withCredentials,
- success: handleEvent,
- timeout: longPollTimeout,
- error: function(textStatus, errorThrown) {
- Janus.error(textStatus + ":", errorThrown);
- retries++;
- if(retries > 3) {
- // Did we just lose the server? :-(
- connected = false;
- gatewayCallbacks.error("Lost connection to the server (is it down?)");
- return;
- }
- eventHandler();
- }
- });
- }
-
- // Private event handler: this will trigger plugin callbacks, if set
- function handleEvent(json, skipTimeout) {
- retries = 0;
- if(!websockets && sessionId !== undefined && sessionId !== null && skipTimeout !== true)
- eventHandler();
- if(!websockets && Janus.isArray(json)) {
- // We got an array: it means we passed a maxev > 1, iterate on all objects
- for(var i=0; i data channel: ' + dcState);
- if(dcState === 'open') {
- // Any pending messages to send?
- if(config.dataChannel[label].pending && config.dataChannel[label].pending.length > 0) {
- Janus.log("Sending pending messages on <" + label + ">:", config.dataChannel[label].pending.length);
- for(var data of config.dataChannel[label].pending) {
- Janus.log("Sending data on data channel <" + label + ">");
- Janus.debug(data);
- config.dataChannel[label].send(data);
- }
- config.dataChannel[label].pending = [];
- }
- // Notify the open data channel
- pluginHandle.ondataopen(label, protocol);
- }
- };
- var onDataChannelError = function(error) {
- Janus.error('Got error on data channel:', error);
- // TODO
- };
- if(!incoming) {
- // FIXME Add options (ordered, maxRetransmits, etc.)
- var dcoptions = config.dataChannelOptions;
- if(dcprotocol)
- dcoptions.protocol = dcprotocol;
- config.dataChannel[dclabel] = config.pc.createDataChannel(dclabel, dcoptions);
- } else {
- // The channel was created by Janus
- config.dataChannel[dclabel] = incoming;
- }
- config.dataChannel[dclabel].onmessage = onDataChannelMessage;
- config.dataChannel[dclabel].onopen = onDataChannelStateChange;
- config.dataChannel[dclabel].onclose = onDataChannelStateChange;
- config.dataChannel[dclabel].onerror = onDataChannelError;
- config.dataChannel[dclabel].pending = [];
- if(pendingData)
- config.dataChannel[dclabel].pending.push(pendingData);
- }
-
- // Private method to send a data channel message
- function sendData(handleId, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- var data = callbacks.text || callbacks.data;
- if(!data) {
- Janus.warn("Invalid data");
- callbacks.error("Invalid data");
- return;
- }
- var label = callbacks.label ? callbacks.label : Janus.dataChanDefaultLabel;
- if(!config.dataChannel[label]) {
- // Create new data channel and wait for it to open
- createDataChannel(handleId, label, callbacks.protocol, false, data, callbacks.protocol);
- callbacks.success();
- return;
- }
- if(config.dataChannel[label].readyState !== "open") {
- config.dataChannel[label].pending.push(data);
- callbacks.success();
- return;
- }
- Janus.log("Sending data on data channel <" + label + ">");
- Janus.debug(data);
- config.dataChannel[label].send(data);
- callbacks.success();
- }
-
- // Private method to send a DTMF tone
- function sendDtmf(handleId, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- if(!config.dtmfSender) {
- // Create the DTMF sender the proper way, if possible
- if(config.pc) {
- var senders = config.pc.getSenders();
- var audioSender = senders.find(function(sender) {
- return sender.track && sender.track.kind === 'audio';
- });
- if(!audioSender) {
- Janus.warn("Invalid DTMF configuration (no audio track)");
- callbacks.error("Invalid DTMF configuration (no audio track)");
- return;
- }
- config.dtmfSender = audioSender.dtmf;
- if(config.dtmfSender) {
- Janus.log("Created DTMF Sender");
- config.dtmfSender.ontonechange = function(tone) { Janus.debug("Sent DTMF tone: " + tone.tone); };
- }
- }
- if(!config.dtmfSender) {
- Janus.warn("Invalid DTMF configuration");
- callbacks.error("Invalid DTMF configuration");
- return;
- }
- }
- var dtmf = callbacks.dtmf;
- if(!dtmf) {
- Janus.warn("Invalid DTMF parameters");
- callbacks.error("Invalid DTMF parameters");
- return;
- }
- var tones = dtmf.tones;
- if(!tones) {
- Janus.warn("Invalid DTMF string");
- callbacks.error("Invalid DTMF string");
- return;
- }
- var duration = (typeof dtmf.duration === 'number') ? dtmf.duration : 500; // We choose 500ms as the default duration for a tone
- var gap = (typeof dtmf.gap === 'number') ? dtmf.gap : 50; // We choose 50ms as the default gap between tones
- Janus.debug("Sending DTMF string " + tones + " (duration " + duration + "ms, gap " + gap + "ms)");
- config.dtmfSender.insertDTMF(tones, duration, gap);
- callbacks.success();
- }
-
- // Private method to destroy a plugin handle
- function destroyHandle(handleId, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- var noRequest = (callbacks.noRequest === true);
- Janus.log("Destroying handle " + handleId + " (only-locally=" + noRequest + ")");
- cleanupWebrtc(handleId);
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || pluginHandle.detached) {
- // Plugin was already detached by Janus, calling detach again will return a handle not found error, so just exit here
- delete pluginHandles[handleId];
- callbacks.success();
- return;
- }
- pluginHandle.detached = true;
- if(noRequest) {
- // We're only removing the handle locally
- delete pluginHandles[handleId];
- callbacks.success();
- return;
- }
- if(!connected) {
- Janus.warn("Is the server down? (connected=false)");
- callbacks.error("Is the server down? (connected=false)");
- return;
- }
- var request = { "janus": "detach", "transaction": Janus.randomString(12) };
- if(pluginHandle.token)
- request["token"] = pluginHandle.token;
- if(apisecret)
- request["apisecret"] = apisecret;
- if(websockets) {
- request["session_id"] = sessionId;
- request["handle_id"] = handleId;
- ws.send(JSON.stringify(request));
- delete pluginHandles[handleId];
- callbacks.success();
- return;
- }
- Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, {
- verb: 'POST',
- withCredentials: withCredentials,
- body: request,
- success: function(json) {
- Janus.log("Destroyed handle:");
- Janus.debug(json);
- if(json["janus"] !== "success") {
- Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
- }
- delete pluginHandles[handleId];
- callbacks.success();
- },
- error: function(textStatus, errorThrown) {
- Janus.error(textStatus + ":", errorThrown); // FIXME
- // We cleanup anyway
- delete pluginHandles[handleId];
- callbacks.success();
- }
- });
- }
-
- // WebRTC stuff
- function streamsDone(handleId, jsep, media, callbacks, stream) {
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- // Close all tracks if the given stream has been created internally
- if(!callbacks.stream) {
- Janus.stopAllTracks(stream);
- }
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- Janus.debug("streamsDone:", stream);
- if(stream) {
- Janus.debug(" -- Audio tracks:", stream.getAudioTracks());
- Janus.debug(" -- Video tracks:", stream.getVideoTracks());
- }
- // We're now capturing the new stream: check if we're updating or if it's a new thing
- var addTracks = false;
- if(!config.myStream || !media.update || (config.streamExternal && !media.replaceAudio && !media.replaceVideo)) {
- config.myStream = stream;
- addTracks = true;
- } else {
- // We only need to update the existing stream
- if(((!media.update && isAudioSendEnabled(media)) || (media.update && (media.addAudio || media.replaceAudio))) &&
- stream.getAudioTracks() && stream.getAudioTracks().length) {
- config.myStream.addTrack(stream.getAudioTracks()[0]);
- if(Janus.unifiedPlan) {
- // Use Transceivers
- Janus.log((media.replaceAudio ? "Replacing" : "Adding") + " audio track:", stream.getAudioTracks()[0]);
- var audioTransceiver = null;
- const transceivers = config.pc.getTransceivers();
- if(transceivers && transceivers.length > 0) {
- for(const t of transceivers) {
- if((t.sender && t.sender.track && t.sender.track.kind === "audio") ||
- (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) {
- audioTransceiver = t;
- break;
- }
- }
- }
- if(audioTransceiver && audioTransceiver.sender) {
- audioTransceiver.sender.replaceTrack(stream.getAudioTracks()[0]);
- } else {
- config.pc.addTrack(stream.getAudioTracks()[0], stream);
- }
- } else {
- Janus.log((media.replaceAudio ? "Replacing" : "Adding") + " audio track:", stream.getAudioTracks()[0]);
- config.pc.addTrack(stream.getAudioTracks()[0], stream);
- }
- }
- if(((!media.update && isVideoSendEnabled(media)) || (media.update && (media.addVideo || media.replaceVideo))) &&
- stream.getVideoTracks() && stream.getVideoTracks().length) {
- config.myStream.addTrack(stream.getVideoTracks()[0]);
- if(Janus.unifiedPlan) {
- // Use Transceivers
- Janus.log((media.replaceVideo ? "Replacing" : "Adding") + " video track:", stream.getVideoTracks()[0]);
- var videoTransceiver = null;
- const transceivers = config.pc.getTransceivers();
- if(transceivers && transceivers.length > 0) {
- for(const t of transceivers) {
- if((t.sender && t.sender.track && t.sender.track.kind === "video") ||
- (t.receiver && t.receiver.track && t.receiver.track.kind === "video")) {
- videoTransceiver = t;
- break;
- }
- }
- }
- if(videoTransceiver && videoTransceiver.sender) {
- videoTransceiver.sender.replaceTrack(stream.getVideoTracks()[0]);
- } else {
- config.pc.addTrack(stream.getVideoTracks()[0], stream);
- }
- } else {
- Janus.log((media.replaceVideo ? "Replacing" : "Adding") + " video track:", stream.getVideoTracks()[0]);
- config.pc.addTrack(stream.getVideoTracks()[0], stream);
- }
- }
- }
- // If we still need to create a PeerConnection, let's do that
- if(!config.pc) {
- var pc_config = {"iceServers": iceServers, "iceTransportPolicy": iceTransportPolicy, "bundlePolicy": bundlePolicy};
- if(Janus.webRTCAdapter.browserDetails.browser === "chrome") {
- // For Chrome versions before 72, we force a plan-b semantic, and unified-plan otherwise
- pc_config["sdpSemantics"] = (Janus.webRTCAdapter.browserDetails.version < 72) ? "plan-b" : "unified-plan";
- }
- var pc_constraints = {
- "optional": [{"DtlsSrtpKeyAgreement": true}]
- };
- if(ipv6Support) {
- pc_constraints.optional.push({"googIPv6":true});
- }
- // Any custom constraint to add?
- if(callbacks.rtcConstraints && typeof callbacks.rtcConstraints === 'object') {
- Janus.debug("Adding custom PeerConnection constraints:", callbacks.rtcConstraints);
- for(var i in callbacks.rtcConstraints) {
- pc_constraints.optional.push(callbacks.rtcConstraints[i]);
- }
- }
- if(Janus.webRTCAdapter.browserDetails.browser === "edge") {
- // This is Edge, enable BUNDLE explicitly
- pc_config.bundlePolicy = "max-bundle";
- }
- // Check if a sender or receiver transform has been provided
- if(RTCRtpSender && (RTCRtpSender.prototype.createEncodedStreams ||
- (RTCRtpSender.prototype.createEncodedAudioStreams &&
- RTCRtpSender.prototype.createEncodedVideoStreams)) &&
- (callbacks.senderTransforms || callbacks.receiverTransforms)) {
- config.senderTransforms = callbacks.senderTransforms;
- config.receiverTransforms = callbacks.receiverTransforms;
- pc_config["forceEncodedAudioInsertableStreams"] = true;
- pc_config["forceEncodedVideoInsertableStreams"] = true;
- pc_config["encodedInsertableStreams"] = true;
- }
- Janus.log("Creating PeerConnection");
- Janus.debug(pc_constraints);
- config.pc = new RTCPeerConnection(pc_config, pc_constraints);
- Janus.debug(config.pc);
- if(config.pc.getStats) { // FIXME
- config.volume = {};
- config.bitrate.value = "0 kbits/sec";
- }
- Janus.log("Preparing local SDP and gathering candidates (trickle=" + config.trickle + ")");
- config.pc.oniceconnectionstatechange = function() {
- if(config.pc)
- pluginHandle.iceState(config.pc.iceConnectionState);
- };
- config.pc.onicecandidate = function(event) {
- if (!event.candidate ||
- (Janus.webRTCAdapter.browserDetails.browser === 'edge' && event.candidate.candidate.indexOf('endOfCandidates') > 0)) {
- Janus.log("End of candidates.");
- config.iceDone = true;
- if(config.trickle === true) {
- // Notify end of candidates
- sendTrickleCandidate(handleId, {"completed": true});
- } else {
- // No trickle, time to send the complete SDP (including all candidates)
- sendSDP(handleId, callbacks);
- }
- } else {
- // JSON.stringify doesn't work on some WebRTC objects anymore
- // See https://code.google.com/p/chromium/issues/detail?id=467366
- var candidate = {
- "candidate": event.candidate.candidate,
- "sdpMid": event.candidate.sdpMid,
- "sdpMLineIndex": event.candidate.sdpMLineIndex
- };
- if(config.trickle === true) {
- // Send candidate
- sendTrickleCandidate(handleId, candidate);
- }
- }
- };
- config.pc.ontrack = function(event) {
- Janus.log("Handling Remote Track");
- Janus.debug(event);
- if(!event.streams)
- return;
- config.remoteStream = event.streams[0];
- pluginHandle.onremotestream(config.remoteStream);
- if(event.track.onended)
- return;
- if(config.receiverTransforms) {
- var receiverStreams = null;
- if(RTCRtpSender.prototype.createEncodedStreams) {
- receiverStreams = event.receiver.createEncodedStreams();
- } else if(RTCRtpSender.prototype.createAudioEncodedStreams || RTCRtpSender.prototype.createEncodedVideoStreams) {
- if(event.track.kind === "audio" && config.receiverTransforms["audio"]) {
- receiverStreams = event.receiver.createEncodedAudioStreams();
- } else if(event.track.kind === "video" && config.receiverTransforms["video"]) {
- receiverStreams = event.receiver.createEncodedVideoStreams();
- }
- }
- if(receiverStreams) {
- console.log(receiverStreams);
- if(receiverStreams.readableStream && receiverStreams.writableStream) {
- receiverStreams.readableStream
- .pipeThrough(config.receiverTransforms[event.track.kind])
- .pipeTo(receiverStreams.writableStream);
- } else if(receiverStreams.readable && receiverStreams.writable) {
- receiverStreams.readable
- .pipeThrough(config.receiverTransforms[event.track.kind])
- .pipeTo(receiverStreams.writable);
- }
- }
- }
- var trackMutedTimeoutId = null;
- Janus.log("Adding onended callback to track:", event.track);
- event.track.onended = function(ev) {
- Janus.log("Remote track removed:", ev);
- if(config.remoteStream) {
- clearTimeout(trackMutedTimeoutId);
- config.remoteStream.removeTrack(ev.target);
- pluginHandle.onremotestream(config.remoteStream);
- }
- };
- event.track.onmute = function(ev) {
- Janus.log("Remote track muted:", ev);
- if(config.remoteStream && trackMutedTimeoutId == null) {
- trackMutedTimeoutId = setTimeout(function() {
- Janus.log("Removing remote track");
- if (config.remoteStream) {
- config.remoteStream.removeTrack(ev.target);
- pluginHandle.onremotestream(config.remoteStream);
- }
- trackMutedTimeoutId = null;
- // Chrome seems to raise mute events only at multiples of 834ms;
- // we set the timeout to three times this value (rounded to 840ms)
- }, 3 * 840);
- }
- };
- event.track.onunmute = function(ev) {
- Janus.log("Remote track flowing again:", ev);
- if(trackMutedTimeoutId != null) {
- clearTimeout(trackMutedTimeoutId);
- trackMutedTimeoutId = null;
- } else {
- try {
- config.remoteStream.addTrack(ev.target);
- pluginHandle.onremotestream(config.remoteStream);
- } catch(e) {
- Janus.error(e);
- }
- }
- };
- };
- }
- if(addTracks && stream) {
- Janus.log('Adding local stream');
- var simulcast2 = (callbacks.simulcast2 === true);
- stream.getTracks().forEach(function(track) {
- Janus.log('Adding local track:', track);
- var sender = null;
- if(!simulcast2 || track.kind === 'audio') {
- sender = config.pc.addTrack(track, stream);
- } else {
- Janus.log('Enabling rid-based simulcasting:', track);
- var maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates);
- var tr = config.pc.addTransceiver(track, {
- direction: "sendrecv",
- streams: [stream],
- sendEncodings: callbacks.sendEncodings || [
- { rid: "h", active: true, maxBitrate: maxBitrates.high },
- { rid: "m", active: true, maxBitrate: maxBitrates.medium, scaleResolutionDownBy: 2 },
- { rid: "l", active: true, maxBitrate: maxBitrates.low, scaleResolutionDownBy: 4 }
- ]
- });
- if(tr)
- sender = tr.sender;
- }
- // Check if insertable streams are involved
- if(sender && config.senderTransforms) {
- var senderStreams = null;
- if(RTCRtpSender.prototype.createEncodedStreams) {
- senderStreams = sender.createEncodedStreams();
- } else if(RTCRtpSender.prototype.createAudioEncodedStreams || RTCRtpSender.prototype.createEncodedVideoStreams) {
- if(sender.track.kind === "audio" && config.senderTransforms["audio"]) {
- senderStreams = sender.createEncodedAudioStreams();
- } else if(sender.track.kind === "video" && config.senderTransforms["video"]) {
- senderStreams = sender.createEncodedVideoStreams();
- }
- }
- if(senderStreams) {
- console.log(senderStreams);
- if(senderStreams.readableStream && senderStreams.writableStream) {
- senderStreams.readableStream
- .pipeThrough(config.senderTransforms[sender.track.kind])
- .pipeTo(senderStreams.writableStream);
- } else if(senderStreams.readable && senderStreams.writable) {
- senderStreams.readable
- .pipeThrough(config.senderTransforms[sender.track.kind])
- .pipeTo(senderStreams.writable);
- }
- }
- }
- });
- }
- // Any data channel to create?
- if(isDataEnabled(media) && !config.dataChannel[Janus.dataChanDefaultLabel]) {
- Janus.log("Creating default data channel");
- createDataChannel(handleId, Janus.dataChanDefaultLabel, null, false);
- config.pc.ondatachannel = function(event) {
- Janus.log("Data channel created by Janus:", event);
- createDataChannel(handleId, event.channel.label, event.channel.protocol, event.channel);
- };
- }
- // If there's a new local stream, let's notify the application
- if(config.myStream) {
- pluginHandle.onlocalstream(config.myStream);
- }
- // Create offer/answer now
- if(!jsep) {
- createOffer(handleId, media, callbacks);
- } else {
- config.pc.setRemoteDescription(jsep)
- .then(function() {
- Janus.log("Remote description accepted!");
- config.remoteSdp = jsep.sdp;
- // Any trickle candidate we cached?
- if(config.candidates && config.candidates.length > 0) {
- for(var i = 0; i< config.candidates.length; i++) {
- var candidate = config.candidates[i];
- Janus.debug("Adding remote candidate:", candidate);
- if(!candidate || candidate.completed === true) {
- // end-of-candidates
- config.pc.addIceCandidate(Janus.endOfCandidates);
- } else {
- // New candidate
- config.pc.addIceCandidate(candidate);
- }
- }
- config.candidates = [];
- }
- // Create the answer now
- createAnswer(handleId, media, callbacks);
- }, callbacks.error);
- }
- }
-
- function prepareWebrtc(handleId, offer, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError;
- var jsep = callbacks.jsep;
- if(offer && jsep) {
- Janus.error("Provided a JSEP to a createOffer");
- callbacks.error("Provided a JSEP to a createOffer");
- return;
- } else if(!offer && (!jsep || !jsep.type || !jsep.sdp)) {
- Janus.error("A valid JSEP is required for createAnswer");
- callbacks.error("A valid JSEP is required for createAnswer");
- return;
- }
- /* Check that callbacks.media is a (not null) Object */
- callbacks.media = (typeof callbacks.media === 'object' && callbacks.media) ? callbacks.media : { audio: true, video: true };
- var media = callbacks.media;
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- config.trickle = isTrickleEnabled(callbacks.trickle);
- // Are we updating a session?
- if(!config.pc) {
- // Nope, new PeerConnection
- media.update = false;
- media.keepAudio = false;
- media.keepVideo = false;
- } else {
- Janus.log("Updating existing media session");
- media.update = true;
- // Check if there's anything to add/remove/replace, or if we
- // can go directly to preparing the new SDP offer or answer
- if(callbacks.stream) {
- // External stream: is this the same as the one we were using before?
- if(callbacks.stream !== config.myStream) {
- Janus.log("Renegotiation involves a new external stream");
- }
- } else {
- // Check if there are changes on audio
- if(media.addAudio) {
- media.keepAudio = false;
- media.replaceAudio = false;
- media.removeAudio = false;
- media.audioSend = true;
- if(config.myStream && config.myStream.getAudioTracks() && config.myStream.getAudioTracks().length) {
- Janus.error("Can't add audio stream, there already is one");
- callbacks.error("Can't add audio stream, there already is one");
- return;
- }
- } else if(media.removeAudio) {
- media.keepAudio = false;
- media.replaceAudio = false;
- media.addAudio = false;
- media.audioSend = false;
- } else if(media.replaceAudio) {
- media.keepAudio = false;
- media.addAudio = false;
- media.removeAudio = false;
- media.audioSend = true;
- }
- if(!config.myStream) {
- // No media stream: if we were asked to replace, it's actually an "add"
- if(media.replaceAudio) {
- media.keepAudio = false;
- media.replaceAudio = false;
- media.addAudio = true;
- media.audioSend = true;
- }
- if(isAudioSendEnabled(media)) {
- media.keepAudio = false;
- media.addAudio = true;
- }
- } else {
- if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) {
- // No audio track: if we were asked to replace, it's actually an "add"
- if(media.replaceAudio) {
- media.keepAudio = false;
- media.replaceAudio = false;
- media.addAudio = true;
- media.audioSend = true;
- }
- if(isAudioSendEnabled(media)) {
- media.keepAudio = false;
- media.addAudio = true;
- }
- } else {
- // We have an audio track: should we keep it as it is?
- if(isAudioSendEnabled(media) &&
- !media.removeAudio && !media.replaceAudio) {
- media.keepAudio = true;
- }
- }
- }
- // Check if there are changes on video
- if(media.addVideo) {
- media.keepVideo = false;
- media.replaceVideo = false;
- media.removeVideo = false;
- media.videoSend = true;
- if(config.myStream && config.myStream.getVideoTracks() && config.myStream.getVideoTracks().length) {
- Janus.error("Can't add video stream, there already is one");
- callbacks.error("Can't add video stream, there already is one");
- return;
- }
- } else if(media.removeVideo) {
- media.keepVideo = false;
- media.replaceVideo = false;
- media.addVideo = false;
- media.videoSend = false;
- } else if(media.replaceVideo) {
- media.keepVideo = false;
- media.addVideo = false;
- media.removeVideo = false;
- media.videoSend = true;
- }
- if(!config.myStream) {
- // No media stream: if we were asked to replace, it's actually an "add"
- if(media.replaceVideo) {
- media.keepVideo = false;
- media.replaceVideo = false;
- media.addVideo = true;
- media.videoSend = true;
- }
- if(isVideoSendEnabled(media)) {
- media.keepVideo = false;
- media.addVideo = true;
- }
- } else {
- if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) {
- // No video track: if we were asked to replace, it's actually an "add"
- if(media.replaceVideo) {
- media.keepVideo = false;
- media.replaceVideo = false;
- media.addVideo = true;
- media.videoSend = true;
- }
- if(isVideoSendEnabled(media)) {
- media.keepVideo = false;
- media.addVideo = true;
- }
- } else {
- // We have a video track: should we keep it as it is?
- if(isVideoSendEnabled(media) && !media.removeVideo && !media.replaceVideo) {
- media.keepVideo = true;
- }
- }
- }
- // Data channels can only be added
- if(media.addData) {
- media.data = true;
- }
- }
- // If we're updating and keeping all tracks, let's skip the getUserMedia part
- if((isAudioSendEnabled(media) && media.keepAudio) &&
- (isVideoSendEnabled(media) && media.keepVideo)) {
- pluginHandle.consentDialog(false);
- streamsDone(handleId, jsep, media, callbacks, config.myStream);
- return;
- }
- }
- // If we're updating, check if we need to remove/replace one of the tracks
- if(media.update && (!config.streamExternal || (config.streamExternal && (media.replaceAudio || media.replaceVideo)))) {
- if(media.removeAudio || media.replaceAudio) {
- if(config.myStream && config.myStream.getAudioTracks() && config.myStream.getAudioTracks().length) {
- var at = config.myStream.getAudioTracks()[0];
- Janus.log("Removing audio track:", at);
- config.myStream.removeTrack(at);
- try {
- at.stop();
- } catch(e) {}
- }
- if(config.pc.getSenders() && config.pc.getSenders().length) {
- var ra = true;
- if(media.replaceAudio && Janus.unifiedPlan) {
- // We can use replaceTrack
- ra = false;
- }
- if(ra) {
- for(var asnd of config.pc.getSenders()) {
- if(asnd && asnd.track && asnd.track.kind === "audio") {
- Janus.log("Removing audio sender:", asnd);
- config.pc.removeTrack(asnd);
- }
- }
- }
- }
- }
- if(media.removeVideo || media.replaceVideo) {
- if(config.myStream && config.myStream.getVideoTracks() && config.myStream.getVideoTracks().length) {
- var vt = config.myStream.getVideoTracks()[0];
- Janus.log("Removing video track:", vt);
- config.myStream.removeTrack(vt);
- try {
- vt.stop();
- } catch(e) {}
- }
- if(config.pc.getSenders() && config.pc.getSenders().length) {
- var rv = true;
- if(media.replaceVideo && Janus.unifiedPlan) {
- // We can use replaceTrack
- rv = false;
- }
- if(rv) {
- for(var vsnd of config.pc.getSenders()) {
- if(vsnd && vsnd.track && vsnd.track.kind === "video") {
- Janus.log("Removing video sender:", vsnd);
- config.pc.removeTrack(vsnd);
- }
- }
- }
- }
- }
- }
- // Was a MediaStream object passed, or do we need to take care of that?
- if(callbacks.stream) {
- var stream = callbacks.stream;
- Janus.log("MediaStream provided by the application");
- Janus.debug(stream);
- // If this is an update, let's check if we need to release the previous stream
- if(media.update && config.myStream && config.myStream !== callbacks.stream && !config.streamExternal && !media.replaceAudio && !media.replaceVideo) {
- // We're replacing a stream we captured ourselves with an external one
- Janus.stopAllTracks(config.myStream);
- config.myStream = null;
- }
- // Skip the getUserMedia part
- config.streamExternal = true;
- pluginHandle.consentDialog(false);
- streamsDone(handleId, jsep, media, callbacks, stream);
- return;
- }
- if(isAudioSendEnabled(media) || isVideoSendEnabled(media)) {
- if(!Janus.isGetUserMediaAvailable()) {
- callbacks.error("getUserMedia not available");
- return;
- }
- var constraints = { mandatory: {}, optional: []};
- pluginHandle.consentDialog(true);
- var audioSupport = isAudioSendEnabled(media);
- if(audioSupport && media && typeof media.audio === 'object')
- audioSupport = media.audio;
- var videoSupport = isVideoSendEnabled(media);
- if(videoSupport && media) {
- var simulcast = (callbacks.simulcast === true);
- var simulcast2 = (callbacks.simulcast2 === true);
- if((simulcast || simulcast2) && !jsep && !media.video)
- media.video = "hires";
- if(media.video && media.video != 'screen' && media.video != 'window') {
- if(typeof media.video === 'object') {
- videoSupport = media.video;
- } else {
- var width = 0;
- var height = 0;
- if(media.video === 'lowres') {
- // Small resolution, 4:3
- height = 240;
- width = 320;
- } else if(media.video === 'lowres-16:9') {
- // Small resolution, 16:9
- height = 180;
- width = 320;
- } else if(media.video === 'hires' || media.video === 'hires-16:9' || media.video === 'hdres') {
- // High(HD) resolution is only 16:9
- height = 720;
- width = 1280;
- } else if(media.video === 'fhdres') {
- // Full HD resolution is only 16:9
- height = 1080;
- width = 1920;
- } else if(media.video === '4kres') {
- // 4K resolution is only 16:9
- height = 2160;
- width = 3840;
- } else if(media.video === 'stdres') {
- // Normal resolution, 4:3
- height = 480;
- width = 640;
- } else if(media.video === 'stdres-16:9') {
- // Normal resolution, 16:9
- height = 360;
- width = 640;
- } else {
- Janus.log("Default video setting is stdres 4:3");
- height = 480;
- width = 640;
- }
- Janus.log("Adding media constraint:", media.video);
- videoSupport = {
- 'height': {'ideal': height},
- 'width': {'ideal': width}
- };
- Janus.log("Adding video constraint:", videoSupport);
- }
- } else if(media.video === 'screen' || media.video === 'window') {
- if(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
- // The new experimental getDisplayMedia API is available, let's use that
- // https://groups.google.com/forum/#!topic/discuss-webrtc/Uf0SrR4uxzk
- // https://webrtchacks.com/chrome-screensharing-getdisplaymedia/
- constraints.video = {};
- if(media.screenshareFrameRate) {
- constraints.video.frameRate = media.screenshareFrameRate;
- }
- if(media.screenshareHeight) {
- constraints.video.height = media.screenshareHeight;
- }
- if(media.screenshareWidth) {
- constraints.video.width = media.screenshareWidth;
- }
- constraints.audio = media.captureDesktopAudio;
- navigator.mediaDevices.getDisplayMedia(constraints)
- .then(function(stream) {
- pluginHandle.consentDialog(false);
- if(isAudioSendEnabled(media) && !media.keepAudio) {
- navigator.mediaDevices.getUserMedia({ audio: true, video: false })
- .then(function (audioStream) {
- stream.addTrack(audioStream.getAudioTracks()[0]);
- streamsDone(handleId, jsep, media, callbacks, stream);
- })
- } else {
- streamsDone(handleId, jsep, media, callbacks, stream);
- }
- }, function (error) {
- pluginHandle.consentDialog(false);
- callbacks.error(error);
- });
- return;
- }
- // We're going to try and use the extension for Chrome 34+, the old approach
- // for older versions of Chrome, or the experimental support in Firefox 33+
- const callbackUserMedia = function(error, stream) {
- pluginHandle.consentDialog(false);
- if(error) {
- callbacks.error(error);
- } else {
- streamsDone(handleId, jsep, media, callbacks, stream);
- }
- }
- const getScreenMedia = function(constraints, gsmCallback, useAudio) {
- Janus.log("Adding media constraint (screen capture)");
- Janus.debug(constraints);
- navigator.mediaDevices.getUserMedia(constraints)
- .then(function(stream) {
- if(useAudio) {
- navigator.mediaDevices.getUserMedia({ audio: true, video: false })
- .then(function (audioStream) {
- stream.addTrack(audioStream.getAudioTracks()[0]);
- gsmCallback(null, stream);
- })
- } else {
- gsmCallback(null, stream);
- }
- })
- .catch(function(error) { pluginHandle.consentDialog(false); gsmCallback(error); });
- }
- if(Janus.webRTCAdapter.browserDetails.browser === 'chrome') {
- var chromever = Janus.webRTCAdapter.browserDetails.version;
- var maxver = 33;
- if(window.navigator.userAgent.match('Linux'))
- maxver = 35; // "known" crash in chrome 34 and 35 on linux
- if(chromever >= 26 && chromever <= maxver) {
- // Chrome 26->33 requires some awkward chrome://flags manipulation
- constraints = {
- video: {
- mandatory: {
- googLeakyBucket: true,
- maxWidth: window.screen.width,
- maxHeight: window.screen.height,
- minFrameRate: media.screenshareFrameRate,
- maxFrameRate: media.screenshareFrameRate,
- chromeMediaSource: 'screen'
- }
- },
- audio: isAudioSendEnabled(media) && !media.keepAudio
- };
- getScreenMedia(constraints, callbackUserMedia);
- } else {
- // Chrome 34+ requires an extension
- Janus.extension.getScreen(function (error, sourceId) {
- if (error) {
- pluginHandle.consentDialog(false);
- return callbacks.error(error);
- }
- constraints = {
- audio: false,
- video: {
- mandatory: {
- chromeMediaSource: 'desktop',
- maxWidth: window.screen.width,
- maxHeight: window.screen.height,
- minFrameRate: media.screenshareFrameRate,
- maxFrameRate: media.screenshareFrameRate,
- },
- optional: [
- {googLeakyBucket: true},
- {googTemporalLayeredScreencast: true}
- ]
- }
- };
- constraints.video.mandatory.chromeMediaSourceId = sourceId;
- getScreenMedia(constraints, callbackUserMedia,
- isAudioSendEnabled(media) && !media.keepAudio);
- });
- }
- } else if(Janus.webRTCAdapter.browserDetails.browser === 'firefox') {
- if(Janus.webRTCAdapter.browserDetails.version >= 33) {
- // Firefox 33+ has experimental support for screen sharing
- constraints = {
- video: {
- mozMediaSource: media.video,
- mediaSource: media.video
- },
- audio: isAudioSendEnabled(media) && !media.keepAudio
- };
- getScreenMedia(constraints, function (err, stream) {
- callbackUserMedia(err, stream);
- // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1045810
- if (!err) {
- var lastTime = stream.currentTime;
- var polly = window.setInterval(function () {
- if(!stream)
- window.clearInterval(polly);
- if(stream.currentTime == lastTime) {
- window.clearInterval(polly);
- if(stream.onended) {
- stream.onended();
- }
- }
- lastTime = stream.currentTime;
- }, 500);
- }
- });
- } else {
- var error = new Error('NavigatorUserMediaError');
- error.name = 'Your version of Firefox does not support screen sharing, please install Firefox 33 (or more recent versions)';
- pluginHandle.consentDialog(false);
- callbacks.error(error);
- return;
- }
- }
- return;
- }
- }
- // If we got here, we're not screensharing
- if(!media || media.video !== 'screen') {
- // Check whether all media sources are actually available or not
- navigator.mediaDevices.enumerateDevices().then(function(devices) {
- var audioExist = devices.some(function(device) {
- return device.kind === 'audioinput';
- }),
- videoExist = isScreenSendEnabled(media) || devices.some(function(device) {
- return device.kind === 'videoinput';
- });
-
- // Check whether a missing device is really a problem
- var audioSend = isAudioSendEnabled(media);
- var videoSend = isVideoSendEnabled(media);
- var needAudioDevice = isAudioSendRequired(media);
- var needVideoDevice = isVideoSendRequired(media);
- if(audioSend || videoSend || needAudioDevice || needVideoDevice) {
- // We need to send either audio or video
- var haveAudioDevice = audioSend ? audioExist : false;
- var haveVideoDevice = videoSend ? videoExist : false;
- if(!haveAudioDevice && !haveVideoDevice) {
- // FIXME Should we really give up, or just assume recvonly for both?
- pluginHandle.consentDialog(false);
- callbacks.error('No capture device found');
- return false;
- } else if(!haveAudioDevice && needAudioDevice) {
- pluginHandle.consentDialog(false);
- callbacks.error('Audio capture is required, but no capture device found');
- return false;
- } else if(!haveVideoDevice && needVideoDevice) {
- pluginHandle.consentDialog(false);
- callbacks.error('Video capture is required, but no capture device found');
- return false;
- }
- }
-
- var gumConstraints = {
- audio: (audioExist && !media.keepAudio) ? audioSupport : false,
- video: (videoExist && !media.keepVideo) ? videoSupport : false
- };
- Janus.debug("getUserMedia constraints", gumConstraints);
- if (!gumConstraints.audio && !gumConstraints.video) {
- pluginHandle.consentDialog(false);
- streamsDone(handleId, jsep, media, callbacks, stream);
- } else {
- navigator.mediaDevices.getUserMedia(gumConstraints)
- .then(function(stream) {
- pluginHandle.consentDialog(false);
- streamsDone(handleId, jsep, media, callbacks, stream);
- }).catch(function(error) {
- pluginHandle.consentDialog(false);
- callbacks.error({code: error.code, name: error.name, message: error.message});
- });
- }
- })
- .catch(function(error) {
- pluginHandle.consentDialog(false);
- callbacks.error(error);
- });
- }
- } else {
- // No need to do a getUserMedia, create offer/answer right away
- streamsDone(handleId, jsep, media, callbacks);
- }
- }
-
- function prepareWebrtcPeer(handleId, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError;
- callbacks.customizeSdp = (typeof callbacks.customizeSdp == "function") ? callbacks.customizeSdp : Janus.noop;
- var jsep = callbacks.jsep;
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- if(jsep) {
- if(!config.pc) {
- Janus.warn("Wait, no PeerConnection?? if this is an answer, use createAnswer and not handleRemoteJsep");
- callbacks.error("No PeerConnection: if this is an answer, use createAnswer and not handleRemoteJsep");
- return;
- }
- callbacks.customizeSdp(jsep);
- config.pc.setRemoteDescription(jsep)
- .then(function() {
- Janus.log("Remote description accepted!");
- config.remoteSdp = jsep.sdp;
- // Any trickle candidate we cached?
- if(config.candidates && config.candidates.length > 0) {
- for(var i = 0; i< config.candidates.length; i++) {
- var candidate = config.candidates[i];
- Janus.debug("Adding remote candidate:", candidate);
- if(!candidate || candidate.completed === true) {
- // end-of-candidates
- config.pc.addIceCandidate(Janus.endOfCandidates);
- } else {
- // New candidate
- config.pc.addIceCandidate(candidate);
- }
- }
- config.candidates = [];
- }
- // Done
- callbacks.success();
- }, callbacks.error);
- } else {
- callbacks.error("Invalid JSEP");
- }
- }
-
- function createOffer(handleId, media, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- callbacks.customizeSdp = (typeof callbacks.customizeSdp == "function") ? callbacks.customizeSdp : Janus.noop;
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- var simulcast = (callbacks.simulcast === true);
- if(!simulcast) {
- Janus.log("Creating offer (iceDone=" + config.iceDone + ")");
- } else {
- Janus.log("Creating offer (iceDone=" + config.iceDone + ", simulcast=" + simulcast + ")");
- }
- // https://code.google.com/p/webrtc/issues/detail?id=3508
- var mediaConstraints = {};
- if(Janus.unifiedPlan) {
- // We can use Transceivers
- var audioTransceiver = null, videoTransceiver = null;
- var transceivers = config.pc.getTransceivers();
- if(transceivers && transceivers.length > 0) {
- for(var t of transceivers) {
- if((t.sender && t.sender.track && t.sender.track.kind === "audio") ||
- (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) {
- if(!audioTransceiver) {
- audioTransceiver = t;
- }
- continue;
- }
- if((t.sender && t.sender.track && t.sender.track.kind === "video") ||
- (t.receiver && t.receiver.track && t.receiver.track.kind === "video")) {
- if(!videoTransceiver) {
- videoTransceiver = t;
- }
- continue;
- }
- }
- }
- // Handle audio (and related changes, if any)
- var audioSend = isAudioSendEnabled(media);
- var audioRecv = isAudioRecvEnabled(media);
- if(!audioSend && !audioRecv) {
- // Audio disabled: have we removed it?
- if(media.removeAudio && audioTransceiver) {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("inactive");
- } else {
- audioTransceiver.direction = "inactive";
- }
- Janus.log("Setting audio transceiver to inactive:", audioTransceiver);
- }
- } else {
- // Take care of audio m-line
- if(audioSend && audioRecv) {
- if(audioTransceiver) {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("sendrecv");
- } else {
- audioTransceiver.direction = "sendrecv";
- }
- Janus.log("Setting audio transceiver to sendrecv:", audioTransceiver);
- }
- } else if(audioSend && !audioRecv) {
- if(audioTransceiver) {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("sendonly");
- } else {
- audioTransceiver.direction = "sendonly";
- }
- Janus.log("Setting audio transceiver to sendonly:", audioTransceiver);
- }
- } else if(!audioSend && audioRecv) {
- if(audioTransceiver) {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("recvonly");
- } else {
- audioTransceiver.direction = "recvonly";
- }
- Janus.log("Setting audio transceiver to recvonly:", audioTransceiver);
- } else {
- // In theory, this is the only case where we might not have a transceiver yet
- audioTransceiver = config.pc.addTransceiver("audio", { direction: "recvonly" });
- Janus.log("Adding recvonly audio transceiver:", audioTransceiver);
- }
- }
- }
- // Handle video (and related changes, if any)
- var videoSend = isVideoSendEnabled(media);
- var videoRecv = isVideoRecvEnabled(media);
- if(!videoSend && !videoRecv) {
- // Video disabled: have we removed it?
- if(media.removeVideo && videoTransceiver) {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("inactive");
- } else {
- videoTransceiver.direction = "inactive";
- }
- Janus.log("Setting video transceiver to inactive:", videoTransceiver);
- }
- } else {
- // Take care of video m-line
- if(videoSend && videoRecv) {
- if(videoTransceiver) {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("sendrecv");
- } else {
- videoTransceiver.direction = "sendrecv";
- }
- Janus.log("Setting video transceiver to sendrecv:", videoTransceiver);
- }
- } else if(videoSend && !videoRecv) {
- if(videoTransceiver) {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("sendonly");
- } else {
- videoTransceiver.direction = "sendonly";
- }
- Janus.log("Setting video transceiver to sendonly:", videoTransceiver);
- }
- } else if(!videoSend && videoRecv) {
- if(videoTransceiver) {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("recvonly");
- } else {
- videoTransceiver.direction = "recvonly";
- }
- Janus.log("Setting video transceiver to recvonly:", videoTransceiver);
- } else {
- // In theory, this is the only case where we might not have a transceiver yet
- videoTransceiver = config.pc.addTransceiver("video", { direction: "recvonly" });
- Janus.log("Adding recvonly video transceiver:", videoTransceiver);
- }
- }
- }
- } else {
- mediaConstraints["offerToReceiveAudio"] = isAudioRecvEnabled(media);
- mediaConstraints["offerToReceiveVideo"] = isVideoRecvEnabled(media);
- }
- var iceRestart = (callbacks.iceRestart === true);
- if(iceRestart) {
- mediaConstraints["iceRestart"] = true;
- }
- Janus.debug(mediaConstraints);
- // Check if this is Firefox and we've been asked to do simulcasting
- var sendVideo = isVideoSendEnabled(media);
- if(sendVideo && simulcast && Janus.webRTCAdapter.browserDetails.browser === "firefox") {
- // FIXME Based on https://gist.github.com/voluntas/088bc3cc62094730647b
- Janus.log("Enabling Simulcasting for Firefox (RID)");
- var sender = config.pc.getSenders().find(function(s) {return s.track && s.track.kind === "video"});
- if(sender) {
- var parameters = sender.getParameters();
- if(!parameters) {
- parameters = {};
- }
- var maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates);
- parameters.encodings = callbacks.sendEncodings || [
- { rid: "h", active: true, maxBitrate: maxBitrates.high },
- { rid: "m", active: true, maxBitrate: maxBitrates.medium, scaleResolutionDownBy: 2 },
- { rid: "l", active: true, maxBitrate: maxBitrates.low, scaleResolutionDownBy: 4 }
- ];
- sender.setParameters(parameters);
- }
- }
- config.pc.createOffer(mediaConstraints)
- .then(function(offer) {
- Janus.debug(offer);
- // JSON.stringify doesn't work on some WebRTC objects anymore
- // See https://code.google.com/p/chromium/issues/detail?id=467366
- var jsep = {
- "type": offer.type,
- "sdp": offer.sdp
- };
- callbacks.customizeSdp(jsep);
- offer.sdp = jsep.sdp;
- Janus.log("Setting local description");
- if(sendVideo && simulcast) {
- // This SDP munging only works with Chrome (Safari STP may support it too)
- if(Janus.webRTCAdapter.browserDetails.browser === "chrome" ||
- Janus.webRTCAdapter.browserDetails.browser === "safari") {
- Janus.log("Enabling Simulcasting for Chrome (SDP munging)");
- offer.sdp = mungeSdpForSimulcasting(offer.sdp);
- } else if(Janus.webRTCAdapter.browserDetails.browser !== "firefox") {
- Janus.warn("simulcast=true, but this is not Chrome nor Firefox, ignoring");
- }
- }
- config.mySdp = {
- type: "offer",
- sdp: offer.sdp
- };
- config.pc.setLocalDescription(offer)
- .catch(callbacks.error);
- config.mediaConstraints = mediaConstraints;
- if(!config.iceDone && !config.trickle) {
- // Don't do anything until we have all candidates
- Janus.log("Waiting for all candidates...");
- return;
- }
- // If transforms are present, notify Janus that the media is end-to-end encrypted
- if(config.senderTransforms || config.receiverTransforms) {
- offer["e2ee"] = true;
- }
- callbacks.success(offer);
- }, callbacks.error);
- }
-
- function createAnswer(handleId, media, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- callbacks.customizeSdp = (typeof callbacks.customizeSdp == "function") ? callbacks.customizeSdp : Janus.noop;
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- callbacks.error("Invalid handle");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- var simulcast = (callbacks.simulcast === true);
- if(!simulcast) {
- Janus.log("Creating answer (iceDone=" + config.iceDone + ")");
- } else {
- Janus.log("Creating answer (iceDone=" + config.iceDone + ", simulcast=" + simulcast + ")");
- }
- var mediaConstraints = null;
- if(Janus.unifiedPlan) {
- // We can use Transceivers
- mediaConstraints = {};
- var audioTransceiver = null, videoTransceiver = null;
- var transceivers = config.pc.getTransceivers();
- if(transceivers && transceivers.length > 0) {
- for(var t of transceivers) {
- if((t.sender && t.sender.track && t.sender.track.kind === "audio") ||
- (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) {
- if(!audioTransceiver)
- audioTransceiver = t;
- continue;
- }
- if((t.sender && t.sender.track && t.sender.track.kind === "video") ||
- (t.receiver && t.receiver.track && t.receiver.track.kind === "video")) {
- if(!videoTransceiver)
- videoTransceiver = t;
- continue;
- }
- }
- }
- // Handle audio (and related changes, if any)
- var audioSend = isAudioSendEnabled(media);
- var audioRecv = isAudioRecvEnabled(media);
- if(!audioSend && !audioRecv) {
- // Audio disabled: have we removed it?
- if(media.removeAudio && audioTransceiver) {
- try {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("inactive");
- } else {
- audioTransceiver.direction = "inactive";
- }
- Janus.log("Setting audio transceiver to inactive:", audioTransceiver);
- } catch(e) {
- Janus.error(e);
- }
- }
- } else {
- // Take care of audio m-line
- if(audioSend && audioRecv) {
- if(audioTransceiver) {
- try {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("sendrecv");
- } else {
- audioTransceiver.direction = "sendrecv";
- }
- Janus.log("Setting audio transceiver to sendrecv:", audioTransceiver);
- } catch(e) {
- Janus.error(e);
- }
- }
- } else if(audioSend && !audioRecv) {
- try {
- if(audioTransceiver) {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("sendonly");
- } else {
- audioTransceiver.direction = "sendonly";
- }
- Janus.log("Setting audio transceiver to sendonly:", audioTransceiver);
- }
- } catch(e) {
- Janus.error(e);
- }
- } else if(!audioSend && audioRecv) {
- if(audioTransceiver) {
- try {
- if (audioTransceiver.setDirection) {
- audioTransceiver.setDirection("recvonly");
- } else {
- audioTransceiver.direction = "recvonly";
- }
- Janus.log("Setting audio transceiver to recvonly:", audioTransceiver);
- } catch(e) {
- Janus.error(e);
- }
- } else {
- // In theory, this is the only case where we might not have a transceiver yet
- audioTransceiver = config.pc.addTransceiver("audio", { direction: "recvonly" });
- Janus.log("Adding recvonly audio transceiver:", audioTransceiver);
- }
- }
- }
- // Handle video (and related changes, if any)
- var videoSend = isVideoSendEnabled(media);
- var videoRecv = isVideoRecvEnabled(media);
- if(!videoSend && !videoRecv) {
- // Video disabled: have we removed it?
- if(media.removeVideo && videoTransceiver) {
- try {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("inactive");
- } else {
- videoTransceiver.direction = "inactive";
- }
- Janus.log("Setting video transceiver to inactive:", videoTransceiver);
- } catch(e) {
- Janus.error(e);
- }
- }
- } else {
- // Take care of video m-line
- if(videoSend && videoRecv) {
- if(videoTransceiver) {
- try {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("sendrecv");
- } else {
- videoTransceiver.direction = "sendrecv";
- }
- Janus.log("Setting video transceiver to sendrecv:", videoTransceiver);
- } catch(e) {
- Janus.error(e);
- }
- }
- } else if(videoSend && !videoRecv) {
- if(videoTransceiver) {
- try {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("sendonly");
- } else {
- videoTransceiver.direction = "sendonly";
- }
- Janus.log("Setting video transceiver to sendonly:", videoTransceiver);
- } catch(e) {
- Janus.error(e);
- }
- }
- } else if(!videoSend && videoRecv) {
- if(videoTransceiver) {
- try {
- if (videoTransceiver.setDirection) {
- videoTransceiver.setDirection("recvonly");
- } else {
- videoTransceiver.direction = "recvonly";
- }
- Janus.log("Setting video transceiver to recvonly:", videoTransceiver);
- } catch(e) {
- Janus.error(e);
- }
- } else {
- // In theory, this is the only case where we might not have a transceiver yet
- videoTransceiver = config.pc.addTransceiver("video", { direction: "recvonly" });
- Janus.log("Adding recvonly video transceiver:", videoTransceiver);
- }
- }
- }
- } else {
- if(Janus.webRTCAdapter.browserDetails.browser === "firefox" || Janus.webRTCAdapter.browserDetails.browser === "edge") {
- mediaConstraints = {
- offerToReceiveAudio: isAudioRecvEnabled(media),
- offerToReceiveVideo: isVideoRecvEnabled(media)
- };
- } else {
- mediaConstraints = {
- mandatory: {
- OfferToReceiveAudio: isAudioRecvEnabled(media),
- OfferToReceiveVideo: isVideoRecvEnabled(media)
- }
- };
- }
- }
- Janus.debug(mediaConstraints);
- // Check if this is Firefox and we've been asked to do simulcasting
- var sendVideo = isVideoSendEnabled(media);
- if(sendVideo && simulcast && Janus.webRTCAdapter.browserDetails.browser === "firefox") {
- // FIXME Based on https://gist.github.com/voluntas/088bc3cc62094730647b
- Janus.log("Enabling Simulcasting for Firefox (RID)");
- var sender = config.pc.getSenders()[1];
- Janus.log(sender);
- var parameters = sender.getParameters();
- Janus.log(parameters);
-
- var maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates);
- sender.setParameters({encodings: callbacks.sendEncodings || [
- { rid: "h", active: true, maxBitrate: maxBitrates.high },
- { rid: "m", active: true, maxBitrate: maxBitrates.medium, scaleResolutionDownBy: 2},
- { rid: "l", active: true, maxBitrate: maxBitrates.low, scaleResolutionDownBy: 4}
- ]});
- }
- config.pc.createAnswer(mediaConstraints)
- .then(function(answer) {
- Janus.debug(answer);
- // JSON.stringify doesn't work on some WebRTC objects anymore
- // See https://code.google.com/p/chromium/issues/detail?id=467366
- var jsep = {
- "type": answer.type,
- "sdp": answer.sdp
- };
- callbacks.customizeSdp(jsep);
- answer.sdp = jsep.sdp;
- Janus.log("Setting local description");
- if(sendVideo && simulcast) {
- // This SDP munging only works with Chrome
- if(Janus.webRTCAdapter.browserDetails.browser === "chrome") {
- // FIXME Apparently trying to simulcast when answering breaks video in Chrome...
- //~ Janus.log("Enabling Simulcasting for Chrome (SDP munging)");
- //~ answer.sdp = mungeSdpForSimulcasting(answer.sdp);
- Janus.warn("simulcast=true, but this is an answer, and video breaks in Chrome if we enable it");
- } else if(Janus.webRTCAdapter.browserDetails.browser !== "firefox") {
- Janus.warn("simulcast=true, but this is not Chrome nor Firefox, ignoring");
- }
- }
- config.mySdp = {
- type: "answer",
- sdp: answer.sdp
- };
- config.pc.setLocalDescription(answer)
- .catch(callbacks.error);
- config.mediaConstraints = mediaConstraints;
- if(!config.iceDone && !config.trickle) {
- // Don't do anything until we have all candidates
- Janus.log("Waiting for all candidates...");
- return;
- }
- // If transforms are present, notify Janus that the media is end-to-end encrypted
- if(config.senderTransforms || config.receiverTransforms) {
- answer["e2ee"] = true;
- }
- callbacks.success(answer);
- }, callbacks.error);
- }
-
- function sendSDP(handleId, callbacks) {
- callbacks = callbacks || {};
- callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
- callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle, not sending anything");
- return;
- }
- var config = pluginHandle.webrtcStuff;
- Janus.log("Sending offer/answer SDP...");
- if(!config.mySdp) {
- Janus.warn("Local SDP instance is invalid, not sending anything...");
- return;
- }
- config.mySdp = {
- "type": config.pc.localDescription.type,
- "sdp": config.pc.localDescription.sdp
- };
- if(config.trickle === false)
- config.mySdp["trickle"] = false;
- Janus.debug(callbacks);
- config.sdpSent = true;
- callbacks.success(config.mySdp);
- }
-
- function getVolume(handleId, remote) {
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- return 0;
- }
- var stream = remote ? "remote" : "local";
- var config = pluginHandle.webrtcStuff;
- if(!config.volume[stream])
- config.volume[stream] = { value: 0 };
- // Start getting the volume, if audioLevel in getStats is supported (apparently
- // they're only available in Chrome/Safari right now: https://webrtc-stats.callstats.io/)
- if(config.pc.getStats && (Janus.webRTCAdapter.browserDetails.browser === "chrome" ||
- Janus.webRTCAdapter.browserDetails.browser === "safari")) {
- if(remote && !config.remoteStream) {
- Janus.warn("Remote stream unavailable");
- return 0;
- } else if(!remote && !config.myStream) {
- Janus.warn("Local stream unavailable");
- return 0;
- }
- if(!config.volume[stream].timer) {
- Janus.log("Starting " + stream + " volume monitor");
- config.volume[stream].timer = setInterval(function() {
- config.pc.getStats()
- .then(function(stats) {
- stats.forEach(function (res) {
- if(!res || res.kind !== "audio")
- return;
- if((remote && !res.remoteSource) || (!remote && res.type !== "media-source"))
- return;
- config.volume[stream].value = (res.audioLevel ? res.audioLevel : 0);
- });
- });
- }, 200);
- return 0; // We don't have a volume to return yet
- }
- return config.volume[stream].value;
- } else {
- // audioInputLevel and audioOutputLevel seem only available in Chrome? audioLevel
- // seems to be available on Chrome and Firefox, but they don't seem to work
- Janus.warn("Getting the " + stream + " volume unsupported by browser");
- return 0;
- }
- }
-
- function isMuted(handleId, video) {
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- return true;
- }
- var config = pluginHandle.webrtcStuff;
- if(!config.pc) {
- Janus.warn("Invalid PeerConnection");
- return true;
- }
- if(!config.myStream) {
- Janus.warn("Invalid local MediaStream");
- return true;
- }
- if(video) {
- // Check video track
- if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) {
- Janus.warn("No video track");
- return true;
- }
- return !config.myStream.getVideoTracks()[0].enabled;
- } else {
- // Check audio track
- if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) {
- Janus.warn("No audio track");
- return true;
- }
- return !config.myStream.getAudioTracks()[0].enabled;
- }
- }
-
- function mute(handleId, video, mute) {
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- return false;
- }
- var config = pluginHandle.webrtcStuff;
- if(!config.pc) {
- Janus.warn("Invalid PeerConnection");
- return false;
- }
- if(!config.myStream) {
- Janus.warn("Invalid local MediaStream");
- return false;
- }
- if(video) {
- // Mute/unmute video track
- if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) {
- Janus.warn("No video track");
- return false;
- }
- config.myStream.getVideoTracks()[0].enabled = !mute;
- return true;
- } else {
- // Mute/unmute audio track
- if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) {
- Janus.warn("No audio track");
- return false;
- }
- config.myStream.getAudioTracks()[0].enabled = !mute;
- return true;
- }
- }
-
- function getBitrate(handleId) {
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle || !pluginHandle.webrtcStuff) {
- Janus.warn("Invalid handle");
- return "Invalid handle";
- }
- var config = pluginHandle.webrtcStuff;
- if(!config.pc)
- return "Invalid PeerConnection";
- // Start getting the bitrate, if getStats is supported
- if(config.pc.getStats) {
- if(!config.bitrate.timer) {
- Janus.log("Starting bitrate timer (via getStats)");
- config.bitrate.timer = setInterval(function() {
- config.pc.getStats()
- .then(function(stats) {
- stats.forEach(function (res) {
- if(!res)
- return;
- var inStats = false;
- // Check if these are statistics on incoming media
- if((res.mediaType === "video" || res.id.toLowerCase().indexOf("video") > -1) &&
- res.type === "inbound-rtp" && res.id.indexOf("rtcp") < 0) {
- // New stats
- inStats = true;
- } else if(res.type == 'ssrc' && res.bytesReceived &&
- (res.googCodecName === "VP8" || res.googCodecName === "")) {
- // Older Chromer versions
- inStats = true;
- }
- // Parse stats now
- if(inStats) {
- config.bitrate.bsnow = res.bytesReceived;
- config.bitrate.tsnow = res.timestamp;
- if(config.bitrate.bsbefore === null || config.bitrate.tsbefore === null) {
- // Skip this round
- config.bitrate.bsbefore = config.bitrate.bsnow;
- config.bitrate.tsbefore = config.bitrate.tsnow;
- } else {
- // Calculate bitrate
- var timePassed = config.bitrate.tsnow - config.bitrate.tsbefore;
- if(Janus.webRTCAdapter.browserDetails.browser === "safari")
- timePassed = timePassed/1000; // Apparently the timestamp is in microseconds, in Safari
- var bitRate = Math.round((config.bitrate.bsnow - config.bitrate.bsbefore) * 8 / timePassed);
- if(Janus.webRTCAdapter.browserDetails.browser === "safari")
- bitRate = parseInt(bitRate/1000);
- config.bitrate.value = bitRate + ' kbits/sec';
- //~ Janus.log("Estimated bitrate is " + config.bitrate.value);
- config.bitrate.bsbefore = config.bitrate.bsnow;
- config.bitrate.tsbefore = config.bitrate.tsnow;
- }
- }
- });
- });
- }, 1000);
- return "0 kbits/sec"; // We don't have a bitrate value yet
- }
- return config.bitrate.value;
- } else {
- Janus.warn("Getting the video bitrate unsupported by browser");
- return "Feature unsupported by browser";
- }
- }
-
- function webrtcError(error) {
- Janus.error("WebRTC error:", error);
- }
-
- function cleanupWebrtc(handleId, hangupRequest) {
- Janus.log("Cleaning WebRTC stuff");
- var pluginHandle = pluginHandles[handleId];
- if(!pluginHandle) {
- // Nothing to clean
- return;
- }
- var config = pluginHandle.webrtcStuff;
- if(config) {
- if(hangupRequest === true) {
- // Send a hangup request (we don't really care about the response)
- var request = { "janus": "hangup", "transaction": Janus.randomString(12) };
- if(pluginHandle.token)
- request["token"] = pluginHandle.token;
- if(apisecret)
- request["apisecret"] = apisecret;
- Janus.debug("Sending hangup request (handle=" + handleId + "):");
- Janus.debug(request);
- if(websockets) {
- request["session_id"] = sessionId;
- request["handle_id"] = handleId;
- ws.send(JSON.stringify(request));
- } else {
- Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, {
- verb: 'POST',
- withCredentials: withCredentials,
- body: request
- });
- }
- }
- // Cleanup stack
- config.remoteStream = null;
- if(config.volume) {
- if(config.volume["local"] && config.volume["local"].timer)
- clearInterval(config.volume["local"].timer);
- if(config.volume["remote"] && config.volume["remote"].timer)
- clearInterval(config.volume["remote"].timer);
- }
- config.volume = {};
- if(config.bitrate.timer)
- clearInterval(config.bitrate.timer);
- config.bitrate.timer = null;
- config.bitrate.bsnow = null;
- config.bitrate.bsbefore = null;
- config.bitrate.tsnow = null;
- config.bitrate.tsbefore = null;
- config.bitrate.value = null;
- if(!config.streamExternal && config.myStream) {
- Janus.log("Stopping local stream tracks");
- Janus.stopAllTracks(config.myStream);
- }
- config.streamExternal = false;
- config.myStream = null;
- // Close PeerConnection
- try {
- config.pc.close();
- } catch(e) {
- // Do nothing
- }
- config.pc = null;
- config.candidates = null;
- config.mySdp = null;
- config.remoteSdp = null;
- config.iceDone = false;
- config.dataChannel = {};
- config.dtmfSender = null;
- config.senderTransforms = null;
- config.receiverTransforms = null;
- }
- pluginHandle.oncleanup();
- }
-
- // Helper method to munge an SDP to enable simulcasting (Chrome only)
- function mungeSdpForSimulcasting(sdp) {
- // Let's munge the SDP to add the attributes for enabling simulcasting
- // (based on https://gist.github.com/ggarber/a19b4c33510028b9c657)
- var lines = sdp.split("\r\n");
- var video = false;
- var ssrc = [ -1 ], ssrc_fid = [ -1 ];
- var cname = null, msid = null, mslabel = null, label = null;
- var insertAt = -1;
- for(let i=0; i -1) {
- // We're done, let's add the new attributes here
- insertAt = i;
- break;
- }
- }
- continue;
- }
- if(!video)
- continue;
- var sim = lines[i].match(/a=ssrc-group:SIM (\d+) (\d+) (\d+)/);
- if(sim) {
- Janus.warn("The SDP already contains a SIM attribute, munging will be skipped");
- return sdp;
- }
- var fid = lines[i].match(/a=ssrc-group:FID (\d+) (\d+)/);
- if(fid) {
- ssrc[0] = fid[1];
- ssrc_fid[0] = fid[2];
- lines.splice(i, 1); i--;
- continue;
- }
- if(ssrc[0]) {
- var match = lines[i].match('a=ssrc:' + ssrc[0] + ' cname:(.+)')
- if(match) {
- cname = match[1];
- }
- match = lines[i].match('a=ssrc:' + ssrc[0] + ' msid:(.+)')
- if(match) {
- msid = match[1];
- }
- match = lines[i].match('a=ssrc:' + ssrc[0] + ' mslabel:(.+)')
- if(match) {
- mslabel = match[1];
- }
- match = lines[i].match('a=ssrc:' + ssrc[0] + ' label:(.+)')
- if(match) {
- label = match[1];
- }
- if(lines[i].indexOf('a=ssrc:' + ssrc_fid[0]) === 0) {
- lines.splice(i, 1); i--;
- continue;
- }
- if(lines[i].indexOf('a=ssrc:' + ssrc[0]) === 0) {
- lines.splice(i, 1); i--;
- continue;
- }
- }
- if(lines[i].length == 0) {
- lines.splice(i, 1); i--;
- continue;
- }
- }
- if(ssrc[0] < 0) {
- // Couldn't find a FID attribute, let's just take the first video SSRC we find
- insertAt = -1;
- video = false;
- for(let i=0; i -1) {
- // We're done, let's add the new attributes here
- insertAt = i;
- break;
- }
- }
- continue;
- }
- if(!video)
- continue;
- if(ssrc[0] < 0) {
- var value = lines[i].match(/a=ssrc:(\d+)/);
- if(value) {
- ssrc[0] = value[1];
- lines.splice(i, 1); i--;
- continue;
- }
- } else {
- let match = lines[i].match('a=ssrc:' + ssrc[0] + ' cname:(.+)')
- if(match) {
- cname = match[1];
- }
- match = lines[i].match('a=ssrc:' + ssrc[0] + ' msid:(.+)')
- if(match) {
- msid = match[1];
- }
- match = lines[i].match('a=ssrc:' + ssrc[0] + ' mslabel:(.+)')
- if(match) {
- mslabel = match[1];
- }
- match = lines[i].match('a=ssrc:' + ssrc[0] + ' label:(.+)')
- if(match) {
- label = match[1];
- }
- if(lines[i].indexOf('a=ssrc:' + ssrc_fid[0]) === 0) {
- lines.splice(i, 1); i--;
- continue;
- }
- if(lines[i].indexOf('a=ssrc:' + ssrc[0]) === 0) {
- lines.splice(i, 1); i--;
- continue;
- }
- }
- if(lines[i].length === 0) {
- lines.splice(i, 1); i--;
- continue;
- }
- }
- }
- if(ssrc[0] < 0) {
- // Still nothing, let's just return the SDP we were asked to munge
- Janus.warn("Couldn't find the video SSRC, simulcasting NOT enabled");
- return sdp;
- }
- if(insertAt < 0) {
- // Append at the end
- insertAt = lines.length;
- }
- // Generate a couple of SSRCs (for retransmissions too)
- // Note: should we check if there are conflicts, here?
- ssrc[1] = Math.floor(Math.random()*0xFFFFFFFF);
- ssrc[2] = Math.floor(Math.random()*0xFFFFFFFF);
- ssrc_fid[1] = Math.floor(Math.random()*0xFFFFFFFF);
- ssrc_fid[2] = Math.floor(Math.random()*0xFFFFFFFF);
- // Add attributes to the SDP
- for(var i=0; i
-
+
diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js
index 369f23e8f..f6da7fbac 100644
--- a/web/skins/classic/views/js/montage.js
+++ b/web/skins/classic/views/js/montage.js
@@ -1,9 +1,3 @@
-var server;
-var janus = null;
-var opaqueId;
-var globalCount = 0;
-var streamingList = [];
-var janusMonitors = [];
/**
* called when the layoutControl select element is changed, or the page
* is rendered
@@ -306,43 +300,19 @@ function initPage() {
$j("#flipMontageHeader").slideToggle("fast");
$j("#hdrbutton").toggleClass('glyphicon-menu-down').toggleClass('glyphicon-menu-up');
}
- var initJanus = false;
- //var streamingMonitors = [];
for ( var i = 0, length = monitorData.length; i < length; i++ ) {
- if (monitorData[i].janusEnabled) {
- initJanus = true;
- janusMonitors.push(monitorData[i]);
- }
- }
- if (initJanus) {
- server = "http://" + window.location.hostname + ":8088/janus";
- opaqueId = "streamingtest-"+Janus.randomString(12);
- Janus.init({debug: "all", callback: function() {
- janus = new Janus({
- server: server,
- success: function() {
- for ( var i = 0, length = janusMonitors.length; i < length; i++ ) {
- attachVideo(janus, i);
- }
- }
- });
- }});
- }
- for ( var i = 0, length = monitorData.length; i < length; i++ ) {
- if (!monitorData[i].janusEnabled) {
- monitors[i] = new MonitorStream(monitorData[i]);
+ monitors[i] = new MonitorStream(monitorData[i]);
- // Start the fps and status updates. give a random delay so that we don't assault the server
- var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout );
- console.log("delay: " + delay);
- monitors[i].start(delay);
+ // Start the fps and status updates. give a random delay so that we don't assault the server
+ var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout );
+ console.log("delay: " + delay);
+ monitors[i].start(delay);
- var interval = monitors[i].refresh;
- if ( monitors[i].type == 'WebSite' && interval > 0 ) {
- setInterval(reloadWebSite, interval*1000, i);
- }
- monitors[i].setup_onclick();
+ var interval = monitors[i].refresh;
+ if ( monitors[i].type == 'WebSite' && interval > 0 ) {
+ setInterval(reloadWebSite, interval*1000, i);
}
+ monitors[i].setup_onclick();
}
selectLayout('#zmMontageLayout');
}
@@ -352,58 +322,5 @@ function watchFullscreen() {
openFullscreen(content);
}
-function attachVideo(janus, i) {
- janus.attach({
- plugin: "janus.plugin.streaming",
- opaqueId: "streamingtest-"+Janus.randomString(12),
- success: function(pluginHandle) {
- janusMonitors[i].streaming = pluginHandle;
- var body = {"request": "watch", "id": parseInt(janusMonitors[i].id)};
- janusMonitors[i].streaming.send({"message": body});
- },
- error: function(error) {
- Janus.error(" -- Error attaching plugin... ", error);
- },
- onmessage: function(msg, jsep) {
- Janus.debug(" ::: Got a message :::");
- Janus.debug(msg);
- var result = msg["result"];
- if (result !== null && result !== undefined) {
- if (result["status"] !== undefined && result["status"] !== null) {
- const status = result["status"];
- console.log(status);
- }
- } else if (msg["error"] !== undefined && msg["error"] !== null) {
- Janus.error(msg["error"]);
- return;
- }
- if (jsep !== undefined && jsep !== null) {
- Janus.debug("Handling SDP as well...");
- Janus.debug(jsep);
- // Offer from the plugin, let's answer
- janusMonitors[i].streaming.createAnswer({
- jsep: jsep,
- // We want recvonly audio/video and, if negotiated, datachannels
- media: {audioSend: false, videoSend: false, data: true},
- success: function(jsep) {
- Janus.debug("Got SDP!");
- Janus.debug(jsep);
- var body = {"request": "start"};
- janusMonitors[i].streaming.send({"message": body, "jsep": jsep});
- },
- error: function(error) {
- Janus.error("WebRTC error:", error);
- }
- });
- }
- }, //onmessage function
- onremotestream: function(ourstream) {
- Janus.debug(" ::: Got a remote track :::");
- Janus.debug(ourstream);
- Janus.attachMediaStream(document.getElementById("liveStream" + janusMonitors[i].id), ourstream);
- document.getElementById("liveStream" + janusMonitors[i].id).play();
- }
- });// attach
-}
// Kick everything off
$j(document).ready(initPage);
diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php
index ed1869fab..bb99916dd 100644
--- a/web/skins/classic/views/montage.php
+++ b/web/skins/classic/views/montage.php
@@ -326,6 +326,6 @@ foreach (array_reverse($zones) as $zone) {
-
+
diff --git a/web/skins/classic/views/watch.php b/web/skins/classic/views/watch.php
index 3128f7767..f47bd1ccb 100644
--- a/web/skins/classic/views/watch.php
+++ b/web/skins/classic/views/watch.php
@@ -412,5 +412,5 @@ if ( ZM_WEB_SOUND_ON_ALARM ) {
-
+