Untitled diff
718 lines
/*
/*
The MIT License (MIT)
The MIT License (MIT)
Copyright (c) 2016 Meetecho
Copyright (c) 2016 Meetecho
Permission is hereby granted, free of charge, to any person obtaining
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
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:
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
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
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
OTHER DEALINGS IN THE SOFTWARE.
*/
*/
// List of sessions
// List of sessions
Janus.sessions = {};
Janus.sessions = {};
// Screensharing Chrome Extension ID
// Screensharing Chrome Extension ID
Janus.extensionId = "hapfgfdkleiggjjpfpenajgdnfckjpaj";
Janus.extensionId = "hapfgfdkleiggjjpfpenajgdnfckjpaj";
Janus.isExtensionEnabled = function() {
Janus.isExtensionEnabled = function() {
if(window.navigator.userAgent.match('Chrome')) {
if(window.navigator.userAgent.match('Chrome')) {
var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10);
var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10);
var maxver = 33;
var maxver = 33;
if(window.navigator.userAgent.match('Linux'))
if(window.navigator.userAgent.match('Linux'))
maxver = 35; // "known" crash in chrome 34 and 35 on linux
maxver = 35; // "known" crash in chrome 34 and 35 on linux
if(chromever >= 26 && chromever <= maxver) {
if(chromever >= 26 && chromever <= maxver) {
// Older versions of Chrome don't support this extension-based approach, so lie
// Older versions of Chrome don't support this extension-based approach, so lie
return true;
return true;
}
}
return ($('#janus-extension-installed').length > 0);
return (document.getElementById('janus-extension-installed') !== null);
} else {
} else {
// Firefox of others, no need for the extension (but this doesn't mean it will work)
// Firefox of others, no need for the extension (but this doesn't mean it will work)
return true;
return true;
}
}
};
};
Janus.noop = function() {};
Janus.noop = function() {};
// Initialization
// Initialization
Janus.init = function(options) {
Janus.init = function(options) {
options = options || {};
options = options || {};
options.callback = (typeof options.callback == "function") ? options.callback : Janus.noop;
options.callback = (typeof options.callback == "function") ? options.callback : Janus.noop;
if(Janus.initDone === true) {
if(Janus.initDone === true) {
// Already initialized
// Already initialized
options.callback();
options.callback();
} else {
} else {
if(typeof console == "undefined" || typeof console.log == "undefined")
if(typeof console == "undefined" || typeof console.log == "undefined")
console = { log: function() {} };
console = { log: function() {} };
// Console logging (all debugging disabled by default)
// Console logging (all debugging disabled by default)
Janus.trace = Janus.noop;
Janus.trace = Janus.noop;
Janus.debug = Janus.noop;
Janus.debug = Janus.noop;
Janus.vdebug = Janus.noop;
Janus.vdebug = Janus.noop;
Janus.log = Janus.noop;
Janus.log = Janus.noop;
Janus.warn = Janus.noop;
Janus.warn = Janus.noop;
Janus.error = Janus.noop;
Janus.error = Janus.noop;
if(options.debug === true || options.debug === "all") {
if(options.debug === true || options.debug === "all") {
// Enable all debugging levels
// Enable all debugging levels
Janus.trace = console.trace.bind(console);
Janus.trace = console.trace.bind(console);
Janus.debug = console.debug.bind(console);
Janus.debug = console.debug.bind(console);
Janus.vdebug = console.debug.bind(console);
Janus.vdebug = console.debug.bind(console);
Janus.log = console.log.bind(console);
Janus.log = console.log.bind(console);
Janus.warn = console.warn.bind(console);
Janus.warn = console.warn.bind(console);
Janus.error = console.error.bind(console);
Janus.error = console.error.bind(console);
} else if(Array.isArray(options.debug)) {
} else if(Array.isArray(options.debug)) {
for(var i in options.debug) {
for(var i in options.debug) {
var d = options.debug[i];
var d = options.debug[i];
switch(d) {
switch(d) {
case "trace":
case "trace":
Janus.trace = console.trace.bind(console);
Janus.trace = console.trace.bind(console);
break;
break;
case "debug":
case "debug":
Janus.debug = console.debug.bind(console);
Janus.debug = console.debug.bind(console);
break;
break;
case "vdebug":
case "vdebug":
Janus.vdebug = console.debug.bind(console);
Janus.vdebug = console.debug.bind(console);
break;
break;
case "log":
case "log":
Janus.log = console.log.bind(console);
Janus.log = console.log.bind(console);
break;
break;
case "warn":
case "warn":
Janus.warn = console.warn.bind(console);
Janus.warn = console.warn.bind(console);
break;
break;
case "error":
case "error":
Janus.error = console.error.bind(console);
Janus.error = console.error.bind(console);
break;
break;
default:
default:
console.error("Unknown debugging option '" + d + "' (supported: 'trace', 'debug', 'vdebug', 'log', warn', 'error')");
console.error("Unknown debugging option '" + d + "' (supported: 'trace', 'debug', 'vdebug', 'log', warn', 'error')");
break;
break;
}
}
}
}
}
}
Janus.log("Initializing library");
Janus.log("Initializing library");
// Helper method to enumerate devices
// Helper method to enumerate devices
Janus.listDevices = function(callback) {
Janus.listDevices = function(callback) {
callback = (typeof callback == "function") ? callback : Janus.noop;
callback = (typeof callback == "function") ? callback : Janus.noop;
if(navigator.mediaDevices) {
if(navigator.mediaDevices) {
navigator.mediaDevices.getUserMedia({ audio: true, video: true })
navigator.mediaDevices.getUserMedia({ audio: true, video: true })
.then(function(stream) {
.then(function(stream) {
navigator.mediaDevices.enumerateDevices().then(function(devices) {
navigator.mediaDevices.enumerateDevices().then(function(devices) {
Janus.debug(devices);
Janus.debug(devices);
callback(devices);
callback(devices);
// Get rid of the now useless stream
// Get rid of the now useless stream
try {
try {
stream.stop();
stream.stop();
} catch(e) {}
} catch(e) {}
try {
try {
var tracks = stream.getTracks();
var tracks = stream.getTracks();
for(var i in tracks) {
for(var i in tracks) {
var mst = tracks[i];
var mst = tracks[i];
if(mst !== null && mst !== undefined)
if(mst !== null && mst !== undefined)
mst.stop();
mst.stop();
}
}
} catch(e) {}
} catch(e) {}
});
});
})
})
.catch(function(err) {
.catch(function(err) {
Janus.error(err);
Janus.error(err);
callback([]);
callback([]);
});
});
} else {
} else {
Janus.warn("navigator.mediaDevices unavailable");
Janus.warn("navigator.mediaDevices unavailable");
callback([]);
callback([]);
}
}
}
}
// Helper methods to attach/reattach a stream to a video element (previously part of adapter.js)
// Helper methods to attach/reattach a stream to a video element (previously part of adapter.js)
Janus.attachMediaStream = function(element, stream) {
Janus.attachMediaStream = function(element, stream) {
if(adapter.browserDetails.browser === 'chrome') {
if(adapter.browserDetails.browser === 'chrome') {
var chromever = adapter.browserDetails.version;
var chromever = adapter.browserDetails.version;
if(chromever >= 43) {
if(chromever >= 43) {
element.srcObject = stream;
element.srcObject = stream;
} else if(typeof element.src !== 'undefined') {
} else if(typeof element.src !== 'undefined') {
element.src = URL.createObjectURL(stream);
element.src = URL.createObjectURL(stream);
} else {
} else {
Janus.error("Error attaching stream to element");
Janus.error("Error attaching stream to element");
}
}
} else if(adapter.browserDetails.browser === 'safari' || window.navigator.userAgent.match(/iPad/i) || window.navigator.userAgent.match(/iPhone/i)) {
} else if(adapter.browserDetails.browser === 'safari' || window.navigator.userAgent.match(/iPad/i) || window.navigator.userAgent.match(/iPhone/i)) {
element.src = URL.createObjectURL(stream);
element.src = URL.createObjectURL(stream);
} else {
}
else {
element.srcObject = stream;
element.srcObject = stream;
}
}
};
};
Janus.reattachMediaStream = function(to, from) {
Janus.reattachMediaStream = function(to, from) {
if(adapter.browserDetails.browser === 'chrome') {
if(adapter.browserDetails.browser === 'chrome') {
var chromever = adapter.browserDetails.version;
var chromever = adapter.browserDetails.version;
if(chromever >= 43) {
if(chromever >= 43) {
to.srcObject = from.srcObject;
to.srcObject = from.srcObject;
} else if(typeof to.src !== 'undefined') {
} else if(typeof to.src !== 'undefined') {
to.src = from.src;
to.src = from.src;
}
}
} else if(adapter.browserDetails.browser === 'safari' || window.navigator.userAgent.match(/iPad/i) || window.navigator.userAgent.match(/iPhone/i)) {
} else if(adapter.browserDetails.browser === 'safari' || window.navigator.userAgent.match(/iPad/i) || window.navigator.userAgent.match(/iPhone/i)) {
to.src = from.src;
to.src = from.src;
} else {
}
else {
to.srcObject = from.srcObject;
to.srcObject = from.srcObject;
}
}
};
};
// Prepare a helper method to send AJAX requests in a syntax similar to jQuery (at least for what we care)
Janus.ajax = function(params) {
// Check params
if(params === null || params === undefined)
return;
params.success = (typeof params.success == "function") ? params.success : Janus.noop;
params.error = (typeof params.error == "function") ? params.error : Janus.noop;
// Make sure there's an URL
if(params.url === null || params.url === undefined) {
Janus.error('Missing url', params.url);
params.error(null, -1, 'Missing url');
return;
}
// Validate async
params.async = (params.async === null || params.async === undefined) ? true : (params.async === true);
Janus.log(params);
// IE doesn't even know what WebRTC is, so no polyfill needed
var XHR = new XMLHttpRequest();
XHR.open(params.type, params.url, params.async);
if(params.contentType !== null && params.contentType !== undefined)
XHR.setRequestHeader('Content-type', params.contentType);
if(params.withCredentials !== null && params.withCredentials !== undefined)
XHR.withCredentials = params.withCredentials;
if(params.async) {
XHR.onreadystatechange = function () {
if(XHR.readyState != 4)
return;
if(XHR.status !== 200) {
// Got an error?
params.error(XHR, XHR.status !== 0 ? XHR.status : 'error', "");
return;
}
// Got payload
try {
params.success(JSON.parse(XHR.responseText));
} catch(e) {
params.error(XHR, XHR.status, 'Could not parse response, error: ' + e + ', text: ' + XHR.responseText);
}
};
}
try {
XHR.send(params.data);
if(!params.async) {
if(XHR.status !== 200) {
// Got an error?
params.error(XHR, XHR.status !== 0 ? XHR.status : 'error', "");
return;
}
// Got payload
try {
params.success(JSON.parse(XHR.responseText));
} catch(e) {
params.error(XHR, XHR.status, 'Could not parse response, error: ' + e + ', text: ' + XHR.responseText);
}
}
} catch(e) {
// Something broke up
params.error(XHR, 'error', '');
};
};
// Detect tab close: make sure we don't loose existing onbeforeunload handlers
// Detect tab close: make sure we don't loose existing onbeforeunload handlers
var oldOBF = window.onbeforeunload;
var oldOBF = window.onbeforeunload;
window.onbeforeunload = function() {
window.onbeforeunload = function() {
Janus.log("Closing window");
Janus.log("Closing window");
for(var s in Janus.sessions) {
for(var s in Janus.sessions) {
if(Janus.sessions[s] !== null && Janus.sessions[s] !== undefined &&
if(Janus.sessions[s] !== null && Janus.sessions[s] !== undefined &&
Janus.sessions[s].destroyOnUnload) {
Janus.sessions[s].destroyOnUnload) {
Janus.log("Destroying session " + s);
Janus.log("Destroying session " + s);
Janus.sessions[s].destroy({asyncRequest: false});
Janus.sessions[s].destroy({asyncRequest: false});
}
}
}
}
if(oldOBF && typeof oldOBF == "function")
if(oldOBF && typeof oldOBF == "function")
oldOBF();
oldOBF();
}
}
Janus.initDone = true;
Janus.initDone = true;
options.callback();
options.callback();
}
}
};
};
// Helper method to check whether WebRTC is supported by this browser
// Helper method to check whether WebRTC is supported by this browser
Janus.isWebrtcSupported = function() {
Janus.isWebrtcSupported = function() {
return window.RTCPeerConnection !== undefined && window.RTCPeerConnection !== null &&
return window.RTCPeerConnection !== undefined && window.RTCPeerConnection !== null &&
navigator.getUserMedia !== undefined && navigator.getUserMedia !== null;
navigator.getUserMedia !== undefined && navigator.getUserMedia !== null;
};
};
// Helper method to create random identifiers (e.g., transaction)
// Helper method to create random identifiers (e.g., transaction)
Janus.randomString = function(len) {
Janus.randomString = function(len) {
var charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var randomString = '';
var randomString = '';
for (var i = 0; i < len; i++) {
for (var i = 0; i < len; i++) {
var randomPoz = Math.floor(Math.random() * charSet.length);
var randomPoz = Math.floor(Math.random() * charSet.length);
randomString += charSet.substring(randomPoz,randomPoz+1);
randomString += charSet.substring(randomPoz,randomPoz+1);
}
}
return randomString;
return randomString;
}
}
// Janus session object
function Janus(gatewayCallbacks) {
function Janus(gatewayCallbacks) {
if(Janus.initDone === undefined) {
if(Janus.initDone === undefined) {
gatewayCallbacks.error("Library not initialized");
gatewayCallbacks.error("Library not initialized");
return {};
return {};
}
}
if(!Janus.isWebrtcSupported()) {
if(!Janus.isWebrtcSupported()) {
gatewayCallbacks.error("WebRTC not supported by this browser");
gatewayCallbacks.error("WebRTC not supported by this browser");
return {};
return {};
}
}
Janus.log("Library initialized: " + Janus.initDone);
Janus.log("Library initialized: " + Janus.initDone);
gatewayCallbacks = gatewayCallbacks || {};
gatewayCallbacks = gatewayCallbacks || {};
gatewayCallbacks.success = (typeof gatewayCallbacks.success == "function") ? gatewayCallbacks.success : jQuery.noop;
gatewayCallbacks.success = (typeof gatewayCallbacks.success == "function") ? gatewayCallbacks.success : Janus.noop;
gatewayCallbacks.error = (typeof gatewayCallbacks.error == "function") ? gatewayCallbacks.error : jQuery.noop;
gatewayCallbacks.error = (typeof gatewayCallbacks.error == "function") ? gatewayCallbacks.error : Janus.noop;
gatewayCallbacks.destroyed = (typeof gatewayCallbacks.destroyed == "function") ? gatewayCallbacks.destroyed : jQuery.noop;
gatewayCallbacks.destroyed = (typeof gatewayCallbacks.destroyed == "function") ? gatewayCallbacks.destroyed : Janus.noop;
if(gatewayCallbacks.server === null || gatewayCallbacks.server === undefined) {
if(gatewayCallbacks.server === null || gatewayCallbacks.server === undefined) {
gatewayCallbacks.error("Invalid gateway url");
gatewayCallbacks.error("Invalid gateway url");
return {};
return {};
}
}
var websockets = false;
var websockets = false;
var ws = null;
var ws = null;
var wsHandlers = {};
var wsHandlers = {};
var wsKeepaliveTimeoutId = null;
var wsKeepaliveTimeoutId = null;
var servers = null, serversIndex = 0;
var servers = null, serversIndex = 0;
var server = gatewayCallbacks.server;
var server = gatewayCallbacks.server;
if($.isArray(server)) {
if(Array.isArray(server)) {
Janus.log("Multiple servers provided (" + server.length + "), will use the first that works");
Janus.log("Multiple servers provided (" + server.length + "), will use the first that works");
server = null;
server = null;
servers = gatewayCallbacks.server;
servers = gatewayCallbacks.server;
Janus.debug(servers);
Janus.debug(servers);
} else {
} else {
if(server.indexOf("ws") === 0) {
if(server.indexOf("ws") === 0) {
websockets = true;
websockets = true;
Janus.log("Using WebSockets to contact Janus: " + server);
Janus.log("Using WebSockets to contact Janus: " + server);
} else {
} else {
websockets = false;
websockets = false;
Janus.log("Using REST API to contact Janus: " + server);
Janus.log("Using REST API to contact Janus: " + server);
}
}
}
}
var iceServers = gatewayCallbacks.iceServers;
var iceServers = gatewayCallbacks.iceServers;
if(iceServers === undefined || iceServers === null)
if(iceServers === undefined || iceServers === null)
iceServers = [{urls: "stun:stun.l.google.com:19302"}];
iceServers = [{urls: "stun:stun.l.google.com:19302"}];
var iceTransportPolicy = gatewayCallbacks.iceTransportPolicy;
var iceTransportPolicy = gatewayCallbacks.iceTransportPolicy;
// Whether IPv6 candidates should be gathered
// Whether IPv6 candidates should be gathered
var ipv6Support = gatewayCallbacks.ipv6;
var ipv6Support = gatewayCallbacks.ipv6;
if(ipv6Support === undefined || ipv6Support === null)
if(ipv6Support === undefined || ipv6Support === null)
ipv6Support = false;
ipv6Support = false;
// Whether we should enable the withCredentials flag for XHR requests
// Whether we should enable the withCredentials flag for XHR requests
var withCredentials = false;
var withCredentials = false;
if(gatewayCallbacks.withCredentials !== undefined && gatewayCallbacks.withCredentials !== null)
if(gatewayCallbacks.withCredentials !== undefined && gatewayCallbacks.withCredentials !== null)
withCredentials = gatewayCallbacks.withCredentials === true;
withCredentials = gatewayCallbacks.withCredentials === true;
// Optional max events
// Optional max events
var maxev = null;
var maxev = null;
if(gatewayCallbacks.max_poll_events !== undefined && gatewayCallbacks.max_poll_events !== null)
if(gatewayCallbacks.max_poll_events !== undefined && gatewayCallbacks.max_poll_events !== null)
maxev = gatewayCallbacks.max_poll_events;
maxev = gatewayCallbacks.max_poll_events;
if(maxev < 1)
if(maxev < 1)
maxev = 1;
maxev = 1;
// Token to use (only if the token based authentication mechanism is enabled)
// Token to use (only if the token based authentication mechanism is enabled)
var token = null;
var token = null;
if(gatewayCallbacks.token !== undefined && gatewayCallbacks.token !== null)
if(gatewayCallbacks.token !== undefined && gatewayCallbacks.token !== null)
token = gatewayCallbacks.token;
token = gatewayCallbacks.token;
// API secret to use (only if the shared API secret is enabled)
// API secret to use (only if the shared API secret is enabled)
var apisecret = null;
var apisecret = null;
if(gatewayCallbacks.apisecret !== undefined && gatewayCallbacks.apisecret !== null)
if(gatewayCallbacks.apisecret !== undefined && gatewayCallbacks.apisecret !== null)
apisecret = gatewayCallbacks.apisecret;
apisecret = gatewayCallbacks.apisecret;
// Whether we should destroy this session when onbeforeunload is called
// Whether we should destroy this session when onbeforeunload is called
this.destroyOnUnload = true;
this.destroyOnUnload = true;
if(gatewayCallbacks.destroyOnUnload !== undefined && gatewayCallbacks.destroyOnUnload !== null)
if(gatewayCallbacks.destroyOnUnload !== undefined && gatewayCallbacks.destroyOnUnload !== null)
this.destroyOnUnload = (gatewayCallbacks.destroyOnUnload === true);
this.destroyOnUnload = (gatewayCallbacks.destroyOnUnload === true);
var connected = false;
var connected = false;
var sessionId = null;
var sessionId = null;
var pluginHandles = {};
var pluginHandles = {};
var that = this;
var that = this;
var retries = 0;
var retries = 0;
var transactions = {};
var transactions = {};
createSession(gatewayCallbacks);
createSession(gatewayCallbacks);
// Public methods
// Public methods
this.getServer = function() { return server; };
this.getServer = function() { return server; };
this.isConnected = function() { return connected; };
this.isConnected = function() { return connected; };
this.getSessionId = function() { return sessionId; };
this.getSessionId = function() { return sessionId; };
this.destroy = function(callbacks) { destroySession(callbacks); };
this.destroy = function(callbacks) { destroySession(callbacks, true); };
this.attach = function(callbacks) { createHandle(callbacks); };
this.attach = function(callbacks) { createHandle(callbacks); };
function eventHandler() {
function eventHandler() {
if(sessionId == null)
if(sessionId == null)
return;
return;
Janus.debug('Long poll...');
Janus.debug('Long poll...');
if(!connected) {
if(!connected) {
Janus.warn("Is the gateway down? (connected=false)");
Janus.warn("Is the gateway down? (connected=false)");
return;
return;
}
}
var longpoll = server + "/" + sessionId + "?rid=" + new Date().getTime();
var longpoll = server + "/" + sessionId + "?rid=" + new Date().getTime();
if(maxev !== undefined && maxev !== null)
if(maxev !== undefined && maxev !== null)
longpoll = longpoll + "&maxev=" + maxev;
longpoll = longpoll + "&maxev=" + maxev;
if(token !== null && token !== undefined)
if(token !== null && token !== undefined)
longpoll = longpoll + "&token=" + token;
longpoll = longpoll + "&token=" + token;
if(apisecret !== null && apisecret !== undefined)
if(apisecret !== null && apisecret !== undefined)
longpoll = longpoll + "&apisecret=" + apisecret;
longpoll = longpoll + "&apisecret=" + apisecret;
$.ajax({
Janus.ajax({
type: 'GET',
type: 'GET',
url: longpoll,
url: longpoll,
xhrFields: {
withCredentials: withCredentials,
withCredentials: withCredentials
},
cache: false,
cache: false,
timeout: 60000, // FIXME
timeout: 60000, // FIXME
success: handleEvent,
success: handleEvent,
error: function(XMLHttpRequest, textStatus, errorThrown) {
error: function(XMLHttpRequest, textStatus, errorThrown) {
Janus.error(textStatus + ": " + errorThrown);
Janus.error(textStatus + ": " + errorThrown);
retries++;
retries++;
if(retries > 3) {
if(retries > 3) {
// Did we just lose the gateway? :-(
// Did we just lose the gateway? :-(
connected = false;
connected = false;
gatewayCallbacks.error("Lost connection to the gateway (is it down?)");
gatewayCallbacks.error("Lost connection to the gateway (is it down?)");
return;
return;
}
}
eventHandler();
eventHandler();
},
},
dataType: "json"
dataType: "json"
});
});
}
}
// Private event handler: this will trigger plugin callbacks, if set
// Private event handler: this will trigger plugin callbacks, if set
function handleEvent(json) {
function handleEvent(json) {
retries = 0;
retries = 0;
if(!websockets && sessionId !== undefined && sessionId !== null)
if(!websockets && sessionId !== undefined && sessionId !== null)
setTimeout(eventHandler, 200);
setTimeout(eventHandler, 200);
if(!websockets && $.isArray(json)) {
if(!websockets && Array.isArray(json)) {
// We got an array: it means we passed a maxev > 1, iterate on all objects
// We got an array: it means we passed a maxev > 1, iterate on all objects
for(var i=0; i<json.length; i++) {
for(var i=0; i<json.length; i++) {
handleEvent(json[i]);
handleEvent(json[i]);
}
}
return;
return;
}
}
if(json["janus"] === "keepalive") {
if(json["janus"] === "keepalive") {
// Nothing happened
// Nothing happened
Janus.vdebug("Got a keepalive on session " + sessionId);
Janus.vdebug("Got a keepalive on session " + sessionId);
return;
return;
} else if(json["janus"] === "ack") {
} else if(json["janus"] === "ack") {
// Just an ack, we can probably ignore
// Just an ack, we can probably ignore
Janus.debug("Got an ack on session " + sessionId);
Janus.debug("Got an ack on session " + sessionId);
Janus.debug(json);
Janus.debug(json);
var transaction = json["transaction"];
var transaction = json["transaction"];
if(transaction !== null && transaction !== undefined) {
if(transaction !== null && transaction !== undefined) {
var reportSuccess = transactions[transaction];
var reportSuccess = transactions[transaction];
if(reportSuccess !== null && reportSuccess !== undefined) {
if(reportSuccess !== null && reportSuccess !== undefined) {
reportSuccess(json);
reportSuccess(json);
}
}
delete transactions[transaction];
delete transactions[transaction];
}
}
return;
return;
} else if(json["janus"] === "success") {
} else if(json["janus"] === "success") {
// Success!
// Success!
Janus.debug("Got a success on session " + sessionId);
Janus.debug("Got a success on session " + sessionId);
Janus.debug(json);
Janus.debug(json);
var transaction = json["transaction"];
var transaction = json["transaction"];
if(transaction !== null && transaction !== undefined) {
if(transaction !== null && transaction !== undefined) {
var reportSuccess = transactions[transaction];
var reportSuccess = transactions[transaction];
if(reportSuccess !== null && reportSuccess !== undefined) {
if(reportSuccess !== null && reportSuccess !== undefined) {
reportSuccess(json);
reportSuccess(json);
}
}
delete transactions[transaction];
delete transactions[transaction];
}
}
return;
return;
} else if(json["janus"] === "webrtcup") {
} else if(json["janus"] === "webrtcup") {
// The PeerConnection with the gateway is up! Notify this
// The PeerConnection with the gateway is up! Notify this
Janus.debug("Got a webrtcup event on session " + sessionId);
Janus.debug("Got a webrtcup event on session " + sessionId);
Janus.debug(json);
Janus.debug(json);
var sender = json["sender"];
var sender = json["sender"];
if(sender === undefined || sender === null) {
if(sender === undefined || sender === null) {
Janus.warn("Missing sender...");
Janus.warn("Missing sender...");
return;
return;
}
}
var pluginHandle = pluginHandles[sender];
var pluginHandle = pluginHandles[sender];
if(pluginHandle === undefined || pluginHandle === null) {
if(pluginHandle === undefined || pluginHandle === null) {
Janus.debug("This handle is not attached to this session");
Janus.debug("This handle is not attached to this session");
return;
return;
}
}
pluginHandle.webrtcState(true);
pluginHandle.webrtcState(true);
return;
return;
} else if(json["janus"] === "hangup") {
} else if(json["janus"] === "hangup") {
// A plugin asked the core to hangup a PeerConnection on one of our handles
// A plugin asked the core to hangup a PeerConnection on one of our handles
Janus.debug("Got a hangup event on session " + sessionId);
Janus.debug("Got a hangup event on session " + sessionId);
Janus.debug(json);
Janus.debug(json);
var sender = json["sender"];
var sender = json["sender"];
if(sender === undefined || sender === null) {
if(sender === undefined || sender === null) {
Janus.warn("Missing sender...");
Janus.warn("Missing sender...");
return;
return;
}
}
var pluginHandle = pluginHandles[sender];
var pluginHandle = pluginHandles[sender];
if(pluginHandle === undefined || pluginHandle === null) {
if(pluginHandle === undefined || pluginHandle === null) {
Janus.debug("This handle is not attached to this session");
Janus.debug("This handle is not attached to this session");
return;
return;
}
}
pluginHandle.webrtcState(false, json["reason"]);
pluginHandle.webrtcState(false, json["reason"]);
pluginHandle.hangup();
pluginHandle.hangup();
} else if(json["janus"] === "detached") {
} else if(json["janus"] === "detached") {
// A plugin asked the core to detach one of our handles
// A plugin asked the core to detach one of our handles
Janus.debug("Got a detached event on session " + sessionId);
Janus.debug("Got a detached event on session " + sessionId);
Janus.debug(json);
Janus.debug(json);
var sender = json["sender"];
var sender = json["sender"];
if(sender === undefined || sender === null) {
if(sender === undefined || sender === null) {
Janus.warn("Missing sender...");
Janus.warn("Missing sender...");
return;
return;
}
}
var pluginHandle = pluginHandles[sender];
var pluginHandle = pluginHandles[sender];
if(pluginHandle === undefined || pluginHandle === null) {
if(pluginHandle === undefined || pluginHandle === null) {
// Don't warn here because destroyHandle causes this situation.
// Don't warn here because destroyHandle causes this situation.
return;
return;
}
}
pluginHandle.detached = true;
pluginHandle.detached = true;
pluginHandle.ondetached();
pluginHandle.ondetached();
pluginHandle.detach();
pluginHandle.detach();
} else if(json["janus"] === "media") {
} else if(json["janus"] === "media") {
// Media started/stopped flowing
// Media started/stopped flowing
Janus.debug("Got a media event on session " + sessionId);
Janus.debug("Got a media event on session " + sessionId);
Janus.debug(json);
Janus.debug(json);
var sender = json["sender"];
var sender = json["sender"];
if(sender === undefined || sender === null) {
if(sender === undefined || sender === null) {
Janus.warn("Missing sender...");
Janus.warn("Missing sender...");
return;
return;
}
}
var pluginHandle = pluginHandles[sender];
var pluginHandle = pluginHandles[sender];
if(pluginHandle === undefined || pluginHandle === null) {
if(pluginHandle === undefined || pluginHandle === null) {
Janus.debug("This handle is not attached to this session");
Janus.debug("This handle is not attached to this session");
return;
return;
}
}
pluginHandle.mediaState(json["type"], json["receiving"]);
pluginHandle.mediaState(json["type"], json["receiving"]);
} else if(json["janus"] === "slowlink") {
} else if(json["janus"] === "slowlink") {
Janus.debug("Got a slowlink event on session " + sessionId);
Janus.debug("Got a slowlink event on session " + sessionId);
Janus.debug(json);
Janus.debug(json);
// Trouble uplink or downlink
// Trouble uplink or downlink
var sender = json["sender"];
var sender = json["sender"];
if(sender === undefined || sender === null) {
if(sender === undefined || sender === null) {
Janus.warn("Missing sender...");
Janus.warn("Missing sender...");
return;
return;
}
}
var pluginHandle = pluginHandles[sender];
var pluginHandle = pluginHandles[sender];
if(pluginHandle === undefined || pluginHandle === null) {
if(pluginHandle === undefined || pluginHandle === null) {
Janus.debug("This handle is not attached to this session");
Janus.debug("This handle is not attached to this session");
return;
return;
}
}
pluginHandle.slowLink(json["uplink"], json["nacks"]);
pluginHandle.slowLink(json["uplink"], json["nacks"]);
} else if(json["janus"] === "error") {
} else if(json["janus"] === "error") {
// Oops, something wrong happened
// Oops, something wrong happened
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
Janus.debug(json);
Janus.debug(json);
var transaction = json["transaction"];
var transaction = json["transaction"];
if(transaction !== null && transaction !== undefined) {
if(transaction !== null && transaction !== undefined) {
var reportSuccess = transactions[transaction];
var reportSuccess = transactions[transaction];
if(reportSuccess !== null && reportSuccess !== undefined) {
if(reportSuccess !== null && reportSuccess !== undefined) {
reportSuccess(json);
reportSuccess(json);
}
}
delete transactions[transaction];
delete transactions[transaction];
}
}
return;
return;
} else if(json["janus"] === "event") {
} else if(json["janus"] === "event") {
Janus.debug("Got a plugin event on session " + sessionId);
Janus.debug("Got a plugin event on session " + sessionId);
Janus.debug(json);
Janus.debug(json);
var sender = json["sender"];
var sender = json["sender"];
if(sender === undefined || sender === null) {
if(sender === undefined || sender === null) {
Janus.warn("Missing sender...");
Janus.warn("Missing sender...");
return;
return;
}
}
var plugindata = json["plugindata"];
var plugindata = json["plugindata"];
if(plugindata === undefined || plugindata === null) {
if(plugindata === undefined || plugindata === null) {
Janus.warn("Missing plugindata...");
Janus.warn("Missing plugindata...");
return;
return;
}
}
Janus.debug(" -- Event is coming from " + sender + " (" + plugindata["plugin"] + ")");
Janus.debug(" -- Event is coming from " + sender + " (" + plugindata["plugin"] + ")");
var data = plugindata["data"];
var data = plugindata["data"];
Janus.debug(data);
Janus.debug(data);
var pluginHandle = pluginHandles[sender];
var pluginHandle = pluginHandles[sender];
if(pluginHandle === undefined || pluginHandle === null) {
if(pluginHandle === undefined || pluginHandle === null) {
Janus.warn("This handle is not attached to this session");
Janus.warn("This handle is not attached to this session");
return;
return;
}
}
var jsep = json["jsep"];
var jsep = json["jsep"];
if(jsep !== undefined && jsep !== null) {
if(jsep !== undefined && jsep !== null) {
Janus.debug("Handling SDP as well...");
Janus.debug("Handling SDP as well...");
Janus.debug(jsep);
Janus.debug(jsep);
}
}
var callback = pluginHandle.onmessage;
var callback = pluginHandle.onmessage;
if(callback !== null && callback !== undefined) {
if(callback !== null && callback !== undefined) {
Janus.debug("Notifying application...");
Janus.debug("Notifying application...");
// Send to callback specified when attaching plugin handle
// Send to callback specified when attaching plugin handle
callback(data, jsep);
callback(data, jsep);
} else {
} else {
// Send to generic callback (?)
// Send to generic callback (?)
Janus.debug("No provided notification callback");
Janus.debug("No provided notification callback");
}
}
} else {
} else {
Janus.warn("Unkown message/event '" + json["janus"] + "' on session " + sessionId);
Janus.warn("Unkown message/event '" + json["janus"] + "' on session " + sessionId);
Janus.debug(json);
Janus.debug(json);
}
}
}
}
// Private helper to send keep-alive messages on WebSockets
// Private helper to send keep-alive messages on WebSockets
function keepAlive() {
function keepAlive() {
if(server === null || !websockets || !connected)
if(server === null || !websockets || !connected)
return;
return;
wsKeepaliveTimeoutId = setTimeout(keepAlive, 30000);
wsKeepaliveTimeoutId = setTimeout(keepAlive, 30000);
var request = { "janus": "keepalive", "session_id": sessionId, "transaction": Janus.randomString(12) };
var request = { "janus": "keepalive", "session_id": sessionId, "transaction": Janus.randomString(12) };
if(token !== null && token !== undefined)
if(token !== null && token !== undefined)
request["token"] = token;
request["token"] = token;
if(apisecret !== null && apisecret !== undefined)
if(apisecret !== null && apisecret !== undefined)
request["apisecret"] = apisecret;
request["apisecret"] = apisecret;
ws.send(JSON.stringify(request));
ws.send(JSON.stringify(request));
}
}
// Private method to create a session
// Private method to create a session
function createSession(callbacks) {
function createSession(callbacks) {
var transaction = Janus.randomString(12);
var transaction = Janus.randomString(12);
var request = { "janus": "create", "transaction": transaction };
var request = { "janus": "create", "transaction": transaction };
if(token !== null && token !== undefined)
if(token !== null && token !== undefined)
request["token"] = token;
request["token"] = token;
if(apisecret !== null && apisecret !== undefined)
if(apisecret !== null && apisecret !== undefined)
request["apisecret"] = apisecret;
request["apisecret"] = apisecret;
if(server === null && $.isArray(servers)) {
if(server === null && Array.isArray(servers)) {
// We still need to find a working server from the list we were given
// We still need to find a working server from the list we were given
server = servers[serversIndex];
server = servers[serversIndex];
if(server.indexOf("ws") === 0) {
if(server.indexOf("ws") === 0) {
websockets = true;
websockets = true;
Janus.log("Server #" + (serversIndex+1) + ": trying WebSockets to contact Janus (" + server + ")");
Janus.log("Server #" + (serversIndex+1) + ": trying WebSockets to contact Janus (" + server + ")");
} else {
} else {
websockets = false;
websockets = false;
Janus.log("Server #" + (serversIndex+1) + ": trying REST API to contact Janus (" + server + ")");
Janus.log("Server #" + (serversIndex+1) + ": trying REST API to contact Janus (" + server + ")");
}
}
}
}
if(websockets) {
if(websockets) {
ws = new WebSocket(server, 'janus-protocol');
ws = new WebSocket(server, 'janus-protocol');
wsHandlers = {
wsHandlers = {
'error': function() {
'error': function() {
Janus.error("Error connecting to the Janus WebSockets server... " + server);
Janus.error("Error connecting to the Janus WebSockets server... " + server);
if ($.isArray(servers)) {
if (Array.isArray(servers)) {
serversIndex++;
serversIndex++;
if (serversIndex == servers.length) {
if (serversIndex == servers.length) {
// We tried all the servers the user gave us and they all failed
// We tried all the servers the user gave us and they all failed
callbacks.error("Error connecting to any of the provided Janus servers: Is the gateway down?");
callbacks.error("Error connecting to any of the provided Janus servers: Is the gateway down?");
return;
return;
}
}
// Let's try the next server
// Let's try the next server
server = null;
server = null;
setTimeout(function() {
setTimeout(function() {
createSession(callbacks);
createSession(callbacks);
}, 200);
}, 200);
return;
return;
}
}
callbacks.error("Error connecting to the Janus WebSockets server: Is the gateway down?");
callbacks.error("Error connecting to the Janus WebSockets server: Is the gateway down?");
},
},
'open': function() {
'open': function() {
// We need to be notified about the success
// We need to be notified about the success
transactions[transaction] = function(json) {
transactions[transaction] = function(json) {
Janus.debug(json);
Janus.debug(json);
if (json["janus"] !== "success") {
if (json["janus"] !== "success") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
callbacks.error(json["error"].reason);
callbacks.error(json["error"].reason);
return;
return;
}
}
wsKeepaliveTimeoutId = setTimeout(keepAlive, 30000);
wsKeepaliveTimeoutId = setTimeout(keepAlive, 30000);
connected = true;
connected = true;
sessionId = json.data["id"];
sessionId = json.data["id"];
Janus.log("Created session: " + sessionId);
Janus.log("Created session: " + sessionId);
Janus.sessions[sessionId] = that;
Janus.sessions[sessionId] = that;
callbacks.success();
callbacks.success();
};
};
ws.send(JSON.stringify(request));
ws.send(JSON.stringify(request));
},
},
'message': function(event) {
'message': function(event) {
handleEvent(JSON.parse(event.data));
try {
handleEvent(JSON.parse(event.data));
} catch(e) {
Janus.error('Error processing event:', e);
}
},
},
'close': function() {
'close': function() {
if (server === null || !connected) {
if (server === null || !connected) {
return;
return;
}
}
connected = false;
connected = false;
// FIXME What if this is called when the page is closed?
// FIXME What if this is called when the page is closed?
gatewayCallbacks.error("Lost connection to the gateway (is it down?)");
gatewayCallbacks.error("Lost connection to the gateway (is it down?)");
}
}
};
};
for(var eventName in wsHandlers) {
for(var eventName in wsHandlers) {
ws.addEventListener(eventName, wsHandlers[eventName]);
ws.addEventListener(eventName, wsHandlers[eventName]);
}
}
return;
return;
}
}
$.ajax({
Janus.ajax({
type: 'POST',
type: 'POST',
url: server,
url: server,
xhrFields: {
withCredentials: withCredentials,
withCredentials: withCredentials
},
cache: false,
cache: false,
contentType: "application/json",
contentType: "application/json",
data: JSON.stringify(request),
data: JSON.stringify(request),
success: function(json) {
success: function(json) {
Janus.debug(json);
Janus.debug(json);
if(json["janus"] !== "success") {
if(json["janus"] !== "success") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
callbacks.error(json["error"].reason);
callbacks.error(json["error"].reason);
return;
return;
}
}
connected = true;
connected = true;
sessionId = json.data["id"];
sessionId = json.data["id"];
Janus.log("Created session: " + sessionId);
Janus.log("Created session: " + sessionId);
Janus.sessions[sessionId] = that;
Janus.sessions[sessionId] = that;
eventHandler();
eventHandler();
callbacks.success();
callbacks.success();
},
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
error: function(XMLHttpRequest, textStatus, errorThrown) {
Janus.error(textStatus + ": " + errorThrown); // FIXME
Janus.error(textStatus + ": " + errorThrown); // FIXME
if($.isArray(servers)) {
if(Array.isArray(servers)) {
serversIndex++;
serversIndex++;
if(serversIndex == servers.length) {
if(serversIndex == servers.length) {
// We tried all the servers the user gave us and they all failed
// We tried all the servers the user gave us and they all failed
callbacks.error("Error connecting to any of the provided Janus servers: Is the gateway down?");
callbacks.error("Error connecting to any of the provided Janus servers: Is the gateway down?");
return;
return;
}
}
// Let's try the next server
// Let's try the next server
server = null;
server = null;
setTimeout(function() { createSession(callbacks); }, 200);
setTimeout(function() { createSession(callbacks); }, 200);
return;
return;
}
}
if(errorThrown === "")
if(errorThrown === "")
callbacks.error(textStatus + ": Is the gateway down?");
callbacks.error(textStatus + ": Is the gateway down?");
else
else
callbacks.error(textStatus + ": " + errorThrown);
callbacks.error(textStatus + ": " + errorThrown);
},
},
dataType: "json"
dataType: "json"
});
});
}
}
// Private method to destroy a session
// Private method to destroy a session
function destroySession(callbacks) {
function destroySession(callbacks) {
callbacks = callbacks || {};
callbacks = callbacks || {};
// FIXME This method triggers a success even when we fail
// FIXME This method triggers a success even when we fail
callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : jQuery.noop;
callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
var asyncRequest = true;
var asyncRequest = true;
if(callbacks.asyncRequest !== undefined && callbacks.asyncRequest !== null)
if(callbacks.asyncRequest !== undefined && callbacks.asyncRequest !== null)
asyncRequest = (callbacks.asyncRequest === true);
asyncRequest = (callbacks.asyncRequest === true);
Janus.log("Destroying session " + sessionId + " (async=" + asyncRequest + ")");
if(!connected) {
Janus.warn("Is the gateway down? (connected=false)");
callbacks.success();
return;
}
if(sessionId === undefined || sessionId === null) {
Janus.warn("No session to destroy");
callbacks.success();
gatewayCallbacks.destroyed();
return;
}
delete Janus.sessions[sessionId];
// No need to destroy all handles first, Janus will do that itself
var request = { "janus": "destroy", "transaction": Janus.randomString(12) };
if(token !== null && token !== undefined)
request["token"] = token;
if(apisecret !== null && apisecret !== undefined)
request["apisecret"] = apisecret;
if(websockets) {
request["session_id"] = sessionId;
var unbindWebSocket = function() {
for(var eventName in wsHandlers) {
ws.removeEventListener(eventName, wsHandlers[eventName]);
}
ws.removeEventListener('message', onUnbindMessage);
ws.removeEventListener('error', onUnbindError);
if(wsKeepaliveTimeoutId) {
clearTimeout(wsKeepaliveTimeoutId);
}
};
var onUnbindMessage = function(event){
var data = JSON.parse(event.data);
if(data.session_id == request.session_id && data.transaction == request.transaction) {
unbindWebSocket();
callbacks.success();
gatewayCallbacks.destroyed();
}
};
var onUnbindError = function(event) {
unbindWebSocket();
callbacks.error("Failed to destroy the gateway: Is the gateway down?");
gatewayCallbacks.destroyed();
};
ws.addEventListener('message', onUnbindMessage);
ws.addEventListener('error', onUnbindError);
ws.send(JSON.stringify(request));
return;
}
$.ajax({
type: 'POST',
url: server + "/" + sessionId,
async: asyncRequest, // Sometimes we need false here, or destroying in onbeforeunload won't work
xhrFields: {
withCredentials: withCredentials
},
cache: false,
contentType: "application/json",
data: JSON.stringify(request),
success: function(json) {
Janus.log("Destroyed session:");
Janus.debug(json);
sessionId = null;
connected = false;
if(json["janus"] !== "success") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);