# janus 核心类
开始判断是否以及初始化,如果没有初始化直接返回,初始化之后,对传入的回调函数进行合理判断
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 server url");
return {};
}
# createSession
创建一个连接,根据传递的URL来选择协议(ws/http)
根据事件监听来处理通道建立之后相关事件触发:
- open: 会建立一个数据管理,在其中会触发 传入参数 success 函数, 并发送一条消息
点击查看源码
function createSession(callbacks) {
var transaction = Janus.randomString(12);
var request = { "janus": "create", "transaction": transaction };
if(callbacks["reconnect"]) {
// We're reconnecting, claim the session
connected = false;
request["janus"] = "claim";
request["session_id"] = sessionId;
// If we were using websockets, ignore the old connection
if(ws) {
ws.onopen = null;
ws.onerror = null;
ws.onclose = null;
if(wsKeepaliveTimeoutId) {
clearTimeout(wsKeepaliveTimeoutId);
wsKeepaliveTimeoutId = null;
}
}
}
if(token)
request["token"] = token;
if(apisecret)
request["apisecret"] = apisecret;
if(!server && Janus.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;
Janus.log("Server #" + (serversIndex+1) + ": trying WebSockets to contact Janus (" + server + ")");
} else {
websockets = false;
Janus.log("Server #" + (serversIndex+1) + ": trying REST API to contact Janus (" + server + ")");
}
}
if(websockets) {
ws = Janus.newWebSocket(server, 'janus-protocol');
wsHandlers = {
'error': function() {
Janus.error("Error connecting to the Janus WebSockets server... " + server);
if (Janus.isArray(servers) && !callbacks["reconnect"]) {
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 server 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 server down?");
},
'open': function() {
// We need to be notified about the success
transactions[transaction] = function(json) {
Janus.debug(json);
if (json["janus"] !== "success") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
callbacks.error(json["error"].reason);
return;
}
wsKeepaliveTimeoutId = setTimeout(keepAlive, keepAlivePeriod);
connected = true;
sessionId = json["session_id"] ? json["session_id"] : json.data["id"];
if(callbacks["reconnect"]) {
Janus.log("Claimed session: " + sessionId);
} else {
Janus.log("Created session: " + sessionId);
}
Janus.sessions[sessionId] = that;
callbacks.success();
};
ws.send(JSON.stringify(request));
},
'message': function(event) {
handleEvent(JSON.parse(event.data));
},
'close': function() {
if (!server || !connected) {
return;
}
connected = false;
// FIXME What if this is called when the page is closed?
gatewayCallbacks.error("Lost connection to the server (is it down?)");
}
};
for(var eventName in wsHandlers) {
ws.addEventListener(eventName, wsHandlers[eventName]);
}
return;
}
Janus.httpAPICall(server, {
verb: 'POST',
withCredentials: withCredentials,
body: request,
success: function(json) {
Janus.debug(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["session_id"] ? json["session_id"] : json.data["id"];
if(callbacks["reconnect"]) {
Janus.log("Claimed session: " + sessionId);
} else {
Janus.log("Created session: " + sessionId);
}
Janus.sessions[sessionId] = that;
eventHandler();
callbacks.success();
},
error: function(textStatus, errorThrown) {
Janus.error(textStatus + ":", errorThrown); // FIXME
if(Janus.isArray(servers) && !callbacks["reconnect"]) {
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 server down?");
return;
}
// Let's try the next server
server = null;
setTimeout(function() { createSession(callbacks); }, 200);
return;
}
if(errorThrown === "")
callbacks.error(textStatus + ": Is the server down?");
else
callbacks.error(textStatus + ": " + errorThrown);
}
});
}
# attach
this.attach = function(callbacks) { createHandle(callbacks); };
# 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.slowLink = (typeof callbacks.slowLink == "function") ? callbacks.slowLink : 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;
拿到callbacks中 plugin 参数,并判断合法性
if(!plugin) {
Janus.error("Invalid plugin");
callbacks.error("Invalid plugin");
return;
}
pluginHandler变量
var pluginHandle =
{
session : that,
plugin : plugin,
id : handleId,
token : handleToken,
detached : false,
webrtcStuff : {
started : false,
myStream : null,
streamExternal : false,
remoteStream : null,
mySdp : null,
mediaConstraints : null,
pc : null,
dataChannel : {},
dtmfSender : null,
trickle : true,
iceDone : 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, true); },
getRemoteVolume : function() { return getVolume(handleId, true); },
getLocalVolume : function() { return getVolume(handleId, false); },
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); },
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,
slowLink : callbacks.slowLink,
onmessage : callbacks.onmessage,
createOffer : function(callbacks) { prepareWebrtc(handleId, true, callbacks); },
createAnswer : function(callbacks) { prepareWebrtc(handleId, false, 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); }
};
点击查看源码
function createHandle(callbacks) {
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.slowLink = (typeof callbacks.slowLink == "function") ? callbacks.slowLink : 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 server down? (connected=false)");
callbacks.error("Is the server down? (connected=false)");
return;
}
var plugin = callbacks.plugin;
if(!plugin) {
Janus.error("Invalid plugin");
callbacks.error("Invalid plugin");
return;
}
var opaqueId = callbacks.opaqueId;
var loopIndex = callbacks.loopIndex;
var handleToken = callbacks.token ? callbacks.token : token;
var transaction = Janus.randomString(12);
var request = { "janus": "attach", "plugin": plugin, "opaque_id": opaqueId, "loop_index": loopIndex, "transaction": transaction };
if(handleToken)
request["token"] = handleToken;
if(apisecret)
request["apisecret"] = apisecret;
if(websockets) {
transactions[transaction] = function(json) {
Janus.debug(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,
token : handleToken,
detached : false,
webrtcStuff : {
started : false,
myStream : null,
streamExternal : false,
remoteStream : null,
mySdp : null,
mediaConstraints : null,
pc : null,
dataChannel : {},
dtmfSender : null,
trickle : true,
iceDone : 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, true); },
getRemoteVolume : function() { return getVolume(handleId, true); },
getLocalVolume : function() { return getVolume(handleId, false); },
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); },
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,
slowLink : callbacks.slowLink,
onmessage : callbacks.onmessage,
createOffer : function(callbacks) { prepareWebrtc(handleId, true, callbacks); },
createAnswer : function(callbacks) { prepareWebrtc(handleId, false, 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;
callbacks.success(pluginHandle);
};
request["session_id"] = sessionId;
ws.send(JSON.stringify(request));
return;
}
Janus.httpAPICall(server + "/" + sessionId, {
verb: 'POST',
withCredentials: withCredentials,
body: request,
success: function(json) {
Janus.debug(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,
token : handleToken,
detached : false,
webrtcStuff : {
started : false,
myStream : null,
streamExternal : false,
remoteStream : null,
mySdp : null,
mediaConstraints : null,
pc : null,
dataChannel : {},
dtmfSender : null,
trickle : true,
iceDone : 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, true); },
getRemoteVolume : function() { return getVolume(handleId, true); },
getLocalVolume : function() { return getVolume(handleId, false); },
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); },
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,
slowLink : callbacks.slowLink,
onmessage : callbacks.onmessage,
createOffer : function(callbacks) { prepareWebrtc(handleId, true, callbacks); },
createAnswer : function(callbacks) { prepareWebrtc(handleId, false, 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;
callbacks.success(pluginHandle);
},
error: function(textStatus, errorThrown) {
Janus.error(textStatus + ":", errorThrown); // FIXME
if(errorThrown === "")
callbacks.error(textStatus + ": Is the server down?");
else
callbacks.error(textStatus + ": " + errorThrown);
}
});
}
# 源码
点击查看源码
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 server url");
return {};
}
var websockets = false;
var ws = null;
var wsHandlers = {};
var wsKeepaliveTimeoutId = null;
var servers = null, 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;
if(iceServers === undefined || iceServers === null)
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;
if(ipv6Support === undefined || ipv6Support === null)
ipv6Support = false;
// 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.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 !== undefined && maxev !== null)
longpoll = longpoll + "&maxev=" + maxev;
if(token !== null && token !== undefined)
longpoll = longpoll + "&token=" + encodeURIComponent(token);
if(apisecret !== null && apisecret !== undefined)
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<json.length; i++) {
handleEvent(json[i], true);
}
return;
}
if(json["rtcgw"] === "keepalive") {
// Nothing happened
Janus.vdebug("Got a keepalive on session " + sessionId);
return;
} else if(json["rtcgw"] === "ack") {
// Just an ack, we can probably ignore
Janus.debug("Got an ack on session " + sessionId);
Janus.debug(json);
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["rtcgw"] === "success") {
// Success!
Janus.debug("Got a success on session " + sessionId);
Janus.debug(json);
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["rtcgw"] === "trickle") {
// We got a trickle candidate from Janus
var sender = json["sender"];
if(sender === undefined || sender === null) {
Janus.warn("Missing sender...");
return;
}
var pluginHandle = pluginHandles[sender];
if(pluginHandle === undefined || pluginHandle === null) {
Janus.debug("This handle is not attached to this session");
return;
}
var candidate = json["candidate"];
Janus.debug("Got a trickled candidate on session " + sessionId);
Janus.debug(candidate);
var config = pluginHandle.webrtcStuff;
if(config.pc && config.remoteSdp) {
// Add candidate right now
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);
}
} else {
// We didn't do setRemoteDescription (trickle got here before the offer?)
Janus.debug("We didn't do setRemoteDescription (trickle got here before the offer?), caching candidate");
if(!config.candidates)
config.candidates = [];
config.candidates.push(candidate);
Janus.debug(config.candidates);
}
} else if(json["rtcgw"] === "webrtcup") {
// The PeerConnection with the server is up! Notify this
Janus.debug("Got a webrtcup event on session " + sessionId);
Janus.debug(json);
var sender = json["sender"];
if(sender === undefined || sender === null) {
Janus.warn("Missing sender...");
return;
}
var pluginHandle = pluginHandles[sender];
if(pluginHandle === undefined || pluginHandle === null) {
Janus.debug("This handle is not attached to this session");
return;
}
pluginHandle.webrtcState(true);
return;
} else if(json["rtcgw"] === "hangup") {
// 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(json);
var sender = json["sender"];
if(sender === undefined || sender === null) {
Janus.warn("Missing sender...");
return;
}
var pluginHandle = pluginHandles[sender];
if(pluginHandle === undefined || pluginHandle === null) {
Janus.debug("This handle is not attached to this session");
return;
}
pluginHandle.webrtcState(false, json["reason"]);
pluginHandle.hangup();
} else if(json["rtcgw"] === "detached") {
// A plugin asked the core to detach one of our handles
Janus.debug("Got a detached event on session " + sessionId);
Janus.debug(json);
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.detached = true;
pluginHandle.ondetached();
pluginHandle.detach();
} else if(json["rtcgw"] === "media") {
// Media started/stopped flowing
Janus.debug("Got a media event on session " + sessionId);
Janus.debug(json);
var sender = json["sender"];
if(sender === undefined || sender === null) {
Janus.warn("Missing sender...");
return;
}
var pluginHandle = pluginHandles[sender];
if(pluginHandle === undefined || pluginHandle === null) {
Janus.debug("This handle is not attached to this session");
return;
}
pluginHandle.mediaState(json["type"], json["receiving"]);
} else if(json["rtcgw"] === "slowlink") {
Janus.debug("Got a slowlink event on session " + sessionId);
Janus.debug(json);
// Trouble uplink or downlink
var sender = json["sender"];
if(sender === undefined || sender === null) {
Janus.warn("Missing sender...");
return;
}
var pluginHandle = pluginHandles[sender];
if(pluginHandle === undefined || pluginHandle === null) {
Janus.debug("This handle is not attached to this session");
return;
}
pluginHandle.slowLink(json["uplink"], json["lost"]);
} else if(json["rtcgw"] === "error") {
// Oops, something wrong happened
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
Janus.debug(json);
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["rtcgw"] === "event") {
Janus.debug("Got a plugin event on session " + sessionId);
Janus.debug(json);
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"];
Janus.debug(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 if(json["rtcgw"] === "timeout") {
Janus.error("Timeout on session " + sessionId);
Janus.debug(json);
if (websockets) {
ws.close(3504, "Gateway timeout");
}
return;
} else {
Janus.warn("Unknown message/event '" + json["rtcgw"] + "' on session " + sessionId);
Janus.debug(json);
}
}
// Private helper to send keep-alive messages on WebSockets
function keepAlive() {
if(server === null || !websockets || !connected)
return;
wsKeepaliveTimeoutId = setTimeout(keepAlive, keepAlivePeriod);
var request = { "rtcgw": "keepalive", "session_id": sessionId, "transaction": Janus.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 = Janus.randomString(12);
// console.log("jannus create_token",stream);
var request = {
"rtcgw": "create",
"transaction": transaction,
"token":window.EZUIKit.opt.stream,
"device": window.EZUIKit.opt.deviceSerial,
"channel": window.EZUIKit.opt.channelNo,
};
if(callbacks["reconnect"]) {
// We're reconnecting, claim the session
connected = false;
request["rtcgw"] = "claim";
request["session_id"] = sessionId;
// If we were using websockets, ignore the old connection
if(ws) {
ws.onopen = null;
ws.onerror = null;
ws.onclose = null;
if(wsKeepaliveTimeoutId) {
clearTimeout(wsKeepaliveTimeoutId);
wsKeepaliveTimeoutId = null;
}
}
}
if(token !== null && token !== undefined)
request["token"] = token;
if(apisecret !== null && apisecret !== undefined)
request["apisecret"] = apisecret;
if(server === null && Janus.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;
Janus.log("Server #" + (serversIndex+1) + ": trying WebSockets to contact Janus (" + server + ")");
} else {
websockets = false;
Janus.log("Server #" + (serversIndex+1) + ": trying REST API to contact Janus (" + server + ")");
}
}
if(websockets) {
ws = Janus.newWebSocket(server, 'rtcgw-protocol');
wsHandlers = {
'error': function() {
Janus.error("Error connecting to the Janus WebSockets server... " + server);
if (Janus.isArray(servers) && !callbacks["reconnect"]) {
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 server 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 server down?");
},
'open': function() {
// We need to be notified about the success
transactions[transaction] = function(json) {
Janus.debug(json);
if (json["rtcgw"] !== "success") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
callbacks.error(json["error"].reason);
return;
}
wsKeepaliveTimeoutId = setTimeout(keepAlive, keepAlivePeriod);
connected = true;
sessionId = json["session_id"] ? json["session_id"] : json.data["id"];
if(callbacks["reconnect"]) {
Janus.log("Claimed session: " + sessionId);
} else {
Janus.log("Created session: " + sessionId);
}
Janus.sessions[sessionId] = that;
callbacks.success();
};
ws.send(JSON.stringify(request));
},
'message': function(event) {
handleEvent(JSON.parse(event.data));
},
'close': function() {
if (server === null || !connected) {
return;
}
connected = false;
// FIXME What if this is called when the page is closed?
gatewayCallbacks.error("Lost connection to the server (is it down?)");
}
};
for(var eventName in wsHandlers) {
ws.addEventListener(eventName, wsHandlers[eventName]);
}
return;
}
Janus.httpAPICall(server, {
verb: 'POST',
withCredentials: withCredentials,
body: request,
success: function(json) {
Janus.debug(json);
if(json["rtcgw"] !== "success") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
callbacks.error(json["error"].reason);
return;
}
connected = true;
sessionId = json["session_id"] ? json["session_id"] : json.data["id"];
if(callbacks["reconnect"]) {
Janus.log("Claimed session: " + sessionId);
} else {
Janus.log("Created session: " + sessionId);
}
Janus.sessions[sessionId] = that;
eventHandler();
callbacks.success();
},
error: function(textStatus, errorThrown) {
Janus.error(textStatus + ":", errorThrown); // FIXME
if(Janus.isArray(servers) && !callbacks["reconnect"]) {
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 server down?");
return;
}
// Let's try the next server
server = null;
setTimeout(function() { createSession(callbacks); }, 200);
return;
}
if(errorThrown === "")
callbacks.error(textStatus + ": Is the server down?");
else
callbacks.error(textStatus + ": " + errorThrown);
}
});
}
// Private method to destroy a session
function destroySession(callbacks) {
callbacks = callbacks || {};
// FIXME This method triggers a success even when we fail
callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
var asyncRequest = true;
if(callbacks.asyncRequest !== undefined && callbacks.asyncRequest !== null)
asyncRequest = (callbacks.asyncRequest === true);
var notifyDestroyed = true;
if(callbacks.notifyDestroyed !== undefined && callbacks.notifyDestroyed !== null)
notifyDestroyed = (callbacks.notifyDestroyed === true);
var cleanupHandles = false;
if(callbacks.cleanupHandles !== undefined && callbacks.cleanupHandles !== null)
cleanupHandles = (callbacks.cleanupHandles === true);
Janus.log("Destroying session " + sessionId + " (async=" + asyncRequest + ")");
if(!connected) {
Janus.warn("Is the server down? (connected=false)");
callbacks.success();
return;
}
if(sessionId === undefined || sessionId === null) {
Janus.warn("No session to destroy");
callbacks.success();
if(notifyDestroyed)
gatewayCallbacks.destroyed();
return;
}
if(cleanupHandles) {
for(var handleId in pluginHandles)
destroyHandle(handleId, { noRequest: true });
}
// No need to destroy all handles first, Janus will do that itself
var request = { "rtcgw": "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);
}
ws.close();
};
var onUnbindMessage = function(event){
var data = JSON.parse(event.data);
if(data.session_id == request.session_id && data.transaction == request.transaction) {
unbindWebSocket();
callbacks.success();
if(notifyDestroyed)
gatewayCallbacks.destroyed();
}
};
var onUnbindError = function(event) {
unbindWebSocket();
callbacks.error("Failed to destroy the server: Is the server down?");
if(notifyDestroyed)
gatewayCallbacks.destroyed();
};
ws.addEventListener('message', onUnbindMessage);
ws.addEventListener('error', onUnbindError);
ws.send(JSON.stringify(request));
return;
}
Janus.httpAPICall(server + "/" + sessionId, {
verb: 'POST',
async: asyncRequest, // Sometimes we need false here, or destroying in onbeforeunload won't work
withCredentials: withCredentials,
body: request,
success: function(json) {
Janus.log("Destroyed session:");
Janus.debug(json);
sessionId = null;
connected = false;
if(json["rtcgw"] !== "success") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
}
callbacks.success();
if(notifyDestroyed)
gatewayCallbacks.destroyed();
},
error: function(textStatus, errorThrown) {
Janus.error(textStatus + ":", errorThrown); // FIXME
// Reset everything anyway
sessionId = null;
connected = false;
callbacks.success();
if(notifyDestroyed)
gatewayCallbacks.destroyed();
}
});
}
// Private method to create a plugin handle
function createHandle(callbacks) {
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.slowLink = (typeof callbacks.slowLink == "function") ? callbacks.slowLink : 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 server down? (connected=false)");
callbacks.error("Is the server down? (connected=false)");
return;
}
var plugin = callbacks.plugin;
if(plugin === undefined || plugin === null) {
Janus.error("Invalid plugin");
callbacks.error("Invalid plugin");
return;
}
var opaqueId = callbacks.opaqueId;
var handleToken = callbacks.token ? callbacks.token : token;
var transaction = Janus.randomString(12);
var request = { "rtcgw": "attach", "plugin": plugin, "opaque_id": opaqueId, "transaction": transaction };
if(handleToken !== null && handleToken !== undefined)
request["token"] = handleToken;
if(apisecret !== null && apisecret !== undefined)
request["apisecret"] = apisecret;
if(websockets) {
transactions[transaction] = function(json) {
Janus.debug(json);
if(json["rtcgw"] !== "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,
token : handleToken,
detached : false,
webrtcStuff : {
started : false,
myStream : null,
streamExternal : false,
remoteStream : null,
mySdp : null,
mediaConstraints : null,
pc : null,
dataChannel : {},
dtmfSender : null,
trickle : true,
iceDone : 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, true); },
getRemoteVolume : function() { return getVolume(handleId, true); },
getLocalVolume : function() { return getVolume(handleId, false); },
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); },
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,
slowLink : callbacks.slowLink,
onmessage : callbacks.onmessage,
createOffer : function(callbacks) { prepareWebrtc(handleId, true, callbacks); },
createAnswer : function(callbacks) { prepareWebrtc(handleId, false, 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;
callbacks.success(pluginHandle);
};
request["session_id"] = sessionId;
ws.send(JSON.stringify(request));
return;
}
Janus.httpAPICall(server + "/" + sessionId, {
verb: 'POST',
withCredentials: withCredentials,
body: request,
success: function(json) {
Janus.debug(json);
if(json["rtcgw"] !== "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,
token : handleToken,
detached : false,
webrtcStuff : {
started : false,
myStream : null,
streamExternal : false,
remoteStream : null,
mySdp : null,
mediaConstraints : null,
pc : null,
dataChannel : {},
dtmfSender : null,
trickle : true,
iceDone : 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, true); },
getRemoteVolume : function() { return getVolume(handleId, true); },
getLocalVolume : function() { return getVolume(handleId, false); },
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); },
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,
slowLink : callbacks.slowLink,
onmessage : callbacks.onmessage,
createOffer : function(callbacks) { prepareWebrtc(handleId, true, callbacks); },
createAnswer : function(callbacks) { prepareWebrtc(handleId, false, 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;
callbacks.success(pluginHandle);
},
error: function(textStatus, errorThrown) {
Janus.error(textStatus + ":", errorThrown); // FIXME
}
});
}
// 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 server down? (connected=false)");
callbacks.error("Is the server down? (connected=false)");
return;
}
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 message = callbacks.message;
var jsep = callbacks.jsep;
var transaction = Janus.randomString(12);
var request = { "rtcgw": "message", "body": message, "transaction": transaction };
if(pluginHandle.token !== null && pluginHandle.token !== undefined)
request["token"] = pluginHandle.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 + "):");
Janus.debug(request);
if(websockets) {
request["session_id"] = sessionId;
request["handle_id"] = handleId;
transactions[transaction] = function(json) {
Janus.debug("Message sent!");
Janus.debug(json);
if(json["rtcgw"] === "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["rtcgw"] !== "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.httpAPICall(server + "/" + sessionId + "/" + handleId, {
verb: 'POST',
withCredentials: withCredentials,
body: request,
success: function(json) {
Janus.debug("Message sent!");
Janus.debug(json);
if(json["rtcgw"] === "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["rtcgw"] !== "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(textStatus, errorThrown) {
Janus.error(textStatus + ":", errorThrown); // FIXME
callbacks.error(textStatus + ": " + errorThrown);
}
});
}
// Private method to send a trickle candidate
function sendTrickleCandidate(handleId, candidate) {
if(!connected) {
Janus.warn("Is the server down? (connected=false)");
return;
}
var pluginHandle = pluginHandles[handleId];
if(pluginHandle === null || pluginHandle === undefined ||
pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
Janus.warn("Invalid handle");
return;
}
var request = { "rtcgw": "trickle", "candidate": candidate, "transaction": Janus.randomString(12) };
if(pluginHandle.token !== null && pluginHandle.token !== undefined)
request["token"] = pluginHandle.token;
if(apisecret !== null && apisecret !== undefined)
request["apisecret"] = apisecret;
Janus.vdebug("Sending trickle candidate (handle=" + handleId + "):");
Janus.vdebug(request);
if(websockets) {
request["session_id"] = sessionId;
request["handle_id"] = handleId;
ws.send(JSON.stringify(request));
return;
}
Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, {
verb: 'POST',
withCredentials: withCredentials,
body: request,
success: function(json) {
Janus.vdebug("Candidate sent!");
Janus.vdebug(json);
if(json["rtcgw"] !== "ack") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
return;
}
},
error: function(textStatus, errorThrown) {
Janus.error(textStatus + ":", errorThrown); // FIXME
}
});
}
// Private method to create a data channel
function createDataChannel(handleId, dclabel, incoming, pendingText) {
var pluginHandle = pluginHandles[handleId];
if(pluginHandle === null || pluginHandle === undefined ||
pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
Janus.warn("Invalid handle");
return;
}
var config = pluginHandle.webrtcStuff;
var onDataChannelMessage = function(event) {
Janus.log('Received message on data channel:', event);
var label = event.target.label;
pluginHandle.ondata(event.data, label);
}
var onDataChannelStateChange = function(event) {
Janus.log('Received state change on data channel:', event);
var label = event.target.label;
var dcState = config.dataChannel[label] ? config.dataChannel[label].readyState : "null";
Janus.log('State change on <' + label + '> 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 i in config.dataChannel[label].pending) {
var text = config.dataChannel[label].pending[i];
Janus.log("Sending string on data channel <" + label + ">: " + text);
config.dataChannel[label].send(text);
}
config.dataChannel[label].pending = [];
}
// Notify the open data channel
pluginHandle.ondataopen(label);
}
}
var onDataChannelError = function(error) {
Janus.error('Got error on data channel:', error);
// TODO
}
if(!incoming) {
// FIXME Add options (ordered, maxRetransmits, etc.)
config.dataChannel[dclabel] = config.pc.createDataChannel(dclabel, {ordered:false});
} 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(pendingText)
config.dataChannel[dclabel].pending.push(pendingText);
}
// 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;
}
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, false, text);
callbacks.success();
return;
}
if(config.dataChannel[label].readyState !== "open") {
config.dataChannel[label].pending.push(text);
callbacks.success();
return;
}
Janus.log("Sending string on data channel <" + label + ">: " + text);
config.dataChannel[label].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 the proper way, if possible
if(config.pc !== undefined && config.pc !== null) {
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 === 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);
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 asyncRequest = true;
if(callbacks.asyncRequest !== undefined && callbacks.asyncRequest !== null)
asyncRequest = (callbacks.asyncRequest === true);
var noRequest = true;
if(callbacks.noRequest !== undefined && callbacks.noRequest !== null)
noRequest = (callbacks.noRequest === true);
Janus.log("Destroying handle " + handleId + " (async=" + asyncRequest + ")");
cleanupWebrtc(handleId);
var pluginHandle = pluginHandles[handleId];
if(pluginHandle === null || pluginHandle === undefined || 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;
}
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 = { "rtcgw": "detach", "transaction": Janus.randomString(12) };
if(pluginHandle.token !== null && pluginHandle.token !== undefined)
request["token"] = pluginHandle.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.httpAPICall(server + "/" + sessionId + "/" + handleId, {
verb: 'POST',
async: asyncRequest, // Sometimes we need false here, or destroying in onbeforeunload won't work
withCredentials: withCredentials,
body: request,
success: function(json) {
Janus.log("Destroyed handle:");
Janus.debug(json);
if(json["rtcgw"] !== "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 === null || pluginHandle === undefined ||
pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
Janus.warn("Invalid handle");
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) {
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;
var transceivers = config.pc.getTransceivers();
if(transceivers && transceivers.length > 0) {
for(var i in transceivers) {
var t = transceivers[i];
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;
var transceivers = config.pc.getTransceivers();
if(transceivers && transceivers.length > 0) {
for(var i in transceivers) {
var t = transceivers[i];
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 === true) {
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";
}
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(e) {
if(config.pc)
pluginHandle.iceState(config.pc.iceConnectionState);
};
config.pc.onicecandidate = function(event) {
if (event.candidate == null ||
(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;
Janus.log("Adding onended callback to track:", event.track);
event.track.onended = function(ev) {
Janus.log("Remote track muted/removed:", ev);
if(config.remoteStream) {
config.remoteStream.removeTrack(ev.target);
pluginHandle.onremotestream(config.remoteStream);
}
};
event.track.onmute = event.track.onended;
event.track.onunmute = function(ev) {
Janus.log("Remote track flowing again:", ev);
try {
config.remoteStream.addTrack(ev.target);
pluginHandle.onremotestream(config.remoteStream);
} catch(e) {
Janus.error(e);
};
};
};
}
if(addTracks && stream !== null && stream !== undefined) {
Janus.log('Adding local stream');
var simulcast2 = callbacks.simulcast2 === true ? true : false;
stream.getTracks().forEach(function(track) {
Janus.log('Adding local track:', track);
if(!simulcast2) {
config.pc.addTrack(track, stream);
} else {
if(track.kind === "audio") {
config.pc.addTrack(track, stream);
} else {
Janus.log('Enabling rid-based simulcasting:', track);
const maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates);
config.pc.addTransceiver(track, {
direction: "sendrecv",
streams: [stream],
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 }
]
});
}
}
});
}
// Any data channel to create?
if(isDataEnabled(media) && !config.dataChannel[Janus.dataChanDefaultLabel]) {
Janus.log("Creating data channel");
createDataChannel(handleId, Janus.dataChanDefaultLabel, false);
config.pc.ondatachannel = function(event) {
Janus.log("Data channel created by Janus:", event);
createDataChannel(handleId, event.channel.label, 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 === null || jsep === undefined) {
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;
}
callbacks.media = callbacks.media || { audio: true, video: true };
var media = callbacks.media;
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;
config.trickle = isTrickleEnabled(callbacks.trickle);
// Are we updating a session?
if(config.pc === undefined || config.pc === null) {
// Nope, new PeerConnection
media.update = false;
media.keepAudio = false;
media.keepVideo = false;
} else if(config.pc !== undefined && config.pc !== null) {
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 !== null && callbacks.stream !== undefined) {
// 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 === null || config.myStream === undefined) {
// 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() === null
|| config.myStream.getAudioTracks() === undefined
|| 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.keepVideo = 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 === null || config.myStream === undefined) {
// 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() === null
|| config.myStream.getVideoTracks() === undefined
|| 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) {
if(media.removeAudio || media.replaceAudio) {
if(config.myStream && config.myStream.getAudioTracks() && config.myStream.getAudioTracks().length) {
var s = config.myStream.getAudioTracks()[0];
Janus.log("Removing audio track:", s);
config.myStream.removeTrack(s);
try {
s.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 index in config.pc.getSenders()) {
var s = config.pc.getSenders()[index];
if(s && s.track && s.track.kind === "audio") {
Janus.log("Removing audio sender:", s);
config.pc.removeTrack(s);
}
}
}
}
}
if(media.removeVideo || media.replaceVideo) {
if(config.myStream && config.myStream.getVideoTracks() && config.myStream.getVideoTracks().length) {
var s = config.myStream.getVideoTracks()[0];
Janus.log("Removing video track:", s);
config.myStream.removeTrack(s);
try {
s.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 index in config.pc.getSenders()) {
var s = config.pc.getSenders()[index];
if(s && s.track && s.track.kind === "video") {
Janus.log("Removing video sender:", s);
config.pc.removeTrack(s);
}
}
}
}
}
}
// Was a MediaStream object passed, or do we need to take care of that?
if(callbacks.stream !== null && callbacks.stream !== undefined) {
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) {
if(config.myStream && config.myStream !== callbacks.stream && !config.streamExternal) {
// We're replacing a stream we captured ourselves with an external one
try {
// Try a MediaStreamTrack.stop() for each track
var tracks = config.myStream.getTracks();
for(var i in tracks) {
var mst = tracks[i];
Janus.log(mst);
if(mst !== null && mst !== undefined)
mst.stop();
}
} catch(e) {
// Do nothing if this fails
}
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 === true && media != undefined && media != null) {
if(typeof media.audio === 'object') {
audioSupport = media.audio;
}
}
var videoSupport = isVideoSendEnabled(media);
if(videoSupport === true && media != undefined && media != null) {
var simulcast = callbacks.simulcast === true ? true : false;
var simulcast2 = callbacks.simulcast2 === true ? true : false;
if((simulcast || simulcast2) && !jsep && (media.video === undefined || media.video === false))
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, maxHeight = 0;
if(media.video === 'lowres') {
// Small resolution, 4:3
height = 240;
maxHeight = 240;
width = 320;
} else if(media.video === 'lowres-16:9') {
// Small resolution, 16:9
height = 180;
maxHeight = 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;
maxHeight = 720;
width = 1280;
} else if(media.video === 'fhdres') {
// Full HD resolution is only 16:9
height = 1080;
maxHeight = 1080;
width = 1920;
} else if(media.video === '4kres') {
// 4K resolution is only 16:9
height = 2160;
maxHeight = 2160;
width = 3840;
} else if(media.video === 'stdres') {
// Normal resolution, 4:3
height = 480;
maxHeight = 480;
width = 640;
} else if(media.video === 'stdres-16:9') {
// Normal resolution, 16:9
height = 360;
maxHeight = 360;
width = 640;
} else {
Janus.log("Default video setting is stdres 4:3");
height = 480;
maxHeight = 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(!media.screenshareFrameRate) {
media.screenshareFrameRate = 3;
}
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/
navigator.mediaDevices.getDisplayMedia({ video: true })
.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+
function callbackUserMedia (error, stream) {
pluginHandle.consentDialog(false);
if(error) {
callbacks.error(error);
} else {
streamsDone(handleId, jsep, media, callbacks, stream);
}
};
function getScreenMedia(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 === null || media === undefined || 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('enumerateDevices 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;
var jsep = callbacks.jsep;
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(jsep !== undefined && jsep !== null) {
if(config.pc === null) {
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;
}
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 === null || pluginHandle === undefined ||
pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
Janus.warn("Invalid handle");
callbacks.error("Invalid handle");
return;
}
var config = pluginHandle.webrtcStuff;
var simulcast = callbacks.simulcast === true ? true : false;
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 i in transceivers) {
var t = transceivers[i];
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 ? true : false;
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.kind == "video"});
if(sender) {
var parameters = sender.getParameters();
if(!parameters)
parameters = {};
const maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates);
parameters.encodings = [
{ 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 = 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;
}
Janus.log("Offer ready");
Janus.debug(callbacks);
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 === null || pluginHandle === undefined ||
pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
Janus.warn("Invalid handle");
callbacks.error("Invalid handle");
return;
}
var config = pluginHandle.webrtcStuff;
var simulcast = callbacks.simulcast === true ? true : false;
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 i in transceivers) {
var t = transceivers[i];
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);
const maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates);
sender.setParameters({encodings: [
{ rid: "high", active: true, priority: "high", maxBitrate: maxBitrates.high },
{ rid: "medium", active: true, priority: "medium", maxBitrate: maxBitrates.medium },
{ rid: "low", active: true, priority: "low", maxBitrate: maxBitrates.low }
]});
}
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 = 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;
}
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 === null || pluginHandle === undefined ||
pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
Janus.warn("Invalid handle, not sending anything");
return;
}
var config = pluginHandle.webrtcStuff;
Janus.log("Sending offer/answer SDP...");
if(config.mySdp === null || config.mySdp === undefined) {
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 === null || pluginHandle === undefined ||
pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
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 getStats is supported
if(config.pc.getStats && Janus.webRTCAdapter.browserDetails.browser === "chrome") {
if(remote && (config.remoteStream === null || config.remoteStream === undefined)) {
Janus.warn("Remote stream unavailable");
return 0;
} else if(!remote && (config.myStream === null || config.myStream === undefined)) {
Janus.warn("Local stream unavailable");
return 0;
}
if(config.volume[stream].timer === null || config.volume[stream].timer === undefined) {
Janus.log("Starting " + stream + " volume monitor");
config.volume[stream].timer = setInterval(function() {
config.pc.getStats(function(stats) {
var results = stats.result();
for(var i=0; i<results.length; i++) {
var res = results[i];
if(res.type == 'ssrc') {
if(remote && res.stat('audioOutputLevel'))
config.volume[stream].value = parseInt(res.stat('audioOutputLevel'));
else if(!remote && res.stat('audioInputLevel'))
config.volume[stream].value = parseInt(res.stat('audioInputLevel'));
}
}
});
}, 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 === null || pluginHandle === undefined ||
pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
Janus.warn("Invalid handle");
return true;
}
var config = pluginHandle.webrtcStuff;
if(config.pc === null || config.pc === undefined) {
Janus.warn("Invalid PeerConnection");
return true;
}
if(config.myStream === undefined || config.myStream === null) {
Janus.warn("Invalid local MediaStream");
return true;
}
if(video) {
// Check video track
if(config.myStream.getVideoTracks() === null
|| config.myStream.getVideoTracks() === undefined
|| 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() === null
|| config.myStream.getAudioTracks() === undefined
|| 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 === null || pluginHandle === undefined ||
pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
Janus.warn("Invalid handle");
return false;
}
var config = pluginHandle.webrtcStuff;
if(config.pc === null || config.pc === undefined) {
Janus.warn("Invalid PeerConnection");
return false;
}
if(config.myStream === undefined || config.myStream === null) {
Janus.warn("Invalid local MediaStream");
return false;
}
if(video) {
// Mute/unmute video track
if(config.myStream.getVideoTracks() === null
|| config.myStream.getVideoTracks() === undefined
|| config.myStream.getVideoTracks().length === 0) {
Janus.warn("No video track");
return false;
}
config.myStream.getVideoTracks()[0].enabled = mute ? false : true;
return true;
} else {
// Mute/unmute audio track
if(config.myStream.getAudioTracks() === null
|| config.myStream.getAudioTracks() === undefined
|| config.myStream.getAudioTracks().length === 0) {
Janus.warn("No audio track");
return false;
}
config.myStream.getAudioTracks()[0].enabled = mute ? false : true;
return true;
}
}
function getBitrate(handleId) {
var pluginHandle = pluginHandles[handleId];
if(pluginHandle === null || pluginHandle === undefined ||
pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
Janus.warn("Invalid handle");
return "Invalid handle";
}
var config = pluginHandle.webrtcStuff;
if(config.pc === null || config.pc === undefined)
return "Invalid PeerConnection";
// Start getting the bitrate, if getStats is supported
if(config.pc.getStats) {
if(config.bitrate.timer === null || config.bitrate.timer === undefined) {
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 === null || pluginHandle === undefined) {
// Nothing to clean
return;
}
var config = pluginHandle.webrtcStuff;
if(config !== null && config !== undefined) {
if(hangupRequest === true) {
// Send a hangup request (we don't really care about the response)
var request = { "rtcgw": "hangup", "transaction": Janus.randomString(12) };
if(pluginHandle.token !== null && pluginHandle.token !== undefined)
request["token"] = pluginHandle.token;
if(apisecret !== null && apisecret !== undefined)
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;
try {
// Try a MediaStreamTrack.stop() for each track
if(!config.streamExternal && config.myStream !== null && config.myStream !== undefined) {
Janus.log("Stopping local stream tracks");
var tracks = config.myStream.getTracks();
for(var i in tracks) {
var mst = tracks[i];
Janus.log(mst);
if(mst !== null && mst !== undefined)
mst.stop();
}
}
} catch(e) {
// Do nothing if this fails
}
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;
}
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(var i=0; i<lines.length; i++) {
var mline = lines[i].match(/m=(\w+) */);
if(mline) {
var medium = mline[1];
if(medium === "video") {
// New video m-line: make sure it's the first one
if(ssrc[0] < 0) {
video = true;
} else {
// We're done, let's add the new attributes here
insertAt = i;
break;
}
} else {
// New non-video m-line: do we have what we were looking for?
if(ssrc[0] > -1) {
// We're done, let's add the new attributes here
insertAt = i;
break;
}
}
continue;
}
if(!video)
continue;
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(var i=0; i<lines.length; i++) {
var mline = lines[i].match(/m=(\w+) */);
if(mline) {
var medium = mline[1];
if(medium === "video") {
// New video m-line: make sure it's the first one
if(ssrc[0] < 0) {
video = true;
} else {
// We're done, let's add the new attributes here
insertAt = i;
break;
}
} else {
// New non-video m-line: do we have what we were looking for?
if(ssrc[0] > -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 {
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) {
// 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<ssrc.length; i++) {
if(cname) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' cname:' + cname);
insertAt++;
}
if(msid) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' msid:' + msid);
insertAt++;
}
if(mslabel) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' mslabel:' + mslabel);
insertAt++;
}
if(label) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' label:' + label);
insertAt++;
}
// Add the same info for the retransmission SSRC
if(cname) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' cname:' + cname);
insertAt++;
}
if(msid) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' msid:' + msid);
insertAt++;
}
if(mslabel) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' mslabel:' + mslabel);
insertAt++;
}
if(label) {
lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' label:' + label);
insertAt++;
}
}
lines.splice(insertAt, 0, 'a=ssrc-group:FID ' + ssrc[2] + ' ' + ssrc_fid[2]);
lines.splice(insertAt, 0, 'a=ssrc-group:FID ' + ssrc[1] + ' ' + ssrc_fid[1]);
lines.splice(insertAt, 0, 'a=ssrc-group:FID ' + ssrc[0] + ' ' + ssrc_fid[0]);
lines.splice(insertAt, 0, 'a=ssrc-group:SIM ' + ssrc[0] + ' ' + ssrc[1] + ' ' + ssrc[2]);
sdp = lines.join("\r\n");
if(!sdp.endsWith("\r\n"))
sdp += "\r\n";
return sdp;
}
// Helper methods to parse a media object
function isAudioSendEnabled(media) {
Janus.debug("isAudioSendEnabled:", media);
if(media === undefined || media === null)
return true; // Default
if(media.audio === false)
return false; // Generic audio has precedence
if(media.audioSend === undefined || media.audioSend === null)
return true; // Default
return (media.audioSend === true);
}
function isAudioSendRequired(media) {
Janus.debug("isAudioSendRequired:", media);
if(media === undefined || media === null)
return false; // Default
if(media.audio === false || media.audioSend === false)
return false; // If we're not asking to capture audio, it's not required
if(media.failIfNoAudio === undefined || media.failIfNoAudio === null)
return false; // Default
return (media.failIfNoAudio === true);
}
function isAudioRecvEnabled(media) {
Janus.debug("isAudioRecvEnabled:", media);
if(media === undefined || media === null)
return true; // Default
if(media.audio === false)
return false; // Generic audio has precedence
if(media.audioRecv === undefined || media.audioRecv === null)
return true; // Default
return (media.audioRecv === true);
}
function isVideoSendEnabled(media) {
Janus.debug("isVideoSendEnabled:", media);
if(media === undefined || media === null)
return true; // Default
if(media.video === false)
return false; // Generic video has precedence
if(media.videoSend === undefined || media.videoSend === null)
return true; // Default
return (media.videoSend === true);
}
function isVideoSendRequired(media) {
Janus.debug("isVideoSendRequired:", media);
if(media === undefined || media === null)
return false; // Default
if(media.video === false || media.videoSend === false)
return false; // If we're not asking to capture video, it's not required
if(media.failIfNoVideo === undefined || media.failIfNoVideo === null)
return false; // Default
return (media.failIfNoVideo === true);
}
function isVideoRecvEnabled(media) {
Janus.debug("isVideoRecvEnabled:", media);
if(media === undefined || media === null)
return true; // Default
if(media.video === false)
return false; // Generic video has precedence
if(media.videoRecv === undefined || media.videoRecv === null)
return true; // Default
return (media.videoRecv === true);
}
function isScreenSendEnabled(media) {
Janus.debug("isScreenSendEnabled:", media);
if (media === undefined || media === null)
return false;
if (typeof media.video !== 'object' || typeof media.video.mandatory !== 'object')
return false;
var constraints = media.video.mandatory;
if (constraints.chromeMediaSource)
return constraints.chromeMediaSource === 'desktop' || constraints.chromeMediaSource === 'screen';
else if (constraints.mozMediaSource)
return constraints.mozMediaSource === 'window' || constraints.mozMediaSource === 'screen';
else if (constraints.mediaSource)
return constraints.mediaSource === 'window' || constraints.mediaSource === 'screen';
return false;
}
function isDataEnabled(media) {
Janus.debug("isDataEnabled:", media);
if(Janus.webRTCAdapter.browserDetails.browser == "edge") {
Janus.warn("Edge doesn't support data channels yet");
return false;
}
if(media === undefined || media === null)
return false; // Default
return (media.data === true);
}
function isTrickleEnabled(trickle) {
Janus.debug("isTrickleEnabled:", trickle);
if(trickle === undefined || trickle === null)
return true; // Default is true
return (trickle === true);
}
};