react-native-enovawbrtc
Version:
Web Rtc
1,290 lines (1,240 loc) • 88.9 kB
JavaScript
import {
RTCPeerConnection,
RTCMediaStream,
RTCIceCandidate,
RTCSessionDescription,
RTCView,
MediaStreamTrack,
mediaDevices
} from 'react-native-webrtc';
const getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices)
MediaStreamTrack.getSources = mediaDevices.enumerateDevices.bind(mediaDevices)
navigator = {
mediaDevices: {
enumerateDevices: mediaDevices.enumerateDevices.bind(mediaDevices)
}
}
import {
Alert
} from 'react-native';
/*
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.mobile = true
// Screensharing Chrome Extension ID
Janus.extensionId = "hapfgfdkleiggjjpfpenajgdnfckjpaj";
// Janus.isExtensionEnabled = function() {
// 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 (document.getElementById('janus-extension-installed') !== null);
// } else {
// // Firefox of others, no need for the extension (but this doesn't mean it will work)
// return true;
// }
// };
Janus.noop = function () { };
// Initialization
Janus.init = function (options) {
options = options || {};
options.callback = (typeof options.callback == "function") ? options.callback : Janus.noop;
if (Janus.initDone === true) {
// Already initialized
options.callback();
} else {
if (typeof console == "undefined" || typeof console.log == "undefined")
console = { log: function () { } };
// Console logging (all debugging disabled by default)
Janus.trace = Janus.noop;
Janus.debug = 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.log = console.log.bind(console);
Janus.warn = console.warn.bind(console);
Janus.error = console.log.bind(console, '[Janus][error]');
} else if (Array.isArray(options.debug)) {
for (var i in options.debug) {
var d = options.debug[i];
switch (d) {
case "trace":
Janus.trace = console.trace.bind(console);
break;
case "debug":
Janus.debug = 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.log.bind(console, '[Janus][error]');
break;
default:
console.log("[Janus][error]Unknown debugging option '" + d + "' (supported: 'trace', 'debug', 'log', warn', 'error')");
break;
}
}
}
Janus.log("Initializing library");
// Helper method to enumerate devices
Janus.listDevices = function (callback) {
callback = (typeof callback == "function") ? callback : Janus.noop;
if (navigator.mediaDevices) {
getUserMedia({ audio: true, video: true }).then(function (stream) {
navigator.mediaDevices.enumerateDevices().then(function (devices) {
Janus.debug(devices);
callback(devices);
// Get rid of the now useless stream
try {
stream.stop();
} catch (e) { }
try {
var tracks = stream.getTracks();
for (var i in tracks) {
var mst = tracks[i];
if (mst !== null && mst !== undefined)
mst.stop();
}
} catch (e) { }
});
}, function (err) {
Janus.error(err);
callback([]);
});
} else {
Janus.warn("navigator.mediaDevices unavailable");
callback([]);
}
}
// 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.async) {
XHR.onreadystatechange = function () {
if (XHR.readyState != 4)
return;
if (XHR.status !== 200) {
// Got an error?
if (XHR.status === 0)
XHR.status = "error";
params.error(XHR, XHR.status, "");
return;
}
// Got payload
params.success(JSON.parse(XHR.responseText));
};
}
try {
XHR.send(params.data);
if (!params.async) {
if (XHR.status !== 200) {
// Got an error?
if (XHR.status === 0)
XHR.status = "error";
params.error(XHR, XHR.status, "");
return;
}
// Got payload
params.success(JSON.parse(XHR.responseText));
}
} catch (e) {
// Something broke up
params.error(XHR, 'error', '');
};
};
// Detect tab close
window.onbeforeunload = function () {
Janus.log("Closing window");
for (var s in Janus.sessions) {
if (Janus.sessions[s] !== null && Janus.sessions[s] !== undefined &&
Janus.sessions[s].destroyOnUnload) {
Janus.log("Destroying session " + s);
Janus.sessions[s].destroy();
}
}
}
// function addJsList(srcArray) {
// if (!srcArray || !Array.isArray(srcArray) || srcArray.length == 0) {
// options.callback();
// }
// var count = 0;
// addJs(srcArray[count],next);
// function next() {
// count++;
// if (count<srcArray.length) {
// addJs(srcArray[count],next);
// }
// else {
// options.callback();
// }
// }
// }
// function addJs(src,done) {
// if(src === 'adapter.js') {
// if(window.getUserMedia && window.RTCPeerConnection) {
// // Already loaded
// done();
// return;
// }
// }
// var oHead = document.getElementsByTagName('head').item(0);
// var oScript = document.createElement("script");
// oScript.type = "text/javascript";
// oScript.src = src;
// oScript.onload = function() {
// Janus.log("Library " + src + " loaded");
// done();
// }
// oHead.appendChild(oScript);
// }
Janus.initDone = true;
// addJsList(["adapter.js"]); // We may need others in the future
options.callback();
}
};
// Helper method to check whether WebRTC is supported by this browser
Janus.isWebrtcSupported = function () {
return (window.RTCPeerConnection && window.getUserMedia) || (RTCPeerConnection && getUserMedia);
};
// Janus session object
function Janus(gatewayCallbacks) {
// function Janus(gatewayCallbacks) {
if (Janus.initDone === undefined) {
gatewayCallbacks.error("Library not initialized");
return {};
}
// if(!Janus.isWebrtcSupported()) {
// gatewayCallbacks.error("WebRTC not supported by this browser");
// return {};
// }
Janus.log("Library initialized: " + Janus.initDone);
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 (gatewayCallbacks.server === null || gatewayCallbacks.server === undefined) {
gatewayCallbacks.error("Invalid gateway url");
return {};
}
var localstream = null
var websockets = false;
var ws = null;
var wsHandlers = {};
var wsKeepaliveTimeoutId = null;
var servers = null, serversIndex = 0;
var server = gatewayCallbacks.server;
var camera_front = gatewayCallbacks.camera_front;
if (Array.isArray(server)) {
Janus.log("Multiple servers provided (" + server.length + "), will use the first that works");
server = null;
servers = gatewayCallbacks.server;
} 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;
if (iceServers === undefined || iceServers === null)
iceServers = [{ "url": "stun:stun.l.google.com:19302" }];
// Whether IPv6 candidates should be gathered
var ipv6Support = gatewayCallbacks.ipv6;
if (ipv6Support === undefined || ipv6Support === null)
ipv6Support = false;
// Optional max events
var maxev = null;
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);
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.getSessionId = function () { return sessionId; };
this.destroy = function (callbacks) { destroySession(callbacks); };
this.attach = function (callbacks) { console.log('[Janus][this.attach]'); createHandle(callbacks); };
// Private method to create random identifiers (e.g., transaction)
function randomString(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 eventHandler() {
if (sessionId == null)
return;
Janus.debug('Long poll...');
if (!connected) {
Janus.warn("Is the gateway down? (connected=false)");
return;
}
var longpoll = server + "/" + sessionId + "?rid=" + new Date().getTime();
if (maxev !== undefined && maxev !== null)
longpoll = longpoll + "&maxev=" + maxev;
if (token !== null && token !== undefined)
longpoll = longpoll + "&token=" + token;
if (apisecret !== null && apisecret !== undefined)
longpoll = longpoll + "&apisecret=" + apisecret;
Janus.ajax({
type: 'GET',
url: longpoll,
cache: false,
timeout: 1000, // FIXME
success: handleEvent,
sucess: function (json) {
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
Janus.error(textStatus + ": " + errorThrown);
//~ clearTimeout(timeoutTimer);
retries++;
if (retries > 1) {
// Did we just lose the gateway? :-(
connected = false;
gatewayCallbacks.error("Lost connection to the gateway (is it down?)");
return;
}
eventHandler();
},
dataType: "json"
});
}
// Private event handler: this will trigger plugin callbacks, if set
function handleEvent(json) {
retries = 0;
if (!websockets && sessionId !== undefined && sessionId !== null)
setTimeout(eventHandler, 200);
Janus.debug("Got event on session " + sessionId);
if (!websockets && Array.isArray(json)) {
// We got an array: it means we passed a maxev > 1, iterate on all objects
for (var i = 0; i < json.length; i++) {
handleEvent(json[i]);
}
return;
}
if (json["janus"] === "keepalive") {
// Nothing happened
return;
} else if (json["janus"] === "ack") {
// Just an ack, we can probably ignore
var transaction = json["transaction"];
if (transaction !== null && transaction !== undefined) {
var reportSuccess = transactions[transaction];
if (reportSuccess !== null && reportSuccess !== undefined) {
reportSuccess(json);
}
delete transactions[transaction];
}
return;
} else if (json["janus"] === "success") {
// Success!
var transaction = json["transaction"];
if (transaction !== null && transaction !== undefined) {
var reportSuccess = transactions[transaction];
if (reportSuccess !== null && reportSuccess !== undefined) {
reportSuccess(json);
}
delete transactions[transaction];
}
return;
} else if (json["janus"] === "webrtcup") {
// The PeerConnection with the gateway is up! Notify this
var sender = json["sender"];
if (sender === undefined || sender === null) {
Janus.warn("Missing sender...");
return;
}
var pluginHandle = pluginHandles[sender];
if (pluginHandle === undefined || pluginHandle === null) {
Janus.warn("This handle is not attached to this session");
return;
}
pluginHandle.webrtcState(true);
return;
} else if (json["janus"] === "hangup") {
// A plugin asked the core to hangup a PeerConnection on one of our handles
var sender = json["sender"];
if (sender === undefined || sender === null) {
Janus.warn("Missing sender...");
return;
}
var pluginHandle = pluginHandles[sender];
if (pluginHandle === undefined || pluginHandle === null) {
Janus.warn("This handle is not attached to this session");
return;
}
pluginHandle.webrtcState(false);
pluginHandle.hangup();
} else if (json["janus"] === "detached") {
// A plugin asked the core to detach one of our handles
var sender = json["sender"];
if (sender === undefined || sender === null) {
Janus.warn("Missing sender...");
return;
}
var pluginHandle = pluginHandles[sender];
if (pluginHandle === undefined || pluginHandle === null) {
// Don't warn here because destroyHandle causes this situation.
return;
}
pluginHandle.ondetached();
pluginHandle.detach();
} else if (json["janus"] === "media") {
// Media started/stopped flowing
var sender = json["sender"];
if (sender === undefined || sender === null) {
Janus.warn("Missing sender...");
return;
}
var pluginHandle = pluginHandles[sender];
if (pluginHandle === undefined || pluginHandle === null) {
Janus.warn("This handle is not attached to this session");
return;
}
pluginHandle.mediaState(json["type"], json["receiving"]);
} else if (json["janus"] === "error") {
// Oops, something wrong happened
// Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
var transaction = json["transaction"];
if (transaction !== null && transaction !== undefined) {
var reportSuccess = transactions[transaction];
if (reportSuccess !== null && reportSuccess !== undefined) {
reportSuccess(json);
}
delete transactions[transaction];
}
return;
} else if (json["janus"] === "event") {
var sender = json["sender"];
if (sender === undefined || sender === null) {
Janus.warn("Missing sender...");
return;
}
var plugindata = json["plugindata"];
if (plugindata === undefined || plugindata === null) {
Janus.warn("Missing plugindata...");
return;
}
Janus.debug(" -- Event is coming from " + sender + " (" + plugindata["plugin"] + ")");
var data = plugindata["data"];
var pluginHandle = pluginHandles[sender];
if (pluginHandle === undefined || pluginHandle === null) {
Janus.warn("This handle is not attached to this session");
return;
}
var jsep = json["jsep"];
if (jsep !== undefined && jsep !== null) {
Janus.debug("Handling SDP as well...");
Janus.debug(jsep);
}
var callback = pluginHandle.onmessage;
if (callback !== null && callback !== undefined) {
Janus.debug("Notifying application...");
// Send to callback specified when attaching plugin handle
callback(data, jsep);
} else {
// Send to generic callback (?)
Janus.debug("No provided notification callback");
}
} else {
Janus.warn("Unknown message '" + json["janus"] + "'");
}
}
// Private helper to send keep-alive messages on WebSockets
function keepAlive() {
if (server === null || !websockets || !connected)
return;
wsKeepaliveTimeoutId = setTimeout(keepAlive, 30000);
var request = { "janus": "keepalive", "session_id": sessionId, "transaction": randomString(12) };
if (token !== null && token !== undefined)
request["token"] = token;
if (apisecret !== null && apisecret !== undefined)
request["apisecret"] = apisecret;
ws.send(JSON.stringify(request));
}
// Private method to create a session
function createSession(callbacks) {
var transaction = randomString(12);
var request = { "janus": "create", "transaction": transaction };
if (token !== null && token !== undefined)
request["token"] = token;
if (apisecret !== null && apisecret !== undefined)
request["apisecret"] = apisecret;
if (server === null && Array.isArray(servers)) {
// We still need to find a working server from the list we were given
server = servers[serversIndex];
if (server.indexOf("ws") === 0) {
websockets = true;
Alert.alert("Server #" + (serversIndex + 1) + ": trying WebSockets to contact Janus (" + server + ")");
} else {
websockets = false;
Alert.alert("Server #" + (serversIndex + 1) + ": trying REST API to contact Janus (" + server + ")");
}
}
if (websockets) {
console.log('[Janus][isWS]')
ws = new WebSocket(server, 'janus-protocol');
wsHandlers = {
'error': function () {
console.log('[Janus][wsHandlers][error][args]', arguments);
console.log("Error connecting to the Janus WebSockets server... " + server);
if (Array.isArray(servers)) {
serversIndex++;
if (serversIndex == servers.length) {
// 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?");
return;
}
// Let's try the next server
server = null;
setTimeout(function () {
createSession(callbacks);
}, 200);
return;
}
callbacks.error("Error connecting to the Janus WebSockets server: Is the gateway down?");
},
'open': function () {
// We need to be notified about the success
console.log('[Janus][wsHandlers][open]')
transactions[transaction] = function (json) {
if (json["janus"] !== "success") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
callbacks.error(json["error"].reason);
return;
}
wsKeepaliveTimeoutId = setTimeout(keepAlive, 30000);
connected = true;
sessionId = json.data["id"];
Janus.log("Created session: " + sessionId);
Janus.sessions[sessionId] = that;
callbacks.success();
};
ws.send(JSON.stringify(request));
},
'message': function (event) {
console.log('[Janus][wsHandlers][message]')
handleEvent(JSON.parse(event.data));
},
'close': function () {
console.log('[Janus][wsHandlers][close]')
if (server === null || !connected) {
return;
}
connected = false;
// FIXME What if this is called when the page is closed?
gatewayCallbacks.error("Lost connection to the gateway (is it down?)");
}
};
for (var eventName in wsHandlers) {
ws.addEventListener(eventName, wsHandlers[eventName]);
}
return;
}
Janus.ajax({
type: 'POST',
url: server,
cache: false,
contentType: "application/json",
data: JSON.stringify(request),
success: function (json) {
if (json["janus"] !== "success") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
callbacks.error(json["error"].reason);
return;
}
connected = true;
sessionId = json.data["id"];
Janus.log("Created session: " + sessionId);
Janus.sessions[sessionId] = that;
eventHandler();
callbacks.success();
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log("*********error***********")
console.log(XMLHttpRequest)
console.log(textStatus)
console.log(errorThrown)
// Janus.error(textStatus + ": " + errorThrown); // FIXME
if (Array.isArray(servers)) {
serversIndex++;
if (serversIndex == servers.length) {
// 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?");
return;
}
// Let's try the next server
server = null;
setTimeout(function () { createSession(callbacks); }, 200);
return;
}
if (errorThrown === "")
callbacks.error(textStatus + ": Is the gateway down?");
else
callbacks.error(textStatus + ": " + errorThrown);
},
dataType: "json"
});
}
// Private method to destroy a session
function destroySession(callbacks, syncRequest) {
syncRequest = (syncRequest === true);
Janus.log("Destroying session " + sessionId + " (sync=" + syncRequest + ")");
callbacks = callbacks || {};
// FIXME This method triggers a success even when we fail
callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
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];
// Destroy all handles first
for (var ph in pluginHandles) {
var phv = pluginHandles[ph];
Janus.log("Destroying handle " + phv.id + " (" + phv.plugin + ")");
destroyHandle(phv.id, null, syncRequest);
}
// Ok, go on
var request = { "janus": "destroy", "transaction": 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;
}
console.log("sessionId")
console.log(sessionId)
Janus.ajax({
type: 'POST',
url: server + "/" + sessionId,
async: true, // Sometimes we need false here, or destroying in onbeforeunload won't work
cache: false,
contentType: "application/json",
data: JSON.stringify(request),
success: function (json) {
Janus.log("Destroyed session:");
sessionId = null;
connected = false;
if (json["janus"] !== "success") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
}
callbacks.success();
gatewayCallbacks.destroyed();
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
Janus.error(textStatus + ": " + errorThrown); // FIXME
// Reset everything anyway
sessionId = null;
connected = false;
callbacks.success();
gatewayCallbacks.destroyed();
},
dataType: "json"
});
}
// Private method to create a plugin handle
function createHandle(callbacks) {
console.log('[Janus][createHandle]')
callbacks = callbacks || {};
callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
callbacks.consentDialog = (typeof callbacks.consentDialog == "function") ? callbacks.consentDialog : Janus.noop;
callbacks.iceState = (typeof callbacks.iceState == "function") ? callbacks.iceState : Janus.noop;
callbacks.mediaState = (typeof callbacks.mediaState == "function") ? callbacks.mediaState : Janus.noop;
callbacks.webrtcState = (typeof callbacks.webrtcState == "function") ? callbacks.webrtcState : Janus.noop;
callbacks.onmessage = (typeof callbacks.onmessage == "function") ? callbacks.onmessage : Janus.noop;
callbacks.onlocalstream = (typeof callbacks.onlocalstream == "function") ? callbacks.onlocalstream : Janus.noop;
callbacks.onremotestream = (typeof callbacks.onremotestream == "function") ? callbacks.onremotestream : Janus.noop;
callbacks.ondata = (typeof callbacks.ondata == "function") ? callbacks.ondata : Janus.noop;
callbacks.ondataopen = (typeof callbacks.ondataopen == "function") ? callbacks.ondataopen : Janus.noop;
callbacks.oncleanup = (typeof callbacks.oncleanup == "function") ? callbacks.oncleanup : Janus.noop;
callbacks.ondetached = (typeof callbacks.ondetached == "function") ? callbacks.ondetached : Janus.noop;
if (!connected) {
Janus.warn("Is the gateway down? (connected=false)");
callbacks.error("Is the gateway down? (connected=false)");
return;
}
var plugin = callbacks.plugin;
if (plugin === undefined || plugin === null) {
Janus.error("Invalid plugin");
callbacks.error("Invalid plugin");
return;
}
var transaction = randomString(12);
var request = { "janus": "attach", "plugin": plugin, "transaction": transaction };
if (token !== null && token !== undefined)
request["token"] = token;
if (apisecret !== null && apisecret !== undefined)
request["apisecret"] = apisecret;
if (websockets) {
transactions[transaction] = function (json) {
if (json["janus"] !== "success") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
callbacks.error("Ooops: " + json["error"].code + " " + json["error"].reason);
return;
}
var handleId = json.data["id"];
console.log('[Janus][websockets]')
Janus.log("Created handle: " + handleId);
var pluginHandle =
{
session: that,
plugin: plugin,
id: handleId,
webrtcStuff: {
started: false,
myStream: null,
streamExternal: false,
remoteStream: null,
mySdp: null,
pc: null,
dataChannel: null,
dtmfSender: null,
trickle: true,
iceDone: false,
sdpSent: false,
volume: {
value: null,
timer: null
},
bitrate: {
value: null,
bsnow: null,
bsbefore: null,
tsnow: null,
tsbefore: null,
timer: null
}
},
getId: function () { return handleId; },
getPlugin: function () { return plugin; },
getVolume: function () { return getVolume(handleId); },
isAudioMuted: function () { return isMuted(handleId, false); },
muteAudio: function () { return mute(handleId, false, true); },
unmuteAudio: function () { return mute(handleId, false, false); },
isVideoMuted: function () { return isMuted(handleId, true); },
muteVideo: function () { return mute(handleId, true, true); },
unmuteVideo: function () { return mute(handleId, true, false); },
getBitrate: function () { return getBitrate(handleId); },
changeLocalCamera: function (isFront) { return changeLocalCamera(handleId, isFront); },
send: function (callbacks) { sendMessage(handleId, callbacks); },
data: function (callbacks) { sendData(handleId, callbacks); },
dtmf: function (callbacks) { sendDtmf(handleId, callbacks); },
consentDialog: callbacks.consentDialog,
iceState: callbacks.iceState,
mediaState: callbacks.mediaState,
webrtcState: callbacks.webrtcState,
onmessage: callbacks.onmessage,
createOffer: function (callbacks) { prepareWebrtc(handleId, callbacks); },
createAnswer: function (callbacks) { prepareWebrtc(handleId, callbacks); },
handleRemoteJsep: function (callbacks) { prepareWebrtcPeer(handleId, callbacks); },
onlocalstream: callbacks.onlocalstream,
onremotestream: callbacks.onremotestream,
ondata: callbacks.ondata,
ondataopen: callbacks.ondataopen,
oncleanup: callbacks.oncleanup,
ondetached: callbacks.ondetached,
hangup: function (sendRequest) { cleanupWebrtc(handleId, sendRequest === true); },
detach: function (callbacks) { destroyHandle(handleId, callbacks); }
}
pluginHandles[handleId] = pluginHandle;
console.log('[Janus][websockets][pluginHandles[handleId]]')
callbacks.success(pluginHandle);
};
console.log('[Janus][websockets][ws]')
request["session_id"] = sessionId;
ws.send(JSON.stringify(request));
return;
}
console.log('[Janus][ajax]')
Janus.ajax({
type: 'POST',
url: server + "/" + sessionId,
cache: false,
contentType: "application/json",
data: JSON.stringify(request),
success: function (json) {
if (json["janus"] !== "success") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
callbacks.error("Ooops: " + json["error"].code + " " + json["error"].reason);
return;
}
var handleId = json.data["id"];
Janus.log("Created handle: " + handleId);
var pluginHandle =
{
session: that,
plugin: plugin,
id: handleId,
webrtcStuff: {
started: false,
myStream: null,
streamExternal: false,
remoteStream: null,
mySdp: null,
pc: null,
dataChannel: null,
dtmfSender: null,
trickle: true,
iceDone: false,
sdpSent: false,
volume: {
value: null,
timer: null
},
bitrate: {
value: null,
bsnow: null,
bsbefore: null,
tsnow: null,
tsbefore: null,
timer: null
}
},
getId: function () { return handleId; },
getPlugin: function () { return plugin; },
getVolume: function () { return getVolume(handleId); },
isAudioMuted: function () { return isMuted(handleId, false); },
muteAudio: function () { return mute(handleId, false, true); },
unmuteAudio: function () { return mute(handleId, false, false); },
isVideoMuted: function () { return isMuted(handleId, true); },
muteVideo: function () { return mute(handleId, true, true); },
unmuteVideo: function () { return mute(handleId, true, false); },
getBitrate: function () { return getBitrate(handleId); },
changeLocalCamera: function (isFront) { return changeLocalCamera(handleId, isFront); },
send: function (callbacks) { sendMessage(handleId, callbacks); },
data: function (callbacks) { sendData(handleId, callbacks); },
dtmf: function (callbacks) { sendDtmf(handleId, callbacks); },
consentDialog: callbacks.consentDialog,
iceState: callbacks.iceState,
mediaState: callbacks.mediaState,
webrtcState: callbacks.webrtcState,
onmessage: callbacks.onmessage,
createOffer: function (callbacks) { prepareWebrtc(handleId, callbacks); },
createAnswer: function (callbacks) { prepareWebrtc(handleId, callbacks); },
handleRemoteJsep: function (callbacks) { prepareWebrtcPeer(handleId, callbacks); },
onlocalstream: callbacks.onlocalstream,
onremotestream: callbacks.onremotestream,
ondata: callbacks.ondata,
ondataopen: callbacks.ondataopen,
oncleanup: callbacks.oncleanup,
ondetached: callbacks.ondetached,
hangup: function (sendRequest) { cleanupWebrtc(handleId, sendRequest === true); },
detach: function (callbacks) { destroyHandle(handleId, callbacks); }
}
pluginHandles[handleId] = pluginHandle;
console.log('[Janus][ajax][pluginHandles[handleId]]')
callbacks.success(pluginHandle);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
Janus.error(textStatus + ": " + errorThrown); // FIXME
},
dataType: "json"
});
}
// Private method to send a message
function sendMessage(handleId, callbacks) {
callbacks = callbacks || {};
callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
if (!connected) {
Janus.warn("Is the gateway down? (connected=false)");
callbacks.error("Is the gateway down? (connected=false)");
return;
}
var message = callbacks.message;
var jsep = callbacks.jsep;
var transaction = randomString(12);
var request = { "janus": "message", "body": message, "transaction": transaction };
if (token !== null && token !== undefined)
request["token"] = token;
if (apisecret !== null && apisecret !== undefined)
request["apisecret"] = apisecret;
if (jsep !== null && jsep !== undefined)
request.jsep = jsep;
Janus.debug("Sending message to plugin (handle=" + handleId + "):");
if (websockets) {
request["session_id"] = sessionId;
request["handle_id"] = handleId;
transactions[transaction] = function (json) {
Janus.debug("Message sent!");
Janus.debug(json);
if (json["janus"] === "success") {
// We got a success, must have been a synchronous transaction
var plugindata = json["plugindata"];
if (plugindata === undefined || plugindata === null) {
Janus.warn("Request succeeded, but missing plugindata...");
callbacks.success();
return;
}
Janus.log("Synchronous transaction successful (" + plugindata["plugin"] + ")");
var data = plugindata["data"];
Janus.debug(data);
callbacks.success(data);
return;
} else if (json["janus"] !== "ack") {
// Not a success and not an ack, must be an error
if (json["error"] !== undefined && json["error"] !== null) {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
callbacks.error(json["error"].code + " " + json["error"].reason);
} else {
Janus.error("Unknown error"); // FIXME
callbacks.error("Unknown error");
}
return;
}
// If we got here, the plugin decided to handle the request asynchronously
callbacks.success();
};
ws.send(JSON.stringify(request));
return;
}
Janus.ajax({
type: 'POST',
url: server + "/" + sessionId + "/" + handleId,
cache: false,
contentType: "application/json",
data: JSON.stringify(request),
success: function (json) {
Janus.debug("Message sent!");
Janus.debug(json);
if (json["janus"] === "success") {
// We got a success, must have been a synchronous transaction
var plugindata = json["plugindata"];
if (plugindata === undefined || plugindata === null) {
Janus.warn("Request succeeded, but missing plugindata...");
callbacks.success();
return;
}
Janus.log("Synchronous transaction successful (" + plugindata["plugin"] + ")");
var data = plugindata["data"];
Janus.debug(data);
callbacks.success(data);
return;
} else if (json["janus"] !== "ack") {
// Not a success and not an ack, must be an error
if (json["error"] !== undefined && json["error"] !== null) {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
callbacks.error(json["error"].code + " " + json["error"].reason);
} else {
Janus.error("Unknown error"); // FIXME
callbacks.error("Unknown error");
}
return;
}
// If we got here, the plugin decided to handle the request asynchronously
callbacks.success();
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
Janus.error(textStatus + ": " + errorThrown); // FIXME
callbacks.error(textStatus + ": " + errorThrown);
},
dataType: "json"
});
}
// Private method to send a trickle candidate
function sendTrickleCandidate(handleId, candidate) {
if (!connected) {
Janus.warn("Is the gateway down? (connected=false)");
return;
}
var request = { "janus": "trickle", "candidate": candidate, "transaction": randomString(12) };
if (token !== null && token !== undefined)
request["token"] = token;
if (apisecret !== null && apisecret !== undefined)
request["apisecret"] = apisecret;
Janus.debug("Sending trickle candidate (handle=" + handleId + "):");
Janus.debug(request);
if (websockets) {
request["session_id"] = sessionId;
request["handle_id"] = handleId;
ws.send(JSON.stringify(request));
return;
}
Janus.ajax({
type: 'POST',
url: server + "/" + sessionId + "/" + handleId,
cache: false,
contentType: "application/json",
data: JSON.stringify(request),
success: function (json) {
Janus.debug("Candidate sent!");
Janus.debug(json);
if (json["janus"] !== "ack") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
return;
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
Janus.error(textStatus + ": " + errorThrown); // FIXME
},
dataType: "json"
});
}
// 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 === null || pluginHandle === undefined ||
pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
Janus.warn("Invalid handle");
callbacks.error("Invalid handle");
return;
}
var config = pluginHandle.webrtcStuff;
var text = callbacks.text;
if (text === null || text === undefined) {
Janus.warn("Invalid text");
callbacks.error("Invalid text");
return;
}
Janus.log("Sending string on data channel: " + text);
config.dataChannel.send(text);
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 === null || pluginHandle === undefined ||
pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
Janus.warn("Invalid handle");
callbacks.error("Invalid handle");
return;
}
var config = pluginHandle.webrtcStuff;
if (config.dtmfSender === null || config.dtmfSender === undefined) {
// Create the DTMF sender, if possible
if (config.myStream !== undefined && config.myStream !== null) {
var tracks = config.myStream.getAudioTracks();
if (tracks !== null && tracks !== undefined && tracks.length > 0) {
var local_audio_track = tracks[0];
config.dtmfSender = config.pc.createDTMFSender(local_audio_track);
Janus.log("Created DTMF Sender");
config.dtmfSender.ontonechange = function (tone) { Janus.debug("Sent DTMF tone: " + tone.tone); };
}
}
if (config.dtmfSender === null || config.dtmfSender === undefined) {
Janus.warn("Invalid DTMF configuration");
callbacks.error("Invalid DTMF configuration");
return;
}
}
var dtmf = callbacks.dtmf;
if (dtmf === null || dtmf === undefined) {
Janus.warn("Invalid DTMF parameters");
callbacks.error("Invalid DTMF parameters");
return;
}
var tones = dtmf.tones;
if (tones === null || tones === undefined) {
Janus.warn("Invalid DTMF string");
callbacks.error("Invalid DTMF string");
return;
}
var duration = dtmf.duration;
if (duration === null || duration === undefined)
duration = 500; // We choose 500ms as the default duration for a tone
var gap = dtmf.gap;
if (gap === null || gap === undefined)
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);
}
// Private method to destroy a plugin handle
function destroyHandle(handleId, callbacks, syncRequest) {
syncRequest = false;
Janus.log("Destroying handle " + handleId + " (sync=" + syncRequest + ")");
callbacks = callbacks || {};
callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
cleanupWebrtc(handleId);
if (!connected) {
Janus.warn("Is the gateway down? (connected=false)");
callbacks.error("Is the gateway down? (connected=false)");
return;
}
var request = { "janus": "detach", "transaction": randomString(12) };
if (token !== null && token !== undefined)
request["token"] = token;
if (apisecret !== null && apisecret !== undefined)
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.ajax({
type: 'POST',
url: server + "/" + sessionId + "/" + handleId,
async: true, // Sometimes we need false here, or destroying in onbeforeunload won't work
cache: false,
contentType: "application/json",
data: JSON.stringify(request),
success: function (json) {
Janus.log("Destroyed handle:");
Janus.debug(json);
if (json["janus"] !== "success") {
Janus.log("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
}
delete pluginHandles[handleId];
callbacks.success();
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
Janus.error(textStatus + ": " + errorThrown); // FIXME
// We cleanup anyway
delete pluginHandles[handleId];
callbacks.success();
},
dataType: "json"
});
}
// WebRTC stuff
function changeLocalCamera(handleId) {
localstream._tracks[1]._switchCamera()
}
function streamsDone(handleId, jsep, media, callbacks, stream) {
var pluginHandle = pluginHandles[handleId];
if (pluginHandle === null || pluginHandle === undefined ||
pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
Janus.warn("Invalid handle");
callbacks.error("Invalid handle");
return;
}
var config = pluginHandle.webrtcStuff;
console.log("streamsDone:", stream)
config.myStream = stream;
var pc_config = { "iceServers": iceServers };
console.log("[Janus][pc_config]")
//~ var pc_constraints = {'mandatory': {'MozDontOfferDataChannel':true}};
var pc_constraints = {
"optional": [{ "DtlsSrtpKeyAgreement": true }]
};
if (ipv6Support === true) {
// FIXME This is only supported in Chrome right now
// For support in Firefox track this: https://bugzilla.mozilla.org/show_bug.cgi?id=797262
pc_constraints.optional.push({ "googIPv6": true });
}
Janus.log("Creating PeerConnection");
Janus.debug('[pc_constraints]');
config.pc = new RTCPeerConnection(pc_config, pc_constraints);
if (config.pc.getStats) { // FIXME
config.volume.value = 0;
config.bitrate.value = "0 kbits/sec";
}
Jan