diff --git a/libs/app.bundle.js b/libs/app.bundle.js
index 6b2feba08..29ae27e55 100644
--- a/libs/app.bundle.js
+++ b/libs/app.bundle.js
@@ -1,18960 +1,18752 @@
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.APP = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && this._events[type].length > m) {
- this._events[type].warned = true;
- console.error('(node) warning: possible EventEmitter memory ' +
- 'leak detected. %d listeners added. ' +
- 'Use emitter.setMaxListeners() to increase limit.',
- this._events[type].length);
- if (typeof console.trace === 'function') {
- // not supported in IE 10
- console.trace();
- }
- }
- }
-
- return this;
-};
-
-EventEmitter.prototype.on = EventEmitter.prototype.addListener;
-
-EventEmitter.prototype.once = function(type, listener) {
- if (!isFunction(listener))
- throw TypeError('listener must be a function');
-
- var fired = false;
-
- function g() {
- this.removeListener(type, g);
-
- if (!fired) {
- fired = true;
- listener.apply(this, arguments);
- }
- }
-
- g.listener = listener;
- this.on(type, g);
-
- return this;
-};
-
-// emits a 'removeListener' event iff the listener was removed
-EventEmitter.prototype.removeListener = function(type, listener) {
- var list, position, length, i;
-
- if (!isFunction(listener))
- throw TypeError('listener must be a function');
-
- if (!this._events || !this._events[type])
- return this;
-
- list = this._events[type];
- length = list.length;
- position = -1;
-
- if (list === listener ||
- (isFunction(list.listener) && list.listener === listener)) {
- delete this._events[type];
- if (this._events.removeListener)
- this.emit('removeListener', type, listener);
-
- } else if (isObject(list)) {
- for (i = length; i-- > 0;) {
- if (list[i] === listener ||
- (list[i].listener && list[i].listener === listener)) {
- position = i;
- break;
- }
- }
-
- if (position < 0)
- return this;
-
- if (list.length === 1) {
- list.length = 0;
- delete this._events[type];
- } else {
- list.splice(position, 1);
- }
-
- if (this._events.removeListener)
- this.emit('removeListener', type, listener);
- }
-
- return this;
-};
-
-EventEmitter.prototype.removeAllListeners = function(type) {
- var key, listeners;
-
- if (!this._events)
- return this;
-
- // not listening for removeListener, no need to emit
- if (!this._events.removeListener) {
- if (arguments.length === 0)
- this._events = {};
- else if (this._events[type])
- delete this._events[type];
- return this;
- }
-
- // emit removeListener for all listeners on all events
- if (arguments.length === 0) {
- for (key in this._events) {
- if (key === 'removeListener') continue;
- this.removeAllListeners(key);
- }
- this.removeAllListeners('removeListener');
- this._events = {};
- return this;
- }
-
- listeners = this._events[type];
-
- if (isFunction(listeners)) {
- this.removeListener(type, listeners);
- } else {
- // LIFO order
- while (listeners.length)
- this.removeListener(type, listeners[listeners.length - 1]);
- }
- delete this._events[type];
-
- return this;
-};
-
-EventEmitter.prototype.listeners = function(type) {
- var ret;
- if (!this._events || !this._events[type])
- ret = [];
- else if (isFunction(this._events[type]))
- ret = [this._events[type]];
- else
- ret = this._events[type].slice();
- return ret;
-};
-
-EventEmitter.listenerCount = function(emitter, type) {
- var ret;
- if (!emitter._events || !emitter._events[type])
- ret = 0;
- else if (isFunction(emitter._events[type]))
- ret = 1;
- else
- ret = emitter._events[type].length;
- return ret;
-};
-
-function isFunction(arg) {
- return typeof arg === 'function';
+ APP.desktopsharing.init();
+ APP.RTC.start();
+ APP.xmpp.start();
+ APP.statistics.start();
+ APP.connectionquality.init();
+ APP.keyboardshortcut.init();
+ APP.members.start();
}
-function isNumber(arg) {
- return typeof arg === 'number';
+
+$(document).ready(function () {
+
+ var URLPRocessor = require("./modules/URLProcessor/URLProcessor");
+ URLPRocessor.setConfigParametersFromUrl();
+ APP.init();
+
+ APP.translation.init();
+
+ if(APP.API.isEnabled())
+ APP.API.init();
+
+ APP.UI.start(init);
+
+});
+
+$(window).bind('beforeunload', function () {
+ if(APP.API.isEnabled())
+ APP.API.dispose();
+});
+
+module.exports = APP;
+
+
+},{"./modules/API/API":2,"./modules/DTMF/DTMF":3,"./modules/RTC/RTC":7,"./modules/UI/UI":11,"./modules/URLProcessor/URLProcessor":42,"./modules/connectionquality/connectionquality":43,"./modules/desktopsharing/desktopsharing":44,"./modules/keyboardshortcut/keyboardshortcut":45,"./modules/members/MemberList":46,"./modules/settings/Settings":47,"./modules/statistics/statistics":51,"./modules/translation/translation":52,"./modules/xmpp/xmpp":66}],2:[function(require,module,exports){
+/**
+ * Implements API class that communicates with external api class
+ * and provides interface to access Jitsi Meet features by external
+ * applications that embed Jitsi Meet
+ */
+
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+
+/**
+ * List of the available commands.
+ * @type {{
+ * displayName: inputDisplayNameHandler,
+ * muteAudio: toggleAudio,
+ * muteVideo: toggleVideo,
+ * filmStrip: toggleFilmStrip
+ * }}
+ */
+var commands = {};
+
+function initCommands() {
+ commands =
+ {
+ displayName: APP.UI.inputDisplayNameHandler,
+ muteAudio: APP.UI.toggleAudio,
+ muteVideo: APP.UI.toggleVideo,
+ toggleFilmStrip: APP.UI.toggleFilmStrip,
+ toggleChat: APP.UI.toggleChat,
+ toggleContactList: APP.UI.toggleContactList
+ };
}
-function isObject(arg) {
- return typeof arg === 'object' && arg !== null;
-}
-function isUndefined(arg) {
- return arg === void 0;
-}
+/**
+ * Maps the supported events and their status
+ * (true it the event is enabled and false if it is disabled)
+ * @type {{
+ * incomingMessage: boolean,
+ * outgoingMessage: boolean,
+ * displayNameChange: boolean,
+ * participantJoined: boolean,
+ * participantLeft: boolean
+ * }}
+ */
+var events =
+{
+ incomingMessage: false,
+ outgoingMessage:false,
+ displayNameChange: false,
+ participantJoined: false,
+ participantLeft: false
+};
-},{}],2:[function(require,module,exports){
-// shim for using process in browser
+var displayName = {};
-var process = module.exports = {};
-var queue = [];
-var draining = false;
-
-function drainQueue() {
- if (draining) {
+/**
+ * Processes commands from external applicaiton.
+ * @param message the object with the command
+ */
+function processCommand(message)
+{
+ if(message.action != "execute")
+ {
+ console.error("Unknown action of the message");
return;
}
- draining = true;
- var currentQueue;
- var len = queue.length;
- while(len) {
- currentQueue = queue;
- queue = [];
- var i = -1;
- while (++i < len) {
- currentQueue[i]();
- }
- len = queue.length;
+ for(var key in message)
+ {
+ if(commands[key])
+ commands[key].apply(null, message[key]);
}
- draining = false;
}
-process.nextTick = function (fun) {
- queue.push(fun);
- if (!draining) {
- setTimeout(drainQueue, 0);
+
+/**
+ * Processes events objects from external applications
+ * @param event the event
+ */
+function processEvent(event) {
+ if(!event.action)
+ {
+ console.error("Event with no action is received.");
+ return;
+ }
+
+ var i = 0;
+ switch(event.action)
+ {
+ case "add":
+ for(; i < event.events.length; i++)
+ {
+ events[event.events[i]] = true;
+ }
+ break;
+ case "remove":
+ for(; i < event.events.length; i++)
+ {
+ events[event.events[i]] = false;
+ }
+ break;
+ default:
+ console.error("Unknown action for event.");
+ }
+
+}
+
+/**
+ * Sends message to the external application.
+ * @param object
+ */
+function sendMessage(object) {
+ window.parent.postMessage(JSON.stringify(object), "*");
+}
+
+/**
+ * Processes a message event from the external application
+ * @param event the message event
+ */
+function processMessage(event)
+{
+ var message;
+ try {
+ message = JSON.parse(event.data);
+ } catch (e) {}
+
+ if(!message.type)
+ return;
+ switch (message.type)
+ {
+ case "command":
+ processCommand(message);
+ break;
+ case "event":
+ processEvent(message);
+ break;
+ default:
+ console.error("Unknown type of the message");
+ return;
+ }
+
+}
+
+function setupListeners() {
+ APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, function (from) {
+ API.triggerEvent("participantJoined", {jid: from});
+ });
+ APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, function (from, nick, txt, myjid, stamp) {
+ if (from != myjid)
+ API.triggerEvent("incomingMessage",
+ {"from": from, "nick": nick, "message": txt, "stamp": stamp});
+ });
+ APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, function (jid) {
+ API.triggerEvent("participantLeft", {jid: jid});
+ });
+ APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, function (jid, newDisplayName) {
+ name = displayName[jid];
+ if(!name || name != newDisplayName) {
+ API.triggerEvent("displayNameChange", {jid: jid, displayname: newDisplayName});
+ displayName[jid] = newDisplayName;
+ }
+ });
+ APP.xmpp.addListener(XMPPEvents.SENDING_CHAT_MESSAGE, function (body) {
+ APP.API.triggerEvent("outgoingMessage", {"message": body});
+ });
+}
+
+var API = {
+ /**
+ * Check whether the API should be enabled or not.
+ * @returns {boolean}
+ */
+ isEnabled: function () {
+ var hash = location.hash;
+ if(hash && hash.indexOf("external") > -1 && window.postMessage)
+ return true;
+ return false;
+ },
+ /**
+ * Initializes the APIConnector. Setups message event listeners that will
+ * receive information from external applications that embed Jitsi Meet.
+ * It also sends a message to the external application that APIConnector
+ * is initialized.
+ */
+ init: function () {
+ initCommands();
+ if (window.addEventListener)
+ {
+ window.addEventListener('message',
+ processMessage, false);
+ }
+ else
+ {
+ window.attachEvent('onmessage', processMessage);
+ }
+ sendMessage({type: "system", loaded: true});
+ setupListeners();
+ },
+ /**
+ * Checks whether the event is enabled ot not.
+ * @param name the name of the event.
+ * @returns {*}
+ */
+ isEventEnabled: function (name) {
+ return events[name];
+ },
+
+ /**
+ * Sends event object to the external application that has been subscribed
+ * for that event.
+ * @param name the name event
+ * @param object data associated with the event
+ */
+ triggerEvent: function (name, object) {
+ if(this.isEnabled() && this.isEventEnabled(name))
+ sendMessage({
+ type: "event", action: "result", event: name, result: object});
+ },
+
+ /**
+ * Removes the listeners.
+ */
+ dispose: function () {
+ if(window.removeEventListener)
+ {
+ window.removeEventListener("message",
+ processMessage, false);
+ }
+ else
+ {
+ window.detachEvent('onmessage', processMessage);
+ }
+
+ }
+
+
+};
+
+module.exports = API;
+},{"../../service/xmpp/XMPPEvents":112}],3:[function(require,module,exports){
+/* global APP */
+
+/**
+ * A module for sending DTMF tones.
+ */
+var DTMFSender;
+var initDtmfSender = function() {
+ // TODO: This needs to reset this if the peerconnection changes
+ // (e.g. the call is re-made)
+ if (DTMFSender)
+ return;
+
+ var localAudio = APP.RTC.localAudio;
+ if (localAudio && localAudio.getTracks().length > 0)
+ {
+ var peerconnection
+ = APP.xmpp.getConnection().jingle.activecall.peerconnection;
+ if (peerconnection) {
+ DTMFSender =
+ peerconnection.peerconnection
+ .createDTMFSender(localAudio.getTracks()[0]);
+ console.log("Initialized DTMFSender");
+ }
+ else {
+ console.log("Failed to initialize DTMFSender: no PeerConnection.");
+ }
+ }
+ else {
+ console.log("Failed to initialize DTMFSender: no audio track.");
}
};
-process.title = 'browser';
-process.browser = true;
-process.env = {};
-process.argv = [];
-process.version = ''; // empty string to avoid regexp issues
-process.versions = {};
+var DTMF = {
+ sendTones: function (tones, duration, pause) {
+ if (!DTMFSender)
+ initDtmfSender();
-function noop() {}
-
-process.on = noop;
-process.addListener = noop;
-process.once = noop;
-process.off = noop;
-process.removeListener = noop;
-process.removeAllListeners = noop;
-process.emit = noop;
-
-process.binding = function (name) {
- throw new Error('process.binding is not supported');
+ if (DTMFSender){
+ DTMFSender.insertDTMF(tones,
+ (duration || 200),
+ (pause || 200));
+ }
+ }
};
-// TODO(shtylman)
-process.cwd = function () { return '/' };
-process.chdir = function (dir) {
- throw new Error('process.chdir is not supported');
+module.exports = DTMF;
+
+
+},{}],4:[function(require,module,exports){
+/* global Strophe, focusedVideoSrc*/
+
+// cache datachannels to avoid garbage collection
+// https://code.google.com/p/chromium/issues/detail?id=405545
+var RTCEvents = require("../../service/RTC/RTCEvents");
+
+var _dataChannels = [];
+var eventEmitter = null;
+
+
+
+
+var DataChannels =
+{
+
+ /**
+ * Callback triggered by PeerConnection when new data channel is opened
+ * on the bridge.
+ * @param event the event info object.
+ */
+
+ onDataChannel: function (event)
+ {
+ var dataChannel = event.channel;
+
+ dataChannel.onopen = function () {
+ console.info("Data channel opened by the Videobridge!", dataChannel);
+
+ // Code sample for sending string and/or binary data
+ // Sends String message to the bridge
+ //dataChannel.send("Hello bridge!");
+ // Sends 12 bytes binary message to the bridge
+ //dataChannel.send(new ArrayBuffer(12));
+
+ eventEmitter.emit(RTCEvents.DATA_CHANNEL_OPEN);
+ };
+
+ dataChannel.onerror = function (error) {
+ console.error("Data Channel Error:", error, dataChannel);
+ };
+
+ dataChannel.onmessage = function (event) {
+ var data = event.data;
+ // JSON
+ var obj;
+
+ try {
+ obj = JSON.parse(data);
+ }
+ catch (e) {
+ console.error(
+ "Failed to parse data channel message as JSON: ",
+ data,
+ dataChannel);
+ }
+ if (('undefined' !== typeof(obj)) && (null !== obj)) {
+ var colibriClass = obj.colibriClass;
+
+ if ("DominantSpeakerEndpointChangeEvent" === colibriClass) {
+ // Endpoint ID from the Videobridge.
+ var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint;
+
+ console.info(
+ "Data channel new dominant speaker event: ",
+ dominantSpeakerEndpoint);
+ eventEmitter.emit(RTCEvents.DOMINANTSPEAKER_CHANGED, dominantSpeakerEndpoint);
+ }
+ else if ("InLastNChangeEvent" === colibriClass)
+ {
+ var oldValue = obj.oldValue;
+ var newValue = obj.newValue;
+ // Make sure that oldValue and newValue are of type boolean.
+ var type;
+
+ if ((type = typeof oldValue) !== 'boolean') {
+ if (type === 'string') {
+ oldValue = (oldValue == "true");
+ } else {
+ oldValue = new Boolean(oldValue).valueOf();
+ }
+ }
+ if ((type = typeof newValue) !== 'boolean') {
+ if (type === 'string') {
+ newValue = (newValue == "true");
+ } else {
+ newValue = new Boolean(newValue).valueOf();
+ }
+ }
+
+ eventEmitter.emit(RTCEvents.LASTN_CHANGED, oldValue, newValue);
+ }
+ else if ("LastNEndpointsChangeEvent" === colibriClass)
+ {
+ // The new/latest list of last-n endpoint IDs.
+ var lastNEndpoints = obj.lastNEndpoints;
+ // The list of endpoint IDs which are entering the list of
+ // last-n at this time i.e. were not in the old list of last-n
+ // endpoint IDs.
+ var endpointsEnteringLastN = obj.endpointsEnteringLastN;
+ var stream = obj.stream;
+
+ console.log(
+ "Data channel new last-n event: ",
+ lastNEndpoints, endpointsEnteringLastN, obj);
+ eventEmitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED,
+ lastNEndpoints, endpointsEnteringLastN, obj);
+ }
+ else
+ {
+ console.debug("Data channel JSON-formatted message: ", obj);
+ }
+ }
+ };
+
+ dataChannel.onclose = function ()
+ {
+ console.info("The Data Channel closed", dataChannel);
+ var idx = _dataChannels.indexOf(dataChannel);
+ if (idx > -1)
+ _dataChannels = _dataChannels.splice(idx, 1);
+ };
+ _dataChannels.push(dataChannel);
+ },
+
+ /**
+ * Binds "ondatachannel" event listener to given PeerConnection instance.
+ * @param peerConnection WebRTC peer connection instance.
+ */
+ init: function (peerConnection, emitter) {
+ if(!config.openSctp)
+ return;
+
+ peerConnection.ondatachannel = this.onDataChannel;
+ eventEmitter = emitter;
+
+ // Sample code for opening new data channel from Jitsi Meet to the bridge.
+ // Although it's not a requirement to open separate channels from both bridge
+ // and peer as single channel can be used for sending and receiving data.
+ // So either channel opened by the bridge or the one opened here is enough
+ // for communication with the bridge.
+ /*var dataChannelOptions =
+ {
+ reliable: true
+ };
+ var dataChannel
+ = peerConnection.createDataChannel("myChannel", dataChannelOptions);
+
+ // Can be used only when is in open state
+ dataChannel.onopen = function ()
+ {
+ dataChannel.send("My channel !!!");
+ };
+ dataChannel.onmessage = function (event)
+ {
+ var msgData = event.data;
+ console.info("Got My Data Channel Message:", msgData, dataChannel);
+ };*/
+ },
+ handleSelectedEndpointEvent: onSelectedEndpointChanged,
+ handlePinnedEndpointEvent: onPinnedEndpointChanged
+
};
-process.umask = function() { return 0; };
-},{}],3:[function(require,module,exports){
-/* jshint -W117 */
-/* application specific logic */
-
-var APP =
-{
- init: function () {
- this.UI = require("./modules/UI/UI");
- this.API = require("./modules/API/API");
- this.connectionquality = require("./modules/connectionquality/connectionquality");
- this.statistics = require("./modules/statistics/statistics");
- this.RTC = require("./modules/RTC/RTC");
- this.desktopsharing = require("./modules/desktopsharing/desktopsharing");
- this.xmpp = require("./modules/xmpp/xmpp");
- this.keyboardshortcut = require("./modules/keyboardshortcut/keyboardshortcut");
- this.translation = require("./modules/translation/translation");
- this.settings = require("./modules/settings/Settings");
- this.DTMF = require("./modules/DTMF/DTMF");
- this.members = require("./modules/members/MemberList");
- }
-};
-
-function init() {
-
- APP.desktopsharing.init();
- APP.RTC.start();
- APP.xmpp.start();
- APP.statistics.start();
- APP.connectionquality.init();
- APP.keyboardshortcut.init();
- APP.members.start();
-}
-
-
-$(document).ready(function () {
-
- var URLPRocessor = require("./modules/URLProcessor/URLProcessor");
- URLPRocessor.setConfigParametersFromUrl();
- APP.init();
-
- APP.translation.init();
-
- if(APP.API.isEnabled())
- APP.API.init();
-
- APP.UI.start(init);
-
-});
-
-$(window).bind('beforeunload', function () {
- if(APP.API.isEnabled())
- APP.API.dispose();
-});
-
-module.exports = APP;
-
+function onSelectedEndpointChanged(userResource)
+{
+ console.log('selected endpoint changed: ', userResource);
+ if (_dataChannels && _dataChannels.length != 0)
+ {
+ _dataChannels.some(function (dataChannel) {
+ if (dataChannel.readyState == 'open')
+ {
+ console.log('sending selected endpoint changed '
+ + 'notification to the bridge: ', userResource);
+ dataChannel.send(JSON.stringify({
+ 'colibriClass': 'SelectedEndpointChangedEvent',
+ 'selectedEndpoint':
+ (!userResource || userResource === null)?
+ null : userResource
+ }));
-},{"./modules/API/API":4,"./modules/DTMF/DTMF":5,"./modules/RTC/RTC":9,"./modules/UI/UI":13,"./modules/URLProcessor/URLProcessor":44,"./modules/connectionquality/connectionquality":45,"./modules/desktopsharing/desktopsharing":46,"./modules/keyboardshortcut/keyboardshortcut":47,"./modules/members/MemberList":48,"./modules/settings/Settings":49,"./modules/statistics/statistics":53,"./modules/translation/translation":54,"./modules/xmpp/xmpp":68}],4:[function(require,module,exports){
-/**
- * Implements API class that communicates with external api class
- * and provides interface to access Jitsi Meet features by external
- * applications that embed Jitsi Meet
- */
-
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-
-/**
- * List of the available commands.
- * @type {{
- * displayName: inputDisplayNameHandler,
- * muteAudio: toggleAudio,
- * muteVideo: toggleVideo,
- * filmStrip: toggleFilmStrip
- * }}
- */
-var commands = {};
-
-function initCommands() {
- commands =
- {
- displayName: APP.UI.inputDisplayNameHandler,
- muteAudio: APP.UI.toggleAudio,
- muteVideo: APP.UI.toggleVideo,
- toggleFilmStrip: APP.UI.toggleFilmStrip,
- toggleChat: APP.UI.toggleChat,
- toggleContactList: APP.UI.toggleContactList
- };
-}
-
-
-/**
- * Maps the supported events and their status
- * (true it the event is enabled and false if it is disabled)
- * @type {{
- * incomingMessage: boolean,
- * outgoingMessage: boolean,
- * displayNameChange: boolean,
- * participantJoined: boolean,
- * participantLeft: boolean
- * }}
- */
-var events =
-{
- incomingMessage: false,
- outgoingMessage:false,
- displayNameChange: false,
- participantJoined: false,
- participantLeft: false
-};
-
-var displayName = {};
-
-/**
- * Processes commands from external applicaiton.
- * @param message the object with the command
- */
-function processCommand(message)
-{
- if(message.action != "execute")
- {
- console.error("Unknown action of the message");
- return;
- }
- for(var key in message)
- {
- if(commands[key])
- commands[key].apply(null, message[key]);
- }
-}
-
-/**
- * Processes events objects from external applications
- * @param event the event
- */
-function processEvent(event) {
- if(!event.action)
- {
- console.error("Event with no action is received.");
- return;
- }
-
- var i = 0;
- switch(event.action)
- {
- case "add":
- for(; i < event.events.length; i++)
- {
- events[event.events[i]] = true;
- }
- break;
- case "remove":
- for(; i < event.events.length; i++)
- {
- events[event.events[i]] = false;
- }
- break;
- default:
- console.error("Unknown action for event.");
- }
-
-}
-
-/**
- * Sends message to the external application.
- * @param object
- */
-function sendMessage(object) {
- window.parent.postMessage(JSON.stringify(object), "*");
-}
-
-/**
- * Processes a message event from the external application
- * @param event the message event
- */
-function processMessage(event)
-{
- var message;
- try {
- message = JSON.parse(event.data);
- } catch (e) {}
-
- if(!message.type)
- return;
- switch (message.type)
- {
- case "command":
- processCommand(message);
- break;
- case "event":
- processEvent(message);
- break;
- default:
- console.error("Unknown type of the message");
- return;
- }
-
-}
-
-function setupListeners() {
- APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, function (from) {
- API.triggerEvent("participantJoined", {jid: from});
- });
- APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, function (from, nick, txt, myjid, stamp) {
- if (from != myjid)
- API.triggerEvent("incomingMessage",
- {"from": from, "nick": nick, "message": txt, "stamp": stamp});
- });
- APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, function (jid) {
- API.triggerEvent("participantLeft", {jid: jid});
- });
- APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, function (jid, newDisplayName) {
- name = displayName[jid];
- if(!name || name != newDisplayName) {
- API.triggerEvent("displayNameChange", {jid: jid, displayname: newDisplayName});
- displayName[jid] = newDisplayName;
- }
- });
- APP.xmpp.addListener(XMPPEvents.SENDING_CHAT_MESSAGE, function (body) {
- APP.API.triggerEvent("outgoingMessage", {"message": body});
- });
-}
-
-var API = {
- /**
- * Check whether the API should be enabled or not.
- * @returns {boolean}
- */
- isEnabled: function () {
- var hash = location.hash;
- if(hash && hash.indexOf("external") > -1 && window.postMessage)
- return true;
- return false;
- },
- /**
- * Initializes the APIConnector. Setups message event listeners that will
- * receive information from external applications that embed Jitsi Meet.
- * It also sends a message to the external application that APIConnector
- * is initialized.
- */
- init: function () {
- initCommands();
- if (window.addEventListener)
- {
- window.addEventListener('message',
- processMessage, false);
- }
- else
- {
- window.attachEvent('onmessage', processMessage);
- }
- sendMessage({type: "system", loaded: true});
- setupListeners();
- },
- /**
- * Checks whether the event is enabled ot not.
- * @param name the name of the event.
- * @returns {*}
- */
- isEventEnabled: function (name) {
- return events[name];
- },
-
- /**
- * Sends event object to the external application that has been subscribed
- * for that event.
- * @param name the name event
- * @param object data associated with the event
- */
- triggerEvent: function (name, object) {
- if(this.isEnabled() && this.isEventEnabled(name))
- sendMessage({
- type: "event", action: "result", event: name, result: object});
- },
-
- /**
- * Removes the listeners.
- */
- dispose: function () {
- if(window.removeEventListener)
- {
- window.removeEventListener("message",
- processMessage, false);
- }
- else
- {
- window.detachEvent('onmessage', processMessage);
- }
-
- }
-
-
-};
-
-module.exports = API;
-},{"../../service/xmpp/XMPPEvents":110}],5:[function(require,module,exports){
-/* global APP */
-
-/**
- * A module for sending DTMF tones.
- */
-var DTMFSender;
-var initDtmfSender = function() {
- // TODO: This needs to reset this if the peerconnection changes
- // (e.g. the call is re-made)
- if (DTMFSender)
- return;
-
- var localAudio = APP.RTC.localAudio;
- if (localAudio && localAudio.getTracks().length > 0)
- {
- var peerconnection
- = APP.xmpp.getConnection().jingle.activecall.peerconnection;
- if (peerconnection) {
- DTMFSender =
- peerconnection.peerconnection
- .createDTMFSender(localAudio.getTracks()[0]);
- console.log("Initialized DTMFSender");
- }
- else {
- console.log("Failed to initialize DTMFSender: no PeerConnection.");
- }
- }
- else {
- console.log("Failed to initialize DTMFSender: no audio track.");
- }
-};
-
-var DTMF = {
- sendTones: function (tones, duration, pause) {
- if (!DTMFSender)
- initDtmfSender();
-
- if (DTMFSender){
- DTMFSender.insertDTMF(tones,
- (duration || 200),
- (pause || 200));
- }
- }
-};
-
-module.exports = DTMF;
-
+ return true;
+ }
+ });
+ }
+}
-},{}],6:[function(require,module,exports){
-/* global Strophe, focusedVideoSrc*/
-
-// cache datachannels to avoid garbage collection
-// https://code.google.com/p/chromium/issues/detail?id=405545
-var RTCEvents = require("../../service/RTC/RTCEvents");
-
-var _dataChannels = [];
-var eventEmitter = null;
-
-
-
-
-var DataChannels =
-{
-
- /**
- * Callback triggered by PeerConnection when new data channel is opened
- * on the bridge.
- * @param event the event info object.
- */
-
- onDataChannel: function (event)
- {
- var dataChannel = event.channel;
-
- dataChannel.onopen = function () {
- console.info("Data channel opened by the Videobridge!", dataChannel);
-
- // Code sample for sending string and/or binary data
- // Sends String message to the bridge
- //dataChannel.send("Hello bridge!");
- // Sends 12 bytes binary message to the bridge
- //dataChannel.send(new ArrayBuffer(12));
-
- eventEmitter.emit(RTCEvents.DATA_CHANNEL_OPEN);
- };
-
- dataChannel.onerror = function (error) {
- console.error("Data Channel Error:", error, dataChannel);
- };
-
- dataChannel.onmessage = function (event) {
- var data = event.data;
- // JSON
- var obj;
-
- try {
- obj = JSON.parse(data);
- }
- catch (e) {
- console.error(
- "Failed to parse data channel message as JSON: ",
- data,
- dataChannel);
- }
- if (('undefined' !== typeof(obj)) && (null !== obj)) {
- var colibriClass = obj.colibriClass;
-
- if ("DominantSpeakerEndpointChangeEvent" === colibriClass) {
- // Endpoint ID from the Videobridge.
- var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint;
-
- console.info(
- "Data channel new dominant speaker event: ",
- dominantSpeakerEndpoint);
- eventEmitter.emit(RTCEvents.DOMINANTSPEAKER_CHANGED, dominantSpeakerEndpoint);
- }
- else if ("InLastNChangeEvent" === colibriClass)
- {
- var oldValue = obj.oldValue;
- var newValue = obj.newValue;
- // Make sure that oldValue and newValue are of type boolean.
- var type;
-
- if ((type = typeof oldValue) !== 'boolean') {
- if (type === 'string') {
- oldValue = (oldValue == "true");
- } else {
- oldValue = new Boolean(oldValue).valueOf();
- }
- }
- if ((type = typeof newValue) !== 'boolean') {
- if (type === 'string') {
- newValue = (newValue == "true");
- } else {
- newValue = new Boolean(newValue).valueOf();
- }
- }
-
- eventEmitter.emit(RTCEvents.LASTN_CHANGED, oldValue, newValue);
- }
- else if ("LastNEndpointsChangeEvent" === colibriClass)
- {
- // The new/latest list of last-n endpoint IDs.
- var lastNEndpoints = obj.lastNEndpoints;
- // The list of endpoint IDs which are entering the list of
- // last-n at this time i.e. were not in the old list of last-n
- // endpoint IDs.
- var endpointsEnteringLastN = obj.endpointsEnteringLastN;
- var stream = obj.stream;
-
- console.log(
- "Data channel new last-n event: ",
- lastNEndpoints, endpointsEnteringLastN, obj);
- eventEmitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED,
- lastNEndpoints, endpointsEnteringLastN, obj);
- }
- else
- {
- console.debug("Data channel JSON-formatted message: ", obj);
- }
- }
- };
-
- dataChannel.onclose = function ()
- {
- console.info("The Data Channel closed", dataChannel);
- var idx = _dataChannels.indexOf(dataChannel);
- if (idx > -1)
- _dataChannels = _dataChannels.splice(idx, 1);
- };
- _dataChannels.push(dataChannel);
- },
-
- /**
- * Binds "ondatachannel" event listener to given PeerConnection instance.
- * @param peerConnection WebRTC peer connection instance.
- */
- init: function (peerConnection, emitter) {
- if(!config.openSctp)
- return;
-
- peerConnection.ondatachannel = this.onDataChannel;
- eventEmitter = emitter;
-
- // Sample code for opening new data channel from Jitsi Meet to the bridge.
- // Although it's not a requirement to open separate channels from both bridge
- // and peer as single channel can be used for sending and receiving data.
- // So either channel opened by the bridge or the one opened here is enough
- // for communication with the bridge.
- /*var dataChannelOptions =
- {
- reliable: true
- };
- var dataChannel
- = peerConnection.createDataChannel("myChannel", dataChannelOptions);
-
- // Can be used only when is in open state
- dataChannel.onopen = function ()
- {
- dataChannel.send("My channel !!!");
- };
- dataChannel.onmessage = function (event)
- {
- var msgData = event.data;
- console.info("Got My Data Channel Message:", msgData, dataChannel);
- };*/
- },
- handleSelectedEndpointEvent: onSelectedEndpointChanged,
- handlePinnedEndpointEvent: onPinnedEndpointChanged
-
-};
-
-function onSelectedEndpointChanged(userResource)
-{
- console.log('selected endpoint changed: ', userResource);
- if (_dataChannels && _dataChannels.length != 0)
- {
- _dataChannels.some(function (dataChannel) {
- if (dataChannel.readyState == 'open')
- {
- console.log('sending selected endpoint changed '
- + 'notification to the bridge: ', userResource);
- dataChannel.send(JSON.stringify({
- 'colibriClass': 'SelectedEndpointChangedEvent',
- 'selectedEndpoint':
- (!userResource || userResource === null)?
- null : userResource
- }));
-
- return true;
- }
- });
- }
-}
-
-function onPinnedEndpointChanged(userResource)
-{
- console.log('pinned endpoint changed: ', userResource);
- if (_dataChannels && _dataChannels.length != 0)
- {
- _dataChannels.some(function (dataChannel) {
- if (dataChannel.readyState == 'open')
- {
- dataChannel.send(JSON.stringify({
- 'colibriClass': 'PinnedEndpointChangedEvent',
- 'pinnedEndpoint':
- (!userResource || userResource == null)?
- null : userResource
- }));
-
- return true;
- }
- });
- }
-}
-
-module.exports = DataChannels;
-
+function onPinnedEndpointChanged(userResource)
+{
+ console.log('pinned endpoint changed: ', userResource);
+ if (_dataChannels && _dataChannels.length != 0)
+ {
+ _dataChannels.some(function (dataChannel) {
+ if (dataChannel.readyState == 'open')
+ {
+ dataChannel.send(JSON.stringify({
+ 'colibriClass': 'PinnedEndpointChangedEvent',
+ 'pinnedEndpoint':
+ (!userResource || userResource == null)?
+ null : userResource
+ }));
-},{"../../service/RTC/RTCEvents":101}],7:[function(require,module,exports){
-var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
-var RTCEvents = require("../../service/RTC/RTCEvents");
-
-
-function LocalStream(stream, type, eventEmitter, videoType, isGUMStream)
-{
- this.stream = stream;
- this.eventEmitter = eventEmitter;
- this.type = type;
- this.videoType = videoType;
- this.isGUMStream = true;
- if(isGUMStream === false)
- this.isGUMStream = isGUMStream;
- var self = this;
- if(type == "audio")
- {
- this.getTracks = function () {
- return self.stream.getAudioTracks();
- };
- }
- else
- {
- this.getTracks = function () {
- return self.stream.getVideoTracks();
- };
- }
-
- this.stream.onended = function()
- {
- self.streamEnded();
- };
-}
-
-LocalStream.prototype.streamEnded = function () {
- this.eventEmitter.emit(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, this);
-}
-
-LocalStream.prototype.getOriginalStream = function()
-{
- return this.stream;
-}
-
-LocalStream.prototype.isAudioStream = function () {
- return this.type === "audio";
-};
-
-LocalStream.prototype.setMute = function(mute)
-{
-
- if((window.location.protocol != "https:" && this.isGUMStream) ||
- (this.isAudioStream() && this.isGUMStream) || this.videoType === "screen")
- {
- var tracks = this.getTracks();
-
- for (var idx = 0; idx < tracks.length; idx++) {
- tracks[idx].enabled = mute;
- }
- this.eventEmitter.emit(
- (this.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE),
- !mute);
- }
- else
- {
- if(mute === false) {
- APP.xmpp.removeStream(this.stream);
- this.stream.stop();
- this.eventEmitter.emit(
- (this.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE),
- true);
- }
- else
- {
- var self = this;
- APP.RTC.rtcUtils.obtainAudioAndVideoPermissions(
- (this.isAudioStream() ? ["audio"] : ["video"]),
- function (stream) {
- if(self.isAudioStream())
- {
- APP.RTC.changeLocalAudio(stream,
- function () {
- self.eventEmitter.emit(
- (self.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE),
- true);
- });
- }
- else
- {
- APP.RTC.changeLocalVideo(stream, false,
- function () {
- self.eventEmitter.emit(
- (self.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE),
- true);
- });
- }
- });
- }
- }
-};
-
-LocalStream.prototype.isMuted = function () {
- var tracks = [];
- if(this.type == "audio")
- {
- tracks = this.stream.getAudioTracks();
- }
- else
- {
- if(this.stream.ended)
- return true;
- tracks = this.stream.getVideoTracks();
- }
- for (var idx = 0; idx < tracks.length; idx++) {
- if(tracks[idx].enabled)
- return false;
- }
- return true;
-}
-
-LocalStream.prototype.getId = function () {
- return this.stream.getTracks()[0].id;
-}
-
-module.exports = LocalStream;
+ return true;
+ }
+ });
+ }
+}
-},{"../../service/RTC/RTCEvents":101,"../../service/RTC/StreamEventTypes.js":103}],8:[function(require,module,exports){
-////These lines should be uncommented when require works in app.js
-var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
-var StreamEventType = require("../../service/RTC/StreamEventTypes");
-
-/**
- * Creates a MediaStream object for the given data, session id and ssrc.
- * It is a wrapper class for the MediaStream.
- *
- * @param data the data object from which we obtain the stream,
- * the peerjid, etc.
- * @param sid the session id
- * @param ssrc the ssrc corresponding to this MediaStream
- *
- * @constructor
- */
-function MediaStream(data, sid, ssrc, browser, eventEmitter) {
-
- // XXX(gp) to minimize headaches in the future, we should build our
- // abstractions around tracks and not streams. ORTC is track based API.
- // Mozilla expects m-lines to represent media tracks.
- //
- // Practically, what I'm saying is that we should have a MediaTrack class
- // and not a MediaStream class.
- //
- // Also, we should be able to associate multiple SSRCs with a MediaTrack as
- // a track might have an associated RTX and FEC sources.
-
- this.sid = sid;
- this.stream = data.stream;
- this.peerjid = data.peerjid;
- this.videoType = data.videoType;
- this.ssrc = ssrc;
- this.type = (this.stream.getVideoTracks().length > 0)?
- MediaStreamType.VIDEO_TYPE : MediaStreamType.AUDIO_TYPE;
- this.muted = false;
- this.eventEmitter = eventEmitter;
-}
-
-
-MediaStream.prototype.getOriginalStream = function()
-{
- return this.stream;
-};
-
-MediaStream.prototype.setMute = function (value)
-{
- this.stream.muted = value;
- this.muted = value;
-};
-
-module.exports = MediaStream;
+module.exports = DataChannels;
-},{"../../service/RTC/MediaStreamTypes":100,"../../service/RTC/StreamEventTypes":103}],9:[function(require,module,exports){
-var EventEmitter = require("events");
-var RTCBrowserType = require("./RTCBrowserType");
-var RTCUtils = require("./RTCUtils.js");
-var LocalStream = require("./LocalStream.js");
-var DataChannels = require("./DataChannels");
-var MediaStream = require("./MediaStream.js");
-var DesktopSharingEventTypes
- = require("../../service/desktopsharing/DesktopSharingEventTypes");
-var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
-var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
-var RTCEvents = require("../../service/RTC/RTCEvents.js");
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-var UIEvents = require("../../service/UI/UIEvents");
-
-var eventEmitter = new EventEmitter();
-
-
-function getMediaStreamUsage()
-{
- var result = {
- audio: true,
- video: true
- };
-
- /** There are some issues with the desktop sharing
- * when this property is enabled.
- * WARNING: We must change the implementation to start video/audio if we
- * receive from the focus that the peer is not muted.
-
- var isSecureConnection = window.location.protocol == "https:";
-
- if(config.disableEarlyMediaPermissionRequests || !isSecureConnection)
- {
- result = {
- audio: false,
- video: false
- };
-
- }
- **/
-
- return result;
-}
-
-var RTC = {
- rtcUtils: null,
- devices: {
- audio: true,
- video: true
- },
- localStreams: [],
- remoteStreams: {},
- localAudio: null,
- localVideo: null,
- addStreamListener: function (listener, eventType) {
- eventEmitter.on(eventType, listener);
- },
- addListener: function (type, listener) {
- eventEmitter.on(type, listener);
- },
- removeStreamListener: function (listener, eventType) {
- if(!(eventType instanceof StreamEventTypes))
- throw "Illegal argument";
-
- eventEmitter.removeListener(eventType, listener);
- },
- createLocalStream: function (stream, type, change, videoType, isMuted, isGUMStream) {
-
- var localStream = new LocalStream(stream, type, eventEmitter, videoType, isGUMStream);
- //in firefox we have only one stream object
- if(this.localStreams.length == 0 ||
- this.localStreams[0].getOriginalStream() != stream)
- this.localStreams.push(localStream);
- if(isMuted === true)
- localStream.setMute(false);
-
- if(type == "audio")
- {
- this.localAudio = localStream;
- }
- else
- {
- this.localVideo = localStream;
- }
- var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED;
- if(change)
- eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED;
-
- eventEmitter.emit(eventType, localStream, isMuted);
- return localStream;
- },
- removeLocalStream: function (stream) {
- for(var i = 0; i < this.localStreams.length; i++)
- {
- if(this.localStreams[i].getOriginalStream() === stream) {
- delete this.localStreams[i];
- return;
- }
- }
- },
- createRemoteStream: function (data, sid, thessrc) {
- var remoteStream = new MediaStream(data, sid, thessrc,
- RTCBrowserType.getBrowserType(), eventEmitter);
- var jid = data.peerjid || APP.xmpp.myJid();
- if(!this.remoteStreams[jid]) {
- this.remoteStreams[jid] = {};
- }
- this.remoteStreams[jid][remoteStream.type]= remoteStream;
- eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, remoteStream);
- return remoteStream;
- },
- getPCConstraints: function () {
- return this.rtcUtils.pc_constraints;
- },
- getUserMediaWithConstraints:function(um, success_callback,
- failure_callback, resolution,
- bandwidth, fps, desktopStream)
- {
- return this.rtcUtils.getUserMediaWithConstraints(um, success_callback,
- failure_callback, resolution, bandwidth, fps, desktopStream);
- },
- attachMediaStream: function (elSelector, stream) {
- this.rtcUtils.attachMediaStream(elSelector, stream);
- },
- getStreamID: function (stream) {
- return this.rtcUtils.getStreamID(stream);
- },
- getVideoSrc: function (element) {
- return this.rtcUtils.getVideoSrc(element);
- },
- setVideoSrc: function (element, src) {
- this.rtcUtils.setVideoSrc(element, src);
- },
- getVideoElementName: function () {
- return RTCBrowserType.isTemasysPluginUsed() ? 'object' : 'video';
- },
- dispose: function() {
- if (this.rtcUtils) {
- this.rtcUtils = null;
- }
- },
- stop: function () {
- this.dispose();
- },
- start: function () {
- var self = this;
- APP.desktopsharing.addListener(
- function (stream, isUsingScreenStream, callback) {
- self.changeLocalVideo(stream, isUsingScreenStream, callback);
- }, DesktopSharingEventTypes.NEW_STREAM_CREATED);
- APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function(event) {
- DataChannels.init(event.peerconnection, eventEmitter);
- });
- APP.UI.addListener(UIEvents.SELECTED_ENDPOINT,
- DataChannels.handleSelectedEndpointEvent);
- APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
- DataChannels.handlePinnedEndpointEvent);
-
- // In case of IE we continue from 'onReady' callback
- // passed to RTCUtils constructor. It will be invoked by Temasys plugin
- // once it is initialized.
- var onReady = function () {
- eventEmitter.emit(RTCEvents.RTC_READY, true);
- self.rtcUtils.obtainAudioAndVideoPermissions(
- null, null, getMediaStreamUsage());
- };
-
- this.rtcUtils = new RTCUtils(this, onReady);
-
- // Call onReady() if Temasys plugin is not used
- if (!RTCBrowserType.isTemasysPluginUsed()) {
- onReady();
- }
- },
- muteRemoteVideoStream: function (jid, value) {
- var stream;
-
- if(this.remoteStreams[jid] &&
- this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE])
- {
- stream = this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
- }
-
- if(!stream)
- return true;
-
- if (value != stream.muted) {
- stream.setMute(value);
- return true;
- }
- return false;
- },
- switchVideoStreams: function (new_stream) {
- this.localVideo.stream = new_stream;
-
- this.localStreams = [];
-
- //in firefox we have only one stream object
- if (this.localAudio.getOriginalStream() != new_stream)
- this.localStreams.push(this.localAudio);
- this.localStreams.push(this.localVideo);
- },
- changeLocalVideo: function (stream, isUsingScreenStream, callback) {
- var oldStream = this.localVideo.getOriginalStream();
- var type = (isUsingScreenStream? "screen" : "video");
- var localCallback = callback;
- if(this.localVideo.isMuted() && this.localVideo.videoType !== type)
- {
- localCallback = function() {
- APP.xmpp.setVideoMute(false, function(mute) {
- eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute);
- });
-
- callback();
- };
- }
- // FIXME: Workaround for FF/IE/Safari
- if (stream && stream.videoStream) {
- stream = stream.videoStream;
- }
- var videoStream = this.rtcUtils.createStream(stream, true);
- this.localVideo = this.createLocalStream(videoStream, "video", true, type);
- // Stop the stream to trigger onended event for old stream
- oldStream.stop();
-
- this.switchVideoStreams(videoStream, oldStream);
-
- APP.xmpp.switchStreams(videoStream, oldStream,localCallback);
- },
- changeLocalAudio: function (stream, callback) {
- var oldStream = this.localAudio.getOriginalStream();
- var newStream = this.rtcUtils.createStream(stream);
- this.localAudio = this.createLocalStream(newStream, "audio", true);
- // Stop the stream to trigger onended event for old stream
- oldStream.stop();
- APP.xmpp.switchStreams(newStream, oldStream, callback, true);
- },
- isVideoMuted: function (jid) {
- if (jid === APP.xmpp.myJid()) {
- var localVideo = APP.RTC.localVideo;
- return (!localVideo || localVideo.isMuted());
- }
- else
- {
- if (!APP.RTC.remoteStreams[jid] || !APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
- return null;
- }
- return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted;
- }
- },
- /**
- * Checks if video identified by given src is desktop stream.
- * @param videoSrc eg.
- * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
- * @returns {boolean}
- */
- isVideoSrcDesktop: function (jid) {
- if(!jid)
- return false;
- var isDesktop = false;
- var stream = null;
- if (APP.xmpp.myJid() === jid) {
- // local video
- stream = this.localVideo;
- } else {
- var peerStreams = this.remoteStreams[jid];
- if(!peerStreams)
- return false;
- stream = peerStreams[MediaStreamType.VIDEO_TYPE];
- }
-
- if(stream)
- isDesktop = (stream.videoType === "screen");
-
- return isDesktop;
- },
- setVideoMute: function(mute, callback, options) {
- if(!this.localVideo)
- return;
-
- if (mute == APP.RTC.localVideo.isMuted())
- {
- APP.xmpp.sendVideoInfoPresence(mute);
- if(callback)
- callback(mute);
- }
- else
- {
- APP.RTC.localVideo.setMute(!mute);
- APP.xmpp.setVideoMute(
- mute,
- callback,
- options);
- }
- },
- setDeviceAvailability: function (devices) {
- if(!devices)
- return;
- if(devices.audio === true || devices.audio === false)
- this.devices.audio = devices.audio;
- if(devices.video === true || devices.video === false)
- this.devices.video = devices.video;
- eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, this.devices);
- }
-};
-
-module.exports = RTC;
-},{"../../service/RTC/MediaStreamTypes":100,"../../service/RTC/RTCEvents.js":101,"../../service/RTC/StreamEventTypes.js":103,"../../service/UI/UIEvents":104,"../../service/desktopsharing/DesktopSharingEventTypes":107,"../../service/xmpp/XMPPEvents":110,"./DataChannels":6,"./LocalStream.js":7,"./MediaStream.js":8,"./RTCBrowserType":10,"./RTCUtils.js":11,"events":1}],10:[function(require,module,exports){
-
-var currentBrowser;
-
-var browserVersion;
-
-var RTCBrowserType = {
-
- RTC_BROWSER_CHROME: "rtc_browser.chrome",
-
- RTC_BROWSER_OPERA: "rtc_browser.opera",
-
- RTC_BROWSER_FIREFOX: "rtc_browser.firefox",
-
- RTC_BROWSER_IEXPLORER: "rtc_browser.iexplorer",
-
- RTC_BROWSER_SAFARI: "rtc_browser.safari",
-
- getBrowserType: function () {
- return currentBrowser;
- },
-
- isChrome: function () {
- return currentBrowser === RTCBrowserType.RTC_BROWSER_CHROME;
- },
-
- isOpera: function () {
- return currentBrowser === RTCBrowserType.RTC_BROWSER_OPERA;
- },
- isFirefox: function () {
- return currentBrowser === RTCBrowserType.RTC_BROWSER_FIREFOX;
- },
-
- isIExplorer: function () {
- return currentBrowser === RTCBrowserType.RTC_BROWSER_IEXPLORER;
- },
-
- isSafari: function () {
- return currentBrowser === RTCBrowserType.RTC_BROWSER_SAFARI;
- },
- isTemasysPluginUsed: function () {
- return RTCBrowserType.isIExplorer() || RTCBrowserType.isSafari();
- },
- getFirefoxVersion: function () {
- return RTCBrowserType.isFirefox() ? browserVersion : null;
- },
-
- getChromeVersion: function () {
- return RTCBrowserType.isChrome() ? browserVersion : null;
- },
-
- usesPlanB: function() {
- return RTCBrowserType.isChrome() || RTCBrowserType.isOpera()
- || RTCBrowserType.isTemasysPluginUsed();
- },
-
- usesUnifiedPlan: function() {
- return RTCBrowserType.isFirefox();
- }
-
- // Add version getters for other browsers when needed
-};
-
-// detectOpera() must be called before detectChrome() !!!
-// otherwise Opera wil be detected as Chrome
-function detectChrome() {
- if (navigator.webkitGetUserMedia) {
- currentBrowser = RTCBrowserType.RTC_BROWSER_CHROME;
- var userAgent = navigator.userAgent.toLowerCase();
- // We can assume that user agent is chrome, because it's
- // enforced when 'ext' streaming method is set
- var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10);
- console.log("This appears to be Chrome, ver: " + ver);
- return ver;
- }
- return null;
-}
-
-function detectOpera() {
- var userAgent = navigator.userAgent;
- if (userAgent.match(/Opera|OPR/)) {
- currentBrowser = RTCBrowserType.RTC_BROWSER_OPERA;
- var version = userAgent.match(/(Opera|OPR) ?\/?(\d+)\.?/)[2];
- console.info("This appears to be Opera, ver: " + version);
- return version;
- }
- return null;
-}
-
-function detectFirefox() {
- if (navigator.mozGetUserMedia) {
- currentBrowser = RTCBrowserType.RTC_BROWSER_FIREFOX;
- var version = parseInt(
- navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
- console.log('This appears to be Firefox, ver: ' + version);
- return version;
- }
- return null;
-}
-
-function detectSafari() {
- if ((/^((?!chrome).)*safari/i.test(navigator.userAgent))) {
- currentBrowser = RTCBrowserType.RTC_BROWSER_SAFARI;
- console.info("This appears to be Safari");
- // FIXME detect Safari version when needed
- return 1;
- }
- return null;
-}
-
-function detectIE() {
- var version;
- var ua = window.navigator.userAgent;
-
- var msie = ua.indexOf('MSIE ');
- if (msie > 0) {
- // IE 10 or older => return version number
- version = parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
- }
-
- var trident = ua.indexOf('Trident/');
- if (!version && trident > 0) {
- // IE 11 => return version number
- var rv = ua.indexOf('rv:');
- version = parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
- }
-
- var edge = ua.indexOf('Edge/');
- if (!version && edge > 0) {
- // IE 12 => return version number
- version = parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
- }
-
- if (version) {
- currentBrowser = RTCBrowserType.RTC_BROWSER_IEXPLORER;
- console.info("This appears to be IExplorer, ver: " + version);
- }
- return version;
-}
-
-function detectBrowser() {
- var version;
- var detectors = [
- detectOpera,
- detectChrome,
- detectFirefox,
- detectIE,
- detectSafari
- ];
- // Try all browser detectors
- for (var i = 0; i < detectors.length; i++) {
- version = detectors[i]();
- if (version)
- return version;
- }
- console.error("Failed to detect browser type");
- return undefined;
-}
-
-browserVersion = detectBrowser();
-
+},{"../../service/RTC/RTCEvents":103}],5:[function(require,module,exports){
+var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
+var RTCEvents = require("../../service/RTC/RTCEvents");
+
+
+function LocalStream(stream, type, eventEmitter, videoType, isGUMStream)
+{
+ this.stream = stream;
+ this.eventEmitter = eventEmitter;
+ this.type = type;
+ this.videoType = videoType;
+ this.isGUMStream = true;
+ if(isGUMStream === false)
+ this.isGUMStream = isGUMStream;
+ var self = this;
+ if(type == "audio")
+ {
+ this.getTracks = function () {
+ return self.stream.getAudioTracks();
+ };
+ }
+ else
+ {
+ this.getTracks = function () {
+ return self.stream.getVideoTracks();
+ };
+ }
+
+ this.stream.onended = function()
+ {
+ self.streamEnded();
+ };
+}
+
+LocalStream.prototype.streamEnded = function () {
+ this.eventEmitter.emit(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, this);
+}
+
+LocalStream.prototype.getOriginalStream = function()
+{
+ return this.stream;
+}
+
+LocalStream.prototype.isAudioStream = function () {
+ return this.type === "audio";
+};
+
+LocalStream.prototype.setMute = function(mute)
+{
+
+ if((window.location.protocol != "https:" && this.isGUMStream) ||
+ (this.isAudioStream() && this.isGUMStream) || this.videoType === "screen")
+ {
+ var tracks = this.getTracks();
+
+ for (var idx = 0; idx < tracks.length; idx++) {
+ tracks[idx].enabled = mute;
+ }
+ this.eventEmitter.emit(
+ (this.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE),
+ !mute);
+ }
+ else
+ {
+ if(mute === false) {
+ APP.xmpp.removeStream(this.stream);
+ this.stream.stop();
+ this.eventEmitter.emit(
+ (this.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE),
+ true);
+ }
+ else
+ {
+ var self = this;
+ APP.RTC.rtcUtils.obtainAudioAndVideoPermissions(
+ (this.isAudioStream() ? ["audio"] : ["video"]),
+ function (stream) {
+ if(self.isAudioStream())
+ {
+ APP.RTC.changeLocalAudio(stream,
+ function () {
+ self.eventEmitter.emit(
+ (self.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE),
+ true);
+ });
+ }
+ else
+ {
+ APP.RTC.changeLocalVideo(stream, false,
+ function () {
+ self.eventEmitter.emit(
+ (self.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE),
+ true);
+ });
+ }
+ });
+ }
+ }
+};
+
+LocalStream.prototype.isMuted = function () {
+ var tracks = [];
+ if(this.type == "audio")
+ {
+ tracks = this.stream.getAudioTracks();
+ }
+ else
+ {
+ if(this.stream.ended)
+ return true;
+ tracks = this.stream.getVideoTracks();
+ }
+ for (var idx = 0; idx < tracks.length; idx++) {
+ if(tracks[idx].enabled)
+ return false;
+ }
+ return true;
+}
+
+LocalStream.prototype.getId = function () {
+ return this.stream.getTracks()[0].id;
+}
+
+module.exports = LocalStream;
+
+},{"../../service/RTC/RTCEvents":103,"../../service/RTC/StreamEventTypes.js":105}],6:[function(require,module,exports){
+////These lines should be uncommented when require works in app.js
+var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
+var StreamEventType = require("../../service/RTC/StreamEventTypes");
+
+/**
+ * Creates a MediaStream object for the given data, session id and ssrc.
+ * It is a wrapper class for the MediaStream.
+ *
+ * @param data the data object from which we obtain the stream,
+ * the peerjid, etc.
+ * @param sid the session id
+ * @param ssrc the ssrc corresponding to this MediaStream
+ *
+ * @constructor
+ */
+function MediaStream(data, sid, ssrc, browser, eventEmitter) {
+
+ // XXX(gp) to minimize headaches in the future, we should build our
+ // abstractions around tracks and not streams. ORTC is track based API.
+ // Mozilla expects m-lines to represent media tracks.
+ //
+ // Practically, what I'm saying is that we should have a MediaTrack class
+ // and not a MediaStream class.
+ //
+ // Also, we should be able to associate multiple SSRCs with a MediaTrack as
+ // a track might have an associated RTX and FEC sources.
+
+ this.sid = sid;
+ this.stream = data.stream;
+ this.peerjid = data.peerjid;
+ this.videoType = data.videoType;
+ this.ssrc = ssrc;
+ this.type = (this.stream.getVideoTracks().length > 0)?
+ MediaStreamType.VIDEO_TYPE : MediaStreamType.AUDIO_TYPE;
+ this.muted = false;
+ this.eventEmitter = eventEmitter;
+}
+
+
+MediaStream.prototype.getOriginalStream = function()
+{
+ return this.stream;
+};
+
+MediaStream.prototype.setMute = function (value)
+{
+ this.stream.muted = value;
+ this.muted = value;
+};
+
+module.exports = MediaStream;
+
+},{"../../service/RTC/MediaStreamTypes":102,"../../service/RTC/StreamEventTypes":105}],7:[function(require,module,exports){
+var EventEmitter = require("events");
+var RTCBrowserType = require("./RTCBrowserType");
+var RTCUtils = require("./RTCUtils.js");
+var LocalStream = require("./LocalStream.js");
+var DataChannels = require("./DataChannels");
+var MediaStream = require("./MediaStream.js");
+var DesktopSharingEventTypes
+ = require("../../service/desktopsharing/DesktopSharingEventTypes");
+var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
+var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
+var RTCEvents = require("../../service/RTC/RTCEvents.js");
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+var UIEvents = require("../../service/UI/UIEvents");
+
+var eventEmitter = new EventEmitter();
+
+
+function getMediaStreamUsage()
+{
+ var result = {
+ audio: true,
+ video: true
+ };
+
+ /** There are some issues with the desktop sharing
+ * when this property is enabled.
+ * WARNING: We must change the implementation to start video/audio if we
+ * receive from the focus that the peer is not muted.
+
+ var isSecureConnection = window.location.protocol == "https:";
+
+ if(config.disableEarlyMediaPermissionRequests || !isSecureConnection)
+ {
+ result = {
+ audio: false,
+ video: false
+ };
+
+ }
+ **/
+
+ return result;
+}
+
+var RTC = {
+ rtcUtils: null,
+ devices: {
+ audio: true,
+ video: true
+ },
+ localStreams: [],
+ remoteStreams: {},
+ localAudio: null,
+ localVideo: null,
+ addStreamListener: function (listener, eventType) {
+ eventEmitter.on(eventType, listener);
+ },
+ addListener: function (type, listener) {
+ eventEmitter.on(type, listener);
+ },
+ removeStreamListener: function (listener, eventType) {
+ if(!(eventType instanceof StreamEventTypes))
+ throw "Illegal argument";
+
+ eventEmitter.removeListener(eventType, listener);
+ },
+ createLocalStream: function (stream, type, change, videoType, isMuted, isGUMStream) {
+
+ var localStream = new LocalStream(stream, type, eventEmitter, videoType, isGUMStream);
+ //in firefox we have only one stream object
+ if(this.localStreams.length == 0 ||
+ this.localStreams[0].getOriginalStream() != stream)
+ this.localStreams.push(localStream);
+ if(isMuted === true)
+ localStream.setMute(false);
+
+ if(type == "audio")
+ {
+ this.localAudio = localStream;
+ }
+ else
+ {
+ this.localVideo = localStream;
+ }
+ var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED;
+ if(change)
+ eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED;
+
+ eventEmitter.emit(eventType, localStream, isMuted);
+ return localStream;
+ },
+ removeLocalStream: function (stream) {
+ for(var i = 0; i < this.localStreams.length; i++)
+ {
+ if(this.localStreams[i].getOriginalStream() === stream) {
+ delete this.localStreams[i];
+ return;
+ }
+ }
+ },
+ createRemoteStream: function (data, sid, thessrc) {
+ var remoteStream = new MediaStream(data, sid, thessrc,
+ RTCBrowserType.getBrowserType(), eventEmitter);
+ var jid = data.peerjid || APP.xmpp.myJid();
+ if(!this.remoteStreams[jid]) {
+ this.remoteStreams[jid] = {};
+ }
+ this.remoteStreams[jid][remoteStream.type]= remoteStream;
+ eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, remoteStream);
+ return remoteStream;
+ },
+ getPCConstraints: function () {
+ return this.rtcUtils.pc_constraints;
+ },
+ getUserMediaWithConstraints:function(um, success_callback,
+ failure_callback, resolution,
+ bandwidth, fps, desktopStream)
+ {
+ return this.rtcUtils.getUserMediaWithConstraints(um, success_callback,
+ failure_callback, resolution, bandwidth, fps, desktopStream);
+ },
+ attachMediaStream: function (elSelector, stream) {
+ this.rtcUtils.attachMediaStream(elSelector, stream);
+ },
+ getStreamID: function (stream) {
+ return this.rtcUtils.getStreamID(stream);
+ },
+ getVideoSrc: function (element) {
+ return this.rtcUtils.getVideoSrc(element);
+ },
+ setVideoSrc: function (element, src) {
+ this.rtcUtils.setVideoSrc(element, src);
+ },
+ getVideoElementName: function () {
+ return RTCBrowserType.isTemasysPluginUsed() ? 'object' : 'video';
+ },
+ dispose: function() {
+ if (this.rtcUtils) {
+ this.rtcUtils = null;
+ }
+ },
+ stop: function () {
+ this.dispose();
+ },
+ start: function () {
+ var self = this;
+ APP.desktopsharing.addListener(
+ function (stream, isUsingScreenStream, callback) {
+ self.changeLocalVideo(stream, isUsingScreenStream, callback);
+ }, DesktopSharingEventTypes.NEW_STREAM_CREATED);
+ APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function(event) {
+ DataChannels.init(event.peerconnection, eventEmitter);
+ });
+ APP.UI.addListener(UIEvents.SELECTED_ENDPOINT,
+ DataChannels.handleSelectedEndpointEvent);
+ APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
+ DataChannels.handlePinnedEndpointEvent);
+
+ // In case of IE we continue from 'onReady' callback
+ // passed to RTCUtils constructor. It will be invoked by Temasys plugin
+ // once it is initialized.
+ var onReady = function () {
+ eventEmitter.emit(RTCEvents.RTC_READY, true);
+ self.rtcUtils.obtainAudioAndVideoPermissions(
+ null, null, getMediaStreamUsage());
+ };
+
+ this.rtcUtils = new RTCUtils(this, onReady);
+
+ // Call onReady() if Temasys plugin is not used
+ if (!RTCBrowserType.isTemasysPluginUsed()) {
+ onReady();
+ }
+ },
+ muteRemoteVideoStream: function (jid, value) {
+ var stream;
+
+ if(this.remoteStreams[jid] &&
+ this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE])
+ {
+ stream = this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
+ }
+
+ if(!stream)
+ return true;
+
+ if (value != stream.muted) {
+ stream.setMute(value);
+ return true;
+ }
+ return false;
+ },
+ switchVideoStreams: function (new_stream) {
+ this.localVideo.stream = new_stream;
+
+ this.localStreams = [];
+
+ //in firefox we have only one stream object
+ if (this.localAudio.getOriginalStream() != new_stream)
+ this.localStreams.push(this.localAudio);
+ this.localStreams.push(this.localVideo);
+ },
+ changeLocalVideo: function (stream, isUsingScreenStream, callback) {
+ var oldStream = this.localVideo.getOriginalStream();
+ var type = (isUsingScreenStream? "screen" : "video");
+ var localCallback = callback;
+ if(this.localVideo.isMuted() && this.localVideo.videoType !== type)
+ {
+ localCallback = function() {
+ APP.xmpp.setVideoMute(false, function(mute) {
+ eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute);
+ });
+
+ callback();
+ };
+ }
+ // FIXME: Workaround for FF/IE/Safari
+ if (stream && stream.videoStream) {
+ stream = stream.videoStream;
+ }
+ var videoStream = this.rtcUtils.createStream(stream, true);
+ this.localVideo = this.createLocalStream(videoStream, "video", true, type);
+ // Stop the stream to trigger onended event for old stream
+ oldStream.stop();
+
+ this.switchVideoStreams(videoStream, oldStream);
+
+ APP.xmpp.switchStreams(videoStream, oldStream,localCallback);
+ },
+ changeLocalAudio: function (stream, callback) {
+ var oldStream = this.localAudio.getOriginalStream();
+ var newStream = this.rtcUtils.createStream(stream);
+ this.localAudio = this.createLocalStream(newStream, "audio", true);
+ // Stop the stream to trigger onended event for old stream
+ oldStream.stop();
+ APP.xmpp.switchStreams(newStream, oldStream, callback, true);
+ },
+ isVideoMuted: function (jid) {
+ if (jid === APP.xmpp.myJid()) {
+ var localVideo = APP.RTC.localVideo;
+ return (!localVideo || localVideo.isMuted());
+ }
+ else
+ {
+ if (!APP.RTC.remoteStreams[jid] || !APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
+ return null;
+ }
+ return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted;
+ }
+ },
+ /**
+ * Checks if video identified by given src is desktop stream.
+ * @param videoSrc eg.
+ * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
+ * @returns {boolean}
+ */
+ isVideoSrcDesktop: function (jid) {
+ if(!jid)
+ return false;
+ var isDesktop = false;
+ var stream = null;
+ if (APP.xmpp.myJid() === jid) {
+ // local video
+ stream = this.localVideo;
+ } else {
+ var peerStreams = this.remoteStreams[jid];
+ if(!peerStreams)
+ return false;
+ stream = peerStreams[MediaStreamType.VIDEO_TYPE];
+ }
+
+ if(stream)
+ isDesktop = (stream.videoType === "screen");
+
+ return isDesktop;
+ },
+ setVideoMute: function(mute, callback, options) {
+ if(!this.localVideo)
+ return;
+
+ if (mute == APP.RTC.localVideo.isMuted())
+ {
+ APP.xmpp.sendVideoInfoPresence(mute);
+ if(callback)
+ callback(mute);
+ }
+ else
+ {
+ APP.RTC.localVideo.setMute(!mute);
+ APP.xmpp.setVideoMute(
+ mute,
+ callback,
+ options);
+ }
+ },
+ setDeviceAvailability: function (devices) {
+ if(!devices)
+ return;
+ if(devices.audio === true || devices.audio === false)
+ this.devices.audio = devices.audio;
+ if(devices.video === true || devices.video === false)
+ this.devices.video = devices.video;
+ eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, this.devices);
+ }
+};
+
+module.exports = RTC;
+
+},{"../../service/RTC/MediaStreamTypes":102,"../../service/RTC/RTCEvents.js":103,"../../service/RTC/StreamEventTypes.js":105,"../../service/UI/UIEvents":106,"../../service/desktopsharing/DesktopSharingEventTypes":109,"../../service/xmpp/XMPPEvents":112,"./DataChannels":4,"./LocalStream.js":5,"./MediaStream.js":6,"./RTCBrowserType":8,"./RTCUtils.js":9,"events":113}],8:[function(require,module,exports){
+
+var currentBrowser;
+
+var browserVersion;
+
+var RTCBrowserType = {
+
+ RTC_BROWSER_CHROME: "rtc_browser.chrome",
+
+ RTC_BROWSER_OPERA: "rtc_browser.opera",
+
+ RTC_BROWSER_FIREFOX: "rtc_browser.firefox",
+
+ RTC_BROWSER_IEXPLORER: "rtc_browser.iexplorer",
+
+ RTC_BROWSER_SAFARI: "rtc_browser.safari",
+
+ getBrowserType: function () {
+ return currentBrowser;
+ },
+
+ isChrome: function () {
+ return currentBrowser === RTCBrowserType.RTC_BROWSER_CHROME;
+ },
+
+ isOpera: function () {
+ return currentBrowser === RTCBrowserType.RTC_BROWSER_OPERA;
+ },
+ isFirefox: function () {
+ return currentBrowser === RTCBrowserType.RTC_BROWSER_FIREFOX;
+ },
+
+ isIExplorer: function () {
+ return currentBrowser === RTCBrowserType.RTC_BROWSER_IEXPLORER;
+ },
+
+ isSafari: function () {
+ return currentBrowser === RTCBrowserType.RTC_BROWSER_SAFARI;
+ },
+ isTemasysPluginUsed: function () {
+ return RTCBrowserType.isIExplorer() || RTCBrowserType.isSafari();
+ },
+ getFirefoxVersion: function () {
+ return RTCBrowserType.isFirefox() ? browserVersion : null;
+ },
+
+ getChromeVersion: function () {
+ return RTCBrowserType.isChrome() ? browserVersion : null;
+ },
+
+ usesPlanB: function() {
+ return RTCBrowserType.isChrome() || RTCBrowserType.isOpera()
+ || RTCBrowserType.isTemasysPluginUsed();
+ },
+
+ usesUnifiedPlan: function() {
+ return RTCBrowserType.isFirefox();
+ }
+
+ // Add version getters for other browsers when needed
+};
+
+// detectOpera() must be called before detectChrome() !!!
+// otherwise Opera wil be detected as Chrome
+function detectChrome() {
+ if (navigator.webkitGetUserMedia) {
+ currentBrowser = RTCBrowserType.RTC_BROWSER_CHROME;
+ var userAgent = navigator.userAgent.toLowerCase();
+ // We can assume that user agent is chrome, because it's
+ // enforced when 'ext' streaming method is set
+ var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10);
+ console.log("This appears to be Chrome, ver: " + ver);
+ return ver;
+ }
+ return null;
+}
+
+function detectOpera() {
+ var userAgent = navigator.userAgent;
+ if (userAgent.match(/Opera|OPR/)) {
+ currentBrowser = RTCBrowserType.RTC_BROWSER_OPERA;
+ var version = userAgent.match(/(Opera|OPR) ?\/?(\d+)\.?/)[2];
+ console.info("This appears to be Opera, ver: " + version);
+ return version;
+ }
+ return null;
+}
+
+function detectFirefox() {
+ if (navigator.mozGetUserMedia) {
+ currentBrowser = RTCBrowserType.RTC_BROWSER_FIREFOX;
+ var version = parseInt(
+ navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
+ console.log('This appears to be Firefox, ver: ' + version);
+ return version;
+ }
+ return null;
+}
+
+function detectSafari() {
+ if ((/^((?!chrome).)*safari/i.test(navigator.userAgent))) {
+ currentBrowser = RTCBrowserType.RTC_BROWSER_SAFARI;
+ console.info("This appears to be Safari");
+ // FIXME detect Safari version when needed
+ return 1;
+ }
+ return null;
+}
+
+function detectIE() {
+ var version;
+ var ua = window.navigator.userAgent;
+
+ var msie = ua.indexOf('MSIE ');
+ if (msie > 0) {
+ // IE 10 or older => return version number
+ version = parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
+ }
+
+ var trident = ua.indexOf('Trident/');
+ if (!version && trident > 0) {
+ // IE 11 => return version number
+ var rv = ua.indexOf('rv:');
+ version = parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
+ }
+
+ var edge = ua.indexOf('Edge/');
+ if (!version && edge > 0) {
+ // IE 12 => return version number
+ version = parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
+ }
+
+ if (version) {
+ currentBrowser = RTCBrowserType.RTC_BROWSER_IEXPLORER;
+ console.info("This appears to be IExplorer, ver: " + version);
+ }
+ return version;
+}
+
+function detectBrowser() {
+ var version;
+ var detectors = [
+ detectOpera,
+ detectChrome,
+ detectFirefox,
+ detectIE,
+ detectSafari
+ ];
+ // Try all browser detectors
+ for (var i = 0; i < detectors.length; i++) {
+ version = detectors[i]();
+ if (version)
+ return version;
+ }
+ console.error("Failed to detect browser type");
+ return undefined;
+}
+
+browserVersion = detectBrowser();
+
module.exports = RTCBrowserType;
-},{}],11:[function(require,module,exports){
-var RTCBrowserType = require("./RTCBrowserType");
-var Resolutions = require("../../service/RTC/Resolutions");
-var AdapterJS = require("./adapter.screenshare");
-var SDPUtil = require("../xmpp/SDPUtil");
-
-var currentResolution = null;
-
-function getPreviousResolution(resolution) {
- if(!Resolutions[resolution])
- return null;
- var order = Resolutions[resolution].order;
- var res = null;
- var resName = null;
- for(var i in Resolutions)
- {
- var tmp = Resolutions[i];
- if(res == null || (res.order < tmp.order && tmp.order < order))
- {
- resName = i;
- res = tmp;
- }
- }
- return resName;
-}
-
-function setResolutionConstraints(constraints, resolution, isAndroid)
-{
- if (resolution && !constraints.video || isAndroid) {
- constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
- }
-
- if(Resolutions[resolution])
- {
- constraints.video.mandatory.minWidth = Resolutions[resolution].width;
- constraints.video.mandatory.minHeight = Resolutions[resolution].height;
- }
- else
- {
- if (isAndroid) {
- constraints.video.mandatory.minWidth = 320;
- constraints.video.mandatory.minHeight = 240;
- constraints.video.mandatory.maxFrameRate = 15;
- }
- }
-
- if (constraints.video.mandatory.minWidth)
- constraints.video.mandatory.maxWidth = constraints.video.mandatory.minWidth;
- if (constraints.video.mandatory.minHeight)
- constraints.video.mandatory.maxHeight = constraints.video.mandatory.minHeight;
-}
-
-function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid)
-{
- var constraints = {audio: false, video: false};
-
- if (um.indexOf('video') >= 0) {
- constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
- }
- if (um.indexOf('audio') >= 0) {
- constraints.audio = { mandatory: {}, optional: []};// same behaviour as true
- }
- if (um.indexOf('screen') >= 0) {
- if (RTCBrowserType.isChrome()) {
- constraints.video = {
- mandatory: {
- chromeMediaSource: "screen",
- googLeakyBucket: true,
- maxWidth: window.screen.width,
- maxHeight: window.screen.height,
- maxFrameRate: 3
- },
- optional: []
- };
- } else if (RTCBrowserType.isTemasysPluginUsed()) {
- constraints.video = {
- optional: [
- {
- sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey
- }
- ]
- };
- } else {
- console.error(
- "'screen' WebRTC media source is supported only in Chrome" +
- " and with Temasys plugin");
- }
- }
- if (um.indexOf('desktop') >= 0) {
- constraints.video = {
- mandatory: {
- chromeMediaSource: "desktop",
- chromeMediaSourceId: desktopStream,
- googLeakyBucket: true,
- maxWidth: window.screen.width,
- maxHeight: window.screen.height,
- maxFrameRate: 3
- },
- optional: []
- };
- }
-
- if (constraints.audio) {
- // if it is good enough for hangouts...
- constraints.audio.optional.push(
- {googEchoCancellation: true},
- {googAutoGainControl: true},
- {googNoiseSupression: true},
- {googHighpassFilter: true},
- {googNoisesuppression2: true},
- {googEchoCancellation2: true},
- {googAutoGainControl2: true}
- );
- }
- if (constraints.video) {
- constraints.video.optional.push(
- {googNoiseReduction: false} // chrome 37 workaround for issue 3807, reenable in M38
- );
- if (um.indexOf('video') >= 0) {
- constraints.video.optional.push(
- {googLeakyBucket: true}
- );
- }
- }
-
- if (um.indexOf('video') >= 0) {
- setResolutionConstraints(constraints, resolution, isAndroid);
- }
-
- if (bandwidth) { // doesn't work currently, see webrtc issue 1846
- if (!constraints.video) constraints.video = {mandatory: {}, optional: []};//same behaviour as true
- constraints.video.optional.push({bandwidth: bandwidth});
- }
- if (fps) { // for some cameras it might be necessary to request 30fps
- // so they choose 30fps mjpg over 10fps yuy2
- if (!constraints.video) constraints.video = {mandatory: {}, optional: []};// same behaviour as true;
- constraints.video.mandatory.minFrameRate = fps;
- }
-
- return constraints;
-}
-
-
-function RTCUtils(RTCService, onTemasysPluginReady)
-{
- var self = this;
- this.service = RTCService;
- if (RTCBrowserType.isFirefox()) {
- var FFversion = RTCBrowserType.getFirefoxVersion();
- if (FFversion >= 40 && config.useBundle && config.useRtcpMux) {
- this.peerconnection = mozRTCPeerConnection;
- this.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
- this.pc_constraints = {};
- this.attachMediaStream = function (element, stream) {
- // srcObject is being standardized and FF will eventually
- // support that unprefixed. FF also supports the
- // "element.src = URL.createObjectURL(...)" combo, but that
- // will be deprecated in favour of srcObject.
- //
- // https://groups.google.com/forum/#!topic/mozilla.dev.media/pKOiioXonJg
- // https://github.com/webrtc/samples/issues/302
- if(!element[0])
- return;
- element[0].mozSrcObject = stream;
- element[0].play();
- };
- this.getStreamID = function (stream) {
- var id = stream.id;
- if (!id) {
- var tracks = stream.getVideoTracks();
- if (!tracks || tracks.length === 0)
- {
- tracks = stream.getAudioTracks();
- }
- id = tracks[0].id;
- }
- return SDPUtil.filter_special_chars(id);
- };
- this.getVideoSrc = function (element) {
- if(!element)
- return null;
- return element.mozSrcObject;
- };
- this.setVideoSrc = function (element, src) {
- if(element)
- element.mozSrcObject = src;
- };
- RTCSessionDescription = mozRTCSessionDescription;
- RTCIceCandidate = mozRTCIceCandidate;
- } else {
- console.error(
- "Firefox requirements not met, ver: " + FFversion +
- ", bundle: " + config.useBundle +
- ", rtcp-mux: " + config.useRtcpMux);
- window.location.href = 'unsupported_browser.html';
- return;
- }
-
- } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) {
- this.peerconnection = webkitRTCPeerConnection;
- this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
- this.attachMediaStream = function (element, stream) {
- element.attr('src', webkitURL.createObjectURL(stream));
- };
- this.getStreamID = function (stream) {
- // streams from FF endpoints have the characters '{' and '}'
- // that make jQuery choke.
- return SDPUtil.filter_special_chars(stream.id);
- };
- this.getVideoSrc = function (element) {
- if(!element)
- return null;
- return element.getAttribute("src");
- };
- this.setVideoSrc = function (element, src) {
- if(element)
- element.setAttribute("src", src);
- };
- // DTLS should now be enabled by default but..
- this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]};
- if (navigator.userAgent.indexOf('Android') != -1) {
- this.pc_constraints = {}; // disable DTLS on Android
- }
- if (!webkitMediaStream.prototype.getVideoTracks) {
- webkitMediaStream.prototype.getVideoTracks = function () {
- return this.videoTracks;
- };
- }
- if (!webkitMediaStream.prototype.getAudioTracks) {
- webkitMediaStream.prototype.getAudioTracks = function () {
- return this.audioTracks;
- };
- }
- }
- // Detect IE/Safari
- else if (RTCBrowserType.isTemasysPluginUsed()) {
-
- //AdapterJS.WebRTCPlugin.setLogLevel(
- // AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE);
-
- AdapterJS.webRTCReady(function (isPlugin) {
-
- self.peerconnection = RTCPeerConnection;
- self.getUserMedia = getUserMedia;
- self.attachMediaStream = function (elSel, stream) {
-
- if (stream.id === "dummyAudio" || stream.id === "dummyVideo") {
- return;
- }
-
- attachMediaStream(elSel[0], stream);
- };
- self.getStreamID = function (stream) {
- var id = SDPUtil.filter_special_chars(stream.label);
- return id;
- };
- self.getVideoSrc = function (element) {
- if (!element) {
- console.warn("Attempt to get video SRC of null element");
- return null;
- }
- var src = null;
- var children = element.children;
- for (var i = 0; i !== children.length; ++i) {
- if (children[i].name === 'streamId') {
- return children[i].value;
- }
- }
- //console.info(element.id + " SRC: " + src);
- return null;
- };
- self.setVideoSrc = function (element, src) {
- //console.info("Set video src: ", element, src);
- if (!src) {
- console.warn("Not attaching video stream, 'src' is null");
- return;
- }
- AdapterJS.WebRTCPlugin.WaitForPluginReady();
- var stream = AdapterJS.WebRTCPlugin.plugin
- .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src);
- attachMediaStream(element, stream);
- };
-
- onTemasysPluginReady(isPlugin);
- });
- } else {
- try {
- console.log('Browser does not appear to be WebRTC-capable');
- } catch (e) { }
- window.location.href = 'unsupported_browser.html';
- }
-}
-
-
-RTCUtils.prototype.getUserMediaWithConstraints = function(
- um, success_callback, failure_callback, resolution,bandwidth, fps,
- desktopStream)
-{
- currentResolution = resolution;
- // Check if we are running on Android device
- var isAndroid = navigator.userAgent.indexOf('Android') != -1;
-
- var constraints = getConstraints(
- um, resolution, bandwidth, fps, desktopStream, isAndroid);
-
- console.info("Get media constraints", constraints);
-
- var self = this;
-
- try {
- this.getUserMedia(constraints,
- function (stream) {
- console.log('onUserMediaSuccess');
- self.setAvailableDevices(um, true);
- success_callback(stream);
- },
- function (error) {
- self.setAvailableDevices(um, false);
- console.warn('Failed to get access to local media. Error ',
- error, constraints);
- if (failure_callback) {
- failure_callback(error);
- }
- });
- } catch (e) {
- console.error('GUM failed: ', e);
- if(failure_callback) {
- failure_callback(e);
- }
- }
-};
-
-RTCUtils.prototype.setAvailableDevices = function (um, available) {
- var devices = {};
- if(um.indexOf("video") != -1)
- {
- devices.video = available;
- }
- if(um.indexOf("audio") != -1)
- {
- devices.audio = available;
- }
- this.service.setDeviceAvailability(devices);
-}
-
-/**
- * We ask for audio and video combined stream in order to get permissions and
- * not to ask twice.
- */
-RTCUtils.prototype.obtainAudioAndVideoPermissions =
- function(devices, callback, usageOptions)
-{
- var self = this;
- // Get AV
-
- var successCallback = function (stream) {
- if(callback)
- callback(stream, usageOptions);
- else
- self.successCallback(stream, usageOptions);
- };
-
- if(!devices)
- devices = ['audio', 'video'];
-
- var newDevices = [];
-
-
- if(usageOptions)
- for(var i = 0; i < devices.length; i++)
- {
- var device = devices[i];
- if(usageOptions[device] === true)
- newDevices.push(device);
- }
- else
- newDevices = devices;
-
- if(newDevices.length === 0)
- {
- successCallback();
- return;
- }
-
- if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) {
-
- // With FF/IE we can't split the stream into audio and video because FF
- // doesn't support media stream constructors. So, we need to get the
- // audio stream separately from the video stream using two distinct GUM
- // calls. Not very user friendly :-( but we don't have many other
- // options neither.
- //
- // Note that we pack those 2 streams in a single object and pass it to
- // the successCallback method.
- var obtainVideo = function (audioStream) {
- self.getUserMediaWithConstraints(
- ['video'],
- function (videoStream) {
- return successCallback({
- audioStream: audioStream,
- videoStream: videoStream
- });
- },
- function (error) {
- console.error(
- 'failed to obtain video stream - stop', error);
- self.errorCallback(error);
- },
- config.resolution || '360');
- };
- var obtainAudio = function () {
- self.getUserMediaWithConstraints(
- ['audio'],
- function (audioStream) {
- if (newDevices.indexOf('video') !== -1)
- obtainVideo(audioStream);
- },
- function (error) {
- console.error(
- 'failed to obtain audio stream - stop', error);
- self.errorCallback(error);
- }
- );
- };
- if (newDevices.indexOf('audio') !== -1) {
- obtainAudio();
- } else {
- obtainVideo(null);
- }
- } else {
- this.getUserMediaWithConstraints(
- newDevices,
- function (stream) {
- successCallback(stream);
- },
- function (error) {
- self.errorCallback(error);
- },
- config.resolution || '360');
- }
-
-};
-
-RTCUtils.prototype.successCallback = function (stream, usageOptions) {
- // If this is FF or IE, the stream parameter is *not* a MediaStream object,
- // it's an object with two properties: audioStream, videoStream.
- if (stream && stream.getAudioTracks && stream.getVideoTracks)
- console.log('got', stream, stream.getAudioTracks().length,
- stream.getVideoTracks().length);
- this.handleLocalStream(stream, usageOptions);
-};
-
-RTCUtils.prototype.errorCallback = function (error) {
- var self = this;
- console.error('failed to obtain audio/video stream - trying audio only', error);
- var resolution = getPreviousResolution(currentResolution);
- if(typeof error == "object" && error.constraintName && error.name
- && (error.name == "ConstraintNotSatisfiedError" ||
- error.name == "OverconstrainedError") &&
- (error.constraintName == "minWidth" || error.constraintName == "maxWidth" ||
- error.constraintName == "minHeight" || error.constraintName == "maxHeight")
- && resolution != null)
- {
- self.getUserMediaWithConstraints(['audio', 'video'],
- function (stream) {
- return self.successCallback(stream);
- }, function (error) {
- return self.errorCallback(error);
- }, resolution);
- }
- else
- {
- self.getUserMediaWithConstraints(
- ['audio'],
- function (stream) {
- return self.successCallback(stream);
- },
- function (error) {
- console.error('failed to obtain audio/video stream - stop',
- error);
- return self.successCallback(null);
- }
- );
- }
-
-}
-
-RTCUtils.prototype.handleLocalStream = function(stream, usageOptions)
-{
- // If this is FF, the stream parameter is *not* a MediaStream object, it's
- // an object with two properties: audioStream, videoStream.
- var audioStream, videoStream;
- if(window.webkitMediaStream)
- {
- audioStream = new webkitMediaStream();
- videoStream = new webkitMediaStream();
- if(stream) {
- var audioTracks = stream.getAudioTracks();
-
- for (var i = 0; i < audioTracks.length; i++) {
- audioStream.addTrack(audioTracks[i]);
- }
-
- var videoTracks = stream.getVideoTracks();
-
- for (i = 0; i < videoTracks.length; i++) {
- videoStream.addTrack(videoTracks[i]);
- }
- }
- }
- else if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed())
- { // Firefox and Temasys plugin
- if (stream && stream.audioStream)
- audioStream = stream.audioStream;
- else
- audioStream = new DummyMediaStream("dummyAudio");
-
- if (stream && stream.videoStream)
- videoStream = stream.videoStream;
- else
- videoStream = new DummyMediaStream("dummyVideo");
- }
-
- var audioMuted = (usageOptions && usageOptions.audio === false),
- videoMuted = (usageOptions && usageOptions.video === false);
-
- var audioGUM = (!usageOptions || usageOptions.audio !== false),
- videoGUM = (!usageOptions || usageOptions.video !== false);
-
-
- this.service.createLocalStream(audioStream, "audio", null, null,
- audioMuted, audioGUM);
-
- this.service.createLocalStream(videoStream, "video", null, null,
- videoMuted, videoGUM);
-};
-
-function DummyMediaStream(id) {
- this.id = id;
- this.label = id;
- this.stop = function() { };
- this.getAudioTracks = function() { return []; }
- this.getVideoTracks = function() { return []; }
-}
-
-RTCUtils.prototype.createStream = function(stream, isVideo) {
- var newStream = null;
- if (window.webkitMediaStream) {
- newStream = new webkitMediaStream();
- if (newStream) {
- var tracks = (isVideo ? stream.getVideoTracks() : stream.getAudioTracks());
-
- for (i = 0; i < tracks.length; i++) {
- newStream.addTrack(tracks[i]);
- }
- }
-
- }
- else {
- // FIXME: this is duplicated with 'handleLocalStream' !!!
- if (stream) {
- newStream = stream;
- } else {
- newStream = new DummyMediaStream(isVideo ? "dummyVideo" : "dummyAudio");
- }
- }
-
- return newStream;
-};
-
-module.exports = RTCUtils;
+},{}],9:[function(require,module,exports){
+var RTCBrowserType = require("./RTCBrowserType");
+var Resolutions = require("../../service/RTC/Resolutions");
+var AdapterJS = require("./adapter.screenshare");
+var SDPUtil = require("../xmpp/SDPUtil");
-},{"../../service/RTC/Resolutions":102,"../xmpp/SDPUtil":58,"./RTCBrowserType":10,"./adapter.screenshare":12}],12:[function(require,module,exports){
-/*! adapterjs - v0.11.0 - 2015-06-08 */
-
-// Adapter's interface.
-var AdapterJS = AdapterJS || {};
-
-// Browserify compatibility
-if(typeof exports !== 'undefined') {
- module.exports = AdapterJS;
-}
-
-AdapterJS.options = AdapterJS.options || {};
-
-// uncomment to get virtual webcams
-// AdapterJS.options.getAllCams = true;
-
-// uncomment to prevent the install prompt when the plugin in not yet installed
-// AdapterJS.options.hidePluginInstallPrompt = true;
-
-// AdapterJS version
-AdapterJS.VERSION = '0.11.0';
-
-// This function will be called when the WebRTC API is ready to be used
-// Whether it is the native implementation (Chrome, Firefox, Opera) or
-// the plugin
-// You may Override this function to synchronise the start of your application
-// with the WebRTC API being ready.
-// If you decide not to override use this synchronisation, it may result in
-// an extensive CPU usage on the plugin start (once per tab loaded)
-// Params:
-// - isUsingPlugin: true is the WebRTC plugin is being used, false otherwise
-//
-AdapterJS.onwebrtcready = AdapterJS.onwebrtcready || function(isUsingPlugin) {
- // The WebRTC API is ready.
- // Override me and do whatever you want here
-};
-
-// Sets a callback function to be called when the WebRTC interface is ready.
-// The first argument is the function to callback.\
-// Throws an error if the first argument is not a function
-AdapterJS.webRTCReady = function (callback) {
- if (typeof callback !== 'function') {
- throw new Error('Callback provided is not a function');
- }
-
- if (true === AdapterJS.onwebrtcreadyDone) {
- // All WebRTC interfaces are ready, just call the callback
- callback(null !== AdapterJS.WebRTCPlugin.plugin);
- } else {
- // will be triggered automatically when your browser/plugin is ready.
- AdapterJS.onwebrtcready = callback;
- }
-};
-
-// Plugin namespace
-AdapterJS.WebRTCPlugin = AdapterJS.WebRTCPlugin || {};
-
-// The object to store plugin information
-AdapterJS.WebRTCPlugin.pluginInfo = {
- prefix : 'Tem',
- plugName : 'TemWebRTCPlugin',
- pluginId : 'plugin0',
- type : 'application/x-temwebrtcplugin',
- onload : '__TemWebRTCReady0',
- portalLink : 'http://skylink.io/plugin/',
- downloadLink : null, //set below
- companyName: 'Temasys'
-};
-if(!!navigator.platform.match(/^Mac/i)) {
- AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1n77hco';
-}
-else if(!!navigator.platform.match(/^Win/i)) {
- AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1kkS4FN';
-}
-
-// Unique identifier of each opened page
-AdapterJS.WebRTCPlugin.pageId = Math.random().toString(36).slice(2);
-
-// Use this whenever you want to call the plugin.
-AdapterJS.WebRTCPlugin.plugin = null;
-
-// Set log level for the plugin once it is ready.
-// The different values are
-// This is an asynchronous function that will run when the plugin is ready
-AdapterJS.WebRTCPlugin.setLogLevel = null;
-
-// Defines webrtc's JS interface according to the plugin's implementation.
-// Define plugin Browsers as WebRTC Interface.
-AdapterJS.WebRTCPlugin.defineWebRTCInterface = null;
-
-// This function detects whether or not a plugin is installed.
-// Checks if Not IE (firefox, for example), else if it's IE,
-// we're running IE and do something. If not it is not supported.
-AdapterJS.WebRTCPlugin.isPluginInstalled = null;
-
- // Lets adapter.js wait until the the document is ready before injecting the plugin
-AdapterJS.WebRTCPlugin.pluginInjectionInterval = null;
-
-// Inject the HTML DOM object element into the page.
-AdapterJS.WebRTCPlugin.injectPlugin = null;
-
-// States of readiness that the plugin goes through when
-// being injected and stated
-AdapterJS.WebRTCPlugin.PLUGIN_STATES = {
- NONE : 0, // no plugin use
- INITIALIZING : 1, // Detected need for plugin
- INJECTING : 2, // Injecting plugin
- INJECTED: 3, // Plugin element injected but not usable yet
- READY: 4 // Plugin ready to be used
-};
-
-// Current state of the plugin. You cannot use the plugin before this is
-// equal to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY
-AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.NONE;
-
-// True is AdapterJS.onwebrtcready was already called, false otherwise
-// Used to make sure AdapterJS.onwebrtcready is only called once
-AdapterJS.onwebrtcreadyDone = false;
-
-// Log levels for the plugin.
-// To be set by calling AdapterJS.WebRTCPlugin.setLogLevel
-/*
-Log outputs are prefixed in some cases.
- INFO: Information reported by the plugin.
- ERROR: Errors originating from within the plugin.
- WEBRTC: Error originating from within the libWebRTC library
-*/
-// From the least verbose to the most verbose
-AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS = {
- NONE : 'NONE',
- ERROR : 'ERROR',
- WARNING : 'WARNING',
- INFO: 'INFO',
- VERBOSE: 'VERBOSE',
- SENSITIVE: 'SENSITIVE'
-};
-
-// Does a waiting check before proceeding to load the plugin.
-AdapterJS.WebRTCPlugin.WaitForPluginReady = null;
-
-// This methid will use an interval to wait for the plugin to be ready.
-AdapterJS.WebRTCPlugin.callWhenPluginReady = null;
-
-// !!!! WARNING: DO NOT OVERRIDE THIS FUNCTION. !!!
-// This function will be called when plugin is ready. It sends necessary
-// details to the plugin.
-// The function will wait for the document to be ready and the set the
-// plugin state to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY,
-// indicating that it can start being requested.
-// This function is not in the IE/Safari condition brackets so that
-// TemPluginLoaded function might be called on Chrome/Firefox.
-// This function is the only private function that is not encapsulated to
-// allow the plugin method to be called.
-__TemWebRTCReady0 = function () {
- if (document.readyState === 'complete') {
- AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY;
-
- AdapterJS.maybeThroughWebRTCReady();
- } else {
- AdapterJS.WebRTCPlugin.documentReadyInterval = setInterval(function () {
- if (document.readyState === 'complete') {
- // TODO: update comments, we wait for the document to be ready
- clearInterval(AdapterJS.WebRTCPlugin.documentReadyInterval);
- AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY;
-
- AdapterJS.maybeThroughWebRTCReady();
- }
- }, 100);
- }
-};
-
-AdapterJS.maybeThroughWebRTCReady = function() {
- if (!AdapterJS.onwebrtcreadyDone) {
- AdapterJS.onwebrtcreadyDone = true;
-
- if (typeof(AdapterJS.onwebrtcready) === 'function') {
- AdapterJS.onwebrtcready(AdapterJS.WebRTCPlugin.plugin !== null);
- }
- }
-};
-
-// Text namespace
-AdapterJS.TEXT = {
- PLUGIN: {
- REQUIRE_INSTALLATION: 'This website requires you to install a WebRTC-enabling plugin ' +
- 'to work on this browser.',
- NOT_SUPPORTED: 'Your browser does not support WebRTC.',
- BUTTON: 'Install Now'
- },
- REFRESH: {
- REQUIRE_REFRESH: 'Please refresh page',
- BUTTON: 'Refresh Page'
- }
-};
-
-// The result of ice connection states.
-// - starting: Ice connection is starting.
-// - checking: Ice connection is checking.
-// - connected Ice connection is connected.
-// - completed Ice connection is connected.
-// - done Ice connection has been completed.
-// - disconnected Ice connection has been disconnected.
-// - failed Ice connection has failed.
-// - closed Ice connection is closed.
-AdapterJS._iceConnectionStates = {
- starting : 'starting',
- checking : 'checking',
- connected : 'connected',
- completed : 'connected',
- done : 'completed',
- disconnected : 'disconnected',
- failed : 'failed',
- closed : 'closed'
-};
-
-//The IceConnection states that has been fired for each peer.
-AdapterJS._iceConnectionFiredStates = [];
-
-
-// Check if WebRTC Interface is defined.
-AdapterJS.isDefined = null;
-
-// This function helps to retrieve the webrtc detected browser information.
-// This sets:
-// - webrtcDetectedBrowser: The browser agent name.
-// - webrtcDetectedVersion: The browser version.
-// - webrtcDetectedType: The types of webRTC support.
-// - 'moz': Mozilla implementation of webRTC.
-// - 'webkit': WebKit implementation of webRTC.
-// - 'plugin': Using the plugin implementation.
-AdapterJS.parseWebrtcDetectedBrowser = function () {
- var hasMatch, checkMatch = navigator.userAgent.match(
- /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
- if (/trident/i.test(checkMatch[1])) {
- hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || [];
- webrtcDetectedBrowser = 'IE';
- webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10);
- } else if (checkMatch[1] === 'Chrome') {
- hasMatch = navigator.userAgent.match(/\bOPR\/(\d+)/);
- if (hasMatch !== null) {
- webrtcDetectedBrowser = 'opera';
- webrtcDetectedVersion = parseInt(hasMatch[1], 10);
- }
- }
- if (navigator.userAgent.indexOf('Safari')) {
- if (typeof InstallTrigger !== 'undefined') {
- webrtcDetectedBrowser = 'firefox';
- } else if (/*@cc_on!@*/ false || !!document.documentMode) {
- webrtcDetectedBrowser = 'IE';
- } else if (
- Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) {
- webrtcDetectedBrowser = 'safari';
- } else if (!!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0) {
- webrtcDetectedBrowser = 'opera';
- } else if (!!window.chrome) {
- webrtcDetectedBrowser = 'chrome';
- }
- }
- if (!webrtcDetectedBrowser) {
- webrtcDetectedVersion = checkMatch[1];
- }
- if (!webrtcDetectedVersion) {
- try {
- checkMatch = (checkMatch[2]) ? [checkMatch[1], checkMatch[2]] :
- [navigator.appName, navigator.appVersion, '-?'];
- if ((hasMatch = navigator.userAgent.match(/version\/(\d+)/i)) !== null) {
- checkMatch.splice(1, 1, hasMatch[1]);
- }
- webrtcDetectedVersion = parseInt(checkMatch[1], 10);
- } catch (error) { }
- }
-};
-
-// To fix configuration as some browsers does not support
-// the 'urls' attribute.
-AdapterJS.maybeFixConfiguration = function (pcConfig) {
- if (pcConfig === null) {
- return;
- }
- for (var i = 0; i < pcConfig.iceServers.length; i++) {
- if (pcConfig.iceServers[i].hasOwnProperty('urls')) {
- pcConfig.iceServers[i].url = pcConfig.iceServers[i].urls;
- delete pcConfig.iceServers[i].urls;
- }
- }
-};
-
-AdapterJS.addEvent = function(elem, evnt, func) {
- if (elem.addEventListener) { // W3C DOM
- elem.addEventListener(evnt, func, false);
- } else if (elem.attachEvent) {// OLD IE DOM
- elem.attachEvent('on'+evnt, func);
- } else { // No much to do
- elem[evnt] = func;
- }
-};
-
-AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNewTab, displayRefreshBar) {
- // only inject once the page is ready
- if (document.readyState !== 'complete') {
- return;
- }
-
- var w = window;
- var i = document.createElement('iframe');
- i.style.position = 'fixed';
- i.style.top = '-41px';
- i.style.left = 0;
- i.style.right = 0;
- i.style.width = '100%';
- i.style.height = '40px';
- i.style.backgroundColor = '#ffffe1';
- i.style.border = 'none';
- i.style.borderBottom = '1px solid #888888';
- i.style.zIndex = '9999999';
- if(typeof i.style.webkitTransition === 'string') {
- i.style.webkitTransition = 'all .5s ease-out';
- } else if(typeof i.style.transition === 'string') {
- i.style.transition = 'all .5s ease-out';
- }
- document.body.appendChild(i);
- c = (i.contentWindow) ? i.contentWindow :
- (i.contentDocument.document) ? i.contentDocument.document : i.contentDocument;
- c.document.open();
- c.document.write('' + text + '');
- if(buttonText && buttonLink) {
- c.document.write('');
- c.document.close();
-
- AdapterJS.addEvent(c.document.getElementById('okay'), 'click', function(e) {
- if (!!displayRefreshBar) {
- AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION ?
- AdapterJS.TEXT.EXTENSION.REQUIRE_REFRESH : AdapterJS.TEXT.REFRESH.REQUIRE_REFRESH,
- AdapterJS.TEXT.REFRESH.BUTTON, 'javascript:location.reload()');
- }
- window.open(buttonLink, !!openNewTab ? '_blank' : '_top');
-
- e.preventDefault();
- try {
- event.cancelBubble = true;
- } catch(error) { }
- });
- }
- else {
- c.document.close();
- }
- AdapterJS.addEvent(c.document, 'click', function() {
- w.document.body.removeChild(i);
- });
- setTimeout(function() {
- if(typeof i.style.webkitTransform === 'string') {
- i.style.webkitTransform = 'translateY(40px)';
- } else if(typeof i.style.transform === 'string') {
- i.style.transform = 'translateY(40px)';
- } else {
- i.style.top = '0px';
- }
- }, 300);
-};
-
-// -----------------------------------------------------------
-// Detected webrtc implementation. Types are:
-// - 'moz': Mozilla implementation of webRTC.
-// - 'webkit': WebKit implementation of webRTC.
-// - 'plugin': Using the plugin implementation.
-webrtcDetectedType = null;
-
-// Detected webrtc datachannel support. Types are:
-// - 'SCTP': SCTP datachannel support.
-// - 'RTP': RTP datachannel support.
-webrtcDetectedDCSupport = null;
-
-// Set the settings for creating DataChannels, MediaStream for
-// Cross-browser compability.
-// - This is only for SCTP based support browsers.
-// the 'urls' attribute.
-checkMediaDataChannelSettings =
- function (peerBrowserAgent, peerBrowserVersion, callback, constraints) {
- if (typeof callback !== 'function') {
- return;
- }
- var beOfferer = true;
- var isLocalFirefox = webrtcDetectedBrowser === 'firefox';
- // Nightly version does not require MozDontOfferDataChannel for interop
- var isLocalFirefoxInterop = webrtcDetectedType === 'moz' && webrtcDetectedVersion > 30;
- var isPeerFirefox = peerBrowserAgent === 'firefox';
- var isPeerFirefoxInterop = peerBrowserAgent === 'firefox' &&
- ((peerBrowserVersion) ? (peerBrowserVersion > 30) : false);
-
- // Resends an updated version of constraints for MozDataChannel to work
- // If other userAgent is firefox and user is firefox, remove MozDataChannel
- if ((isLocalFirefox && isPeerFirefox) || (isLocalFirefoxInterop)) {
- try {
- delete constraints.mandatory.MozDontOfferDataChannel;
- } catch (error) {
- console.error('Failed deleting MozDontOfferDataChannel');
- console.error(error);
- }
- } else if ((isLocalFirefox && !isPeerFirefox)) {
- constraints.mandatory.MozDontOfferDataChannel = true;
- }
- if (!isLocalFirefox) {
- // temporary measure to remove Moz* constraints in non Firefox browsers
- for (var prop in constraints.mandatory) {
- if (constraints.mandatory.hasOwnProperty(prop)) {
- if (prop.indexOf('Moz') !== -1) {
- delete constraints.mandatory[prop];
- }
- }
- }
- }
- // Firefox (not interopable) cannot offer DataChannel as it will cause problems to the
- // interopability of the media stream
- if (isLocalFirefox && !isPeerFirefox && !isLocalFirefoxInterop) {
- beOfferer = false;
- }
- callback(beOfferer, constraints);
-};
-
-// Handles the differences for all browsers ice connection state output.
-// - Tested outcomes are:
-// - Chrome (offerer) : 'checking' > 'completed' > 'completed'
-// - Chrome (answerer) : 'checking' > 'connected'
-// - Firefox (offerer) : 'checking' > 'connected'
-// - Firefox (answerer): 'checking' > 'connected'
-checkIceConnectionState = function (peerId, iceConnectionState, callback) {
- if (typeof callback !== 'function') {
- console.warn('No callback specified in checkIceConnectionState. Aborted.');
- return;
- }
- peerId = (peerId) ? peerId : 'peer';
-
- if (!AdapterJS._iceConnectionFiredStates[peerId] ||
- iceConnectionState === AdapterJS._iceConnectionStates.disconnected ||
- iceConnectionState === AdapterJS._iceConnectionStates.failed ||
- iceConnectionState === AdapterJS._iceConnectionStates.closed) {
- AdapterJS._iceConnectionFiredStates[peerId] = [];
- }
- iceConnectionState = AdapterJS._iceConnectionStates[iceConnectionState];
- if (AdapterJS._iceConnectionFiredStates[peerId].indexOf(iceConnectionState) < 0) {
- AdapterJS._iceConnectionFiredStates[peerId].push(iceConnectionState);
- if (iceConnectionState === AdapterJS._iceConnectionStates.connected) {
- setTimeout(function () {
- AdapterJS._iceConnectionFiredStates[peerId]
- .push(AdapterJS._iceConnectionStates.done);
- callback(AdapterJS._iceConnectionStates.done);
- }, 1000);
- }
- callback(iceConnectionState);
- }
- return;
-};
-
-// Firefox:
-// - Creates iceServer from the url for Firefox.
-// - Create iceServer with stun url.
-// - Create iceServer with turn url.
-// - Ignore the transport parameter from TURN url for FF version <=27.
-// - Return null for createIceServer if transport=tcp.
-// - FF 27 and above supports transport parameters in TURN url,
-// - So passing in the full url to create iceServer.
-// Chrome:
-// - Creates iceServer from the url for Chrome M33 and earlier.
-// - Create iceServer with stun url.
-// - Chrome M28 & above uses below TURN format.
-// Plugin:
-// - Creates Ice Server for Plugin Browsers
-// - If Stun - Create iceServer with stun url.
-// - Else - Create iceServer with turn url
-// - This is a WebRTC Function
-createIceServer = null;
-
-// Firefox:
-// - Creates IceServers for Firefox
-// - Use .url for FireFox.
-// - Multiple Urls support
-// Chrome:
-// - Creates iceServers from the urls for Chrome M34 and above.
-// - .urls is supported since Chrome M34.
-// - Multiple Urls support
-// Plugin:
-// - Creates Ice Servers for Plugin Browsers
-// - Multiple Urls support
-// - This is a WebRTC Function
-createIceServers = null;
-//------------------------------------------------------------
-
-//The RTCPeerConnection object.
-RTCPeerConnection = null;
-
-// Creates RTCSessionDescription object for Plugin Browsers
-RTCSessionDescription = (typeof RTCSessionDescription === 'function') ?
- RTCSessionDescription : null;
-
-// Creates RTCIceCandidate object for Plugin Browsers
-RTCIceCandidate = (typeof RTCIceCandidate === 'function') ?
- RTCIceCandidate : null;
-
-// Get UserMedia (only difference is the prefix).
-// Code from Adam Barth.
-getUserMedia = null;
-
-// Attach a media stream to an element.
-attachMediaStream = null;
-
-// Re-attach a media stream to an element.
-reattachMediaStream = null;
-
-
-// Detected browser agent name. Types are:
-// - 'firefox': Firefox browser.
-// - 'chrome': Chrome browser.
-// - 'opera': Opera browser.
-// - 'safari': Safari browser.
-// - 'IE' - Internet Explorer browser.
-webrtcDetectedBrowser = null;
-
-// Detected browser version.
-webrtcDetectedVersion = null;
-
-// Check for browser types and react accordingly
-if (navigator.mozGetUserMedia) {
- webrtcDetectedBrowser = 'firefox';
- webrtcDetectedVersion = parseInt(navigator
- .userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
- webrtcDetectedType = 'moz';
- webrtcDetectedDCSupport = 'SCTP';
-
- RTCPeerConnection = function (pcConfig, pcConstraints) {
- AdapterJS.maybeFixConfiguration(pcConfig);
- return new mozRTCPeerConnection(pcConfig, pcConstraints);
- };
-
- // The RTCSessionDescription object.
- RTCSessionDescription = mozRTCSessionDescription;
- window.RTCSessionDescription = RTCSessionDescription;
-
- // The RTCIceCandidate object.
- RTCIceCandidate = mozRTCIceCandidate;
- window.RTCIceCandidate = RTCIceCandidate;
-
- window.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
- navigator.getUserMedia = window.getUserMedia;
-
- // Shim for MediaStreamTrack.getSources.
- MediaStreamTrack.getSources = function(successCb) {
- setTimeout(function() {
- var infos = [
- { kind: 'audio', id: 'default', label:'', facing:'' },
- { kind: 'video', id: 'default', label:'', facing:'' }
- ];
- successCb(infos);
- }, 0);
- };
-
- createIceServer = function (url, username, password) {
- var iceServer = null;
- var url_parts = url.split(':');
- if (url_parts[0].indexOf('stun') === 0) {
- iceServer = { url : url };
- } else if (url_parts[0].indexOf('turn') === 0) {
- if (webrtcDetectedVersion < 27) {
- var turn_url_parts = url.split('?');
- if (turn_url_parts.length === 1 ||
- turn_url_parts[1].indexOf('transport=udp') === 0) {
- iceServer = {
- url : turn_url_parts[0],
- credential : password,
- username : username
- };
- }
- } else {
- iceServer = {
- url : url,
- credential : password,
- username : username
- };
- }
- }
- return iceServer;
- };
-
- createIceServers = function (urls, username, password) {
- var iceServers = [];
- for (i = 0; i < urls.length; i++) {
- var iceServer = createIceServer(urls[i], username, password);
- if (iceServer !== null) {
- iceServers.push(iceServer);
- }
- }
- return iceServers;
- };
-
- attachMediaStream = function (element, stream) {
- element.mozSrcObject = stream;
- if (stream !== null)
- element.play();
-
- return element;
- };
-
- reattachMediaStream = function (to, from) {
- to.mozSrcObject = from.mozSrcObject;
- to.play();
- return to;
- };
-
- MediaStreamTrack.getSources = MediaStreamTrack.getSources || function (callback) {
- if (!callback) {
- throw new TypeError('Failed to execute \'getSources\' on \'MediaStreamTrack\'' +
- ': 1 argument required, but only 0 present.');
- }
- return callback([]);
- };
-
- // Fake get{Video,Audio}Tracks
- if (!MediaStream.prototype.getVideoTracks) {
- MediaStream.prototype.getVideoTracks = function () {
- return [];
- };
- }
- if (!MediaStream.prototype.getAudioTracks) {
- MediaStream.prototype.getAudioTracks = function () {
- return [];
- };
- }
-
- AdapterJS.maybeThroughWebRTCReady();
-} else if (navigator.webkitGetUserMedia) {
- webrtcDetectedBrowser = 'chrome';
- webrtcDetectedType = 'webkit';
- webrtcDetectedVersion = parseInt(navigator
- .userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);
- // check if browser is opera 20+
- var checkIfOpera = navigator.userAgent.match(/\bOPR\/(\d+)/);
- if (checkIfOpera !== null) {
- webrtcDetectedBrowser = 'opera';
- webrtcDetectedVersion = parseInt(checkIfOpera[1], 10);
- }
- // check browser datachannel support
- if ((webrtcDetectedBrowser === 'chrome' && webrtcDetectedVersion >= 31) ||
- (webrtcDetectedBrowser === 'opera' && webrtcDetectedVersion >= 20)) {
- webrtcDetectedDCSupport = 'SCTP';
- } else if (webrtcDetectedBrowser === 'chrome' && webrtcDetectedVersion < 30 &&
- webrtcDetectedVersion > 24) {
- webrtcDetectedDCSupport = 'RTP';
- } else {
- webrtcDetectedDCSupport = '';
- }
-
- createIceServer = function (url, username, password) {
- var iceServer = null;
- var url_parts = url.split(':');
- if (url_parts[0].indexOf('stun') === 0) {
- iceServer = { 'url' : url };
- } else if (url_parts[0].indexOf('turn') === 0) {
- iceServer = {
- 'url' : url,
- 'credential' : password,
- 'username' : username
- };
- }
- return iceServer;
- };
-
- createIceServers = function (urls, username, password) {
- var iceServers = [];
- if (webrtcDetectedVersion >= 34) {
- iceServers = {
- 'urls' : urls,
- 'credential' : password,
- 'username' : username
- };
- } else {
- for (i = 0; i < urls.length; i++) {
- var iceServer = createIceServer(urls[i], username, password);
- if (iceServer !== null) {
- iceServers.push(iceServer);
- }
- }
- }
- return iceServers;
- };
-
- RTCPeerConnection = function (pcConfig, pcConstraints) {
- if (webrtcDetectedVersion < 34) {
- AdapterJS.maybeFixConfiguration(pcConfig);
- }
- return new webkitRTCPeerConnection(pcConfig, pcConstraints);
- };
-
- window.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
- navigator.getUserMedia = window.getUserMedia;
-
- attachMediaStream = function (element, stream) {
- if (typeof element.srcObject !== 'undefined') {
- element.srcObject = stream;
- } else if (typeof element.mozSrcObject !== 'undefined') {
- element.mozSrcObject = stream;
- } else if (typeof element.src !== 'undefined') {
- element.src = (stream === null ? '' : URL.createObjectURL(stream));
- } else {
- console.log('Error attaching stream to element.');
- }
- return element;
- };
-
- reattachMediaStream = function (to, from) {
- to.src = from.src;
- return to;
- };
-
- AdapterJS.maybeThroughWebRTCReady();
-} else { // TRY TO USE PLUGIN
- // IE 9 is not offering an implementation of console.log until you open a console
- if (typeof console !== 'object' || typeof console.log !== 'function') {
- /* jshint -W020 */
- console = {} || console;
- // Implemented based on console specs from MDN
- // You may override these functions
- console.log = function (arg) {};
- console.info = function (arg) {};
- console.error = function (arg) {};
- console.dir = function (arg) {};
- console.exception = function (arg) {};
- console.trace = function (arg) {};
- console.warn = function (arg) {};
- console.count = function (arg) {};
- console.debug = function (arg) {};
- console.count = function (arg) {};
- console.time = function (arg) {};
- console.timeEnd = function (arg) {};
- console.group = function (arg) {};
- console.groupCollapsed = function (arg) {};
- console.groupEnd = function (arg) {};
- /* jshint +W020 */
- }
- webrtcDetectedType = 'plugin';
- webrtcDetectedDCSupport = 'plugin';
- AdapterJS.parseWebrtcDetectedBrowser();
- isIE = webrtcDetectedBrowser === 'IE';
-
- /* jshint -W035 */
- AdapterJS.WebRTCPlugin.WaitForPluginReady = function() {
- while (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) {
- /* empty because it needs to prevent the function from running. */
- }
- };
- /* jshint +W035 */
-
- AdapterJS.WebRTCPlugin.callWhenPluginReady = function (callback) {
- if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) {
- // Call immediately if possible
- // Once the plugin is set, the code will always take this path
- callback();
- } else {
- // otherwise start a 100ms interval
- var checkPluginReadyState = setInterval(function () {
- if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) {
- clearInterval(checkPluginReadyState);
- callback();
- }
- }, 100);
- }
- };
-
- AdapterJS.WebRTCPlugin.setLogLevel = function(logLevel) {
- AdapterJS.WebRTCPlugin.callWhenPluginReady(function() {
- AdapterJS.WebRTCPlugin.plugin.setLogLevel(logLevel);
- });
- };
-
- AdapterJS.WebRTCPlugin.injectPlugin = function () {
- // only inject once the page is ready
- if (document.readyState !== 'complete') {
- return;
- }
-
- // Prevent multiple injections
- if (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING) {
- return;
- }
-
- AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTING;
-
- if (webrtcDetectedBrowser === 'IE' && webrtcDetectedVersion <= 10) {
- var frag = document.createDocumentFragment();
- AdapterJS.WebRTCPlugin.plugin = document.createElement('div');
- AdapterJS.WebRTCPlugin.plugin.innerHTML = '';
- while (AdapterJS.WebRTCPlugin.plugin.firstChild) {
- frag.appendChild(AdapterJS.WebRTCPlugin.plugin.firstChild);
- }
- document.body.appendChild(frag);
-
- // Need to re-fetch the plugin
- AdapterJS.WebRTCPlugin.plugin =
- document.getElementById(AdapterJS.WebRTCPlugin.pluginInfo.pluginId);
- } else {
- // Load Plugin
- AdapterJS.WebRTCPlugin.plugin = document.createElement('object');
- AdapterJS.WebRTCPlugin.plugin.id =
- AdapterJS.WebRTCPlugin.pluginInfo.pluginId;
- // IE will only start the plugin if it's ACTUALLY visible
- if (isIE) {
- AdapterJS.WebRTCPlugin.plugin.width = '1px';
- AdapterJS.WebRTCPlugin.plugin.height = '1px';
- } else { // The size of the plugin on Safari should be 0x0px
- // so that the autorisation prompt is at the top
- AdapterJS.WebRTCPlugin.plugin.width = '0px';
- AdapterJS.WebRTCPlugin.plugin.height = '0px';
- }
- AdapterJS.WebRTCPlugin.plugin.type = AdapterJS.WebRTCPlugin.pluginInfo.type;
- AdapterJS.WebRTCPlugin.plugin.innerHTML = '' +
- '' +
- ' ' +
- (AdapterJS.options.getAllCams ? '':'') +
- '';
- document.body.appendChild(AdapterJS.WebRTCPlugin.plugin);
- }
-
-
- AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTED;
- };
-
- AdapterJS.WebRTCPlugin.isPluginInstalled =
- function (comName, plugName, installedCb, notInstalledCb) {
- if (!isIE) {
- var pluginArray = navigator.plugins;
- for (var i = 0; i < pluginArray.length; i++) {
- if (pluginArray[i].name.indexOf(plugName) >= 0) {
- installedCb();
- return;
- }
- }
- notInstalledCb();
- } else {
- try {
- var axo = new ActiveXObject(comName + '.' + plugName);
- } catch (e) {
- notInstalledCb();
- return;
- }
- installedCb();
- }
- };
-
- AdapterJS.WebRTCPlugin.defineWebRTCInterface = function () {
- AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING;
-
- AdapterJS.isDefined = function (variable) {
- return variable !== null && variable !== undefined;
- };
-
- createIceServer = function (url, username, password) {
- var iceServer = null;
- var url_parts = url.split(':');
- if (url_parts[0].indexOf('stun') === 0) {
- iceServer = {
- 'url' : url,
- 'hasCredentials' : false
- };
- } else if (url_parts[0].indexOf('turn') === 0) {
- iceServer = {
- 'url' : url,
- 'hasCredentials' : true,
- 'credential' : password,
- 'username' : username
- };
- }
- return iceServer;
- };
-
- createIceServers = function (urls, username, password) {
- var iceServers = [];
- for (var i = 0; i < urls.length; ++i) {
- iceServers.push(createIceServer(urls[i], username, password));
- }
- return iceServers;
- };
-
- RTCSessionDescription = function (info) {
- AdapterJS.WebRTCPlugin.WaitForPluginReady();
- return AdapterJS.WebRTCPlugin.plugin.
- ConstructSessionDescription(info.type, info.sdp);
- };
-
- RTCPeerConnection = function (servers, constraints) {
- var iceServers = null;
- if (servers) {
- iceServers = servers.iceServers;
- for (var i = 0; i < iceServers.length; i++) {
- if (iceServers[i].urls && !iceServers[i].url) {
- iceServers[i].url = iceServers[i].urls;
- }
- iceServers[i].hasCredentials = AdapterJS.
- isDefined(iceServers[i].username) &&
- AdapterJS.isDefined(iceServers[i].credential);
- }
- }
- var mandatory = (constraints && constraints.mandatory) ?
- constraints.mandatory : null;
- var optional = (constraints && constraints.optional) ?
- constraints.optional : null;
-
- AdapterJS.WebRTCPlugin.WaitForPluginReady();
- return AdapterJS.WebRTCPlugin.plugin.
- PeerConnection(AdapterJS.WebRTCPlugin.pageId,
- iceServers, mandatory, optional);
- };
-
- MediaStreamTrack = {};
- MediaStreamTrack.getSources = function (callback) {
- AdapterJS.WebRTCPlugin.callWhenPluginReady(function() {
- AdapterJS.WebRTCPlugin.plugin.GetSources(callback);
- });
- };
-
- window.getUserMedia = function (constraints, successCallback, failureCallback) {
- constraints.audio = constraints.audio || false;
- constraints.video = constraints.video || false;
-
- AdapterJS.WebRTCPlugin.callWhenPluginReady(function() {
- AdapterJS.WebRTCPlugin.plugin.
- getUserMedia(constraints, successCallback, failureCallback);
- });
- };
- window.navigator.getUserMedia = window.getUserMedia;
-
- attachMediaStream = function (element, stream) {
- if (!element || !element.parentNode) {
- return;
- }
-
- var streamId
- if (stream === null) {
- streamId = '';
- }
- else {
- stream.enableSoundTracks(true);
- streamId = stream.id;
- }
-
- if (element.nodeName.toLowerCase() !== 'audio') {
- var elementId = element.id.length === 0 ? Math.random().toString(36).slice(2) : element.id;
- if (!element.isWebRTCPlugin || !element.isWebRTCPlugin()) {
- var frag = document.createDocumentFragment();
- var temp = document.createElement('div');
- var classHTML = '';
- if (element.className) {
- classHTML = 'class="' + element.className + '" ';
- } else if (element.attributes && element.attributes['class']) {
- classHTML = 'class="' + element.attributes['class'].value + '" ';
- }
-
- temp.innerHTML = '';
- while (temp.firstChild) {
- frag.appendChild(temp.firstChild);
- }
-
- var height = '';
- var width = '';
- if (element.getBoundingClientRect) {
- var rectObject = element.getBoundingClientRect();
- width = rectObject.width + 'px';
- height = rectObject.height + 'px';
- }
- else if (element.width) {
- width = element.width;
- height = element.height;
- } else {
- // TODO: What scenario could bring us here?
- }
-
- element.parentNode.insertBefore(frag, element);
- frag = document.getElementById(elementId);
- frag.width = width;
- frag.height = height;
- element.parentNode.removeChild(element);
- } else {
- var children = element.children;
- for (var i = 0; i !== children.length; ++i) {
- if (children[i].name === 'streamId') {
- children[i].value = streamId;
- break;
- }
- }
- element.setStreamId(streamId);
- }
- var newElement = document.getElementById(elementId);
- newElement.onplaying = (element.onplaying) ? element.onplaying : function (arg) {};
- if (isIE) { // on IE the event needs to be plugged manually
- newElement.attachEvent('onplaying', newElement.onplaying);
- newElement.onclick = (element.onclick) ? element.onclick : function (arg) {};
- newElement._TemOnClick = function (id) {
- var arg = {
- srcElement : document.getElementById(id)
- };
- newElement.onclick(arg);
- };
- }
- return newElement;
- } else {
- return element;
- }
- };
-
- reattachMediaStream = function (to, from) {
- var stream = null;
- var children = from.children;
- for (var i = 0; i !== children.length; ++i) {
- if (children[i].name === 'streamId') {
- AdapterJS.WebRTCPlugin.WaitForPluginReady();
- stream = AdapterJS.WebRTCPlugin.plugin
- .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, children[i].value);
- break;
- }
- }
- if (stream !== null) {
- return attachMediaStream(to, stream);
- } else {
- console.log('Could not find the stream associated with this element');
- }
- };
-
- RTCIceCandidate = function (candidate) {
- if (!candidate.sdpMid) {
- candidate.sdpMid = '';
- }
-
- AdapterJS.WebRTCPlugin.WaitForPluginReady();
- return AdapterJS.WebRTCPlugin.plugin.ConstructIceCandidate(
- candidate.sdpMid, candidate.sdpMLineIndex, candidate.candidate
- );
- };
-
- // inject plugin
- AdapterJS.addEvent(document, 'readystatechange', AdapterJS.WebRTCPlugin.injectPlugin);
- AdapterJS.WebRTCPlugin.injectPlugin();
- };
-
- // This function will be called if the plugin is needed (browser different
- // from Chrome or Firefox), but the plugin is not installed.
- AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb = AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb ||
- function() {
- AdapterJS.addEvent(document,
- 'readystatechange',
- AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv);
- AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv();
- };
-
- AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv = function () {
- if (AdapterJS.options.hidePluginInstallPrompt) {
- return;
- }
-
- var downloadLink = AdapterJS.WebRTCPlugin.pluginInfo.downloadLink;
- if(downloadLink) { // if download link
- var popupString;
- if (AdapterJS.WebRTCPlugin.pluginInfo.portalLink) { // is portal link
- popupString = 'This website requires you to install the ' +
- ' ' + AdapterJS.WebRTCPlugin.pluginInfo.companyName +
- ' WebRTC Plugin' +
- ' to work on this browser.';
- } else { // no portal link, just print a generic explanation
- popupString = AdapterJS.TEXT.PLUGIN.REQUIRE_INSTALLATION;
- }
-
- AdapterJS.renderNotificationBar(popupString, AdapterJS.TEXT.PLUGIN.BUTTON, downloadLink);
- } else { // no download link, just print a generic explanation
- AdapterJS.renderNotificationBar(AdapterJS.TEXT.PLUGIN.NOT_SUPPORTED);
- }
- };
-
- // Try to detect the plugin and act accordingly
- AdapterJS.WebRTCPlugin.isPluginInstalled(
- AdapterJS.WebRTCPlugin.pluginInfo.prefix,
- AdapterJS.WebRTCPlugin.pluginInfo.plugName,
- AdapterJS.WebRTCPlugin.defineWebRTCInterface,
- AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb);
-}
-
-
-
-(function () {
-
- 'use strict';
-
- var baseGetUserMedia = null;
-
- AdapterJS.TEXT.EXTENSION = {
- REQUIRE_INSTALLATION_FF: 'To enable screensharing you need to install the Skylink WebRTC tools Firefox Add-on.',
- REQUIRE_INSTALLATION_CHROME: 'To enable screensharing you need to install the Skylink WebRTC tools Chrome Extension.',
- REQUIRE_REFRESH: 'Please refresh this page after the Skylink WebRTC tools extension has been installed.',
- BUTTON_FF: 'Install Now',
- BUTTON_CHROME: 'Go to Chrome Web Store'
- };
-
- var clone = function(obj) {
- if (null == obj || "object" != typeof obj) return obj;
- var copy = obj.constructor();
- for (var attr in obj) {
- if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
- }
- return copy;
- };
-
- if (window.navigator.mozGetUserMedia) {
- baseGetUserMedia = window.navigator.getUserMedia;
-
- navigator.getUserMedia = function (constraints, successCb, failureCb) {
-
- if (constraints && constraints.video && !!constraints.video.mediaSource) {
- // intercepting screensharing requests
-
- if (constraints.video.mediaSource !== 'screen' && constraints.video.mediaSource !== 'window') {
- throw new Error('Only "screen" and "window" option is available as mediaSource');
- }
-
- var updatedConstraints = clone(constraints);
-
- //constraints.video.mediaSource = constraints.video.mediaSource;
- updatedConstraints.video.mozMediaSource = updatedConstraints.video.mediaSource;
-
- // so generally, it requires for document.readyState to be completed before the getUserMedia could be invoked.
- // strange but this works anyway
- var checkIfReady = setInterval(function () {
- if (document.readyState === 'complete') {
- clearInterval(checkIfReady);
-
- baseGetUserMedia(updatedConstraints, successCb, function (error) {
- if (error.name === 'PermissionDeniedError' && window.parent.location.protocol === 'https:') {
- AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_FF,
- AdapterJS.TEXT.EXTENSION.BUTTON_FF,
- 'http://skylink.io/screensharing/ff_addon.php?domain=' + window.location.hostname, false, true);
- //window.location.href = 'http://skylink.io/screensharing/ff_addon.php?domain=' + window.location.hostname;
- } else {
- failureCb(error);
- }
- });
- }
- }, 1);
-
- } else { // regular GetUserMediaRequest
- baseGetUserMedia(constraints, successCb, failureCb);
- }
- };
-
- getUserMedia = navigator.getUserMedia;
-
- } else if (window.navigator.webkitGetUserMedia) {
- baseGetUserMedia = window.navigator.getUserMedia;
-
- navigator.getUserMedia = function (constraints, successCb, failureCb) {
-
- if (constraints && constraints.video && !!constraints.video.mediaSource) {
- if (window.webrtcDetectedBrowser !== 'chrome') {
- throw new Error('Current browser does not support screensharing');
- }
-
- // would be fine since no methods
- var updatedConstraints = clone(constraints);
-
- var chromeCallback = function(error, sourceId) {
- if(!error) {
- updatedConstraints.video.mandatory = updatedConstraints.video.mandatory || {};
- updatedConstraints.video.mandatory.chromeMediaSource = 'desktop';
- updatedConstraints.video.mandatory.maxWidth = window.screen.width > 1920 ? window.screen.width : 1920;
- updatedConstraints.video.mandatory.maxHeight = window.screen.height > 1080 ? window.screen.height : 1080;
-
- if (sourceId) {
- updatedConstraints.video.mandatory.chromeMediaSourceId = sourceId;
- }
-
- delete updatedConstraints.video.mediaSource;
-
- baseGetUserMedia(updatedConstraints, successCb, failureCb);
-
- } else {
- if (error === 'permission-denied') {
- throw new Error('Permission denied for screen retrieval');
- } else {
- throw new Error('Failed retrieving selected screen');
- }
- }
- };
-
- var onIFrameCallback = function (event) {
- if (!event.data) {
- return;
- }
-
- if (event.data.chromeMediaSourceId) {
- if (event.data.chromeMediaSourceId === 'PermissionDeniedError') {
- chromeCallback('permission-denied');
- } else {
- chromeCallback(null, event.data.chromeMediaSourceId);
- }
- }
-
- if (event.data.chromeExtensionStatus) {
- if (event.data.chromeExtensionStatus === 'not-installed') {
- AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_CHROME,
- AdapterJS.TEXT.EXTENSION.BUTTON_CHROME,
- event.data.data, true, true);
- } else {
- chromeCallback(event.data.chromeExtensionStatus, null);
- }
- }
-
- // this event listener is no more needed
- window.removeEventListener('message', onIFrameCallback);
- };
-
- window.addEventListener('message', onIFrameCallback);
-
- postFrameMessage({
- captureSourceId: true
- });
-
- } else {
- baseGetUserMedia(constraints, successCb, failureCb);
- }
- };
-
- getUserMedia = navigator.getUserMedia;
-
- } else {
- baseGetUserMedia = window.navigator.getUserMedia;
-
- navigator.getUserMedia = function (constraints, successCb, failureCb) {
- if (constraints && constraints.video && !!constraints.video.mediaSource) {
- // would be fine since no methods
- var updatedConstraints = clone(constraints);
-
- // wait for plugin to be ready
- AdapterJS.WebRTCPlugin.callWhenPluginReady(function() {
- // check if screensharing feature is available
- if (!!AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature &&
- !!AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable) {
-
-
- // set the constraints
- updatedConstraints.video.optional = updatedConstraints.video.optional || [];
- updatedConstraints.video.optional.push({
- sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey || 'Screensharing'
- });
-
- delete updatedConstraints.video.mediaSource;
- } else {
- throw new Error('Your WebRTC plugin does not support screensharing');
- }
- baseGetUserMedia(updatedConstraints, successCb, failureCb);
- });
- } else {
- baseGetUserMedia(constraints, successCb, failureCb);
- }
- };
-
- getUserMedia = window.navigator.getUserMedia;
- }
-
- if (window.webrtcDetectedBrowser === 'chrome') {
- var iframe = document.createElement('iframe');
-
- iframe.onload = function() {
- iframe.isLoaded = true;
- };
-
- iframe.src = 'https://cdn.temasys.com.sg/skylink/extensions/detectRTC.html';
- //'https://temasys-cdn.s3.amazonaws.com/skylink/extensions/detection-script-dev/detectRTC.html';
- iframe.style.display = 'none';
-
- (document.body || document.documentElement).appendChild(iframe);
-
- var postFrameMessage = function (object) {
- object = object || {};
-
- if (!iframe.isLoaded) {
- setTimeout(function () {
- iframe.contentWindow.postMessage(object, '*');
- }, 100);
- return;
- }
-
- iframe.contentWindow.postMessage(object, '*');
- };
- }
-})();
-},{}],13:[function(require,module,exports){
-var UI = {};
-
-var VideoLayout = require("./videolayout/VideoLayout.js");
-var AudioLevels = require("./audio_levels/AudioLevels.js");
-var Prezi = require("./prezi/Prezi.js");
-var Etherpad = require("./etherpad/Etherpad.js");
-var Chat = require("./side_pannels/chat/Chat.js");
-var Toolbar = require("./toolbars/Toolbar");
-var ToolbarToggler = require("./toolbars/ToolbarToggler");
-var BottomToolbar = require("./toolbars/BottomToolbar");
-var ContactList = require("./side_pannels/contactlist/ContactList");
-var Avatar = require("./avatar/Avatar");
-var EventEmitter = require("events");
-var SettingsMenu = require("./side_pannels/settings/SettingsMenu");
-var Settings = require("./../settings/Settings");
-var PanelToggler = require("./side_pannels/SidePanelToggler");
-var RoomNameGenerator = require("./welcome_page/RoomnameGenerator");
-UI.messageHandler = require("./util/MessageHandler");
-var messageHandler = UI.messageHandler;
-var Authentication = require("./authentication/Authentication");
-var UIUtil = require("./util/UIUtil");
-var NicknameHandler = require("./util/NicknameHandler");
-var CQEvents = require("../../service/connectionquality/CQEvents");
-var DesktopSharingEventTypes
- = require("../../service/desktopsharing/DesktopSharingEventTypes");
-var RTCEvents = require("../../service/RTC/RTCEvents");
-var RTCBrowserType = require("../RTC/RTCBrowserType");
-var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-var UIEvents = require("../../service/UI/UIEvents");
-var MemberEvents = require("../../service/members/Events");
-
-var eventEmitter = new EventEmitter();
-var roomName = null;
-
-
-function promptDisplayName() {
- var message = '';
- message += APP.translation.translateString(
- "dialog.displayNameRequired");
- message += '
' +
- '';
-
- var buttonTxt
- = APP.translation.generateTranslationHTML("dialog.Ok");
- var buttons = [];
- buttons.push({title: buttonTxt, value: "ok"});
-
- messageHandler.openDialog(null, message,
- true,
- buttons,
- function (e, v, m, f) {
- if (v == "ok") {
- var displayName = f.displayName;
- if (displayName) {
- VideoLayout.inputDisplayNameHandler(displayName);
- return true;
- }
- }
- e.preventDefault();
- },
- function () {
- var form = $.prompt.getPrompt();
- var input = form.find("input[name='displayName']");
- input.focus();
- var button = form.find("button");
- button.attr("disabled", "disabled");
- input.keyup(function () {
- if(!input.val())
- button.attr("disabled", "disabled");
- else
- button.removeAttr("disabled");
- });
- }
- );
-}
-
-
-function notifyForInitialMute()
-{
- messageHandler.notify(null, "notify.mutedTitle", "connected",
- "notify.muted", null, {timeOut: 120000});
-}
-
-function setupPrezi()
-{
- $("#reloadPresentationLink").click(function()
- {
- Prezi.reloadPresentation();
- });
-}
-
-function setupChat()
-{
- Chat.init();
- $("#toggle_smileys").click(function() {
- Chat.toggleSmileys();
- });
-}
-
-function setupToolbars() {
- Toolbar.init(UI);
- Toolbar.setupButtonsFromConfig();
- BottomToolbar.init();
-}
-
-function streamHandler(stream, isMuted) {
- switch (stream.type)
- {
- case "audio":
- VideoLayout.changeLocalAudio(stream, isMuted);
- break;
- case "video":
- VideoLayout.changeLocalVideo(stream, isMuted);
- break;
- case "stream":
- VideoLayout.changeLocalStream(stream, isMuted);
- break;
- }
-}
-
-function onXmppConnectionFailed(stropheErrorMsg) {
-
- var title = APP.translation.generateTranslationHTML(
- "dialog.error");
-
- var message;
- if (stropheErrorMsg) {
- message = APP.translation.generateTranslationHTML(
- "dialog.connectErrorWithMsg", {msg: stropheErrorMsg});
- } else {
- message = APP.translation.generateTranslationHTML(
- "dialog.connectError");
- }
-
- messageHandler.openDialog(
- title, message, true, {}, function (e, v, m, f) { return false; });
-}
-
-function onDisposeConference(unload) {
- Toolbar.showAuthenticateButton(false);
-}
-
-function onDisplayNameChanged(jid, displayName) {
- ContactList.onDisplayNameChange(jid, displayName);
- SettingsMenu.onDisplayNameChange(jid, displayName);
- VideoLayout.onDisplayNameChanged(jid, displayName);
-}
-
-function registerListeners() {
- APP.RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
-
- APP.RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED);
- APP.RTC.addStreamListener(function (stream) {
- VideoLayout.onRemoteStreamAdded(stream);
- }, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED);
- APP.RTC.addStreamListener(function (jid) {
- VideoLayout.onVideoTypeChanged(jid);
- }, StreamEventTypes.EVENT_TYPE_REMOTE_CHANGED);
- APP.RTC.addListener(RTCEvents.LASTN_CHANGED, onLastNChanged);
- APP.RTC.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED, function (resourceJid) {
- VideoLayout.onDominantSpeakerChanged(resourceJid);
- });
- APP.RTC.addListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
- function (lastNEndpoints, endpointsEnteringLastN, stream) {
- VideoLayout.onLastNEndpointsChanged(lastNEndpoints,
- endpointsEnteringLastN, stream);
- });
- APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED,
- function (devices) {
- VideoLayout.setDeviceAvailabilityIcons(null, devices);
- });
- APP.RTC.addListener(RTCEvents.VIDEO_MUTE, UI.setVideoMuteButtonsState);
- APP.RTC.addListener(RTCEvents.DATA_CHANNEL_OPEN, function() {
- // when the data channel becomes available, tell the bridge about video
- // selections so that it can do adaptive simulcast,
- // we want the notification to trigger even if userJid is undefined,
- // or null.
- var userJid = APP.UI.getLargeVideoJid();
- eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, userJid);
- });
- APP.statistics.addAudioLevelListener(function(jid, audioLevel)
- {
- var resourceJid;
- if(jid === APP.statistics.LOCAL_JID)
- {
- resourceJid = AudioLevels.LOCAL_LEVEL;
- if(APP.RTC.localAudio.isMuted())
- {
- audioLevel = 0;
- }
- }
- else
- {
- resourceJid = Strophe.getResourceFromJid(jid);
- }
-
- AudioLevels.updateAudioLevel(resourceJid, audioLevel,
- UI.getLargeVideoJid());
- });
- APP.desktopsharing.addListener(function () {
- ToolbarToggler.showDesktopSharingButton();
- }, DesktopSharingEventTypes.INIT);
- APP.desktopsharing.addListener(
- Toolbar.changeDesktopSharingButtonState,
- DesktopSharingEventTypes.SWITCHING_DONE);
- APP.connectionquality.addListener(CQEvents.LOCALSTATS_UPDATED,
- VideoLayout.updateLocalConnectionStats);
- APP.connectionquality.addListener(CQEvents.REMOTESTATS_UPDATED,
- VideoLayout.updateConnectionStats);
- APP.connectionquality.addListener(CQEvents.STOP,
- VideoLayout.onStatsStop);
- APP.xmpp.addListener(XMPPEvents.CONNECTION_FAILED, onXmppConnectionFailed);
- APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
- APP.xmpp.addListener(XMPPEvents.GRACEFUL_SHUTDOWN, function () {
- messageHandler.openMessageDialog(
- 'dialog.serviceUnavailable',
- 'dialog.gracefulShutdown'
- );
- });
- APP.xmpp.addListener(XMPPEvents.RESERVATION_ERROR, function (code, msg) {
- var title = APP.translation.generateTranslationHTML(
- "dialog.reservationError");
- var message = APP.translation.generateTranslationHTML(
- "dialog.reservationErrorMsg", {code: code, msg: msg});
- messageHandler.openDialog(
- title,
- message,
- true, {},
- function (event, value, message, formVals)
- {
- return false;
- }
- );
- });
- APP.xmpp.addListener(XMPPEvents.KICKED, function () {
- messageHandler.openMessageDialog("dialog.sessTerminated",
- "dialog.kickMessage");
- });
- APP.xmpp.addListener(XMPPEvents.MUC_DESTROYED, function (reason) {
- //FIXME: use Session Terminated from translation, but
- // 'reason' text comes from XMPP packet and is not translated
- var title = APP.translation.generateTranslationHTML("dialog.sessTerminated");
- messageHandler.openDialog(
- title, reason, true, {},
- function (event, value, message, formVals)
- {
- return false;
- }
- );
- });
- APP.xmpp.addListener(XMPPEvents.BRIDGE_DOWN, function () {
- messageHandler.showError("dialog.error",
- "dialog.bridgeUnavailable");
- });
- APP.xmpp.addListener(XMPPEvents.USER_ID_CHANGED, function (from, id) {
- Avatar.setUserAvatar(from, id);
- });
- APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, onDisplayNameChanged);
- APP.xmpp.addListener(XMPPEvents.MUC_JOINED, onMucJoined);
- APP.xmpp.addListener(XMPPEvents.LOCAL_ROLE_CHANGED, onLocalRoleChanged);
- APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, onMucMemberJoined);
- APP.xmpp.addListener(XMPPEvents.MUC_ROLE_CHANGED, onMucRoleChanged);
- APP.xmpp.addListener(XMPPEvents.PRESENCE_STATUS, onMucPresenceStatus);
- APP.xmpp.addListener(XMPPEvents.SUBJECT_CHANGED, chatSetSubject);
- APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation);
- APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, onMucMemberLeft);
- APP.xmpp.addListener(XMPPEvents.PASSWORD_REQUIRED, onPasswordRequired);
- APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError);
- APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad);
- APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED,
- onAuthenticationRequired);
- APP.xmpp.addListener(XMPPEvents.DEVICE_AVAILABLE,
- function (resource, devices) {
- VideoLayout.setDeviceAvailabilityIcons(resource, devices);
- });
-
- APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED, VideoLayout.onAudioMute);
- APP.xmpp.addListener(XMPPEvents.VIDEO_MUTED, VideoLayout.onVideoMute);
- APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS, function(doMuteAudio) {
- UI.setAudioMuted(doMuteAudio);
- });
- APP.members.addListener(MemberEvents.DTMF_SUPPORT_CHANGED,
- onDtmfSupportChanged);
- APP.xmpp.addListener(XMPPEvents.START_MUTED_SETTING_CHANGED, function (audio, video) {
- SettingsMenu.setStartMuted(audio, video);
- });
- APP.xmpp.addListener(XMPPEvents.START_MUTED_FROM_FOCUS, function (audio, video) {
- UI.setInitialMuteFromFocus(audio, video);
- });
-
- APP.xmpp.addListener(XMPPEvents.JINGLE_FATAL_ERROR, function (session, error) {
- UI.messageHandler.showError("dialog.sorry",
- "dialog.internalError");
- });
-
- APP.xmpp.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR, function() {
- messageHandler.showError("dialog.error",
- "dialog.SLDFailure");
- });
- APP.xmpp.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR, function() {
- messageHandler.showError("dialog.error",
- "dialog.SRDFailure");
- });
- APP.xmpp.addListener(XMPPEvents.CREATE_ANSWER_ERROR, function() {
- messageHandler.showError();
- });
- APP.xmpp.addListener(XMPPEvents.PROMPT_FOR_LOGIN, function() {
- // FIXME: re-use LoginDialog which supports retries
- UI.showLoginPopup(connect);
- });
-
- APP.xmpp.addListener(XMPPEvents.FOCUS_DISCONNECTED, function(focusComponent, retrySec) {
- UI.messageHandler.notify(
- null, "notify.focus",
- 'disconnected', "notify.focusFail",
- {component: focusComponent, ms: retrySec});
- });
-
- APP.xmpp.addListener(XMPPEvents.ROOM_JOIN_ERROR, function(pres) {
- UI.messageHandler.openReportDialog(null,
- "dialog.joinError", pres);
- });
- APP.xmpp.addListener(XMPPEvents.ROOM_CONNECT_ERROR, function(pres) {
- UI.messageHandler.openReportDialog(null,
- "dialog.connectError", pres);
- });
-
- APP.xmpp.addListener(XMPPEvents.READY_TO_JOIN, function() {
- var roomName = UI.generateRoomName();
- APP.xmpp.allocateConferenceFocus(roomName, UI.checkForNicknameAndJoin);
- });
-
- //NicknameHandler emits this event
- UI.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) {
- APP.xmpp.addToPresence("displayName", nickname);
- });
-}
-
-
-/**
- * Mutes/unmutes the local video.
- *
- * @param mute true to mute the local video; otherwise, false
- * @param options an object which specifies optional arguments such as the
- * boolean key byUser with default value true which
- * specifies whether the method was initiated in response to a user command (in
- * contrast to an automatic decision taken by the application logic)
- */
-function setVideoMute(mute, options) {
- APP.RTC.setVideoMute(mute,
- UI.setVideoMuteButtonsState,
- options);
-}
-
-function onResize()
-{
- Chat.resizeChat();
- VideoLayout.resizeLargeVideoContainer();
-}
-
-function bindEvents()
-{
- /**
- * Resizes and repositions videos in full screen mode.
- */
- $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange',
- onResize);
-
- $(window).resize(onResize);
-}
-
-UI.start = function (init) {
- document.title = interfaceConfig.APP_NAME;
- if(config.enableWelcomePage && window.location.pathname == "/" &&
- (!window.localStorage.welcomePageDisabled || window.localStorage.welcomePageDisabled == "false"))
- {
- $("#videoconference_page").hide();
- var setupWelcomePage = require("./welcome_page/WelcomePage");
- setupWelcomePage();
-
- return;
- }
-
- if (interfaceConfig.SHOW_JITSI_WATERMARK) {
- var leftWatermarkDiv
- = $("#largeVideoContainer div[class='watermark leftwatermark']");
-
- leftWatermarkDiv.css({display: 'block'});
- leftWatermarkDiv.parent().get(0).href
- = interfaceConfig.JITSI_WATERMARK_LINK;
- }
-
- if (interfaceConfig.SHOW_BRAND_WATERMARK) {
- var rightWatermarkDiv
- = $("#largeVideoContainer div[class='watermark rightwatermark']");
-
- rightWatermarkDiv.css({display: 'block'});
- rightWatermarkDiv.parent().get(0).href
- = interfaceConfig.BRAND_WATERMARK_LINK;
- rightWatermarkDiv.get(0).style.backgroundImage
- = "url(images/rightwatermark.png)";
- }
-
- if (interfaceConfig.SHOW_POWERED_BY) {
- $("#largeVideoContainer>a[class='poweredby']").css({display: 'block'});
- }
-
- $("#welcome_page").hide();
-
- $("#videospace").mousemove(function () {
- return ToolbarToggler.showToolbar();
- });
- // Set the defaults for prompt dialogs.
- jQuery.prompt.setDefaults({persistent: false});
-
- VideoLayout.init(eventEmitter);
- AudioLevels.init();
- NicknameHandler.init(eventEmitter);
- registerListeners();
- bindEvents();
- setupPrezi();
- setupToolbars();
- setupChat();
-
-
- document.title = interfaceConfig.APP_NAME;
-
- $("#downloadlog").click(function (event) {
- dump(event.target);
- });
-
- if(config.enableWelcomePage && window.location.pathname == "/" &&
- (!window.localStorage.welcomePageDisabled || window.localStorage.welcomePageDisabled == "false"))
- {
- $("#videoconference_page").hide();
- var setupWelcomePage = require("./welcome_page/WelcomePage");
- setupWelcomePage();
-
- return;
- }
-
- $("#welcome_page").hide();
-
- // Display notice message at the top of the toolbar
- if (config.noticeMessage) {
- $('#noticeText').text(config.noticeMessage);
- $('#notice').css({display: 'block'});
- }
-
- if (!RTCBrowserType.isIExplorer()) {
- document.getElementById('largeVideo').volume = 0;
- }
-
- if(config.requireDisplayName) {
- var currentSettings = Settings.getSettings();
- if (!currentSettings.displayName) {
- promptDisplayName();
- }
- }
-
- init();
-
- toastr.options = {
- "closeButton": true,
- "debug": false,
- "positionClass": "notification-bottom-right",
- "onclick": null,
- "showDuration": "300",
- "hideDuration": "1000",
- "timeOut": "2000",
- "extendedTimeOut": "1000",
- "showEasing": "swing",
- "hideEasing": "linear",
- "showMethod": "fadeIn",
- "hideMethod": "fadeOut",
- "reposition": function() {
- if(PanelToggler.isVisible()) {
- $("#toast-container").addClass("notification-bottom-right-center");
- } else {
- $("#toast-container").removeClass("notification-bottom-right-center");
- }
- },
- "newestOnTop": false
- };
-
- SettingsMenu.init();
-
-};
-
-function chatAddError(errorMessage, originalText)
-{
- return Chat.chatAddError(errorMessage, originalText);
-};
-
-function chatSetSubject(text)
-{
- return Chat.chatSetSubject(text);
-};
-
-function updateChatConversation(from, displayName, message, myjid, stamp) {
- return Chat.updateChatConversation(from, displayName, message, myjid, stamp);
-}
-
-function onMucJoined(jid, info) {
- Toolbar.updateRoomUrl(window.location.href);
- var meHTML = APP.translation.generateTranslationHTML("me");
- $("#localNick").html(Strophe.getResourceFromJid(jid) + " (" + meHTML + ")");
-
- var settings = Settings.getSettings();
-
- // Make sure we configure our avatar id, before creating avatar for us
- Avatar.setUserAvatar(jid, settings.email || settings.uid);
-
- // Add myself to the contact list.
- ContactList.addContact(jid);
-
- // Once we've joined the muc show the toolbar
- ToolbarToggler.showToolbar();
-
- var displayName = !config.displayJids
- ? info.displayName : Strophe.getResourceFromJid(jid);
-
- if (displayName)
- onDisplayNameChanged('localVideoContainer', displayName);
-
-
- VideoLayout.mucJoined();
-}
-
-function initEtherpad(name) {
- Etherpad.init(name);
-}
-
-function onMucMemberLeft(jid) {
- console.log('left.muc', jid);
- var displayName = $('#participant_' + Strophe.getResourceFromJid(jid) +
- '>.displayname').html();
- messageHandler.notify(displayName,'notify.somebody',
- 'disconnected',
- 'notify.disconnected');
- if(!config.startAudioMuted ||
- config.startAudioMuted > APP.members.size())
- UIUtil.playSoundNotification('userLeft');
-
- ContactList.removeContact(jid);
-
- VideoLayout.participantLeft(jid);
-};
-
-
-function onLocalRoleChanged(jid, info, pres, isModerator)
-{
-
- console.info("My role changed, new role: " + info.role);
- onModeratorStatusChanged(isModerator);
- VideoLayout.showModeratorIndicator();
- SettingsMenu.onRoleChanged();
-
- if (isModerator) {
- Authentication.closeAuthenticationWindow();
- messageHandler.notify(null, "notify.me",
- 'connected', "notify.moderator");
- }
-}
-
-function onModeratorStatusChanged(isModerator) {
-
- Toolbar.showSipCallButton(isModerator);
- Toolbar.showRecordingButton(
- isModerator); //&&
- // FIXME:
- // Recording visible if
- // there are at least 2(+ 1 focus) participants
- //Object.keys(connection.emuc.members).length >= 3);
-}
-
-function onPasswordRequired(callback) {
- // password is required
- Toolbar.lockLockButton();
- var message = '';
- message += APP.translation.translateString(
- "dialog.passwordRequired");
- message += '
' +
- '';
-
- messageHandler.openTwoButtonDialog(null, null, null, message,
- true,
- "dialog.Ok",
- function (e, v, m, f) {},
- null,
- function (e, v, m, f) {
- if (v) {
- var lockKey = f.lockKey;
- if (lockKey) {
- Toolbar.setSharedKey(lockKey);
- callback(lockKey);
- }
- }
- },
- ':input:first'
- );
-}
-
-/**
- * The dialpad button is shown iff there is at least one member that supports
- * DTMF (e.g. jigasi).
- */
-function onDtmfSupportChanged(dtmfSupport) {
- //TODO: enable when the UI is ready
- //Toolbar.showDialPadButton(dtmfSupport);
-}
-
-function onMucMemberJoined(jid, id, displayName) {
- messageHandler.notify(displayName,'notify.somebody',
- 'connected',
- 'notify.connected');
-
- if(!config.startAudioMuted ||
- config.startAudioMuted > APP.members.size())
- UIUtil.playSoundNotification('userJoined');
-
- // Configure avatar
- Avatar.setUserAvatar(jid, id);
-
- // Add Peer's container
- VideoLayout.ensurePeerContainerExists(jid);
-}
-
-function onMucPresenceStatus(jid, info) {
- VideoLayout.setPresenceStatus(Strophe.getResourceFromJid(jid), info.status);
-}
-
-function onMucRoleChanged(role, displayName) {
- VideoLayout.showModeratorIndicator();
-
- if (role === 'moderator') {
- var messageKey, messageOptions = {};
- if (!displayName) {
- messageKey = "notify.grantedToUnknown";
- }
- else
- {
- messageKey = "notify.grantedTo";
- messageOptions = {to: displayName};
- }
- messageHandler.notify(
- displayName,'notify.somebody',
- 'connected', messageKey,
- messageOptions);
- }
-}
-
-function onAuthenticationRequired(intervalCallback) {
- Authentication.openAuthenticationDialog(
- roomName, intervalCallback, function () {
- Toolbar.authenticateClicked();
- });
-};
-
-
-function onLastNChanged(oldValue, newValue) {
- if (config.muteLocalVideoIfNotInLastN) {
- setVideoMute(!newValue, { 'byUser': false });
- }
-}
-
-
-UI.toggleSmileys = function () {
- Chat.toggleSmileys();
-};
-
-UI.getSettings = function () {
- return Settings.getSettings();
-};
-
-UI.toggleFilmStrip = function () {
- return BottomToolbar.toggleFilmStrip();
-};
-
-UI.toggleChat = function () {
- return BottomToolbar.toggleChat();
-};
-
-UI.toggleContactList = function () {
- return BottomToolbar.toggleContactList();
-};
-
-UI.inputDisplayNameHandler = function (value) {
- VideoLayout.inputDisplayNameHandler(value);
-};
-
-
-UI.getLargeVideoJid = function()
-{
- return VideoLayout.getLargeVideoJid();
-};
-
-UI.generateRoomName = function() {
- if(roomName)
- return roomName;
- var roomnode = null;
- var path = window.location.pathname;
-
- // determinde the room node from the url
- // TODO: just the roomnode or the whole bare jid?
- if (config.getroomnode && typeof config.getroomnode === 'function') {
- // custom function might be responsible for doing the pushstate
- roomnode = config.getroomnode(path);
- } else {
- /* fall back to default strategy
- * this is making assumptions about how the URL->room mapping happens.
- * It currently assumes deployment at root, with a rewrite like the
- * following one (for nginx):
- location ~ ^/([a-zA-Z0-9]+)$ {
- rewrite ^/(.*)$ / break;
- }
- */
- if (path.length > 1) {
- roomnode = path.substr(1).toLowerCase();
- } else {
- var word = RoomNameGenerator.generateRoomWithoutSeparator();
- roomnode = word.toLowerCase();
-
- window.history.pushState('VideoChat',
- 'Room: ' + word, window.location.pathname + word);
- }
- }
-
- roomName = roomnode + '@' + config.hosts.muc;
- return roomName;
-};
-
-
-UI.connectionIndicatorShowMore = function(jid)
-{
- return VideoLayout.showMore(jid);
-};
-
-UI.showLoginPopup = function(callback)
-{
- console.log('password is required');
- var message = '';
- message += APP.translation.translateString(
- "dialog.passwordRequired");
- message += '
' +
- '' +
- '';
- UI.messageHandler.openTwoButtonDialog(null, null, null, message,
- true,
- "dialog.Ok",
- function (e, v, m, f) {
- if (v) {
- if (f.username !== null && f.password != null) {
- callback(f.username, f.password);
- }
- }
- },
- null, null, ':input:first'
-
- );
-}
-
-UI.checkForNicknameAndJoin = function () {
-
- Authentication.closeAuthenticationDialog();
- Authentication.stopInterval();
-
- var nick = null;
- if (config.useNicks) {
- nick = window.prompt('Your nickname (optional)');
- }
- APP.xmpp.joinRoom(roomName, config.useNicks, nick);
-};
-
-
-function dump(elem, filename) {
- elem = elem.parentNode;
- elem.download = filename || 'meetlog.json';
- elem.href = 'data:application/json;charset=utf-8,\n';
- var data = APP.xmpp.populateData();
- var metadata = {};
- metadata.time = new Date();
- metadata.url = window.location.href;
- metadata.ua = navigator.userAgent;
- var log = APP.xmpp.getLogger();
- if (log) {
- metadata.xmpp = log;
- }
- data.metadata = metadata;
- elem.href += encodeURIComponent(JSON.stringify(data, null, ' '));
- return false;
-}
-
-UI.getRoomName = function () {
- return roomName;
-};
-
-UI.setInitialMuteFromFocus = function (muteAudio, muteVideo) {
- if(muteAudio || muteVideo) notifyForInitialMute();
- if(muteAudio) UI.setAudioMuted(true);
- if(muteVideo) UI.setVideoMute(true);
-}
-
-/**
- * Mutes/unmutes the local video.
- */
-UI.toggleVideo = function () {
- setVideoMute(!APP.RTC.localVideo.isMuted());
-};
-
-/**
- * Mutes / unmutes audio for the local participant.
- */
-UI.toggleAudio = function() {
- UI.setAudioMuted(!APP.RTC.localAudio.isMuted());
-};
-
-/**
- * Sets muted audio state for the local participant.
- */
-UI.setAudioMuted = function (mute, earlyMute) {
- var audioMute = null;
- if(earlyMute)
- audioMute = function (mute, cb) {
- return APP.xmpp.sendAudioInfoPresence(mute, cb);
- };
- else
- audioMute = function (mute, cb) {
- return APP.xmpp.setAudioMute(mute, cb);
- }
- if(!audioMute(mute, function () {
- VideoLayout.showLocalAudioIndicator(mute);
-
- UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
- }))
- {
- // We still click the button.
- UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
- return;
- }
-
-}
-
-UI.addListener = function (type, listener) {
- eventEmitter.on(type, listener);
-}
-
-UI.clickOnVideo = function (videoNumber) {
- var remoteVideos = $(".videocontainer:not(#mixedstream)");
- if (remoteVideos.length > videoNumber) {
- remoteVideos[videoNumber].click();
- }
-}
-
-//Used by torture
-UI.showToolbar = function () {
- return ToolbarToggler.showToolbar();
-}
-
-//Used by torture
-UI.dockToolbar = function (isDock) {
- return ToolbarToggler.dockToolbar(isDock);
-}
-
-UI.setVideoMuteButtonsState = function (mute) {
- var video = $('#video');
- var communicativeClass = "icon-camera";
- var muteClass = "icon-camera icon-camera-disabled";
-
- if (mute) {
- video.removeClass(communicativeClass);
- video.addClass(muteClass);
- } else {
- video.removeClass(muteClass);
- video.addClass(communicativeClass);
- }
-}
-
-UI.userAvatarChanged = function (resourceJid, thumbUrl, contactListUrl) {
- VideoLayout.userAvatarChanged(resourceJid, thumbUrl);
- ContactList.userAvatarChanged(resourceJid, contactListUrl);
- if(resourceJid === APP.xmpp.myResource())
- SettingsMenu.changeAvatar(thumbUrl);
-}
-
-UI.setVideoMute = setVideoMute;
-
-module.exports = UI;
-
+var currentResolution = null;
-},{"../../service/RTC/RTCEvents":101,"../../service/RTC/StreamEventTypes":103,"../../service/UI/UIEvents":104,"../../service/connectionquality/CQEvents":106,"../../service/desktopsharing/DesktopSharingEventTypes":107,"../../service/members/Events":108,"../../service/xmpp/XMPPEvents":110,"../RTC/RTCBrowserType":10,"./../settings/Settings":49,"./audio_levels/AudioLevels.js":14,"./authentication/Authentication":16,"./avatar/Avatar":18,"./etherpad/Etherpad.js":19,"./prezi/Prezi.js":20,"./side_pannels/SidePanelToggler":22,"./side_pannels/chat/Chat.js":23,"./side_pannels/contactlist/ContactList":27,"./side_pannels/settings/SettingsMenu":28,"./toolbars/BottomToolbar":29,"./toolbars/Toolbar":30,"./toolbars/ToolbarToggler":31,"./util/MessageHandler":33,"./util/NicknameHandler":34,"./util/UIUtil":35,"./videolayout/VideoLayout.js":41,"./welcome_page/RoomnameGenerator":42,"./welcome_page/WelcomePage":43,"events":1}],14:[function(require,module,exports){
-var CanvasUtil = require("./CanvasUtils");
-
-var ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
-
-function initActiveSpeakerAudioLevels() {
- var ASRadius = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2;
- var ASCenter = (interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE + ASRadius) / 2;
-
-// Draw a circle.
- ASDrawContext.arc(ASCenter, ASCenter, ASRadius, 0, 2 * Math.PI);
-
-// Add a shadow around the circle
- ASDrawContext.shadowColor = interfaceConfig.SHADOW_COLOR;
- ASDrawContext.shadowOffsetX = 0;
- ASDrawContext.shadowOffsetY = 0;
-}
-
-/**
- * The audio Levels plugin.
- */
-var AudioLevels = (function(my) {
- var audioLevelCanvasCache = {};
-
- my.LOCAL_LEVEL = 'local';
-
- my.init = function () {
- initActiveSpeakerAudioLevels();
- }
-
- /**
- * Updates the audio level canvas for the given peerJid. If the canvas
- * didn't exist we create it.
- */
- my.updateAudioLevelCanvas = function (peerJid, VideoLayout) {
- var resourceJid = null;
- var videoSpanId = null;
- if (!peerJid)
- videoSpanId = 'localVideoContainer';
- else {
- resourceJid = Strophe.getResourceFromJid(peerJid);
-
- videoSpanId = 'participant_' + resourceJid;
- }
-
- var videoSpan = document.getElementById(videoSpanId);
-
- if (!videoSpan) {
- if (resourceJid)
- console.error("No video element for jid", resourceJid);
- else
- console.error("No video element for local video.");
-
- return;
- }
-
- var audioLevelCanvas = $('#' + videoSpanId + '>canvas');
-
- var videoSpaceWidth = $('#remoteVideos').width();
- var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
- var thumbnailWidth = thumbnailSize[0];
- var thumbnailHeight = thumbnailSize[1];
-
- if (!audioLevelCanvas || audioLevelCanvas.length === 0) {
-
- audioLevelCanvas = document.createElement('canvas');
- audioLevelCanvas.className = "audiolevel";
- audioLevelCanvas.style.bottom = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px";
- audioLevelCanvas.style.left = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px";
- resizeAudioLevelCanvas( audioLevelCanvas,
- thumbnailWidth,
- thumbnailHeight);
-
- videoSpan.appendChild(audioLevelCanvas);
- } else {
- audioLevelCanvas = audioLevelCanvas.get(0);
-
- resizeAudioLevelCanvas( audioLevelCanvas,
- thumbnailWidth,
- thumbnailHeight);
- }
- };
-
- /**
- * Updates the audio level UI for the given resourceJid.
- *
- * @param resourceJid the resource jid indicating the video element for
- * which we draw the audio level
- * @param audioLevel the newAudio level to render
- */
- my.updateAudioLevel = function (resourceJid, audioLevel, largeVideoResourceJid) {
- drawAudioLevelCanvas(resourceJid, audioLevel);
-
- var videoSpanId = getVideoSpanId(resourceJid);
-
- var audioLevelCanvas = $('#' + videoSpanId + '>canvas').get(0);
-
- if (!audioLevelCanvas)
- return;
-
- var drawContext = audioLevelCanvas.getContext('2d');
-
- var canvasCache = audioLevelCanvasCache[resourceJid];
-
- drawContext.clearRect (0, 0,
- audioLevelCanvas.width, audioLevelCanvas.height);
- drawContext.drawImage(canvasCache, 0, 0);
-
- if(resourceJid === AudioLevels.LOCAL_LEVEL) {
- if(!APP.xmpp.myJid()) {
- return;
- }
- resourceJid = APP.xmpp.myResource();
- }
-
- if(resourceJid === largeVideoResourceJid) {
- window.requestAnimationFrame(function () {
- AudioLevels.updateActiveSpeakerAudioLevel(audioLevel);
- });
- }
- };
-
- my.updateActiveSpeakerAudioLevel = function(audioLevel) {
- if($("#activeSpeaker").css("visibility") == "hidden")
- return;
-
-
- ASDrawContext.clearRect(0, 0, 300, 300);
- if(audioLevel == 0)
- return;
-
- ASDrawContext.shadowBlur = getShadowLevel(audioLevel);
-
-
- // Fill the shape.
- ASDrawContext.fill();
- };
-
- /**
- * Resizes the given audio level canvas to match the given thumbnail size.
- */
- function resizeAudioLevelCanvas(audioLevelCanvas,
- thumbnailWidth,
- thumbnailHeight) {
- audioLevelCanvas.width = thumbnailWidth + interfaceConfig.CANVAS_EXTRA;
- audioLevelCanvas.height = thumbnailHeight + interfaceConfig.CANVAS_EXTRA;
- }
-
- /**
- * Draws the audio level canvas into the cached canvas object.
- *
- * @param resourceJid the resource jid indicating the video element for
- * which we draw the audio level
- * @param audioLevel the newAudio level to render
- */
- function drawAudioLevelCanvas(resourceJid, audioLevel) {
- if (!audioLevelCanvasCache[resourceJid]) {
-
- var videoSpanId = getVideoSpanId(resourceJid);
-
- var audioLevelCanvasOrig = $('#' + videoSpanId + '>canvas').get(0);
-
- /*
- * FIXME Testing has shown that audioLevelCanvasOrig may not exist.
- * In such a case, the method CanvasUtil.cloneCanvas may throw an
- * error. Since audio levels are frequently updated, the errors have
- * been observed to pile into the console, strain the CPU.
- */
- if (audioLevelCanvasOrig)
- {
- audioLevelCanvasCache[resourceJid]
- = CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
- }
- }
-
- var canvas = audioLevelCanvasCache[resourceJid];
-
- if (!canvas)
- return;
-
- var drawContext = canvas.getContext('2d');
-
- drawContext.clearRect(0, 0, canvas.width, canvas.height);
-
- var shadowLevel = getShadowLevel(audioLevel);
-
- if (shadowLevel > 0)
- // drawContext, x, y, w, h, r, shadowColor, shadowLevel
- CanvasUtil.drawRoundRectGlow( drawContext,
- interfaceConfig.CANVAS_EXTRA/2, interfaceConfig.CANVAS_EXTRA/2,
- canvas.width - interfaceConfig.CANVAS_EXTRA,
- canvas.height - interfaceConfig.CANVAS_EXTRA,
- interfaceConfig.CANVAS_RADIUS,
- interfaceConfig.SHADOW_COLOR,
- shadowLevel);
- }
-
- /**
- * Returns the shadow/glow level for the given audio level.
- *
- * @param audioLevel the audio level from which we determine the shadow
- * level
- */
- function getShadowLevel (audioLevel) {
- var shadowLevel = 0;
-
- if (audioLevel <= 0.3) {
- shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*(audioLevel/0.3));
- }
- else if (audioLevel <= 0.6) {
- shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3));
- }
- else {
- shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4));
- }
- return shadowLevel;
- }
-
- /**
- * Returns the video span id corresponding to the given resourceJid or local
- * user.
- */
- function getVideoSpanId(resourceJid) {
- var videoSpanId = null;
- if (resourceJid === AudioLevels.LOCAL_LEVEL
- || (APP.xmpp.myResource() && resourceJid
- === APP.xmpp.myResource()))
- videoSpanId = 'localVideoContainer';
- else
- videoSpanId = 'participant_' + resourceJid;
-
- return videoSpanId;
- }
-
- /**
- * Indicates that the remote video has been resized.
- */
- $(document).bind('remotevideo.resized', function (event, width, height) {
- var resized = false;
- $('#remoteVideos>span>canvas').each(function() {
- var canvas = $(this).get(0);
- if (canvas.width !== width + interfaceConfig.CANVAS_EXTRA) {
- canvas.width = width + interfaceConfig.CANVAS_EXTRA;
- resized = true;
- }
-
- if (canvas.heigh !== height + interfaceConfig.CANVAS_EXTRA) {
- canvas.height = height + interfaceConfig.CANVAS_EXTRA;
- resized = true;
- }
- });
-
- if (resized)
- Object.keys(audioLevelCanvasCache).forEach(function (resourceJid) {
- audioLevelCanvasCache[resourceJid].width
- = width + interfaceConfig.CANVAS_EXTRA;
- audioLevelCanvasCache[resourceJid].height
- = height + interfaceConfig.CANVAS_EXTRA;
- });
- });
-
- return my;
-
-})(AudioLevels || {});
-
-module.exports = AudioLevels;
-},{"./CanvasUtils":15}],15:[function(require,module,exports){
-/**
- * Utility class for drawing canvas shapes.
- */
-var CanvasUtil = (function(my) {
-
- /**
- * Draws a round rectangle with a glow. The glowWidth indicates the depth
- * of the glow.
- *
- * @param drawContext the context of the canvas to draw to
- * @param x the x coordinate of the round rectangle
- * @param y the y coordinate of the round rectangle
- * @param w the width of the round rectangle
- * @param h the height of the round rectangle
- * @param glowColor the color of the glow
- * @param glowWidth the width of the glow
- */
- my.drawRoundRectGlow
- = function(drawContext, x, y, w, h, r, glowColor, glowWidth) {
-
- // Save the previous state of the context.
- drawContext.save();
-
- if (w < 2 * r) r = w / 2;
- if (h < 2 * r) r = h / 2;
-
- // Draw a round rectangle.
- drawContext.beginPath();
- drawContext.moveTo(x+r, y);
- drawContext.arcTo(x+w, y, x+w, y+h, r);
- drawContext.arcTo(x+w, y+h, x, y+h, r);
- drawContext.arcTo(x, y+h, x, y, r);
- drawContext.arcTo(x, y, x+w, y, r);
- drawContext.closePath();
-
- // Add a shadow around the rectangle
- drawContext.shadowColor = glowColor;
- drawContext.shadowBlur = glowWidth;
- drawContext.shadowOffsetX = 0;
- drawContext.shadowOffsetY = 0;
-
- // Fill the shape.
- drawContext.fill();
-
- drawContext.save();
-
- drawContext.restore();
-
-// 1) Uncomment this line to use Composite Operation, which is doing the
-// same as the clip function below and is also antialiasing the round
-// border, but is said to be less fast performance wise.
-
-// drawContext.globalCompositeOperation='destination-out';
-
- drawContext.beginPath();
- drawContext.moveTo(x+r, y);
- drawContext.arcTo(x+w, y, x+w, y+h, r);
- drawContext.arcTo(x+w, y+h, x, y+h, r);
- drawContext.arcTo(x, y+h, x, y, r);
- drawContext.arcTo(x, y, x+w, y, r);
- drawContext.closePath();
-
-// 2) Uncomment this line to use Composite Operation, which is doing the
-// same as the clip function below and is also antialiasing the round
-// border, but is said to be less fast performance wise.
-
-// drawContext.fill();
-
- // Comment these two lines if choosing to do the same with composite
- // operation above 1 and 2.
- drawContext.clip();
- drawContext.clearRect(0, 0, 277, 200);
-
- // Restore the previous context state.
- drawContext.restore();
- };
-
- /**
- * Clones the given canvas.
- *
- * @return the new cloned canvas.
- */
- my.cloneCanvas = function (oldCanvas) {
- /*
- * FIXME Testing has shown that oldCanvas may not exist. In such a case,
- * the method CanvasUtil.cloneCanvas may throw an error. Since audio
- * levels are frequently updated, the errors have been observed to pile
- * into the console, strain the CPU.
- */
- if (!oldCanvas)
- return oldCanvas;
-
- //create a new canvas
- var newCanvas = document.createElement('canvas');
- var context = newCanvas.getContext('2d');
-
- //set dimensions
- newCanvas.width = oldCanvas.width;
- newCanvas.height = oldCanvas.height;
-
- //apply the old canvas to the new one
- context.drawImage(oldCanvas, 0, 0);
-
- //return the new canvas
- return newCanvas;
- };
-
- return my;
-})(CanvasUtil || {});
-
-module.exports = CanvasUtil;
-},{}],16:[function(require,module,exports){
-/* global $, APP*/
-
-var LoginDialog = require('./LoginDialog');
-var Moderator = require('../../xmpp/moderator');
-
-/* Initial "authentication required" dialog */
-var authDialog = null;
-/* Loop retry ID that wits for other user to create the room */
-var authRetryId = null;
-var authenticationWindow = null;
-
-var Authentication = {
- openAuthenticationDialog: function (roomName, intervalCallback, callback) {
- // This is the loop that will wait for the room to be created by
- // someone else. 'auth_required.moderator' will bring us back here.
- authRetryId = window.setTimeout(intervalCallback, 5000);
- // Show prompt only if it's not open
- if (authDialog !== null) {
- return;
- }
- // extract room name from 'room@muc.server.net'
- var room = roomName.substr(0, roomName.indexOf('@'));
-
- var title
- = APP.translation.generateTranslationHTML("dialog.WaitingForHost");
- var msg
- = APP.translation.generateTranslationHTML(
- "dialog.WaitForHostMsg", {room: room});
-
- var buttonTxt
- = APP.translation.generateTranslationHTML("dialog.IamHost");
- var buttons = [];
- buttons.push({title: buttonTxt, value: "authNow"});
-
- authDialog = APP.UI.messageHandler.openDialog(
- title,
- msg,
- true,
- buttons,
- function (onSubmitEvent, submitValue) {
-
- // Do not close the dialog yet
- onSubmitEvent.preventDefault();
-
- // Open login popup
- if (submitValue === 'authNow') {
- callback();
- }
- }
- );
- },
- closeAuthenticationWindow: function () {
- if (authenticationWindow) {
- authenticationWindow.close();
- authenticationWindow = null;
- }
- },
- xmppAuthenticate: function () {
-
- var loginDialog = LoginDialog.show(
- function (connection, state) {
- if (!state) {
- // User cancelled
- loginDialog.close();
- return;
- } else if (state == APP.xmpp.Status.CONNECTED) {
-
- loginDialog.close();
-
- Authentication.stopInterval();
- Authentication.closeAuthenticationDialog();
-
- // Close the connection as anonymous one will be used
- // to create the conference. Session-id will authorize
- // the request.
- connection.disconnect();
-
- var roomName = APP.UI.generateRoomName();
- Moderator.allocateConferenceFocus(roomName, function () {
- // If it's not "on the fly" authentication now join
- // the conference room
- if (!APP.xmpp.getMUCJoined()) {
- APP.UI.checkForNicknameAndJoin();
- }
- });
- }
- }, true);
- },
- focusAuthenticationWindow: function () {
- // If auth window exists just bring it to the front
- if (authenticationWindow) {
- authenticationWindow.focus();
- return;
- }
- },
- closeAuthenticationDialog: function () {
- // Close authentication dialog if opened
- if (authDialog) {
- authDialog.close();
- authDialog = null;
- }
- },
- createAuthenticationWindow: function (callback, url) {
- authenticationWindow = APP.UI.messageHandler.openCenteredPopup(
- url, 910, 660,
- // On closed
- function () {
- // Close authentication dialog if opened
- Authentication.closeAuthenticationDialog();
- callback();
- authenticationWindow = null;
- });
- return authenticationWindow;
- },
- stopInterval: function () {
- // Clear retry interval, so that we don't call 'doJoinAfterFocus' twice
- if (authRetryId) {
- window.clearTimeout(authRetryId);
- authRetryId = null;
- }
- }
-};
-
-module.exports = Authentication;
-},{"../../xmpp/moderator":60,"./LoginDialog":17}],17:[function(require,module,exports){
-/* global $, APP, config*/
-
-var XMPP = require('../../xmpp/xmpp');
-var Moderator = require('../../xmpp/moderator');
-
-//FIXME: use LoginDialog to add retries to XMPP.connect method used when
-// anonymous domain is not enabled
-
-/**
- * Creates new Dialog instance.
- * @param callback function(Strophe.Connection, Strophe.Status) called
- * when we either fail to connect or succeed(check Strophe.Status).
- * @param obtainSession true if we want to send ConferenceIQ to Jicofo
- * in order to create session-id after the connection is established.
- * @constructor
- */
-function Dialog(callback, obtainSession) {
-
- var self = this;
-
- var stop = false;
-
- var connection = APP.xmpp.createConnection();
-
- var message = '';
- message += APP.translation.translateString("dialog.passwordRequired");
- message += '
' +
- '' +
- '';
-
- var okButton = APP.translation.generateTranslationHTML("dialog.Ok");
-
- var cancelButton = APP.translation.generateTranslationHTML("dialog.Cancel");
-
- var states = {
- login: {
- html: message,
- buttons: [
- { title: okButton, value: true},
- { title: cancelButton, value: false}
- ],
- focus: ':input:first',
- submit: function (e, v, m, f) {
- e.preventDefault();
- if (v) {
- var jid = f.username;
- var password = f.password;
- if (jid && password) {
- stop = false;
- connection.reset();
- connDialog.goToState('connecting');
- connection.connect(jid, password, stateHandler);
- }
- } else {
- // User cancelled
- stop = true;
- callback();
- }
- }
- },
- connecting: {
- title: APP.translation.translateString('dialog.connecting'),
- html: '',
- buttons: [],
- defaultButton: 0
- },
- finished: {
- title: APP.translation.translateString('dialog.error'),
- html: '',
- buttons: [
- {
- title: APP.translation.translateString('dialog.retry'),
- value: 'retry'
- },
- {
- title: APP.translation.translateString('dialog.Cancel'),
- value: 'cancel'
- },
- ],
- defaultButton: 0,
- submit: function (e, v, m, f) {
- e.preventDefault();
- if (v === 'retry')
- connDialog.goToState('login');
- else
- callback();
- }
- }
- };
-
- var connDialog
- = APP.UI.messageHandler.openDialogWithStates(states,
- { persistent: true, closeText: '' }, null);
-
- var stateHandler = function (status, message) {
- if (stop) {
- return;
- }
-
- var translateKey = "connection." + XMPP.getStatusString(status);
- var statusStr = APP.translation.translateString(translateKey);
-
- // Display current state
- var connectionStatus =
- connDialog.getState('connecting').find('#connectionStatus');
-
- connectionStatus.text(statusStr);
-
- switch (status) {
- case XMPP.Status.CONNECTED:
-
- stop = true;
- if (!obtainSession) {
- callback(connection, status);
- return;
- }
- // Obtaining session-id status
- connectionStatus.text(
- APP.translation.translateString(
- 'connection.FETCH_SESSION_ID'));
-
- // Authenticate with Jicofo and obtain session-id
- var roomName = APP.UI.generateRoomName();
-
- // Jicofo will return new session-id when connected
- // from authenticated domain
- connection.sendIQ(
- Moderator.createConferenceIq(roomName),
- function (result) {
-
- connectionStatus.text(
- APP.translation.translateString(
- 'connection.GOT_SESSION_ID'));
-
- stop = true;
-
- // Parse session-id
- Moderator.parseSessionId(result);
-
- callback(connection, status);
- },
- function (error) {
- console.error("Auth on the fly failed", error);
-
- stop = true;
-
- var errorMsg =
- APP.translation.translateString(
- 'connection.GET_SESSION_ID_ERROR') +
- $(error).find('>error').attr('code');
-
- self.displayError(errorMsg);
-
- connection.disconnect();
- });
-
- break;
- case XMPP.Status.AUTHFAIL:
- case XMPP.Status.CONNFAIL:
- case XMPP.Status.DISCONNECTED:
-
- stop = true;
-
- callback(connection, status);
-
- var errorMessage = statusStr;
-
- if (message)
- {
- errorMessage += ': ' + message;
- }
- self.displayError(errorMessage);
-
- break;
- default:
- break;
- }
- };
-
- /**
- * Displays error message in 'finished' state which allows either to cancel
- * or retry.
- * @param message the final message to be displayed.
- */
- this.displayError = function (message) {
-
- var finishedState = connDialog.getState('finished');
-
- var errorMessageElem = finishedState.find('#errorMessage');
- errorMessageElem.text(message);
-
- connDialog.goToState('finished');
- };
-
- /**
- * Closes LoginDialog.
- */
- this.close = function () {
- stop = true;
- connDialog.close();
- };
-}
-
-var LoginDialog = {
-
- /**
- * Displays login prompt used to establish new XMPP connection. Given
- * callback(Strophe.Connection, Strophe.Status) function will be
- * called when we connect successfully(status === CONNECTED) or when we fail
- * to do so. On connection failure program can call Dialog.close() method in
- * order to cancel or do nothing to let the user retry.
- * @param callback function(Strophe.Connection, Strophe.Status)
- * called when we either fail to connect or succeed(check
- * Strophe.Status).
- * @param obtainSession true if we want to send ConferenceIQ to
- * Jicofo in order to create session-id after the connection is
- * established.
- * @returns {Dialog}
- */
- show: function (callback, obtainSession) {
- return new Dialog(callback, obtainSession);
- }
-};
-
-module.exports = LoginDialog;
-},{"../../xmpp/moderator":60,"../../xmpp/xmpp":68}],18:[function(require,module,exports){
-var Settings = require("../../settings/Settings");
-
-var users = {};
-
-var Avatar = {
-
- /**
- * Sets the user's avatar in the settings menu(if local user), contact list
- * and thumbnail
- * @param jid jid of the user
- * @param id email or userID to be used as a hash
- */
- setUserAvatar: function (jid, id) {
- if (id) {
- if (users[jid] === id) {
- return;
- }
- users[jid] = id;
- }
- var thumbUrl = this.getThumbUrl(jid);
- var contactListUrl = this.getContactListUrl(jid);
- var resourceJid = Strophe.getResourceFromJid(jid);
-
- APP.UI.userAvatarChanged(resourceJid, thumbUrl, contactListUrl);
- },
- /**
- * Returns image URL for the avatar to be displayed on large video area
- * where current active speaker is presented.
- * @param jid full MUC jid of the user for whom we want to obtain avatar URL
- */
- getActiveSpeakerUrl: function (jid) {
- return this.getGravatarUrl(jid, 100);
- },
- /**
- * Returns image URL for the avatar to be displayed on small video thumbnail
- * @param jid full MUC jid of the user for whom we want to obtain avatar URL
- */
- getThumbUrl: function (jid) {
- return this.getGravatarUrl(jid, 100);
- },
- /**
- * Returns the URL for the avatar to be displayed as contactlist item
- * @param jid full MUC jid of the user for whom we want to obtain avatar URL
- */
- getContactListUrl: function (jid) {
- return this.getGravatarUrl(jid, 30);
- },
- getGravatarUrl: function (jid, size) {
- if (!jid) {
- console.error("Get gravatar - jid is undefined");
- return null;
- }
- var id = users[jid];
- if (!id) {
- console.warn("No avatar stored yet for " + jid);
- return null;
- }
- return 'https://www.gravatar.com/avatar/' +
- MD5.hexdigest(id.trim().toLowerCase()) +
- "?d=wavatar&size=" + (size || "30");
- }
-
-};
-
-
-module.exports = Avatar;
-},{"../../settings/Settings":49}],19:[function(require,module,exports){
-/* global $, config,
- setLargeVideoVisible, Util */
-
-var VideoLayout = require("../videolayout/VideoLayout");
-var Prezi = require("../prezi/Prezi");
-var UIUtil = require("../util/UIUtil");
-
-var etherpadName = null;
-var etherpadIFrame = null;
-var domain = null;
-var options = "?showControls=true&showChat=false&showLineNumbers=true&useMonospaceFont=false";
-
-
-/**
- * Resizes the etherpad.
- */
-function resize() {
- if ($('#etherpad>iframe').length) {
- var remoteVideos = $('#remoteVideos');
- var availableHeight
- = window.innerHeight - remoteVideos.outerHeight();
- var availableWidth = UIUtil.getAvailableVideoWidth();
-
- $('#etherpad>iframe').width(availableWidth);
- $('#etherpad>iframe').height(availableHeight);
- }
-}
-
-/**
- * Creates the Etherpad button and adds it to the toolbar.
- */
-function enableEtherpadButton() {
- if (!$('#etherpadButton').is(":visible"))
- $('#etherpadButton').css({display: 'inline-block'});
-}
-
-/**
- * Creates the IFrame for the etherpad.
- */
-function createIFrame() {
- etherpadIFrame = document.createElement('iframe');
- etherpadIFrame.src = domain + etherpadName + options;
- etherpadIFrame.frameBorder = 0;
- etherpadIFrame.scrolling = "no";
- etherpadIFrame.width = $('#largeVideoContainer').width() || 640;
- etherpadIFrame.height = $('#largeVideoContainer').height() || 480;
- etherpadIFrame.setAttribute('style', 'visibility: hidden;');
-
- document.getElementById('etherpad').appendChild(etherpadIFrame);
-
- etherpadIFrame.onload = function() {
-
- document.domain = document.domain;
- bubbleIframeMouseMove(etherpadIFrame);
- setTimeout(function() {
- // the iframes inside of the etherpad are
- // not yet loaded when the etherpad iframe is loaded
- var outer = etherpadIFrame.
- contentDocument.getElementsByName("ace_outer")[0];
- bubbleIframeMouseMove(outer);
- var inner = outer.
- contentDocument.getElementsByName("ace_inner")[0];
- bubbleIframeMouseMove(inner);
- }, 2000);
- };
-}
-
-function bubbleIframeMouseMove(iframe){
- var existingOnMouseMove = iframe.contentWindow.onmousemove;
- iframe.contentWindow.onmousemove = function(e){
- if(existingOnMouseMove) existingOnMouseMove(e);
- var evt = document.createEvent("MouseEvents");
- var boundingClientRect = iframe.getBoundingClientRect();
- evt.initMouseEvent(
- "mousemove",
- true, // bubbles
- false, // not cancelable
- window,
- e.detail,
- e.screenX,
- e.screenY,
- e.clientX + boundingClientRect.left,
- e.clientY + boundingClientRect.top,
- e.ctrlKey,
- e.altKey,
- e.shiftKey,
- e.metaKey,
- e.button,
- null // no related element
- );
- iframe.dispatchEvent(evt);
- };
-}
-
-
-/**
- * On video selected event.
- */
-$(document).bind('video.selected', function (event, isPresentation) {
- if (config.etherpad_base && etherpadIFrame && etherpadIFrame.style.visibility !== 'hidden')
- Etherpad.toggleEtherpad(isPresentation);
-});
-
-
-var Etherpad = {
- /**
- * Initializes the etherpad.
- */
- init: function (name) {
-
- if (config.etherpad_base && !etherpadName && name) {
-
- domain = config.etherpad_base;
-
- etherpadName = name;
-
- enableEtherpadButton();
-
- /**
- * Resizes the etherpad, when the window is resized.
- */
- $(window).resize(function () {
- resize();
- });
- }
- },
-
- /**
- * Opens/hides the Etherpad.
- */
- toggleEtherpad: function (isPresentation) {
- if (!etherpadIFrame)
- createIFrame();
-
- var largeVideo = null;
- if (Prezi.isPresentationVisible())
- largeVideo = $('#presentation>iframe');
- else
- largeVideo = $('#largeVideo');
-
- if ($('#etherpad>iframe').css('visibility') === 'hidden') {
- $('#activeSpeaker').css('visibility', 'hidden');
- largeVideo.fadeOut(300, function () {
- if (Prezi.isPresentationVisible()) {
- largeVideo.css({opacity: '0'});
- } else {
- VideoLayout.setLargeVideoVisible(false);
- }
- });
-
- $('#etherpad>iframe').fadeIn(300, function () {
- document.body.style.background = '#eeeeee';
- $('#etherpad>iframe').css({visibility: 'visible'});
- $('#etherpad').css({zIndex: 2});
- });
- }
- else if ($('#etherpad>iframe')) {
- $('#etherpad>iframe').fadeOut(300, function () {
- $('#etherpad>iframe').css({visibility: 'hidden'});
- $('#etherpad').css({zIndex: 0});
- document.body.style.background = 'black';
- });
-
- if (!isPresentation) {
- $('#largeVideo').fadeIn(300, function () {
- VideoLayout.setLargeVideoVisible(true);
- });
- }
- }
- resize();
- },
-
- isVisible: function() {
- var etherpadIframe = $('#etherpad>iframe');
- return etherpadIframe && etherpadIframe.is(':visible');
- }
-
-};
-
-module.exports = Etherpad;
+function getPreviousResolution(resolution) {
+ if(!Resolutions[resolution])
+ return null;
+ var order = Resolutions[resolution].order;
+ var res = null;
+ var resName = null;
+ for(var i in Resolutions)
+ {
+ var tmp = Resolutions[i];
+ if(res == null || (res.order < tmp.order && tmp.order < order))
+ {
+ resName = i;
+ res = tmp;
+ }
+ }
+ return resName;
+}
-},{"../prezi/Prezi":20,"../util/UIUtil":35,"../videolayout/VideoLayout":41}],20:[function(require,module,exports){
-var ToolbarToggler = require("../toolbars/ToolbarToggler");
-var UIUtil = require("../util/UIUtil");
-var VideoLayout = require("../videolayout/VideoLayout");
-var messageHandler = require("../util/MessageHandler");
-var PreziPlayer = require("./PreziPlayer");
-
-var preziPlayer = null;
-
-var Prezi = {
-
-
- /**
- * Reloads the current presentation.
- */
- reloadPresentation: function() {
- var iframe = document.getElementById(preziPlayer.options.preziId);
- iframe.src = iframe.src;
- },
-
- /**
- * Returns true if the presentation is visible, false -
- * otherwise.
- */
- isPresentationVisible: function () {
- return ($('#presentation>iframe') != null
- && $('#presentation>iframe').css('opacity') == 1);
- },
-
- /**
- * Opens the Prezi dialog, from which the user could choose a presentation
- * to load.
- */
- openPreziDialog: function() {
- var myprezi = APP.xmpp.getPrezi();
- if (myprezi) {
- messageHandler.openTwoButtonDialog("dialog.removePreziTitle",
- null,
- "dialog.removePreziMsg",
- null,
- false,
- "dialog.Remove",
- function(e,v,m,f) {
- if(v) {
- APP.xmpp.removePreziFromPresence();
- }
- }
- );
- }
- else if (preziPlayer != null) {
- messageHandler.openTwoButtonDialog("dialog.sharePreziTitle",
- null, "dialog.sharePreziMsg",
- null,
- false,
- "dialog.Ok",
- function(e,v,m,f) {
- $.prompt.close();
- }
- );
- }
- else {
- var html = APP.translation.generateTranslationHTML(
- "dialog.sharePreziTitle");
- var cancelButton = APP.translation.generateTranslationHTML(
- "dialog.Cancel");
- var shareButton = APP.translation.generateTranslationHTML(
- "dialog.Share");
- var backButton = APP.translation.generateTranslationHTML(
- "dialog.Back");
- var buttons = [];
- var buttons1 = [];
- // Cancel button to both states
- buttons.push({title: cancelButton, value: false});
- buttons1.push({title: cancelButton, value: false});
- // Share button
- buttons.push({title: shareButton, value: true});
- // Back button
- buttons1.push({title: backButton, value: true});
- var linkError = APP.translation.generateTranslationHTML(
- "dialog.preziLinkError");
- var defaultUrl = APP.translation.translateString("defaultPreziLink",
- {url: "http://prezi.com/wz7vhjycl7e6/my-prezi"});
- var openPreziState = {
- state0: {
- html: '' + html + '
' +
- '',
- persistent: false,
- buttons: buttons,
- focus: ':input:first',
- defaultButton: 0,
- submit: function (e, v, m, f) {
- e.preventDefault();
- if(v)
- {
- var preziUrl = f.preziUrl;
-
- if (preziUrl)
- {
- var urlValue
- = encodeURI(UIUtil.escapeHtml(preziUrl));
-
- if (urlValue.indexOf('http://prezi.com/') != 0
- && urlValue.indexOf('https://prezi.com/') != 0)
- {
- $.prompt.goToState('state1');
- return false;
- }
- else {
- var presIdTmp = urlValue.substring(
- urlValue.indexOf("prezi.com/") + 10);
- if (!isAlphanumeric(presIdTmp)
- || presIdTmp.indexOf('/') < 2) {
- $.prompt.goToState('state1');
- return false;
- }
- else {
- APP.xmpp.addToPresence("prezi", urlValue);
- $.prompt.close();
- }
- }
- }
- }
- else
- $.prompt.close();
- }
- },
- state1: {
- html: '' + html + '
' +
- linkError,
- persistent: false,
- buttons: buttons1,
- focus: ':input:first',
- defaultButton: 1,
- submit: function (e, v, m, f) {
- e.preventDefault();
- if (v === 0)
- $.prompt.close();
- else
- $.prompt.goToState('state0');
- }
- }
- };
- messageHandler.openDialogWithStates(openPreziState);
- }
- }
-
-};
-
-/**
- * A new presentation has been added.
- *
- * @param event the event indicating the add of a presentation
- * @param jid the jid from which the presentation was added
- * @param presUrl url of the presentation
- * @param currentSlide the current slide to which we should move
- */
-function presentationAdded(event, jid, presUrl, currentSlide) {
- console.log("presentation added", presUrl);
-
- var presId = getPresentationId(presUrl);
-
- var elementId = 'participant_'
- + Strophe.getResourceFromJid(jid)
- + '_' + presId;
-
-
-
-
- VideoLayout.addPreziContainer(elementId);
- VideoLayout.resizeThumbnails();
-
- var controlsEnabled = false;
- if (jid === APP.xmpp.myJid())
- controlsEnabled = true;
-
- setPresentationVisible(true);
- $('#largeVideoContainer').hover(
- function (event) {
- if (Prezi.isPresentationVisible()) {
- var reloadButtonRight = window.innerWidth
- - $('#presentation>iframe').offset().left
- - $('#presentation>iframe').width();
-
- $('#reloadPresentation').css({ right: reloadButtonRight,
- display:'inline-block'});
- }
- },
- function (event) {
- if (!Prezi.isPresentationVisible())
- $('#reloadPresentation').css({display:'none'});
- else {
- var e = event.toElement || event.relatedTarget;
-
- if (e && e.id != 'reloadPresentation' && e.id != 'header')
- $('#reloadPresentation').css({display:'none'});
- }
- });
-
- preziPlayer = new PreziPlayer(
- 'presentation',
- {preziId: presId,
- width: getPresentationWidth(),
- height: getPresentationHeihgt(),
- controls: controlsEnabled,
- debug: true
- });
-
- $('#presentation>iframe').attr('id', preziPlayer.options.preziId);
-
- preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) {
- console.log("prezi status", event.value);
- if (event.value == PreziPlayer.STATUS_CONTENT_READY) {
- if (jid != APP.xmpp.myJid())
- preziPlayer.flyToStep(currentSlide);
- }
- });
-
- preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) {
- console.log("event value", event.value);
- APP.xmpp.addToPresence("preziSlide", event.value);
- });
-
- $("#" + elementId).css( 'background-image',
- 'url(../images/avatarprezi.png)');
- $("#" + elementId).click(
- function () {
- setPresentationVisible(true);
- }
- );
-};
-
-/**
- * A presentation has been removed.
- *
- * @param event the event indicating the remove of a presentation
- * @param jid the jid for which the presentation was removed
- * @param the url of the presentation
- */
-function presentationRemoved(event, jid, presUrl) {
- console.log('presentation removed', presUrl);
- var presId = getPresentationId(presUrl);
- setPresentationVisible(false);
- $('#participant_'
- + Strophe.getResourceFromJid(jid)
- + '_' + presId).remove();
- $('#presentation>iframe').remove();
- if (preziPlayer != null) {
- preziPlayer.destroy();
- preziPlayer = null;
- }
-};
-
-/**
- * Indicates if the given string is an alphanumeric string.
- * Note that some special characters are also allowed (-, _ , /, &, ?, =, ;) for the
- * purpose of checking URIs.
- */
-function isAlphanumeric(unsafeText) {
- var regex = /^[a-z0-9-_\/&\?=;]+$/i;
- return regex.test(unsafeText);
-}
-
-/**
- * Returns the presentation id from the given url.
- */
-function getPresentationId (presUrl) {
- var presIdTmp = presUrl.substring(presUrl.indexOf("prezi.com/") + 10);
- return presIdTmp.substring(0, presIdTmp.indexOf('/'));
-}
-
-/**
- * Returns the presentation width.
- */
-function getPresentationWidth() {
- var availableWidth = UIUtil.getAvailableVideoWidth();
- var availableHeight = getPresentationHeihgt();
-
- var aspectRatio = 16.0 / 9.0;
- if (availableHeight < availableWidth / aspectRatio) {
- availableWidth = Math.floor(availableHeight * aspectRatio);
- }
- return availableWidth;
-}
-
-/**
- * Returns the presentation height.
- */
-function getPresentationHeihgt() {
- var remoteVideos = $('#remoteVideos');
- return window.innerHeight - remoteVideos.outerHeight();
-}
-
-/**
- * Resizes the presentation iframe.
- */
-function resize() {
- if ($('#presentation>iframe')) {
- $('#presentation>iframe').width(getPresentationWidth());
- $('#presentation>iframe').height(getPresentationHeihgt());
- }
-}
-
-/**
- * Shows/hides a presentation.
- */
-function setPresentationVisible(visible) {
- var prezi = $('#presentation>iframe');
- if (visible) {
- // Trigger the video.selected event to indicate a change in the
- // large video.
- $(document).trigger("video.selected", [true]);
-
- $('#largeVideo').fadeOut(300);
- prezi.fadeIn(300, function() {
- prezi.css({opacity:'1'});
- ToolbarToggler.dockToolbar(true);
- VideoLayout.setLargeVideoVisible(false);
- });
- $('#activeSpeaker').css('visibility', 'hidden');
- }
- else {
- if (prezi.css('opacity') == '1') {
- prezi.fadeOut(300, function () {
- prezi.css({opacity:'0'});
- $('#reloadPresentation').css({display:'none'});
- $('#largeVideo').fadeIn(300, function() {
- VideoLayout.setLargeVideoVisible(true);
- ToolbarToggler.dockToolbar(false);
- });
- });
- }
- }
-}
-
-/**
- * Presentation has been removed.
- */
-$(document).bind('presentationremoved.muc', presentationRemoved);
-
-/**
- * Presentation has been added.
- */
-$(document).bind('presentationadded.muc', presentationAdded);
-
-/*
- * Indicates presentation slide change.
- */
-$(document).bind('gotoslide.muc', function (event, jid, presUrl, current) {
- if (preziPlayer && preziPlayer.getCurrentStep() != current) {
- preziPlayer.flyToStep(current);
-
- var animationStepsArray = preziPlayer.getAnimationCountOnSteps();
- for (var i = 0; i < parseInt(animationStepsArray[current]); i++) {
- preziPlayer.flyToStep(current, i);
- }
- }
-});
-
-/**
- * On video selected event.
- */
-$(document).bind('video.selected', function (event, isPresentation) {
- if (!isPresentation && $('#presentation>iframe')) {
- setPresentationVisible(false);
- }
-});
-
-$(window).resize(function () {
- resize();
-});
-
-module.exports = Prezi;
+function setResolutionConstraints(constraints, resolution, isAndroid)
+{
+ if (resolution && !constraints.video || isAndroid) {
+ constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
+ }
-},{"../toolbars/ToolbarToggler":31,"../util/MessageHandler":33,"../util/UIUtil":35,"../videolayout/VideoLayout":41,"./PreziPlayer":21}],21:[function(require,module,exports){
-(function() {
- "use strict";
- var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
-
- window.PreziPlayer = (function() {
-
- PreziPlayer.API_VERSION = 1;
- PreziPlayer.CURRENT_STEP = 'currentStep';
- PreziPlayer.CURRENT_ANIMATION_STEP = 'currentAnimationStep';
- PreziPlayer.CURRENT_OBJECT = 'currentObject';
- PreziPlayer.STATUS_LOADING = 'loading';
- PreziPlayer.STATUS_READY = 'ready';
- PreziPlayer.STATUS_CONTENT_READY = 'contentready';
- PreziPlayer.EVENT_CURRENT_STEP = "currentStepChange";
- PreziPlayer.EVENT_CURRENT_ANIMATION_STEP = "currentAnimationStepChange";
- PreziPlayer.EVENT_CURRENT_OBJECT = "currentObjectChange";
- PreziPlayer.EVENT_STATUS = "statusChange";
- PreziPlayer.EVENT_PLAYING = "isAutoPlayingChange";
- PreziPlayer.EVENT_IS_MOVING = "isMovingChange";
- PreziPlayer.domain = "https://prezi.com";
- PreziPlayer.path = "/player/";
- PreziPlayer.players = {};
- PreziPlayer.binded_methods = ['changesHandler'];
-
- PreziPlayer.createMultiplePlayers = function(optionArray){
- for(var i=0; i 0 &&
- obj.values.animationCountOnSteps &&
- obj.values.animationCountOnSteps[step] <= animation_step) {
- animation_step = obj.values.animationCountOnSteps[step];
- }
- // jump to animation steps by calling flyToNextStep()
- function doAnimationSteps() {
- if (obj.values.isMoving == true) {
- setTimeout(doAnimationSteps, 100); // wait until the flight ends
- return;
- }
- while (animation_step-- > 0) {
- obj.flyToNextStep(); // do the animation steps
- }
- }
- setTimeout(doAnimationSteps, 200); // 200ms is the internal "reporting" time
- // jump to the step
- return this.sendMessage({
- 'action': 'present',
- 'data': ['moveToStep', step]
- });
- };
-
- PreziPlayer.prototype.toObject = /* toObject is DEPRECATED */
- PreziPlayer.prototype.flyToObject = function(objectId) {
- return this.sendMessage({
- 'action': 'present',
- 'data': ['moveToObject', objectId]
- });
- };
-
- PreziPlayer.prototype.play = function(defaultDelay) {
- return this.sendMessage({
- 'action': 'present',
- 'data': ['startAutoPlay', defaultDelay]
- });
- };
-
- PreziPlayer.prototype.stop = function() {
- return this.sendMessage({
- 'action': 'present',
- 'data': ['stopAutoPlay']
- });
- };
-
- PreziPlayer.prototype.pause = function(defaultDelay) {
- return this.sendMessage({
- 'action': 'present',
- 'data': ['pauseAutoPlay', defaultDelay]
- });
- };
-
- PreziPlayer.prototype.getCurrentStep = function() {
- return this.values.currentStep;
- };
-
- PreziPlayer.prototype.getCurrentAnimationStep = function() {
- return this.values.currentAnimationStep;
- };
-
- PreziPlayer.prototype.getCurrentObject = function() {
- return this.values.currentObject;
- };
-
- PreziPlayer.prototype.getStatus = function() {
- return this.values.status;
- };
-
- PreziPlayer.prototype.isPlaying = function() {
- return this.values.isAutoPlaying;
- };
-
- PreziPlayer.prototype.getStepCount = function() {
- return this.values.stepCount;
- };
-
- PreziPlayer.prototype.getAnimationCountOnSteps = function() {
- return this.values.animationCountOnSteps;
- };
-
- PreziPlayer.prototype.getTitle = function() {
- return this.values.title;
- };
-
- PreziPlayer.prototype.setDimensions = function(dims) {
- for (var parameter in dims) {
- this.iframe[parameter] = dims[parameter];
- }
- }
-
- PreziPlayer.prototype.getDimensions = function() {
- return {
- width: parseInt(this.iframe.width, 10),
- height: parseInt(this.iframe.height, 10)
- }
- }
-
- PreziPlayer.prototype.on = function(event, callback) {
- this.callbacks.push({
- event: event,
- callback: callback
- });
- };
-
- PreziPlayer.prototype.off = function(event, callback) {
- var j, item;
- if (event === undefined) {
- this.callbacks = [];
- }
- j = this.callbacks.length;
- while (j--) {
- item = this.callbacks[j];
- if (item && item.event === event && (callback === undefined || item.callback === callback)){
- this.callbacks.splice(j, 1);
- }
- }
- };
-
- if (window.addEventListener) {
- window.addEventListener('message', PreziPlayer.messageReceived, false);
- } else {
- window.attachEvent('onmessage', PreziPlayer.messageReceived);
- }
-
- return PreziPlayer;
-
- })();
-
-})();
-
-module.exports = PreziPlayer;
+ if(Resolutions[resolution])
+ {
+ constraints.video.mandatory.minWidth = Resolutions[resolution].width;
+ constraints.video.mandatory.minHeight = Resolutions[resolution].height;
+ }
+ else
+ {
+ if (isAndroid) {
+ constraints.video.mandatory.minWidth = 320;
+ constraints.video.mandatory.minHeight = 240;
+ constraints.video.mandatory.maxFrameRate = 15;
+ }
+ }
-},{}],22:[function(require,module,exports){
-var Chat = require("./chat/Chat");
-var ContactList = require("./contactlist/ContactList");
-var Settings = require("./../../settings/Settings");
-var SettingsMenu = require("./settings/SettingsMenu");
-var VideoLayout = require("../videolayout/VideoLayout");
-var ToolbarToggler = require("../toolbars/ToolbarToggler");
-var UIUtil = require("../util/UIUtil");
-var LargeVideo = require("../videolayout/LargeVideo");
-
-/**
- * Toggler for the chat, contact list, settings menu, etc..
- */
-var PanelToggler = (function(my) {
-
- var currentlyOpen = null;
- var buttons = {
- '#chatspace': '#chatBottomButton',
- '#contactlist': '#contactListButton',
- '#settingsmenu': '#settingsButton'
- };
-
- /**
- * Toggles the windows in the side panel
- * @param object the window that should be shown
- * @param selector the selector for the element containing the panel
- * @param onOpenComplete function to be called when the panel is opened
- * @param onOpen function to be called if the window is going to be opened
- * @param onClose function to be called if the window is going to be closed
- */
- var toggle = function(object, selector, onOpenComplete, onOpen, onClose) {
- UIUtil.buttonClick(buttons[selector], "active");
-
- if (object.isVisible()) {
- $("#toast-container").animate({
- right: '5px'
- },
- {
- queue: false,
- duration: 500
- });
- $(selector).hide("slide", {
- direction: "right",
- queue: false,
- duration: 500
- });
- if(typeof onClose === "function") {
- onClose();
- }
-
- currentlyOpen = null;
- }
- else {
- // Undock the toolbar when the chat is shown and if we're in a
- // video mode.
- if (LargeVideo.isLargeVideoVisible()) {
- ToolbarToggler.dockToolbar(false);
- }
-
- if(currentlyOpen) {
- var current = $(currentlyOpen);
- UIUtil.buttonClick(buttons[currentlyOpen], "active");
- current.css('z-index', 4);
- setTimeout(function () {
- current.css('display', 'none');
- current.css('z-index', 5);
- }, 500);
- }
-
- $("#toast-container").animate({
- right: (PanelToggler.getPanelSize()[0] + 5) + 'px'
- },
- {
- queue: false,
- duration: 500
- });
- $(selector).show("slide", {
- direction: "right",
- queue: false,
- duration: 500,
- complete: onOpenComplete
- });
- if(typeof onOpen === "function") {
- onOpen();
- }
-
- currentlyOpen = selector;
- }
- };
-
- /**
- * Opens / closes the chat area.
- */
- my.toggleChat = function() {
- var chatCompleteFunction = Chat.isVisible() ?
- function() {} : function () {
- Chat.scrollChatToBottom();
- $('#chatspace').trigger('shown');
- };
-
- VideoLayout.resizeVideoArea(!Chat.isVisible(), chatCompleteFunction);
-
- toggle(Chat,
- '#chatspace',
- function () {
- // Request the focus in the nickname field or the chat input field.
- if ($('#nickname').css('visibility') === 'visible') {
- $('#nickinput').focus();
- } else {
- $('#usermsg').focus();
- }
- },
- null,
- Chat.resizeChat,
- null);
- };
-
- /**
- * Opens / closes the contact list area.
- */
- my.toggleContactList = function () {
- var completeFunction = ContactList.isVisible() ?
- function() {} : function () { $('#contactlist').trigger('shown');};
- VideoLayout.resizeVideoArea(!ContactList.isVisible(), completeFunction);
-
- toggle(ContactList,
- '#contactlist',
- null,
- function() {
- ContactList.setVisualNotification(false);
- },
- null);
- };
-
- /**
- * Opens / closes the settings menu
- */
- my.toggleSettingsMenu = function() {
- VideoLayout.resizeVideoArea(!SettingsMenu.isVisible(), function (){});
- toggle(SettingsMenu,
- '#settingsmenu',
- null,
- function() {
- var settings = Settings.getSettings();
- $('#setDisplayName').get(0).value = settings.displayName;
- $('#setEmail').get(0).value = settings.email;
- },
- null);
- };
-
- /**
- * Returns the size of the side panel.
- */
- my.getPanelSize = function () {
- var availableHeight = window.innerHeight;
- var availableWidth = window.innerWidth;
-
- var panelWidth = 200;
- if (availableWidth * 0.2 < 200) {
- panelWidth = availableWidth * 0.2;
- }
-
- return [panelWidth, availableHeight];
- };
-
- my.isVisible = function() {
- return (Chat.isVisible() || ContactList.isVisible() || SettingsMenu.isVisible());
- };
-
- return my;
-
-}(PanelToggler || {}));
-
-module.exports = PanelToggler;
-},{"../toolbars/ToolbarToggler":31,"../util/UIUtil":35,"../videolayout/LargeVideo":37,"../videolayout/VideoLayout":41,"./../../settings/Settings":49,"./chat/Chat":23,"./contactlist/ContactList":27,"./settings/SettingsMenu":28}],23:[function(require,module,exports){
-/* global $, Util, nickname:true */
-var Replacement = require("./Replacement");
-var CommandsProcessor = require("./Commands");
-var ToolbarToggler = require("../../toolbars/ToolbarToggler");
-var smileys = require("./smileys.json").smileys;
-var NicknameHandler = require("../../util/NicknameHandler");
-var UIUtil = require("../../util/UIUtil");
-var UIEvents = require("../../../../service/UI/UIEvents");
-
-var notificationInterval = false;
-var unreadMessages = 0;
-
-
-/**
- * Shows/hides a visual notification, indicating that a message has arrived.
- */
-function setVisualNotification(show) {
- var unreadMsgElement = document.getElementById('unreadMessages');
- var unreadMsgBottomElement
- = document.getElementById('bottomUnreadMessages');
-
- var glower = $('#chatButton');
- var bottomGlower = $('#chatBottomButton');
-
- if (unreadMessages) {
- unreadMsgElement.innerHTML = unreadMessages.toString();
- unreadMsgBottomElement.innerHTML = unreadMessages.toString();
-
- ToolbarToggler.dockToolbar(true);
-
- var chatButtonElement
- = document.getElementById('chatButton').parentNode;
- var leftIndent = (UIUtil.getTextWidth(chatButtonElement) -
- UIUtil.getTextWidth(unreadMsgElement)) / 2;
- var topIndent = (UIUtil.getTextHeight(chatButtonElement) -
- UIUtil.getTextHeight(unreadMsgElement)) / 2 - 3;
-
- unreadMsgElement.setAttribute(
- 'style',
- 'top:' + topIndent +
- '; left:' + leftIndent + ';');
-
- var chatBottomButtonElement
- = document.getElementById('chatBottomButton').parentNode;
- var bottomLeftIndent = (UIUtil.getTextWidth(chatBottomButtonElement) -
- UIUtil.getTextWidth(unreadMsgBottomElement)) / 2;
- var bottomTopIndent = (UIUtil.getTextHeight(chatBottomButtonElement) -
- UIUtil.getTextHeight(unreadMsgBottomElement)) / 2 - 2;
-
- unreadMsgBottomElement.setAttribute(
- 'style',
- 'top:' + bottomTopIndent +
- '; left:' + bottomLeftIndent + ';');
-
-
- if (!glower.hasClass('icon-chat-simple')) {
- glower.removeClass('icon-chat');
- glower.addClass('icon-chat-simple');
- }
- }
- else {
- unreadMsgElement.innerHTML = '';
- unreadMsgBottomElement.innerHTML = '';
- glower.removeClass('icon-chat-simple');
- glower.addClass('icon-chat');
- }
-
- if (show && !notificationInterval) {
- notificationInterval = window.setInterval(function () {
- glower.toggleClass('active');
- bottomGlower.toggleClass('active glowing');
- }, 800);
- }
- else if (!show && notificationInterval) {
- window.clearInterval(notificationInterval);
- notificationInterval = false;
- glower.removeClass('active');
- bottomGlower.removeClass('glowing');
- bottomGlower.addClass('active');
- }
-}
-
-
-/**
- * Returns the current time in the format it is shown to the user
- * @returns {string}
- */
-function getCurrentTime(stamp) {
- var now = (stamp? new Date(stamp): new Date());
- var hour = now.getHours();
- var minute = now.getMinutes();
- var second = now.getSeconds();
- if(hour.toString().length === 1) {
- hour = '0'+hour;
- }
- if(minute.toString().length === 1) {
- minute = '0'+minute;
- }
- if(second.toString().length === 1) {
- second = '0'+second;
- }
- return hour+':'+minute+':'+second;
-}
-
-function toggleSmileys()
-{
- var smileys = $('#smileysContainer');
- if(!smileys.is(':visible')) {
- smileys.show("slide", { direction: "down", duration: 300});
- } else {
- smileys.hide("slide", { direction: "down", duration: 300});
- }
- $('#usermsg').focus();
-}
-
-function addClickFunction(smiley, number) {
- smiley.onclick = function addSmileyToMessage() {
- var usermsg = $('#usermsg');
- var message = usermsg.val();
- message += smileys['smiley' + number];
- usermsg.val(message);
- usermsg.get(0).setSelectionRange(message.length, message.length);
- toggleSmileys();
- usermsg.focus();
- };
-}
-
-/**
- * Adds the smileys container to the chat
- */
-function addSmileys() {
- var smileysContainer = document.createElement('div');
- smileysContainer.id = 'smileysContainer';
- for(var i = 1; i <= 21; i++) {
- var smileyContainer = document.createElement('div');
- smileyContainer.id = 'smiley' + i;
- smileyContainer.className = 'smileyContainer';
- var smiley = document.createElement('img');
- smiley.src = 'images/smileys/smiley' + i + '.svg';
- smiley.className = 'smiley';
- addClickFunction(smiley, i);
- smileyContainer.appendChild(smiley);
- smileysContainer.appendChild(smileyContainer);
- }
-
- $("#chatspace").append(smileysContainer);
-}
-
-/**
- * Resizes the chat conversation.
- */
-function resizeChatConversation() {
- var msgareaHeight = $('#usermsg').outerHeight();
- var chatspace = $('#chatspace');
- var width = chatspace.width();
- var chat = $('#chatconversation');
- var smileys = $('#smileysarea');
-
- smileys.height(msgareaHeight);
- $("#smileys").css('bottom', (msgareaHeight - 26) / 2);
- $('#smileysContainer').css('bottom', msgareaHeight);
- chat.width(width - 10);
- chat.height(window.innerHeight - 15 - msgareaHeight);
-}
-
-/**
- * Chat related user interface.
- */
-var Chat = (function (my) {
- /**
- * Initializes chat related interface.
- */
- my.init = function () {
- if(NicknameHandler.getNickname())
- Chat.setChatConversationMode(true);
- NicknameHandler.addListener(UIEvents.NICKNAME_CHANGED,
- function (nickname) {
- Chat.setChatConversationMode(true);
- });
-
- $('#nickinput').keydown(function (event) {
- if (event.keyCode === 13) {
- event.preventDefault();
- var val = UIUtil.escapeHtml(this.value);
- this.value = '';
- if (!NicknameHandler.getNickname()) {
- NicknameHandler.setNickname(val);
-
- return;
- }
- }
- });
-
- $('#usermsg').keydown(function (event) {
- if (event.keyCode === 13) {
- event.preventDefault();
- var value = this.value;
- $('#usermsg').val('').trigger('autosize.resize');
- this.focus();
- var command = new CommandsProcessor(value);
- if(command.isCommand())
- {
- command.processCommand();
- }
- else
- {
- var message = UIUtil.escapeHtml(value);
- APP.xmpp.sendChatMessage(message, NicknameHandler.getNickname());
- }
- }
- });
-
- var onTextAreaResize = function () {
- resizeChatConversation();
- Chat.scrollChatToBottom();
- };
- $('#usermsg').autosize({callback: onTextAreaResize});
-
- $("#chatspace").bind("shown",
- function () {
- unreadMessages = 0;
- setVisualNotification(false);
- });
-
- addSmileys();
- };
-
- /**
- * Appends the given message to the chat conversation.
- */
- my.updateChatConversation = function (from, displayName, message, myjid, stamp) {
- var divClassName = '';
-
- if (APP.xmpp.myJid() === from) {
- divClassName = "localuser";
- }
- else {
- divClassName = "remoteuser";
-
- if (!Chat.isVisible()) {
- unreadMessages++;
- UIUtil.playSoundNotification('chatNotification');
- setVisualNotification(true);
- }
- }
-
- // replace links and smileys
- // Strophe already escapes special symbols on sending,
- // so we escape here only tags to avoid double &
- var escMessage = message.replace(//g, '>').replace(/\n/g, '
');
- var escDisplayName = UIUtil.escapeHtml(displayName);
- message = Replacement.processReplacements(escMessage);
-
- var messageContainer =
- ''+
- '
![](../images/chatArrow.svg)
' +
- '
' + escDisplayName +
- '
' + '
' + getCurrentTime(stamp) +
- '
' + '
' + message + '
' +
- '
';
-
- $('#chatconversation').append(messageContainer);
- $('#chatconversation').animate(
- { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
- };
-
- /**
- * Appends error message to the conversation
- * @param errorMessage the received error message.
- * @param originalText the original message.
- */
- my.chatAddError = function(errorMessage, originalText)
- {
- errorMessage = UIUtil.escapeHtml(errorMessage);
- originalText = UIUtil.escapeHtml(originalText);
-
- $('#chatconversation').append(
- 'Error: ' + 'Your message' +
- (originalText? (' \"'+ originalText + '\"') : "") +
- ' was not sent.' +
- (errorMessage? (' Reason: ' + errorMessage) : '') + '
');
- $('#chatconversation').animate(
- { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
- };
-
- /**
- * Sets the subject to the UI
- * @param subject the subject
- */
- my.chatSetSubject = function(subject)
- {
- if(subject)
- subject = subject.trim();
- $('#subject').html(Replacement.linkify(UIUtil.escapeHtml(subject)));
- if(subject === "")
- {
- $("#subject").css({display: "none"});
- }
- else
- {
- $("#subject").css({display: "block"});
- }
- };
-
-
-
- /**
- * Sets the chat conversation mode.
- */
- my.setChatConversationMode = function (isConversationMode) {
- if (isConversationMode) {
- $('#nickname').css({visibility: 'hidden'});
- $('#chatconversation').css({visibility: 'visible'});
- $('#usermsg').css({visibility: 'visible'});
- $('#smileysarea').css({visibility: 'visible'});
- $('#usermsg').focus();
- }
- };
-
- /**
- * Resizes the chat area.
- */
- my.resizeChat = function () {
- var chatSize = require("../SidePanelToggler").getPanelSize();
-
- $('#chatspace').width(chatSize[0]);
- $('#chatspace').height(chatSize[1]);
-
- resizeChatConversation();
- };
-
- /**
- * Indicates if the chat is currently visible.
- */
- my.isVisible = function () {
- return $('#chatspace').is(":visible");
- };
- /**
- * Shows and hides the window with the smileys
- */
- my.toggleSmileys = toggleSmileys;
-
- /**
- * Scrolls chat to the bottom.
- */
- my.scrollChatToBottom = function() {
- setTimeout(function () {
- $('#chatconversation').scrollTop(
- $('#chatconversation')[0].scrollHeight);
- }, 5);
- };
-
-
- return my;
-}(Chat || {}));
-module.exports = Chat;
-},{"../../../../service/UI/UIEvents":104,"../../toolbars/ToolbarToggler":31,"../../util/NicknameHandler":34,"../../util/UIUtil":35,"../SidePanelToggler":22,"./Commands":24,"./Replacement":25,"./smileys.json":26}],24:[function(require,module,exports){
-var UIUtil = require("../../util/UIUtil");
-
-/**
- * List with supported commands. The keys are the names of the commands and
- * the value is the function that processes the message.
- * @type {{String: function}}
- */
-var commands = {
- "topic" : processTopic
-};
-
-/**
- * Extracts the command from the message.
- * @param message the received message
- * @returns {string} the command
- */
-function getCommand(message)
-{
- if(message)
- {
- for(var command in commands)
- {
- if(message.indexOf("/" + command) == 0)
- return command;
- }
- }
- return "";
-};
-
-/**
- * Processes the data for topic command.
- * @param commandArguments the arguments of the topic command.
- */
-function processTopic(commandArguments)
-{
- var topic = UIUtil.escapeHtml(commandArguments);
- APP.xmpp.setSubject(topic);
-}
-
-/**
- * Constructs new CommandProccessor instance from a message that
- * handles commands received via chat messages.
- * @param message the message
- * @constructor
- */
-function CommandsProcessor(message)
-{
-
-
- var command = getCommand(message);
-
- /**
- * Returns the name of the command.
- * @returns {String} the command
- */
- this.getCommand = function()
- {
- return command;
- };
-
-
- var messageArgument = message.substr(command.length + 2);
-
- /**
- * Returns the arguments of the command.
- * @returns {string}
- */
- this.getArgument = function()
- {
- return messageArgument;
- };
-}
-
-/**
- * Checks whether this instance is valid command or not.
- * @returns {boolean}
- */
-CommandsProcessor.prototype.isCommand = function()
-{
- if(this.getCommand())
- return true;
- return false;
-};
-
-/**
- * Processes the command.
- */
-CommandsProcessor.prototype.processCommand = function()
-{
- if(!this.isCommand())
- return;
-
- commands[this.getCommand()](this.getArgument());
-
-};
-
-module.exports = CommandsProcessor;
-},{"../../util/UIUtil":35}],25:[function(require,module,exports){
-var Smileys = require("./smileys.json");
-/**
- * Processes links and smileys in "body"
- */
-function processReplacements(body)
-{
- //make links clickable
- body = linkify(body);
-
- //add smileys
- body = smilify(body);
-
- return body;
-}
-
-/**
- * Finds and replaces all links in the links in "body"
- * with their
- */
-function linkify(inputText)
-{
- var replacedText, replacePattern1, replacePattern2, replacePattern3;
-
- //URLs starting with http://, https://, or ftp://
- replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
- replacedText = inputText.replace(replacePattern1, '$1');
-
- //URLs starting with "www." (without // before it, or it'd re-link the ones done above).
- replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
- replacedText = replacedText.replace(replacePattern2, '$1$2');
-
- //Change email addresses to mailto:: links.
- replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
- replacedText = replacedText.replace(replacePattern3, '$1');
-
- return replacedText;
-}
-
-/**
- * Replaces common smiley strings with images
- */
-function smilify(body)
-{
- if(!body) {
- return body;
- }
-
- var regexs = Smileys["regexs"];
- for(var smiley in regexs) {
- if(regexs.hasOwnProperty(smiley)) {
- body = body.replace(regexs[smiley],
- '
');
- }
- }
-
- return body;
-}
-
-module.exports = {
- processReplacements: processReplacements,
- linkify: linkify
-};
+ if (constraints.video.mandatory.minWidth)
+ constraints.video.mandatory.maxWidth = constraints.video.mandatory.minWidth;
+ if (constraints.video.mandatory.minHeight)
+ constraints.video.mandatory.maxHeight = constraints.video.mandatory.minHeight;
+}
-},{"./smileys.json":26}],26:[function(require,module,exports){
-module.exports={
- "smileys": {
- "smiley1": ":)",
- "smiley2": ":(",
- "smiley3": ":D",
- "smiley4": "(y)",
- "smiley5": " :P",
- "smiley6": "(wave)",
- "smiley7": "(blush)",
- "smiley8": "(chuckle)",
- "smiley9": "(shocked)",
- "smiley10": ":*",
- "smiley11": "(n)",
- "smiley12": "(search)",
- "smiley13": " <3",
- "smiley14": "(oops)",
- "smiley15": "(angry)",
- "smiley16": "(angel)",
- "smiley17": "(sick)",
- "smiley18": ";(",
- "smiley19": "(bomb)",
- "smiley20": "(clap)",
- "smiley21": " ;)"
- },
- "regexs": {
- "smiley2": /(:-\(\(|:-\(|:\(\(|:\(|\(sad\))/gi,
- "smiley3": /(:-\)\)|:\)\)|\(lol\)|:-D|:D)/gi,
- "smiley1": /(:-\)|:\))/gi,
- "smiley4": /(\(y\)|\(Y\)|\(ok\))/gi,
- "smiley5": /(:-P|:P|:-p|:p)/gi,
- "smiley6": /(\(wave\))/gi,
- "smiley7": /(\(blush\))/gi,
- "smiley8": /(\(chuckle\))/gi,
- "smiley9": /(:-0|\(shocked\))/gi,
- "smiley10": /(:-\*|:\*|\(kiss\))/gi,
- "smiley11": /(\(n\))/gi,
- "smiley12": /(\(search\))/g,
- "smiley13": /(<3|<3|<3|\(L\)|\(l\)|\(H\)|\(h\))/gi,
- "smiley14": /(\(oops\))/gi,
- "smiley15": /(\(angry\))/gi,
- "smiley16": /(\(angel\))/gi,
- "smiley17": /(\(sick\))/gi,
- "smiley18": /(;-\(\(|;\(\(|;-\(|;\(|:"\(|:"-\(|:~-\(|:~\(|\(upset\))/gi,
- "smiley19": /(\(bomb\))/gi,
- "smiley20": /(\(clap\))/gi,
- "smiley21": /(;-\)|;\)|;-\)\)|;\)\)|;-D|;D|\(wink\))/gi
- }
-}
+function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid)
+{
+ var constraints = {audio: false, video: false};
-},{}],27:[function(require,module,exports){
-
-var Avatar = require('../../avatar/Avatar');
-
-var numberOfContacts = 0;
-var notificationInterval;
-
-/**
- * Updates the number of participants in the contact list button and sets
- * the glow
- * @param delta indicates whether a new user has joined (1) or someone has
- * left(-1)
- */
-function updateNumberOfParticipants(delta) {
- numberOfContacts += delta;
- if (numberOfContacts === 1) {
- // when the user is alone we don't show the number of participants
- $("#numberOfParticipants").text('');
- ContactList.setVisualNotification(false);
- } else if (numberOfContacts > 1) {
- ContactList.setVisualNotification(!ContactList.isVisible());
- $("#numberOfParticipants").text(numberOfContacts);
- } else {
- console.error("Invalid number of participants: " + numberOfContacts);
- }
-}
-
-/**
- * Creates the avatar element.
- *
- * @return the newly created avatar element
- */
-function createAvatar(jid) {
- var avatar = document.createElement('img');
- avatar.className = "icon-avatar avatar";
- avatar.src = Avatar.getContactListUrl(jid);
-
- return avatar;
-}
-
-/**
- * Creates the display name paragraph.
- *
- * @param displayName the display name to set
- */
-function createDisplayNameParagraph(key, displayName) {
- var p = document.createElement('p');
- if(displayName)
- p.innerText = displayName;
- else if(key)
- {
- p.setAttribute("data-i18n",key);
- p.innerText = APP.translation.translateString(key);
- }
-
- return p;
-}
-
-
-function stopGlowing(glower) {
- window.clearInterval(notificationInterval);
- notificationInterval = false;
- glower.removeClass('glowing');
- if (!ContactList.isVisible()) {
- glower.removeClass('active');
- }
-}
-
-
-/**
- * Contact list.
- */
-var ContactList = {
- /**
- * Indicates if the chat is currently visible.
- *
- * @return true if the chat is currently visible, false -
- * otherwise
- */
- isVisible: function () {
- return $('#contactlist').is(":visible");
- },
-
- /**
- * Adds a contact for the given peerJid if such doesn't yet exist.
- *
- * @param peerJid the peerJid corresponding to the contact
- */
- ensureAddContact: function (peerJid) {
- var resourceJid = Strophe.getResourceFromJid(peerJid);
-
- var contact = $('#contacts>li[id="' + resourceJid + '"]');
-
- if (!contact || contact.length <= 0)
- ContactList.addContact(peerJid);
- },
-
- /**
- * Adds a contact for the given peer jid.
- *
- * @param peerJid the jid of the contact to add
- */
- addContact: function (peerJid) {
- var resourceJid = Strophe.getResourceFromJid(peerJid);
-
- var contactlist = $('#contacts');
-
- var newContact = document.createElement('li');
- newContact.id = resourceJid;
- newContact.className = "clickable";
- newContact.onclick = function (event) {
- if (event.currentTarget.className === "clickable") {
- $(ContactList).trigger('contactclicked', [peerJid]);
- }
- };
-
- newContact.appendChild(createAvatar(peerJid));
- newContact.appendChild(createDisplayNameParagraph("participant"));
-
- if (resourceJid === APP.xmpp.myResource()) {
- contactlist.prepend(newContact);
- }
- else {
- contactlist.append(newContact);
- }
- updateNumberOfParticipants(1);
- },
-
- /**
- * Removes a contact for the given peer jid.
- *
- * @param peerJid the peerJid corresponding to the contact to remove
- */
- removeContact: function (peerJid) {
- var resourceJid = Strophe.getResourceFromJid(peerJid);
-
- var contact = $('#contacts>li[id="' + resourceJid + '"]');
-
- if (contact && contact.length > 0) {
- var contactlist = $('#contactlist>ul');
-
- contactlist.get(0).removeChild(contact.get(0));
-
- updateNumberOfParticipants(-1);
- }
- },
-
- setVisualNotification: function (show, stopGlowingIn) {
- var glower = $('#contactListButton');
-
- if (show && !notificationInterval) {
- notificationInterval = window.setInterval(function () {
- glower.toggleClass('active glowing');
- }, 800);
- }
- else if (!show && notificationInterval) {
- stopGlowing(glower);
- }
- if (stopGlowingIn) {
- setTimeout(function () {
- stopGlowing(glower);
- }, stopGlowingIn);
- }
- },
-
- setClickable: function (resourceJid, isClickable) {
- var contact = $('#contacts>li[id="' + resourceJid + '"]');
- if (isClickable) {
- contact.addClass('clickable');
- } else {
- contact.removeClass('clickable');
- }
- },
-
- onDisplayNameChange: function (peerJid, displayName) {
- if (peerJid === 'localVideoContainer')
- peerJid = APP.xmpp.myJid();
-
- var resourceJid = Strophe.getResourceFromJid(peerJid);
-
- var contactName = $('#contacts #' + resourceJid + '>p');
-
- if (contactName && displayName && displayName.length > 0)
- contactName.html(displayName);
- },
-
- userAvatarChanged: function (resourceJid, contactListUrl) {
- // set the avatar in the contact list
- var contact = $('#' + resourceJid + '>img');
- if (contact && contact.length > 0) {
- contact.get(0).src = contactListUrl;
- }
-
- }
-};
-
-module.exports = ContactList;
-},{"../../avatar/Avatar":18}],28:[function(require,module,exports){
-var Avatar = require("../../avatar/Avatar");
-var Settings = require("./../../../settings/Settings");
-var UIUtil = require("../../util/UIUtil");
-var languages = require("../../../../service/translation/languages");
-
-function generateLanguagesSelectBox()
-{
- var currentLang = APP.translation.getCurrentLanguage();
- var html = "";
-}
-
-
-var SettingsMenu = {
-
- init: function () {
- $("#startMutedOptions").before(generateLanguagesSelectBox());
- APP.translation.translateElement($("#languages_selectbox"));
- $('#settingsmenu>input').keyup(function(event){
- if(event.keyCode === 13) {//enter
- SettingsMenu.update();
- }
- });
-
- if(APP.xmpp.isModerator())
- {
- $("#startMutedOptions").css("display", "block");
- }
- else
- {
- $("#startMutedOptions").css("display", "none");
- }
-
- $("#updateSettings").click(function () {
- SettingsMenu.update();
- });
- },
-
- onRoleChanged: function () {
- if(APP.xmpp.isModerator())
- {
- $("#startMutedOptions").css("display", "block");
- }
- else
- {
- $("#startMutedOptions").css("display", "none");
- }
- },
-
- setStartMuted: function (audio, video) {
- $("#startAudioMuted").attr("checked", audio);
- $("#startVideoMuted").attr("checked", video);
- },
-
- update: function() {
- var newDisplayName = UIUtil.escapeHtml($('#setDisplayName').get(0).value);
- var newEmail = UIUtil.escapeHtml($('#setEmail').get(0).value);
-
- if(newDisplayName) {
- var displayName = Settings.setDisplayName(newDisplayName);
- APP.xmpp.addToPresence("displayName", displayName, true);
- }
-
- var language = $("#languages_selectbox").val();
- APP.translation.setLanguage(language);
- Settings.setLanguage(language);
-
- APP.xmpp.addToPresence("email", newEmail);
- var email = Settings.setEmail(newEmail);
-
- var startAudioMuted = ($("#startAudioMuted").is(":checked"));
- var startVideoMuted = ($("#startVideoMuted").is(":checked"));
- APP.xmpp.addToPresence("startMuted",
- [startAudioMuted, startVideoMuted]);
-
- Avatar.setUserAvatar(APP.xmpp.myJid(), email);
- },
-
- isVisible: function() {
- return $('#settingsmenu').is(':visible');
- },
-
- setDisplayName: function(newDisplayName) {
- var displayName = Settings.setDisplayName(newDisplayName);
- $('#setDisplayName').get(0).value = displayName;
- },
-
- onDisplayNameChange: function(peerJid, newDisplayName) {
- if(peerJid === 'localVideoContainer' ||
- peerJid === APP.xmpp.myJid()) {
- this.setDisplayName(newDisplayName);
- }
- },
- changeAvatar: function (thumbUrl) {
- $('#avatar').get(0).src = thumbUrl;
- }
-};
-
-
-module.exports = SettingsMenu;
-},{"../../../../service/translation/languages":109,"../../avatar/Avatar":18,"../../util/UIUtil":35,"./../../../settings/Settings":49}],29:[function(require,module,exports){
-var PanelToggler = require("../side_pannels/SidePanelToggler");
-
-var buttonHandlers = {
- "bottom_toolbar_contact_list": function () {
- BottomToolbar.toggleContactList();
- },
- "bottom_toolbar_film_strip": function () {
- BottomToolbar.toggleFilmStrip();
- },
- "bottom_toolbar_chat": function () {
- BottomToolbar.toggleChat();
- }
-};
-
-var BottomToolbar = (function (my) {
- my.init = function () {
- for(var k in buttonHandlers)
- $("#" + k).click(buttonHandlers[k]);
- };
-
- my.toggleChat = function() {
- PanelToggler.toggleChat();
- };
-
- my.toggleContactList = function() {
- PanelToggler.toggleContactList();
- };
-
- my.toggleFilmStrip = function() {
- var filmstrip = $("#remoteVideos");
- filmstrip.toggleClass("hidden");
- };
-
- $(document).bind("remotevideo.resized", function (event, width, height) {
- var bottom = (height - $('#bottomToolbar').outerHeight())/2 + 18;
-
- $('#bottomToolbar').css({bottom: bottom + 'px'});
- });
-
- return my;
-}(BottomToolbar || {}));
-
-module.exports = BottomToolbar;
+ if (um.indexOf('video') >= 0) {
+ constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
+ }
+ if (um.indexOf('audio') >= 0) {
+ constraints.audio = { mandatory: {}, optional: []};// same behaviour as true
+ }
+ if (um.indexOf('screen') >= 0) {
+ if (RTCBrowserType.isChrome()) {
+ constraints.video = {
+ mandatory: {
+ chromeMediaSource: "screen",
+ googLeakyBucket: true,
+ maxWidth: window.screen.width,
+ maxHeight: window.screen.height,
+ maxFrameRate: 3
+ },
+ optional: []
+ };
+ } else if (RTCBrowserType.isTemasysPluginUsed()) {
+ constraints.video = {
+ optional: [
+ {
+ sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey
+ }
+ ]
+ };
+ } else {
+ console.error(
+ "'screen' WebRTC media source is supported only in Chrome" +
+ " and with Temasys plugin");
+ }
+ }
+ if (um.indexOf('desktop') >= 0) {
+ constraints.video = {
+ mandatory: {
+ chromeMediaSource: "desktop",
+ chromeMediaSourceId: desktopStream,
+ googLeakyBucket: true,
+ maxWidth: window.screen.width,
+ maxHeight: window.screen.height,
+ maxFrameRate: 3
+ },
+ optional: []
+ };
+ }
-},{"../side_pannels/SidePanelToggler":22}],30:[function(require,module,exports){
-/* global APP,$, buttonClick, config, lockRoom,
- setSharedKey, Util */
-var messageHandler = require("../util/MessageHandler");
-var BottomToolbar = require("./BottomToolbar");
-var Prezi = require("../prezi/Prezi");
-var Etherpad = require("../etherpad/Etherpad");
-var PanelToggler = require("../side_pannels/SidePanelToggler");
-var Authentication = require("../authentication/Authentication");
-var UIUtil = require("../util/UIUtil");
-var AuthenticationEvents
- = require("../../../service/authentication/AuthenticationEvents");
-
-var roomUrl = null;
-var sharedKey = '';
-var UI = null;
-
-var buttonHandlers =
-{
- "toolbar_button_mute": function () {
- return APP.UI.toggleAudio();
- },
- "toolbar_button_camera": function () {
- return APP.UI.toggleVideo();
- },
- /*"toolbar_button_authentication": function () {
- return Toolbar.authenticateClicked();
- },*/
- "toolbar_button_record": function () {
- return toggleRecording();
- },
- "toolbar_button_security": function () {
- return Toolbar.openLockDialog();
- },
- "toolbar_button_link": function () {
- return Toolbar.openLinkDialog();
- },
- "toolbar_button_chat": function () {
- return BottomToolbar.toggleChat();
- },
- "toolbar_button_prezi": function () {
- return Prezi.openPreziDialog();
- },
- "toolbar_button_etherpad": function () {
- return Etherpad.toggleEtherpad(0);
- },
- "toolbar_button_desktopsharing": function () {
- return APP.desktopsharing.toggleScreenSharing();
- },
- "toolbar_button_fullScreen": function()
- {
- UIUtil.buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");
- return Toolbar.toggleFullScreen();
- },
- "toolbar_button_sip": function () {
- return callSipButtonClicked();
- },
- "toolbar_button_dialpad": function () {
- return dialpadButtonClicked();
- },
- "toolbar_button_settings": function () {
- PanelToggler.toggleSettingsMenu();
- },
- "toolbar_button_hangup": function () {
- return hangup();
- },
- "toolbar_button_login": function () {
- Toolbar.authenticateClicked();
- },
- "toolbar_button_logout": function () {
- // Ask for confirmation
- messageHandler.openTwoButtonDialog(
- "dialog.logoutTitle",
- null,
- "dialog.logoutQuestion",
- null,
- false,
- "dialog.Yes",
- function (evt, yes) {
- if (yes) {
- APP.xmpp.logout(function (url) {
- if (url) {
- window.location.href = url;
- } else {
- hangup();
- }
- });
- }
- });
- }
-};
-
-function hangup() {
- APP.xmpp.disposeConference();
- if(config.enableWelcomePage)
- {
- setTimeout(function()
- {
- window.localStorage.welcomePageDisabled = false;
- window.location.pathname = "/";
- }, 10000);
-
- }
-
- var title = APP.translation.generateTranslationHTML(
- "dialog.sessTerminated");
- var msg = APP.translation.generateTranslationHTML(
- "dialog.hungUp");
- var button = APP.translation.generateTranslationHTML(
- "dialog.joinAgain");
- var buttons = [];
- buttons.push({title: button, value: true});
-
- UI.messageHandler.openDialog(
- title,
- msg,
- true,
- buttons,
- function(event, value, message, formVals)
- {
- window.location.reload();
- return false;
- }
- );
-}
-
-/**
- * Starts or stops the recording for the conference.
- */
-
-function toggleRecording() {
- APP.xmpp.toggleRecording(function (callback) {
- var msg = APP.translation.generateTranslationHTML(
- "dialog.recordingToken");
- var token = APP.translation.translateString("dialog.token");
- APP.UI.messageHandler.openTwoButtonDialog(null, null, null,
- '' + msg + '
' +
- '',
- false,
- "dialog.Save",
- function (e, v, m, f) {
- if (v) {
- var token = f.recordingToken;
-
- if (token) {
- callback(UIUtil.escapeHtml(token));
- }
- }
- },
- null,
- function () { },
- ':input:first'
- );
- }, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState);
-}
-
-/**
- * Locks / unlocks the room.
- */
-function lockRoom(lock) {
- var currentSharedKey = '';
- if (lock)
- currentSharedKey = sharedKey;
-
- APP.xmpp.lockRoom(currentSharedKey, function (res) {
- // password is required
- if (sharedKey)
- {
- console.log('set room password');
- Toolbar.lockLockButton();
- }
- else
- {
- console.log('removed room password');
- Toolbar.unlockLockButton();
- }
- }, function (err) {
- console.warn('setting password failed', err);
- messageHandler.showError("dialog.lockTitle",
- "dialog.lockMessage");
- Toolbar.setSharedKey('');
- }, function () {
- console.warn('room passwords not supported');
- messageHandler.showError("dialog.warning",
- "dialog.passwordNotSupported");
- Toolbar.setSharedKey('');
- });
-};
-
-/**
- * Invite participants to conference.
- */
-function inviteParticipants() {
- if (roomUrl === null)
- return;
-
- var sharedKeyText = "";
- if (sharedKey && sharedKey.length > 0) {
- sharedKeyText =
- APP.translation.translateString("email.sharedKey",
- {sharedKey: sharedKey});
- sharedKeyText = sharedKeyText.replace(/\n/g, "%0D%0A");
- }
-
- var supportedBrowsers = "Chromium, Google Chrome " +
- APP.translation.translateString("email.and") + " Opera";
- var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1);
- var subject = APP.translation.translateString("email.subject",
- {appName:interfaceConfig.APP_NAME, conferenceName: conferenceName});
- var body = APP.translation.translateString("email.body",
- {appName:interfaceConfig.APP_NAME, sharedKeyText: sharedKeyText,
- roomUrl: roomUrl, supportedBrowsers: supportedBrowsers});
- body = body.replace(/\n/g, "%0D%0A");
-
- if (window.localStorage.displayname) {
- body += "%0D%0A%0D%0A" + window.localStorage.displayname;
- }
-
- if (interfaceConfig.INVITATION_POWERED_BY) {
- body += "%0D%0A%0D%0A--%0D%0Apowered by jitsi.org";
- }
-
- window.open("mailto:?subject=" + subject + "&body=" + body, '_blank');
-}
-
-function dialpadButtonClicked()
-{
- //TODO show the dialpad window
-}
-
-function callSipButtonClicked()
-{
- var defaultNumber
- = config.defaultSipNumber ? config.defaultSipNumber : '';
-
- var sipMsg = APP.translation.generateTranslationHTML(
- "dialog.sipMsg");
- messageHandler.openTwoButtonDialog(null, null, null,
- '' + sipMsg + '
' +
- '',
- false,
- "dialog.Dial",
- function (e, v, m, f) {
- if (v) {
- var numberInput = f.sipNumber;
- if (numberInput) {
- APP.xmpp.dial(
- numberInput, 'fromnumber', UI.getRoomName(), sharedKey);
- }
- }
- },
- null, null, ':input:first'
- );
-}
-
-var Toolbar = (function (my) {
-
- my.init = function (ui) {
- for(var k in buttonHandlers)
- $("#" + k).click(buttonHandlers[k]);
- UI = ui;
- // Update login info
- APP.xmpp.addListener(
- AuthenticationEvents.IDENTITY_UPDATED,
- function (authenticationEnabled, userIdentity) {
-
- var loggedIn = false;
- if (userIdentity) {
- loggedIn = true;
- }
-
- Toolbar.showAuthenticateButton(authenticationEnabled);
-
- if (authenticationEnabled) {
- Toolbar.setAuthenticatedIdentity(userIdentity);
-
- Toolbar.showLoginButton(!loggedIn);
- Toolbar.showLogoutButton(loggedIn);
- }
- }
- );
- },
-
- /**
- * Sets shared key
- * @param sKey the shared key
- */
- my.setSharedKey = function (sKey) {
- sharedKey = sKey;
- };
-
- my.authenticateClicked = function () {
- Authentication.focusAuthenticationWindow();
- if (!APP.xmpp.isExternalAuthEnabled()) {
- Authentication.xmppAuthenticate();
- return;
- }
- // Get authentication URL
- if (!APP.xmpp.getMUCJoined()) {
- APP.xmpp.getLoginUrl(UI.getRoomName(), function (url) {
- // If conference has not been started yet - redirect to login page
- window.location.href = url;
- });
- } else {
- APP.xmpp.getPopupLoginUrl(UI.getRoomName(), function (url) {
- // Otherwise - open popup with authentication URL
- var authenticationWindow = Authentication.createAuthenticationWindow(
- function () {
- // On popup closed - retry room allocation
- APP.xmpp.allocateConferenceFocus(
- APP.UI.getRoomName(),
- function () { console.info("AUTH DONE"); }
- );
- }, url);
- if (!authenticationWindow) {
- messageHandler.openMessageDialog(
- null, "dialog.popupError");
- }
- });
- }
- };
-
- /**
- * Updates the room invite url.
- */
- my.updateRoomUrl = function (newRoomUrl) {
- roomUrl = newRoomUrl;
-
- // If the invite dialog has been already opened we update the information.
- var inviteLink = document.getElementById('inviteLinkRef');
- if (inviteLink) {
- inviteLink.value = roomUrl;
- inviteLink.select();
- $('#inviteLinkRef').parent()
- .find('button[value=true]').prop('disabled', false);
- }
- };
-
- /**
- * Disables and enables some of the buttons.
- */
- my.setupButtonsFromConfig = function () {
- if (config.disablePrezi)
- {
- $("#prezi_button").css({display: "none"});
- }
- };
-
- /**
- * Opens the lock room dialog.
- */
- my.openLockDialog = function () {
- // Only the focus is able to set a shared key.
- if (!APP.xmpp.isModerator()) {
- if (sharedKey) {
- messageHandler.openMessageDialog(null,
- "dialog.passwordError");
- } else {
- messageHandler.openMessageDialog(null, "dialog.passwordError2");
- }
- } else {
- if (sharedKey) {
- messageHandler.openTwoButtonDialog(null, null,
- "dialog.passwordCheck",
- null,
- false,
- "dialog.Remove",
- function (e, v) {
- if (v) {
- Toolbar.setSharedKey('');
- lockRoom(false);
- }
- });
- } else {
- var msg = APP.translation.generateTranslationHTML(
- "dialog.passwordMsg");
- var yourPassword = APP.translation.translateString(
- "dialog.yourPassword");
- messageHandler.openTwoButtonDialog(null, null, null,
- '' + msg + '
' +
- '',
- false,
- "dialog.Save",
- function (e, v, m, f) {
- if (v) {
- var lockKey = f.lockKey;
-
- if (lockKey) {
- Toolbar.setSharedKey(
- UIUtil.escapeHtml(lockKey));
- lockRoom(true);
- }
- }
- },
- null, null, 'input:first'
- );
- }
- }
- };
-
- /**
- * Opens the invite link dialog.
- */
- my.openLinkDialog = function () {
- var inviteAttreibutes;
-
- if (roomUrl === null) {
- inviteAttreibutes = 'data-i18n="[value]roomUrlDefaultMsg" value="' +
- APP.translation.translateString("roomUrlDefaultMsg") + '"';
- } else {
- inviteAttreibutes = "value=\"" + encodeURI(roomUrl) + "\"";
- }
- messageHandler.openTwoButtonDialog("dialog.shareLink",
- null, null,
- '',
- false,
- "dialog.Invite",
- function (e, v) {
- if (v) {
- if (roomUrl) {
- inviteParticipants();
- }
- }
- },
- function (event) {
- if (roomUrl) {
- document.getElementById('inviteLinkRef').select();
- } else {
- if (event && event.target)
- $(event.target)
- .find('button[value=true]').prop('disabled', true);
- }
- }
- );
- };
-
- /**
- * Opens the settings dialog.
- * FIXME: not used ?
- */
- my.openSettingsDialog = function () {
- var settings1 = APP.translation.generateTranslationHTML(
- "dialog.settings1");
- var settings2 = APP.translation.generateTranslationHTML(
- "dialog.settings2");
- var settings3 = APP.translation.generateTranslationHTML(
- "dialog.settings3");
-
- var yourPassword = APP.translation.translateString(
- "dialog.yourPassword");
-
- messageHandler.openTwoButtonDialog(null,
- '' + settings1 + '
' +
- '' +
- settings2 + '
' +
- '' +
- settings3 +
- '',
- null,
- null,
- false,
- "dialog.Save",
- function () {
- document.getElementById('lockKey').focus();
- },
- function (e, v) {
- if (v) {
- if ($('#initMuted').is(":checked")) {
- // it is checked
- }
-
- if ($('#requireNicknames').is(":checked")) {
- // it is checked
- }
- /*
- var lockKey = document.getElementById('lockKey');
-
- if (lockKey.value) {
- setSharedKey(lockKey.value);
- lockRoom(true);
- }
- */
- }
- }
- );
- };
-
- /**
- * Toggles the application in and out of full screen mode
- * (a.k.a. presentation mode in Chrome).
- */
- my.toggleFullScreen = function () {
- var fsElement = document.documentElement;
-
- if (!document.mozFullScreen && !document.webkitIsFullScreen) {
- //Enter Full Screen
- if (fsElement.mozRequestFullScreen) {
- fsElement.mozRequestFullScreen();
- }
- else {
- fsElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
- }
- } else {
- //Exit Full Screen
- if (document.mozCancelFullScreen) {
- document.mozCancelFullScreen();
- } else {
- document.webkitCancelFullScreen();
- }
- }
- };
- /**
- * Unlocks the lock button state.
- */
- my.unlockLockButton = function () {
- if ($("#lockIcon").hasClass("icon-security-locked"))
- UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
- };
- /**
- * Updates the lock button state to locked.
- */
- my.lockLockButton = function () {
- if ($("#lockIcon").hasClass("icon-security"))
- UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
- };
-
- /**
- * Shows or hides authentication button
- * @param show true to show or false to hide
- */
- my.showAuthenticateButton = function (show) {
- if (show) {
- $('#authentication').css({display: "inline"});
- }
- else {
- $('#authentication').css({display: "none"});
- }
- };
-
- // Shows or hides the 'recording' button.
- my.showRecordingButton = function (show) {
- if (!config.enableRecording) {
- return;
- }
-
- if (show) {
- $('#recording').css({display: "inline"});
- }
- else {
- $('#recording').css({display: "none"});
- }
- };
-
- // Sets the state of the recording button
- my.setRecordingButtonState = function (isRecording) {
- var selector = $('#recordButton');
- if (isRecording) {
- selector.removeClass("icon-recEnable");
- selector.addClass("icon-recEnable active");
- } else {
- selector.removeClass("icon-recEnable active");
- selector.addClass("icon-recEnable");
- }
- };
-
- // Shows or hides SIP calls button
- my.showSipCallButton = function (show) {
- if (APP.xmpp.isSipGatewayEnabled() && show) {
- $('#sipCallButton').css({display: "inline-block"});
- } else {
- $('#sipCallButton').css({display: "none"});
- }
- };
-
- // Shows or hides the dialpad button
- my.showDialPadButton = function (show) {
- if (show) {
- $('#dialPadButton').css({display: "inline-block"});
- } else {
- $('#dialPadButton').css({display: "none"});
- }
- };
-
- /**
- * Displays user authenticated identity name(login).
- * @param authIdentity identity name to be displayed.
- */
- my.setAuthenticatedIdentity = function (authIdentity) {
- if (authIdentity) {
- var selector = $('#toolbar_auth_identity');
- selector.css({display: "list-item"});
- selector.text(authIdentity);
- } else {
- $('#toolbar_auth_identity').css({display: "none"});
- }
- };
-
- /**
- * Shows/hides login button.
- * @param show true to show
- */
- my.showLoginButton = function (show) {
- if (show) {
- $('#toolbar_button_login').css({display: "list-item"});
- } else {
- $('#toolbar_button_login').css({display: "none"});
- }
- };
-
- /**
- * Shows/hides logout button.
- * @param show true to show
- */
- my.showLogoutButton = function (show) {
- if (show) {
- $('#toolbar_button_logout').css({display: "list-item"});
- } else {
- $('#toolbar_button_logout').css({display: "none"});
- }
- };
-
- /**
- * Sets the state of the button. The button has blue glow if desktop
- * streaming is active.
- * @param active the state of the desktop streaming.
- */
- my.changeDesktopSharingButtonState = function (active) {
- var button = $("#desktopsharing > a");
- if (active)
- {
- button.addClass("glow");
- }
- else
- {
- button.removeClass("glow");
- }
- };
-
- return my;
-}(Toolbar || {}));
-
-module.exports = Toolbar;
-},{"../../../service/authentication/AuthenticationEvents":105,"../authentication/Authentication":16,"../etherpad/Etherpad":19,"../prezi/Prezi":20,"../side_pannels/SidePanelToggler":22,"../util/MessageHandler":33,"../util/UIUtil":35,"./BottomToolbar":29}],31:[function(require,module,exports){
-/* global $, interfaceConfig, Moderator, DesktopStreaming.showDesktopSharingButton */
-
-var toolbarTimeoutObject,
- toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
-
-function showDesktopSharingButton() {
- if (APP.desktopsharing.isDesktopSharingEnabled()) {
- $('#desktopsharing').css({display: "inline"});
- } else {
- $('#desktopsharing').css({display: "none"});
- }
-}
-
-/**
- * Hides the toolbar.
- */
-function hideToolbar() {
- if(config.alwaysVisibleToolbar)
- return;
-
- var header = $("#header"),
- bottomToolbar = $("#bottomToolbar");
- var isToolbarHover = false;
- header.find('*').each(function () {
- var id = $(this).attr('id');
- if ($("#" + id + ":hover").length > 0) {
- isToolbarHover = true;
- }
- });
- if ($("#bottomToolbar:hover").length > 0) {
- isToolbarHover = true;
- }
-
- clearTimeout(toolbarTimeoutObject);
- toolbarTimeoutObject = null;
-
- if (!isToolbarHover) {
- header.hide("slide", { direction: "up", duration: 300});
- $('#subject').animate({top: "-=40"}, 300);
- if ($("#remoteVideos").hasClass("hidden")) {
- bottomToolbar.hide(
- "slide", {direction: "right", duration: 300});
- }
- }
- else {
- toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
- }
-}
-
-var ToolbarToggler = {
- /**
- * Shows the main toolbar.
- */
- showToolbar: function () {
- var header = $("#header"),
- bottomToolbar = $("#bottomToolbar");
- if (!header.is(':visible') || !bottomToolbar.is(":visible")) {
- header.show("slide", { direction: "up", duration: 300});
- $('#subject').animate({top: "+=40"}, 300);
- if (!bottomToolbar.is(":visible")) {
- bottomToolbar.show(
- "slide", {direction: "right", duration: 300});
- }
-
- if (toolbarTimeoutObject) {
- clearTimeout(toolbarTimeoutObject);
- toolbarTimeoutObject = null;
- }
- toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
- toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
- }
-
- if (APP.xmpp.isModerator())
- {
-// TODO: Enable settings functionality.
-// Need to uncomment the settings button in index.html.
-// $('#settingsButton').css({visibility:"visible"});
- }
-
- // Show/hide desktop sharing button
- showDesktopSharingButton();
- },
-
-
- /**
- * Docks/undocks the toolbar.
- *
- * @param isDock indicates what operation to perform
- */
- dockToolbar: function (isDock) {
- if (isDock) {
- // First make sure the toolbar is shown.
- if (!$('#header').is(':visible')) {
- this.showToolbar();
- }
-
- // Then clear the time out, to dock the toolbar.
- if (toolbarTimeoutObject) {
- clearTimeout(toolbarTimeoutObject);
- toolbarTimeoutObject = null;
- }
- }
- else {
- if (!$('#header').is(':visible')) {
- this.showToolbar();
- }
- else {
- toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
- }
- }
- },
-
- showDesktopSharingButton: showDesktopSharingButton
-
-};
-
-module.exports = ToolbarToggler;
-},{}],32:[function(require,module,exports){
-var JitsiPopover = (function () {
- /**
- * Constructs new JitsiPopover and attaches it to the element
- * @param element jquery selector
- * @param options the options for the popover.
- * @constructor
- */
- function JitsiPopover(element, options)
- {
- this.options = {
- skin: "white",
- content: ""
- };
- if(options)
- {
- if(options.skin)
- this.options.skin = options.skin;
-
- if(options.content)
- this.options.content = options.content;
- }
-
- this.elementIsHovered = false;
- this.popoverIsHovered = false;
- this.popoverShown = false;
-
- element.data("jitsi_popover", this);
- this.element = element;
- this.template = ' ';
- var self = this;
- this.element.on("mouseenter", function () {
- self.elementIsHovered = true;
- self.show();
- }).on("mouseleave", function () {
- self.elementIsHovered = false;
- setTimeout(function () {
- self.hide();
- }, 10);
- });
- }
-
- /**
- * Shows the popover
- */
- JitsiPopover.prototype.show = function () {
- this.createPopover();
- this.popoverShown = true;
-
- };
-
- /**
- * Hides the popover
- */
- JitsiPopover.prototype.hide = function () {
- if(!this.elementIsHovered && !this.popoverIsHovered && this.popoverShown)
- {
- this.forceHide();
- }
- };
-
- /**
- * Hides the popover
- */
- JitsiPopover.prototype.forceHide = function () {
- $(".jitsipopover").remove();
- this.popoverShown = false;
- };
-
- /**
- * Creates the popover html
- */
- JitsiPopover.prototype.createPopover = function () {
- $("body").append(this.template);
- $(".jitsipopover > .jitsipopover-content").html(this.options.content);
- var self = this;
- $(".jitsipopover").on("mouseenter", function () {
- self.popoverIsHovered = true;
- }).on("mouseleave", function () {
- self.popoverIsHovered = false;
- self.hide();
- });
-
- this.refreshPosition();
- };
-
- /**
- * Refreshes the position of the popover
- */
- JitsiPopover.prototype.refreshPosition = function () {
- $(".jitsipopover").position({
- my: "bottom",
- at: "top",
- collision: "fit",
- of: this.element,
- using: function (position, elements) {
- var calcLeft = elements.target.left - elements.element.left + elements.target.width/2;
- $(".jitsipopover").css({top: position.top, left: position.left, display: "table"});
- $(".jitsipopover > .arrow").css({left: calcLeft});
- $(".jitsipopover > .jitsiPopupmenuPadding").css({left: calcLeft - 50});
- }
- });
- };
-
- /**
- * Updates the content of popover.
- * @param content new content
- */
- JitsiPopover.prototype.updateContent = function (content) {
- this.options.content = content;
- if(!this.popoverShown)
- return;
- $(".jitsipopover").remove();
- this.createPopover();
- };
-
- return JitsiPopover;
-
-
-})();
-
-module.exports = JitsiPopover;
-},{}],33:[function(require,module,exports){
-/* global $, APP, jQuery, toastr */
-var messageHandler = (function(my) {
-
- /**
- * Shows a message to the user.
- *
- * @param titleString the title of the message
- * @param messageString the text of the message
- */
- my.openMessageDialog = function(titleKey, messageKey) {
- var title = null;
- if(titleKey)
- {
- title = APP.translation.generateTranslationHTML(titleKey);
- }
- var message = APP.translation.generateTranslationHTML(messageKey);
- $.prompt(message,
- {
- title: title,
- persistent: false
- }
- );
- };
-
- /**
- * Shows a message to the user with two buttons: first is given as a parameter and the second is Cancel.
- *
- * @param titleString the title of the message
- * @param msgString the text of the message
- * @param persistent boolean value which determines whether the message is persistent or not
- * @param leftButton the fist button's text
- * @param submitFunction function to be called on submit
- * @param loadedFunction function to be called after the prompt is fully loaded
- * @param closeFunction function to be called after the prompt is closed
- * @param focus optional focus selector or button index to be focused after
- * the dialog is opened
- * @param defaultButton index of default button which will be activated when
- * the user press 'enter'. Indexed from 0.
- */
- my.openTwoButtonDialog = function(titleKey, titleString, msgKey, msgString,
- persistent, leftButtonKey, submitFunction, loadedFunction,
- closeFunction, focus, defaultButton)
- {
- var buttons = [];
-
- var leftButton = APP.translation.generateTranslationHTML(leftButtonKey);
- buttons.push({ title: leftButton, value: true});
-
- var cancelButton
- = APP.translation.generateTranslationHTML("dialog.Cancel");
- buttons.push({title: cancelButton, value: false});
-
- var message = msgString, title = titleString;
- if (titleKey)
- {
- title = APP.translation.generateTranslationHTML(titleKey);
- }
- if (msgKey) {
- message = APP.translation.generateTranslationHTML(msgKey);
- }
- $.prompt(message, {
- title: title,
- persistent: false,
- buttons: buttons,
- defaultButton: defaultButton,
- focus: focus,
- loaded: loadedFunction,
- submit: submitFunction,
- close: closeFunction
- });
- };
-
- /**
- * Shows a message to the user with two buttons: first is given as a parameter and the second is Cancel.
- *
- * @param titleString the title of the message
- * @param msgString the text of the message
- * @param persistent boolean value which determines whether the message is persistent or not
- * @param buttons object with the buttons. The keys must be the name of the button and value is the value
- * that will be passed to submitFunction
- * @param submitFunction function to be called on submit
- * @param loadedFunction function to be called after the prompt is fully loaded
- */
- my.openDialog = function (titleString, msgString, persistent, buttons,
- submitFunction, loadedFunction) {
- var args = {
- title: titleString,
- persistent: persistent,
- buttons: buttons,
- defaultButton: 1,
- loaded: loadedFunction,
- submit: submitFunction
- };
- if (persistent) {
- args.closeText = '';
- }
- return new Impromptu(msgString, args);
- };
-
- /**
- * Closes currently opened dialog.
- */
- my.closeDialog = function () {
- $.prompt.close();
- };
-
- /**
- * Shows a dialog with different states to the user.
- *
- * @param statesObject object containing all the states of the dialog
- */
- my.openDialogWithStates = function (statesObject, options) {
-
- return new Impromptu(statesObject, options);
- };
-
- /**
- * Opens new popup window for given url centered over current
- * window.
- *
- * @param url the URL to be displayed in the popup window
- * @param w the width of the popup window
- * @param h the height of the popup window
- * @param onPopupClosed optional callback function called when popup window
- * has been closed.
- *
- * @returns popup window object if opened successfully or undefined
- * in case we failed to open it(popup blocked)
- */
- my.openCenteredPopup = function (url, w, h, onPopupClosed) {
- var l = window.screenX + (window.innerWidth / 2) - (w / 2);
- var t = window.screenY + (window.innerHeight / 2) - (h / 2);
- var popup = window.open(
- url, '_blank',
- 'top=' + t + ', left=' + l + ', width=' + w + ', height=' + h + '');
- if (popup && onPopupClosed) {
- var pollTimer = window.setInterval(function () {
- if (popup.closed !== false) {
- window.clearInterval(pollTimer);
- onPopupClosed();
- }
- }, 200);
- }
- return popup;
- };
-
- /**
- * Shows a dialog prompting the user to send an error report.
- *
- * @param titleString the title of the message
- * @param msgString the text of the message
- * @param error the error that is being reported
- */
- my.openReportDialog = function(titleKey, msgKey, error) {
- my.openMessageDialog(titleKey, msgKey);
- console.log(error);
- //FIXME send the error to the server
- };
-
- /**
- * Shows an error dialog to the user.
- * @param title the title of the message
- * @param message the text of the messafe
- */
- my.showError = function(titleKey, msgKey) {
-
- if(!titleKey) {
- titleKey = "dialog.oops";
- }
- if(!msgKey)
- {
- msgKey = "dialog.defaultError";
- }
- messageHandler.openMessageDialog(titleKey, msgKey);
- };
-
- my.notify = function(displayName, displayNameKey,
- cls, messageKey, messageArguments, options) {
- var displayNameSpan = '" + displayName;
- }
- else
- {
- displayNameSpan += "data-i18n='" + displayNameKey +
- "'>" + APP.translation.translateString(displayNameKey);
- }
- displayNameSpan += "";
- toastr.info(
- displayNameSpan + '
' +
- '" +
- APP.translation.translateString(messageKey,
- messageArguments) +
- '', null, options);
- };
-
- return my;
-}(messageHandler || {}));
-
-module.exports = messageHandler;
-
-
+ if (constraints.audio) {
+ // if it is good enough for hangouts...
+ constraints.audio.optional.push(
+ {googEchoCancellation: true},
+ {googAutoGainControl: true},
+ {googNoiseSupression: true},
+ {googHighpassFilter: true},
+ {googNoisesuppression2: true},
+ {googEchoCancellation2: true},
+ {googAutoGainControl2: true}
+ );
+ }
+ if (constraints.video) {
+ constraints.video.optional.push(
+ {googNoiseReduction: false} // chrome 37 workaround for issue 3807, reenable in M38
+ );
+ if (um.indexOf('video') >= 0) {
+ constraints.video.optional.push(
+ {googLeakyBucket: true}
+ );
+ }
+ }
-},{}],34:[function(require,module,exports){
-var UIEvents = require("../../../service/UI/UIEvents");
-
-var nickname = null;
-var eventEmitter = null;
-
-var NickanameHandler = {
- init: function (emitter) {
- eventEmitter = emitter;
- var storedDisplayName = window.localStorage.displayname;
- if (storedDisplayName) {
- nickname = storedDisplayName;
- }
- },
- setNickname: function (newNickname) {
- if (!newNickname || nickname === newNickname)
- return;
-
- nickname = newNickname;
- window.localStorage.displayname = nickname;
- eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newNickname);
- },
- getNickname: function () {
- return nickname;
- },
- addListener: function (type, listener) {
- eventEmitter.on(type, listener);
- }
-};
-
-module.exports = NickanameHandler;
-},{"../../../service/UI/UIEvents":104}],35:[function(require,module,exports){
-/**
- * Created by hristo on 12/22/14.
- */
-module.exports = {
- /**
- * Returns the available video width.
- */
- getAvailableVideoWidth: function (isVisible) {
- var PanelToggler = require("../side_pannels/SidePanelToggler");
- if(typeof isVisible === "undefined" || isVisible === null)
- isVisible = PanelToggler.isVisible();
- var rightPanelWidth
- = isVisible ? PanelToggler.getPanelSize()[0] : 0;
-
- return window.innerWidth - rightPanelWidth;
- },
- /**
- * Changes the style class of the element given by id.
- */
- buttonClick: function(id, classname) {
- $(id).toggleClass(classname); // add the class to the clicked element
- },
- /**
- * Returns the text width for the given element.
- *
- * @param el the element
- */
- getTextWidth: function (el) {
- return (el.clientWidth + 1);
- },
-
- /**
- * Returns the text height for the given element.
- *
- * @param el the element
- */
- getTextHeight: function (el) {
- return (el.clientHeight + 1);
- },
-
- /**
- * Plays the sound given by id.
- *
- * @param id the identifier of the audio element.
- */
- playSoundNotification: function (id) {
- document.getElementById(id).play();
- },
-
- /**
- * Escapes the given text.
- */
- escapeHtml: function (unsafeText) {
- return $('').text(unsafeText).html();
- },
-
- imageToGrayScale: function (canvas) {
- var context = canvas.getContext('2d');
- var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
- var pixels = imgData.data;
-
- for (var i = 0, n = pixels.length; i < n; i += 4) {
- var grayscale
- = pixels[i] * .3 + pixels[i+1] * .59 + pixels[i+2] * .11;
- pixels[i ] = grayscale; // red
- pixels[i+1] = grayscale; // green
- pixels[i+2] = grayscale; // blue
- // pixels[i+3] is alpha
- }
- // redraw the image in black & white
- context.putImageData(imgData, 0, 0);
- },
-
- setTooltip: function (element, key, position) {
- element.setAttribute("data-i18n", "[data-content]" + key);
- element.setAttribute("data-toggle", "popover");
- element.setAttribute("data-placement", position);
- element.setAttribute("data-html", true);
- element.setAttribute("data-container", "body");
- }
-
-
+ if (um.indexOf('video') >= 0) {
+ setResolutionConstraints(constraints, resolution, isAndroid);
+ }
+
+ if (bandwidth) { // doesn't work currently, see webrtc issue 1846
+ if (!constraints.video) constraints.video = {mandatory: {}, optional: []};//same behaviour as true
+ constraints.video.optional.push({bandwidth: bandwidth});
+ }
+ if (fps) { // for some cameras it might be necessary to request 30fps
+ // so they choose 30fps mjpg over 10fps yuy2
+ if (!constraints.video) constraints.video = {mandatory: {}, optional: []};// same behaviour as true;
+ constraints.video.mandatory.minFrameRate = fps;
+ }
+
+ return constraints;
+}
+
+
+function RTCUtils(RTCService, onTemasysPluginReady)
+{
+ var self = this;
+ this.service = RTCService;
+ if (RTCBrowserType.isFirefox()) {
+ var FFversion = RTCBrowserType.getFirefoxVersion();
+ if (FFversion >= 40 && config.useBundle && config.useRtcpMux) {
+ this.peerconnection = mozRTCPeerConnection;
+ this.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
+ this.pc_constraints = {};
+ this.attachMediaStream = function (element, stream) {
+ // srcObject is being standardized and FF will eventually
+ // support that unprefixed. FF also supports the
+ // "element.src = URL.createObjectURL(...)" combo, but that
+ // will be deprecated in favour of srcObject.
+ //
+ // https://groups.google.com/forum/#!topic/mozilla.dev.media/pKOiioXonJg
+ // https://github.com/webrtc/samples/issues/302
+ if(!element[0])
+ return;
+ element[0].mozSrcObject = stream;
+ element[0].play();
+ };
+ this.getStreamID = function (stream) {
+ var id = stream.id;
+ if (!id) {
+ var tracks = stream.getVideoTracks();
+ if (!tracks || tracks.length === 0)
+ {
+ tracks = stream.getAudioTracks();
+ }
+ id = tracks[0].id;
+ }
+ return SDPUtil.filter_special_chars(id);
+ };
+ this.getVideoSrc = function (element) {
+ if(!element)
+ return null;
+ return element.mozSrcObject;
+ };
+ this.setVideoSrc = function (element, src) {
+ if(element)
+ element.mozSrcObject = src;
+ };
+ RTCSessionDescription = mozRTCSessionDescription;
+ RTCIceCandidate = mozRTCIceCandidate;
+ } else {
+ console.error(
+ "Firefox requirements not met, ver: " + FFversion +
+ ", bundle: " + config.useBundle +
+ ", rtcp-mux: " + config.useRtcpMux);
+ window.location.href = 'unsupported_browser.html';
+ return;
+ }
+
+ } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) {
+ this.peerconnection = webkitRTCPeerConnection;
+ this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
+ this.attachMediaStream = function (element, stream) {
+ element.attr('src', webkitURL.createObjectURL(stream));
+ };
+ this.getStreamID = function (stream) {
+ // streams from FF endpoints have the characters '{' and '}'
+ // that make jQuery choke.
+ return SDPUtil.filter_special_chars(stream.id);
+ };
+ this.getVideoSrc = function (element) {
+ if(!element)
+ return null;
+ return element.getAttribute("src");
+ };
+ this.setVideoSrc = function (element, src) {
+ if(element)
+ element.setAttribute("src", src);
+ };
+ // DTLS should now be enabled by default but..
+ this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]};
+ if (navigator.userAgent.indexOf('Android') != -1) {
+ this.pc_constraints = {}; // disable DTLS on Android
+ }
+ if (!webkitMediaStream.prototype.getVideoTracks) {
+ webkitMediaStream.prototype.getVideoTracks = function () {
+ return this.videoTracks;
+ };
+ }
+ if (!webkitMediaStream.prototype.getAudioTracks) {
+ webkitMediaStream.prototype.getAudioTracks = function () {
+ return this.audioTracks;
+ };
+ }
+ }
+ // Detect IE/Safari
+ else if (RTCBrowserType.isTemasysPluginUsed()) {
+
+ //AdapterJS.WebRTCPlugin.setLogLevel(
+ // AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE);
+
+ AdapterJS.webRTCReady(function (isPlugin) {
+
+ self.peerconnection = RTCPeerConnection;
+ self.getUserMedia = getUserMedia;
+ self.attachMediaStream = function (elSel, stream) {
+
+ if (stream.id === "dummyAudio" || stream.id === "dummyVideo") {
+ return;
+ }
+
+ attachMediaStream(elSel[0], stream);
+ };
+ self.getStreamID = function (stream) {
+ var id = SDPUtil.filter_special_chars(stream.label);
+ return id;
+ };
+ self.getVideoSrc = function (element) {
+ if (!element) {
+ console.warn("Attempt to get video SRC of null element");
+ return null;
+ }
+ var src = null;
+ var children = element.children;
+ for (var i = 0; i !== children.length; ++i) {
+ if (children[i].name === 'streamId') {
+ return children[i].value;
+ }
+ }
+ //console.info(element.id + " SRC: " + src);
+ return null;
+ };
+ self.setVideoSrc = function (element, src) {
+ //console.info("Set video src: ", element, src);
+ if (!src) {
+ console.warn("Not attaching video stream, 'src' is null");
+ return;
+ }
+ AdapterJS.WebRTCPlugin.WaitForPluginReady();
+ var stream = AdapterJS.WebRTCPlugin.plugin
+ .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src);
+ attachMediaStream(element, stream);
+ };
+
+ onTemasysPluginReady(isPlugin);
+ });
+ } else {
+ try {
+ console.log('Browser does not appear to be WebRTC-capable');
+ } catch (e) { }
+ window.location.href = 'unsupported_browser.html';
+ }
+}
+
+
+RTCUtils.prototype.getUserMediaWithConstraints = function(
+ um, success_callback, failure_callback, resolution,bandwidth, fps,
+ desktopStream)
+{
+ currentResolution = resolution;
+ // Check if we are running on Android device
+ var isAndroid = navigator.userAgent.indexOf('Android') != -1;
+
+ var constraints = getConstraints(
+ um, resolution, bandwidth, fps, desktopStream, isAndroid);
+
+ console.info("Get media constraints", constraints);
+
+ var self = this;
+
+ try {
+ this.getUserMedia(constraints,
+ function (stream) {
+ console.log('onUserMediaSuccess');
+ self.setAvailableDevices(um, true);
+ success_callback(stream);
+ },
+ function (error) {
+ self.setAvailableDevices(um, false);
+ console.warn('Failed to get access to local media. Error ',
+ error, constraints);
+ if (failure_callback) {
+ failure_callback(error);
+ }
+ });
+ } catch (e) {
+ console.error('GUM failed: ', e);
+ if(failure_callback) {
+ failure_callback(e);
+ }
+ }
};
-},{"../side_pannels/SidePanelToggler":22}],36:[function(require,module,exports){
-var JitsiPopover = require("../util/JitsiPopover");
-
-/**
- * Constructs new connection indicator.
- * @param videoContainer the video container associated with the indicator.
- * @constructor
- */
-function ConnectionIndicator(videoContainer, jid)
-{
- this.videoContainer = videoContainer;
- this.bandwidth = null;
- this.packetLoss = null;
- this.bitrate = null;
- this.showMoreValue = false;
- this.resolution = null;
- this.transport = [];
- this.popover = null;
- this.jid = jid;
- this.create();
-}
-
-/**
- * Values for the connection quality
- * @type {{98: string,
- * 81: string,
- * 64: string,
- * 47: string,
- * 30: string,
- * 0: string}}
- */
-ConnectionIndicator.connectionQualityValues = {
- 98: "18px", //full
- 81: "15px",//4 bars
- 64: "11px",//3 bars
- 47: "7px",//2 bars
- 30: "3px",//1 bar
- 0: "0px"//empty
-};
-
-ConnectionIndicator.getIP = function(value)
-{
- return value.substring(0, value.lastIndexOf(":"));
-};
-
-ConnectionIndicator.getPort = function(value)
-{
- return value.substring(value.lastIndexOf(":") + 1, value.length);
-};
-
-ConnectionIndicator.getStringFromArray = function (array) {
- var res = "";
- for(var i = 0; i < array.length; i++)
- {
- res += (i === 0? "" : ", ") + array[i];
- }
- return res;
-};
-
-/**
- * Generates the html content.
- * @returns {string} the html content.
- */
-ConnectionIndicator.prototype.generateText = function () {
- var downloadBitrate, uploadBitrate, packetLoss, resolution, i;
-
- var translate = APP.translation.translateString;
-
- if(this.bitrate === null)
- {
- downloadBitrate = "N/A";
- uploadBitrate = "N/A";
- }
- else
- {
- downloadBitrate =
- this.bitrate.download? this.bitrate.download + " Kbps" : "N/A";
- uploadBitrate =
- this.bitrate.upload? this.bitrate.upload + " Kbps" : "N/A";
- }
-
- if(this.packetLoss === null)
- {
- packetLoss = "N/A";
- }
- else
- {
-
- packetLoss = "↓" +
- (this.packetLoss.download !== null? this.packetLoss.download : "N/A") +
- "% ↑" +
- (this.packetLoss.upload !== null? this.packetLoss.upload : "N/A") + "%";
- }
-
- var resolutionValue = null;
- if(this.resolution && this.jid != null)
- {
- var keys = Object.keys(this.resolution);
- for(var ssrc in this.resolution)
- {
- resolutionValue = this.resolution[ssrc];
- }
- }
-
- if(this.jid === null)
- {
- resolution = "";
- if(this.resolution === null || !Object.keys(this.resolution) ||
- Object.keys(this.resolution).length === 0)
- {
- resolution = "N/A";
- }
- else
- for(i in this.resolution)
- {
- resolutionValue = this.resolution[i];
- if(resolutionValue)
- {
- if(resolutionValue.height &&
- resolutionValue.width)
- {
- resolution += (resolution === ""? "" : ", ") +
- resolutionValue.width + "x" +
- resolutionValue.height;
- }
- }
- }
- }
- else if(!resolutionValue ||
- !resolutionValue.height ||
- !resolutionValue.width)
- {
- resolution = "N/A";
- }
- else
- {
- resolution = resolutionValue.width + "x" + resolutionValue.height;
- }
-
- var result = "" +
- "" +
- "" +
- translate("connectionindicator.bitrate") + " | " +
- "↓" +
- downloadBitrate + " ↑" +
- uploadBitrate + " | " +
- "
" +
- "" +
- translate("connectionindicator.packetloss") + " | " +
- "" + packetLoss + " | " +
- "
" +
- "" +
- translate("connectionindicator.resolution") + " | " +
- "" + resolution + " |
";
-
- if(this.videoContainer.videoSpanId == "localVideoContainer") {
- result += "" +
- translate("connectionindicator." + (this.showMoreValue ? "less" : "more")) +
- "
";
- }
-
- if(this.showMoreValue)
- {
- var downloadBandwidth, uploadBandwidth, transport;
- if(this.bandwidth === null)
- {
- downloadBandwidth = "N/A";
- uploadBandwidth = "N/A";
- }
- else
- {
- downloadBandwidth = this.bandwidth.download?
- this.bandwidth.download + " Kbps" :
- "N/A";
- uploadBandwidth = this.bandwidth.upload?
- this.bandwidth.upload + " Kbps" :
- "N/A";
- }
-
- if(!this.transport || this.transport.length === 0)
- {
- transport = "" +
- "" +
- translate("connectionindicator.address") + " | " +
- " N/A |
";
- }
- else
- {
- var data = {remoteIP: [], localIP:[], remotePort:[], localPort:[]};
- for(i = 0; i < this.transport.length; i++)
- {
- var ip = ConnectionIndicator.getIP(this.transport[i].ip);
- var port = ConnectionIndicator.getPort(this.transport[i].ip);
- var localIP =
- ConnectionIndicator.getIP(this.transport[i].localip);
- var localPort =
- ConnectionIndicator.getPort(this.transport[i].localip);
- if(data.remoteIP.indexOf(ip) == -1)
- {
- data.remoteIP.push(ip);
- }
-
- if(data.remotePort.indexOf(port) == -1)
- {
- data.remotePort.push(port);
- }
-
- if(data.localIP.indexOf(localIP) == -1)
- {
- data.localIP.push(localIP);
- }
-
- if(data.localPort.indexOf(localPort) == -1)
- {
- data.localPort.push(localPort);
- }
-
- }
-
- var local_address_key = "connectionindicator.localaddress";
- var remote_address_key = "connectionindicator.remoteaddress";
- var localTransport =
- "" +
- translate(local_address_key, {count: data.localIP.length}) +
- " | " +
- ConnectionIndicator.getStringFromArray(data.localIP) +
- " |
";
- transport =
- "" +
- translate(remote_address_key,
- {count: data.remoteIP.length}) +
- " | " +
- ConnectionIndicator.getStringFromArray(data.remoteIP) +
- " |
";
-
- var key_remote = "connectionindicator.remoteport",
- key_local = "connectionindicator.localport";
-
- transport += "" +
- "" +
- "" +
- translate(key_remote, {count: this.transport.length}) +
- " | ";
- localTransport += " |
" +
- "" +
- "" +
- translate(key_local, {count: this.transport.length}) +
- " | ";
-
- transport +=
- ConnectionIndicator.getStringFromArray(data.remotePort);
- localTransport +=
- ConnectionIndicator.getStringFromArray(data.localPort);
- transport += " |
";
- transport += localTransport + "";
- transport +="" +
- "" +
- translate("connectionindicator.transport") + " | " +
- "" + this.transport[0].type + " |
";
-
- }
-
- result += "" +
- "" +
- "" +
- "" +
- translate("connectionindicator.bandwidth") + "" +
- " | " +
- "↓" +
- downloadBandwidth +
- " ↑" +
- uploadBandwidth + " |
";
-
- result += transport + "
";
-
- }
-
- return result;
-};
-
-/**
- * Shows or hide the additional information.
- */
-ConnectionIndicator.prototype.showMore = function () {
- this.showMoreValue = !this.showMoreValue;
- this.updatePopoverData();
-};
-
-
-function createIcon(classes)
-{
- var icon = document.createElement("span");
- for(var i in classes)
- {
- icon.classList.add(classes[i]);
- }
- icon.appendChild(
- document.createElement("i")).classList.add("icon-connection");
- return icon;
-}
-
-/**
- * Creates the indicator
- */
-ConnectionIndicator.prototype.create = function () {
- this.connectionIndicatorContainer = document.createElement("div");
- this.connectionIndicatorContainer.className = "connectionindicator";
- this.connectionIndicatorContainer.style.display = "none";
- this.videoContainer.container.appendChild(this.connectionIndicatorContainer);
- this.popover = new JitsiPopover(
- $("#" + this.videoContainer.videoSpanId + " > .connectionindicator"),
- {content: "" +
- APP.translation.translateString("connectionindicator.na") + "
",
- skin: "black"});
-
- this.emptyIcon = this.connectionIndicatorContainer.appendChild(
- createIcon(["connection", "connection_empty"]));
- this.fullIcon = this.connectionIndicatorContainer.appendChild(
- createIcon(["connection", "connection_full"]));
-
-};
-
-/**
- * Removes the indicator
- */
-ConnectionIndicator.prototype.remove = function()
-{
- if (this.connectionIndicatorContainer.parentNode) {
- this.connectionIndicatorContainer.parentNode.removeChild(
- this.connectionIndicatorContainer);
- }
- this.popover.forceHide();
-
-};
-
-/**
- * Updates the data of the indicator
- * @param percent the percent of connection quality
- * @param object the statistics data.
- */
-ConnectionIndicator.prototype.updateConnectionQuality =
-function (percent, object) {
-
- if(percent === null)
- {
- this.connectionIndicatorContainer.style.display = "none";
- this.popover.forceHide();
- return;
- }
- else
- {
- if(this.connectionIndicatorContainer.style.display == "none") {
- this.connectionIndicatorContainer.style.display = "block";
- this.videoContainer.updateIconPositions();
- }
- }
- this.bandwidth = object.bandwidth;
- this.bitrate = object.bitrate;
- this.packetLoss = object.packetLoss;
- this.transport = object.transport;
- if(object.resolution)
- {
- this.resolution = object.resolution;
- }
- for(var quality in ConnectionIndicator.connectionQualityValues)
- {
- if(percent >= quality)
- {
- this.fullIcon.style.width =
- ConnectionIndicator.connectionQualityValues[quality];
- }
- }
- this.updatePopoverData();
-};
-
-/**
- * Updates the resolution
- * @param resolution the new resolution
- */
-ConnectionIndicator.prototype.updateResolution = function (resolution) {
- this.resolution = resolution;
- this.updatePopoverData();
-};
-
-/**
- * Updates the content of the popover
- */
-ConnectionIndicator.prototype.updatePopoverData = function () {
- this.popover.updateContent(
- "" + this.generateText() + "
");
- APP.translation.translateElement($(".connection_info"));
-};
-
-/**
- * Hides the popover
- */
-ConnectionIndicator.prototype.hide = function () {
- this.popover.forceHide();
-};
-
-/**
- * Hides the indicator
- */
-ConnectionIndicator.prototype.hideIndicator = function () {
- this.connectionIndicatorContainer.style.display = "none";
- if(this.popover)
- this.popover.forceHide();
-};
-
+
+RTCUtils.prototype.setAvailableDevices = function (um, available) {
+ var devices = {};
+ if(um.indexOf("video") != -1)
+ {
+ devices.video = available;
+ }
+ if(um.indexOf("audio") != -1)
+ {
+ devices.audio = available;
+ }
+ this.service.setDeviceAvailability(devices);
+}
+
+/**
+ * We ask for audio and video combined stream in order to get permissions and
+ * not to ask twice.
+ */
+RTCUtils.prototype.obtainAudioAndVideoPermissions =
+ function(devices, callback, usageOptions)
+{
+ var self = this;
+ // Get AV
+
+ var successCallback = function (stream) {
+ if(callback)
+ callback(stream, usageOptions);
+ else
+ self.successCallback(stream, usageOptions);
+ };
+
+ if(!devices)
+ devices = ['audio', 'video'];
+
+ var newDevices = [];
+
+
+ if(usageOptions)
+ for(var i = 0; i < devices.length; i++)
+ {
+ var device = devices[i];
+ if(usageOptions[device] === true)
+ newDevices.push(device);
+ }
+ else
+ newDevices = devices;
+
+ if(newDevices.length === 0)
+ {
+ successCallback();
+ return;
+ }
+
+ if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) {
+
+ // With FF/IE we can't split the stream into audio and video because FF
+ // doesn't support media stream constructors. So, we need to get the
+ // audio stream separately from the video stream using two distinct GUM
+ // calls. Not very user friendly :-( but we don't have many other
+ // options neither.
+ //
+ // Note that we pack those 2 streams in a single object and pass it to
+ // the successCallback method.
+ var obtainVideo = function (audioStream) {
+ self.getUserMediaWithConstraints(
+ ['video'],
+ function (videoStream) {
+ return successCallback({
+ audioStream: audioStream,
+ videoStream: videoStream
+ });
+ },
+ function (error) {
+ console.error(
+ 'failed to obtain video stream - stop', error);
+ self.errorCallback(error);
+ },
+ config.resolution || '360');
+ };
+ var obtainAudio = function () {
+ self.getUserMediaWithConstraints(
+ ['audio'],
+ function (audioStream) {
+ if (newDevices.indexOf('video') !== -1)
+ obtainVideo(audioStream);
+ },
+ function (error) {
+ console.error(
+ 'failed to obtain audio stream - stop', error);
+ self.errorCallback(error);
+ }
+ );
+ };
+ if (newDevices.indexOf('audio') !== -1) {
+ obtainAudio();
+ } else {
+ obtainVideo(null);
+ }
+ } else {
+ this.getUserMediaWithConstraints(
+ newDevices,
+ function (stream) {
+ successCallback(stream);
+ },
+ function (error) {
+ self.errorCallback(error);
+ },
+ config.resolution || '360');
+ }
+
+};
+
+RTCUtils.prototype.successCallback = function (stream, usageOptions) {
+ // If this is FF or IE, the stream parameter is *not* a MediaStream object,
+ // it's an object with two properties: audioStream, videoStream.
+ if (stream && stream.getAudioTracks && stream.getVideoTracks)
+ console.log('got', stream, stream.getAudioTracks().length,
+ stream.getVideoTracks().length);
+ this.handleLocalStream(stream, usageOptions);
+};
+
+RTCUtils.prototype.errorCallback = function (error) {
+ var self = this;
+ console.error('failed to obtain audio/video stream - trying audio only', error);
+ var resolution = getPreviousResolution(currentResolution);
+ if(typeof error == "object" && error.constraintName && error.name
+ && (error.name == "ConstraintNotSatisfiedError" ||
+ error.name == "OverconstrainedError") &&
+ (error.constraintName == "minWidth" || error.constraintName == "maxWidth" ||
+ error.constraintName == "minHeight" || error.constraintName == "maxHeight")
+ && resolution != null)
+ {
+ self.getUserMediaWithConstraints(['audio', 'video'],
+ function (stream) {
+ return self.successCallback(stream);
+ }, function (error) {
+ return self.errorCallback(error);
+ }, resolution);
+ }
+ else
+ {
+ self.getUserMediaWithConstraints(
+ ['audio'],
+ function (stream) {
+ return self.successCallback(stream);
+ },
+ function (error) {
+ console.error('failed to obtain audio/video stream - stop',
+ error);
+ return self.successCallback(null);
+ }
+ );
+ }
+
+}
+
+RTCUtils.prototype.handleLocalStream = function(stream, usageOptions)
+{
+ // If this is FF, the stream parameter is *not* a MediaStream object, it's
+ // an object with two properties: audioStream, videoStream.
+ var audioStream, videoStream;
+ if(window.webkitMediaStream)
+ {
+ audioStream = new webkitMediaStream();
+ videoStream = new webkitMediaStream();
+ if(stream) {
+ var audioTracks = stream.getAudioTracks();
+
+ for (var i = 0; i < audioTracks.length; i++) {
+ audioStream.addTrack(audioTracks[i]);
+ }
+
+ var videoTracks = stream.getVideoTracks();
+
+ for (i = 0; i < videoTracks.length; i++) {
+ videoStream.addTrack(videoTracks[i]);
+ }
+ }
+ }
+ else if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed())
+ { // Firefox and Temasys plugin
+ if (stream && stream.audioStream)
+ audioStream = stream.audioStream;
+ else
+ audioStream = new DummyMediaStream("dummyAudio");
+
+ if (stream && stream.videoStream)
+ videoStream = stream.videoStream;
+ else
+ videoStream = new DummyMediaStream("dummyVideo");
+ }
+
+ var audioMuted = (usageOptions && usageOptions.audio === false),
+ videoMuted = (usageOptions && usageOptions.video === false);
+
+ var audioGUM = (!usageOptions || usageOptions.audio !== false),
+ videoGUM = (!usageOptions || usageOptions.video !== false);
+
+
+ this.service.createLocalStream(audioStream, "audio", null, null,
+ audioMuted, audioGUM);
+
+ this.service.createLocalStream(videoStream, "video", null, null,
+ videoMuted, videoGUM);
+};
+
+function DummyMediaStream(id) {
+ this.id = id;
+ this.label = id;
+ this.stop = function() { };
+ this.getAudioTracks = function() { return []; }
+ this.getVideoTracks = function() { return []; }
+}
+
+RTCUtils.prototype.createStream = function(stream, isVideo) {
+ var newStream = null;
+ if (window.webkitMediaStream) {
+ newStream = new webkitMediaStream();
+ if (newStream) {
+ var tracks = (isVideo ? stream.getVideoTracks() : stream.getAudioTracks());
+
+ for (i = 0; i < tracks.length; i++) {
+ newStream.addTrack(tracks[i]);
+ }
+ }
+
+ }
+ else {
+ // FIXME: this is duplicated with 'handleLocalStream' !!!
+ if (stream) {
+ newStream = stream;
+ } else {
+ newStream = new DummyMediaStream(isVideo ? "dummyVideo" : "dummyAudio");
+ }
+ }
+
+ return newStream;
+};
+
+module.exports = RTCUtils;
+
+},{"../../service/RTC/Resolutions":104,"../xmpp/SDPUtil":56,"./RTCBrowserType":8,"./adapter.screenshare":10}],10:[function(require,module,exports){
+/*! adapterjs - v0.11.0 - 2015-06-08 */
+
+// Adapter's interface.
+var AdapterJS = AdapterJS || {};
+
+// Browserify compatibility
+if(typeof exports !== 'undefined') {
+ module.exports = AdapterJS;
+}
+
+AdapterJS.options = AdapterJS.options || {};
+
+// uncomment to get virtual webcams
+// AdapterJS.options.getAllCams = true;
+
+// uncomment to prevent the install prompt when the plugin in not yet installed
+// AdapterJS.options.hidePluginInstallPrompt = true;
+
+// AdapterJS version
+AdapterJS.VERSION = '0.11.0';
+
+// This function will be called when the WebRTC API is ready to be used
+// Whether it is the native implementation (Chrome, Firefox, Opera) or
+// the plugin
+// You may Override this function to synchronise the start of your application
+// with the WebRTC API being ready.
+// If you decide not to override use this synchronisation, it may result in
+// an extensive CPU usage on the plugin start (once per tab loaded)
+// Params:
+// - isUsingPlugin: true is the WebRTC plugin is being used, false otherwise
+//
+AdapterJS.onwebrtcready = AdapterJS.onwebrtcready || function(isUsingPlugin) {
+ // The WebRTC API is ready.
+ // Override me and do whatever you want here
+};
+
+// Sets a callback function to be called when the WebRTC interface is ready.
+// The first argument is the function to callback.\
+// Throws an error if the first argument is not a function
+AdapterJS.webRTCReady = function (callback) {
+ if (typeof callback !== 'function') {
+ throw new Error('Callback provided is not a function');
+ }
+
+ if (true === AdapterJS.onwebrtcreadyDone) {
+ // All WebRTC interfaces are ready, just call the callback
+ callback(null !== AdapterJS.WebRTCPlugin.plugin);
+ } else {
+ // will be triggered automatically when your browser/plugin is ready.
+ AdapterJS.onwebrtcready = callback;
+ }
+};
+
+// Plugin namespace
+AdapterJS.WebRTCPlugin = AdapterJS.WebRTCPlugin || {};
+
+// The object to store plugin information
+AdapterJS.WebRTCPlugin.pluginInfo = {
+ prefix : 'Tem',
+ plugName : 'TemWebRTCPlugin',
+ pluginId : 'plugin0',
+ type : 'application/x-temwebrtcplugin',
+ onload : '__TemWebRTCReady0',
+ portalLink : 'http://skylink.io/plugin/',
+ downloadLink : null, //set below
+ companyName: 'Temasys'
+};
+if(!!navigator.platform.match(/^Mac/i)) {
+ AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1n77hco';
+}
+else if(!!navigator.platform.match(/^Win/i)) {
+ AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1kkS4FN';
+}
+
+// Unique identifier of each opened page
+AdapterJS.WebRTCPlugin.pageId = Math.random().toString(36).slice(2);
+
+// Use this whenever you want to call the plugin.
+AdapterJS.WebRTCPlugin.plugin = null;
+
+// Set log level for the plugin once it is ready.
+// The different values are
+// This is an asynchronous function that will run when the plugin is ready
+AdapterJS.WebRTCPlugin.setLogLevel = null;
+
+// Defines webrtc's JS interface according to the plugin's implementation.
+// Define plugin Browsers as WebRTC Interface.
+AdapterJS.WebRTCPlugin.defineWebRTCInterface = null;
+
+// This function detects whether or not a plugin is installed.
+// Checks if Not IE (firefox, for example), else if it's IE,
+// we're running IE and do something. If not it is not supported.
+AdapterJS.WebRTCPlugin.isPluginInstalled = null;
+
+ // Lets adapter.js wait until the the document is ready before injecting the plugin
+AdapterJS.WebRTCPlugin.pluginInjectionInterval = null;
+
+// Inject the HTML DOM object element into the page.
+AdapterJS.WebRTCPlugin.injectPlugin = null;
+
+// States of readiness that the plugin goes through when
+// being injected and stated
+AdapterJS.WebRTCPlugin.PLUGIN_STATES = {
+ NONE : 0, // no plugin use
+ INITIALIZING : 1, // Detected need for plugin
+ INJECTING : 2, // Injecting plugin
+ INJECTED: 3, // Plugin element injected but not usable yet
+ READY: 4 // Plugin ready to be used
+};
+
+// Current state of the plugin. You cannot use the plugin before this is
+// equal to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY
+AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.NONE;
+
+// True is AdapterJS.onwebrtcready was already called, false otherwise
+// Used to make sure AdapterJS.onwebrtcready is only called once
+AdapterJS.onwebrtcreadyDone = false;
+
+// Log levels for the plugin.
+// To be set by calling AdapterJS.WebRTCPlugin.setLogLevel
+/*
+Log outputs are prefixed in some cases.
+ INFO: Information reported by the plugin.
+ ERROR: Errors originating from within the plugin.
+ WEBRTC: Error originating from within the libWebRTC library
+*/
+// From the least verbose to the most verbose
+AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS = {
+ NONE : 'NONE',
+ ERROR : 'ERROR',
+ WARNING : 'WARNING',
+ INFO: 'INFO',
+ VERBOSE: 'VERBOSE',
+ SENSITIVE: 'SENSITIVE'
+};
+
+// Does a waiting check before proceeding to load the plugin.
+AdapterJS.WebRTCPlugin.WaitForPluginReady = null;
+
+// This methid will use an interval to wait for the plugin to be ready.
+AdapterJS.WebRTCPlugin.callWhenPluginReady = null;
+
+// !!!! WARNING: DO NOT OVERRIDE THIS FUNCTION. !!!
+// This function will be called when plugin is ready. It sends necessary
+// details to the plugin.
+// The function will wait for the document to be ready and the set the
+// plugin state to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY,
+// indicating that it can start being requested.
+// This function is not in the IE/Safari condition brackets so that
+// TemPluginLoaded function might be called on Chrome/Firefox.
+// This function is the only private function that is not encapsulated to
+// allow the plugin method to be called.
+__TemWebRTCReady0 = function () {
+ if (document.readyState === 'complete') {
+ AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY;
+
+ AdapterJS.maybeThroughWebRTCReady();
+ } else {
+ AdapterJS.WebRTCPlugin.documentReadyInterval = setInterval(function () {
+ if (document.readyState === 'complete') {
+ // TODO: update comments, we wait for the document to be ready
+ clearInterval(AdapterJS.WebRTCPlugin.documentReadyInterval);
+ AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY;
+
+ AdapterJS.maybeThroughWebRTCReady();
+ }
+ }, 100);
+ }
+};
+
+AdapterJS.maybeThroughWebRTCReady = function() {
+ if (!AdapterJS.onwebrtcreadyDone) {
+ AdapterJS.onwebrtcreadyDone = true;
+
+ if (typeof(AdapterJS.onwebrtcready) === 'function') {
+ AdapterJS.onwebrtcready(AdapterJS.WebRTCPlugin.plugin !== null);
+ }
+ }
+};
+
+// Text namespace
+AdapterJS.TEXT = {
+ PLUGIN: {
+ REQUIRE_INSTALLATION: 'This website requires you to install a WebRTC-enabling plugin ' +
+ 'to work on this browser.',
+ NOT_SUPPORTED: 'Your browser does not support WebRTC.',
+ BUTTON: 'Install Now'
+ },
+ REFRESH: {
+ REQUIRE_REFRESH: 'Please refresh page',
+ BUTTON: 'Refresh Page'
+ }
+};
+
+// The result of ice connection states.
+// - starting: Ice connection is starting.
+// - checking: Ice connection is checking.
+// - connected Ice connection is connected.
+// - completed Ice connection is connected.
+// - done Ice connection has been completed.
+// - disconnected Ice connection has been disconnected.
+// - failed Ice connection has failed.
+// - closed Ice connection is closed.
+AdapterJS._iceConnectionStates = {
+ starting : 'starting',
+ checking : 'checking',
+ connected : 'connected',
+ completed : 'connected',
+ done : 'completed',
+ disconnected : 'disconnected',
+ failed : 'failed',
+ closed : 'closed'
+};
+
+//The IceConnection states that has been fired for each peer.
+AdapterJS._iceConnectionFiredStates = [];
+
+
+// Check if WebRTC Interface is defined.
+AdapterJS.isDefined = null;
+
+// This function helps to retrieve the webrtc detected browser information.
+// This sets:
+// - webrtcDetectedBrowser: The browser agent name.
+// - webrtcDetectedVersion: The browser version.
+// - webrtcDetectedType: The types of webRTC support.
+// - 'moz': Mozilla implementation of webRTC.
+// - 'webkit': WebKit implementation of webRTC.
+// - 'plugin': Using the plugin implementation.
+AdapterJS.parseWebrtcDetectedBrowser = function () {
+ var hasMatch, checkMatch = navigator.userAgent.match(
+ /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
+ if (/trident/i.test(checkMatch[1])) {
+ hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || [];
+ webrtcDetectedBrowser = 'IE';
+ webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10);
+ } else if (checkMatch[1] === 'Chrome') {
+ hasMatch = navigator.userAgent.match(/\bOPR\/(\d+)/);
+ if (hasMatch !== null) {
+ webrtcDetectedBrowser = 'opera';
+ webrtcDetectedVersion = parseInt(hasMatch[1], 10);
+ }
+ }
+ if (navigator.userAgent.indexOf('Safari')) {
+ if (typeof InstallTrigger !== 'undefined') {
+ webrtcDetectedBrowser = 'firefox';
+ } else if (/*@cc_on!@*/ false || !!document.documentMode) {
+ webrtcDetectedBrowser = 'IE';
+ } else if (
+ Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) {
+ webrtcDetectedBrowser = 'safari';
+ } else if (!!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0) {
+ webrtcDetectedBrowser = 'opera';
+ } else if (!!window.chrome) {
+ webrtcDetectedBrowser = 'chrome';
+ }
+ }
+ if (!webrtcDetectedBrowser) {
+ webrtcDetectedVersion = checkMatch[1];
+ }
+ if (!webrtcDetectedVersion) {
+ try {
+ checkMatch = (checkMatch[2]) ? [checkMatch[1], checkMatch[2]] :
+ [navigator.appName, navigator.appVersion, '-?'];
+ if ((hasMatch = navigator.userAgent.match(/version\/(\d+)/i)) !== null) {
+ checkMatch.splice(1, 1, hasMatch[1]);
+ }
+ webrtcDetectedVersion = parseInt(checkMatch[1], 10);
+ } catch (error) { }
+ }
+};
+
+// To fix configuration as some browsers does not support
+// the 'urls' attribute.
+AdapterJS.maybeFixConfiguration = function (pcConfig) {
+ if (pcConfig === null) {
+ return;
+ }
+ for (var i = 0; i < pcConfig.iceServers.length; i++) {
+ if (pcConfig.iceServers[i].hasOwnProperty('urls')) {
+ pcConfig.iceServers[i].url = pcConfig.iceServers[i].urls;
+ delete pcConfig.iceServers[i].urls;
+ }
+ }
+};
+
+AdapterJS.addEvent = function(elem, evnt, func) {
+ if (elem.addEventListener) { // W3C DOM
+ elem.addEventListener(evnt, func, false);
+ } else if (elem.attachEvent) {// OLD IE DOM
+ elem.attachEvent('on'+evnt, func);
+ } else { // No much to do
+ elem[evnt] = func;
+ }
+};
+
+AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNewTab, displayRefreshBar) {
+ // only inject once the page is ready
+ if (document.readyState !== 'complete') {
+ return;
+ }
+
+ var w = window;
+ var i = document.createElement('iframe');
+ i.style.position = 'fixed';
+ i.style.top = '-41px';
+ i.style.left = 0;
+ i.style.right = 0;
+ i.style.width = '100%';
+ i.style.height = '40px';
+ i.style.backgroundColor = '#ffffe1';
+ i.style.border = 'none';
+ i.style.borderBottom = '1px solid #888888';
+ i.style.zIndex = '9999999';
+ if(typeof i.style.webkitTransition === 'string') {
+ i.style.webkitTransition = 'all .5s ease-out';
+ } else if(typeof i.style.transition === 'string') {
+ i.style.transition = 'all .5s ease-out';
+ }
+ document.body.appendChild(i);
+ c = (i.contentWindow) ? i.contentWindow :
+ (i.contentDocument.document) ? i.contentDocument.document : i.contentDocument;
+ c.document.open();
+ c.document.write('' + text + '');
+ if(buttonText && buttonLink) {
+ c.document.write('');
+ c.document.close();
+
+ AdapterJS.addEvent(c.document.getElementById('okay'), 'click', function(e) {
+ if (!!displayRefreshBar) {
+ AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION ?
+ AdapterJS.TEXT.EXTENSION.REQUIRE_REFRESH : AdapterJS.TEXT.REFRESH.REQUIRE_REFRESH,
+ AdapterJS.TEXT.REFRESH.BUTTON, 'javascript:location.reload()');
+ }
+ window.open(buttonLink, !!openNewTab ? '_blank' : '_top');
+
+ e.preventDefault();
+ try {
+ event.cancelBubble = true;
+ } catch(error) { }
+ });
+ }
+ else {
+ c.document.close();
+ }
+ AdapterJS.addEvent(c.document, 'click', function() {
+ w.document.body.removeChild(i);
+ });
+ setTimeout(function() {
+ if(typeof i.style.webkitTransform === 'string') {
+ i.style.webkitTransform = 'translateY(40px)';
+ } else if(typeof i.style.transform === 'string') {
+ i.style.transform = 'translateY(40px)';
+ } else {
+ i.style.top = '0px';
+ }
+ }, 300);
+};
+
+// -----------------------------------------------------------
+// Detected webrtc implementation. Types are:
+// - 'moz': Mozilla implementation of webRTC.
+// - 'webkit': WebKit implementation of webRTC.
+// - 'plugin': Using the plugin implementation.
+webrtcDetectedType = null;
+
+// Detected webrtc datachannel support. Types are:
+// - 'SCTP': SCTP datachannel support.
+// - 'RTP': RTP datachannel support.
+webrtcDetectedDCSupport = null;
+
+// Set the settings for creating DataChannels, MediaStream for
+// Cross-browser compability.
+// - This is only for SCTP based support browsers.
+// the 'urls' attribute.
+checkMediaDataChannelSettings =
+ function (peerBrowserAgent, peerBrowserVersion, callback, constraints) {
+ if (typeof callback !== 'function') {
+ return;
+ }
+ var beOfferer = true;
+ var isLocalFirefox = webrtcDetectedBrowser === 'firefox';
+ // Nightly version does not require MozDontOfferDataChannel for interop
+ var isLocalFirefoxInterop = webrtcDetectedType === 'moz' && webrtcDetectedVersion > 30;
+ var isPeerFirefox = peerBrowserAgent === 'firefox';
+ var isPeerFirefoxInterop = peerBrowserAgent === 'firefox' &&
+ ((peerBrowserVersion) ? (peerBrowserVersion > 30) : false);
+
+ // Resends an updated version of constraints for MozDataChannel to work
+ // If other userAgent is firefox and user is firefox, remove MozDataChannel
+ if ((isLocalFirefox && isPeerFirefox) || (isLocalFirefoxInterop)) {
+ try {
+ delete constraints.mandatory.MozDontOfferDataChannel;
+ } catch (error) {
+ console.error('Failed deleting MozDontOfferDataChannel');
+ console.error(error);
+ }
+ } else if ((isLocalFirefox && !isPeerFirefox)) {
+ constraints.mandatory.MozDontOfferDataChannel = true;
+ }
+ if (!isLocalFirefox) {
+ // temporary measure to remove Moz* constraints in non Firefox browsers
+ for (var prop in constraints.mandatory) {
+ if (constraints.mandatory.hasOwnProperty(prop)) {
+ if (prop.indexOf('Moz') !== -1) {
+ delete constraints.mandatory[prop];
+ }
+ }
+ }
+ }
+ // Firefox (not interopable) cannot offer DataChannel as it will cause problems to the
+ // interopability of the media stream
+ if (isLocalFirefox && !isPeerFirefox && !isLocalFirefoxInterop) {
+ beOfferer = false;
+ }
+ callback(beOfferer, constraints);
+};
+
+// Handles the differences for all browsers ice connection state output.
+// - Tested outcomes are:
+// - Chrome (offerer) : 'checking' > 'completed' > 'completed'
+// - Chrome (answerer) : 'checking' > 'connected'
+// - Firefox (offerer) : 'checking' > 'connected'
+// - Firefox (answerer): 'checking' > 'connected'
+checkIceConnectionState = function (peerId, iceConnectionState, callback) {
+ if (typeof callback !== 'function') {
+ console.warn('No callback specified in checkIceConnectionState. Aborted.');
+ return;
+ }
+ peerId = (peerId) ? peerId : 'peer';
+
+ if (!AdapterJS._iceConnectionFiredStates[peerId] ||
+ iceConnectionState === AdapterJS._iceConnectionStates.disconnected ||
+ iceConnectionState === AdapterJS._iceConnectionStates.failed ||
+ iceConnectionState === AdapterJS._iceConnectionStates.closed) {
+ AdapterJS._iceConnectionFiredStates[peerId] = [];
+ }
+ iceConnectionState = AdapterJS._iceConnectionStates[iceConnectionState];
+ if (AdapterJS._iceConnectionFiredStates[peerId].indexOf(iceConnectionState) < 0) {
+ AdapterJS._iceConnectionFiredStates[peerId].push(iceConnectionState);
+ if (iceConnectionState === AdapterJS._iceConnectionStates.connected) {
+ setTimeout(function () {
+ AdapterJS._iceConnectionFiredStates[peerId]
+ .push(AdapterJS._iceConnectionStates.done);
+ callback(AdapterJS._iceConnectionStates.done);
+ }, 1000);
+ }
+ callback(iceConnectionState);
+ }
+ return;
+};
+
+// Firefox:
+// - Creates iceServer from the url for Firefox.
+// - Create iceServer with stun url.
+// - Create iceServer with turn url.
+// - Ignore the transport parameter from TURN url for FF version <=27.
+// - Return null for createIceServer if transport=tcp.
+// - FF 27 and above supports transport parameters in TURN url,
+// - So passing in the full url to create iceServer.
+// Chrome:
+// - Creates iceServer from the url for Chrome M33 and earlier.
+// - Create iceServer with stun url.
+// - Chrome M28 & above uses below TURN format.
+// Plugin:
+// - Creates Ice Server for Plugin Browsers
+// - If Stun - Create iceServer with stun url.
+// - Else - Create iceServer with turn url
+// - This is a WebRTC Function
+createIceServer = null;
+
+// Firefox:
+// - Creates IceServers for Firefox
+// - Use .url for FireFox.
+// - Multiple Urls support
+// Chrome:
+// - Creates iceServers from the urls for Chrome M34 and above.
+// - .urls is supported since Chrome M34.
+// - Multiple Urls support
+// Plugin:
+// - Creates Ice Servers for Plugin Browsers
+// - Multiple Urls support
+// - This is a WebRTC Function
+createIceServers = null;
+//------------------------------------------------------------
+
+//The RTCPeerConnection object.
+RTCPeerConnection = null;
+
+// Creates RTCSessionDescription object for Plugin Browsers
+RTCSessionDescription = (typeof RTCSessionDescription === 'function') ?
+ RTCSessionDescription : null;
+
+// Creates RTCIceCandidate object for Plugin Browsers
+RTCIceCandidate = (typeof RTCIceCandidate === 'function') ?
+ RTCIceCandidate : null;
+
+// Get UserMedia (only difference is the prefix).
+// Code from Adam Barth.
+getUserMedia = null;
+
+// Attach a media stream to an element.
+attachMediaStream = null;
+
+// Re-attach a media stream to an element.
+reattachMediaStream = null;
+
+
+// Detected browser agent name. Types are:
+// - 'firefox': Firefox browser.
+// - 'chrome': Chrome browser.
+// - 'opera': Opera browser.
+// - 'safari': Safari browser.
+// - 'IE' - Internet Explorer browser.
+webrtcDetectedBrowser = null;
+
+// Detected browser version.
+webrtcDetectedVersion = null;
+
+// Check for browser types and react accordingly
+if (navigator.mozGetUserMedia) {
+ webrtcDetectedBrowser = 'firefox';
+ webrtcDetectedVersion = parseInt(navigator
+ .userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
+ webrtcDetectedType = 'moz';
+ webrtcDetectedDCSupport = 'SCTP';
+
+ RTCPeerConnection = function (pcConfig, pcConstraints) {
+ AdapterJS.maybeFixConfiguration(pcConfig);
+ return new mozRTCPeerConnection(pcConfig, pcConstraints);
+ };
+
+ // The RTCSessionDescription object.
+ RTCSessionDescription = mozRTCSessionDescription;
+ window.RTCSessionDescription = RTCSessionDescription;
+
+ // The RTCIceCandidate object.
+ RTCIceCandidate = mozRTCIceCandidate;
+ window.RTCIceCandidate = RTCIceCandidate;
+
+ window.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
+ navigator.getUserMedia = window.getUserMedia;
+
+ // Shim for MediaStreamTrack.getSources.
+ MediaStreamTrack.getSources = function(successCb) {
+ setTimeout(function() {
+ var infos = [
+ { kind: 'audio', id: 'default', label:'', facing:'' },
+ { kind: 'video', id: 'default', label:'', facing:'' }
+ ];
+ successCb(infos);
+ }, 0);
+ };
+
+ createIceServer = function (url, username, password) {
+ var iceServer = null;
+ var url_parts = url.split(':');
+ if (url_parts[0].indexOf('stun') === 0) {
+ iceServer = { url : url };
+ } else if (url_parts[0].indexOf('turn') === 0) {
+ if (webrtcDetectedVersion < 27) {
+ var turn_url_parts = url.split('?');
+ if (turn_url_parts.length === 1 ||
+ turn_url_parts[1].indexOf('transport=udp') === 0) {
+ iceServer = {
+ url : turn_url_parts[0],
+ credential : password,
+ username : username
+ };
+ }
+ } else {
+ iceServer = {
+ url : url,
+ credential : password,
+ username : username
+ };
+ }
+ }
+ return iceServer;
+ };
+
+ createIceServers = function (urls, username, password) {
+ var iceServers = [];
+ for (i = 0; i < urls.length; i++) {
+ var iceServer = createIceServer(urls[i], username, password);
+ if (iceServer !== null) {
+ iceServers.push(iceServer);
+ }
+ }
+ return iceServers;
+ };
+
+ attachMediaStream = function (element, stream) {
+ element.mozSrcObject = stream;
+ if (stream !== null)
+ element.play();
+
+ return element;
+ };
+
+ reattachMediaStream = function (to, from) {
+ to.mozSrcObject = from.mozSrcObject;
+ to.play();
+ return to;
+ };
+
+ MediaStreamTrack.getSources = MediaStreamTrack.getSources || function (callback) {
+ if (!callback) {
+ throw new TypeError('Failed to execute \'getSources\' on \'MediaStreamTrack\'' +
+ ': 1 argument required, but only 0 present.');
+ }
+ return callback([]);
+ };
+
+ // Fake get{Video,Audio}Tracks
+ if (!MediaStream.prototype.getVideoTracks) {
+ MediaStream.prototype.getVideoTracks = function () {
+ return [];
+ };
+ }
+ if (!MediaStream.prototype.getAudioTracks) {
+ MediaStream.prototype.getAudioTracks = function () {
+ return [];
+ };
+ }
+
+ AdapterJS.maybeThroughWebRTCReady();
+} else if (navigator.webkitGetUserMedia) {
+ webrtcDetectedBrowser = 'chrome';
+ webrtcDetectedType = 'webkit';
+ webrtcDetectedVersion = parseInt(navigator
+ .userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);
+ // check if browser is opera 20+
+ var checkIfOpera = navigator.userAgent.match(/\bOPR\/(\d+)/);
+ if (checkIfOpera !== null) {
+ webrtcDetectedBrowser = 'opera';
+ webrtcDetectedVersion = parseInt(checkIfOpera[1], 10);
+ }
+ // check browser datachannel support
+ if ((webrtcDetectedBrowser === 'chrome' && webrtcDetectedVersion >= 31) ||
+ (webrtcDetectedBrowser === 'opera' && webrtcDetectedVersion >= 20)) {
+ webrtcDetectedDCSupport = 'SCTP';
+ } else if (webrtcDetectedBrowser === 'chrome' && webrtcDetectedVersion < 30 &&
+ webrtcDetectedVersion > 24) {
+ webrtcDetectedDCSupport = 'RTP';
+ } else {
+ webrtcDetectedDCSupport = '';
+ }
+
+ createIceServer = function (url, username, password) {
+ var iceServer = null;
+ var url_parts = url.split(':');
+ if (url_parts[0].indexOf('stun') === 0) {
+ iceServer = { 'url' : url };
+ } else if (url_parts[0].indexOf('turn') === 0) {
+ iceServer = {
+ 'url' : url,
+ 'credential' : password,
+ 'username' : username
+ };
+ }
+ return iceServer;
+ };
+
+ createIceServers = function (urls, username, password) {
+ var iceServers = [];
+ if (webrtcDetectedVersion >= 34) {
+ iceServers = {
+ 'urls' : urls,
+ 'credential' : password,
+ 'username' : username
+ };
+ } else {
+ for (i = 0; i < urls.length; i++) {
+ var iceServer = createIceServer(urls[i], username, password);
+ if (iceServer !== null) {
+ iceServers.push(iceServer);
+ }
+ }
+ }
+ return iceServers;
+ };
+
+ RTCPeerConnection = function (pcConfig, pcConstraints) {
+ if (webrtcDetectedVersion < 34) {
+ AdapterJS.maybeFixConfiguration(pcConfig);
+ }
+ return new webkitRTCPeerConnection(pcConfig, pcConstraints);
+ };
+
+ window.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
+ navigator.getUserMedia = window.getUserMedia;
+
+ attachMediaStream = function (element, stream) {
+ if (typeof element.srcObject !== 'undefined') {
+ element.srcObject = stream;
+ } else if (typeof element.mozSrcObject !== 'undefined') {
+ element.mozSrcObject = stream;
+ } else if (typeof element.src !== 'undefined') {
+ element.src = (stream === null ? '' : URL.createObjectURL(stream));
+ } else {
+ console.log('Error attaching stream to element.');
+ }
+ return element;
+ };
+
+ reattachMediaStream = function (to, from) {
+ to.src = from.src;
+ return to;
+ };
+
+ AdapterJS.maybeThroughWebRTCReady();
+} else { // TRY TO USE PLUGIN
+ // IE 9 is not offering an implementation of console.log until you open a console
+ if (typeof console !== 'object' || typeof console.log !== 'function') {
+ /* jshint -W020 */
+ console = {} || console;
+ // Implemented based on console specs from MDN
+ // You may override these functions
+ console.log = function (arg) {};
+ console.info = function (arg) {};
+ console.error = function (arg) {};
+ console.dir = function (arg) {};
+ console.exception = function (arg) {};
+ console.trace = function (arg) {};
+ console.warn = function (arg) {};
+ console.count = function (arg) {};
+ console.debug = function (arg) {};
+ console.count = function (arg) {};
+ console.time = function (arg) {};
+ console.timeEnd = function (arg) {};
+ console.group = function (arg) {};
+ console.groupCollapsed = function (arg) {};
+ console.groupEnd = function (arg) {};
+ /* jshint +W020 */
+ }
+ webrtcDetectedType = 'plugin';
+ webrtcDetectedDCSupport = 'plugin';
+ AdapterJS.parseWebrtcDetectedBrowser();
+ isIE = webrtcDetectedBrowser === 'IE';
+
+ /* jshint -W035 */
+ AdapterJS.WebRTCPlugin.WaitForPluginReady = function() {
+ while (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) {
+ /* empty because it needs to prevent the function from running. */
+ }
+ };
+ /* jshint +W035 */
+
+ AdapterJS.WebRTCPlugin.callWhenPluginReady = function (callback) {
+ if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) {
+ // Call immediately if possible
+ // Once the plugin is set, the code will always take this path
+ callback();
+ } else {
+ // otherwise start a 100ms interval
+ var checkPluginReadyState = setInterval(function () {
+ if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) {
+ clearInterval(checkPluginReadyState);
+ callback();
+ }
+ }, 100);
+ }
+ };
+
+ AdapterJS.WebRTCPlugin.setLogLevel = function(logLevel) {
+ AdapterJS.WebRTCPlugin.callWhenPluginReady(function() {
+ AdapterJS.WebRTCPlugin.plugin.setLogLevel(logLevel);
+ });
+ };
+
+ AdapterJS.WebRTCPlugin.injectPlugin = function () {
+ // only inject once the page is ready
+ if (document.readyState !== 'complete') {
+ return;
+ }
+
+ // Prevent multiple injections
+ if (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING) {
+ return;
+ }
+
+ AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTING;
+
+ if (webrtcDetectedBrowser === 'IE' && webrtcDetectedVersion <= 10) {
+ var frag = document.createDocumentFragment();
+ AdapterJS.WebRTCPlugin.plugin = document.createElement('div');
+ AdapterJS.WebRTCPlugin.plugin.innerHTML = '';
+ while (AdapterJS.WebRTCPlugin.plugin.firstChild) {
+ frag.appendChild(AdapterJS.WebRTCPlugin.plugin.firstChild);
+ }
+ document.body.appendChild(frag);
+
+ // Need to re-fetch the plugin
+ AdapterJS.WebRTCPlugin.plugin =
+ document.getElementById(AdapterJS.WebRTCPlugin.pluginInfo.pluginId);
+ } else {
+ // Load Plugin
+ AdapterJS.WebRTCPlugin.plugin = document.createElement('object');
+ AdapterJS.WebRTCPlugin.plugin.id =
+ AdapterJS.WebRTCPlugin.pluginInfo.pluginId;
+ // IE will only start the plugin if it's ACTUALLY visible
+ if (isIE) {
+ AdapterJS.WebRTCPlugin.plugin.width = '1px';
+ AdapterJS.WebRTCPlugin.plugin.height = '1px';
+ } else { // The size of the plugin on Safari should be 0x0px
+ // so that the autorisation prompt is at the top
+ AdapterJS.WebRTCPlugin.plugin.width = '0px';
+ AdapterJS.WebRTCPlugin.plugin.height = '0px';
+ }
+ AdapterJS.WebRTCPlugin.plugin.type = AdapterJS.WebRTCPlugin.pluginInfo.type;
+ AdapterJS.WebRTCPlugin.plugin.innerHTML = '' +
+ '' +
+ ' ' +
+ (AdapterJS.options.getAllCams ? '':'') +
+ '';
+ document.body.appendChild(AdapterJS.WebRTCPlugin.plugin);
+ }
+
+
+ AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTED;
+ };
+
+ AdapterJS.WebRTCPlugin.isPluginInstalled =
+ function (comName, plugName, installedCb, notInstalledCb) {
+ if (!isIE) {
+ var pluginArray = navigator.plugins;
+ for (var i = 0; i < pluginArray.length; i++) {
+ if (pluginArray[i].name.indexOf(plugName) >= 0) {
+ installedCb();
+ return;
+ }
+ }
+ notInstalledCb();
+ } else {
+ try {
+ var axo = new ActiveXObject(comName + '.' + plugName);
+ } catch (e) {
+ notInstalledCb();
+ return;
+ }
+ installedCb();
+ }
+ };
+
+ AdapterJS.WebRTCPlugin.defineWebRTCInterface = function () {
+ AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING;
+
+ AdapterJS.isDefined = function (variable) {
+ return variable !== null && variable !== undefined;
+ };
+
+ createIceServer = function (url, username, password) {
+ var iceServer = null;
+ var url_parts = url.split(':');
+ if (url_parts[0].indexOf('stun') === 0) {
+ iceServer = {
+ 'url' : url,
+ 'hasCredentials' : false
+ };
+ } else if (url_parts[0].indexOf('turn') === 0) {
+ iceServer = {
+ 'url' : url,
+ 'hasCredentials' : true,
+ 'credential' : password,
+ 'username' : username
+ };
+ }
+ return iceServer;
+ };
+
+ createIceServers = function (urls, username, password) {
+ var iceServers = [];
+ for (var i = 0; i < urls.length; ++i) {
+ iceServers.push(createIceServer(urls[i], username, password));
+ }
+ return iceServers;
+ };
+
+ RTCSessionDescription = function (info) {
+ AdapterJS.WebRTCPlugin.WaitForPluginReady();
+ return AdapterJS.WebRTCPlugin.plugin.
+ ConstructSessionDescription(info.type, info.sdp);
+ };
+
+ RTCPeerConnection = function (servers, constraints) {
+ var iceServers = null;
+ if (servers) {
+ iceServers = servers.iceServers;
+ for (var i = 0; i < iceServers.length; i++) {
+ if (iceServers[i].urls && !iceServers[i].url) {
+ iceServers[i].url = iceServers[i].urls;
+ }
+ iceServers[i].hasCredentials = AdapterJS.
+ isDefined(iceServers[i].username) &&
+ AdapterJS.isDefined(iceServers[i].credential);
+ }
+ }
+ var mandatory = (constraints && constraints.mandatory) ?
+ constraints.mandatory : null;
+ var optional = (constraints && constraints.optional) ?
+ constraints.optional : null;
+
+ AdapterJS.WebRTCPlugin.WaitForPluginReady();
+ return AdapterJS.WebRTCPlugin.plugin.
+ PeerConnection(AdapterJS.WebRTCPlugin.pageId,
+ iceServers, mandatory, optional);
+ };
+
+ MediaStreamTrack = {};
+ MediaStreamTrack.getSources = function (callback) {
+ AdapterJS.WebRTCPlugin.callWhenPluginReady(function() {
+ AdapterJS.WebRTCPlugin.plugin.GetSources(callback);
+ });
+ };
+
+ window.getUserMedia = function (constraints, successCallback, failureCallback) {
+ constraints.audio = constraints.audio || false;
+ constraints.video = constraints.video || false;
+
+ AdapterJS.WebRTCPlugin.callWhenPluginReady(function() {
+ AdapterJS.WebRTCPlugin.plugin.
+ getUserMedia(constraints, successCallback, failureCallback);
+ });
+ };
+ window.navigator.getUserMedia = window.getUserMedia;
+
+ attachMediaStream = function (element, stream) {
+ if (!element || !element.parentNode) {
+ return;
+ }
+
+ var streamId
+ if (stream === null) {
+ streamId = '';
+ }
+ else {
+ stream.enableSoundTracks(true);
+ streamId = stream.id;
+ }
+
+ if (element.nodeName.toLowerCase() !== 'audio') {
+ var elementId = element.id.length === 0 ? Math.random().toString(36).slice(2) : element.id;
+ if (!element.isWebRTCPlugin || !element.isWebRTCPlugin()) {
+ var frag = document.createDocumentFragment();
+ var temp = document.createElement('div');
+ var classHTML = '';
+ if (element.className) {
+ classHTML = 'class="' + element.className + '" ';
+ } else if (element.attributes && element.attributes['class']) {
+ classHTML = 'class="' + element.attributes['class'].value + '" ';
+ }
+
+ temp.innerHTML = '';
+ while (temp.firstChild) {
+ frag.appendChild(temp.firstChild);
+ }
+
+ var height = '';
+ var width = '';
+ if (element.getBoundingClientRect) {
+ var rectObject = element.getBoundingClientRect();
+ width = rectObject.width + 'px';
+ height = rectObject.height + 'px';
+ }
+ else if (element.width) {
+ width = element.width;
+ height = element.height;
+ } else {
+ // TODO: What scenario could bring us here?
+ }
+
+ element.parentNode.insertBefore(frag, element);
+ frag = document.getElementById(elementId);
+ frag.width = width;
+ frag.height = height;
+ element.parentNode.removeChild(element);
+ } else {
+ var children = element.children;
+ for (var i = 0; i !== children.length; ++i) {
+ if (children[i].name === 'streamId') {
+ children[i].value = streamId;
+ break;
+ }
+ }
+ element.setStreamId(streamId);
+ }
+ var newElement = document.getElementById(elementId);
+ newElement.onplaying = (element.onplaying) ? element.onplaying : function (arg) {};
+ if (isIE) { // on IE the event needs to be plugged manually
+ newElement.attachEvent('onplaying', newElement.onplaying);
+ newElement.onclick = (element.onclick) ? element.onclick : function (arg) {};
+ newElement._TemOnClick = function (id) {
+ var arg = {
+ srcElement : document.getElementById(id)
+ };
+ newElement.onclick(arg);
+ };
+ }
+ return newElement;
+ } else {
+ return element;
+ }
+ };
+
+ reattachMediaStream = function (to, from) {
+ var stream = null;
+ var children = from.children;
+ for (var i = 0; i !== children.length; ++i) {
+ if (children[i].name === 'streamId') {
+ AdapterJS.WebRTCPlugin.WaitForPluginReady();
+ stream = AdapterJS.WebRTCPlugin.plugin
+ .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, children[i].value);
+ break;
+ }
+ }
+ if (stream !== null) {
+ return attachMediaStream(to, stream);
+ } else {
+ console.log('Could not find the stream associated with this element');
+ }
+ };
+
+ RTCIceCandidate = function (candidate) {
+ if (!candidate.sdpMid) {
+ candidate.sdpMid = '';
+ }
+
+ AdapterJS.WebRTCPlugin.WaitForPluginReady();
+ return AdapterJS.WebRTCPlugin.plugin.ConstructIceCandidate(
+ candidate.sdpMid, candidate.sdpMLineIndex, candidate.candidate
+ );
+ };
+
+ // inject plugin
+ AdapterJS.addEvent(document, 'readystatechange', AdapterJS.WebRTCPlugin.injectPlugin);
+ AdapterJS.WebRTCPlugin.injectPlugin();
+ };
+
+ // This function will be called if the plugin is needed (browser different
+ // from Chrome or Firefox), but the plugin is not installed.
+ AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb = AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb ||
+ function() {
+ AdapterJS.addEvent(document,
+ 'readystatechange',
+ AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv);
+ AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv();
+ };
+
+ AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv = function () {
+ if (AdapterJS.options.hidePluginInstallPrompt) {
+ return;
+ }
+
+ var downloadLink = AdapterJS.WebRTCPlugin.pluginInfo.downloadLink;
+ if(downloadLink) { // if download link
+ var popupString;
+ if (AdapterJS.WebRTCPlugin.pluginInfo.portalLink) { // is portal link
+ popupString = 'This website requires you to install the ' +
+ ' ' + AdapterJS.WebRTCPlugin.pluginInfo.companyName +
+ ' WebRTC Plugin' +
+ ' to work on this browser.';
+ } else { // no portal link, just print a generic explanation
+ popupString = AdapterJS.TEXT.PLUGIN.REQUIRE_INSTALLATION;
+ }
+
+ AdapterJS.renderNotificationBar(popupString, AdapterJS.TEXT.PLUGIN.BUTTON, downloadLink);
+ } else { // no download link, just print a generic explanation
+ AdapterJS.renderNotificationBar(AdapterJS.TEXT.PLUGIN.NOT_SUPPORTED);
+ }
+ };
+
+ // Try to detect the plugin and act accordingly
+ AdapterJS.WebRTCPlugin.isPluginInstalled(
+ AdapterJS.WebRTCPlugin.pluginInfo.prefix,
+ AdapterJS.WebRTCPlugin.pluginInfo.plugName,
+ AdapterJS.WebRTCPlugin.defineWebRTCInterface,
+ AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb);
+}
+
+
+
+(function () {
+
+ 'use strict';
+
+ var baseGetUserMedia = null;
+
+ AdapterJS.TEXT.EXTENSION = {
+ REQUIRE_INSTALLATION_FF: 'To enable screensharing you need to install the Skylink WebRTC tools Firefox Add-on.',
+ REQUIRE_INSTALLATION_CHROME: 'To enable screensharing you need to install the Skylink WebRTC tools Chrome Extension.',
+ REQUIRE_REFRESH: 'Please refresh this page after the Skylink WebRTC tools extension has been installed.',
+ BUTTON_FF: 'Install Now',
+ BUTTON_CHROME: 'Go to Chrome Web Store'
+ };
+
+ var clone = function(obj) {
+ if (null == obj || "object" != typeof obj) return obj;
+ var copy = obj.constructor();
+ for (var attr in obj) {
+ if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
+ }
+ return copy;
+ };
+
+ if (window.navigator.mozGetUserMedia) {
+ baseGetUserMedia = window.navigator.getUserMedia;
+
+ navigator.getUserMedia = function (constraints, successCb, failureCb) {
+
+ if (constraints && constraints.video && !!constraints.video.mediaSource) {
+ // intercepting screensharing requests
+
+ if (constraints.video.mediaSource !== 'screen' && constraints.video.mediaSource !== 'window') {
+ throw new Error('Only "screen" and "window" option is available as mediaSource');
+ }
+
+ var updatedConstraints = clone(constraints);
+
+ //constraints.video.mediaSource = constraints.video.mediaSource;
+ updatedConstraints.video.mozMediaSource = updatedConstraints.video.mediaSource;
+
+ // so generally, it requires for document.readyState to be completed before the getUserMedia could be invoked.
+ // strange but this works anyway
+ var checkIfReady = setInterval(function () {
+ if (document.readyState === 'complete') {
+ clearInterval(checkIfReady);
+
+ baseGetUserMedia(updatedConstraints, successCb, function (error) {
+ if (error.name === 'PermissionDeniedError' && window.parent.location.protocol === 'https:') {
+ AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_FF,
+ AdapterJS.TEXT.EXTENSION.BUTTON_FF,
+ 'http://skylink.io/screensharing/ff_addon.php?domain=' + window.location.hostname, false, true);
+ //window.location.href = 'http://skylink.io/screensharing/ff_addon.php?domain=' + window.location.hostname;
+ } else {
+ failureCb(error);
+ }
+ });
+ }
+ }, 1);
+
+ } else { // regular GetUserMediaRequest
+ baseGetUserMedia(constraints, successCb, failureCb);
+ }
+ };
+
+ getUserMedia = navigator.getUserMedia;
+
+ } else if (window.navigator.webkitGetUserMedia) {
+ baseGetUserMedia = window.navigator.getUserMedia;
+
+ navigator.getUserMedia = function (constraints, successCb, failureCb) {
+
+ if (constraints && constraints.video && !!constraints.video.mediaSource) {
+ if (window.webrtcDetectedBrowser !== 'chrome') {
+ throw new Error('Current browser does not support screensharing');
+ }
+
+ // would be fine since no methods
+ var updatedConstraints = clone(constraints);
+
+ var chromeCallback = function(error, sourceId) {
+ if(!error) {
+ updatedConstraints.video.mandatory = updatedConstraints.video.mandatory || {};
+ updatedConstraints.video.mandatory.chromeMediaSource = 'desktop';
+ updatedConstraints.video.mandatory.maxWidth = window.screen.width > 1920 ? window.screen.width : 1920;
+ updatedConstraints.video.mandatory.maxHeight = window.screen.height > 1080 ? window.screen.height : 1080;
+
+ if (sourceId) {
+ updatedConstraints.video.mandatory.chromeMediaSourceId = sourceId;
+ }
+
+ delete updatedConstraints.video.mediaSource;
+
+ baseGetUserMedia(updatedConstraints, successCb, failureCb);
+
+ } else {
+ if (error === 'permission-denied') {
+ throw new Error('Permission denied for screen retrieval');
+ } else {
+ throw new Error('Failed retrieving selected screen');
+ }
+ }
+ };
+
+ var onIFrameCallback = function (event) {
+ if (!event.data) {
+ return;
+ }
+
+ if (event.data.chromeMediaSourceId) {
+ if (event.data.chromeMediaSourceId === 'PermissionDeniedError') {
+ chromeCallback('permission-denied');
+ } else {
+ chromeCallback(null, event.data.chromeMediaSourceId);
+ }
+ }
+
+ if (event.data.chromeExtensionStatus) {
+ if (event.data.chromeExtensionStatus === 'not-installed') {
+ AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_CHROME,
+ AdapterJS.TEXT.EXTENSION.BUTTON_CHROME,
+ event.data.data, true, true);
+ } else {
+ chromeCallback(event.data.chromeExtensionStatus, null);
+ }
+ }
+
+ // this event listener is no more needed
+ window.removeEventListener('message', onIFrameCallback);
+ };
+
+ window.addEventListener('message', onIFrameCallback);
+
+ postFrameMessage({
+ captureSourceId: true
+ });
+
+ } else {
+ baseGetUserMedia(constraints, successCb, failureCb);
+ }
+ };
+
+ getUserMedia = navigator.getUserMedia;
+
+ } else {
+ baseGetUserMedia = window.navigator.getUserMedia;
+
+ navigator.getUserMedia = function (constraints, successCb, failureCb) {
+ if (constraints && constraints.video && !!constraints.video.mediaSource) {
+ // would be fine since no methods
+ var updatedConstraints = clone(constraints);
+
+ // wait for plugin to be ready
+ AdapterJS.WebRTCPlugin.callWhenPluginReady(function() {
+ // check if screensharing feature is available
+ if (!!AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature &&
+ !!AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable) {
+
+
+ // set the constraints
+ updatedConstraints.video.optional = updatedConstraints.video.optional || [];
+ updatedConstraints.video.optional.push({
+ sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey || 'Screensharing'
+ });
+
+ delete updatedConstraints.video.mediaSource;
+ } else {
+ throw new Error('Your WebRTC plugin does not support screensharing');
+ }
+ baseGetUserMedia(updatedConstraints, successCb, failureCb);
+ });
+ } else {
+ baseGetUserMedia(constraints, successCb, failureCb);
+ }
+ };
+
+ getUserMedia = window.navigator.getUserMedia;
+ }
+
+ if (window.webrtcDetectedBrowser === 'chrome') {
+ var iframe = document.createElement('iframe');
+
+ iframe.onload = function() {
+ iframe.isLoaded = true;
+ };
+
+ iframe.src = 'https://cdn.temasys.com.sg/skylink/extensions/detectRTC.html';
+ //'https://temasys-cdn.s3.amazonaws.com/skylink/extensions/detection-script-dev/detectRTC.html';
+ iframe.style.display = 'none';
+
+ (document.body || document.documentElement).appendChild(iframe);
+
+ var postFrameMessage = function (object) {
+ object = object || {};
+
+ if (!iframe.isLoaded) {
+ setTimeout(function () {
+ iframe.contentWindow.postMessage(object, '*');
+ }, 100);
+ return;
+ }
+
+ iframe.contentWindow.postMessage(object, '*');
+ };
+ }
+})();
+},{}],11:[function(require,module,exports){
+var UI = {};
+
+var VideoLayout = require("./videolayout/VideoLayout.js");
+var AudioLevels = require("./audio_levels/AudioLevels.js");
+var Prezi = require("./prezi/Prezi.js");
+var Etherpad = require("./etherpad/Etherpad.js");
+var Chat = require("./side_pannels/chat/Chat.js");
+var Toolbar = require("./toolbars/Toolbar");
+var ToolbarToggler = require("./toolbars/ToolbarToggler");
+var BottomToolbar = require("./toolbars/BottomToolbar");
+var ContactList = require("./side_pannels/contactlist/ContactList");
+var Avatar = require("./avatar/Avatar");
+var EventEmitter = require("events");
+var SettingsMenu = require("./side_pannels/settings/SettingsMenu");
+var Settings = require("./../settings/Settings");
+var PanelToggler = require("./side_pannels/SidePanelToggler");
+var RoomNameGenerator = require("./welcome_page/RoomnameGenerator");
+UI.messageHandler = require("./util/MessageHandler");
+var messageHandler = UI.messageHandler;
+var Authentication = require("./authentication/Authentication");
+var UIUtil = require("./util/UIUtil");
+var NicknameHandler = require("./util/NicknameHandler");
+var CQEvents = require("../../service/connectionquality/CQEvents");
+var DesktopSharingEventTypes
+ = require("../../service/desktopsharing/DesktopSharingEventTypes");
+var RTCEvents = require("../../service/RTC/RTCEvents");
+var RTCBrowserType = require("../RTC/RTCBrowserType");
+var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+var UIEvents = require("../../service/UI/UIEvents");
+var MemberEvents = require("../../service/members/Events");
+
+var eventEmitter = new EventEmitter();
+var roomName = null;
+
+
+function promptDisplayName() {
+ var message = '';
+ message += APP.translation.translateString(
+ "dialog.displayNameRequired");
+ message += '
' +
+ '';
+
+ var buttonTxt
+ = APP.translation.generateTranslationHTML("dialog.Ok");
+ var buttons = [];
+ buttons.push({title: buttonTxt, value: "ok"});
+
+ messageHandler.openDialog(null, message,
+ true,
+ buttons,
+ function (e, v, m, f) {
+ if (v == "ok") {
+ var displayName = f.displayName;
+ if (displayName) {
+ VideoLayout.inputDisplayNameHandler(displayName);
+ return true;
+ }
+ }
+ e.preventDefault();
+ },
+ function () {
+ var form = $.prompt.getPrompt();
+ var input = form.find("input[name='displayName']");
+ input.focus();
+ var button = form.find("button");
+ button.attr("disabled", "disabled");
+ input.keyup(function () {
+ if(!input.val())
+ button.attr("disabled", "disabled");
+ else
+ button.removeAttr("disabled");
+ });
+ }
+ );
+}
+
+
+function notifyForInitialMute()
+{
+ messageHandler.notify(null, "notify.mutedTitle", "connected",
+ "notify.muted", null, {timeOut: 120000});
+}
+
+function setupPrezi()
+{
+ $("#reloadPresentationLink").click(function()
+ {
+ Prezi.reloadPresentation();
+ });
+}
+
+function setupChat()
+{
+ Chat.init();
+ $("#toggle_smileys").click(function() {
+ Chat.toggleSmileys();
+ });
+}
+
+function setupToolbars() {
+ Toolbar.init(UI);
+ Toolbar.setupButtonsFromConfig();
+ BottomToolbar.init();
+}
+
+function streamHandler(stream, isMuted) {
+ switch (stream.type)
+ {
+ case "audio":
+ VideoLayout.changeLocalAudio(stream, isMuted);
+ break;
+ case "video":
+ VideoLayout.changeLocalVideo(stream, isMuted);
+ break;
+ case "stream":
+ VideoLayout.changeLocalStream(stream, isMuted);
+ break;
+ }
+}
+
+function onXmppConnectionFailed(stropheErrorMsg) {
+
+ var title = APP.translation.generateTranslationHTML(
+ "dialog.error");
+
+ var message;
+ if (stropheErrorMsg) {
+ message = APP.translation.generateTranslationHTML(
+ "dialog.connectErrorWithMsg", {msg: stropheErrorMsg});
+ } else {
+ message = APP.translation.generateTranslationHTML(
+ "dialog.connectError");
+ }
+
+ messageHandler.openDialog(
+ title, message, true, {}, function (e, v, m, f) { return false; });
+}
+
+function onDisposeConference(unload) {
+ Toolbar.showAuthenticateButton(false);
+}
+
+function onDisplayNameChanged(jid, displayName) {
+ ContactList.onDisplayNameChange(jid, displayName);
+ SettingsMenu.onDisplayNameChange(jid, displayName);
+ VideoLayout.onDisplayNameChanged(jid, displayName);
+}
+
+function registerListeners() {
+ APP.RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
+
+ APP.RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED);
+ APP.RTC.addStreamListener(function (stream) {
+ VideoLayout.onRemoteStreamAdded(stream);
+ }, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED);
+ APP.RTC.addStreamListener(function (jid) {
+ VideoLayout.onVideoTypeChanged(jid);
+ }, StreamEventTypes.EVENT_TYPE_REMOTE_CHANGED);
+ APP.RTC.addListener(RTCEvents.LASTN_CHANGED, onLastNChanged);
+ APP.RTC.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED, function (resourceJid) {
+ VideoLayout.onDominantSpeakerChanged(resourceJid);
+ });
+ APP.RTC.addListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
+ function (lastNEndpoints, endpointsEnteringLastN, stream) {
+ VideoLayout.onLastNEndpointsChanged(lastNEndpoints,
+ endpointsEnteringLastN, stream);
+ });
+ APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED,
+ function (devices) {
+ VideoLayout.setDeviceAvailabilityIcons(null, devices);
+ });
+ APP.RTC.addListener(RTCEvents.VIDEO_MUTE, UI.setVideoMuteButtonsState);
+ APP.RTC.addListener(RTCEvents.DATA_CHANNEL_OPEN, function() {
+ // when the data channel becomes available, tell the bridge about video
+ // selections so that it can do adaptive simulcast,
+ // we want the notification to trigger even if userJid is undefined,
+ // or null.
+ var userJid = APP.UI.getLargeVideoJid();
+ eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, userJid);
+ });
+ APP.statistics.addAudioLevelListener(function(jid, audioLevel)
+ {
+ var resourceJid;
+ if(jid === APP.statistics.LOCAL_JID)
+ {
+ resourceJid = AudioLevels.LOCAL_LEVEL;
+ if(APP.RTC.localAudio.isMuted())
+ {
+ audioLevel = 0;
+ }
+ }
+ else
+ {
+ resourceJid = Strophe.getResourceFromJid(jid);
+ }
+
+ AudioLevels.updateAudioLevel(resourceJid, audioLevel,
+ UI.getLargeVideoJid());
+ });
+ APP.desktopsharing.addListener(function () {
+ ToolbarToggler.showDesktopSharingButton();
+ }, DesktopSharingEventTypes.INIT);
+ APP.desktopsharing.addListener(
+ Toolbar.changeDesktopSharingButtonState,
+ DesktopSharingEventTypes.SWITCHING_DONE);
+ APP.connectionquality.addListener(CQEvents.LOCALSTATS_UPDATED,
+ VideoLayout.updateLocalConnectionStats);
+ APP.connectionquality.addListener(CQEvents.REMOTESTATS_UPDATED,
+ VideoLayout.updateConnectionStats);
+ APP.connectionquality.addListener(CQEvents.STOP,
+ VideoLayout.onStatsStop);
+ APP.xmpp.addListener(XMPPEvents.CONNECTION_FAILED, onXmppConnectionFailed);
+ APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
+ APP.xmpp.addListener(XMPPEvents.GRACEFUL_SHUTDOWN, function () {
+ messageHandler.openMessageDialog(
+ 'dialog.serviceUnavailable',
+ 'dialog.gracefulShutdown'
+ );
+ });
+ APP.xmpp.addListener(XMPPEvents.RESERVATION_ERROR, function (code, msg) {
+ var title = APP.translation.generateTranslationHTML(
+ "dialog.reservationError");
+ var message = APP.translation.generateTranslationHTML(
+ "dialog.reservationErrorMsg", {code: code, msg: msg});
+ messageHandler.openDialog(
+ title,
+ message,
+ true, {},
+ function (event, value, message, formVals)
+ {
+ return false;
+ }
+ );
+ });
+ APP.xmpp.addListener(XMPPEvents.KICKED, function () {
+ messageHandler.openMessageDialog("dialog.sessTerminated",
+ "dialog.kickMessage");
+ });
+ APP.xmpp.addListener(XMPPEvents.MUC_DESTROYED, function (reason) {
+ //FIXME: use Session Terminated from translation, but
+ // 'reason' text comes from XMPP packet and is not translated
+ var title = APP.translation.generateTranslationHTML("dialog.sessTerminated");
+ messageHandler.openDialog(
+ title, reason, true, {},
+ function (event, value, message, formVals)
+ {
+ return false;
+ }
+ );
+ });
+ APP.xmpp.addListener(XMPPEvents.BRIDGE_DOWN, function () {
+ messageHandler.showError("dialog.error",
+ "dialog.bridgeUnavailable");
+ });
+ APP.xmpp.addListener(XMPPEvents.USER_ID_CHANGED, function (from, id) {
+ Avatar.setUserAvatar(from, id);
+ });
+ APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, onDisplayNameChanged);
+ APP.xmpp.addListener(XMPPEvents.MUC_JOINED, onMucJoined);
+ APP.xmpp.addListener(XMPPEvents.LOCAL_ROLE_CHANGED, onLocalRoleChanged);
+ APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, onMucMemberJoined);
+ APP.xmpp.addListener(XMPPEvents.MUC_ROLE_CHANGED, onMucRoleChanged);
+ APP.xmpp.addListener(XMPPEvents.PRESENCE_STATUS, onMucPresenceStatus);
+ APP.xmpp.addListener(XMPPEvents.SUBJECT_CHANGED, chatSetSubject);
+ APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation);
+ APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, onMucMemberLeft);
+ APP.xmpp.addListener(XMPPEvents.PASSWORD_REQUIRED, onPasswordRequired);
+ APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError);
+ APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad);
+ APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED,
+ onAuthenticationRequired);
+ APP.xmpp.addListener(XMPPEvents.DEVICE_AVAILABLE,
+ function (resource, devices) {
+ VideoLayout.setDeviceAvailabilityIcons(resource, devices);
+ });
+
+ APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED, VideoLayout.onAudioMute);
+ APP.xmpp.addListener(XMPPEvents.VIDEO_MUTED, VideoLayout.onVideoMute);
+ APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS, function(doMuteAudio) {
+ UI.setAudioMuted(doMuteAudio);
+ });
+ APP.members.addListener(MemberEvents.DTMF_SUPPORT_CHANGED,
+ onDtmfSupportChanged);
+ APP.xmpp.addListener(XMPPEvents.START_MUTED_SETTING_CHANGED, function (audio, video) {
+ SettingsMenu.setStartMuted(audio, video);
+ });
+ APP.xmpp.addListener(XMPPEvents.START_MUTED_FROM_FOCUS, function (audio, video) {
+ UI.setInitialMuteFromFocus(audio, video);
+ });
+
+ APP.xmpp.addListener(XMPPEvents.JINGLE_FATAL_ERROR, function (session, error) {
+ UI.messageHandler.showError("dialog.sorry",
+ "dialog.internalError");
+ });
+
+ APP.xmpp.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR, function() {
+ messageHandler.showError("dialog.error",
+ "dialog.SLDFailure");
+ });
+ APP.xmpp.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR, function() {
+ messageHandler.showError("dialog.error",
+ "dialog.SRDFailure");
+ });
+ APP.xmpp.addListener(XMPPEvents.CREATE_ANSWER_ERROR, function() {
+ messageHandler.showError();
+ });
+ APP.xmpp.addListener(XMPPEvents.PROMPT_FOR_LOGIN, function() {
+ // FIXME: re-use LoginDialog which supports retries
+ UI.showLoginPopup(connect);
+ });
+
+ APP.xmpp.addListener(XMPPEvents.FOCUS_DISCONNECTED, function(focusComponent, retrySec) {
+ UI.messageHandler.notify(
+ null, "notify.focus",
+ 'disconnected', "notify.focusFail",
+ {component: focusComponent, ms: retrySec});
+ });
+
+ APP.xmpp.addListener(XMPPEvents.ROOM_JOIN_ERROR, function(pres) {
+ UI.messageHandler.openReportDialog(null,
+ "dialog.joinError", pres);
+ });
+ APP.xmpp.addListener(XMPPEvents.ROOM_CONNECT_ERROR, function(pres) {
+ UI.messageHandler.openReportDialog(null,
+ "dialog.connectError", pres);
+ });
+
+ APP.xmpp.addListener(XMPPEvents.READY_TO_JOIN, function() {
+ var roomName = UI.generateRoomName();
+ APP.xmpp.allocateConferenceFocus(roomName, UI.checkForNicknameAndJoin);
+ });
+
+ //NicknameHandler emits this event
+ UI.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) {
+ APP.xmpp.addToPresence("displayName", nickname);
+ });
+
+ UI.addListener(UIEvents.LARGEVIDEO_INIT, function () {
+ AudioLevels.init();
+ });
+}
+
+
+/**
+ * Mutes/unmutes the local video.
+ *
+ * @param mute true to mute the local video; otherwise, false
+ * @param options an object which specifies optional arguments such as the
+ * boolean key byUser with default value true which
+ * specifies whether the method was initiated in response to a user command (in
+ * contrast to an automatic decision taken by the application logic)
+ */
+function setVideoMute(mute, options) {
+ APP.RTC.setVideoMute(mute,
+ UI.setVideoMuteButtonsState,
+ options);
+}
+
+function onResize()
+{
+ Chat.resizeChat();
+ VideoLayout.resizeLargeVideoContainer();
+}
+
+function bindEvents()
+{
+ /**
+ * Resizes and repositions videos in full screen mode.
+ */
+ $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange',
+ onResize);
+
+ $(window).resize(onResize);
+}
+
+UI.start = function (init) {
+ document.title = interfaceConfig.APP_NAME;
+ if(config.enableWelcomePage && window.location.pathname == "/" &&
+ (!window.localStorage.welcomePageDisabled || window.localStorage.welcomePageDisabled == "false"))
+ {
+ $("#videoconference_page").hide();
+ var setupWelcomePage = require("./welcome_page/WelcomePage");
+ setupWelcomePage();
+
+ return;
+ }
+
+
+ $("#welcome_page").hide();
+
+ $("#videospace").mousemove(function () {
+ return ToolbarToggler.showToolbar();
+ });
+ // Set the defaults for prompt dialogs.
+ jQuery.prompt.setDefaults({persistent: false});
+
+
+ registerListeners();
+ VideoLayout.init(eventEmitter);
+ NicknameHandler.init(eventEmitter);
+ bindEvents();
+ setupPrezi();
+ setupToolbars();
+ setupChat();
+
+
+ document.title = interfaceConfig.APP_NAME;
+
+ $("#downloadlog").click(function (event) {
+ dump(event.target);
+ });
+
+ if(config.enableWelcomePage && window.location.pathname == "/" &&
+ (!window.localStorage.welcomePageDisabled || window.localStorage.welcomePageDisabled == "false"))
+ {
+ $("#videoconference_page").hide();
+ var setupWelcomePage = require("./welcome_page/WelcomePage");
+ setupWelcomePage();
+
+ return;
+ }
+
+ $("#welcome_page").hide();
+
+ // Display notice message at the top of the toolbar
+ if (config.noticeMessage) {
+ $('#noticeText').text(config.noticeMessage);
+ $('#notice').css({display: 'block'});
+ }
+
+ if(config.requireDisplayName) {
+ var currentSettings = Settings.getSettings();
+ if (!currentSettings.displayName) {
+ promptDisplayName();
+ }
+ }
+
+ init();
+
+ toastr.options = {
+ "closeButton": true,
+ "debug": false,
+ "positionClass": "notification-bottom-right",
+ "onclick": null,
+ "showDuration": "300",
+ "hideDuration": "1000",
+ "timeOut": "2000",
+ "extendedTimeOut": "1000",
+ "showEasing": "swing",
+ "hideEasing": "linear",
+ "showMethod": "fadeIn",
+ "hideMethod": "fadeOut",
+ "reposition": function() {
+ if(PanelToggler.isVisible()) {
+ $("#toast-container").addClass("notification-bottom-right-center");
+ } else {
+ $("#toast-container").removeClass("notification-bottom-right-center");
+ }
+ },
+ "newestOnTop": false
+ };
+
+ SettingsMenu.init();
+
+};
+
+function chatAddError(errorMessage, originalText)
+{
+ return Chat.chatAddError(errorMessage, originalText);
+};
+
+function chatSetSubject(text)
+{
+ return Chat.chatSetSubject(text);
+};
+
+function updateChatConversation(from, displayName, message, myjid, stamp) {
+ return Chat.updateChatConversation(from, displayName, message, myjid, stamp);
+}
+
+function onMucJoined(jid, info) {
+ Toolbar.updateRoomUrl(window.location.href);
+ var meHTML = APP.translation.generateTranslationHTML("me");
+ $("#localNick").html(Strophe.getResourceFromJid(jid) + " (" + meHTML + ")");
+
+ var settings = Settings.getSettings();
+
+ // Make sure we configure our avatar id, before creating avatar for us
+ Avatar.setUserAvatar(jid, settings.email || settings.uid);
+
+ // Add myself to the contact list.
+ ContactList.addContact(jid);
+
+ // Once we've joined the muc show the toolbar
+ ToolbarToggler.showToolbar();
+
+ var displayName = !config.displayJids
+ ? info.displayName : Strophe.getResourceFromJid(jid);
+
+ if (displayName)
+ onDisplayNameChanged('localVideoContainer', displayName);
+
+
+ VideoLayout.mucJoined();
+}
+
+function initEtherpad(name) {
+ Etherpad.init(name);
+}
+
+function onMucMemberLeft(jid) {
+ console.log('left.muc', jid);
+ var displayName = $('#participant_' + Strophe.getResourceFromJid(jid) +
+ '>.displayname').html();
+ messageHandler.notify(displayName,'notify.somebody',
+ 'disconnected',
+ 'notify.disconnected');
+ if(!config.startAudioMuted ||
+ config.startAudioMuted > APP.members.size())
+ UIUtil.playSoundNotification('userLeft');
+
+ ContactList.removeContact(jid);
+
+ VideoLayout.participantLeft(jid);
+};
+
+
+function onLocalRoleChanged(jid, info, pres, isModerator)
+{
+
+ console.info("My role changed, new role: " + info.role);
+ onModeratorStatusChanged(isModerator);
+ VideoLayout.showModeratorIndicator();
+ SettingsMenu.onRoleChanged();
+
+ if (isModerator) {
+ Authentication.closeAuthenticationWindow();
+ messageHandler.notify(null, "notify.me",
+ 'connected', "notify.moderator");
+ }
+}
+
+function onModeratorStatusChanged(isModerator) {
+
+ Toolbar.showSipCallButton(isModerator);
+ Toolbar.showRecordingButton(
+ isModerator); //&&
+ // FIXME:
+ // Recording visible if
+ // there are at least 2(+ 1 focus) participants
+ //Object.keys(connection.emuc.members).length >= 3);
+}
+
+function onPasswordRequired(callback) {
+ // password is required
+ Toolbar.lockLockButton();
+ var message = '';
+ message += APP.translation.translateString(
+ "dialog.passwordRequired");
+ message += '
' +
+ '';
+
+ messageHandler.openTwoButtonDialog(null, null, null, message,
+ true,
+ "dialog.Ok",
+ function (e, v, m, f) {},
+ null,
+ function (e, v, m, f) {
+ if (v) {
+ var lockKey = f.lockKey;
+ if (lockKey) {
+ Toolbar.setSharedKey(lockKey);
+ callback(lockKey);
+ }
+ }
+ },
+ ':input:first'
+ );
+}
+
+/**
+ * The dialpad button is shown iff there is at least one member that supports
+ * DTMF (e.g. jigasi).
+ */
+function onDtmfSupportChanged(dtmfSupport) {
+ //TODO: enable when the UI is ready
+ //Toolbar.showDialPadButton(dtmfSupport);
+}
+
+function onMucMemberJoined(jid, id, displayName) {
+ messageHandler.notify(displayName,'notify.somebody',
+ 'connected',
+ 'notify.connected');
+
+ if(!config.startAudioMuted ||
+ config.startAudioMuted > APP.members.size())
+ UIUtil.playSoundNotification('userJoined');
+
+ // Configure avatar
+ Avatar.setUserAvatar(jid, id);
+
+ // Add Peer's container
+ VideoLayout.ensurePeerContainerExists(jid);
+}
+
+function onMucPresenceStatus(jid, info) {
+ VideoLayout.setPresenceStatus(Strophe.getResourceFromJid(jid), info.status);
+}
+
+function onMucRoleChanged(role, displayName) {
+ VideoLayout.showModeratorIndicator();
+
+ if (role === 'moderator') {
+ var messageKey, messageOptions = {};
+ if (!displayName) {
+ messageKey = "notify.grantedToUnknown";
+ }
+ else
+ {
+ messageKey = "notify.grantedTo";
+ messageOptions = {to: displayName};
+ }
+ messageHandler.notify(
+ displayName,'notify.somebody',
+ 'connected', messageKey,
+ messageOptions);
+ }
+}
+
+function onAuthenticationRequired(intervalCallback) {
+ Authentication.openAuthenticationDialog(
+ roomName, intervalCallback, function () {
+ Toolbar.authenticateClicked();
+ });
+};
+
+
+function onLastNChanged(oldValue, newValue) {
+ if (config.muteLocalVideoIfNotInLastN) {
+ setVideoMute(!newValue, { 'byUser': false });
+ }
+}
+
+
+UI.toggleSmileys = function () {
+ Chat.toggleSmileys();
+};
+
+UI.getSettings = function () {
+ return Settings.getSettings();
+};
+
+UI.toggleFilmStrip = function () {
+ return BottomToolbar.toggleFilmStrip();
+};
+
+UI.toggleChat = function () {
+ return BottomToolbar.toggleChat();
+};
+
+UI.toggleContactList = function () {
+ return BottomToolbar.toggleContactList();
+};
+
+UI.inputDisplayNameHandler = function (value) {
+ VideoLayout.inputDisplayNameHandler(value);
+};
+
+
+UI.getLargeVideoJid = function()
+{
+ return VideoLayout.getLargeVideoJid();
+};
+
+UI.generateRoomName = function() {
+ if(roomName)
+ return roomName;
+ var roomnode = null;
+ var path = window.location.pathname;
+
+ // determinde the room node from the url
+ // TODO: just the roomnode or the whole bare jid?
+ if (config.getroomnode && typeof config.getroomnode === 'function') {
+ // custom function might be responsible for doing the pushstate
+ roomnode = config.getroomnode(path);
+ } else {
+ /* fall back to default strategy
+ * this is making assumptions about how the URL->room mapping happens.
+ * It currently assumes deployment at root, with a rewrite like the
+ * following one (for nginx):
+ location ~ ^/([a-zA-Z0-9]+)$ {
+ rewrite ^/(.*)$ / break;
+ }
+ */
+ if (path.length > 1) {
+ roomnode = path.substr(1).toLowerCase();
+ } else {
+ var word = RoomNameGenerator.generateRoomWithoutSeparator();
+ roomnode = word.toLowerCase();
+
+ window.history.pushState('VideoChat',
+ 'Room: ' + word, window.location.pathname + word);
+ }
+ }
+
+ roomName = roomnode + '@' + config.hosts.muc;
+ return roomName;
+};
+
+
+UI.connectionIndicatorShowMore = function(jid)
+{
+ return VideoLayout.showMore(jid);
+};
+
+UI.showLoginPopup = function(callback)
+{
+ console.log('password is required');
+ var message = '';
+ message += APP.translation.translateString(
+ "dialog.passwordRequired");
+ message += '
' +
+ '' +
+ '';
+ UI.messageHandler.openTwoButtonDialog(null, null, null, message,
+ true,
+ "dialog.Ok",
+ function (e, v, m, f) {
+ if (v) {
+ if (f.username !== null && f.password != null) {
+ callback(f.username, f.password);
+ }
+ }
+ },
+ null, null, ':input:first'
+
+ );
+}
+
+UI.checkForNicknameAndJoin = function () {
+
+ Authentication.closeAuthenticationDialog();
+ Authentication.stopInterval();
+
+ var nick = null;
+ if (config.useNicks) {
+ nick = window.prompt('Your nickname (optional)');
+ }
+ APP.xmpp.joinRoom(roomName, config.useNicks, nick);
+};
+
+
+function dump(elem, filename) {
+ elem = elem.parentNode;
+ elem.download = filename || 'meetlog.json';
+ elem.href = 'data:application/json;charset=utf-8,\n';
+ var data = APP.xmpp.populateData();
+ var metadata = {};
+ metadata.time = new Date();
+ metadata.url = window.location.href;
+ metadata.ua = navigator.userAgent;
+ var log = APP.xmpp.getLogger();
+ if (log) {
+ metadata.xmpp = log;
+ }
+ data.metadata = metadata;
+ elem.href += encodeURIComponent(JSON.stringify(data, null, ' '));
+ return false;
+}
+
+UI.getRoomName = function () {
+ return roomName;
+};
+
+UI.setInitialMuteFromFocus = function (muteAudio, muteVideo) {
+ if(muteAudio || muteVideo) notifyForInitialMute();
+ if(muteAudio) UI.setAudioMuted(true);
+ if(muteVideo) UI.setVideoMute(true);
+}
+
+/**
+ * Mutes/unmutes the local video.
+ */
+UI.toggleVideo = function () {
+ setVideoMute(!APP.RTC.localVideo.isMuted());
+};
+
+/**
+ * Mutes / unmutes audio for the local participant.
+ */
+UI.toggleAudio = function() {
+ UI.setAudioMuted(!APP.RTC.localAudio.isMuted());
+};
+
+/**
+ * Sets muted audio state for the local participant.
+ */
+UI.setAudioMuted = function (mute, earlyMute) {
+ var audioMute = null;
+ if(earlyMute)
+ audioMute = function (mute, cb) {
+ return APP.xmpp.sendAudioInfoPresence(mute, cb);
+ };
+ else
+ audioMute = function (mute, cb) {
+ return APP.xmpp.setAudioMute(mute, cb);
+ }
+ if(!audioMute(mute, function () {
+ VideoLayout.showLocalAudioIndicator(mute);
+
+ UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
+ }))
+ {
+ // We still click the button.
+ UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
+ return;
+ }
+
+}
+
+UI.addListener = function (type, listener) {
+ eventEmitter.on(type, listener);
+}
+
+UI.clickOnVideo = function (videoNumber) {
+ var remoteVideos = $(".videocontainer:not(#mixedstream)");
+ if (remoteVideos.length > videoNumber) {
+ remoteVideos[videoNumber].click();
+ }
+}
+
+//Used by torture
+UI.showToolbar = function () {
+ return ToolbarToggler.showToolbar();
+}
+
+//Used by torture
+UI.dockToolbar = function (isDock) {
+ return ToolbarToggler.dockToolbar(isDock);
+}
+
+UI.setVideoMuteButtonsState = function (mute) {
+ var video = $('#video');
+ var communicativeClass = "icon-camera";
+ var muteClass = "icon-camera icon-camera-disabled";
+
+ if (mute) {
+ video.removeClass(communicativeClass);
+ video.addClass(muteClass);
+ } else {
+ video.removeClass(muteClass);
+ video.addClass(communicativeClass);
+ }
+}
+
+UI.userAvatarChanged = function (resourceJid, thumbUrl, contactListUrl) {
+ VideoLayout.userAvatarChanged(resourceJid, thumbUrl);
+ ContactList.userAvatarChanged(resourceJid, contactListUrl);
+ if(resourceJid === APP.xmpp.myResource())
+ SettingsMenu.changeAvatar(thumbUrl);
+}
+
+UI.setVideoMute = setVideoMute;
+
+module.exports = UI;
+
+
+},{"../../service/RTC/RTCEvents":103,"../../service/RTC/StreamEventTypes":105,"../../service/UI/UIEvents":106,"../../service/connectionquality/CQEvents":108,"../../service/desktopsharing/DesktopSharingEventTypes":109,"../../service/members/Events":110,"../../service/xmpp/XMPPEvents":112,"../RTC/RTCBrowserType":8,"./../settings/Settings":47,"./audio_levels/AudioLevels.js":12,"./authentication/Authentication":14,"./avatar/Avatar":16,"./etherpad/Etherpad.js":17,"./prezi/Prezi.js":18,"./side_pannels/SidePanelToggler":20,"./side_pannels/chat/Chat.js":21,"./side_pannels/contactlist/ContactList":25,"./side_pannels/settings/SettingsMenu":26,"./toolbars/BottomToolbar":27,"./toolbars/Toolbar":28,"./toolbars/ToolbarToggler":29,"./util/MessageHandler":31,"./util/NicknameHandler":32,"./util/UIUtil":33,"./videolayout/VideoLayout.js":39,"./welcome_page/RoomnameGenerator":40,"./welcome_page/WelcomePage":41,"events":113}],12:[function(require,module,exports){
+var CanvasUtil = require("./CanvasUtils");
+
+var ASDrawContext = null;
+
+function initActiveSpeakerAudioLevels() {
+ var ASRadius = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2;
+ var ASCenter = (interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE + ASRadius) / 2;
+
+// Draw a circle.
+ ASDrawContext.arc(ASCenter, ASCenter, ASRadius, 0, 2 * Math.PI);
+
+// Add a shadow around the circle
+ ASDrawContext.shadowColor = interfaceConfig.SHADOW_COLOR;
+ ASDrawContext.shadowOffsetX = 0;
+ ASDrawContext.shadowOffsetY = 0;
+}
+
+/**
+ * The audio Levels plugin.
+ */
+var AudioLevels = (function(my) {
+ var audioLevelCanvasCache = {};
+
+ my.LOCAL_LEVEL = 'local';
+
+ my.init = function () {
+ ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
+ initActiveSpeakerAudioLevels();
+ }
+
+ /**
+ * Updates the audio level canvas for the given peerJid. If the canvas
+ * didn't exist we create it.
+ */
+ my.updateAudioLevelCanvas = function (peerJid, VideoLayout) {
+ var resourceJid = null;
+ var videoSpanId = null;
+ if (!peerJid)
+ videoSpanId = 'localVideoContainer';
+ else {
+ resourceJid = Strophe.getResourceFromJid(peerJid);
+
+ videoSpanId = 'participant_' + resourceJid;
+ }
+
+ var videoSpan = document.getElementById(videoSpanId);
+
+ if (!videoSpan) {
+ if (resourceJid)
+ console.error("No video element for jid", resourceJid);
+ else
+ console.error("No video element for local video.");
+
+ return;
+ }
+
+ var audioLevelCanvas = $('#' + videoSpanId + '>canvas');
+
+ var videoSpaceWidth = $('#remoteVideos').width();
+ var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
+ var thumbnailWidth = thumbnailSize[0];
+ var thumbnailHeight = thumbnailSize[1];
+
+ if (!audioLevelCanvas || audioLevelCanvas.length === 0) {
+
+ audioLevelCanvas = document.createElement('canvas');
+ audioLevelCanvas.className = "audiolevel";
+ audioLevelCanvas.style.bottom = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px";
+ audioLevelCanvas.style.left = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px";
+ resizeAudioLevelCanvas( audioLevelCanvas,
+ thumbnailWidth,
+ thumbnailHeight);
+
+ videoSpan.appendChild(audioLevelCanvas);
+ } else {
+ audioLevelCanvas = audioLevelCanvas.get(0);
+
+ resizeAudioLevelCanvas( audioLevelCanvas,
+ thumbnailWidth,
+ thumbnailHeight);
+ }
+ };
+
+ /**
+ * Updates the audio level UI for the given resourceJid.
+ *
+ * @param resourceJid the resource jid indicating the video element for
+ * which we draw the audio level
+ * @param audioLevel the newAudio level to render
+ */
+ my.updateAudioLevel = function (resourceJid, audioLevel, largeVideoResourceJid) {
+ drawAudioLevelCanvas(resourceJid, audioLevel);
+
+ var videoSpanId = getVideoSpanId(resourceJid);
+
+ var audioLevelCanvas = $('#' + videoSpanId + '>canvas').get(0);
+
+ if (!audioLevelCanvas)
+ return;
+
+ var drawContext = audioLevelCanvas.getContext('2d');
+
+ var canvasCache = audioLevelCanvasCache[resourceJid];
+
+ drawContext.clearRect (0, 0,
+ audioLevelCanvas.width, audioLevelCanvas.height);
+ drawContext.drawImage(canvasCache, 0, 0);
+
+ if(resourceJid === AudioLevels.LOCAL_LEVEL) {
+ if(!APP.xmpp.myJid()) {
+ return;
+ }
+ resourceJid = APP.xmpp.myResource();
+ }
+
+ if(resourceJid === largeVideoResourceJid) {
+ window.requestAnimationFrame(function () {
+ AudioLevels.updateActiveSpeakerAudioLevel(audioLevel);
+ });
+ }
+ };
+
+ my.updateActiveSpeakerAudioLevel = function(audioLevel) {
+ if($("#activeSpeaker").css("visibility") == "hidden" || ASDrawContext === null)
+ return;
+
+
+ ASDrawContext.clearRect(0, 0, 300, 300);
+ if(audioLevel == 0)
+ return;
+
+ ASDrawContext.shadowBlur = getShadowLevel(audioLevel);
+
+
+ // Fill the shape.
+ ASDrawContext.fill();
+ };
+
+ /**
+ * Resizes the given audio level canvas to match the given thumbnail size.
+ */
+ function resizeAudioLevelCanvas(audioLevelCanvas,
+ thumbnailWidth,
+ thumbnailHeight) {
+ audioLevelCanvas.width = thumbnailWidth + interfaceConfig.CANVAS_EXTRA;
+ audioLevelCanvas.height = thumbnailHeight + interfaceConfig.CANVAS_EXTRA;
+ }
+
+ /**
+ * Draws the audio level canvas into the cached canvas object.
+ *
+ * @param resourceJid the resource jid indicating the video element for
+ * which we draw the audio level
+ * @param audioLevel the newAudio level to render
+ */
+ function drawAudioLevelCanvas(resourceJid, audioLevel) {
+ if (!audioLevelCanvasCache[resourceJid]) {
+
+ var videoSpanId = getVideoSpanId(resourceJid);
+
+ var audioLevelCanvasOrig = $('#' + videoSpanId + '>canvas').get(0);
+
+ /*
+ * FIXME Testing has shown that audioLevelCanvasOrig may not exist.
+ * In such a case, the method CanvasUtil.cloneCanvas may throw an
+ * error. Since audio levels are frequently updated, the errors have
+ * been observed to pile into the console, strain the CPU.
+ */
+ if (audioLevelCanvasOrig)
+ {
+ audioLevelCanvasCache[resourceJid]
+ = CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
+ }
+ }
+
+ var canvas = audioLevelCanvasCache[resourceJid];
+
+ if (!canvas)
+ return;
+
+ var drawContext = canvas.getContext('2d');
+
+ drawContext.clearRect(0, 0, canvas.width, canvas.height);
+
+ var shadowLevel = getShadowLevel(audioLevel);
+
+ if (shadowLevel > 0)
+ // drawContext, x, y, w, h, r, shadowColor, shadowLevel
+ CanvasUtil.drawRoundRectGlow( drawContext,
+ interfaceConfig.CANVAS_EXTRA/2, interfaceConfig.CANVAS_EXTRA/2,
+ canvas.width - interfaceConfig.CANVAS_EXTRA,
+ canvas.height - interfaceConfig.CANVAS_EXTRA,
+ interfaceConfig.CANVAS_RADIUS,
+ interfaceConfig.SHADOW_COLOR,
+ shadowLevel);
+ }
+
+ /**
+ * Returns the shadow/glow level for the given audio level.
+ *
+ * @param audioLevel the audio level from which we determine the shadow
+ * level
+ */
+ function getShadowLevel (audioLevel) {
+ var shadowLevel = 0;
+
+ if (audioLevel <= 0.3) {
+ shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*(audioLevel/0.3));
+ }
+ else if (audioLevel <= 0.6) {
+ shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3));
+ }
+ else {
+ shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4));
+ }
+ return shadowLevel;
+ }
+
+ /**
+ * Returns the video span id corresponding to the given resourceJid or local
+ * user.
+ */
+ function getVideoSpanId(resourceJid) {
+ var videoSpanId = null;
+ if (resourceJid === AudioLevels.LOCAL_LEVEL
+ || (APP.xmpp.myResource() && resourceJid
+ === APP.xmpp.myResource()))
+ videoSpanId = 'localVideoContainer';
+ else
+ videoSpanId = 'participant_' + resourceJid;
+
+ return videoSpanId;
+ }
+
+ /**
+ * Indicates that the remote video has been resized.
+ */
+ $(document).bind('remotevideo.resized', function (event, width, height) {
+ var resized = false;
+ $('#remoteVideos>span>canvas').each(function() {
+ var canvas = $(this).get(0);
+ if (canvas.width !== width + interfaceConfig.CANVAS_EXTRA) {
+ canvas.width = width + interfaceConfig.CANVAS_EXTRA;
+ resized = true;
+ }
+
+ if (canvas.heigh !== height + interfaceConfig.CANVAS_EXTRA) {
+ canvas.height = height + interfaceConfig.CANVAS_EXTRA;
+ resized = true;
+ }
+ });
+
+ if (resized)
+ Object.keys(audioLevelCanvasCache).forEach(function (resourceJid) {
+ audioLevelCanvasCache[resourceJid].width
+ = width + interfaceConfig.CANVAS_EXTRA;
+ audioLevelCanvasCache[resourceJid].height
+ = height + interfaceConfig.CANVAS_EXTRA;
+ });
+ });
+
+ return my;
+
+})(AudioLevels || {});
+
+module.exports = AudioLevels;
+},{"./CanvasUtils":13}],13:[function(require,module,exports){
+/**
+ * Utility class for drawing canvas shapes.
+ */
+var CanvasUtil = (function(my) {
+
+ /**
+ * Draws a round rectangle with a glow. The glowWidth indicates the depth
+ * of the glow.
+ *
+ * @param drawContext the context of the canvas to draw to
+ * @param x the x coordinate of the round rectangle
+ * @param y the y coordinate of the round rectangle
+ * @param w the width of the round rectangle
+ * @param h the height of the round rectangle
+ * @param glowColor the color of the glow
+ * @param glowWidth the width of the glow
+ */
+ my.drawRoundRectGlow
+ = function(drawContext, x, y, w, h, r, glowColor, glowWidth) {
+
+ // Save the previous state of the context.
+ drawContext.save();
+
+ if (w < 2 * r) r = w / 2;
+ if (h < 2 * r) r = h / 2;
+
+ // Draw a round rectangle.
+ drawContext.beginPath();
+ drawContext.moveTo(x+r, y);
+ drawContext.arcTo(x+w, y, x+w, y+h, r);
+ drawContext.arcTo(x+w, y+h, x, y+h, r);
+ drawContext.arcTo(x, y+h, x, y, r);
+ drawContext.arcTo(x, y, x+w, y, r);
+ drawContext.closePath();
+
+ // Add a shadow around the rectangle
+ drawContext.shadowColor = glowColor;
+ drawContext.shadowBlur = glowWidth;
+ drawContext.shadowOffsetX = 0;
+ drawContext.shadowOffsetY = 0;
+
+ // Fill the shape.
+ drawContext.fill();
+
+ drawContext.save();
+
+ drawContext.restore();
+
+// 1) Uncomment this line to use Composite Operation, which is doing the
+// same as the clip function below and is also antialiasing the round
+// border, but is said to be less fast performance wise.
+
+// drawContext.globalCompositeOperation='destination-out';
+
+ drawContext.beginPath();
+ drawContext.moveTo(x+r, y);
+ drawContext.arcTo(x+w, y, x+w, y+h, r);
+ drawContext.arcTo(x+w, y+h, x, y+h, r);
+ drawContext.arcTo(x, y+h, x, y, r);
+ drawContext.arcTo(x, y, x+w, y, r);
+ drawContext.closePath();
+
+// 2) Uncomment this line to use Composite Operation, which is doing the
+// same as the clip function below and is also antialiasing the round
+// border, but is said to be less fast performance wise.
+
+// drawContext.fill();
+
+ // Comment these two lines if choosing to do the same with composite
+ // operation above 1 and 2.
+ drawContext.clip();
+ drawContext.clearRect(0, 0, 277, 200);
+
+ // Restore the previous context state.
+ drawContext.restore();
+ };
+
+ /**
+ * Clones the given canvas.
+ *
+ * @return the new cloned canvas.
+ */
+ my.cloneCanvas = function (oldCanvas) {
+ /*
+ * FIXME Testing has shown that oldCanvas may not exist. In such a case,
+ * the method CanvasUtil.cloneCanvas may throw an error. Since audio
+ * levels are frequently updated, the errors have been observed to pile
+ * into the console, strain the CPU.
+ */
+ if (!oldCanvas)
+ return oldCanvas;
+
+ //create a new canvas
+ var newCanvas = document.createElement('canvas');
+ var context = newCanvas.getContext('2d');
+
+ //set dimensions
+ newCanvas.width = oldCanvas.width;
+ newCanvas.height = oldCanvas.height;
+
+ //apply the old canvas to the new one
+ context.drawImage(oldCanvas, 0, 0);
+
+ //return the new canvas
+ return newCanvas;
+ };
+
+ return my;
+})(CanvasUtil || {});
+
+module.exports = CanvasUtil;
+},{}],14:[function(require,module,exports){
+/* global $, APP*/
+
+var LoginDialog = require('./LoginDialog');
+var Moderator = require('../../xmpp/moderator');
+
+/* Initial "authentication required" dialog */
+var authDialog = null;
+/* Loop retry ID that wits for other user to create the room */
+var authRetryId = null;
+var authenticationWindow = null;
+
+var Authentication = {
+ openAuthenticationDialog: function (roomName, intervalCallback, callback) {
+ // This is the loop that will wait for the room to be created by
+ // someone else. 'auth_required.moderator' will bring us back here.
+ authRetryId = window.setTimeout(intervalCallback, 5000);
+ // Show prompt only if it's not open
+ if (authDialog !== null) {
+ return;
+ }
+ // extract room name from 'room@muc.server.net'
+ var room = roomName.substr(0, roomName.indexOf('@'));
+
+ var title
+ = APP.translation.generateTranslationHTML("dialog.WaitingForHost");
+ var msg
+ = APP.translation.generateTranslationHTML(
+ "dialog.WaitForHostMsg", {room: room});
+
+ var buttonTxt
+ = APP.translation.generateTranslationHTML("dialog.IamHost");
+ var buttons = [];
+ buttons.push({title: buttonTxt, value: "authNow"});
+
+ authDialog = APP.UI.messageHandler.openDialog(
+ title,
+ msg,
+ true,
+ buttons,
+ function (onSubmitEvent, submitValue) {
+
+ // Do not close the dialog yet
+ onSubmitEvent.preventDefault();
+
+ // Open login popup
+ if (submitValue === 'authNow') {
+ callback();
+ }
+ }
+ );
+ },
+ closeAuthenticationWindow: function () {
+ if (authenticationWindow) {
+ authenticationWindow.close();
+ authenticationWindow = null;
+ }
+ },
+ xmppAuthenticate: function () {
+
+ var loginDialog = LoginDialog.show(
+ function (connection, state) {
+ if (!state) {
+ // User cancelled
+ loginDialog.close();
+ return;
+ } else if (state == APP.xmpp.Status.CONNECTED) {
+
+ loginDialog.close();
+
+ Authentication.stopInterval();
+ Authentication.closeAuthenticationDialog();
+
+ // Close the connection as anonymous one will be used
+ // to create the conference. Session-id will authorize
+ // the request.
+ connection.disconnect();
+
+ var roomName = APP.UI.generateRoomName();
+ Moderator.allocateConferenceFocus(roomName, function () {
+ // If it's not "on the fly" authentication now join
+ // the conference room
+ if (!APP.xmpp.getMUCJoined()) {
+ APP.UI.checkForNicknameAndJoin();
+ }
+ });
+ }
+ }, true);
+ },
+ focusAuthenticationWindow: function () {
+ // If auth window exists just bring it to the front
+ if (authenticationWindow) {
+ authenticationWindow.focus();
+ return;
+ }
+ },
+ closeAuthenticationDialog: function () {
+ // Close authentication dialog if opened
+ if (authDialog) {
+ authDialog.close();
+ authDialog = null;
+ }
+ },
+ createAuthenticationWindow: function (callback, url) {
+ authenticationWindow = APP.UI.messageHandler.openCenteredPopup(
+ url, 910, 660,
+ // On closed
+ function () {
+ // Close authentication dialog if opened
+ Authentication.closeAuthenticationDialog();
+ callback();
+ authenticationWindow = null;
+ });
+ return authenticationWindow;
+ },
+ stopInterval: function () {
+ // Clear retry interval, so that we don't call 'doJoinAfterFocus' twice
+ if (authRetryId) {
+ window.clearTimeout(authRetryId);
+ authRetryId = null;
+ }
+ }
+};
+
+module.exports = Authentication;
+},{"../../xmpp/moderator":58,"./LoginDialog":15}],15:[function(require,module,exports){
+/* global $, APP, config*/
+
+var XMPP = require('../../xmpp/xmpp');
+var Moderator = require('../../xmpp/moderator');
+
+//FIXME: use LoginDialog to add retries to XMPP.connect method used when
+// anonymous domain is not enabled
+
+/**
+ * Creates new Dialog instance.
+ * @param callback function(Strophe.Connection, Strophe.Status) called
+ * when we either fail to connect or succeed(check Strophe.Status).
+ * @param obtainSession true if we want to send ConferenceIQ to Jicofo
+ * in order to create session-id after the connection is established.
+ * @constructor
+ */
+function Dialog(callback, obtainSession) {
+
+ var self = this;
+
+ var stop = false;
+
+ var connection = APP.xmpp.createConnection();
+
+ var message = '';
+ message += APP.translation.translateString("dialog.passwordRequired");
+ message += '
' +
+ '' +
+ '';
+
+ var okButton = APP.translation.generateTranslationHTML("dialog.Ok");
+
+ var cancelButton = APP.translation.generateTranslationHTML("dialog.Cancel");
+
+ var states = {
+ login: {
+ html: message,
+ buttons: [
+ { title: okButton, value: true},
+ { title: cancelButton, value: false}
+ ],
+ focus: ':input:first',
+ submit: function (e, v, m, f) {
+ e.preventDefault();
+ if (v) {
+ var jid = f.username;
+ var password = f.password;
+ if (jid && password) {
+ stop = false;
+ connection.reset();
+ connDialog.goToState('connecting');
+ connection.connect(jid, password, stateHandler);
+ }
+ } else {
+ // User cancelled
+ stop = true;
+ callback();
+ }
+ }
+ },
+ connecting: {
+ title: APP.translation.translateString('dialog.connecting'),
+ html: '',
+ buttons: [],
+ defaultButton: 0
+ },
+ finished: {
+ title: APP.translation.translateString('dialog.error'),
+ html: '',
+ buttons: [
+ {
+ title: APP.translation.translateString('dialog.retry'),
+ value: 'retry'
+ },
+ {
+ title: APP.translation.translateString('dialog.Cancel'),
+ value: 'cancel'
+ },
+ ],
+ defaultButton: 0,
+ submit: function (e, v, m, f) {
+ e.preventDefault();
+ if (v === 'retry')
+ connDialog.goToState('login');
+ else
+ callback();
+ }
+ }
+ };
+
+ var connDialog
+ = APP.UI.messageHandler.openDialogWithStates(states,
+ { persistent: true, closeText: '' }, null);
+
+ var stateHandler = function (status, message) {
+ if (stop) {
+ return;
+ }
+
+ var translateKey = "connection." + XMPP.getStatusString(status);
+ var statusStr = APP.translation.translateString(translateKey);
+
+ // Display current state
+ var connectionStatus =
+ connDialog.getState('connecting').find('#connectionStatus');
+
+ connectionStatus.text(statusStr);
+
+ switch (status) {
+ case XMPP.Status.CONNECTED:
+
+ stop = true;
+ if (!obtainSession) {
+ callback(connection, status);
+ return;
+ }
+ // Obtaining session-id status
+ connectionStatus.text(
+ APP.translation.translateString(
+ 'connection.FETCH_SESSION_ID'));
+
+ // Authenticate with Jicofo and obtain session-id
+ var roomName = APP.UI.generateRoomName();
+
+ // Jicofo will return new session-id when connected
+ // from authenticated domain
+ connection.sendIQ(
+ Moderator.createConferenceIq(roomName),
+ function (result) {
+
+ connectionStatus.text(
+ APP.translation.translateString(
+ 'connection.GOT_SESSION_ID'));
+
+ stop = true;
+
+ // Parse session-id
+ Moderator.parseSessionId(result);
+
+ callback(connection, status);
+ },
+ function (error) {
+ console.error("Auth on the fly failed", error);
+
+ stop = true;
+
+ var errorMsg =
+ APP.translation.translateString(
+ 'connection.GET_SESSION_ID_ERROR') +
+ $(error).find('>error').attr('code');
+
+ self.displayError(errorMsg);
+
+ connection.disconnect();
+ });
+
+ break;
+ case XMPP.Status.AUTHFAIL:
+ case XMPP.Status.CONNFAIL:
+ case XMPP.Status.DISCONNECTED:
+
+ stop = true;
+
+ callback(connection, status);
+
+ var errorMessage = statusStr;
+
+ if (message)
+ {
+ errorMessage += ': ' + message;
+ }
+ self.displayError(errorMessage);
+
+ break;
+ default:
+ break;
+ }
+ };
+
+ /**
+ * Displays error message in 'finished' state which allows either to cancel
+ * or retry.
+ * @param message the final message to be displayed.
+ */
+ this.displayError = function (message) {
+
+ var finishedState = connDialog.getState('finished');
+
+ var errorMessageElem = finishedState.find('#errorMessage');
+ errorMessageElem.text(message);
+
+ connDialog.goToState('finished');
+ };
+
+ /**
+ * Closes LoginDialog.
+ */
+ this.close = function () {
+ stop = true;
+ connDialog.close();
+ };
+}
+
+var LoginDialog = {
+
+ /**
+ * Displays login prompt used to establish new XMPP connection. Given
+ * callback(Strophe.Connection, Strophe.Status) function will be
+ * called when we connect successfully(status === CONNECTED) or when we fail
+ * to do so. On connection failure program can call Dialog.close() method in
+ * order to cancel or do nothing to let the user retry.
+ * @param callback function(Strophe.Connection, Strophe.Status)
+ * called when we either fail to connect or succeed(check
+ * Strophe.Status).
+ * @param obtainSession true if we want to send ConferenceIQ to
+ * Jicofo in order to create session-id after the connection is
+ * established.
+ * @returns {Dialog}
+ */
+ show: function (callback, obtainSession) {
+ return new Dialog(callback, obtainSession);
+ }
+};
+
+module.exports = LoginDialog;
+},{"../../xmpp/moderator":58,"../../xmpp/xmpp":66}],16:[function(require,module,exports){
+var Settings = require("../../settings/Settings");
+
+var users = {};
+
+var Avatar = {
+
+ /**
+ * Sets the user's avatar in the settings menu(if local user), contact list
+ * and thumbnail
+ * @param jid jid of the user
+ * @param id email or userID to be used as a hash
+ */
+ setUserAvatar: function (jid, id) {
+ if (id) {
+ if (users[jid] === id) {
+ return;
+ }
+ users[jid] = id;
+ }
+ var thumbUrl = this.getThumbUrl(jid);
+ var contactListUrl = this.getContactListUrl(jid);
+ var resourceJid = Strophe.getResourceFromJid(jid);
+
+ APP.UI.userAvatarChanged(resourceJid, thumbUrl, contactListUrl);
+ },
+ /**
+ * Returns image URL for the avatar to be displayed on large video area
+ * where current active speaker is presented.
+ * @param jid full MUC jid of the user for whom we want to obtain avatar URL
+ */
+ getActiveSpeakerUrl: function (jid) {
+ return this.getGravatarUrl(jid, 100);
+ },
+ /**
+ * Returns image URL for the avatar to be displayed on small video thumbnail
+ * @param jid full MUC jid of the user for whom we want to obtain avatar URL
+ */
+ getThumbUrl: function (jid) {
+ return this.getGravatarUrl(jid, 100);
+ },
+ /**
+ * Returns the URL for the avatar to be displayed as contactlist item
+ * @param jid full MUC jid of the user for whom we want to obtain avatar URL
+ */
+ getContactListUrl: function (jid) {
+ return this.getGravatarUrl(jid, 30);
+ },
+ getGravatarUrl: function (jid, size) {
+ if (!jid) {
+ console.error("Get gravatar - jid is undefined");
+ return null;
+ }
+ var id = users[jid];
+ if (!id) {
+ console.warn("No avatar stored yet for " + jid);
+ return null;
+ }
+ return 'https://www.gravatar.com/avatar/' +
+ MD5.hexdigest(id.trim().toLowerCase()) +
+ "?d=wavatar&size=" + (size || "30");
+ }
+
+};
+
+
+module.exports = Avatar;
+},{"../../settings/Settings":47}],17:[function(require,module,exports){
+/* global $, config,
+ setLargeVideoVisible, Util */
+
+var VideoLayout = require("../videolayout/VideoLayout");
+var Prezi = require("../prezi/Prezi");
+var UIUtil = require("../util/UIUtil");
+
+var etherpadName = null;
+var etherpadIFrame = null;
+var domain = null;
+var options = "?showControls=true&showChat=false&showLineNumbers=true&useMonospaceFont=false";
+
+
+/**
+ * Resizes the etherpad.
+ */
+function resize() {
+ if ($('#etherpad>iframe').length) {
+ var remoteVideos = $('#remoteVideos');
+ var availableHeight
+ = window.innerHeight - remoteVideos.outerHeight();
+ var availableWidth = UIUtil.getAvailableVideoWidth();
+
+ $('#etherpad>iframe').width(availableWidth);
+ $('#etherpad>iframe').height(availableHeight);
+ }
+}
+
+/**
+ * Creates the Etherpad button and adds it to the toolbar.
+ */
+function enableEtherpadButton() {
+ if (!$('#etherpadButton').is(":visible"))
+ $('#etherpadButton').css({display: 'inline-block'});
+}
+
+/**
+ * Creates the IFrame for the etherpad.
+ */
+function createIFrame() {
+ etherpadIFrame = VideoLayout.createEtherpadIframe(
+ domain + etherpadName + options, function() {
+
+ document.domain = document.domain;
+ bubbleIframeMouseMove(etherpadIFrame);
+ setTimeout(function() {
+ // the iframes inside of the etherpad are
+ // not yet loaded when the etherpad iframe is loaded
+ var outer = etherpadIFrame.
+ contentDocument.getElementsByName("ace_outer")[0];
+ bubbleIframeMouseMove(outer);
+ var inner = outer.
+ contentDocument.getElementsByName("ace_inner")[0];
+ bubbleIframeMouseMove(inner);
+ }, 2000);
+ });
+}
+
+function bubbleIframeMouseMove(iframe){
+ var existingOnMouseMove = iframe.contentWindow.onmousemove;
+ iframe.contentWindow.onmousemove = function(e){
+ if(existingOnMouseMove) existingOnMouseMove(e);
+ var evt = document.createEvent("MouseEvents");
+ var boundingClientRect = iframe.getBoundingClientRect();
+ evt.initMouseEvent(
+ "mousemove",
+ true, // bubbles
+ false, // not cancelable
+ window,
+ e.detail,
+ e.screenX,
+ e.screenY,
+ e.clientX + boundingClientRect.left,
+ e.clientY + boundingClientRect.top,
+ e.ctrlKey,
+ e.altKey,
+ e.shiftKey,
+ e.metaKey,
+ e.button,
+ null // no related element
+ );
+ iframe.dispatchEvent(evt);
+ };
+}
+
+
+var Etherpad = {
+ /**
+ * Initializes the etherpad.
+ */
+ init: function (name) {
+
+ if (config.etherpad_base && !etherpadName && name) {
+
+ domain = config.etherpad_base;
+
+ etherpadName = name;
+
+ enableEtherpadButton();
+
+ /**
+ * Resizes the etherpad, when the window is resized.
+ */
+ $(window).resize(function () {
+ resize();
+ });
+ }
+ },
+
+ /**
+ * Opens/hides the Etherpad.
+ */
+ toggleEtherpad: function (isPresentation) {
+ if (!etherpadIFrame)
+ createIFrame();
+
+
+ if(VideoLayout.getLargeVideoState() === "etherpad")
+ {
+ VideoLayout.setLargeVideoState("video");
+ }
+ else
+ {
+ VideoLayout.setLargeVideoState("etherpad");
+ }
+ resize();
+ }
+};
+
+module.exports = Etherpad;
+
+},{"../prezi/Prezi":18,"../util/UIUtil":33,"../videolayout/VideoLayout":39}],18:[function(require,module,exports){
+var ToolbarToggler = require("../toolbars/ToolbarToggler");
+var UIUtil = require("../util/UIUtil");
+var VideoLayout = require("../videolayout/VideoLayout");
+var messageHandler = require("../util/MessageHandler");
+var PreziPlayer = require("./PreziPlayer");
+
+var preziPlayer = null;
+
+
+/**
+ * Shows/hides a presentation.
+ */
+function setPresentationVisible(visible) {
+
+ if (visible) {
+ VideoLayout.setLargeVideoState("prezi");
+ }
+ else {
+ VideoLayout.setLargeVideoState("video");
+ }
+}
+
+var Prezi = {
+
+
+ /**
+ * Reloads the current presentation.
+ */
+ reloadPresentation: function() {
+ var iframe = document.getElementById(preziPlayer.options.preziId);
+ iframe.src = iframe.src;
+ },
+
+ /**
+ * Returns true if the presentation is visible, false -
+ * otherwise.
+ */
+ isPresentationVisible: function () {
+ return ($('#presentation>iframe') != null
+ && $('#presentation>iframe').css('opacity') == 1);
+ },
+
+ /**
+ * Opens the Prezi dialog, from which the user could choose a presentation
+ * to load.
+ */
+ openPreziDialog: function() {
+ var myprezi = APP.xmpp.getPrezi();
+ if (myprezi) {
+ messageHandler.openTwoButtonDialog("dialog.removePreziTitle",
+ null,
+ "dialog.removePreziMsg",
+ null,
+ false,
+ "dialog.Remove",
+ function(e,v,m,f) {
+ if(v) {
+ APP.xmpp.removePreziFromPresence();
+ }
+ }
+ );
+ }
+ else if (preziPlayer != null) {
+ messageHandler.openTwoButtonDialog("dialog.sharePreziTitle",
+ null, "dialog.sharePreziMsg",
+ null,
+ false,
+ "dialog.Ok",
+ function(e,v,m,f) {
+ $.prompt.close();
+ }
+ );
+ }
+ else {
+ var html = APP.translation.generateTranslationHTML(
+ "dialog.sharePreziTitle");
+ var cancelButton = APP.translation.generateTranslationHTML(
+ "dialog.Cancel");
+ var shareButton = APP.translation.generateTranslationHTML(
+ "dialog.Share");
+ var backButton = APP.translation.generateTranslationHTML(
+ "dialog.Back");
+ var buttons = [];
+ var buttons1 = [];
+ // Cancel button to both states
+ buttons.push({title: cancelButton, value: false});
+ buttons1.push({title: cancelButton, value: false});
+ // Share button
+ buttons.push({title: shareButton, value: true});
+ // Back button
+ buttons1.push({title: backButton, value: true});
+ var linkError = APP.translation.generateTranslationHTML(
+ "dialog.preziLinkError");
+ var defaultUrl = APP.translation.translateString("defaultPreziLink",
+ {url: "http://prezi.com/wz7vhjycl7e6/my-prezi"});
+ var openPreziState = {
+ state0: {
+ html: '' + html + '
' +
+ '',
+ persistent: false,
+ buttons: buttons,
+ focus: ':input:first',
+ defaultButton: 0,
+ submit: function (e, v, m, f) {
+ e.preventDefault();
+ if(v)
+ {
+ var preziUrl = f.preziUrl;
+
+ if (preziUrl)
+ {
+ var urlValue
+ = encodeURI(UIUtil.escapeHtml(preziUrl));
+
+ if (urlValue.indexOf('http://prezi.com/') != 0
+ && urlValue.indexOf('https://prezi.com/') != 0)
+ {
+ $.prompt.goToState('state1');
+ return false;
+ }
+ else {
+ var presIdTmp = urlValue.substring(
+ urlValue.indexOf("prezi.com/") + 10);
+ if (!isAlphanumeric(presIdTmp)
+ || presIdTmp.indexOf('/') < 2) {
+ $.prompt.goToState('state1');
+ return false;
+ }
+ else {
+ APP.xmpp.addToPresence("prezi", urlValue);
+ $.prompt.close();
+ }
+ }
+ }
+ }
+ else
+ $.prompt.close();
+ }
+ },
+ state1: {
+ html: '' + html + '
' +
+ linkError,
+ persistent: false,
+ buttons: buttons1,
+ focus: ':input:first',
+ defaultButton: 1,
+ submit: function (e, v, m, f) {
+ e.preventDefault();
+ if (v === 0)
+ $.prompt.close();
+ else
+ $.prompt.goToState('state0');
+ }
+ }
+ };
+ messageHandler.openDialogWithStates(openPreziState);
+ }
+ }
+
+};
+
+/**
+ * A new presentation has been added.
+ *
+ * @param event the event indicating the add of a presentation
+ * @param jid the jid from which the presentation was added
+ * @param presUrl url of the presentation
+ * @param currentSlide the current slide to which we should move
+ */
+function presentationAdded(event, jid, presUrl, currentSlide) {
+ console.log("presentation added", presUrl);
+
+ var presId = getPresentationId(presUrl);
+
+ var elementId = 'participant_'
+ + Strophe.getResourceFromJid(jid)
+ + '_' + presId;
+
+ VideoLayout.addPreziContainer(elementId);
+
+ var controlsEnabled = false;
+ if (jid === APP.xmpp.myJid())
+ controlsEnabled = true;
+
+ setPresentationVisible(true);
+ VideoLayout.setLargeVideoHover(
+ function (event) {
+ if (Prezi.isPresentationVisible()) {
+ var reloadButtonRight = window.innerWidth
+ - $('#presentation>iframe').offset().left
+ - $('#presentation>iframe').width();
+
+ $('#reloadPresentation').css({ right: reloadButtonRight,
+ display:'inline-block'});
+ }
+ },
+ function (event) {
+ if (!Prezi.isPresentationVisible())
+ $('#reloadPresentation').css({display:'none'});
+ else {
+ var e = event.toElement || event.relatedTarget;
+
+ if (e && e.id != 'reloadPresentation' && e.id != 'header')
+ $('#reloadPresentation').css({display:'none'});
+ }
+ });
+
+ preziPlayer = new PreziPlayer(
+ 'presentation',
+ {preziId: presId,
+ width: getPresentationWidth(),
+ height: getPresentationHeihgt(),
+ controls: controlsEnabled,
+ debug: true
+ });
+
+ $('#presentation>iframe').attr('id', preziPlayer.options.preziId);
+
+ preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) {
+ console.log("prezi status", event.value);
+ if (event.value == PreziPlayer.STATUS_CONTENT_READY) {
+ if (jid != APP.xmpp.myJid())
+ preziPlayer.flyToStep(currentSlide);
+ }
+ });
+
+ preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) {
+ console.log("event value", event.value);
+ APP.xmpp.addToPresence("preziSlide", event.value);
+ });
+
+ $("#" + elementId).css( 'background-image',
+ 'url(../images/avatarprezi.png)');
+ $("#" + elementId).click(
+ function () {
+ setPresentationVisible(true);
+ }
+ );
+};
+
+/**
+ * A presentation has been removed.
+ *
+ * @param event the event indicating the remove of a presentation
+ * @param jid the jid for which the presentation was removed
+ * @param the url of the presentation
+ */
+function presentationRemoved(event, jid, presUrl) {
+ console.log('presentation removed', presUrl);
+ var presId = getPresentationId(presUrl);
+ setPresentationVisible(false);
+ $('#participant_'
+ + Strophe.getResourceFromJid(jid)
+ + '_' + presId).remove();
+ $('#presentation>iframe').remove();
+ if (preziPlayer != null) {
+ preziPlayer.destroy();
+ preziPlayer = null;
+ }
+};
+
+/**
+ * Indicates if the given string is an alphanumeric string.
+ * Note that some special characters are also allowed (-, _ , /, &, ?, =, ;) for the
+ * purpose of checking URIs.
+ */
+function isAlphanumeric(unsafeText) {
+ var regex = /^[a-z0-9-_\/&\?=;]+$/i;
+ return regex.test(unsafeText);
+}
+
+/**
+ * Returns the presentation id from the given url.
+ */
+function getPresentationId (presUrl) {
+ var presIdTmp = presUrl.substring(presUrl.indexOf("prezi.com/") + 10);
+ return presIdTmp.substring(0, presIdTmp.indexOf('/'));
+}
+
+/**
+ * Returns the presentation width.
+ */
+function getPresentationWidth() {
+ var availableWidth = UIUtil.getAvailableVideoWidth();
+ var availableHeight = getPresentationHeihgt();
+
+ var aspectRatio = 16.0 / 9.0;
+ if (availableHeight < availableWidth / aspectRatio) {
+ availableWidth = Math.floor(availableHeight * aspectRatio);
+ }
+ return availableWidth;
+}
+
+/**
+ * Returns the presentation height.
+ */
+function getPresentationHeihgt() {
+ var remoteVideos = $('#remoteVideos');
+ return window.innerHeight - remoteVideos.outerHeight();
+}
+
+/**
+ * Resizes the presentation iframe.
+ */
+function resize() {
+ if ($('#presentation>iframe')) {
+ $('#presentation>iframe').width(getPresentationWidth());
+ $('#presentation>iframe').height(getPresentationHeihgt());
+ }
+}
+
+/**
+ * Presentation has been removed.
+ */
+$(document).bind('presentationremoved.muc', presentationRemoved);
+
+/**
+ * Presentation has been added.
+ */
+$(document).bind('presentationadded.muc', presentationAdded);
+
+/*
+ * Indicates presentation slide change.
+ */
+$(document).bind('gotoslide.muc', function (event, jid, presUrl, current) {
+ if (preziPlayer && preziPlayer.getCurrentStep() != current) {
+ preziPlayer.flyToStep(current);
+
+ var animationStepsArray = preziPlayer.getAnimationCountOnSteps();
+ for (var i = 0; i < parseInt(animationStepsArray[current]); i++) {
+ preziPlayer.flyToStep(current, i);
+ }
+ }
+});
+
+$(window).resize(function () {
+ resize();
+});
+
+module.exports = Prezi;
+
+},{"../toolbars/ToolbarToggler":29,"../util/MessageHandler":31,"../util/UIUtil":33,"../videolayout/VideoLayout":39,"./PreziPlayer":19}],19:[function(require,module,exports){
+(function() {
+ "use strict";
+ var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+ window.PreziPlayer = (function() {
+
+ PreziPlayer.API_VERSION = 1;
+ PreziPlayer.CURRENT_STEP = 'currentStep';
+ PreziPlayer.CURRENT_ANIMATION_STEP = 'currentAnimationStep';
+ PreziPlayer.CURRENT_OBJECT = 'currentObject';
+ PreziPlayer.STATUS_LOADING = 'loading';
+ PreziPlayer.STATUS_READY = 'ready';
+ PreziPlayer.STATUS_CONTENT_READY = 'contentready';
+ PreziPlayer.EVENT_CURRENT_STEP = "currentStepChange";
+ PreziPlayer.EVENT_CURRENT_ANIMATION_STEP = "currentAnimationStepChange";
+ PreziPlayer.EVENT_CURRENT_OBJECT = "currentObjectChange";
+ PreziPlayer.EVENT_STATUS = "statusChange";
+ PreziPlayer.EVENT_PLAYING = "isAutoPlayingChange";
+ PreziPlayer.EVENT_IS_MOVING = "isMovingChange";
+ PreziPlayer.domain = "https://prezi.com";
+ PreziPlayer.path = "/player/";
+ PreziPlayer.players = {};
+ PreziPlayer.binded_methods = ['changesHandler'];
+
+ PreziPlayer.createMultiplePlayers = function(optionArray){
+ for(var i=0; i 0 &&
+ obj.values.animationCountOnSteps &&
+ obj.values.animationCountOnSteps[step] <= animation_step) {
+ animation_step = obj.values.animationCountOnSteps[step];
+ }
+ // jump to animation steps by calling flyToNextStep()
+ function doAnimationSteps() {
+ if (obj.values.isMoving == true) {
+ setTimeout(doAnimationSteps, 100); // wait until the flight ends
+ return;
+ }
+ while (animation_step-- > 0) {
+ obj.flyToNextStep(); // do the animation steps
+ }
+ }
+ setTimeout(doAnimationSteps, 200); // 200ms is the internal "reporting" time
+ // jump to the step
+ return this.sendMessage({
+ 'action': 'present',
+ 'data': ['moveToStep', step]
+ });
+ };
+
+ PreziPlayer.prototype.toObject = /* toObject is DEPRECATED */
+ PreziPlayer.prototype.flyToObject = function(objectId) {
+ return this.sendMessage({
+ 'action': 'present',
+ 'data': ['moveToObject', objectId]
+ });
+ };
+
+ PreziPlayer.prototype.play = function(defaultDelay) {
+ return this.sendMessage({
+ 'action': 'present',
+ 'data': ['startAutoPlay', defaultDelay]
+ });
+ };
+
+ PreziPlayer.prototype.stop = function() {
+ return this.sendMessage({
+ 'action': 'present',
+ 'data': ['stopAutoPlay']
+ });
+ };
+
+ PreziPlayer.prototype.pause = function(defaultDelay) {
+ return this.sendMessage({
+ 'action': 'present',
+ 'data': ['pauseAutoPlay', defaultDelay]
+ });
+ };
+
+ PreziPlayer.prototype.getCurrentStep = function() {
+ return this.values.currentStep;
+ };
+
+ PreziPlayer.prototype.getCurrentAnimationStep = function() {
+ return this.values.currentAnimationStep;
+ };
+
+ PreziPlayer.prototype.getCurrentObject = function() {
+ return this.values.currentObject;
+ };
+
+ PreziPlayer.prototype.getStatus = function() {
+ return this.values.status;
+ };
+
+ PreziPlayer.prototype.isPlaying = function() {
+ return this.values.isAutoPlaying;
+ };
+
+ PreziPlayer.prototype.getStepCount = function() {
+ return this.values.stepCount;
+ };
+
+ PreziPlayer.prototype.getAnimationCountOnSteps = function() {
+ return this.values.animationCountOnSteps;
+ };
+
+ PreziPlayer.prototype.getTitle = function() {
+ return this.values.title;
+ };
+
+ PreziPlayer.prototype.setDimensions = function(dims) {
+ for (var parameter in dims) {
+ this.iframe[parameter] = dims[parameter];
+ }
+ }
+
+ PreziPlayer.prototype.getDimensions = function() {
+ return {
+ width: parseInt(this.iframe.width, 10),
+ height: parseInt(this.iframe.height, 10)
+ }
+ }
+
+ PreziPlayer.prototype.on = function(event, callback) {
+ this.callbacks.push({
+ event: event,
+ callback: callback
+ });
+ };
+
+ PreziPlayer.prototype.off = function(event, callback) {
+ var j, item;
+ if (event === undefined) {
+ this.callbacks = [];
+ }
+ j = this.callbacks.length;
+ while (j--) {
+ item = this.callbacks[j];
+ if (item && item.event === event && (callback === undefined || item.callback === callback)){
+ this.callbacks.splice(j, 1);
+ }
+ }
+ };
+
+ if (window.addEventListener) {
+ window.addEventListener('message', PreziPlayer.messageReceived, false);
+ } else {
+ window.attachEvent('onmessage', PreziPlayer.messageReceived);
+ }
+
+ return PreziPlayer;
+
+ })();
+
+})();
+
+module.exports = PreziPlayer;
+
+},{}],20:[function(require,module,exports){
+var Chat = require("./chat/Chat");
+var ContactList = require("./contactlist/ContactList");
+var Settings = require("./../../settings/Settings");
+var SettingsMenu = require("./settings/SettingsMenu");
+var VideoLayout = require("../videolayout/VideoLayout");
+var ToolbarToggler = require("../toolbars/ToolbarToggler");
+var UIUtil = require("../util/UIUtil");
+var LargeVideo = require("../videolayout/LargeVideo");
+
+/**
+ * Toggler for the chat, contact list, settings menu, etc..
+ */
+var PanelToggler = (function(my) {
+
+ var currentlyOpen = null;
+ var buttons = {
+ '#chatspace': '#chatBottomButton',
+ '#contactlist': '#contactListButton',
+ '#settingsmenu': '#settingsButton'
+ };
+
+ /**
+ * Toggles the windows in the side panel
+ * @param object the window that should be shown
+ * @param selector the selector for the element containing the panel
+ * @param onOpenComplete function to be called when the panel is opened
+ * @param onOpen function to be called if the window is going to be opened
+ * @param onClose function to be called if the window is going to be closed
+ */
+ var toggle = function(object, selector, onOpenComplete, onOpen, onClose) {
+ UIUtil.buttonClick(buttons[selector], "active");
+
+ if (object.isVisible()) {
+ $("#toast-container").animate({
+ right: '5px'
+ },
+ {
+ queue: false,
+ duration: 500
+ });
+ $(selector).hide("slide", {
+ direction: "right",
+ queue: false,
+ duration: 500
+ });
+ if(typeof onClose === "function") {
+ onClose();
+ }
+
+ currentlyOpen = null;
+ }
+ else {
+ // Undock the toolbar when the chat is shown and if we're in a
+ // video mode.
+ if (LargeVideo.isLargeVideoVisible()) {
+ ToolbarToggler.dockToolbar(false);
+ }
+
+ if(currentlyOpen) {
+ var current = $(currentlyOpen);
+ UIUtil.buttonClick(buttons[currentlyOpen], "active");
+ current.css('z-index', 4);
+ setTimeout(function () {
+ current.css('display', 'none');
+ current.css('z-index', 5);
+ }, 500);
+ }
+
+ $("#toast-container").animate({
+ right: (PanelToggler.getPanelSize()[0] + 5) + 'px'
+ },
+ {
+ queue: false,
+ duration: 500
+ });
+ $(selector).show("slide", {
+ direction: "right",
+ queue: false,
+ duration: 500,
+ complete: onOpenComplete
+ });
+ if(typeof onOpen === "function") {
+ onOpen();
+ }
+
+ currentlyOpen = selector;
+ }
+ };
+
+ /**
+ * Opens / closes the chat area.
+ */
+ my.toggleChat = function() {
+ var chatCompleteFunction = Chat.isVisible() ?
+ function() {} : function () {
+ Chat.scrollChatToBottom();
+ $('#chatspace').trigger('shown');
+ };
+
+ VideoLayout.resizeVideoArea(!Chat.isVisible(), chatCompleteFunction);
+
+ toggle(Chat,
+ '#chatspace',
+ function () {
+ // Request the focus in the nickname field or the chat input field.
+ if ($('#nickname').css('visibility') === 'visible') {
+ $('#nickinput').focus();
+ } else {
+ $('#usermsg').focus();
+ }
+ },
+ null,
+ Chat.resizeChat,
+ null);
+ };
+
+ /**
+ * Opens / closes the contact list area.
+ */
+ my.toggleContactList = function () {
+ var completeFunction = ContactList.isVisible() ?
+ function() {} : function () { $('#contactlist').trigger('shown');};
+ VideoLayout.resizeVideoArea(!ContactList.isVisible(), completeFunction);
+
+ toggle(ContactList,
+ '#contactlist',
+ null,
+ function() {
+ ContactList.setVisualNotification(false);
+ },
+ null);
+ };
+
+ /**
+ * Opens / closes the settings menu
+ */
+ my.toggleSettingsMenu = function() {
+ VideoLayout.resizeVideoArea(!SettingsMenu.isVisible(), function (){});
+ toggle(SettingsMenu,
+ '#settingsmenu',
+ null,
+ function() {
+ var settings = Settings.getSettings();
+ $('#setDisplayName').get(0).value = settings.displayName;
+ $('#setEmail').get(0).value = settings.email;
+ },
+ null);
+ };
+
+ /**
+ * Returns the size of the side panel.
+ */
+ my.getPanelSize = function () {
+ var availableHeight = window.innerHeight;
+ var availableWidth = window.innerWidth;
+
+ var panelWidth = 200;
+ if (availableWidth * 0.2 < 200) {
+ panelWidth = availableWidth * 0.2;
+ }
+
+ return [panelWidth, availableHeight];
+ };
+
+ my.isVisible = function() {
+ return (Chat.isVisible() || ContactList.isVisible() || SettingsMenu.isVisible());
+ };
+
+ return my;
+
+}(PanelToggler || {}));
+
+module.exports = PanelToggler;
+},{"../toolbars/ToolbarToggler":29,"../util/UIUtil":33,"../videolayout/LargeVideo":35,"../videolayout/VideoLayout":39,"./../../settings/Settings":47,"./chat/Chat":21,"./contactlist/ContactList":25,"./settings/SettingsMenu":26}],21:[function(require,module,exports){
+/* global $, Util, nickname:true */
+var Replacement = require("./Replacement");
+var CommandsProcessor = require("./Commands");
+var ToolbarToggler = require("../../toolbars/ToolbarToggler");
+var smileys = require("./smileys.json").smileys;
+var NicknameHandler = require("../../util/NicknameHandler");
+var UIUtil = require("../../util/UIUtil");
+var UIEvents = require("../../../../service/UI/UIEvents");
+
+var notificationInterval = false;
+var unreadMessages = 0;
+
+
+/**
+ * Shows/hides a visual notification, indicating that a message has arrived.
+ */
+function setVisualNotification(show) {
+ var unreadMsgElement = document.getElementById('unreadMessages');
+ var unreadMsgBottomElement
+ = document.getElementById('bottomUnreadMessages');
+
+ var glower = $('#chatButton');
+ var bottomGlower = $('#chatBottomButton');
+
+ if (unreadMessages) {
+ unreadMsgElement.innerHTML = unreadMessages.toString();
+ unreadMsgBottomElement.innerHTML = unreadMessages.toString();
+
+ ToolbarToggler.dockToolbar(true);
+
+ var chatButtonElement
+ = document.getElementById('chatButton').parentNode;
+ var leftIndent = (UIUtil.getTextWidth(chatButtonElement) -
+ UIUtil.getTextWidth(unreadMsgElement)) / 2;
+ var topIndent = (UIUtil.getTextHeight(chatButtonElement) -
+ UIUtil.getTextHeight(unreadMsgElement)) / 2 - 3;
+
+ unreadMsgElement.setAttribute(
+ 'style',
+ 'top:' + topIndent +
+ '; left:' + leftIndent + ';');
+
+ var chatBottomButtonElement
+ = document.getElementById('chatBottomButton').parentNode;
+ var bottomLeftIndent = (UIUtil.getTextWidth(chatBottomButtonElement) -
+ UIUtil.getTextWidth(unreadMsgBottomElement)) / 2;
+ var bottomTopIndent = (UIUtil.getTextHeight(chatBottomButtonElement) -
+ UIUtil.getTextHeight(unreadMsgBottomElement)) / 2 - 2;
+
+ unreadMsgBottomElement.setAttribute(
+ 'style',
+ 'top:' + bottomTopIndent +
+ '; left:' + bottomLeftIndent + ';');
+
+
+ if (!glower.hasClass('icon-chat-simple')) {
+ glower.removeClass('icon-chat');
+ glower.addClass('icon-chat-simple');
+ }
+ }
+ else {
+ unreadMsgElement.innerHTML = '';
+ unreadMsgBottomElement.innerHTML = '';
+ glower.removeClass('icon-chat-simple');
+ glower.addClass('icon-chat');
+ }
+
+ if (show && !notificationInterval) {
+ notificationInterval = window.setInterval(function () {
+ glower.toggleClass('active');
+ bottomGlower.toggleClass('active glowing');
+ }, 800);
+ }
+ else if (!show && notificationInterval) {
+ window.clearInterval(notificationInterval);
+ notificationInterval = false;
+ glower.removeClass('active');
+ bottomGlower.removeClass('glowing');
+ bottomGlower.addClass('active');
+ }
+}
+
+
+/**
+ * Returns the current time in the format it is shown to the user
+ * @returns {string}
+ */
+function getCurrentTime(stamp) {
+ var now = (stamp? new Date(stamp): new Date());
+ var hour = now.getHours();
+ var minute = now.getMinutes();
+ var second = now.getSeconds();
+ if(hour.toString().length === 1) {
+ hour = '0'+hour;
+ }
+ if(minute.toString().length === 1) {
+ minute = '0'+minute;
+ }
+ if(second.toString().length === 1) {
+ second = '0'+second;
+ }
+ return hour+':'+minute+':'+second;
+}
+
+function toggleSmileys()
+{
+ var smileys = $('#smileysContainer');
+ if(!smileys.is(':visible')) {
+ smileys.show("slide", { direction: "down", duration: 300});
+ } else {
+ smileys.hide("slide", { direction: "down", duration: 300});
+ }
+ $('#usermsg').focus();
+}
+
+function addClickFunction(smiley, number) {
+ smiley.onclick = function addSmileyToMessage() {
+ var usermsg = $('#usermsg');
+ var message = usermsg.val();
+ message += smileys['smiley' + number];
+ usermsg.val(message);
+ usermsg.get(0).setSelectionRange(message.length, message.length);
+ toggleSmileys();
+ usermsg.focus();
+ };
+}
+
+/**
+ * Adds the smileys container to the chat
+ */
+function addSmileys() {
+ var smileysContainer = document.createElement('div');
+ smileysContainer.id = 'smileysContainer';
+ for(var i = 1; i <= 21; i++) {
+ var smileyContainer = document.createElement('div');
+ smileyContainer.id = 'smiley' + i;
+ smileyContainer.className = 'smileyContainer';
+ var smiley = document.createElement('img');
+ smiley.src = 'images/smileys/smiley' + i + '.svg';
+ smiley.className = 'smiley';
+ addClickFunction(smiley, i);
+ smileyContainer.appendChild(smiley);
+ smileysContainer.appendChild(smileyContainer);
+ }
+
+ $("#chatspace").append(smileysContainer);
+}
+
+/**
+ * Resizes the chat conversation.
+ */
+function resizeChatConversation() {
+ var msgareaHeight = $('#usermsg').outerHeight();
+ var chatspace = $('#chatspace');
+ var width = chatspace.width();
+ var chat = $('#chatconversation');
+ var smileys = $('#smileysarea');
+
+ smileys.height(msgareaHeight);
+ $("#smileys").css('bottom', (msgareaHeight - 26) / 2);
+ $('#smileysContainer').css('bottom', msgareaHeight);
+ chat.width(width - 10);
+ chat.height(window.innerHeight - 15 - msgareaHeight);
+}
+
+/**
+ * Chat related user interface.
+ */
+var Chat = (function (my) {
+ /**
+ * Initializes chat related interface.
+ */
+ my.init = function () {
+ if(NicknameHandler.getNickname())
+ Chat.setChatConversationMode(true);
+ NicknameHandler.addListener(UIEvents.NICKNAME_CHANGED,
+ function (nickname) {
+ Chat.setChatConversationMode(true);
+ });
+
+ $('#nickinput').keydown(function (event) {
+ if (event.keyCode === 13) {
+ event.preventDefault();
+ var val = UIUtil.escapeHtml(this.value);
+ this.value = '';
+ if (!NicknameHandler.getNickname()) {
+ NicknameHandler.setNickname(val);
+
+ return;
+ }
+ }
+ });
+
+ $('#usermsg').keydown(function (event) {
+ if (event.keyCode === 13) {
+ event.preventDefault();
+ var value = this.value;
+ $('#usermsg').val('').trigger('autosize.resize');
+ this.focus();
+ var command = new CommandsProcessor(value);
+ if(command.isCommand())
+ {
+ command.processCommand();
+ }
+ else
+ {
+ var message = UIUtil.escapeHtml(value);
+ APP.xmpp.sendChatMessage(message, NicknameHandler.getNickname());
+ }
+ }
+ });
+
+ var onTextAreaResize = function () {
+ resizeChatConversation();
+ Chat.scrollChatToBottom();
+ };
+ $('#usermsg').autosize({callback: onTextAreaResize});
+
+ $("#chatspace").bind("shown",
+ function () {
+ unreadMessages = 0;
+ setVisualNotification(false);
+ });
+
+ addSmileys();
+ };
+
+ /**
+ * Appends the given message to the chat conversation.
+ */
+ my.updateChatConversation = function (from, displayName, message, myjid, stamp) {
+ var divClassName = '';
+
+ if (APP.xmpp.myJid() === from) {
+ divClassName = "localuser";
+ }
+ else {
+ divClassName = "remoteuser";
+
+ if (!Chat.isVisible()) {
+ unreadMessages++;
+ UIUtil.playSoundNotification('chatNotification');
+ setVisualNotification(true);
+ }
+ }
+
+ // replace links and smileys
+ // Strophe already escapes special symbols on sending,
+ // so we escape here only tags to avoid double &
+ var escMessage = message.replace(//g, '>').replace(/\n/g, '
');
+ var escDisplayName = UIUtil.escapeHtml(displayName);
+ message = Replacement.processReplacements(escMessage);
+
+ var messageContainer =
+ ''+
+ '
![](../images/chatArrow.svg)
' +
+ '
' + escDisplayName +
+ '
' + '
' + getCurrentTime(stamp) +
+ '
' + '
' + message + '
' +
+ '
';
+
+ $('#chatconversation').append(messageContainer);
+ $('#chatconversation').animate(
+ { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
+ };
+
+ /**
+ * Appends error message to the conversation
+ * @param errorMessage the received error message.
+ * @param originalText the original message.
+ */
+ my.chatAddError = function(errorMessage, originalText)
+ {
+ errorMessage = UIUtil.escapeHtml(errorMessage);
+ originalText = UIUtil.escapeHtml(originalText);
+
+ $('#chatconversation').append(
+ 'Error: ' + 'Your message' +
+ (originalText? (' \"'+ originalText + '\"') : "") +
+ ' was not sent.' +
+ (errorMessage? (' Reason: ' + errorMessage) : '') + '
');
+ $('#chatconversation').animate(
+ { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
+ };
+
+ /**
+ * Sets the subject to the UI
+ * @param subject the subject
+ */
+ my.chatSetSubject = function(subject)
+ {
+ if(subject)
+ subject = subject.trim();
+ $('#subject').html(Replacement.linkify(UIUtil.escapeHtml(subject)));
+ if(subject === "")
+ {
+ $("#subject").css({display: "none"});
+ }
+ else
+ {
+ $("#subject").css({display: "block"});
+ }
+ };
+
+
+
+ /**
+ * Sets the chat conversation mode.
+ */
+ my.setChatConversationMode = function (isConversationMode) {
+ if (isConversationMode) {
+ $('#nickname').css({visibility: 'hidden'});
+ $('#chatconversation').css({visibility: 'visible'});
+ $('#usermsg').css({visibility: 'visible'});
+ $('#smileysarea').css({visibility: 'visible'});
+ $('#usermsg').focus();
+ }
+ };
+
+ /**
+ * Resizes the chat area.
+ */
+ my.resizeChat = function () {
+ var chatSize = require("../SidePanelToggler").getPanelSize();
+
+ $('#chatspace').width(chatSize[0]);
+ $('#chatspace').height(chatSize[1]);
+
+ resizeChatConversation();
+ };
+
+ /**
+ * Indicates if the chat is currently visible.
+ */
+ my.isVisible = function () {
+ return $('#chatspace').is(":visible");
+ };
+ /**
+ * Shows and hides the window with the smileys
+ */
+ my.toggleSmileys = toggleSmileys;
+
+ /**
+ * Scrolls chat to the bottom.
+ */
+ my.scrollChatToBottom = function() {
+ setTimeout(function () {
+ $('#chatconversation').scrollTop(
+ $('#chatconversation')[0].scrollHeight);
+ }, 5);
+ };
+
+
+ return my;
+}(Chat || {}));
+module.exports = Chat;
+},{"../../../../service/UI/UIEvents":106,"../../toolbars/ToolbarToggler":29,"../../util/NicknameHandler":32,"../../util/UIUtil":33,"../SidePanelToggler":20,"./Commands":22,"./Replacement":23,"./smileys.json":24}],22:[function(require,module,exports){
+var UIUtil = require("../../util/UIUtil");
+
+/**
+ * List with supported commands. The keys are the names of the commands and
+ * the value is the function that processes the message.
+ * @type {{String: function}}
+ */
+var commands = {
+ "topic" : processTopic
+};
+
+/**
+ * Extracts the command from the message.
+ * @param message the received message
+ * @returns {string} the command
+ */
+function getCommand(message)
+{
+ if(message)
+ {
+ for(var command in commands)
+ {
+ if(message.indexOf("/" + command) == 0)
+ return command;
+ }
+ }
+ return "";
+};
+
+/**
+ * Processes the data for topic command.
+ * @param commandArguments the arguments of the topic command.
+ */
+function processTopic(commandArguments)
+{
+ var topic = UIUtil.escapeHtml(commandArguments);
+ APP.xmpp.setSubject(topic);
+}
+
+/**
+ * Constructs new CommandProccessor instance from a message that
+ * handles commands received via chat messages.
+ * @param message the message
+ * @constructor
+ */
+function CommandsProcessor(message)
+{
+
+
+ var command = getCommand(message);
+
+ /**
+ * Returns the name of the command.
+ * @returns {String} the command
+ */
+ this.getCommand = function()
+ {
+ return command;
+ };
+
+
+ var messageArgument = message.substr(command.length + 2);
+
+ /**
+ * Returns the arguments of the command.
+ * @returns {string}
+ */
+ this.getArgument = function()
+ {
+ return messageArgument;
+ };
+}
+
+/**
+ * Checks whether this instance is valid command or not.
+ * @returns {boolean}
+ */
+CommandsProcessor.prototype.isCommand = function()
+{
+ if(this.getCommand())
+ return true;
+ return false;
+};
+
+/**
+ * Processes the command.
+ */
+CommandsProcessor.prototype.processCommand = function()
+{
+ if(!this.isCommand())
+ return;
+
+ commands[this.getCommand()](this.getArgument());
+
+};
+
+module.exports = CommandsProcessor;
+},{"../../util/UIUtil":33}],23:[function(require,module,exports){
+var Smileys = require("./smileys.json");
+/**
+ * Processes links and smileys in "body"
+ */
+function processReplacements(body)
+{
+ //make links clickable
+ body = linkify(body);
+
+ //add smileys
+ body = smilify(body);
+
+ return body;
+}
+
+/**
+ * Finds and replaces all links in the links in "body"
+ * with their
+ */
+function linkify(inputText)
+{
+ var replacedText, replacePattern1, replacePattern2, replacePattern3;
+
+ //URLs starting with http://, https://, or ftp://
+ replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
+ replacedText = inputText.replace(replacePattern1, '$1');
+
+ //URLs starting with "www." (without // before it, or it'd re-link the ones done above).
+ replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
+ replacedText = replacedText.replace(replacePattern2, '$1$2');
+
+ //Change email addresses to mailto:: links.
+ replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
+ replacedText = replacedText.replace(replacePattern3, '$1');
+
+ return replacedText;
+}
+
+/**
+ * Replaces common smiley strings with images
+ */
+function smilify(body)
+{
+ if(!body) {
+ return body;
+ }
+
+ var regexs = Smileys["regexs"];
+ for(var smiley in regexs) {
+ if(regexs.hasOwnProperty(smiley)) {
+ body = body.replace(regexs[smiley],
+ '
');
+ }
+ }
+
+ return body;
+}
+
+module.exports = {
+ processReplacements: processReplacements,
+ linkify: linkify
+};
+
+},{"./smileys.json":24}],24:[function(require,module,exports){
+module.exports={
+ "smileys": {
+ "smiley1": ":)",
+ "smiley2": ":(",
+ "smiley3": ":D",
+ "smiley4": "(y)",
+ "smiley5": " :P",
+ "smiley6": "(wave)",
+ "smiley7": "(blush)",
+ "smiley8": "(chuckle)",
+ "smiley9": "(shocked)",
+ "smiley10": ":*",
+ "smiley11": "(n)",
+ "smiley12": "(search)",
+ "smiley13": " <3",
+ "smiley14": "(oops)",
+ "smiley15": "(angry)",
+ "smiley16": "(angel)",
+ "smiley17": "(sick)",
+ "smiley18": ";(",
+ "smiley19": "(bomb)",
+ "smiley20": "(clap)",
+ "smiley21": " ;)"
+ },
+ "regexs": {
+ "smiley2": /(:-\(\(|:-\(|:\(\(|:\(|\(sad\))/gi,
+ "smiley3": /(:-\)\)|:\)\)|\(lol\)|:-D|:D)/gi,
+ "smiley1": /(:-\)|:\))/gi,
+ "smiley4": /(\(y\)|\(Y\)|\(ok\))/gi,
+ "smiley5": /(:-P|:P|:-p|:p)/gi,
+ "smiley6": /(\(wave\))/gi,
+ "smiley7": /(\(blush\))/gi,
+ "smiley8": /(\(chuckle\))/gi,
+ "smiley9": /(:-0|\(shocked\))/gi,
+ "smiley10": /(:-\*|:\*|\(kiss\))/gi,
+ "smiley11": /(\(n\))/gi,
+ "smiley12": /(\(search\))/g,
+ "smiley13": /(<3|<3|<3|\(L\)|\(l\)|\(H\)|\(h\))/gi,
+ "smiley14": /(\(oops\))/gi,
+ "smiley15": /(\(angry\))/gi,
+ "smiley16": /(\(angel\))/gi,
+ "smiley17": /(\(sick\))/gi,
+ "smiley18": /(;-\(\(|;\(\(|;-\(|;\(|:"\(|:"-\(|:~-\(|:~\(|\(upset\))/gi,
+ "smiley19": /(\(bomb\))/gi,
+ "smiley20": /(\(clap\))/gi,
+ "smiley21": /(;-\)|;\)|;-\)\)|;\)\)|;-D|;D|\(wink\))/gi
+ }
+}
+
+},{}],25:[function(require,module,exports){
+
+var Avatar = require('../../avatar/Avatar');
+
+var numberOfContacts = 0;
+var notificationInterval;
+
+/**
+ * Updates the number of participants in the contact list button and sets
+ * the glow
+ * @param delta indicates whether a new user has joined (1) or someone has
+ * left(-1)
+ */
+function updateNumberOfParticipants(delta) {
+ numberOfContacts += delta;
+ if (numberOfContacts === 1) {
+ // when the user is alone we don't show the number of participants
+ $("#numberOfParticipants").text('');
+ ContactList.setVisualNotification(false);
+ } else if (numberOfContacts > 1) {
+ ContactList.setVisualNotification(!ContactList.isVisible());
+ $("#numberOfParticipants").text(numberOfContacts);
+ } else {
+ console.error("Invalid number of participants: " + numberOfContacts);
+ }
+}
+
+/**
+ * Creates the avatar element.
+ *
+ * @return the newly created avatar element
+ */
+function createAvatar(jid) {
+ var avatar = document.createElement('img');
+ avatar.className = "icon-avatar avatar";
+ avatar.src = Avatar.getContactListUrl(jid);
+
+ return avatar;
+}
+
+/**
+ * Creates the display name paragraph.
+ *
+ * @param displayName the display name to set
+ */
+function createDisplayNameParagraph(key, displayName) {
+ var p = document.createElement('p');
+ if(displayName)
+ p.innerText = displayName;
+ else if(key)
+ {
+ p.setAttribute("data-i18n",key);
+ p.innerText = APP.translation.translateString(key);
+ }
+
+ return p;
+}
+
+
+function stopGlowing(glower) {
+ window.clearInterval(notificationInterval);
+ notificationInterval = false;
+ glower.removeClass('glowing');
+ if (!ContactList.isVisible()) {
+ glower.removeClass('active');
+ }
+}
+
+
+/**
+ * Contact list.
+ */
+var ContactList = {
+ /**
+ * Indicates if the chat is currently visible.
+ *
+ * @return true if the chat is currently visible, false -
+ * otherwise
+ */
+ isVisible: function () {
+ return $('#contactlist').is(":visible");
+ },
+
+ /**
+ * Adds a contact for the given peerJid if such doesn't yet exist.
+ *
+ * @param peerJid the peerJid corresponding to the contact
+ */
+ ensureAddContact: function (peerJid) {
+ var resourceJid = Strophe.getResourceFromJid(peerJid);
+
+ var contact = $('#contacts>li[id="' + resourceJid + '"]');
+
+ if (!contact || contact.length <= 0)
+ ContactList.addContact(peerJid);
+ },
+
+ /**
+ * Adds a contact for the given peer jid.
+ *
+ * @param peerJid the jid of the contact to add
+ */
+ addContact: function (peerJid) {
+ var resourceJid = Strophe.getResourceFromJid(peerJid);
+
+ var contactlist = $('#contacts');
+
+ var newContact = document.createElement('li');
+ newContact.id = resourceJid;
+ newContact.className = "clickable";
+ newContact.onclick = function (event) {
+ if (event.currentTarget.className === "clickable") {
+ $(ContactList).trigger('contactclicked', [peerJid]);
+ }
+ };
+
+ newContact.appendChild(createAvatar(peerJid));
+ newContact.appendChild(createDisplayNameParagraph("participant"));
+
+ if (resourceJid === APP.xmpp.myResource()) {
+ contactlist.prepend(newContact);
+ }
+ else {
+ contactlist.append(newContact);
+ }
+ updateNumberOfParticipants(1);
+ },
+
+ /**
+ * Removes a contact for the given peer jid.
+ *
+ * @param peerJid the peerJid corresponding to the contact to remove
+ */
+ removeContact: function (peerJid) {
+ var resourceJid = Strophe.getResourceFromJid(peerJid);
+
+ var contact = $('#contacts>li[id="' + resourceJid + '"]');
+
+ if (contact && contact.length > 0) {
+ var contactlist = $('#contactlist>ul');
+
+ contactlist.get(0).removeChild(contact.get(0));
+
+ updateNumberOfParticipants(-1);
+ }
+ },
+
+ setVisualNotification: function (show, stopGlowingIn) {
+ var glower = $('#contactListButton');
+
+ if (show && !notificationInterval) {
+ notificationInterval = window.setInterval(function () {
+ glower.toggleClass('active glowing');
+ }, 800);
+ }
+ else if (!show && notificationInterval) {
+ stopGlowing(glower);
+ }
+ if (stopGlowingIn) {
+ setTimeout(function () {
+ stopGlowing(glower);
+ }, stopGlowingIn);
+ }
+ },
+
+ setClickable: function (resourceJid, isClickable) {
+ var contact = $('#contacts>li[id="' + resourceJid + '"]');
+ if (isClickable) {
+ contact.addClass('clickable');
+ } else {
+ contact.removeClass('clickable');
+ }
+ },
+
+ onDisplayNameChange: function (peerJid, displayName) {
+ if (peerJid === 'localVideoContainer')
+ peerJid = APP.xmpp.myJid();
+
+ var resourceJid = Strophe.getResourceFromJid(peerJid);
+
+ var contactName = $('#contacts #' + resourceJid + '>p');
+
+ if (contactName && displayName && displayName.length > 0)
+ contactName.html(displayName);
+ },
+
+ userAvatarChanged: function (resourceJid, contactListUrl) {
+ // set the avatar in the contact list
+ var contact = $('#' + resourceJid + '>img');
+ if (contact && contact.length > 0) {
+ contact.get(0).src = contactListUrl;
+ }
+
+ }
+};
+
+module.exports = ContactList;
+},{"../../avatar/Avatar":16}],26:[function(require,module,exports){
+var Avatar = require("../../avatar/Avatar");
+var Settings = require("./../../../settings/Settings");
+var UIUtil = require("../../util/UIUtil");
+var languages = require("../../../../service/translation/languages");
+
+function generateLanguagesSelectBox()
+{
+ var currentLang = APP.translation.getCurrentLanguage();
+ var html = "";
+}
+
+
+var SettingsMenu = {
+
+ init: function () {
+ $("#startMutedOptions").before(generateLanguagesSelectBox());
+ APP.translation.translateElement($("#languages_selectbox"));
+ $('#settingsmenu>input').keyup(function(event){
+ if(event.keyCode === 13) {//enter
+ SettingsMenu.update();
+ }
+ });
+
+ if(APP.xmpp.isModerator())
+ {
+ $("#startMutedOptions").css("display", "block");
+ }
+ else
+ {
+ $("#startMutedOptions").css("display", "none");
+ }
+
+ $("#updateSettings").click(function () {
+ SettingsMenu.update();
+ });
+ },
+
+ onRoleChanged: function () {
+ if(APP.xmpp.isModerator())
+ {
+ $("#startMutedOptions").css("display", "block");
+ }
+ else
+ {
+ $("#startMutedOptions").css("display", "none");
+ }
+ },
+
+ setStartMuted: function (audio, video) {
+ $("#startAudioMuted").attr("checked", audio);
+ $("#startVideoMuted").attr("checked", video);
+ },
+
+ update: function() {
+ var newDisplayName = UIUtil.escapeHtml($('#setDisplayName').get(0).value);
+ var newEmail = UIUtil.escapeHtml($('#setEmail').get(0).value);
+
+ if(newDisplayName) {
+ var displayName = Settings.setDisplayName(newDisplayName);
+ APP.xmpp.addToPresence("displayName", displayName, true);
+ }
+
+ var language = $("#languages_selectbox").val();
+ APP.translation.setLanguage(language);
+ Settings.setLanguage(language);
+
+ APP.xmpp.addToPresence("email", newEmail);
+ var email = Settings.setEmail(newEmail);
+
+ var startAudioMuted = ($("#startAudioMuted").is(":checked"));
+ var startVideoMuted = ($("#startVideoMuted").is(":checked"));
+ APP.xmpp.addToPresence("startMuted",
+ [startAudioMuted, startVideoMuted]);
+
+ Avatar.setUserAvatar(APP.xmpp.myJid(), email);
+ },
+
+ isVisible: function() {
+ return $('#settingsmenu').is(':visible');
+ },
+
+ setDisplayName: function(newDisplayName) {
+ var displayName = Settings.setDisplayName(newDisplayName);
+ $('#setDisplayName').get(0).value = displayName;
+ },
+
+ onDisplayNameChange: function(peerJid, newDisplayName) {
+ if(peerJid === 'localVideoContainer' ||
+ peerJid === APP.xmpp.myJid()) {
+ this.setDisplayName(newDisplayName);
+ }
+ },
+ changeAvatar: function (thumbUrl) {
+ $('#avatar').get(0).src = thumbUrl;
+ }
+};
+
+
+module.exports = SettingsMenu;
+},{"../../../../service/translation/languages":111,"../../avatar/Avatar":16,"../../util/UIUtil":33,"./../../../settings/Settings":47}],27:[function(require,module,exports){
+var PanelToggler = require("../side_pannels/SidePanelToggler");
+
+var buttonHandlers = {
+ "bottom_toolbar_contact_list": function () {
+ BottomToolbar.toggleContactList();
+ },
+ "bottom_toolbar_film_strip": function () {
+ BottomToolbar.toggleFilmStrip();
+ },
+ "bottom_toolbar_chat": function () {
+ BottomToolbar.toggleChat();
+ }
+};
+
+var BottomToolbar = (function (my) {
+ my.init = function () {
+ for(var k in buttonHandlers)
+ $("#" + k).click(buttonHandlers[k]);
+ };
+
+ my.toggleChat = function() {
+ PanelToggler.toggleChat();
+ };
+
+ my.toggleContactList = function() {
+ PanelToggler.toggleContactList();
+ };
+
+ my.toggleFilmStrip = function() {
+ var filmstrip = $("#remoteVideos");
+ filmstrip.toggleClass("hidden");
+ };
+
+ $(document).bind("remotevideo.resized", function (event, width, height) {
+ var bottom = (height - $('#bottomToolbar').outerHeight())/2 + 18;
+
+ $('#bottomToolbar').css({bottom: bottom + 'px'});
+ });
+
+ return my;
+}(BottomToolbar || {}));
+
+module.exports = BottomToolbar;
+
+},{"../side_pannels/SidePanelToggler":20}],28:[function(require,module,exports){
+/* global APP,$, buttonClick, config, lockRoom,
+ setSharedKey, Util */
+var messageHandler = require("../util/MessageHandler");
+var BottomToolbar = require("./BottomToolbar");
+var Prezi = require("../prezi/Prezi");
+var Etherpad = require("../etherpad/Etherpad");
+var PanelToggler = require("../side_pannels/SidePanelToggler");
+var Authentication = require("../authentication/Authentication");
+var UIUtil = require("../util/UIUtil");
+var AuthenticationEvents
+ = require("../../../service/authentication/AuthenticationEvents");
+
+var roomUrl = null;
+var sharedKey = '';
+var UI = null;
+
+var buttonHandlers =
+{
+ "toolbar_button_mute": function () {
+ return APP.UI.toggleAudio();
+ },
+ "toolbar_button_camera": function () {
+ return APP.UI.toggleVideo();
+ },
+ /*"toolbar_button_authentication": function () {
+ return Toolbar.authenticateClicked();
+ },*/
+ "toolbar_button_record": function () {
+ return toggleRecording();
+ },
+ "toolbar_button_security": function () {
+ return Toolbar.openLockDialog();
+ },
+ "toolbar_button_link": function () {
+ return Toolbar.openLinkDialog();
+ },
+ "toolbar_button_chat": function () {
+ return BottomToolbar.toggleChat();
+ },
+ "toolbar_button_prezi": function () {
+ return Prezi.openPreziDialog();
+ },
+ "toolbar_button_etherpad": function () {
+ return Etherpad.toggleEtherpad(0);
+ },
+ "toolbar_button_desktopsharing": function () {
+ return APP.desktopsharing.toggleScreenSharing();
+ },
+ "toolbar_button_fullScreen": function()
+ {
+ UIUtil.buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");
+ return Toolbar.toggleFullScreen();
+ },
+ "toolbar_button_sip": function () {
+ return callSipButtonClicked();
+ },
+ "toolbar_button_dialpad": function () {
+ return dialpadButtonClicked();
+ },
+ "toolbar_button_settings": function () {
+ PanelToggler.toggleSettingsMenu();
+ },
+ "toolbar_button_hangup": function () {
+ return hangup();
+ },
+ "toolbar_button_login": function () {
+ Toolbar.authenticateClicked();
+ },
+ "toolbar_button_logout": function () {
+ // Ask for confirmation
+ messageHandler.openTwoButtonDialog(
+ "dialog.logoutTitle",
+ null,
+ "dialog.logoutQuestion",
+ null,
+ false,
+ "dialog.Yes",
+ function (evt, yes) {
+ if (yes) {
+ APP.xmpp.logout(function (url) {
+ if (url) {
+ window.location.href = url;
+ } else {
+ hangup();
+ }
+ });
+ }
+ });
+ }
+};
+
+function hangup() {
+ APP.xmpp.disposeConference();
+ if(config.enableWelcomePage)
+ {
+ setTimeout(function()
+ {
+ window.localStorage.welcomePageDisabled = false;
+ window.location.pathname = "/";
+ }, 10000);
+
+ }
+
+ var title = APP.translation.generateTranslationHTML(
+ "dialog.sessTerminated");
+ var msg = APP.translation.generateTranslationHTML(
+ "dialog.hungUp");
+ var button = APP.translation.generateTranslationHTML(
+ "dialog.joinAgain");
+ var buttons = [];
+ buttons.push({title: button, value: true});
+
+ UI.messageHandler.openDialog(
+ title,
+ msg,
+ true,
+ buttons,
+ function(event, value, message, formVals)
+ {
+ window.location.reload();
+ return false;
+ }
+ );
+}
+
+/**
+ * Starts or stops the recording for the conference.
+ */
+
+function toggleRecording() {
+ APP.xmpp.toggleRecording(function (callback) {
+ var msg = APP.translation.generateTranslationHTML(
+ "dialog.recordingToken");
+ var token = APP.translation.translateString("dialog.token");
+ APP.UI.messageHandler.openTwoButtonDialog(null, null, null,
+ '' + msg + '
' +
+ '',
+ false,
+ "dialog.Save",
+ function (e, v, m, f) {
+ if (v) {
+ var token = f.recordingToken;
+
+ if (token) {
+ callback(UIUtil.escapeHtml(token));
+ }
+ }
+ },
+ null,
+ function () { },
+ ':input:first'
+ );
+ }, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState);
+}
+
+/**
+ * Locks / unlocks the room.
+ */
+function lockRoom(lock) {
+ var currentSharedKey = '';
+ if (lock)
+ currentSharedKey = sharedKey;
+
+ APP.xmpp.lockRoom(currentSharedKey, function (res) {
+ // password is required
+ if (sharedKey)
+ {
+ console.log('set room password');
+ Toolbar.lockLockButton();
+ }
+ else
+ {
+ console.log('removed room password');
+ Toolbar.unlockLockButton();
+ }
+ }, function (err) {
+ console.warn('setting password failed', err);
+ messageHandler.showError("dialog.lockTitle",
+ "dialog.lockMessage");
+ Toolbar.setSharedKey('');
+ }, function () {
+ console.warn('room passwords not supported');
+ messageHandler.showError("dialog.warning",
+ "dialog.passwordNotSupported");
+ Toolbar.setSharedKey('');
+ });
+};
+
+/**
+ * Invite participants to conference.
+ */
+function inviteParticipants() {
+ if (roomUrl === null)
+ return;
+
+ var sharedKeyText = "";
+ if (sharedKey && sharedKey.length > 0) {
+ sharedKeyText =
+ APP.translation.translateString("email.sharedKey",
+ {sharedKey: sharedKey});
+ sharedKeyText = sharedKeyText.replace(/\n/g, "%0D%0A");
+ }
+
+ var supportedBrowsers = "Chromium, Google Chrome " +
+ APP.translation.translateString("email.and") + " Opera";
+ var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1);
+ var subject = APP.translation.translateString("email.subject",
+ {appName:interfaceConfig.APP_NAME, conferenceName: conferenceName});
+ var body = APP.translation.translateString("email.body",
+ {appName:interfaceConfig.APP_NAME, sharedKeyText: sharedKeyText,
+ roomUrl: roomUrl, supportedBrowsers: supportedBrowsers});
+ body = body.replace(/\n/g, "%0D%0A");
+
+ if (window.localStorage.displayname) {
+ body += "%0D%0A%0D%0A" + window.localStorage.displayname;
+ }
+
+ if (interfaceConfig.INVITATION_POWERED_BY) {
+ body += "%0D%0A%0D%0A--%0D%0Apowered by jitsi.org";
+ }
+
+ window.open("mailto:?subject=" + subject + "&body=" + body, '_blank');
+}
+
+function dialpadButtonClicked()
+{
+ //TODO show the dialpad window
+}
+
+function callSipButtonClicked()
+{
+ var defaultNumber
+ = config.defaultSipNumber ? config.defaultSipNumber : '';
+
+ var sipMsg = APP.translation.generateTranslationHTML(
+ "dialog.sipMsg");
+ messageHandler.openTwoButtonDialog(null, null, null,
+ '' + sipMsg + '
' +
+ '',
+ false,
+ "dialog.Dial",
+ function (e, v, m, f) {
+ if (v) {
+ var numberInput = f.sipNumber;
+ if (numberInput) {
+ APP.xmpp.dial(
+ numberInput, 'fromnumber', UI.getRoomName(), sharedKey);
+ }
+ }
+ },
+ null, null, ':input:first'
+ );
+}
+
+var Toolbar = (function (my) {
+
+ my.init = function (ui) {
+ for(var k in buttonHandlers)
+ $("#" + k).click(buttonHandlers[k]);
+ UI = ui;
+ // Update login info
+ APP.xmpp.addListener(
+ AuthenticationEvents.IDENTITY_UPDATED,
+ function (authenticationEnabled, userIdentity) {
+
+ var loggedIn = false;
+ if (userIdentity) {
+ loggedIn = true;
+ }
+
+ Toolbar.showAuthenticateButton(authenticationEnabled);
+
+ if (authenticationEnabled) {
+ Toolbar.setAuthenticatedIdentity(userIdentity);
+
+ Toolbar.showLoginButton(!loggedIn);
+ Toolbar.showLogoutButton(loggedIn);
+ }
+ }
+ );
+ },
+
+ /**
+ * Sets shared key
+ * @param sKey the shared key
+ */
+ my.setSharedKey = function (sKey) {
+ sharedKey = sKey;
+ };
+
+ my.authenticateClicked = function () {
+ Authentication.focusAuthenticationWindow();
+ if (!APP.xmpp.isExternalAuthEnabled()) {
+ Authentication.xmppAuthenticate();
+ return;
+ }
+ // Get authentication URL
+ if (!APP.xmpp.getMUCJoined()) {
+ APP.xmpp.getLoginUrl(UI.getRoomName(), function (url) {
+ // If conference has not been started yet - redirect to login page
+ window.location.href = url;
+ });
+ } else {
+ APP.xmpp.getPopupLoginUrl(UI.getRoomName(), function (url) {
+ // Otherwise - open popup with authentication URL
+ var authenticationWindow = Authentication.createAuthenticationWindow(
+ function () {
+ // On popup closed - retry room allocation
+ APP.xmpp.allocateConferenceFocus(
+ APP.UI.getRoomName(),
+ function () { console.info("AUTH DONE"); }
+ );
+ }, url);
+ if (!authenticationWindow) {
+ messageHandler.openMessageDialog(
+ null, "dialog.popupError");
+ }
+ });
+ }
+ };
+
+ /**
+ * Updates the room invite url.
+ */
+ my.updateRoomUrl = function (newRoomUrl) {
+ roomUrl = newRoomUrl;
+
+ // If the invite dialog has been already opened we update the information.
+ var inviteLink = document.getElementById('inviteLinkRef');
+ if (inviteLink) {
+ inviteLink.value = roomUrl;
+ inviteLink.select();
+ $('#inviteLinkRef').parent()
+ .find('button[value=true]').prop('disabled', false);
+ }
+ };
+
+ /**
+ * Disables and enables some of the buttons.
+ */
+ my.setupButtonsFromConfig = function () {
+ if (config.disablePrezi)
+ {
+ $("#prezi_button").css({display: "none"});
+ }
+ };
+
+ /**
+ * Opens the lock room dialog.
+ */
+ my.openLockDialog = function () {
+ // Only the focus is able to set a shared key.
+ if (!APP.xmpp.isModerator()) {
+ if (sharedKey) {
+ messageHandler.openMessageDialog(null,
+ "dialog.passwordError");
+ } else {
+ messageHandler.openMessageDialog(null, "dialog.passwordError2");
+ }
+ } else {
+ if (sharedKey) {
+ messageHandler.openTwoButtonDialog(null, null,
+ "dialog.passwordCheck",
+ null,
+ false,
+ "dialog.Remove",
+ function (e, v) {
+ if (v) {
+ Toolbar.setSharedKey('');
+ lockRoom(false);
+ }
+ });
+ } else {
+ var msg = APP.translation.generateTranslationHTML(
+ "dialog.passwordMsg");
+ var yourPassword = APP.translation.translateString(
+ "dialog.yourPassword");
+ messageHandler.openTwoButtonDialog(null, null, null,
+ '' + msg + '
' +
+ '',
+ false,
+ "dialog.Save",
+ function (e, v, m, f) {
+ if (v) {
+ var lockKey = f.lockKey;
+
+ if (lockKey) {
+ Toolbar.setSharedKey(
+ UIUtil.escapeHtml(lockKey));
+ lockRoom(true);
+ }
+ }
+ },
+ null, null, 'input:first'
+ );
+ }
+ }
+ };
+
+ /**
+ * Opens the invite link dialog.
+ */
+ my.openLinkDialog = function () {
+ var inviteAttreibutes;
+
+ if (roomUrl === null) {
+ inviteAttreibutes = 'data-i18n="[value]roomUrlDefaultMsg" value="' +
+ APP.translation.translateString("roomUrlDefaultMsg") + '"';
+ } else {
+ inviteAttreibutes = "value=\"" + encodeURI(roomUrl) + "\"";
+ }
+ messageHandler.openTwoButtonDialog("dialog.shareLink",
+ null, null,
+ '',
+ false,
+ "dialog.Invite",
+ function (e, v) {
+ if (v) {
+ if (roomUrl) {
+ inviteParticipants();
+ }
+ }
+ },
+ function (event) {
+ if (roomUrl) {
+ document.getElementById('inviteLinkRef').select();
+ } else {
+ if (event && event.target)
+ $(event.target)
+ .find('button[value=true]').prop('disabled', true);
+ }
+ }
+ );
+ };
+
+ /**
+ * Opens the settings dialog.
+ * FIXME: not used ?
+ */
+ my.openSettingsDialog = function () {
+ var settings1 = APP.translation.generateTranslationHTML(
+ "dialog.settings1");
+ var settings2 = APP.translation.generateTranslationHTML(
+ "dialog.settings2");
+ var settings3 = APP.translation.generateTranslationHTML(
+ "dialog.settings3");
+
+ var yourPassword = APP.translation.translateString(
+ "dialog.yourPassword");
+
+ messageHandler.openTwoButtonDialog(null,
+ '' + settings1 + '
' +
+ '' +
+ settings2 + '
' +
+ '' +
+ settings3 +
+ '',
+ null,
+ null,
+ false,
+ "dialog.Save",
+ function () {
+ document.getElementById('lockKey').focus();
+ },
+ function (e, v) {
+ if (v) {
+ if ($('#initMuted').is(":checked")) {
+ // it is checked
+ }
+
+ if ($('#requireNicknames').is(":checked")) {
+ // it is checked
+ }
+ /*
+ var lockKey = document.getElementById('lockKey');
+
+ if (lockKey.value) {
+ setSharedKey(lockKey.value);
+ lockRoom(true);
+ }
+ */
+ }
+ }
+ );
+ };
+
+ /**
+ * Toggles the application in and out of full screen mode
+ * (a.k.a. presentation mode in Chrome).
+ */
+ my.toggleFullScreen = function () {
+ var fsElement = document.documentElement;
+
+ if (!document.mozFullScreen && !document.webkitIsFullScreen) {
+ //Enter Full Screen
+ if (fsElement.mozRequestFullScreen) {
+ fsElement.mozRequestFullScreen();
+ }
+ else {
+ fsElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
+ }
+ } else {
+ //Exit Full Screen
+ if (document.mozCancelFullScreen) {
+ document.mozCancelFullScreen();
+ } else {
+ document.webkitCancelFullScreen();
+ }
+ }
+ };
+ /**
+ * Unlocks the lock button state.
+ */
+ my.unlockLockButton = function () {
+ if ($("#lockIcon").hasClass("icon-security-locked"))
+ UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
+ };
+ /**
+ * Updates the lock button state to locked.
+ */
+ my.lockLockButton = function () {
+ if ($("#lockIcon").hasClass("icon-security"))
+ UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
+ };
+
+ /**
+ * Shows or hides authentication button
+ * @param show true to show or false to hide
+ */
+ my.showAuthenticateButton = function (show) {
+ if (show) {
+ $('#authentication').css({display: "inline"});
+ }
+ else {
+ $('#authentication').css({display: "none"});
+ }
+ };
+
+ // Shows or hides the 'recording' button.
+ my.showRecordingButton = function (show) {
+ if (!config.enableRecording) {
+ return;
+ }
+
+ if (show) {
+ $('#recording').css({display: "inline"});
+ }
+ else {
+ $('#recording').css({display: "none"});
+ }
+ };
+
+ // Sets the state of the recording button
+ my.setRecordingButtonState = function (isRecording) {
+ var selector = $('#recordButton');
+ if (isRecording) {
+ selector.removeClass("icon-recEnable");
+ selector.addClass("icon-recEnable active");
+ } else {
+ selector.removeClass("icon-recEnable active");
+ selector.addClass("icon-recEnable");
+ }
+ };
+
+ // Shows or hides SIP calls button
+ my.showSipCallButton = function (show) {
+ if (APP.xmpp.isSipGatewayEnabled() && show) {
+ $('#sipCallButton').css({display: "inline-block"});
+ } else {
+ $('#sipCallButton').css({display: "none"});
+ }
+ };
+
+ // Shows or hides the dialpad button
+ my.showDialPadButton = function (show) {
+ if (show) {
+ $('#dialPadButton').css({display: "inline-block"});
+ } else {
+ $('#dialPadButton').css({display: "none"});
+ }
+ };
+
+ /**
+ * Displays user authenticated identity name(login).
+ * @param authIdentity identity name to be displayed.
+ */
+ my.setAuthenticatedIdentity = function (authIdentity) {
+ if (authIdentity) {
+ var selector = $('#toolbar_auth_identity');
+ selector.css({display: "list-item"});
+ selector.text(authIdentity);
+ } else {
+ $('#toolbar_auth_identity').css({display: "none"});
+ }
+ };
+
+ /**
+ * Shows/hides login button.
+ * @param show true to show
+ */
+ my.showLoginButton = function (show) {
+ if (show) {
+ $('#toolbar_button_login').css({display: "list-item"});
+ } else {
+ $('#toolbar_button_login').css({display: "none"});
+ }
+ };
+
+ /**
+ * Shows/hides logout button.
+ * @param show true to show
+ */
+ my.showLogoutButton = function (show) {
+ if (show) {
+ $('#toolbar_button_logout').css({display: "list-item"});
+ } else {
+ $('#toolbar_button_logout').css({display: "none"});
+ }
+ };
+
+ /**
+ * Sets the state of the button. The button has blue glow if desktop
+ * streaming is active.
+ * @param active the state of the desktop streaming.
+ */
+ my.changeDesktopSharingButtonState = function (active) {
+ var button = $("#desktopsharing > a");
+ if (active)
+ {
+ button.addClass("glow");
+ }
+ else
+ {
+ button.removeClass("glow");
+ }
+ };
+
+ return my;
+}(Toolbar || {}));
+
+module.exports = Toolbar;
+},{"../../../service/authentication/AuthenticationEvents":107,"../authentication/Authentication":14,"../etherpad/Etherpad":17,"../prezi/Prezi":18,"../side_pannels/SidePanelToggler":20,"../util/MessageHandler":31,"../util/UIUtil":33,"./BottomToolbar":27}],29:[function(require,module,exports){
+/* global $, interfaceConfig, Moderator, DesktopStreaming.showDesktopSharingButton */
+
+var toolbarTimeoutObject,
+ toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
+
+function showDesktopSharingButton() {
+ if (APP.desktopsharing.isDesktopSharingEnabled()) {
+ $('#desktopsharing').css({display: "inline"});
+ } else {
+ $('#desktopsharing').css({display: "none"});
+ }
+}
+
+/**
+ * Hides the toolbar.
+ */
+function hideToolbar() {
+ if(config.alwaysVisibleToolbar)
+ return;
+
+ var header = $("#header"),
+ bottomToolbar = $("#bottomToolbar");
+ var isToolbarHover = false;
+ header.find('*').each(function () {
+ var id = $(this).attr('id');
+ if ($("#" + id + ":hover").length > 0) {
+ isToolbarHover = true;
+ }
+ });
+ if ($("#bottomToolbar:hover").length > 0) {
+ isToolbarHover = true;
+ }
+
+ clearTimeout(toolbarTimeoutObject);
+ toolbarTimeoutObject = null;
+
+ if (!isToolbarHover) {
+ header.hide("slide", { direction: "up", duration: 300});
+ $('#subject').animate({top: "-=40"}, 300);
+ if ($("#remoteVideos").hasClass("hidden")) {
+ bottomToolbar.hide(
+ "slide", {direction: "right", duration: 300});
+ }
+ }
+ else {
+ toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
+ }
+}
+
+var ToolbarToggler = {
+ /**
+ * Shows the main toolbar.
+ */
+ showToolbar: function () {
+ var header = $("#header"),
+ bottomToolbar = $("#bottomToolbar");
+ if (!header.is(':visible') || !bottomToolbar.is(":visible")) {
+ header.show("slide", { direction: "up", duration: 300});
+ $('#subject').animate({top: "+=40"}, 300);
+ if (!bottomToolbar.is(":visible")) {
+ bottomToolbar.show(
+ "slide", {direction: "right", duration: 300});
+ }
+
+ if (toolbarTimeoutObject) {
+ clearTimeout(toolbarTimeoutObject);
+ toolbarTimeoutObject = null;
+ }
+ toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
+ toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
+ }
+
+ if (APP.xmpp.isModerator())
+ {
+// TODO: Enable settings functionality.
+// Need to uncomment the settings button in index.html.
+// $('#settingsButton').css({visibility:"visible"});
+ }
+
+ // Show/hide desktop sharing button
+ showDesktopSharingButton();
+ },
+
+
+ /**
+ * Docks/undocks the toolbar.
+ *
+ * @param isDock indicates what operation to perform
+ */
+ dockToolbar: function (isDock) {
+ if (isDock) {
+ // First make sure the toolbar is shown.
+ if (!$('#header').is(':visible')) {
+ this.showToolbar();
+ }
+
+ // Then clear the time out, to dock the toolbar.
+ if (toolbarTimeoutObject) {
+ clearTimeout(toolbarTimeoutObject);
+ toolbarTimeoutObject = null;
+ }
+ }
+ else {
+ if (!$('#header').is(':visible')) {
+ this.showToolbar();
+ }
+ else {
+ toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
+ }
+ }
+ },
+
+ showDesktopSharingButton: showDesktopSharingButton
+
+};
+
+module.exports = ToolbarToggler;
+},{}],30:[function(require,module,exports){
+var JitsiPopover = (function () {
+ /**
+ * Constructs new JitsiPopover and attaches it to the element
+ * @param element jquery selector
+ * @param options the options for the popover.
+ * @constructor
+ */
+ function JitsiPopover(element, options)
+ {
+ this.options = {
+ skin: "white",
+ content: ""
+ };
+ if(options)
+ {
+ if(options.skin)
+ this.options.skin = options.skin;
+
+ if(options.content)
+ this.options.content = options.content;
+ }
+
+ this.elementIsHovered = false;
+ this.popoverIsHovered = false;
+ this.popoverShown = false;
+
+ element.data("jitsi_popover", this);
+ this.element = element;
+ this.template = ' ';
+ var self = this;
+ this.element.on("mouseenter", function () {
+ self.elementIsHovered = true;
+ self.show();
+ }).on("mouseleave", function () {
+ self.elementIsHovered = false;
+ setTimeout(function () {
+ self.hide();
+ }, 10);
+ });
+ }
+
+ /**
+ * Shows the popover
+ */
+ JitsiPopover.prototype.show = function () {
+ this.createPopover();
+ this.popoverShown = true;
+
+ };
+
+ /**
+ * Hides the popover
+ */
+ JitsiPopover.prototype.hide = function () {
+ if(!this.elementIsHovered && !this.popoverIsHovered && this.popoverShown)
+ {
+ this.forceHide();
+ }
+ };
+
+ /**
+ * Hides the popover
+ */
+ JitsiPopover.prototype.forceHide = function () {
+ $(".jitsipopover").remove();
+ this.popoverShown = false;
+ };
+
+ /**
+ * Creates the popover html
+ */
+ JitsiPopover.prototype.createPopover = function () {
+ $("body").append(this.template);
+ $(".jitsipopover > .jitsipopover-content").html(this.options.content);
+ var self = this;
+ $(".jitsipopover").on("mouseenter", function () {
+ self.popoverIsHovered = true;
+ }).on("mouseleave", function () {
+ self.popoverIsHovered = false;
+ self.hide();
+ });
+
+ this.refreshPosition();
+ };
+
+ /**
+ * Refreshes the position of the popover
+ */
+ JitsiPopover.prototype.refreshPosition = function () {
+ $(".jitsipopover").position({
+ my: "bottom",
+ at: "top",
+ collision: "fit",
+ of: this.element,
+ using: function (position, elements) {
+ var calcLeft = elements.target.left - elements.element.left + elements.target.width/2;
+ $(".jitsipopover").css({top: position.top, left: position.left, display: "table"});
+ $(".jitsipopover > .arrow").css({left: calcLeft});
+ $(".jitsipopover > .jitsiPopupmenuPadding").css({left: calcLeft - 50});
+ }
+ });
+ };
+
+ /**
+ * Updates the content of popover.
+ * @param content new content
+ */
+ JitsiPopover.prototype.updateContent = function (content) {
+ this.options.content = content;
+ if(!this.popoverShown)
+ return;
+ $(".jitsipopover").remove();
+ this.createPopover();
+ };
+
+ return JitsiPopover;
+
+
+})();
+
+module.exports = JitsiPopover;
+},{}],31:[function(require,module,exports){
+/* global $, APP, jQuery, toastr */
+var messageHandler = (function(my) {
+
+ /**
+ * Shows a message to the user.
+ *
+ * @param titleString the title of the message
+ * @param messageString the text of the message
+ */
+ my.openMessageDialog = function(titleKey, messageKey) {
+ var title = null;
+ if(titleKey)
+ {
+ title = APP.translation.generateTranslationHTML(titleKey);
+ }
+ var message = APP.translation.generateTranslationHTML(messageKey);
+ $.prompt(message,
+ {
+ title: title,
+ persistent: false
+ }
+ );
+ };
+
+ /**
+ * Shows a message to the user with two buttons: first is given as a parameter and the second is Cancel.
+ *
+ * @param titleString the title of the message
+ * @param msgString the text of the message
+ * @param persistent boolean value which determines whether the message is persistent or not
+ * @param leftButton the fist button's text
+ * @param submitFunction function to be called on submit
+ * @param loadedFunction function to be called after the prompt is fully loaded
+ * @param closeFunction function to be called after the prompt is closed
+ * @param focus optional focus selector or button index to be focused after
+ * the dialog is opened
+ * @param defaultButton index of default button which will be activated when
+ * the user press 'enter'. Indexed from 0.
+ */
+ my.openTwoButtonDialog = function(titleKey, titleString, msgKey, msgString,
+ persistent, leftButtonKey, submitFunction, loadedFunction,
+ closeFunction, focus, defaultButton)
+ {
+ var buttons = [];
+
+ var leftButton = APP.translation.generateTranslationHTML(leftButtonKey);
+ buttons.push({ title: leftButton, value: true});
+
+ var cancelButton
+ = APP.translation.generateTranslationHTML("dialog.Cancel");
+ buttons.push({title: cancelButton, value: false});
+
+ var message = msgString, title = titleString;
+ if (titleKey)
+ {
+ title = APP.translation.generateTranslationHTML(titleKey);
+ }
+ if (msgKey) {
+ message = APP.translation.generateTranslationHTML(msgKey);
+ }
+ $.prompt(message, {
+ title: title,
+ persistent: false,
+ buttons: buttons,
+ defaultButton: defaultButton,
+ focus: focus,
+ loaded: loadedFunction,
+ submit: submitFunction,
+ close: closeFunction
+ });
+ };
+
+ /**
+ * Shows a message to the user with two buttons: first is given as a parameter and the second is Cancel.
+ *
+ * @param titleString the title of the message
+ * @param msgString the text of the message
+ * @param persistent boolean value which determines whether the message is persistent or not
+ * @param buttons object with the buttons. The keys must be the name of the button and value is the value
+ * that will be passed to submitFunction
+ * @param submitFunction function to be called on submit
+ * @param loadedFunction function to be called after the prompt is fully loaded
+ */
+ my.openDialog = function (titleString, msgString, persistent, buttons,
+ submitFunction, loadedFunction) {
+ var args = {
+ title: titleString,
+ persistent: persistent,
+ buttons: buttons,
+ defaultButton: 1,
+ loaded: loadedFunction,
+ submit: submitFunction
+ };
+ if (persistent) {
+ args.closeText = '';
+ }
+ return new Impromptu(msgString, args);
+ };
+
+ /**
+ * Closes currently opened dialog.
+ */
+ my.closeDialog = function () {
+ $.prompt.close();
+ };
+
+ /**
+ * Shows a dialog with different states to the user.
+ *
+ * @param statesObject object containing all the states of the dialog
+ */
+ my.openDialogWithStates = function (statesObject, options) {
+
+ return new Impromptu(statesObject, options);
+ };
+
+ /**
+ * Opens new popup window for given url centered over current
+ * window.
+ *
+ * @param url the URL to be displayed in the popup window
+ * @param w the width of the popup window
+ * @param h the height of the popup window
+ * @param onPopupClosed optional callback function called when popup window
+ * has been closed.
+ *
+ * @returns popup window object if opened successfully or undefined
+ * in case we failed to open it(popup blocked)
+ */
+ my.openCenteredPopup = function (url, w, h, onPopupClosed) {
+ var l = window.screenX + (window.innerWidth / 2) - (w / 2);
+ var t = window.screenY + (window.innerHeight / 2) - (h / 2);
+ var popup = window.open(
+ url, '_blank',
+ 'top=' + t + ', left=' + l + ', width=' + w + ', height=' + h + '');
+ if (popup && onPopupClosed) {
+ var pollTimer = window.setInterval(function () {
+ if (popup.closed !== false) {
+ window.clearInterval(pollTimer);
+ onPopupClosed();
+ }
+ }, 200);
+ }
+ return popup;
+ };
+
+ /**
+ * Shows a dialog prompting the user to send an error report.
+ *
+ * @param titleString the title of the message
+ * @param msgString the text of the message
+ * @param error the error that is being reported
+ */
+ my.openReportDialog = function(titleKey, msgKey, error) {
+ my.openMessageDialog(titleKey, msgKey);
+ console.log(error);
+ //FIXME send the error to the server
+ };
+
+ /**
+ * Shows an error dialog to the user.
+ * @param title the title of the message
+ * @param message the text of the messafe
+ */
+ my.showError = function(titleKey, msgKey) {
+
+ if(!titleKey) {
+ titleKey = "dialog.oops";
+ }
+ if(!msgKey)
+ {
+ msgKey = "dialog.defaultError";
+ }
+ messageHandler.openMessageDialog(titleKey, msgKey);
+ };
+
+ my.notify = function(displayName, displayNameKey,
+ cls, messageKey, messageArguments, options) {
+ var displayNameSpan = '" + displayName;
+ }
+ else
+ {
+ displayNameSpan += "data-i18n='" + displayNameKey +
+ "'>" + APP.translation.translateString(displayNameKey);
+ }
+ displayNameSpan += "";
+ toastr.info(
+ displayNameSpan + '
' +
+ '" +
+ APP.translation.translateString(messageKey,
+ messageArguments) +
+ '', null, options);
+ };
+
+ return my;
+}(messageHandler || {}));
+
+module.exports = messageHandler;
+
+
+
+},{}],32:[function(require,module,exports){
+var UIEvents = require("../../../service/UI/UIEvents");
+
+var nickname = null;
+var eventEmitter = null;
+
+var NickanameHandler = {
+ init: function (emitter) {
+ eventEmitter = emitter;
+ var storedDisplayName = window.localStorage.displayname;
+ if (storedDisplayName) {
+ nickname = storedDisplayName;
+ }
+ },
+ setNickname: function (newNickname) {
+ if (!newNickname || nickname === newNickname)
+ return;
+
+ nickname = newNickname;
+ window.localStorage.displayname = nickname;
+ eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newNickname);
+ },
+ getNickname: function () {
+ return nickname;
+ },
+ addListener: function (type, listener) {
+ eventEmitter.on(type, listener);
+ }
+};
+
+module.exports = NickanameHandler;
+},{"../../../service/UI/UIEvents":106}],33:[function(require,module,exports){
+/**
+ * Created by hristo on 12/22/14.
+ */
+module.exports = {
+ /**
+ * Returns the available video width.
+ */
+ getAvailableVideoWidth: function (isVisible) {
+ var PanelToggler = require("../side_pannels/SidePanelToggler");
+ if(typeof isVisible === "undefined" || isVisible === null)
+ isVisible = PanelToggler.isVisible();
+ var rightPanelWidth
+ = isVisible ? PanelToggler.getPanelSize()[0] : 0;
+
+ return window.innerWidth - rightPanelWidth;
+ },
+ /**
+ * Changes the style class of the element given by id.
+ */
+ buttonClick: function(id, classname) {
+ $(id).toggleClass(classname); // add the class to the clicked element
+ },
+ /**
+ * Returns the text width for the given element.
+ *
+ * @param el the element
+ */
+ getTextWidth: function (el) {
+ return (el.clientWidth + 1);
+ },
+
+ /**
+ * Returns the text height for the given element.
+ *
+ * @param el the element
+ */
+ getTextHeight: function (el) {
+ return (el.clientHeight + 1);
+ },
+
+ /**
+ * Plays the sound given by id.
+ *
+ * @param id the identifier of the audio element.
+ */
+ playSoundNotification: function (id) {
+ document.getElementById(id).play();
+ },
+
+ /**
+ * Escapes the given text.
+ */
+ escapeHtml: function (unsafeText) {
+ return $('').text(unsafeText).html();
+ },
+
+ imageToGrayScale: function (canvas) {
+ var context = canvas.getContext('2d');
+ var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
+ var pixels = imgData.data;
+
+ for (var i = 0, n = pixels.length; i < n; i += 4) {
+ var grayscale
+ = pixels[i] * .3 + pixels[i+1] * .59 + pixels[i+2] * .11;
+ pixels[i ] = grayscale; // red
+ pixels[i+1] = grayscale; // green
+ pixels[i+2] = grayscale; // blue
+ // pixels[i+3] is alpha
+ }
+ // redraw the image in black & white
+ context.putImageData(imgData, 0, 0);
+ },
+
+ setTooltip: function (element, key, position) {
+ element.setAttribute("data-i18n", "[data-content]" + key);
+ element.setAttribute("data-toggle", "popover");
+ element.setAttribute("data-placement", position);
+ element.setAttribute("data-html", true);
+ element.setAttribute("data-container", "body");
+ }
+
+
+};
+},{"../side_pannels/SidePanelToggler":20}],34:[function(require,module,exports){
+var JitsiPopover = require("../util/JitsiPopover");
+
+/**
+ * Constructs new connection indicator.
+ * @param videoContainer the video container associated with the indicator.
+ * @constructor
+ */
+function ConnectionIndicator(videoContainer, jid)
+{
+ this.videoContainer = videoContainer;
+ this.bandwidth = null;
+ this.packetLoss = null;
+ this.bitrate = null;
+ this.showMoreValue = false;
+ this.resolution = null;
+ this.transport = [];
+ this.popover = null;
+ this.jid = jid;
+ this.create();
+}
+
+/**
+ * Values for the connection quality
+ * @type {{98: string,
+ * 81: string,
+ * 64: string,
+ * 47: string,
+ * 30: string,
+ * 0: string}}
+ */
+ConnectionIndicator.connectionQualityValues = {
+ 98: "18px", //full
+ 81: "15px",//4 bars
+ 64: "11px",//3 bars
+ 47: "7px",//2 bars
+ 30: "3px",//1 bar
+ 0: "0px"//empty
+};
+
+ConnectionIndicator.getIP = function(value)
+{
+ return value.substring(0, value.lastIndexOf(":"));
+};
+
+ConnectionIndicator.getPort = function(value)
+{
+ return value.substring(value.lastIndexOf(":") + 1, value.length);
+};
+
+ConnectionIndicator.getStringFromArray = function (array) {
+ var res = "";
+ for(var i = 0; i < array.length; i++)
+ {
+ res += (i === 0? "" : ", ") + array[i];
+ }
+ return res;
+};
+
+/**
+ * Generates the html content.
+ * @returns {string} the html content.
+ */
+ConnectionIndicator.prototype.generateText = function () {
+ var downloadBitrate, uploadBitrate, packetLoss, resolution, i;
+
+ var translate = APP.translation.translateString;
+
+ if(this.bitrate === null)
+ {
+ downloadBitrate = "N/A";
+ uploadBitrate = "N/A";
+ }
+ else
+ {
+ downloadBitrate =
+ this.bitrate.download? this.bitrate.download + " Kbps" : "N/A";
+ uploadBitrate =
+ this.bitrate.upload? this.bitrate.upload + " Kbps" : "N/A";
+ }
+
+ if(this.packetLoss === null)
+ {
+ packetLoss = "N/A";
+ }
+ else
+ {
+
+ packetLoss = "↓" +
+ (this.packetLoss.download !== null? this.packetLoss.download : "N/A") +
+ "% ↑" +
+ (this.packetLoss.upload !== null? this.packetLoss.upload : "N/A") + "%";
+ }
+
+ var resolutionValue = null;
+ if(this.resolution && this.jid != null)
+ {
+ var keys = Object.keys(this.resolution);
+ for(var ssrc in this.resolution)
+ {
+ resolutionValue = this.resolution[ssrc];
+ }
+ }
+
+ if(this.jid === null)
+ {
+ resolution = "";
+ if(this.resolution === null || !Object.keys(this.resolution) ||
+ Object.keys(this.resolution).length === 0)
+ {
+ resolution = "N/A";
+ }
+ else
+ for(i in this.resolution)
+ {
+ resolutionValue = this.resolution[i];
+ if(resolutionValue)
+ {
+ if(resolutionValue.height &&
+ resolutionValue.width)
+ {
+ resolution += (resolution === ""? "" : ", ") +
+ resolutionValue.width + "x" +
+ resolutionValue.height;
+ }
+ }
+ }
+ }
+ else if(!resolutionValue ||
+ !resolutionValue.height ||
+ !resolutionValue.width)
+ {
+ resolution = "N/A";
+ }
+ else
+ {
+ resolution = resolutionValue.width + "x" + resolutionValue.height;
+ }
+
+ var result = "" +
+ "" +
+ "" +
+ translate("connectionindicator.bitrate") + " | " +
+ "↓" +
+ downloadBitrate + " ↑" +
+ uploadBitrate + " | " +
+ "
" +
+ "" +
+ translate("connectionindicator.packetloss") + " | " +
+ "" + packetLoss + " | " +
+ "
" +
+ "" +
+ translate("connectionindicator.resolution") + " | " +
+ "" + resolution + " |
";
+
+ if(this.videoContainer.videoSpanId == "localVideoContainer") {
+ result += "" +
+ translate("connectionindicator." + (this.showMoreValue ? "less" : "more")) +
+ "
";
+ }
+
+ if(this.showMoreValue)
+ {
+ var downloadBandwidth, uploadBandwidth, transport;
+ if(this.bandwidth === null)
+ {
+ downloadBandwidth = "N/A";
+ uploadBandwidth = "N/A";
+ }
+ else
+ {
+ downloadBandwidth = this.bandwidth.download?
+ this.bandwidth.download + " Kbps" :
+ "N/A";
+ uploadBandwidth = this.bandwidth.upload?
+ this.bandwidth.upload + " Kbps" :
+ "N/A";
+ }
+
+ if(!this.transport || this.transport.length === 0)
+ {
+ transport = "" +
+ "" +
+ translate("connectionindicator.address") + " | " +
+ " N/A |
";
+ }
+ else
+ {
+ var data = {remoteIP: [], localIP:[], remotePort:[], localPort:[]};
+ for(i = 0; i < this.transport.length; i++)
+ {
+ var ip = ConnectionIndicator.getIP(this.transport[i].ip);
+ var port = ConnectionIndicator.getPort(this.transport[i].ip);
+ var localIP =
+ ConnectionIndicator.getIP(this.transport[i].localip);
+ var localPort =
+ ConnectionIndicator.getPort(this.transport[i].localip);
+ if(data.remoteIP.indexOf(ip) == -1)
+ {
+ data.remoteIP.push(ip);
+ }
+
+ if(data.remotePort.indexOf(port) == -1)
+ {
+ data.remotePort.push(port);
+ }
+
+ if(data.localIP.indexOf(localIP) == -1)
+ {
+ data.localIP.push(localIP);
+ }
+
+ if(data.localPort.indexOf(localPort) == -1)
+ {
+ data.localPort.push(localPort);
+ }
+
+ }
+
+ var local_address_key = "connectionindicator.localaddress";
+ var remote_address_key = "connectionindicator.remoteaddress";
+ var localTransport =
+ "" +
+ translate(local_address_key, {count: data.localIP.length}) +
+ " | " +
+ ConnectionIndicator.getStringFromArray(data.localIP) +
+ " |
";
+ transport =
+ "" +
+ translate(remote_address_key,
+ {count: data.remoteIP.length}) +
+ " | " +
+ ConnectionIndicator.getStringFromArray(data.remoteIP) +
+ " |
";
+
+ var key_remote = "connectionindicator.remoteport",
+ key_local = "connectionindicator.localport";
+
+ transport += "" +
+ "" +
+ "" +
+ translate(key_remote, {count: this.transport.length}) +
+ " | ";
+ localTransport += " |
" +
+ "" +
+ "" +
+ translate(key_local, {count: this.transport.length}) +
+ " | ";
+
+ transport +=
+ ConnectionIndicator.getStringFromArray(data.remotePort);
+ localTransport +=
+ ConnectionIndicator.getStringFromArray(data.localPort);
+ transport += " |
";
+ transport += localTransport + "";
+ transport +="" +
+ "" +
+ translate("connectionindicator.transport") + " | " +
+ "" + this.transport[0].type + " |
";
+
+ }
+
+ result += "" +
+ "" +
+ "" +
+ "" +
+ translate("connectionindicator.bandwidth") + "" +
+ " | " +
+ "↓" +
+ downloadBandwidth +
+ " ↑" +
+ uploadBandwidth + " |
";
+
+ result += transport + "
";
+
+ }
+
+ return result;
+};
+
+/**
+ * Shows or hide the additional information.
+ */
+ConnectionIndicator.prototype.showMore = function () {
+ this.showMoreValue = !this.showMoreValue;
+ this.updatePopoverData();
+};
+
+
+function createIcon(classes)
+{
+ var icon = document.createElement("span");
+ for(var i in classes)
+ {
+ icon.classList.add(classes[i]);
+ }
+ icon.appendChild(
+ document.createElement("i")).classList.add("icon-connection");
+ return icon;
+}
+
+/**
+ * Creates the indicator
+ */
+ConnectionIndicator.prototype.create = function () {
+ this.connectionIndicatorContainer = document.createElement("div");
+ this.connectionIndicatorContainer.className = "connectionindicator";
+ this.connectionIndicatorContainer.style.display = "none";
+ this.videoContainer.container.appendChild(this.connectionIndicatorContainer);
+ this.popover = new JitsiPopover(
+ $("#" + this.videoContainer.videoSpanId + " > .connectionindicator"),
+ {content: "" +
+ APP.translation.translateString("connectionindicator.na") + "
",
+ skin: "black"});
+
+ this.emptyIcon = this.connectionIndicatorContainer.appendChild(
+ createIcon(["connection", "connection_empty"]));
+ this.fullIcon = this.connectionIndicatorContainer.appendChild(
+ createIcon(["connection", "connection_full"]));
+
+};
+
+/**
+ * Removes the indicator
+ */
+ConnectionIndicator.prototype.remove = function()
+{
+ if (this.connectionIndicatorContainer.parentNode) {
+ this.connectionIndicatorContainer.parentNode.removeChild(
+ this.connectionIndicatorContainer);
+ }
+ this.popover.forceHide();
+
+};
+
+/**
+ * Updates the data of the indicator
+ * @param percent the percent of connection quality
+ * @param object the statistics data.
+ */
+ConnectionIndicator.prototype.updateConnectionQuality =
+function (percent, object) {
+
+ if(percent === null)
+ {
+ this.connectionIndicatorContainer.style.display = "none";
+ this.popover.forceHide();
+ return;
+ }
+ else
+ {
+ if(this.connectionIndicatorContainer.style.display == "none") {
+ this.connectionIndicatorContainer.style.display = "block";
+ this.videoContainer.updateIconPositions();
+ }
+ }
+ this.bandwidth = object.bandwidth;
+ this.bitrate = object.bitrate;
+ this.packetLoss = object.packetLoss;
+ this.transport = object.transport;
+ if(object.resolution)
+ {
+ this.resolution = object.resolution;
+ }
+ for(var quality in ConnectionIndicator.connectionQualityValues)
+ {
+ if(percent >= quality)
+ {
+ this.fullIcon.style.width =
+ ConnectionIndicator.connectionQualityValues[quality];
+ }
+ }
+ this.updatePopoverData();
+};
+
+/**
+ * Updates the resolution
+ * @param resolution the new resolution
+ */
+ConnectionIndicator.prototype.updateResolution = function (resolution) {
+ this.resolution = resolution;
+ this.updatePopoverData();
+};
+
+/**
+ * Updates the content of the popover
+ */
+ConnectionIndicator.prototype.updatePopoverData = function () {
+ this.popover.updateContent(
+ "" + this.generateText() + "
");
+ APP.translation.translateElement($(".connection_info"));
+};
+
+/**
+ * Hides the popover
+ */
+ConnectionIndicator.prototype.hide = function () {
+ this.popover.forceHide();
+};
+
+/**
+ * Hides the indicator
+ */
+ConnectionIndicator.prototype.hideIndicator = function () {
+ this.connectionIndicatorContainer.style.display = "none";
+ if(this.popover)
+ this.popover.forceHide();
+};
+
module.exports = ConnectionIndicator;
-},{"../util/JitsiPopover":32}],37:[function(require,module,exports){
-var Avatar = require("../avatar/Avatar");
-var RTCBrowserType = require("../../RTC/RTCBrowserType");
-var UIUtil = require("../util/UIUtil");
-var UIEvents = require("../../../service/UI/UIEvents");
-var xmpp = require("../../xmpp/xmpp");
-
-// FIXME: With Temasys we have to re-select everytime
-//var video = $('#largeVideo');
-
-var currentVideoWidth = null;
-var currentVideoHeight = null;
-// By default we use camera
-var getVideoSize = getCameraVideoSize;
-var getVideoPosition = getCameraVideoPosition;
-var currentSmallVideo = null;
-
-
-
-/**
- * Sets the size and position of the given video element.
- *
- * @param video the video element to position
- * @param width the desired video width
- * @param height the desired video height
- * @param horizontalIndent the left and right indent
- * @param verticalIndent the top and bottom indent
- */
-function positionVideo(video,
- width,
- height,
- horizontalIndent,
- verticalIndent,
- animate) {
- if(animate)
- {
- video.animate({
- width: width,
- height: height,
- top: verticalIndent,
- bottom: verticalIndent,
- left: horizontalIndent,
- right: horizontalIndent
- },
- {
- queue: false,
- duration: 500
- });
- }
- else
- {
- video.width(width);
- video.height(height);
- video.css({ top: verticalIndent + 'px',
- bottom: verticalIndent + 'px',
- left: horizontalIndent + 'px',
- right: horizontalIndent + 'px'});
- }
-
-}
-
-
-/**
- * Returns an array of the video dimensions, so that it keeps it's aspect
- * ratio and fits available area with it's larger dimension. This method
- * ensures that whole video will be visible and can leave empty areas.
- *
- * @return an array with 2 elements, the video width and the video height
- */
-function getDesktopVideoSize(videoWidth,
- videoHeight,
- videoSpaceWidth,
- videoSpaceHeight) {
- if (!videoWidth)
- videoWidth = currentVideoWidth;
- if (!videoHeight)
- videoHeight = currentVideoHeight;
-
- var aspectRatio = videoWidth / videoHeight;
-
- var availableWidth = Math.max(videoWidth, videoSpaceWidth);
- var availableHeight = Math.max(videoHeight, videoSpaceHeight);
-
- videoSpaceHeight -= $('#remoteVideos').outerHeight();
-
- if (availableWidth / aspectRatio >= videoSpaceHeight)
- {
- availableHeight = videoSpaceHeight;
- availableWidth = availableHeight * aspectRatio;
- }
-
- if (availableHeight * aspectRatio >= videoSpaceWidth)
- {
- availableWidth = videoSpaceWidth;
- availableHeight = availableWidth / aspectRatio;
- }
-
- return [availableWidth, availableHeight];
-}
-
-
-/**
- * Returns an array of the video horizontal and vertical indents,
- * so that if fits its parent.
- *
- * @return an array with 2 elements, the horizontal indent and the vertical
- * indent
- */
-function getCameraVideoPosition(videoWidth,
- videoHeight,
- videoSpaceWidth,
- videoSpaceHeight) {
- // Parent height isn't completely calculated when we position the video in
- // full screen mode and this is why we use the screen height in this case.
- // Need to think it further at some point and implement it properly.
- var isFullScreen = document.fullScreen ||
- document.mozFullScreen ||
- document.webkitIsFullScreen;
- if (isFullScreen)
- videoSpaceHeight = window.innerHeight;
-
- var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
- var verticalIndent = (videoSpaceHeight - videoHeight) / 2;
-
- return [horizontalIndent, verticalIndent];
-}
-
-/**
- * Returns an array of the video horizontal and vertical indents.
- * Centers horizontally and top aligns vertically.
- *
- * @return an array with 2 elements, the horizontal indent and the vertical
- * indent
- */
-function getDesktopVideoPosition(videoWidth,
- videoHeight,
- videoSpaceWidth,
- videoSpaceHeight) {
-
- var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
-
- var verticalIndent = 0;// Top aligned
-
- return [horizontalIndent, verticalIndent];
-}
-
-
-/**
- * Returns an array of the video dimensions, so that it covers the screen.
- * It leaves no empty areas, but some parts of the video might not be visible.
- *
- * @return an array with 2 elements, the video width and the video height
- */
-function getCameraVideoSize(videoWidth,
- videoHeight,
- videoSpaceWidth,
- videoSpaceHeight) {
- if (!videoWidth)
- videoWidth = currentVideoWidth;
- if (!videoHeight)
- videoHeight = currentVideoHeight;
-
- var aspectRatio = videoWidth / videoHeight;
-
- var availableWidth = Math.max(videoWidth, videoSpaceWidth);
- var availableHeight = Math.max(videoHeight, videoSpaceHeight);
-
- if (availableWidth / aspectRatio < videoSpaceHeight) {
- availableHeight = videoSpaceHeight;
- availableWidth = availableHeight * aspectRatio;
- }
-
- if (availableHeight * aspectRatio < videoSpaceWidth) {
- availableWidth = videoSpaceWidth;
- availableHeight = availableWidth / aspectRatio;
- }
-
- return [availableWidth, availableHeight];
-}
-
-/**
- * Updates the src of the active speaker avatar
- * @param jid of the current active speaker
- */
-function updateActiveSpeakerAvatarSrc() {
- var avatar = $("#activeSpeakerAvatar")[0];
- var jid = currentSmallVideo.peerJid;
- var url = Avatar.getActiveSpeakerUrl(jid);
- if (avatar.src === url)
- return;
- var isMuted = null;
- if (!currentSmallVideo.isLocal &&
- !LargeVideo.VideoLayout.isInLastN(currentSmallVideo.getResourceJid())) {
- isMuted = true;
- }
- else
- {
- isMuted = APP.RTC.isVideoMuted(jid);
- }
-
- if (jid && isMuted !== null) {
- avatar.src = url;
- $("#largeVideo").css("visibility", isMuted ? "hidden" : "visible");
- currentSmallVideo.showAvatar(isMuted);
- }
-}
-
-function changeVideo(isVisible) {
-
- if (!currentSmallVideo) {
- console.error("Unable to change large video - no 'currentSmallVideo'");
- return;
- }
-
- updateActiveSpeakerAvatarSrc();
-
- APP.RTC.setVideoSrc($('#largeVideo')[0], currentSmallVideo.getSrc());
-
- var videoTransform = document.getElementById('largeVideo')
- .style.webkitTransform;
-
- var flipX = currentSmallVideo.flipX;
-
- if (flipX && videoTransform !== 'scaleX(-1)') {
- document.getElementById('largeVideo').style.webkitTransform
- = "scaleX(-1)";
- }
- else if (!flipX && videoTransform === 'scaleX(-1)') {
- document.getElementById('largeVideo').style.webkitTransform
- = "none";
- }
-
- var isDesktop = APP.RTC.isVideoSrcDesktop(currentSmallVideo.peerJid);
- // Change the way we'll be measuring and positioning large video
-
- getVideoSize = isDesktop
- ? getDesktopVideoSize
- : getCameraVideoSize;
- getVideoPosition = isDesktop
- ? getDesktopVideoPosition
- : getCameraVideoPosition;
-
-
- // Only if the large video is currently visible.
- if (isVisible) {
- LargeVideo.VideoLayout.largeVideoUpdated(currentSmallVideo);
-
- $('#largeVideo').fadeIn(300);
- }
-}
-
-var LargeVideo = {
-
- init: function (VideoLayout, emitter) {
- this.VideoLayout = VideoLayout;
- this.eventEmitter = emitter;
- var self = this;
- // Listen for large video size updates
- var largeVideo = $('#largeVideo')[0];
- var onplaying = function (arg1, arg2, arg3) {
- // re-select
- if (RTCBrowserType.isTemasysPluginUsed())
- largeVideo = $('#largeVideo')[0];
- currentVideoWidth = largeVideo.videoWidth;
- currentVideoHeight = largeVideo.videoHeight;
- self.position(currentVideoWidth, currentVideoHeight);
- };
- largeVideo.onplaying = onplaying;
- },
- /**
- * Indicates if the large video is currently visible.
- *
- * @return true if visible, false - otherwise
- */
- isLargeVideoVisible: function() {
- return $('#largeVideo').is(':visible');
- },
- /**
- * Returns true if the user is currently displayed on large video.
- */
- isCurrentlyOnLarge: function (resourceJid) {
- return currentSmallVideo && resourceJid &&
- currentSmallVideo.getResourceJid() === resourceJid;
- },
- /**
- * Updates the large video with the given new video source.
- */
- updateLargeVideo: function (resourceJid, forceUpdate) {
- var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid);
- console.log('hover in ' + resourceJid + ', video: ', newSmallVideo);
-
- if (!LargeVideo.isCurrentlyOnLarge(resourceJid) || forceUpdate) {
- $('#activeSpeaker').css('visibility', 'hidden');
-
- var oldSmallVideo = null;
- if (currentSmallVideo) {
- oldSmallVideo = currentSmallVideo;
- }
- currentSmallVideo = newSmallVideo;
-
- var oldJid = null;
- if (oldSmallVideo)
- oldJid = oldSmallVideo.peerJid;
- if (oldJid !== resourceJid) {
- // we want the notification to trigger even if userJid is undefined,
- // or null.
- this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, resourceJid);
- }
- if (RTCBrowserType.isSafari()) {
- // FIXME In Safari fadeOut works only for the first time
- changeVideo(this.isLargeVideoVisible());
- } else {
- $('#largeVideo').fadeOut(300,
- changeVideo.bind($('#largeVideo'), this.isLargeVideoVisible()));
- }
- } else {
- if (currentSmallVideo) {
- currentSmallVideo.showAvatar();
- }
- }
-
- },
-
- /**
- * Shows/hides the large video.
- */
- setLargeVideoVisible: function(isVisible) {
- if (isVisible) {
- $('#largeVideo').css({visibility: 'visible'});
- $('.watermark').css({visibility: 'visible'});
- if(currentSmallVideo)
- currentSmallVideo.enableDominantSpeaker(true);
- }
- else {
- $('#largeVideo').css({visibility: 'hidden'});
- $('#activeSpeaker').css('visibility', 'hidden');
- $('.watermark').css({visibility: 'hidden'});
- if(currentSmallVideo)
- currentSmallVideo.enableDominantSpeaker(false);
- }
- },
- onVideoTypeChanged: function (jid) {
- var resourceJid = Strophe.getResourceFromJid(jid);
- if (LargeVideo.isCurrentlyOnLarge(resourceJid))
- {
- var isDesktop = APP.RTC.isVideoSrcDesktop(jid);
- getVideoSize = isDesktop
- ? getDesktopVideoSize
- : getCameraVideoSize;
- getVideoPosition = isDesktop
- ? getDesktopVideoPosition
- : getCameraVideoPosition;
- this.position(null, null);
- }
- },
- /**
- * Positions the large video.
- *
- * @param videoWidth the stream video width
- * @param videoHeight the stream video height
- */
- position: function (videoWidth, videoHeight,
- videoSpaceWidth, videoSpaceHeight, animate) {
- if(!videoSpaceWidth)
- videoSpaceWidth = $('#videospace').width();
- if(!videoSpaceHeight)
- videoSpaceHeight = window.innerHeight;
-
- var videoSize = getVideoSize(videoWidth,
- videoHeight,
- videoSpaceWidth,
- videoSpaceHeight);
-
- var largeVideoWidth = videoSize[0];
- var largeVideoHeight = videoSize[1];
-
- var videoPosition = getVideoPosition(largeVideoWidth,
- largeVideoHeight,
- videoSpaceWidth,
- videoSpaceHeight);
-
- var horizontalIndent = videoPosition[0];
- var verticalIndent = videoPosition[1];
-
- positionVideo($('#largeVideo'),
- largeVideoWidth,
- largeVideoHeight,
- horizontalIndent, verticalIndent, animate);
- },
-
- isLargeVideoOnTop: function () {
- var Etherpad = require("../etherpad/Etherpad");
- var Prezi = require("../prezi/Prezi");
- return !Prezi.isPresentationVisible() && !Etherpad.isVisible();
- },
- resize: function (animate, isVisible, completeFunction) {
- var availableHeight = window.innerHeight;
- var availableWidth = UIUtil.getAvailableVideoWidth(isVisible);
-
- if (availableWidth < 0 || availableHeight < 0) return;
-
- var avatarSize = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE;
- var top = availableHeight / 2 - avatarSize / 4 * 3;
- $('#activeSpeaker').css('top', top);
-
- if(animate)
- {
- $('#videospace').animate({
- right: window.innerWidth - availableWidth,
- width: availableWidth,
- height: availableHeight
- },
- {
- queue: false,
- duration: 500,
- complete: completeFunction
- });
-
- $('#largeVideoContainer').animate({
- width: availableWidth,
- height: availableHeight
- },
- {
- queue: false,
- duration: 500
- });
-
-
- }
- else
- {
- $('#videospace').width(availableWidth);
- $('#videospace').height(availableHeight);
- $('#largeVideoContainer').width(availableWidth);
- $('#largeVideoContainer').height(availableHeight);
- }
- return [availableWidth, availableHeight];
-
- },
- resizeVideoAreaAnimated: function (isVisible, completeFunction) {
- var size = this.resize(true, isVisible, completeFunction);
- this.position(null, null, size[0], size[1], true);
- },
- getResourceJid: function () {
- return currentSmallVideo ? currentSmallVideo.getResourceJid() : null;
- },
- updateAvatar: function (resourceJid) {
- if (resourceJid === this.getResourceJid()) {
- updateActiveSpeakerAvatarSrc();
- }
- },
- showAvatar: function (resourceJid, show) {
- if(this.getResourceJid() === resourceJid
- && LargeVideo.isLargeVideoOnTop())
- {
- $("#largeVideo").css("visibility", show ? "hidden" : "visible");
- $('#activeSpeaker').css("visibility", show ? "visible" : "hidden");
- return true;
- }
- return false;
- }
-
-}
-
-
+},{"../util/JitsiPopover":30}],35:[function(require,module,exports){
+var Avatar = require("../avatar/Avatar");
+var RTCBrowserType = require("../../RTC/RTCBrowserType");
+var UIUtil = require("../util/UIUtil");
+var UIEvents = require("../../../service/UI/UIEvents");
+var xmpp = require("../../xmpp/xmpp");
+var ToolbarToggler = require("../toolbars/ToolbarToggler");
+
+// FIXME: With Temasys we have to re-select everytime
+//var video = $('#largeVideo');
+
+var currentVideoWidth = null;
+var currentVideoHeight = null;
+// By default we use camera
+var getVideoSize = getCameraVideoSize;
+var getVideoPosition = getCameraVideoPosition;
+/**
+ * The small video instance that is displayed in the large video
+ * @type {SmallVideo}
+ */
+var currentSmallVideo = null;
+/**
+ * Indicates whether the large video is enabled.
+ * @type {boolean}
+ */
+var isEnabled = true;
+/**
+ * Current large video state.
+ * Possible values - video, prezi or etherpad.
+ * @type {string}
+ */
+var state = "video";
+
+/**
+ * Returns the html element associated with the passed state of large video
+ * @param state the state.
+ * @returns {JQuery|*|jQuery|HTMLElement} the container.
+ */
+function getContainerByState(state)
+{
+ var selector = null;
+ switch (state)
+ {
+ case "video":
+ selector = "#largeVideo";
+ break;
+ case "etherpad":
+ selector = "#etherpad>iframe";
+ break;
+ case "prezi":
+ selector = "#presentation>iframe";
+ break;
+ }
+ return (selector !== null)? $(selector) : null;
+}
+
+/**
+ * Sets the size and position of the given video element.
+ *
+ * @param video the video element to position
+ * @param width the desired video width
+ * @param height the desired video height
+ * @param horizontalIndent the left and right indent
+ * @param verticalIndent the top and bottom indent
+ */
+function positionVideo(video,
+ width,
+ height,
+ horizontalIndent,
+ verticalIndent,
+ animate) {
+ if(animate)
+ {
+ video.animate({
+ width: width,
+ height: height,
+ top: verticalIndent,
+ bottom: verticalIndent,
+ left: horizontalIndent,
+ right: horizontalIndent
+ },
+ {
+ queue: false,
+ duration: 500
+ });
+ }
+ else
+ {
+ video.width(width);
+ video.height(height);
+ video.css({ top: verticalIndent + 'px',
+ bottom: verticalIndent + 'px',
+ left: horizontalIndent + 'px',
+ right: horizontalIndent + 'px'});
+ }
+
+}
+
+
+/**
+ * Returns an array of the video dimensions, so that it keeps it's aspect
+ * ratio and fits available area with it's larger dimension. This method
+ * ensures that whole video will be visible and can leave empty areas.
+ *
+ * @return an array with 2 elements, the video width and the video height
+ */
+function getDesktopVideoSize(videoWidth,
+ videoHeight,
+ videoSpaceWidth,
+ videoSpaceHeight) {
+ if (!videoWidth)
+ videoWidth = currentVideoWidth;
+ if (!videoHeight)
+ videoHeight = currentVideoHeight;
+
+ var aspectRatio = videoWidth / videoHeight;
+
+ var availableWidth = Math.max(videoWidth, videoSpaceWidth);
+ var availableHeight = Math.max(videoHeight, videoSpaceHeight);
+
+ videoSpaceHeight -= $('#remoteVideos').outerHeight();
+
+ if (availableWidth / aspectRatio >= videoSpaceHeight)
+ {
+ availableHeight = videoSpaceHeight;
+ availableWidth = availableHeight * aspectRatio;
+ }
+
+ if (availableHeight * aspectRatio >= videoSpaceWidth)
+ {
+ availableWidth = videoSpaceWidth;
+ availableHeight = availableWidth / aspectRatio;
+ }
+
+ return [availableWidth, availableHeight];
+}
+
+
+/**
+ * Returns an array of the video horizontal and vertical indents,
+ * so that if fits its parent.
+ *
+ * @return an array with 2 elements, the horizontal indent and the vertical
+ * indent
+ */
+function getCameraVideoPosition(videoWidth,
+ videoHeight,
+ videoSpaceWidth,
+ videoSpaceHeight) {
+ // Parent height isn't completely calculated when we position the video in
+ // full screen mode and this is why we use the screen height in this case.
+ // Need to think it further at some point and implement it properly.
+ var isFullScreen = document.fullScreen ||
+ document.mozFullScreen ||
+ document.webkitIsFullScreen;
+ if (isFullScreen)
+ videoSpaceHeight = window.innerHeight;
+
+ var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
+ var verticalIndent = (videoSpaceHeight - videoHeight) / 2;
+
+ return [horizontalIndent, verticalIndent];
+}
+
+/**
+ * Returns an array of the video horizontal and vertical indents.
+ * Centers horizontally and top aligns vertically.
+ *
+ * @return an array with 2 elements, the horizontal indent and the vertical
+ * indent
+ */
+function getDesktopVideoPosition(videoWidth,
+ videoHeight,
+ videoSpaceWidth,
+ videoSpaceHeight) {
+
+ var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
+
+ var verticalIndent = 0;// Top aligned
+
+ return [horizontalIndent, verticalIndent];
+}
+
+
+/**
+ * Returns an array of the video dimensions, so that it covers the screen.
+ * It leaves no empty areas, but some parts of the video might not be visible.
+ *
+ * @return an array with 2 elements, the video width and the video height
+ */
+function getCameraVideoSize(videoWidth,
+ videoHeight,
+ videoSpaceWidth,
+ videoSpaceHeight) {
+ if (!videoWidth)
+ videoWidth = currentVideoWidth;
+ if (!videoHeight)
+ videoHeight = currentVideoHeight;
+
+ var aspectRatio = videoWidth / videoHeight;
+
+ var availableWidth = Math.max(videoWidth, videoSpaceWidth);
+ var availableHeight = Math.max(videoHeight, videoSpaceHeight);
+
+ if (availableWidth / aspectRatio < videoSpaceHeight) {
+ availableHeight = videoSpaceHeight;
+ availableWidth = availableHeight * aspectRatio;
+ }
+
+ if (availableHeight * aspectRatio < videoSpaceWidth) {
+ availableWidth = videoSpaceWidth;
+ availableHeight = availableWidth / aspectRatio;
+ }
+
+ return [availableWidth, availableHeight];
+}
+
+/**
+ * Updates the src of the active speaker avatar
+ * @param jid of the current active speaker
+ */
+function updateActiveSpeakerAvatarSrc() {
+ var avatar = $("#activeSpeakerAvatar")[0];
+ var jid = currentSmallVideo.peerJid;
+ var url = Avatar.getActiveSpeakerUrl(jid);
+ if (avatar.src === url)
+ return;
+ var isMuted = null;
+ if (!currentSmallVideo.isLocal &&
+ !LargeVideo.VideoLayout.isInLastN(currentSmallVideo.getResourceJid())) {
+ isMuted = true;
+ }
+ else
+ {
+ isMuted = APP.RTC.isVideoMuted(jid);
+ }
+
+ if (jid && isMuted !== null) {
+ avatar.src = url;
+ $("#largeVideo").css("visibility", isMuted ? "hidden" : "visible");
+ currentSmallVideo.showAvatar(isMuted);
+ }
+}
+
+/**
+ * Change the video source of the large video.
+ * @param isVisible
+ */
+function changeVideo(isVisible) {
+
+ if (!currentSmallVideo) {
+ console.error("Unable to change large video - no 'currentSmallVideo'");
+ return;
+ }
+
+ updateActiveSpeakerAvatarSrc();
+
+ APP.RTC.setVideoSrc($('#largeVideo')[0], currentSmallVideo.getSrc());
+
+ var videoTransform = document.getElementById('largeVideo')
+ .style.webkitTransform;
+
+ var flipX = currentSmallVideo.flipX;
+
+ if (flipX && videoTransform !== 'scaleX(-1)') {
+ document.getElementById('largeVideo').style.webkitTransform
+ = "scaleX(-1)";
+ }
+ else if (!flipX && videoTransform === 'scaleX(-1)') {
+ document.getElementById('largeVideo').style.webkitTransform
+ = "none";
+ }
+
+ var isDesktop = APP.RTC.isVideoSrcDesktop(currentSmallVideo.peerJid);
+ // Change the way we'll be measuring and positioning large video
+
+ getVideoSize = isDesktop
+ ? getDesktopVideoSize
+ : getCameraVideoSize;
+ getVideoPosition = isDesktop
+ ? getDesktopVideoPosition
+ : getCameraVideoPosition;
+
+
+ // Only if the large video is currently visible.
+ if (isVisible) {
+ LargeVideo.VideoLayout.largeVideoUpdated(currentSmallVideo);
+
+ $('#largeVideo').fadeIn(300);
+ }
+}
+
+/**
+ * Creates the html elements for the large video.
+ */
+function createLargeVideoHTML()
+{
+ var html = '';
+ $(html).prependTo("#videospace");
+
+ if (interfaceConfig.SHOW_JITSI_WATERMARK) {
+ var leftWatermarkDiv
+ = $("#largeVideoContainer div[class='watermark leftwatermark']");
+
+ leftWatermarkDiv.css({display: 'block'});
+ leftWatermarkDiv.parent().get(0).href
+ = interfaceConfig.JITSI_WATERMARK_LINK;
+ }
+
+ if (interfaceConfig.SHOW_BRAND_WATERMARK) {
+ var rightWatermarkDiv
+ = $("#largeVideoContainer div[class='watermark rightwatermark']");
+
+ rightWatermarkDiv.css({display: 'block'});
+ rightWatermarkDiv.parent().get(0).href
+ = interfaceConfig.BRAND_WATERMARK_LINK;
+ rightWatermarkDiv.get(0).style.backgroundImage
+ = "url(images/rightwatermark.png)";
+ }
+
+ if (interfaceConfig.SHOW_POWERED_BY) {
+ $("#largeVideoContainer>a[class='poweredby']").css({display: 'block'});
+ }
+
+ if (!RTCBrowserType.isIExplorer()) {
+ $('#largeVideo').volume = 0;
+ }
+}
+
+var LargeVideo = {
+
+ init: function (VideoLayout, emitter) {
+ if(!isEnabled)
+ return;
+ createLargeVideoHTML();
+
+ this.VideoLayout = VideoLayout;
+ this.eventEmitter = emitter;
+ this.eventEmitter.emit(UIEvents.LARGEVIDEO_INIT);
+ var self = this;
+ // Listen for large video size updates
+ var largeVideo = $('#largeVideo')[0];
+ var onplaying = function (arg1, arg2, arg3) {
+ // re-select
+ if (RTCBrowserType.isTemasysPluginUsed())
+ largeVideo = $('#largeVideo')[0];
+ currentVideoWidth = largeVideo.videoWidth;
+ currentVideoHeight = largeVideo.videoHeight;
+ self.position(currentVideoWidth, currentVideoHeight);
+ };
+ largeVideo.onplaying = onplaying;
+ },
+ /**
+ * Indicates if the large video is currently visible.
+ *
+ * @return true if visible, false - otherwise
+ */
+ isLargeVideoVisible: function() {
+ return $('#largeVideo').is(':visible');
+ },
+ /**
+ * Returns true if the user is currently displayed on large video.
+ */
+ isCurrentlyOnLarge: function (resourceJid) {
+ return currentSmallVideo && resourceJid &&
+ currentSmallVideo.getResourceJid() === resourceJid;
+ },
+ /**
+ * Updates the large video with the given new video source.
+ */
+ updateLargeVideo: function (resourceJid, forceUpdate) {
+ if(!isEnabled)
+ return;
+ var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid);
+ console.log('hover in ' + resourceJid + ', video: ', newSmallVideo);
+
+ if (!LargeVideo.isCurrentlyOnLarge(resourceJid) || forceUpdate) {
+ $('#activeSpeaker').css('visibility', 'hidden');
+
+ var oldSmallVideo = null;
+ if (currentSmallVideo) {
+ oldSmallVideo = currentSmallVideo;
+ }
+ currentSmallVideo = newSmallVideo;
+
+ var oldJid = null;
+ if (oldSmallVideo)
+ oldJid = oldSmallVideo.peerJid;
+ if (oldJid !== resourceJid) {
+ // we want the notification to trigger even if userJid is undefined,
+ // or null.
+ this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, resourceJid);
+ }
+ if (RTCBrowserType.isSafari()) {
+ // FIXME In Safari fadeOut works only for the first time
+ changeVideo(this.isLargeVideoVisible());
+ } else {
+ $('#largeVideo').fadeOut(300,
+ changeVideo.bind($('#largeVideo'), this.isLargeVideoVisible()));
+ }
+ } else {
+ if (currentSmallVideo) {
+ currentSmallVideo.showAvatar();
+ }
+ }
+
+ },
+
+ /**
+ * Shows/hides the large video.
+ */
+ setLargeVideoVisible: function(isVisible) {
+ if(!isEnabled)
+ return;
+ if (isVisible) {
+ $('#largeVideo').css({visibility: 'visible'});
+ $('.watermark').css({visibility: 'visible'});
+ if(currentSmallVideo)
+ currentSmallVideo.enableDominantSpeaker(true);
+ }
+ else {
+ $('#largeVideo').css({visibility: 'hidden'});
+ $('#activeSpeaker').css('visibility', 'hidden');
+ $('.watermark').css({visibility: 'hidden'});
+ if(currentSmallVideo)
+ currentSmallVideo.enableDominantSpeaker(false);
+ }
+ },
+ onVideoTypeChanged: function (jid) {
+ if(!isEnabled)
+ return;
+ var resourceJid = Strophe.getResourceFromJid(jid);
+ if (LargeVideo.isCurrentlyOnLarge(resourceJid))
+ {
+ var isDesktop = APP.RTC.isVideoSrcDesktop(jid);
+ getVideoSize = isDesktop
+ ? getDesktopVideoSize
+ : getCameraVideoSize;
+ getVideoPosition = isDesktop
+ ? getDesktopVideoPosition
+ : getCameraVideoPosition;
+ this.position(null, null);
+ }
+ },
+ /**
+ * Positions the large video.
+ *
+ * @param videoWidth the stream video width
+ * @param videoHeight the stream video height
+ */
+ position: function (videoWidth, videoHeight,
+ videoSpaceWidth, videoSpaceHeight, animate) {
+ if(!isEnabled)
+ return;
+ if(!videoSpaceWidth)
+ videoSpaceWidth = $('#videospace').width();
+ if(!videoSpaceHeight)
+ videoSpaceHeight = window.innerHeight;
+
+ var videoSize = getVideoSize(videoWidth,
+ videoHeight,
+ videoSpaceWidth,
+ videoSpaceHeight);
+
+ var largeVideoWidth = videoSize[0];
+ var largeVideoHeight = videoSize[1];
+
+ var videoPosition = getVideoPosition(largeVideoWidth,
+ largeVideoHeight,
+ videoSpaceWidth,
+ videoSpaceHeight);
+
+ var horizontalIndent = videoPosition[0];
+ var verticalIndent = videoPosition[1];
+
+ positionVideo($('#largeVideo'),
+ largeVideoWidth,
+ largeVideoHeight,
+ horizontalIndent, verticalIndent, animate);
+ },
+ resize: function (animate, isVisible, completeFunction) {
+ if(!isEnabled)
+ return;
+ var availableHeight = window.innerHeight;
+ var availableWidth = UIUtil.getAvailableVideoWidth(isVisible);
+
+ if (availableWidth < 0 || availableHeight < 0) return;
+
+ var avatarSize = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE;
+ var top = availableHeight / 2 - avatarSize / 4 * 3;
+ $('#activeSpeaker').css('top', top);
+
+ if(animate)
+ {
+ $('#videospace').animate({
+ right: window.innerWidth - availableWidth,
+ width: availableWidth,
+ height: availableHeight
+ },
+ {
+ queue: false,
+ duration: 500,
+ complete: completeFunction
+ });
+
+ $('#largeVideoContainer').animate({
+ width: availableWidth,
+ height: availableHeight
+ },
+ {
+ queue: false,
+ duration: 500
+ });
+
+
+ }
+ else
+ {
+ $('#videospace').width(availableWidth);
+ $('#videospace').height(availableHeight);
+ $('#largeVideoContainer').width(availableWidth);
+ $('#largeVideoContainer').height(availableHeight);
+ }
+ return [availableWidth, availableHeight];
+
+ },
+ resizeVideoAreaAnimated: function (isVisible, completeFunction) {
+ if(!isEnabled)
+ return;
+ var size = this.resize(true, isVisible, completeFunction);
+ this.position(null, null, size[0], size[1], true);
+ },
+ getResourceJid: function () {
+ return currentSmallVideo ? currentSmallVideo.getResourceJid() : null;
+ },
+ updateAvatar: function (resourceJid) {
+ if(!isEnabled)
+ return;
+ if (resourceJid === this.getResourceJid()) {
+ updateActiveSpeakerAvatarSrc();
+ }
+ },
+ showAvatar: function (resourceJid, show) {
+ if(!isEnabled)
+ return;
+ if(this.getResourceJid() === resourceJid
+ && state === "video")
+ {
+ $("#largeVideo").css("visibility", show ? "hidden" : "visible");
+ $('#activeSpeaker').css("visibility", show ? "visible" : "hidden");
+ return true;
+ }
+ return false;
+ },
+ /**
+ * Disables the large video
+ */
+ disable: function () {
+ isEnabled = false;
+ },
+ /**
+ * Enables the large video
+ */
+ enable: function () {
+ isEnabled = true;
+ },
+ /**
+ * Returns true if the video is enabled.
+ */
+ isEnabled: function () {
+ return isEnabled;
+ },
+ /**
+ * Creates the iframe used by the etherpad
+ * @param src the value for src attribute
+ * @param onloadHandler handler executed when the iframe loads it content
+ * @returns {HTMLElement} the iframe
+ */
+ createEtherpadIframe: function (src, onloadHandler) {
+ if(!isEnabled)
+ return;
+
+ var etherpadIFrame = document.createElement('iframe');
+ etherpadIFrame.src = src;
+ etherpadIFrame.frameBorder = 0;
+ etherpadIFrame.scrolling = "no";
+ etherpadIFrame.width = $('#largeVideoContainer').width() || 640;
+ etherpadIFrame.height = $('#largeVideoContainer').height() || 480;
+ etherpadIFrame.setAttribute('style', 'visibility: hidden;');
+
+ document.getElementById('etherpad').appendChild(etherpadIFrame);
+
+ etherpadIFrame.onload = onloadHandler;
+
+ return etherpadIFrame;
+ },
+ /**
+ * Changes the state of the large video.
+ * Possible values - video, prezi, etherpad.
+ * @param newState - the new state
+ */
+ setState: function (newState) {
+ if(state === newState)
+ return;
+ var currentContainer = getContainerByState(state);
+ if(!currentContainer)
+ return;
+
+ var self = this;
+ var oldState = state;
+
+ switch (newState)
+ {
+ case "etherpad":
+ $('#activeSpeaker').css('visibility', 'hidden');
+ currentContainer.fadeOut(300, function () {
+ if (oldState === "prezi") {
+ currentContainer.css({opacity: '0'});
+ $('#reloadPresentation').css({display: 'none'});
+ }
+ else {
+ self.setLargeVideoVisible(false);
+ }
+ });
+
+ $('#etherpad>iframe').fadeIn(300, function () {
+ document.body.style.background = '#eeeeee';
+ $('#etherpad>iframe').css({visibility: 'visible'});
+ $('#etherpad').css({zIndex: 2});
+ });
+ break;
+ case "prezi":
+ var prezi = $('#presentation>iframe');
+ currentContainer.fadeOut(300, function() {
+ document.body.style.background = 'black';
+ });
+ prezi.fadeIn(300, function() {
+ prezi.css({opacity:'1'});
+ ToolbarToggler.dockToolbar(true);//fix that
+ self.setLargeVideoVisible(false);
+ $('#etherpad>iframe').css({visibility: 'hidden'});
+ $('#etherpad').css({zIndex: 0});
+ });
+ $('#activeSpeaker').css('visibility', 'hidden');
+ break;
+
+ case "video":
+ currentContainer.fadeOut(300, function () {
+ $('#presentation>iframe').css({opacity:'0'});
+ $('#reloadPresentation').css({display:'none'});
+ $('#etherpad>iframe').css({visibility: 'hidden'});
+ $('#etherpad').css({zIndex: 0});
+ document.body.style.background = 'black';
+ ToolbarToggler.dockToolbar(false);//fix that
+ });
+ $('#largeVideo').fadeIn(300, function () {
+ self.setLargeVideoVisible(true);
+ });
+ break;
+ }
+
+ state = newState;
+
+ },
+ /**
+ * Returns the current state of the large video.
+ * @returns {string} the current state - video, prezi or etherpad.
+ */
+ getState: function () {
+ return state;
+ },
+ /**
+ * Sets hover handlers for the large video container div.
+ * @param inHandler
+ * @param outHandler
+ */
+ setHover: function(inHandler, outHandler)
+ {
+ $('#largeVideoContainer').hover(inHandler, outHandler);
+ }
+
+};
+
+
module.exports = LargeVideo;
-},{"../../../service/UI/UIEvents":104,"../../RTC/RTCBrowserType":10,"../../xmpp/xmpp":68,"../avatar/Avatar":18,"../etherpad/Etherpad":19,"../prezi/Prezi":20,"../util/UIUtil":35}],38:[function(require,module,exports){
-var SmallVideo = require("./SmallVideo");
-var ConnectionIndicator = require("./ConnectionIndicator");
-var NicknameHandler = require("../util/NicknameHandler");
-var UIUtil = require("../util/UIUtil");
-var LargeVideo = require("./LargeVideo");
-var RTCBrowserType = require("../../RTC/RTCBrowserType");
-
-function LocalVideo(VideoLayout)
-{
- this.videoSpanId = "localVideoContainer";
- this.container = $("#localVideoContainer").get(0);
- this.VideoLayout = VideoLayout;
- this.flipX = true;
- this.isLocal = true;
- this.peerJid = null;
-}
-
-LocalVideo.prototype = Object.create(SmallVideo.prototype);
-LocalVideo.prototype.constructor = LocalVideo;
-
-/**
- * Creates the edit display name button.
- *
- * @returns the edit button
- */
-function createEditDisplayNameButton() {
- var editButton = document.createElement('a');
- editButton.className = 'displayname';
- UIUtil.setTooltip(editButton,
- "videothumbnail.editnickname",
- "top");
- editButton.innerHTML = '';
-
- return editButton;
-}
-
-
-/**
- * Sets the display name for the given video span id.
- */
-LocalVideo.prototype.setDisplayName = function(displayName, key) {
-
- if (!this.container) {
- console.warn(
- "Unable to set displayName - " + this.videoSpanId + " does not exist");
- return;
- }
-
- var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
- var defaultLocalDisplayName = APP.translation.generateTranslationHTML(
- interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
-
- // If we already have a display name for this video.
- if (nameSpan.length > 0) {
-
- if (nameSpan.text() !== displayName) {
- if (displayName && displayName.length > 0)
- {
- var meHTML = APP.translation.generateTranslationHTML("me");
- $('#localDisplayName').html(displayName + ' (' + meHTML + ')');
- }
- else
- $('#localDisplayName').html(defaultLocalDisplayName);
- }
- } else {
- var editButton = createEditDisplayNameButton();
-
- nameSpan = document.createElement('span');
- nameSpan.className = 'displayname';
- $('#' + this.videoSpanId)[0].appendChild(nameSpan);
-
-
- if (displayName && displayName.length > 0) {
- var meHTML = APP.translation.generateTranslationHTML("me");
- nameSpan.innerHTML = displayName + meHTML;
- }
- else {
- nameSpan.innerHTML = defaultLocalDisplayName;
- }
-
-
- nameSpan.id = 'localDisplayName';
- this.container.appendChild(editButton);
- //translates popover of edit button
- APP.translation.translateElement($("a.displayname"));
-
- var editableText = document.createElement('input');
- editableText.className = 'displayname';
- editableText.type = 'text';
- editableText.id = 'editDisplayName';
-
- if (displayName && displayName.length) {
- editableText.value = displayName;
- }
-
- var defaultNickname = APP.translation.translateString(
- "defaultNickname", {name: "Jane Pink"});
- editableText.setAttribute('style', 'display:none;');
- editableText.setAttribute('data-18n',
- '[placeholder]defaultNickname');
- editableText.setAttribute("data-i18n-options",
- JSON.stringify({name: "Jane Pink"}));
- editableText.setAttribute("placeholder", defaultNickname);
-
- this.container.appendChild(editableText);
-
- var self = this;
- $('#localVideoContainer .displayname')
- .bind("click", function (e) {
-
- e.preventDefault();
- e.stopPropagation();
- $('#localDisplayName').hide();
- $('#editDisplayName').show();
- $('#editDisplayName').focus();
- $('#editDisplayName').select();
-
- $('#editDisplayName').one("focusout", function (e) {
- self.VideoLayout.inputDisplayNameHandler(this.value);
- });
-
- $('#editDisplayName').on('keydown', function (e) {
- if (e.keyCode === 13) {
- e.preventDefault();
- self.VideoLayout.inputDisplayNameHandler(this.value);
- }
- });
- });
- }
-}
-
-LocalVideo.prototype.inputDisplayNameHandler = function (name) {
- NicknameHandler.setNickname(name);
-
- if (!$('#localDisplayName').is(":visible")) {
- if (NicknameHandler.getNickname())
- {
- var meHTML = APP.translation.generateTranslationHTML("me");
- $('#localDisplayName').html(NicknameHandler.getNickname() + " (" + meHTML + ")");
- }
- else
- {
- var defaultHTML = APP.translation.generateTranslationHTML(
- interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
- $('#localDisplayName')
- .html(defaultHTML);
- }
- $('#localDisplayName').show();
- }
-
- $('#editDisplayName').hide();
-}
-
-LocalVideo.prototype.createConnectionIndicator = function()
-{
- if(this.connectionIndicator)
- return;
-
- this.connectionIndicator
- = new ConnectionIndicator(this, null);
-}
-
-LocalVideo.prototype.changeVideo = function (stream, isMuted) {
- var self = this;
-
- function localVideoClick(event) {
- // FIXME: with Temasys plugin event arg is not an event, but
- // the clicked object itself, so we have to skip this call
- if (event.stopPropagation) {
- event.stopPropagation();
- }
- self.VideoLayout.handleVideoThumbClicked(
- false,
- APP.xmpp.myResource());
- }
-
- $('#localVideoContainer').off('click');
- $('#localVideoContainer').on('click', localVideoClick);
-
- // Add hover handler
- $('#localVideoContainer').hover(
- function() {
- self.showDisplayName(true);
- },
- function() {
- if (!LargeVideo.isLargeVideoVisible() ||
- !LargeVideo.isCurrentlyOnLarge(self.getResourceJid()))
- self.showDisplayName(false);
- }
- );
-
- if(isMuted)
- {
- APP.UI.setVideoMute(true);
- return;
- }
- this.flipX = (stream.videoType == "screen")? false : true;
- var localVideo = document.createElement('video');
- localVideo.id = 'localVideo_' +
- APP.RTC.getStreamID(stream.getOriginalStream());
- if (!RTCBrowserType.isIExplorer()) {
- localVideo.autoplay = true;
- localVideo.volume = 0; // is it required if audio is separated ?
- }
- localVideo.oncontextmenu = function () { return false; };
-
- var localVideoContainer = document.getElementById('localVideoWrapper');
- localVideoContainer.appendChild(localVideo);
-
- var localVideoSelector = $('#' + localVideo.id);
-
- // Add click handler to both video and video wrapper elements in case
- // there's no video.
-
- // onclick has to be used with Temasys plugin
- localVideo.onclick = localVideoClick;
-
- if (this.flipX) {
- localVideoSelector.addClass("flipVideoX");
- }
-
- // Attach WebRTC stream
- APP.RTC.attachMediaStream(localVideoSelector, stream.getOriginalStream());
-
- // Add stream ended handler
- stream.getOriginalStream().onended = function () {
- // We have to re-select after attach when Temasys plugin is used,
- // because ";
- return str;
-
- }
-};
-
-},{"../../service/translation/languages":109,"../settings/Settings":49,"i18next-client":70}],55:[function(require,module,exports){
-/* jshint -W117 */
-var TraceablePeerConnection = require("./TraceablePeerConnection");
-var SDPDiffer = require("./SDPDiffer");
-var SDPUtil = require("./SDPUtil");
-var SDP = require("./SDP");
-var async = require("async");
-var transform = require("sdp-transform");
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-var RTCBrowserType = require("../RTC/RTCBrowserType");
-
-// Jingle stuff
-function JingleSession(me, sid, connection, service, eventEmitter) {
- this.me = me;
- this.sid = sid;
- this.connection = connection;
- this.initiator = null;
- this.responder = null;
- this.isInitiator = null;
- this.peerjid = null;
- this.state = null;
- this.localSDP = null;
- this.remoteSDP = null;
- this.relayedStreams = [];
- this.startTime = null;
- this.stopTime = null;
- this.media_constraints = null;
- this.pc_constraints = null;
- this.ice_config = {};
- this.drip_container = [];
- this.service = service;
- this.eventEmitter = eventEmitter;
-
- this.usetrickle = true;
- this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
- this.usedrip = false; // dripping is sending trickle candidates not one-by-one
-
- this.hadstuncandidate = false;
- this.hadturncandidate = false;
- this.lasticecandidate = false;
-
- this.statsinterval = null;
-
- this.reason = null;
-
- this.addssrc = [];
- this.removessrc = [];
- this.pendingop = null;
- this.switchstreams = false;
-
- this.wait = true;
- this.localStreamsSSRC = null;
- this.ssrcOwners = {};
- this.ssrcVideoTypes = {};
- this.eventEmitter = eventEmitter;
-
- /**
- * The indicator which determines whether the (local) video has been muted
- * in response to a user command in contrast to an automatic decision made
- * by the application logic.
- */
- this.videoMuteByUser = false;
- this.modifySourcesQueue = async.queue(this._modifySources.bind(this), 1);
- // We start with the queue paused. We resume it when the signaling state is
- // stable and the ice connection state is connected.
- this.modifySourcesQueue.pause();
-}
-
-JingleSession.prototype.updateModifySourcesQueue = function() {
- var signalingState = this.peerconnection.signalingState;
- var iceConnectionState = this.peerconnection.iceConnectionState;
- if (signalingState === 'stable' && iceConnectionState === 'connected') {
- this.modifySourcesQueue.resume();
- } else {
- this.modifySourcesQueue.pause();
- }
-};
-
-JingleSession.prototype.initiate = function (peerjid, isInitiator) {
- var self = this;
- if (this.state !== null) {
- console.error('attempt to initiate on session ' + this.sid +
- 'in state ' + this.state);
- return;
- }
- this.isInitiator = isInitiator;
- this.state = 'pending';
- this.initiator = isInitiator ? this.me : peerjid;
- this.responder = !isInitiator ? this.me : peerjid;
- this.peerjid = peerjid;
- this.hadstuncandidate = false;
- this.hadturncandidate = false;
- this.lasticecandidate = false;
-
- this.peerconnection
- = new TraceablePeerConnection(
- this.connection.jingle.ice_config,
- this.connection.jingle.pc_constraints,
- this);
-
- this.peerconnection.onicecandidate = function (event) {
- self.sendIceCandidate(event.candidate);
- };
- this.peerconnection.onaddstream = function (event) {
- if (event.stream.id !== 'default') {
- console.log("REMOTE STREAM ADDED: ", event.stream , event.stream.id);
- self.remoteStreamAdded(event);
- } else {
- // This is a recvonly stream. Clients that implement Unified Plan,
- // such as Firefox use recvonly "streams/channels/tracks" for
- // receiving remote stream/tracks, as opposed to Plan B where there
- // are only 3 channels: audio, video and data.
- console.log("RECVONLY REMOTE STREAM IGNORED: " + event.stream + " - " + event.stream.id);
- }
- };
- this.peerconnection.onremovestream = function (event) {
- // Remove the stream from remoteStreams
- // FIXME: remotestreamremoved.jingle not defined anywhere(unused)
- $(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
- };
- this.peerconnection.onsignalingstatechange = function (event) {
- if (!(self && self.peerconnection)) return;
- self.updateModifySourcesQueue();
- };
- this.peerconnection.oniceconnectionstatechange = function (event) {
- if (!(self && self.peerconnection)) return;
- self.updateModifySourcesQueue();
- switch (self.peerconnection.iceConnectionState) {
- case 'connected':
- this.startTime = new Date();
- break;
- case 'disconnected':
- this.stopTime = new Date();
- break;
- case 'failed':
- self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
- break;
- }
- onIceConnectionStateChange(self.sid, self);
- };
- this.peerconnection.onnegotiationneeded = function (event) {
- self.eventEmitter.emit(XMPPEvents.PEERCONNECTION_READY, self);
- };
- // add any local and relayed stream
- APP.RTC.localStreams.forEach(function(stream) {
- self.peerconnection.addStream(stream.getOriginalStream());
- });
- this.relayedStreams.forEach(function(stream) {
- self.peerconnection.addStream(stream);
- });
-};
-
-function onIceConnectionStateChange(sid, session) {
- switch (session.peerconnection.iceConnectionState) {
- case 'checking':
- session.timeChecking = (new Date()).getTime();
- session.firstconnect = true;
- break;
- case 'completed': // on caller side
- case 'connected':
- if (session.firstconnect) {
- session.firstconnect = false;
- var metadata = {};
- metadata.setupTime
- = (new Date()).getTime() - session.timeChecking;
- session.peerconnection.getStats(function (res) {
- if(res && res.result) {
- res.result().forEach(function (report) {
- if (report.type == 'googCandidatePair' &&
- report.stat('googActiveConnection') == 'true') {
- metadata.localCandidateType
- = report.stat('googLocalCandidateType');
- metadata.remoteCandidateType
- = report.stat('googRemoteCandidateType');
-
- // log pair as well so we can get nice pie
- // charts
- metadata.candidatePair
- = report.stat('googLocalCandidateType') +
- ';' +
- report.stat('googRemoteCandidateType');
-
- if (report.stat('googRemoteAddress').indexOf('[') === 0)
- {
- metadata.ipv6 = true;
- }
- }
- });
- }
- });
- }
- break;
- }
-}
-
-JingleSession.prototype.getVideoType = function () {
- return APP.desktopsharing.isUsingScreenStream() ? 'screen' : 'camera';
-};
-
-JingleSession.prototype.accept = function () {
- this.state = 'active';
-
- var pranswer = this.peerconnection.localDescription;
- if (!pranswer || pranswer.type != 'pranswer') {
- return;
- }
- console.log('going from pranswer to answer');
- if (this.usetrickle) {
- // remove candidates already sent from session-accept
- var lines = SDPUtil.find_lines(pranswer.sdp, 'a=candidate:');
- for (var i = 0; i < lines.length; i++) {
- pranswer.sdp = pranswer.sdp.replace(lines[i] + '\r\n', '');
- }
- }
- while (SDPUtil.find_line(pranswer.sdp, 'a=inactive')) {
- // FIXME: change any inactive to sendrecv or whatever they were originally
- pranswer.sdp = pranswer.sdp.replace('a=inactive', 'a=sendrecv');
- }
- var prsdp = new SDP(pranswer.sdp);
- var accept = $iq({to: this.peerjid,
- type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: 'session-accept',
- initiator: this.initiator,
- responder: this.responder,
- sid: this.sid });
- // FIXME why do we generate session-accept in 3 different places ?
- prsdp.toJingle(
- accept,
- this.initiator == this.me ? 'initiator' : 'responder',
- this.localStreamsSSRC,
- self.getVideoType());
- var sdp = this.peerconnection.localDescription.sdp;
- while (SDPUtil.find_line(sdp, 'a=inactive')) {
- // FIXME: change any inactive to sendrecv or whatever they were originally
- sdp = sdp.replace('a=inactive', 'a=sendrecv');
- }
- var self = this;
- this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
- function () {
- //console.log('setLocalDescription success');
- self.setLocalDescription();
-
- self.connection.sendIQ(accept,
- function () {
- var ack = {};
- ack.source = 'answer';
- $(document).trigger('ack.jingle', [self.sid, ack]);
- },
- function (stanza) {
- var error = ($(stanza).find('error').length) ? {
- code: $(stanza).find('error').attr('code'),
- reason: $(stanza).find('error :first')[0].tagName
- }:{};
- error.source = 'answer';
- JingleSession.onJingleError(self.sid, error);
- },
- 10000);
- },
- function (e) {
- console.error('setLocalDescription failed', e);
- self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
- }
- );
-};
-
-JingleSession.prototype.terminate = function (reason) {
- this.state = 'ended';
- this.reason = reason;
- this.peerconnection.close();
- if (this.statsinterval !== null) {
- window.clearInterval(this.statsinterval);
- this.statsinterval = null;
- }
-};
-
-JingleSession.prototype.active = function () {
- return this.state == 'active';
-};
-
-JingleSession.prototype.sendIceCandidate = function (candidate) {
- var self = this;
- if (candidate && !this.lasticecandidate) {
- var ice = SDPUtil.iceparams(this.localSDP.media[candidate.sdpMLineIndex], this.localSDP.session);
- var jcand = SDPUtil.candidateToJingle(candidate.candidate);
- if (!(ice && jcand)) {
- console.error('failed to get ice && jcand');
- return;
- }
- ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
-
- if (jcand.type === 'srflx') {
- this.hadstuncandidate = true;
- } else if (jcand.type === 'relay') {
- this.hadturncandidate = true;
- }
-
- if (this.usetrickle) {
- if (this.usedrip) {
- if (this.drip_container.length === 0) {
- // start 20ms callout
- window.setTimeout(function () {
- if (self.drip_container.length === 0) return;
- self.sendIceCandidates(self.drip_container);
- self.drip_container = [];
- }, 20);
-
- }
- this.drip_container.push(candidate);
- return;
- } else {
- self.sendIceCandidate([candidate]);
- }
- }
- } else {
- //console.log('sendIceCandidate: last candidate.');
- if (!this.usetrickle) {
- //console.log('should send full offer now...');
- //FIXME why do we generate session-accept in 3 different places ?
- var init = $iq({to: this.peerjid,
- type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: this.peerconnection.localDescription.type == 'offer' ? 'session-initiate' : 'session-accept',
- initiator: this.initiator,
- sid: this.sid});
- this.localSDP = new SDP(this.peerconnection.localDescription.sdp);
- var self = this;
- var sendJingle = function (ssrc) {
- if(!ssrc)
- ssrc = {};
- self.localSDP.toJingle(
- init,
- self.initiator == self.me ? 'initiator' : 'responder',
- ssrc,
- self.getVideoType());
- self.connection.sendIQ(init,
- function () {
- //console.log('session initiate ack');
- var ack = {};
- ack.source = 'offer';
- $(document).trigger('ack.jingle', [self.sid, ack]);
- },
- function (stanza) {
- self.state = 'error';
- self.peerconnection.close();
- var error = ($(stanza).find('error').length) ? {
- code: $(stanza).find('error').attr('code'),
- reason: $(stanza).find('error :first')[0].tagName,
- }:{};
- error.source = 'offer';
- JingleSession.onJingleError(self.sid, error);
- },
- 10000);
- }
- sendJingle();
- }
- this.lasticecandidate = true;
- console.log('Have we encountered any srflx candidates? ' + this.hadstuncandidate);
- console.log('Have we encountered any relay candidates? ' + this.hadturncandidate);
-
- if (!(this.hadstuncandidate || this.hadturncandidate) && this.peerconnection.signalingState != 'closed') {
- $(document).trigger('nostuncandidates.jingle', [this.sid]);
- }
- }
-};
-
-JingleSession.prototype.sendIceCandidates = function (candidates) {
- console.log('sendIceCandidates', candidates);
- var cand = $iq({to: this.peerjid, type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: 'transport-info',
- initiator: this.initiator,
- sid: this.sid});
- for (var mid = 0; mid < this.localSDP.media.length; mid++) {
- var cands = candidates.filter(function (el) { return el.sdpMLineIndex == mid; });
- var mline = SDPUtil.parse_mline(this.localSDP.media[mid].split('\r\n')[0]);
- if (cands.length > 0) {
- var ice = SDPUtil.iceparams(this.localSDP.media[mid], this.localSDP.session);
- ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
- cand.c('content', {creator: this.initiator == this.me ? 'initiator' : 'responder',
- name: (cands[0].sdpMid? cands[0].sdpMid : mline.media)
- }).c('transport', ice);
- for (var i = 0; i < cands.length; i++) {
- cand.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
- }
- // add fingerprint
- if (SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)) {
- var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session));
- tmp.required = true;
- cand.c(
- 'fingerprint',
- {xmlns: 'urn:xmpp:jingle:apps:dtls:0'})
- .t(tmp.fingerprint);
- delete tmp.fingerprint;
- cand.attrs(tmp);
- cand.up();
- }
- cand.up(); // transport
- cand.up(); // content
- }
- }
- // might merge last-candidate notification into this, but it is called alot later. See webrtc issue #2340
- //console.log('was this the last candidate', this.lasticecandidate);
- this.connection.sendIQ(cand,
- function () {
- var ack = {};
- ack.source = 'transportinfo';
- $(document).trigger('ack.jingle', [this.sid, ack]);
- },
- function (stanza) {
- var error = ($(stanza).find('error').length) ? {
- code: $(stanza).find('error').attr('code'),
- reason: $(stanza).find('error :first')[0].tagName,
- }:{};
- error.source = 'transportinfo';
- JingleSession.onJingleError(this.sid, error);
- },
- 10000);
-};
-
-
-JingleSession.prototype.sendOffer = function () {
- //console.log('sendOffer...');
- var self = this;
- this.peerconnection.createOffer(function (sdp) {
- self.createdOffer(sdp);
- },
- function (e) {
- console.error('createOffer failed', e);
- },
- this.media_constraints
- );
-};
-
-// FIXME createdOffer is never used in jitsi-meet
-JingleSession.prototype.createdOffer = function (sdp) {
- //console.log('createdOffer', sdp);
- var self = this;
- this.localSDP = new SDP(sdp.sdp);
- //this.localSDP.mangle();
- var sendJingle = function () {
- var init = $iq({to: this.peerjid,
- type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: 'session-initiate',
- initiator: this.initiator,
- sid: this.sid});
- self.localSDP.toJingle(
- init,
- this.initiator == this.me ? 'initiator' : 'responder',
- this.localStreamsSSRC,
- self.getVideoType());
- self.connection.sendIQ(init,
- function () {
- var ack = {};
- ack.source = 'offer';
- $(document).trigger('ack.jingle', [self.sid, ack]);
- },
- function (stanza) {
- self.state = 'error';
- self.peerconnection.close();
- var error = ($(stanza).find('error').length) ? {
- code: $(stanza).find('error').attr('code'),
- reason: $(stanza).find('error :first')[0].tagName,
- }:{};
- error.source = 'offer';
- JingleSession.onJingleError(self.sid, error);
- },
- 10000);
- }
- sdp.sdp = this.localSDP.raw;
- this.peerconnection.setLocalDescription(sdp,
- function () {
- if(self.usetrickle)
- {
- sendJingle();
- }
- self.setLocalDescription();
- //console.log('setLocalDescription success');
- },
- function (e) {
- console.error('setLocalDescription failed', e);
- self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
- }
- );
- var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
- for (var i = 0; i < cands.length; i++) {
- var cand = SDPUtil.parse_icecandidate(cands[i]);
- if (cand.type == 'srflx') {
- this.hadstuncandidate = true;
- } else if (cand.type == 'relay') {
- this.hadturncandidate = true;
- }
- }
-};
-
-JingleSession.prototype.readSsrcInfo = function (contents) {
- var self = this;
- $(contents).each(function (idx, content) {
- var name = $(content).attr('name');
- var mediaType = this.getAttribute('name');
- var ssrcs = $(content).find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
- ssrcs.each(function () {
- var ssrc = this.getAttribute('ssrc');
- $(this).find('>ssrc-info[xmlns="http://jitsi.org/jitmeet"]').each(
- function () {
- var owner = this.getAttribute('owner');
- var videoType = this.getAttribute('video-type');
- self.ssrcOwners[ssrc] = owner;
- self.ssrcVideoTypes[ssrc] = videoType;
- }
- );
- });
- });
-};
-
-JingleSession.prototype.getSsrcOwner = function (ssrc) {
- return this.ssrcOwners[ssrc];
-};
-
-JingleSession.prototype.setRemoteDescription = function (elem, desctype) {
- //console.log('setting remote description... ', desctype);
- this.remoteSDP = new SDP('');
- this.remoteSDP.fromJingle(elem);
- this.readSsrcInfo($(elem).find(">content"));
- if (this.peerconnection.remoteDescription !== null) {
- console.log('setRemoteDescription when remote description is not null, should be pranswer', this.peerconnection.remoteDescription);
- if (this.peerconnection.remoteDescription.type == 'pranswer') {
- var pranswer = new SDP(this.peerconnection.remoteDescription.sdp);
- for (var i = 0; i < pranswer.media.length; i++) {
- // make sure we have ice ufrag and pwd
- if (!SDPUtil.find_line(this.remoteSDP.media[i], 'a=ice-ufrag:', this.remoteSDP.session)) {
- if (SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session)) {
- this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session) + '\r\n';
- } else {
- console.warn('no ice ufrag?');
- }
- if (SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session)) {
- this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session) + '\r\n';
- } else {
- console.warn('no ice pwd?');
- }
- }
- // copy over candidates
- var lines = SDPUtil.find_lines(pranswer.media[i], 'a=candidate:');
- for (var j = 0; j < lines.length; j++) {
- this.remoteSDP.media[i] += lines[j] + '\r\n';
- }
- }
- this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
- }
- }
- var remotedesc = new RTCSessionDescription({type: desctype, sdp: this.remoteSDP.raw});
-
- this.peerconnection.setRemoteDescription(remotedesc,
- function () {
- //console.log('setRemoteDescription success');
- },
- function (e) {
- console.error('setRemoteDescription error', e);
- JingleSession.onJingleFatalError(self, e);
- }
- );
-};
-
-JingleSession.prototype.addIceCandidate = function (elem) {
- var self = this;
- if (this.peerconnection.signalingState == 'closed') {
- return;
- }
- if (!this.peerconnection.remoteDescription && this.peerconnection.signalingState == 'have-local-offer') {
- console.log('trickle ice candidate arriving before session accept...');
- // create a PRANSWER for setRemoteDescription
- if (!this.remoteSDP) {
- var cobbled = 'v=0\r\n' +
- 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
- 's=-\r\n' +
- 't=0 0\r\n';
- // first, take some things from the local description
- for (var i = 0; i < this.localSDP.media.length; i++) {
- cobbled += SDPUtil.find_line(this.localSDP.media[i], 'm=') + '\r\n';
- cobbled += SDPUtil.find_lines(this.localSDP.media[i], 'a=rtpmap:').join('\r\n') + '\r\n';
- if (SDPUtil.find_line(this.localSDP.media[i], 'a=mid:')) {
- cobbled += SDPUtil.find_line(this.localSDP.media[i], 'a=mid:') + '\r\n';
- }
- cobbled += 'a=inactive\r\n';
- }
- this.remoteSDP = new SDP(cobbled);
- }
- // then add things like ice and dtls from remote candidate
- elem.each(function () {
- for (var i = 0; i < self.remoteSDP.media.length; i++) {
- if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
- self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
- if (!SDPUtil.find_line(self.remoteSDP.media[i], 'a=ice-ufrag:')) {
- var tmp = $(this).find('transport');
- self.remoteSDP.media[i] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
- self.remoteSDP.media[i] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n';
- tmp = $(this).find('transport>fingerprint');
- if (tmp.length) {
- self.remoteSDP.media[i] += 'a=fingerprint:' + tmp.attr('hash') + ' ' + tmp.text() + '\r\n';
- } else {
- console.log('no dtls fingerprint (webrtc issue #1718?)');
- self.remoteSDP.media[i] += 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:BAADBAADBAADBAADBAADBAADBAADBAADBAADBAAD\r\n';
- }
- break;
- }
- }
- }
- });
- this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
-
- // we need a complete SDP with ice-ufrag/ice-pwd in all parts
- // this makes the assumption that the PRANSWER is constructed such that the ice-ufrag is in all mediaparts
- // but it could be in the session part as well. since the code above constructs this sdp this can't happen however
- var iscomplete = this.remoteSDP.media.filter(function (mediapart) {
- return SDPUtil.find_line(mediapart, 'a=ice-ufrag:');
- }).length == this.remoteSDP.media.length;
-
- if (iscomplete) {
- console.log('setting pranswer');
- try {
- this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'pranswer', sdp: this.remoteSDP.raw }),
- function() {
- },
- function(e) {
- console.log('setRemoteDescription pranswer failed', e.toString());
- });
- } catch (e) {
- console.error('setting pranswer failed', e);
- }
- } else {
- //console.log('not yet setting pranswer');
- }
- }
- // operate on each content element
- elem.each(function () {
- // would love to deactivate this, but firefox still requires it
- var idx = -1;
- var i;
- for (i = 0; i < self.remoteSDP.media.length; i++) {
- if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
- self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
- idx = i;
- break;
- }
- }
- if (idx == -1) { // fall back to localdescription
- for (i = 0; i < self.localSDP.media.length; i++) {
- if (SDPUtil.find_line(self.localSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
- self.localSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
- idx = i;
- break;
- }
- }
- }
- var name = $(this).attr('name');
- // TODO: check ice-pwd and ice-ufrag?
- $(this).find('transport>candidate').each(function () {
- var line, candidate;
- line = SDPUtil.candidateFromJingle(this);
- candidate = new RTCIceCandidate({sdpMLineIndex: idx,
- sdpMid: name,
- candidate: line});
- try {
- self.peerconnection.addIceCandidate(candidate);
- } catch (e) {
- console.error('addIceCandidate failed', e.toString(), line);
- }
- });
- });
-};
-
-JingleSession.prototype.sendAnswer = function (provisional) {
- //console.log('createAnswer', provisional);
- var self = this;
- this.peerconnection.createAnswer(
- function (sdp) {
- self.createdAnswer(sdp, provisional);
- },
- function (e) {
- console.error('createAnswer failed', e);
- self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
- },
- this.media_constraints
- );
-};
-
-JingleSession.prototype.createdAnswer = function (sdp, provisional) {
- //console.log('createAnswer callback');
- var self = this;
- this.localSDP = new SDP(sdp.sdp);
- //this.localSDP.mangle();
- this.usepranswer = provisional === true;
- if (this.usetrickle) {
- if (this.usepranswer) {
- sdp.type = 'pranswer';
- for (var i = 0; i < this.localSDP.media.length; i++) {
- this.localSDP.media[i] = this.localSDP.media[i].replace('a=sendrecv\r\n', 'a=inactive\r\n');
- }
- this.localSDP.raw = this.localSDP.session + '\r\n' + this.localSDP.media.join('');
- }
- }
- var self = this;
- var sendJingle = function (ssrcs) {
- // FIXME why do we generate session-accept in 3 different places ?
- var accept = $iq({to: self.peerjid,
- type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: 'session-accept',
- initiator: self.initiator,
- responder: self.responder,
- sid: self.sid });
- self.localSDP.toJingle(
- accept,
- self.initiator == self.me ? 'initiator' : 'responder',
- ssrcs,
- self.getVideoType());
- self.connection.sendIQ(accept,
- function () {
- var ack = {};
- ack.source = 'answer';
- $(document).trigger('ack.jingle', [self.sid, ack]);
- },
- function (stanza) {
- var error = ($(stanza).find('error').length) ? {
- code: $(stanza).find('error').attr('code'),
- reason: $(stanza).find('error :first')[0].tagName,
- }:{};
- error.source = 'answer';
- JingleSession.onJingleError(self.sid, error);
- },
- 10000);
- }
- sdp.sdp = this.localSDP.raw;
- this.peerconnection.setLocalDescription(sdp,
- function () {
-
- //console.log('setLocalDescription success');
- if (self.usetrickle && !self.usepranswer) {
- sendJingle();
- }
- self.setLocalDescription();
- },
- function (e) {
- console.error('setLocalDescription failed', e);
- self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
- }
- );
- var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
- for (var j = 0; j < cands.length; j++) {
- var cand = SDPUtil.parse_icecandidate(cands[j]);
- if (cand.type == 'srflx') {
- this.hadstuncandidate = true;
- } else if (cand.type == 'relay') {
- this.hadturncandidate = true;
- }
- }
-};
-
-JingleSession.prototype.sendTerminate = function (reason, text) {
- var self = this,
- term = $iq({to: this.peerjid,
- type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: 'session-terminate',
- initiator: this.initiator,
- sid: this.sid})
- .c('reason')
- .c(reason || 'success');
-
- if (text) {
- term.up().c('text').t(text);
- }
-
- this.connection.sendIQ(term,
- function () {
- self.peerconnection.close();
- self.peerconnection = null;
- self.terminate();
- var ack = {};
- ack.source = 'terminate';
- $(document).trigger('ack.jingle', [self.sid, ack]);
- },
- function (stanza) {
- var error = ($(stanza).find('error').length) ? {
- code: $(stanza).find('error').attr('code'),
- reason: $(stanza).find('error :first')[0].tagName,
- }:{};
- $(document).trigger('ack.jingle', [self.sid, error]);
- },
- 10000);
- if (this.statsinterval !== null) {
- window.clearInterval(this.statsinterval);
- this.statsinterval = null;
- }
-};
-
-JingleSession.prototype.addSource = function (elem, fromJid) {
-
- var self = this;
- // FIXME: dirty waiting
- if (!this.peerconnection.localDescription)
- {
- console.warn("addSource - localDescription not ready yet")
- setTimeout(function()
- {
- self.addSource(elem, fromJid);
- },
- 200
- );
- return;
- }
-
- console.log('addssrc', new Date().getTime());
- console.log('ice', this.peerconnection.iceConnectionState);
-
- this.readSsrcInfo(elem);
-
- var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
- var mySdp = new SDP(this.peerconnection.localDescription.sdp);
-
- $(elem).each(function (idx, content) {
- var name = $(content).attr('name');
- var lines = '';
- $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
- var semantics = this.getAttribute('semantics');
- var ssrcs = $(this).find('>source').map(function () {
- return this.getAttribute('ssrc');
- }).get();
-
- if (ssrcs.length != 0) {
- lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
- }
- });
- var tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
- tmp.each(function () {
- var ssrc = $(this).attr('ssrc');
- if(mySdp.containsSSRC(ssrc)){
- /**
- * This happens when multiple participants change their streams at the same time and
- * ColibriFocus.modifySources have to wait for stable state. In the meantime multiple
- * addssrc are scheduled for update IQ. See
- */
- console.warn("Got add stream request for my own ssrc: "+ssrc);
- return;
- }
- $(this).find('>parameter').each(function () {
- lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
- if ($(this).attr('value') && $(this).attr('value').length)
- lines += ':' + $(this).attr('value');
- lines += '\r\n';
- });
- });
- sdp.media.forEach(function(media, idx) {
- if (!SDPUtil.find_line(media, 'a=mid:' + name))
- return;
- sdp.media[idx] += lines;
- if (!self.addssrc[idx]) self.addssrc[idx] = '';
- self.addssrc[idx] += lines;
- });
- sdp.raw = sdp.session + sdp.media.join('');
- });
-
- this.modifySourcesQueue.push(function() {
- // When a source is added and if this is FF, a new channel is allocated
- // for receiving the added source. We need to diffuse the SSRC of this
- // new recvonly channel to the rest of the peers.
- console.log('modify sources done');
-
- var newSdp = new SDP(self.peerconnection.localDescription.sdp);
- console.log("SDPs", mySdp, newSdp);
- self.notifyMySSRCUpdate(mySdp, newSdp);
- });
-};
-
-JingleSession.prototype.removeSource = function (elem, fromJid) {
-
- var self = this;
- // FIXME: dirty waiting
- if (!this.peerconnection.localDescription)
- {
- console.warn("removeSource - localDescription not ready yet")
- setTimeout(function()
- {
- self.removeSource(elem, fromJid);
- },
- 200
- );
- return;
- }
-
- console.log('removessrc', new Date().getTime());
- console.log('ice', this.peerconnection.iceConnectionState);
- var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
- var mySdp = new SDP(this.peerconnection.localDescription.sdp);
-
- $(elem).each(function (idx, content) {
- var name = $(content).attr('name');
- var lines = '';
- $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
- var semantics = this.getAttribute('semantics');
- var ssrcs = $(this).find('>source').map(function () {
- return this.getAttribute('ssrc');
- }).get();
-
- if (ssrcs.length != 0) {
- lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
- }
- });
- var tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
- tmp.each(function () {
- var ssrc = $(this).attr('ssrc');
- // This should never happen, but can be useful for bug detection
- if(mySdp.containsSSRC(ssrc)){
- console.error("Got remove stream request for my own ssrc: "+ssrc);
- return;
- }
- $(this).find('>parameter').each(function () {
- lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
- if ($(this).attr('value') && $(this).attr('value').length)
- lines += ':' + $(this).attr('value');
- lines += '\r\n';
- });
- });
- sdp.media.forEach(function(media, idx) {
- if (!SDPUtil.find_line(media, 'a=mid:' + name))
- return;
- sdp.media[idx] += lines;
- if (!self.removessrc[idx]) self.removessrc[idx] = '';
- self.removessrc[idx] += lines;
- });
- sdp.raw = sdp.session + sdp.media.join('');
- });
-
- this.modifySourcesQueue.push(function() {
- // When a source is removed and if this is FF, the recvonly channel that
- // receives the remote stream is deactivated . We need to diffuse the
- // recvonly SSRC removal to the rest of the peers.
- console.log('modify sources done');
-
- var newSdp = new SDP(self.peerconnection.localDescription.sdp);
- console.log("SDPs", mySdp, newSdp);
- self.notifyMySSRCUpdate(mySdp, newSdp);
- });
-};
-
-JingleSession.prototype._modifySources = function (successCallback, queueCallback) {
- var self = this;
-
- if (this.peerconnection.signalingState == 'closed') return;
- if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
- // There is nothing to do since scheduled job might have been executed by another succeeding call
- this.setLocalDescription();
- if(successCallback){
- successCallback();
- }
- queueCallback();
- return;
- }
-
- // Reset switch streams flag
- this.switchstreams = false;
-
- var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
-
- // add sources
- this.addssrc.forEach(function(lines, idx) {
- sdp.media[idx] += lines;
- });
- this.addssrc = [];
-
- // remove sources
- this.removessrc.forEach(function(lines, idx) {
- lines = lines.split('\r\n');
- lines.pop(); // remove empty last element;
- lines.forEach(function(line) {
- sdp.media[idx] = sdp.media[idx].replace(line + '\r\n', '');
- });
- });
- this.removessrc = [];
-
- sdp.raw = sdp.session + sdp.media.join('');
- this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}),
- function() {
-
- if(self.signalingState == 'closed') {
- console.error("createAnswer attempt on closed state");
- queueCallback("createAnswer attempt on closed state");
- return;
- }
-
- self.peerconnection.createAnswer(
- function(modifiedAnswer) {
- // change video direction, see https://github.com/jitsi/jitmeet/issues/41
- if (self.pendingop !== null) {
- var sdp = new SDP(modifiedAnswer.sdp);
- if (sdp.media.length > 1) {
- switch(self.pendingop) {
- case 'mute':
- sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
- break;
- case 'unmute':
- sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
- break;
- }
- sdp.raw = sdp.session + sdp.media.join('');
- modifiedAnswer.sdp = sdp.raw;
- }
- self.pendingop = null;
- }
-
- // FIXME: pushing down an answer while ice connection state
- // is still checking is bad...
- //console.log(self.peerconnection.iceConnectionState);
-
- // trying to work around another chrome bug
- //modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass');
- self.peerconnection.setLocalDescription(modifiedAnswer,
- function() {
- //console.log('modified setLocalDescription ok');
- self.setLocalDescription();
- if(successCallback){
- successCallback();
- }
- queueCallback();
- },
- function(error) {
- console.error('modified setLocalDescription failed', error);
- queueCallback(error);
- }
- );
- },
- function(error) {
- console.error('modified answer failed', error);
- queueCallback(error);
- }
- );
- },
- function(error) {
- console.error('modify failed', error);
- queueCallback(error);
- }
- );
-};
-
-
-/**
- * Switches video streams.
- * @param new_stream new stream that will be used as video of this session.
- * @param oldStream old video stream of this session.
- * @param success_callback callback executed after successful stream switch.
- */
-JingleSession.prototype.switchStreams = function (new_stream, oldStream, success_callback, isAudio) {
-
- var self = this;
-
- // Remember SDP to figure out added/removed SSRCs
- var oldSdp = null;
- if(self.peerconnection) {
- if(self.peerconnection.localDescription) {
- oldSdp = new SDP(self.peerconnection.localDescription.sdp);
- }
- self.peerconnection.removeStream(oldStream, true);
- if(new_stream)
- self.peerconnection.addStream(new_stream);
- }
-
- // Conference is not active
- if(!oldSdp || !self.peerconnection) {
- success_callback();
- return;
- }
-
- self.switchstreams = true;
- self.modifySourcesQueue.push(function() {
- console.log('modify sources done');
-
- success_callback();
-
- var newSdp = new SDP(self.peerconnection.localDescription.sdp);
- console.log("SDPs", oldSdp, newSdp);
- self.notifyMySSRCUpdate(oldSdp, newSdp);
- });
-};
-
-/**
- * Figures out added/removed ssrcs and send update IQs.
- * @param old_sdp SDP object for old description.
- * @param new_sdp SDP object for new description.
- */
-JingleSession.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
-
- if (!(this.peerconnection.signalingState == 'stable' &&
- this.peerconnection.iceConnectionState == 'connected')){
- console.log("Too early to send updates");
- return;
- }
-
- // send source-remove IQ.
- sdpDiffer = new SDPDiffer(new_sdp, old_sdp);
- var remove = $iq({to: this.peerjid, type: 'set'})
- .c('jingle', {
- xmlns: 'urn:xmpp:jingle:1',
- action: 'source-remove',
- initiator: this.initiator,
- sid: this.sid
- }
- );
- var removed = sdpDiffer.toJingle(remove);
- if (removed) {
- this.connection.sendIQ(remove,
- function (res) {
- console.info('got remove result', res);
- },
- function (err) {
- console.error('got remove error', err);
- }
- );
- } else {
- console.log('removal not necessary');
- }
-
- // send source-add IQ.
- var sdpDiffer = new SDPDiffer(old_sdp, new_sdp);
- var add = $iq({to: this.peerjid, type: 'set'})
- .c('jingle', {
- xmlns: 'urn:xmpp:jingle:1',
- action: 'source-add',
- initiator: this.initiator,
- sid: this.sid
- }
- );
- var added = sdpDiffer.toJingle(add, this.getVideoType());
- if (added) {
- this.connection.sendIQ(add,
- function (res) {
- console.info('got add result', res);
- },
- function (err) {
- console.error('got add error', err);
- }
- );
- } else {
- console.log('addition not necessary');
- }
-};
-
-/**
- * Mutes/unmutes the (local) video i.e. enables/disables all video tracks.
- *
- * @param mute
true to mute the (local) video i.e. to disable all video
- * tracks; otherwise,
false
- * @param callback a function to be invoked with
mute after all video
- * tracks have been enabled/disabled. The function may, optionally, return
- * another function which is to be invoked after the whole mute/unmute operation
- * has completed successfully.
- * @param options an object which specifies optional arguments such as the
- *
boolean key
byUser with default value
true which
- * specifies whether the method was initiated in response to a user command (in
- * contrast to an automatic decision made by the application logic)
- */
-JingleSession.prototype.setVideoMute = function (mute, callback, options) {
- var byUser;
-
- if (options) {
- byUser = options.byUser;
- if (typeof byUser === 'undefined') {
- byUser = true;
- }
- } else {
- byUser = true;
- }
- // The user's command to mute the (local) video takes precedence over any
- // automatic decision made by the application logic.
- if (byUser) {
- this.videoMuteByUser = mute;
- } else if (this.videoMuteByUser) {
- return;
- }
-
- this.hardMuteVideo(mute);
-
- var self = this;
- var oldSdp = null;
- if(self.peerconnection) {
- if(self.peerconnection.localDescription) {
- oldSdp = new SDP(self.peerconnection.localDescription.sdp);
- }
- }
-
- this.modifySourcesQueue.push(function() {
- console.log('modify sources done');
-
- callback(mute);
-
- var newSdp = new SDP(self.peerconnection.localDescription.sdp);
- console.log("SDPs", oldSdp, newSdp);
- self.notifyMySSRCUpdate(oldSdp, newSdp);
- });
-};
-
-JingleSession.prototype.hardMuteVideo = function (muted) {
- this.pendingop = muted ? 'mute' : 'unmute';
-};
-
-JingleSession.prototype.sendMute = function (muted, content) {
- var info = $iq({to: this.peerjid,
- type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: 'session-info',
- initiator: this.initiator,
- sid: this.sid });
- info.c(muted ? 'mute' : 'unmute', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
- info.attrs({'creator': this.me == this.initiator ? 'creator' : 'responder'});
- if (content) {
- info.attrs({'name': content});
- }
- this.connection.send(info);
-};
-
-JingleSession.prototype.sendRinging = function () {
- var info = $iq({to: this.peerjid,
- type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: 'session-info',
- initiator: this.initiator,
- sid: this.sid });
- info.c('ringing', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
- this.connection.send(info);
-};
-
-JingleSession.prototype.getStats = function (interval) {
- var self = this;
- var recv = {audio: 0, video: 0};
- var lost = {audio: 0, video: 0};
- var lastrecv = {audio: 0, video: 0};
- var lastlost = {audio: 0, video: 0};
- var loss = {audio: 0, video: 0};
- var delta = {audio: 0, video: 0};
- this.statsinterval = window.setInterval(function () {
- if (self && self.peerconnection && self.peerconnection.getStats) {
- self.peerconnection.getStats(function (stats) {
- var results = stats.result();
- // TODO: there are so much statistics you can get from this..
- for (var i = 0; i < results.length; ++i) {
- if (results[i].type == 'ssrc') {
- var packetsrecv = results[i].stat('packetsReceived');
- var packetslost = results[i].stat('packetsLost');
- if (packetsrecv && packetslost) {
- packetsrecv = parseInt(packetsrecv, 10);
- packetslost = parseInt(packetslost, 10);
-
- if (results[i].stat('googFrameRateReceived')) {
- lastlost.video = lost.video;
- lastrecv.video = recv.video;
- recv.video = packetsrecv;
- lost.video = packetslost;
- } else {
- lastlost.audio = lost.audio;
- lastrecv.audio = recv.audio;
- recv.audio = packetsrecv;
- lost.audio = packetslost;
- }
- }
- }
- }
- delta.audio = recv.audio - lastrecv.audio;
- delta.video = recv.video - lastrecv.video;
- loss.audio = (delta.audio > 0) ? Math.ceil(100 * (lost.audio - lastlost.audio) / delta.audio) : 0;
- loss.video = (delta.video > 0) ? Math.ceil(100 * (lost.video - lastlost.video) / delta.video) : 0;
- $(document).trigger('packetloss.jingle', [self.sid, loss]);
- });
- }
- }, interval || 3000);
- return this.statsinterval;
-};
-
-JingleSession.onJingleError = function (session, error)
-{
- console.error("Jingle error", error);
-}
-
-JingleSession.onJingleFatalError = function (session, error)
-{
- this.service.sessionTerminated = true;
- this.connection.emuc.doLeave();
- this.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
- this.eventEmitter.emit(XMPPEvents.JINGLE_FATAL_ERROR, session, error);
-}
-
-JingleSession.prototype.setLocalDescription = function () {
- var self = this;
- var newssrcs = [];
- var session = transform.parse(this.peerconnection.localDescription.sdp);
- session.media.forEach(function (media) {
-
- if (media.ssrcs != null && media.ssrcs.length > 0) {
- // TODO(gp) maybe exclude FID streams?
- media.ssrcs.forEach(function (ssrc) {
- if (ssrc.attribute !== 'cname') {
- return;
- }
- newssrcs.push({
- 'ssrc': ssrc.id,
- 'type': media.type
- });
- });
- }
- else if(self.localStreamsSSRC && self.localStreamsSSRC[media.type])
- {
- newssrcs.push({
- 'ssrc': self.localStreamsSSRC[media.type],
- 'type': media.type
- });
- }
-
- });
-
- console.log('new ssrcs', newssrcs);
-
- // Bind us as local SSRCs owner
- if (newssrcs.length > 0) {
- for (var i = 1; i <= newssrcs.length; i ++) {
- var ssrc = newssrcs[i-1].ssrc;
- var myJid = self.connection.emuc.myroomjid;
- self.ssrcOwners[ssrc] = myJid;
- if (newssrcs[i-1].type === 'video'){
- self.ssrcVideoTypes[ssrc] = self.getVideoType();
- }
- }
- }
-}
-
-// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
-function sendKeyframe(pc) {
- console.log('sendkeyframe', pc.iceConnectionState);
- if (pc.iceConnectionState !== 'connected') return; // safe...
- var self = this;
- pc.setRemoteDescription(
- pc.remoteDescription,
- function () {
- pc.createAnswer(
- function (modifiedAnswer) {
- pc.setLocalDescription(
- modifiedAnswer,
- function () {
- // noop
- },
- function (error) {
- console.log('triggerKeyframe setLocalDescription failed', error);
- eventEmitter.emit(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR);
- }
- );
- },
- function (error) {
- console.log('triggerKeyframe createAnswer failed', error);
- eventEmitter.emit(XMPPEvents.CREATE_ANSWER_ERROR);
- }
- );
- },
- function (error) {
- console.log('triggerKeyframe setRemoteDescription failed', error);
- eventEmitter.emit(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR);
- }
- );
-}
-
-
-JingleSession.prototype.remoteStreamAdded = function (data, times) {
- var self = this;
- var thessrc;
- var streamId = APP.RTC.getStreamID(data.stream);
-
- // look up an associated JID for a stream id
- if (!streamId) {
- console.error("No stream ID for", data.stream);
- } else if (streamId && streamId.indexOf('mixedmslabel') === -1) {
- // look only at a=ssrc: and _not_ at a=ssrc-group: lines
-
- var ssrclines
- = SDPUtil.find_lines(this.peerconnection.remoteDescription.sdp, 'a=ssrc:');
- ssrclines = ssrclines.filter(function (line) {
- // NOTE(gp) previously we filtered on the mslabel, but that property
- // is not always present.
- // return line.indexOf('mslabel:' + data.stream.label) !== -1;
-
- if (RTCBrowserType.isTemasysPluginUsed()) {
- return ((line.indexOf('mslabel:' + streamId) !== -1));
- } else {
- return ((line.indexOf('msid:' + streamId) !== -1));
- }
- });
- if (ssrclines.length) {
- thessrc = ssrclines[0].substring(7).split(' ')[0];
-
- if (!self.ssrcOwners[thessrc]) {
- console.error("No SSRC owner known for: " + thessrc);
- return;
- }
- data.peerjid = self.ssrcOwners[thessrc];
- data.videoType = self.ssrcVideoTypes[thessrc]
- console.log('associated jid', self.ssrcOwners[thessrc],
- thessrc, data.videoType);
- } else {
- console.error("No SSRC lines for ", streamId);
- }
- }
-
- APP.RTC.createRemoteStream(data, this.sid, thessrc);
-
- var isVideo = data.stream.getVideoTracks().length > 0;
- // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
- if (isVideo &&
- data.peerjid && this.peerjid === data.peerjid &&
- data.stream.getVideoTracks().length === 0 &&
- APP.RTC.localVideo.getTracks().length > 0) {
- window.setTimeout(function () {
- sendKeyframe(self.peerconnection);
- }, 3000);
- }
-}
-
-module.exports = JingleSession;
-
-},{"../../service/xmpp/XMPPEvents":110,"../RTC/RTCBrowserType":10,"./SDP":56,"./SDPDiffer":57,"./SDPUtil":58,"./TraceablePeerConnection":59,"async":69,"sdp-transform":97}],56:[function(require,module,exports){
-/* jshint -W117 */
-var SDPUtil = require("./SDPUtil");
-
-// SDP STUFF
-function SDP(sdp) {
- this.media = sdp.split('\r\nm=');
- for (var i = 1; i < this.media.length; i++) {
- this.media[i] = 'm=' + this.media[i];
- if (i != this.media.length - 1) {
- this.media[i] += '\r\n';
- }
- }
- this.session = this.media.shift() + '\r\n';
- this.raw = this.session + this.media.join('');
-}
-/**
- * Returns map of MediaChannel mapped per channel idx.
- */
-SDP.prototype.getMediaSsrcMap = function() {
- var self = this;
- var media_ssrcs = {};
- var tmp;
- for (var mediaindex = 0; mediaindex < self.media.length; mediaindex++) {
- tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc:');
- var mid = SDPUtil.parse_mid(SDPUtil.find_line(self.media[mediaindex], 'a=mid:'));
- var media = {
- mediaindex: mediaindex,
- mid: mid,
- ssrcs: {},
- ssrcGroups: []
- };
- media_ssrcs[mediaindex] = media;
- tmp.forEach(function (line) {
- var linessrc = line.substring(7).split(' ')[0];
- // allocate new ChannelSsrc
- if(!media.ssrcs[linessrc]) {
- media.ssrcs[linessrc] = {
- ssrc: linessrc,
- lines: []
- };
- }
- media.ssrcs[linessrc].lines.push(line);
- });
- tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc-group:');
- tmp.forEach(function(line){
- var semantics = line.substr(0, idx).substr(13);
- var ssrcs = line.substr(14 + semantics.length).split(' ');
- if (ssrcs.length != 0) {
- media.ssrcGroups.push({
- semantics: semantics,
- ssrcs: ssrcs
- });
- }
- });
- }
- return media_ssrcs;
-};
-/**
- * Returns
true if this SDP contains given SSRC.
- * @param ssrc the ssrc to check.
- * @returns {boolean}
true if this SDP contains given SSRC.
- */
-SDP.prototype.containsSSRC = function(ssrc) {
- var medias = this.getMediaSsrcMap();
- var contains = false;
- Object.keys(medias).forEach(function(mediaindex){
- var media = medias[mediaindex];
- //console.log("Check", channel, ssrc);
- if(Object.keys(media.ssrcs).indexOf(ssrc) != -1){
- contains = true;
- }
- });
- return contains;
-};
-
-
-// remove iSAC and CN from SDP
-SDP.prototype.mangle = function () {
- var i, j, mline, lines, rtpmap, newdesc;
- for (i = 0; i < this.media.length; i++) {
- lines = this.media[i].split('\r\n');
- lines.pop(); // remove empty last element
- mline = SDPUtil.parse_mline(lines.shift());
- if (mline.media != 'audio')
- continue;
- newdesc = '';
- mline.fmt.length = 0;
- for (j = 0; j < lines.length; j++) {
- if (lines[j].substr(0, 9) == 'a=rtpmap:') {
- rtpmap = SDPUtil.parse_rtpmap(lines[j]);
- if (rtpmap.name == 'CN' || rtpmap.name == 'ISAC')
- continue;
- mline.fmt.push(rtpmap.id);
- newdesc += lines[j] + '\r\n';
- } else {
- newdesc += lines[j] + '\r\n';
- }
- }
- this.media[i] = SDPUtil.build_mline(mline) + '\r\n';
- this.media[i] += newdesc;
- }
- this.raw = this.session + this.media.join('');
-};
-
-// remove lines matching prefix from session section
-SDP.prototype.removeSessionLines = function(prefix) {
- var self = this;
- var lines = SDPUtil.find_lines(this.session, prefix);
- lines.forEach(function(line) {
- self.session = self.session.replace(line + '\r\n', '');
- });
- this.raw = this.session + this.media.join('');
- return lines;
-}
-// remove lines matching prefix from a media section specified by mediaindex
-// TODO: non-numeric mediaindex could match mid
-SDP.prototype.removeMediaLines = function(mediaindex, prefix) {
- var self = this;
- var lines = SDPUtil.find_lines(this.media[mediaindex], prefix);
- lines.forEach(function(line) {
- self.media[mediaindex] = self.media[mediaindex].replace(line + '\r\n', '');
- });
- this.raw = this.session + this.media.join('');
- return lines;
-}
-
-// add content's to a jingle element
-SDP.prototype.toJingle = function (elem, thecreator, ssrcs, videoType) {
-// console.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]);
- var i, j, k, mline, ssrc, rtpmap, tmp, line, lines;
- var self = this;
- // new bundle plan
- if (SDPUtil.find_line(this.session, 'a=group:')) {
- lines = SDPUtil.find_lines(this.session, 'a=group:');
- for (i = 0; i < lines.length; i++) {
- tmp = lines[i].split(' ');
- var semantics = tmp.shift().substr(8);
- elem.c('group', {xmlns: 'urn:xmpp:jingle:apps:grouping:0', semantics:semantics});
- for (j = 0; j < tmp.length; j++) {
- elem.c('content', {name: tmp[j]}).up();
- }
- elem.up();
- }
- }
- for (i = 0; i < this.media.length; i++) {
- mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]);
- if (!(mline.media === 'audio' ||
- mline.media === 'video' ||
- mline.media === 'application'))
- {
- continue;
- }
- if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) {
- ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first
- } else {
- if(ssrcs && ssrcs[mline.media])
- {
- ssrc = ssrcs[mline.media];
- }
- else
- ssrc = false;
- }
-
- elem.c('content', {creator: thecreator, name: mline.media});
- if (SDPUtil.find_line(this.media[i], 'a=mid:')) {
- // prefer identifier from a=mid if present
- var mid = SDPUtil.parse_mid(SDPUtil.find_line(this.media[i], 'a=mid:'));
- elem.attrs({ name: mid });
- }
-
- if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length)
- {
- elem.c('description',
- {xmlns: 'urn:xmpp:jingle:apps:rtp:1',
- media: mline.media });
- if (ssrc) {
- elem.attrs({ssrc: ssrc});
- }
- for (j = 0; j < mline.fmt.length; j++) {
- rtpmap = SDPUtil.find_line(this.media[i], 'a=rtpmap:' + mline.fmt[j]);
- elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
- // put any 'a=fmtp:' + mline.fmt[j] lines into
- if (SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])) {
- tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j]));
- for (k = 0; k < tmp.length; k++) {
- elem.c('parameter', tmp[k]).up();
- }
- }
- this.RtcpFbToJingle(i, elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb
-
- elem.up();
- }
- if (SDPUtil.find_line(this.media[i], 'a=crypto:', this.session)) {
- elem.c('encryption', {required: 1});
- var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session);
- crypto.forEach(function(line) {
- elem.c('crypto', SDPUtil.parse_crypto(line)).up();
- });
- elem.up(); // end of encryption
- }
-
- if (ssrc) {
- // new style mapping
- elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
- // FIXME: group by ssrc and support multiple different ssrcs
- var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:');
- if(ssrclines.length > 0) {
- ssrclines.forEach(function (line) {
- idx = line.indexOf(' ');
- var linessrc = line.substr(0, idx).substr(7);
- if (linessrc != ssrc) {
- elem.up();
- ssrc = linessrc;
- elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
- }
- var kv = line.substr(idx + 1);
- elem.c('parameter');
- if (kv.indexOf(':') == -1) {
- elem.attrs({ name: kv });
- } else {
- var k = kv.split(':', 2)[0];
- elem.attrs({ name: k });
-
- var v = kv.split(':', 2)[1];
- v = SDPUtil.filter_special_chars(v);
- elem.attrs({ value: v });
- }
- elem.up();
- });
- }
- else
- {
- elem.up();
- elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
- elem.c('parameter');
- elem.attrs({name: "cname", value:Math.random().toString(36).substring(7)});
- elem.up();
- var msid = null;
- if(mline.media == "audio")
- {
- msid = APP.RTC.localAudio.getId();
- }
- else
- {
- msid = APP.RTC.localVideo.getId();
- }
- if(msid != null)
- {
- msid = SDPUtil.filter_special_chars(msid);
- elem.c('parameter');
- elem.attrs({name: "msid", value:msid});
- elem.up();
- elem.c('parameter');
- elem.attrs({name: "mslabel", value:msid});
- elem.up();
- elem.c('parameter');
- elem.attrs({name: "label", value:msid});
- elem.up();
- }
- }
- // Video type
- if (videoType && mline.media == "video") {
- elem.c('ssrc-info',
- {
- xmlns: 'http://jitsi.org/jitmeet',
- 'video-type': videoType
- }).up();
- }
- elem.up();
-
- // XEP-0339 handle ssrc-group attributes
- var ssrc_group_lines = SDPUtil.find_lines(this.media[i], 'a=ssrc-group:');
- ssrc_group_lines.forEach(function(line) {
- idx = line.indexOf(' ');
- var semantics = line.substr(0, idx).substr(13);
- var ssrcs = line.substr(14 + semantics.length).split(' ');
- if (ssrcs.length != 0) {
- elem.c('ssrc-group', { semantics: semantics, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
- ssrcs.forEach(function(ssrc) {
- elem.c('source', { ssrc: ssrc })
- .up();
- });
- elem.up();
- }
- });
- }
-
- if (SDPUtil.find_line(this.media[i], 'a=rtcp-mux')) {
- elem.c('rtcp-mux').up();
- }
-
- // XEP-0293 -- map a=rtcp-fb:*
- this.RtcpFbToJingle(i, elem, '*');
-
- // XEP-0294
- if (SDPUtil.find_line(this.media[i], 'a=extmap:')) {
- lines = SDPUtil.find_lines(this.media[i], 'a=extmap:');
- for (j = 0; j < lines.length; j++) {
- tmp = SDPUtil.parse_extmap(lines[j]);
- elem.c('rtp-hdrext', { xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0',
- uri: tmp.uri,
- id: tmp.value });
- if (tmp.hasOwnProperty('direction')) {
- switch (tmp.direction) {
- case 'sendonly':
- elem.attrs({senders: 'responder'});
- break;
- case 'recvonly':
- elem.attrs({senders: 'initiator'});
- break;
- case 'sendrecv':
- elem.attrs({senders: 'both'});
- break;
- case 'inactive':
- elem.attrs({senders: 'none'});
- break;
- }
- }
- // TODO: handle params
- elem.up();
- }
- }
- elem.up(); // end of description
- }
-
- // map ice-ufrag/pwd, dtls fingerprint, candidates
- this.TransportToJingle(i, elem);
-
- if (SDPUtil.find_line(this.media[i], 'a=sendrecv', this.session)) {
- elem.attrs({senders: 'both'});
- } else if (SDPUtil.find_line(this.media[i], 'a=sendonly', this.session)) {
- elem.attrs({senders: 'initiator'});
- } else if (SDPUtil.find_line(this.media[i], 'a=recvonly', this.session)) {
- elem.attrs({senders: 'responder'});
- } else if (SDPUtil.find_line(this.media[i], 'a=inactive', this.session)) {
- elem.attrs({senders: 'none'});
- }
- if (mline.port == '0') {
- // estos hack to reject an m-line
- elem.attrs({senders: 'rejected'});
- }
- elem.up(); // end of content
- }
- elem.up();
- return elem;
-};
-
-SDP.prototype.TransportToJingle = function (mediaindex, elem) {
- var i = mediaindex;
- var tmp;
- var self = this;
- elem.c('transport');
-
- // XEP-0343 DTLS/SCTP
- if (SDPUtil.find_line(this.media[mediaindex], 'a=sctpmap:').length)
- {
- var sctpmap = SDPUtil.find_line(
- this.media[i], 'a=sctpmap:', self.session);
- if (sctpmap)
- {
- var sctpAttrs = SDPUtil.parse_sctpmap(sctpmap);
- elem.c('sctpmap',
- {
- xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1',
- number: sctpAttrs[0], /* SCTP port */
- protocol: sctpAttrs[1], /* protocol */
- });
- // Optional stream count attribute
- if (sctpAttrs.length > 2)
- elem.attrs({ streams: sctpAttrs[2]});
- elem.up();
- }
- }
- // XEP-0320
- var fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session);
- fingerprints.forEach(function(line) {
- tmp = SDPUtil.parse_fingerprint(line);
- tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0';
- elem.c('fingerprint').t(tmp.fingerprint);
- delete tmp.fingerprint;
- line = SDPUtil.find_line(self.media[mediaindex], 'a=setup:', self.session);
- if (line) {
- tmp.setup = line.substr(8);
- }
- elem.attrs(tmp);
- elem.up(); // end of fingerprint
- });
- tmp = SDPUtil.iceparams(this.media[mediaindex], this.session);
- if (tmp) {
- tmp.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
- elem.attrs(tmp);
- // XEP-0176
- if (SDPUtil.find_line(this.media[mediaindex], 'a=candidate:', this.session)) { // add any a=candidate lines
- var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=candidate:', this.session);
- lines.forEach(function (line) {
- elem.c('candidate', SDPUtil.candidateToJingle(line)).up();
- });
- }
- }
- elem.up(); // end of transport
-}
-
-SDP.prototype.RtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP-0293
- var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=rtcp-fb:' + payloadtype);
- lines.forEach(function (line) {
- var tmp = SDPUtil.parse_rtcpfb(line);
- if (tmp.type == 'trr-int') {
- elem.c('rtcp-fb-trr-int', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', value: tmp.params[0]});
- elem.up();
- } else {
- elem.c('rtcp-fb', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', type: tmp.type});
- if (tmp.params.length > 0) {
- elem.attrs({'subtype': tmp.params[0]});
- }
- elem.up();
- }
- });
-};
-
-SDP.prototype.RtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293
- var media = '';
- var tmp = elem.find('>rtcp-fb-trr-int[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
- if (tmp.length) {
- media += 'a=rtcp-fb:' + '*' + ' ' + 'trr-int' + ' ';
- if (tmp.attr('value')) {
- media += tmp.attr('value');
- } else {
- media += '0';
- }
- media += '\r\n';
- }
- tmp = elem.find('>rtcp-fb[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
- tmp.each(function () {
- media += 'a=rtcp-fb:' + payloadtype + ' ' + $(this).attr('type');
- if ($(this).attr('subtype')) {
- media += ' ' + $(this).attr('subtype');
- }
- media += '\r\n';
- });
- return media;
-};
-
-// construct an SDP from a jingle stanza
-SDP.prototype.fromJingle = function (jingle) {
- var self = this;
- this.raw = 'v=0\r\n' +
- 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
- 's=-\r\n' +
- 't=0 0\r\n';
- // http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04#section-8
- if ($(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').length) {
- $(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').each(function (idx, group) {
- var contents = $(group).find('>content').map(function (idx, content) {
- return content.getAttribute('name');
- }).get();
- if (contents.length > 0) {
- self.raw += 'a=group:' + (group.getAttribute('semantics') || group.getAttribute('type')) + ' ' + contents.join(' ') + '\r\n';
- }
- });
- }
-
- this.session = this.raw;
- jingle.find('>content').each(function () {
- var m = self.jingle2media($(this));
- self.media.push(m);
- });
-
- // reconstruct msid-semantic -- apparently not necessary
- /*
- var msid = SDPUtil.parse_ssrc(this.raw);
- if (msid.hasOwnProperty('mslabel')) {
- this.session += "a=msid-semantic: WMS " + msid.mslabel + "\r\n";
- }
- */
-
- this.raw = this.session + this.media.join('');
-};
-
-// translate a jingle content element into an an SDP media part
-SDP.prototype.jingle2media = function (content) {
- var media = '',
- desc = content.find('description'),
- ssrc = desc.attr('ssrc'),
- self = this,
- tmp;
- var sctp = content.find(
- '>transport>sctpmap[xmlns="urn:xmpp:jingle:transports:dtls-sctp:1"]');
-
- tmp = { media: desc.attr('media') };
- tmp.port = '1';
- if (content.attr('senders') == 'rejected') {
- // estos hack to reject an m-line.
- tmp.port = '0';
- }
- if (content.find('>transport>fingerprint').length || desc.find('encryption').length) {
- if (sctp.length)
- tmp.proto = 'DTLS/SCTP';
- else
- tmp.proto = 'RTP/SAVPF';
- } else {
- tmp.proto = 'RTP/AVPF';
- }
- if (!sctp.length)
- {
- tmp.fmt = desc.find('payload-type').map(
- function () { return this.getAttribute('id'); }).get();
- media += SDPUtil.build_mline(tmp) + '\r\n';
- }
- else
- {
- media += 'm=application 1 DTLS/SCTP ' + sctp.attr('number') + '\r\n';
- media += 'a=sctpmap:' + sctp.attr('number') +
- ' ' + sctp.attr('protocol');
-
- var streamCount = sctp.attr('streams');
- if (streamCount)
- media += ' ' + streamCount + '\r\n';
- else
- media += '\r\n';
- }
-
- media += 'c=IN IP4 0.0.0.0\r\n';
- if (!sctp.length)
- media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n';
- tmp = content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
- if (tmp.length) {
- if (tmp.attr('ufrag')) {
- media += SDPUtil.build_iceufrag(tmp.attr('ufrag')) + '\r\n';
- }
- if (tmp.attr('pwd')) {
- media += SDPUtil.build_icepwd(tmp.attr('pwd')) + '\r\n';
- }
- tmp.find('>fingerprint').each(function () {
- // FIXME: check namespace at some point
- media += 'a=fingerprint:' + this.getAttribute('hash');
- media += ' ' + $(this).text();
- media += '\r\n';
- if (this.getAttribute('setup')) {
- media += 'a=setup:' + this.getAttribute('setup') + '\r\n';
- }
- });
- }
- switch (content.attr('senders')) {
- case 'initiator':
- media += 'a=sendonly\r\n';
- break;
- case 'responder':
- media += 'a=recvonly\r\n';
- break;
- case 'none':
- media += 'a=inactive\r\n';
- break;
- case 'both':
- media += 'a=sendrecv\r\n';
- break;
- }
- media += 'a=mid:' + content.attr('name') + '\r\n';
-
- //
- // see http://code.google.com/p/libjingle/issues/detail?id=309 -- no spec though
- // and http://mail.jabber.org/pipermail/jingle/2011-December/001761.html
- if (desc.find('rtcp-mux').length) {
- media += 'a=rtcp-mux\r\n';
- }
-
- if (desc.find('encryption').length) {
- desc.find('encryption>crypto').each(function () {
- media += 'a=crypto:' + this.getAttribute('tag');
- media += ' ' + this.getAttribute('crypto-suite');
- media += ' ' + this.getAttribute('key-params');
- if (this.getAttribute('session-params')) {
- media += ' ' + this.getAttribute('session-params');
- }
- media += '\r\n';
- });
- }
- desc.find('payload-type').each(function () {
- media += SDPUtil.build_rtpmap(this) + '\r\n';
- if ($(this).find('>parameter').length) {
- media += 'a=fmtp:' + this.getAttribute('id') + ' ';
- media += $(this).find('parameter').map(function () { return (this.getAttribute('name') ? (this.getAttribute('name') + '=') : '') + this.getAttribute('value'); }).get().join('; ');
- media += '\r\n';
- }
- // xep-0293
- media += self.RtcpFbFromJingle($(this), this.getAttribute('id'));
- });
-
- // xep-0293
- media += self.RtcpFbFromJingle(desc, '*');
-
- // xep-0294
- tmp = desc.find('>rtp-hdrext[xmlns="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"]');
- tmp.each(function () {
- media += 'a=extmap:' + this.getAttribute('id') + ' ' + this.getAttribute('uri') + '\r\n';
- });
-
- content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]>candidate').each(function () {
- media += SDPUtil.candidateFromJingle(this);
- });
-
- // XEP-0339 handle ssrc-group attributes
- tmp = content.find('description>ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
- var semantics = this.getAttribute('semantics');
- var ssrcs = $(this).find('>source').map(function() {
- return this.getAttribute('ssrc');
- }).get();
-
- if (ssrcs.length != 0) {
- media += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
- }
- });
-
- tmp = content.find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
- tmp.each(function () {
- var ssrc = this.getAttribute('ssrc');
- $(this).find('>parameter').each(function () {
- var name = this.getAttribute('name');
- var value = this.getAttribute('value');
- value = SDPUtil.filter_special_chars(value);
- media += 'a=ssrc:' + ssrc + ' ' + name;
- if (value && value.length)
- media += ':' + value;
- media += '\r\n';
- });
- });
-
- return media;
-};
-
-
-module.exports = SDP;
-
-
-},{"./SDPUtil":58}],57:[function(require,module,exports){
-
-var SDPUtil = require("./SDPUtil");
-
-function SDPDiffer(mySDP, otherSDP) {
- this.mySDP = mySDP;
- this.otherSDP = otherSDP;
-}
-
-/**
- * Returns map of MediaChannel that contains only media not contained in
otherSdp. Mapped by channel idx.
- * @param otherSdp the other SDP to check ssrc with.
- */
-SDPDiffer.prototype.getNewMedia = function() {
-
- // this could be useful in Array.prototype.
- function arrayEquals(array) {
- // if the other array is a falsy value, return
- if (!array)
- return false;
-
- // compare lengths - can save a lot of time
- if (this.length != array.length)
- return false;
-
- for (var i = 0, l=this.length; i < l; i++) {
- // Check if we have nested arrays
- if (this[i] instanceof Array && array[i] instanceof Array) {
- // recurse into the nested arrays
- if (!this[i].equals(array[i]))
- return false;
- }
- else if (this[i] != array[i]) {
- // Warning - two different object instances will never be equal: {x:20} != {x:20}
- return false;
- }
- }
- return true;
- }
-
- var myMedias = this.mySDP.getMediaSsrcMap();
- var othersMedias = this.otherSDP.getMediaSsrcMap();
- var newMedia = {};
- Object.keys(othersMedias).forEach(function(othersMediaIdx) {
- var myMedia = myMedias[othersMediaIdx];
- var othersMedia = othersMedias[othersMediaIdx];
- if(!myMedia && othersMedia) {
- // Add whole channel
- newMedia[othersMediaIdx] = othersMedia;
- return;
- }
- // Look for new ssrcs accross the channel
- Object.keys(othersMedia.ssrcs).forEach(function(ssrc) {
- if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
- // Allocate channel if we've found ssrc that doesn't exist in our channel
- if(!newMedia[othersMediaIdx]){
- newMedia[othersMediaIdx] = {
- mediaindex: othersMedia.mediaindex,
- mid: othersMedia.mid,
- ssrcs: {},
- ssrcGroups: []
- };
- }
- newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
- }
- });
-
- // Look for new ssrc groups across the channels
- othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){
-
- // try to match the other ssrc-group with an ssrc-group of ours
- var matched = false;
- for (var i = 0; i < myMedia.ssrcGroups.length; i++) {
- var mySsrcGroup = myMedia.ssrcGroups[i];
- if (otherSsrcGroup.semantics == mySsrcGroup.semantics
- && arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
-
- matched = true;
- break;
- }
- }
-
- if (!matched) {
- // Allocate channel if we've found an ssrc-group that doesn't
- // exist in our channel
-
- if(!newMedia[othersMediaIdx]){
- newMedia[othersMediaIdx] = {
- mediaindex: othersMedia.mediaindex,
- mid: othersMedia.mid,
- ssrcs: {},
- ssrcGroups: []
- };
- }
- newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
- }
- });
- });
- return newMedia;
-};
-
-/**
- * Sends SSRC update IQ.
- * @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
- * @param sid session identifier that will be put into the IQ.
- * @param initiator initiator identifier.
- * @param toJid destination Jid
- * @param isAdd indicates if this is remove or add operation.
- */
-SDPDiffer.prototype.toJingle = function(modify, videoType) {
- var sdpMediaSsrcs = this.getNewMedia();
- var self = this;
-
- // FIXME: only announce video ssrcs since we mix audio and dont need
- // the audio ssrcs therefore
- var modified = false;
- Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){
- modified = true;
- var media = sdpMediaSsrcs[mediaindex];
- modify.c('content', {name: media.mid});
-
- modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid});
- // FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
- // generate sources from lines
- Object.keys(media.ssrcs).forEach(function(ssrcNum) {
- var mediaSsrc = media.ssrcs[ssrcNum];
- modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
- modify.attrs({ssrc: mediaSsrc.ssrc});
- // iterate over ssrc lines
- mediaSsrc.lines.forEach(function (line) {
- var idx = line.indexOf(' ');
- var kv = line.substr(idx + 1);
- modify.c('parameter');
- if (kv.indexOf(':') == -1) {
- modify.attrs({ name: kv });
- } else {
- var nv = kv.split(':', 2);
- var name = nv[0];
- var value = SDPUtil.filter_special_chars(nv[1]);
- modify.attrs({ name: name });
- modify.attrs({ value: value });
- }
- modify.up(); // end of parameter
- });
- // indicate video type
- if (videoType && media.mid == 'video') {
- modify.c('ssrc-info',
- {
- xmlns: 'http://jitsi.org/jitmeet',
- 'video-type': videoType
- })
- .up();
- }
- modify.up(); // end of source
- });
-
- // generate source groups from lines
- media.ssrcGroups.forEach(function(ssrcGroup) {
- if (ssrcGroup.ssrcs.length != 0) {
-
- modify.c('ssrc-group', {
- semantics: ssrcGroup.semantics,
- xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
- });
-
- ssrcGroup.ssrcs.forEach(function (ssrc) {
- modify.c('source', { ssrc: ssrc })
- .up(); // end of source
- });
- modify.up(); // end of ssrc-group
- }
- });
-
- modify.up(); // end of description
- modify.up(); // end of content
- });
-
- return modified;
-};
-
-module.exports = SDPDiffer;
-},{"./SDPUtil":58}],58:[function(require,module,exports){
-SDPUtil = {
- filter_special_chars: function (text) {
- return text.replace(/[\\\/\{,\}\+]/g, "");
- },
- iceparams: function (mediadesc, sessiondesc) {
- var data = null;
- if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
- SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
- data = {
- ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
- pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
- };
- }
- return data;
- },
- parse_iceufrag: function (line) {
- return line.substring(12);
- },
- build_iceufrag: function (frag) {
- return 'a=ice-ufrag:' + frag;
- },
- parse_icepwd: function (line) {
- return line.substring(10);
- },
- build_icepwd: function (pwd) {
- return 'a=ice-pwd:' + pwd;
- },
- parse_mid: function (line) {
- return line.substring(6);
- },
- parse_mline: function (line) {
- var parts = line.substring(2).split(' '),
- data = {};
- data.media = parts.shift();
- data.port = parts.shift();
- data.proto = parts.shift();
- if (parts[parts.length - 1] === '') { // trailing whitespace
- parts.pop();
- }
- data.fmt = parts;
- return data;
- },
- build_mline: function (mline) {
- return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
- },
- parse_rtpmap: function (line) {
- var parts = line.substring(9).split(' '),
- data = {};
- data.id = parts.shift();
- parts = parts[0].split('/');
- data.name = parts.shift();
- data.clockrate = parts.shift();
- data.channels = parts.length ? parts.shift() : '1';
- return data;
- },
- /**
- * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
- * @param line eg. "a=sctpmap:5000 webrtc-datachannel"
- * @returns [SCTP port number, protocol, streams]
- */
- parse_sctpmap: function (line)
- {
- var parts = line.substring(10).split(' ');
- var sctpPort = parts[0];
- var protocol = parts[1];
- // Stream count is optional
- var streamCount = parts.length > 2 ? parts[2] : null;
- return [sctpPort, protocol, streamCount];// SCTP port
- },
- build_rtpmap: function (el) {
- var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
- if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
- line += '/' + el.getAttribute('channels');
- }
- return line;
- },
- parse_crypto: function (line) {
- var parts = line.substring(9).split(' '),
- data = {};
- data.tag = parts.shift();
- data['crypto-suite'] = parts.shift();
- data['key-params'] = parts.shift();
- if (parts.length) {
- data['session-params'] = parts.join(' ');
- }
- return data;
- },
- parse_fingerprint: function (line) { // RFC 4572
- var parts = line.substring(14).split(' '),
- data = {};
- data.hash = parts.shift();
- data.fingerprint = parts.shift();
- // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
- return data;
- },
- parse_fmtp: function (line) {
- var parts = line.split(' '),
- i, key, value,
- data = [];
- parts.shift();
- parts = parts.join(' ').split(';');
- for (i = 0; i < parts.length; i++) {
- key = parts[i].split('=')[0];
- while (key.length && key[0] == ' ') {
- key = key.substring(1);
- }
- value = parts[i].split('=')[1];
- if (key && value) {
- data.push({name: key, value: value});
- } else if (key) {
- // rfc 4733 (DTMF) style stuff
- data.push({name: '', value: key});
- }
- }
- return data;
- },
- parse_icecandidate: function (line) {
- var candidate = {},
- elems = line.split(' ');
- candidate.foundation = elems[0].substring(12);
- candidate.component = elems[1];
- candidate.protocol = elems[2].toLowerCase();
- candidate.priority = elems[3];
- candidate.ip = elems[4];
- candidate.port = elems[5];
- // elems[6] => "typ"
- candidate.type = elems[7];
- candidate.generation = 0; // default value, may be overwritten below
- for (var i = 8; i < elems.length; i += 2) {
- switch (elems[i]) {
- case 'raddr':
- candidate['rel-addr'] = elems[i + 1];
- break;
- case 'rport':
- candidate['rel-port'] = elems[i + 1];
- break;
- case 'generation':
- candidate.generation = elems[i + 1];
- break;
- case 'tcptype':
- candidate.tcptype = elems[i + 1];
- break;
- default: // TODO
- console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
- }
- }
- candidate.network = '1';
- candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
- return candidate;
- },
- build_icecandidate: function (cand) {
- var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
- line += ' ';
- switch (cand.type) {
- case 'srflx':
- case 'prflx':
- case 'relay':
- if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
- line += 'raddr';
- line += ' ';
- line += cand['rel-addr'];
- line += ' ';
- line += 'rport';
- line += ' ';
- line += cand['rel-port'];
- line += ' ';
- }
- break;
- }
- if (cand.hasOwnAttribute('tcptype')) {
- line += 'tcptype';
- line += ' ';
- line += cand.tcptype;
- line += ' ';
- }
- line += 'generation';
- line += ' ';
- line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
- return line;
- },
- parse_ssrc: function (desc) {
- // proprietary mapping of a=ssrc lines
- // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
- // and parse according to that
- var lines = desc.split('\r\n'),
- data = {};
- for (var i = 0; i < lines.length; i++) {
- if (lines[i].substring(0, 7) == 'a=ssrc:') {
- var idx = lines[i].indexOf(' ');
- data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
- }
- }
- return data;
- },
- parse_rtcpfb: function (line) {
- var parts = line.substr(10).split(' ');
- var data = {};
- data.pt = parts.shift();
- data.type = parts.shift();
- data.params = parts;
- return data;
- },
- parse_extmap: function (line) {
- var parts = line.substr(9).split(' ');
- var data = {};
- data.value = parts.shift();
- if (data.value.indexOf('/') != -1) {
- data.direction = data.value.substr(data.value.indexOf('/') + 1);
- data.value = data.value.substr(0, data.value.indexOf('/'));
- } else {
- data.direction = 'both';
- }
- data.uri = parts.shift();
- data.params = parts;
- return data;
- },
- find_line: function (haystack, needle, sessionpart) {
- var lines = haystack.split('\r\n');
- for (var i = 0; i < lines.length; i++) {
- if (lines[i].substring(0, needle.length) == needle) {
- return lines[i];
- }
- }
- if (!sessionpart) {
- return false;
- }
- // search session part
- lines = sessionpart.split('\r\n');
- for (var j = 0; j < lines.length; j++) {
- if (lines[j].substring(0, needle.length) == needle) {
- return lines[j];
- }
- }
- return false;
- },
- find_lines: function (haystack, needle, sessionpart) {
- var lines = haystack.split('\r\n'),
- needles = [];
- for (var i = 0; i < lines.length; i++) {
- if (lines[i].substring(0, needle.length) == needle)
- needles.push(lines[i]);
- }
- if (needles.length || !sessionpart) {
- return needles;
- }
- // search session part
- lines = sessionpart.split('\r\n');
- for (var j = 0; j < lines.length; j++) {
- if (lines[j].substring(0, needle.length) == needle) {
- needles.push(lines[j]);
- }
- }
- return needles;
- },
- candidateToJingle: function (line) {
- // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
- //
- if (line.indexOf('candidate:') === 0) {
- line = 'a=' + line;
- } else if (line.substring(0, 12) != 'a=candidate:') {
- console.log('parseCandidate called with a line that is not a candidate line');
- console.log(line);
- return null;
- }
- if (line.substring(line.length - 2) == '\r\n') // chomp it
- line = line.substring(0, line.length - 2);
- var candidate = {},
- elems = line.split(' '),
- i;
- if (elems[6] != 'typ') {
- console.log('did not find typ in the right place');
- console.log(line);
- return null;
- }
- candidate.foundation = elems[0].substring(12);
- candidate.component = elems[1];
- candidate.protocol = elems[2].toLowerCase();
- candidate.priority = elems[3];
- candidate.ip = elems[4];
- candidate.port = elems[5];
- // elems[6] => "typ"
- candidate.type = elems[7];
-
- candidate.generation = '0'; // default, may be overwritten below
- for (i = 8; i < elems.length; i += 2) {
- switch (elems[i]) {
- case 'raddr':
- candidate['rel-addr'] = elems[i + 1];
- break;
- case 'rport':
- candidate['rel-port'] = elems[i + 1];
- break;
- case 'generation':
- candidate.generation = elems[i + 1];
- break;
- case 'tcptype':
- candidate.tcptype = elems[i + 1];
- break;
- default: // TODO
- console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
- }
- }
- candidate.network = '1';
- candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
- return candidate;
- },
- candidateFromJingle: function (cand) {
- var line = 'a=candidate:';
- line += cand.getAttribute('foundation');
- line += ' ';
- line += cand.getAttribute('component');
- line += ' ';
- line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
- line += ' ';
- line += cand.getAttribute('priority');
- line += ' ';
- line += cand.getAttribute('ip');
- line += ' ';
- line += cand.getAttribute('port');
- line += ' ';
- line += 'typ';
- line += ' ' + cand.getAttribute('type');
- line += ' ';
- switch (cand.getAttribute('type')) {
- case 'srflx':
- case 'prflx':
- case 'relay':
- if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
- line += 'raddr';
- line += ' ';
- line += cand.getAttribute('rel-addr');
- line += ' ';
- line += 'rport';
- line += ' ';
- line += cand.getAttribute('rel-port');
- line += ' ';
- }
- break;
- }
- if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
- line += 'tcptype';
- line += ' ';
- line += cand.getAttribute('tcptype');
- line += ' ';
- }
- line += 'generation';
- line += ' ';
- line += cand.getAttribute('generation') || '0';
- return line + '\r\n';
- }
-};
-module.exports = SDPUtil;
-},{}],59:[function(require,module,exports){
-var RTC = require('../RTC/RTC');
-var RTCBrowserType = require("../RTC/RTCBrowserType.js");
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-
-function TraceablePeerConnection(ice_config, constraints, session) {
- var self = this;
- var RTCPeerconnectionType = null;
- if (RTCBrowserType.isFirefox()) {
- RTCPeerconnectionType = mozRTCPeerConnection;
- } else if (RTCBrowserType.isTemasysPluginUsed()) {
- RTCPeerconnectionType = RTCPeerConnection;
- } else {
- RTCPeerconnectionType = webkitRTCPeerConnection;
- }
- this.peerconnection = new RTCPeerconnectionType(ice_config, constraints);
- this.updateLog = [];
- this.stats = {};
- this.statsinterval = null;
- this.maxstats = 0; // limit to 300 values, i.e. 5 minutes; set to 0 to disable
- var Interop = require('sdp-interop').Interop;
- this.interop = new Interop();
- var Simulcast = require('sdp-simulcast');
- this.simulcast = new Simulcast({numOfLayers: 3, explodeRemoteSimulcast: false});
-
- // override as desired
- this.trace = function (what, info) {
- /*console.warn('WTRACE', what, info);
- if (info && RTCBrowserType.isIExplorer()) {
- if (info.length > 1024) {
- console.warn('WTRACE', what, info.substr(1024));
- }
- if (info.length > 2048) {
- console.warn('WTRACE', what, info.substr(2048));
- }
- }*/
- self.updateLog.push({
- time: new Date(),
- type: what,
- value: info || ""
- });
- };
- this.onicecandidate = null;
- this.peerconnection.onicecandidate = function (event) {
- // FIXME: this causes stack overflow with Temasys Plugin
- if (!RTCBrowserType.isTemasysPluginUsed())
- self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
- if (self.onicecandidate !== null) {
- self.onicecandidate(event);
- }
- };
- this.onaddstream = null;
- this.peerconnection.onaddstream = function (event) {
- self.trace('onaddstream', event.stream.id);
- if (self.onaddstream !== null) {
- self.onaddstream(event);
- }
- };
- this.onremovestream = null;
- this.peerconnection.onremovestream = function (event) {
- self.trace('onremovestream', event.stream.id);
- if (self.onremovestream !== null) {
- self.onremovestream(event);
- }
- };
- this.onsignalingstatechange = null;
- this.peerconnection.onsignalingstatechange = function (event) {
- self.trace('onsignalingstatechange', self.signalingState);
- if (self.onsignalingstatechange !== null) {
- self.onsignalingstatechange(event);
- }
- };
- this.oniceconnectionstatechange = null;
- this.peerconnection.oniceconnectionstatechange = function (event) {
- self.trace('oniceconnectionstatechange', self.iceConnectionState);
- if (self.oniceconnectionstatechange !== null) {
- self.oniceconnectionstatechange(event);
- }
- };
- this.onnegotiationneeded = null;
- this.peerconnection.onnegotiationneeded = function (event) {
- self.trace('onnegotiationneeded');
- if (self.onnegotiationneeded !== null) {
- self.onnegotiationneeded(event);
- }
- };
- self.ondatachannel = null;
- this.peerconnection.ondatachannel = function (event) {
- self.trace('ondatachannel', event);
- if (self.ondatachannel !== null) {
- self.ondatachannel(event);
- }
- };
- // XXX: do all non-firefox browsers which we support also support this?
- if (!RTCBrowserType.isFirefox() && this.maxstats) {
- this.statsinterval = window.setInterval(function() {
- self.peerconnection.getStats(function(stats) {
- var results = stats.result();
- for (var i = 0; i < results.length; ++i) {
- //console.log(results[i].type, results[i].id, results[i].names())
- var now = new Date();
- results[i].names().forEach(function (name) {
- var id = results[i].id + '-' + name;
- if (!self.stats[id]) {
- self.stats[id] = {
- startTime: now,
- endTime: now,
- values: [],
- times: []
- };
- }
- self.stats[id].values.push(results[i].stat(name));
- self.stats[id].times.push(now.getTime());
- if (self.stats[id].values.length > self.maxstats) {
- self.stats[id].values.shift();
- self.stats[id].times.shift();
- }
- self.stats[id].endTime = now;
- });
- }
- });
-
- }, 1000);
- }
-};
-
-dumpSDP = function(description) {
- if (typeof description === 'undefined' || description == null) {
- return '';
- }
-
- return 'type: ' + description.type + '\r\n' + description.sdp;
-};
-
-/**
- * Takes a SessionDescription object and returns a "normalized" version.
- * Currently it only takes care of ordering the a=ssrc lines.
- */
-normalizePlanB = function(desc) {
- if (typeof desc !== 'object' || desc === null ||
- typeof desc.sdp !== 'string') {
- console.warn('An empty description was passed as an argument.');
- return desc;
- }
-
- var transform = require('sdp-transform');
- var session = transform.parse(desc.sdp);
-
- if (typeof session !== 'undefined' && typeof session.media !== 'undefined' &&
- Array.isArray(session.media)) {
- session.media.forEach(function (mLine) {
-
- // Chrome appears to be picky about the order in which a=ssrc lines
- // are listed in an m-line when rtx is enabled (and thus there are
- // a=ssrc-group lines with FID semantics). Specifically if we have
- // "a=ssrc-group:FID S1 S2" and the "a=ssrc:S2" lines appear before
- // the "a=ssrc:S1" lines, SRD fails.
- // So, put SSRC which appear as the first SSRC in an FID ssrc-group
- // first.
- var firstSsrcs = [];
- var newSsrcLines = [];
-
- if (typeof mLine.ssrcGroups !== 'undefined' && Array.isArray(mLine.ssrcGroups)) {
- mLine.ssrcGroups.forEach(function (group) {
- if (typeof group.semantics !== 'undefined' &&
- group.semantics === 'FID') {
- if (typeof group.ssrcs !== 'undefined') {
- firstSsrcs.push(Number(group.ssrcs.split(' ')[0]));
- }
- }
- });
- }
-
- if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {
- for (var i = 0; i 0) {
- // start gathering stats
- }
- */
-};
-
-TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
- this.trace('setRemoteDescription::preTransform', dumpSDP(description));
- // TODO the focus should squeze or explode the remote simulcast
- description = this.simulcast.mungeRemoteDescription(description);
- this.trace('setRemoteDescription::postTransform (simulcast)', dumpSDP(description));
-
- // if we're running on FF, transform to Plan A first.
- if (RTCBrowserType.usesUnifiedPlan()) {
- description = this.interop.toUnifiedPlan(description);
- this.trace('setRemoteDescription::postTransform (Plan A)', dumpSDP(description));
- }
-
- if (RTCBrowserType.usesPlanB()) {
- description = normalizePlanB(description);
- }
-
- var self = this;
- this.peerconnection.setRemoteDescription(description,
- function () {
- self.trace('setRemoteDescriptionOnSuccess');
- successCallback();
- },
- function (err) {
- self.trace('setRemoteDescriptionOnFailure', err);
- failureCallback(err);
- }
- );
- /*
- if (this.statsinterval === null && this.maxstats > 0) {
- // start gathering stats
- }
- */
-};
-
-TraceablePeerConnection.prototype.close = function () {
- this.trace('stop');
- if (this.statsinterval !== null) {
- window.clearInterval(this.statsinterval);
- this.statsinterval = null;
- }
- this.peerconnection.close();
-};
-
-TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
- var self = this;
- this.trace('createOffer', JSON.stringify(constraints, null, ' '));
- this.peerconnection.createOffer(
- function (offer) {
- self.trace('createOfferOnSuccess::preTransform', dumpSDP(offer));
- // if we're running on FF, transform to Plan B first.
- // NOTE this is not tested because in meet the focus generates the
- // offer.
- if (RTCBrowserType.usesUnifiedPlan()) {
- offer = self.interop.toPlanB(offer);
- self.trace('createOfferOnSuccess::postTransform (Plan B)', dumpSDP(offer));
- }
-
- if (config.enableSimulcast && self.simulcast.isSupported()) {
- offer = self.simulcast.mungeLocalDescription(offer);
- self.trace('createOfferOnSuccess::postTransform (simulcast)', dumpSDP(offer));
- }
- successCallback(offer);
- },
- function(err) {
- self.trace('createOfferOnFailure', err);
- failureCallback(err);
- },
- constraints
- );
-};
-
-TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
- var self = this;
- this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
- this.peerconnection.createAnswer(
- function (answer) {
- self.trace('createAnswerOnSuccess::preTransfom', dumpSDP(answer));
- // if we're running on FF, transform to Plan A first.
- if (RTCBrowserType.usesUnifiedPlan()) {
- answer = self.interop.toPlanB(answer);
- self.trace('createAnswerOnSuccess::postTransfom (Plan B)', dumpSDP(answer));
- }
- if (config.enableSimulcast && self.simulcast.isSupported()) {
- answer = self.simulcast.mungeLocalDescription(answer);
- self.trace('createAnswerOnSuccess::postTransfom (simulcast)', dumpSDP(answer));
- }
- successCallback(answer);
- },
- function(err) {
- self.trace('createAnswerOnFailure', err);
- failureCallback(err);
- },
- constraints
- );
-};
-
-TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
- var self = this;
- this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
- this.peerconnection.addIceCandidate(candidate);
- /* maybe later
- this.peerconnection.addIceCandidate(candidate,
- function () {
- self.trace('addIceCandidateOnSuccess');
- successCallback();
- },
- function (err) {
- self.trace('addIceCandidateOnFailure', err);
- failureCallback(err);
- }
- );
- */
-};
-
-TraceablePeerConnection.prototype.getStats = function(callback, errback) {
- // TODO: Is this the correct way to handle Opera, Temasys?
- if (RTCBrowserType.isFirefox()) {
- // ignore for now...
- if(!errback)
- errback = function () {};
- this.peerconnection.getStats(null,callback,errback);
- } else {
- this.peerconnection.getStats(callback);
- }
-};
-
-module.exports = TraceablePeerConnection;
-
-
-},{"../../service/xmpp/XMPPEvents":110,"../RTC/RTC":9,"../RTC/RTCBrowserType.js":10,"sdp-interop":91,"sdp-simulcast":94,"sdp-transform":97}],60:[function(require,module,exports){
-/* global $, $iq, APP, config, connection, messageHandler,
- roomName, sessionTerminated, Strophe, Util */
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-var Settings = require("../settings/Settings");
-
-var AuthenticationEvents
- = require("../../service/authentication/AuthenticationEvents");
-
-/**
- * Contains logic responsible for enabling/disabling functionality available
- * only to moderator users.
- */
-var connection = null;
-var focusUserJid;
-
-function createExpBackoffTimer(step) {
- var count = 1;
- return function (reset) {
- // Reset call
- if (reset) {
- count = 1;
- return;
- }
- // Calculate next timeout
- var timeout = Math.pow(2, count - 1);
- count += 1;
- return timeout * step;
- };
-}
-
-var getNextTimeout = createExpBackoffTimer(1000);
-var getNextErrorTimeout = createExpBackoffTimer(1000);
-// External authentication stuff
-var externalAuthEnabled = false;
-// Sip gateway can be enabled by configuring Jigasi host in config.js or
-// it will be enabled automatically if focus detects the component through
-// service discovery.
-var sipGatewayEnabled = config.hosts.call_control !== undefined;
-
-var eventEmitter = null;
-
-var Moderator = {
- isModerator: function () {
- return connection && connection.emuc.isModerator();
- },
-
- isPeerModerator: function (peerJid) {
- return connection &&
- connection.emuc.getMemberRole(peerJid) === 'moderator';
- },
-
- isExternalAuthEnabled: function () {
- return externalAuthEnabled;
- },
-
- isSipGatewayEnabled: function () {
- return sipGatewayEnabled;
- },
-
- setConnection: function (con) {
- connection = con;
- },
-
- init: function (xmpp, emitter) {
- this.xmppService = xmpp;
- eventEmitter = emitter;
-
- // Message listener that talks to POPUP window
- function listener(event) {
- if (event.data && event.data.sessionId) {
- if (event.origin !== window.location.origin) {
- console.warn(
- "Ignoring sessionId from different origin: " + event.origin);
- return;
- }
- localStorage.setItem('sessionId', event.data.sessionId);
- // After popup is closed we will authenticate
- }
- }
- // Register
- if (window.addEventListener) {
- window.addEventListener("message", listener, false);
- } else {
- window.attachEvent("onmessage", listener);
- }
- },
-
- onMucMemberLeft: function (jid) {
- console.info("Someone left is it focus ? " + jid);
- var resource = Strophe.getResourceFromJid(jid);
- if (resource === 'focus' && !this.xmppService.sessionTerminated) {
- console.info(
- "Focus has left the room - leaving conference");
- //hangUp();
- // We'd rather reload to have everything re-initialized
- // FIXME: show some message before reload
- location.reload();
- }
- },
-
- setFocusUserJid: function (focusJid) {
- if (!focusUserJid) {
- focusUserJid = focusJid;
- console.info("Focus jid set to: " + focusUserJid);
- }
- },
-
- getFocusUserJid: function () {
- return focusUserJid;
- },
-
- getFocusComponent: function () {
- // Get focus component address
- var focusComponent = config.hosts.focus;
- // If not specified use default: 'focus.domain'
- if (!focusComponent) {
- focusComponent = 'focus.' + config.hosts.domain;
- }
- return focusComponent;
- },
-
- createConferenceIq: function (roomName) {
- // Generate create conference IQ
- var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'});
-
- // Session Id used for authentication
- var sessionId = localStorage.getItem('sessionId');
- var machineUID = Settings.getSettings().uid;
-
- console.info(
- "Session ID: " + sessionId + " machine UID: " + machineUID);
-
- elem.c('conference', {
- xmlns: 'http://jitsi.org/protocol/focus',
- room: roomName,
- 'machine-uid': machineUID
- });
-
- if (sessionId) {
- elem.attrs({ 'session-id': sessionId});
- }
-
- if (config.hosts.bridge !== undefined) {
- elem.c(
- 'property',
- { name: 'bridge', value: config.hosts.bridge})
- .up();
- }
- // Tell the focus we have Jigasi configured
- if (config.hosts.call_control !== undefined) {
- elem.c(
- 'property',
- { name: 'call_control', value: config.hosts.call_control})
- .up();
- }
- if (config.channelLastN !== undefined) {
- elem.c(
- 'property',
- { name: 'channelLastN', value: config.channelLastN})
- .up();
- }
- if (config.adaptiveLastN !== undefined) {
- elem.c(
- 'property',
- { name: 'adaptiveLastN', value: config.adaptiveLastN})
- .up();
- }
- if (config.adaptiveSimulcast !== undefined) {
- elem.c(
- 'property',
- { name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
- .up();
- }
- if (config.openSctp !== undefined) {
- elem.c(
- 'property',
- { name: 'openSctp', value: config.openSctp})
- .up();
- }
- if(config.startAudioMuted !== undefined)
- {
- elem.c(
- 'property',
- { name: 'startAudioMuted', value: config.startAudioMuted})
- .up();
- }
- if(config.startVideoMuted !== undefined)
- {
- elem.c(
- 'property',
- { name: 'startVideoMuted', value: config.startVideoMuted})
- .up();
- }
- elem.c(
- 'property',
- { name: 'simulcastMode', value: 'rewriting'})
- .up();
- elem.up();
- return elem;
- },
-
- parseSessionId: function (resultIq) {
- var sessionId = $(resultIq).find('conference').attr('session-id');
- if (sessionId) {
- console.info('Received sessionId: ' + sessionId);
- localStorage.setItem('sessionId', sessionId);
- }
- },
-
- parseConfigOptions: function (resultIq) {
-
- Moderator.setFocusUserJid(
- $(resultIq).find('conference').attr('focusjid'));
-
- var authenticationEnabled
- = $(resultIq).find(
- '>conference>property' +
- '[name=\'authentication\'][value=\'true\']').length > 0;
-
- console.info("Authentication enabled: " + authenticationEnabled);
-
- externalAuthEnabled
- = $(resultIq).find(
- '>conference>property' +
- '[name=\'externalAuth\'][value=\'true\']').length > 0;
-
- console.info('External authentication enabled: ' + externalAuthEnabled);
-
- if (!externalAuthEnabled) {
- // We expect to receive sessionId in 'internal' authentication mode
- Moderator.parseSessionId(resultIq);
- }
-
- var authIdentity = $(resultIq).find('>conference').attr('identity');
-
- eventEmitter.emit(AuthenticationEvents.IDENTITY_UPDATED,
- authenticationEnabled, authIdentity);
-
- // Check if focus has auto-detected Jigasi component(this will be also
- // included if we have passed our host from the config)
- if ($(resultIq).find(
- '>conference>property' +
- '[name=\'sipGatewayEnabled\'][value=\'true\']').length) {
- sipGatewayEnabled = true;
- }
-
- console.info("Sip gateway enabled: " + sipGatewayEnabled);
- },
-
- // FIXME: we need to show the fact that we're waiting for the focus
- // to the user(or that focus is not available)
- allocateConferenceFocus: function (roomName, callback) {
- // Try to use focus user JID from the config
- Moderator.setFocusUserJid(config.focusUserJid);
- // Send create conference IQ
- var iq = Moderator.createConferenceIq(roomName);
- var self = this;
- connection.sendIQ(
- iq,
- function (result) {
-
- // Setup config options
- Moderator.parseConfigOptions(result);
-
- if ('true' === $(result).find('conference').attr('ready')) {
- // Reset both timers
- getNextTimeout(true);
- getNextErrorTimeout(true);
- // Exec callback
- callback();
- } else {
- var waitMs = getNextTimeout();
- console.info("Waiting for the focus... " + waitMs);
- // Reset error timeout
- getNextErrorTimeout(true);
- window.setTimeout(
- function () {
- Moderator.allocateConferenceFocus(
- roomName, callback);
- }, waitMs);
- }
- },
- function (error) {
- // Invalid session ? remove and try again
- // without session ID to get a new one
- var invalidSession
- = $(error).find('>error>session-invalid').length;
- if (invalidSession) {
- console.info("Session expired! - removing");
- localStorage.removeItem("sessionId");
- }
- if ($(error).find('>error>graceful-shutdown').length) {
- eventEmitter.emit(XMPPEvents.GRACEFUL_SHUTDOWN);
- return;
- }
- // Check for error returned by the reservation system
- var reservationErr = $(error).find('>error>reservation-error');
- if (reservationErr.length) {
- // Trigger error event
- var errorCode = reservationErr.attr('error-code');
- var errorMsg;
- if ($(error).find('>error>text')) {
- errorMsg = $(error).find('>error>text').text();
- }
- eventEmitter.emit(
- XMPPEvents.RESERVATION_ERROR, errorCode, errorMsg);
- return;
- }
- // Not authorized to create new room
- if ($(error).find('>error>not-authorized').length) {
- console.warn("Unauthorized to start the conference", error);
- var toDomain
- = Strophe.getDomainFromJid(error.getAttribute('to'));
- if (toDomain !== config.hosts.anonymousdomain) {
- // FIXME: "is external" should come either from
- // the focus or config.js
- externalAuthEnabled = true;
- }
- eventEmitter.emit(
- XMPPEvents.AUTHENTICATION_REQUIRED,
- function () {
- Moderator.allocateConferenceFocus(
- roomName, callback);
- });
- return;
- }
- var waitMs = getNextErrorTimeout();
- console.error("Focus error, retry after " + waitMs, error);
- // Show message
- var focusComponent = Moderator.getFocusComponent();
- var retrySec = waitMs / 1000;
- // FIXME: message is duplicated ?
- // Do not show in case of session invalid
- // which means just a retry
- if (!invalidSession) {
- eventEmitter.emit(XMPPEvents.FOCUS_DISCONNECTED, focusComponent, retrySec);
- }
- // Reset response timeout
- getNextTimeout(true);
- window.setTimeout(
- function () {
- Moderator.allocateConferenceFocus(roomName, callback);
- }, waitMs);
- }
- );
- },
-
- getLoginUrl: function (roomName, urlCallback) {
- var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
- iq.c('login-url', {
- xmlns: 'http://jitsi.org/protocol/focus',
- room: roomName,
- 'machine-uid': Settings.getSettings().uid
- });
- connection.sendIQ(
- iq,
- function (result) {
- var url = $(result).find('login-url').attr('url');
- url = url = decodeURIComponent(url);
- if (url) {
- console.info("Got auth url: " + url);
- urlCallback(url);
- } else {
- console.error(
- "Failed to get auth url from the focus", result);
- }
- },
- function (error) {
- console.error("Get auth url error", error);
- }
- );
- },
- getPopupLoginUrl: function (roomName, urlCallback) {
- var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
- iq.c('login-url', {
- xmlns: 'http://jitsi.org/protocol/focus',
- room: roomName,
- 'machine-uid': Settings.getSettings().uid,
- popup: true
- });
- connection.sendIQ(
- iq,
- function (result) {
- var url = $(result).find('login-url').attr('url');
- url = url = decodeURIComponent(url);
- if (url) {
- console.info("Got POPUP auth url: " + url);
- urlCallback(url);
- } else {
- console.error(
- "Failed to get POPUP auth url from the focus", result);
- }
- },
- function (error) {
- console.error('Get POPUP auth url error', error);
- }
- );
- },
- logout: function (callback) {
- var iq = $iq({to: Moderator.getFocusComponent(), type: 'set'});
- var sessionId = localStorage.getItem('sessionId');
- if (!sessionId) {
- callback();
- return;
- }
- iq.c('logout', {
- xmlns: 'http://jitsi.org/protocol/focus',
- 'session-id': sessionId
- });
- connection.sendIQ(
- iq,
- function (result) {
- var logoutUrl = $(result).find('logout').attr('logout-url');
- if (logoutUrl) {
- logoutUrl = decodeURIComponent(logoutUrl);
- }
- console.info("Log out OK, url: " + logoutUrl, result);
- localStorage.removeItem('sessionId');
- callback(logoutUrl);
- },
- function (error) {
- console.error("Logout error", error);
- }
- );
- }
-};
-
-module.exports = Moderator;
-
-
-
-
-},{"../../service/authentication/AuthenticationEvents":105,"../../service/xmpp/XMPPEvents":110,"../settings/Settings":49}],61:[function(require,module,exports){
-/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator,
- Toolbar, Util */
-var Moderator = require("./moderator");
-
-
-var recordingToken = null;
-var recordingEnabled;
-
-/**
- * Whether to use a jirecon component for recording, or use the videobridge
- * through COLIBRI.
- */
-var useJirecon = (typeof config.hosts.jirecon != "undefined");
-
-/**
- * The ID of the jirecon recording session. Jirecon generates it when we
- * initially start recording, and it needs to be used in subsequent requests
- * to jirecon.
- */
-var jireconRid = null;
-
-function setRecordingToken(token) {
- recordingToken = token;
-}
-
-function setRecording(state, token, callback, connection) {
- if (useJirecon){
- setRecordingJirecon(state, token, callback, connection);
- } else {
- setRecordingColibri(state, token, callback, connection);
- }
-}
-
-function setRecordingJirecon(state, token, callback, connection) {
- if (state == recordingEnabled){
- return;
- }
-
- var iq = $iq({to: config.hosts.jirecon, type: 'set'})
- .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
- action: state ? 'start' : 'stop',
- mucjid: connection.emuc.roomjid});
- if (!state){
- iq.attrs({rid: jireconRid});
- }
-
- console.log('Start recording');
-
- connection.sendIQ(
- iq,
- function (result) {
- // TODO wait for an IQ with the real status, since this is
- // provisional?
- jireconRid = $(result).find('recording').attr('rid');
- console.log('Recording ' + (state ? 'started' : 'stopped') +
- '(jirecon)' + result);
- recordingEnabled = state;
- if (!state){
- jireconRid = null;
- }
-
- callback(state);
- },
- function (error) {
- console.log('Failed to start recording, error: ', error);
- callback(recordingEnabled);
- });
-}
-
-// Sends a COLIBRI message which enables or disables (according to 'state')
-// the recording on the bridge. Waits for the result IQ and calls 'callback'
-// with the new recording state, according to the IQ.
-function setRecordingColibri(state, token, callback, connection) {
- var elem = $iq({to: connection.emuc.focusMucJid, type: 'set'});
- elem.c('conference', {
- xmlns: 'http://jitsi.org/protocol/colibri'
- });
- elem.c('recording', {state: state, token: token});
-
- connection.sendIQ(elem,
- function (result) {
- console.log('Set recording "', state, '". Result:', result);
- var recordingElem = $(result).find('>conference>recording');
- var newState = ('true' === recordingElem.attr('state'));
-
- recordingEnabled = newState;
- callback(newState);
- },
- function (error) {
- console.warn(error);
- callback(recordingEnabled);
- }
- );
-}
-
-var Recording = {
- toggleRecording: function (tokenEmptyCallback,
- startingCallback, startedCallback, connection) {
- if (!Moderator.isModerator()) {
- console.log(
- 'non-focus, or conference not yet organized:' +
- ' not enabling recording');
- return;
- }
-
- var self = this;
- // Jirecon does not (currently) support a token.
- if (!recordingToken && !useJirecon) {
- tokenEmptyCallback(function (value) {
- setRecordingToken(value);
- self.toggleRecording(tokenEmptyCallback,
- startingCallback, startedCallback, connection);
- });
-
- return;
- }
-
- var oldState = recordingEnabled;
- startingCallback(!oldState);
- setRecording(!oldState,
- recordingToken,
- function (state) {
- console.log("New recording state: ", state);
- if (state === oldState) {
- // FIXME: new focus:
- // this will not work when moderator changes
- // during active session. Then it will assume that
- // recording status has changed to true, but it might have
- // been already true(and we only received actual status from
- // the focus).
- //
- // SO we start with status null, so that it is initialized
- // here and will fail only after second click, so if invalid
- // token was used we have to press the button twice before
- // current status will be fetched and token will be reset.
- //
- // Reliable way would be to return authentication error.
- // Or status update when moderator connects.
- // Or we have to stop recording session when current
- // moderator leaves the room.
-
- // Failed to change, reset the token because it might
- // have been wrong
- setRecordingToken(null);
- }
- startedCallback(state);
-
- },
- connection
- );
- }
-
-}
-
-module.exports = Recording;
-},{"./moderator":60}],62:[function(require,module,exports){
-/* jshint -W117 */
-/* a simple MUC connection plugin
- * can only handle a single MUC room
- */
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-var Moderator = require("./moderator");
-var JingleSession = require("./JingleSession");
-
-var bridgeIsDown = false;
-
-module.exports = function(XMPP, eventEmitter) {
- Strophe.addConnectionPlugin('emuc', {
- connection: null,
- roomjid: null,
- myroomjid: null,
- members: {},
- list_members: [], // so we can elect a new focus
- presMap: {},
- preziMap: {},
- joined: false,
- isOwner: false,
- role: null,
- focusMucJid: null,
- init: function (conn) {
- this.connection = conn;
- },
- initPresenceMap: function (myroomjid) {
- this.presMap['to'] = myroomjid;
- this.presMap['xns'] = 'http://jabber.org/protocol/muc';
- if (APP.RTC.localAudio && APP.RTC.localAudio.isMuted())
- {
- this.addAudioInfoToPresence(true);
- }
- if (APP.RTC.localVideo && APP.RTC.localVideo.isMuted())
- {
- this.addVideoInfoToPresence(true);
- }
- },
- doJoin: function (jid, password) {
- this.myroomjid = jid;
-
- console.info("Joined MUC as " + this.myroomjid);
-
- this.initPresenceMap(this.myroomjid);
-
- if (!this.roomjid) {
- this.roomjid = Strophe.getBareJidFromJid(jid);
- // add handlers (just once)
- this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true});
- this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true});
- this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true});
- this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true});
- }
- if (password !== undefined) {
- this.presMap['password'] = password;
- }
- this.sendPresence();
- },
- doLeave: function () {
- console.log("do leave", this.myroomjid);
- var pres = $pres({to: this.myroomjid, type: 'unavailable' });
- this.presMap.length = 0;
- this.connection.send(pres);
- },
- createNonAnonymousRoom: function () {
- // http://xmpp.org/extensions/xep-0045.html#createroom-reserved
-
- var getForm = $iq({type: 'get', to: this.roomjid})
- .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
- .c('x', {xmlns: 'jabber:x:data', type: 'submit'});
-
- var self = this;
-
- this.connection.sendIQ(getForm, function (form) {
-
- if (!$(form).find(
- '>query>x[xmlns="jabber:x:data"]' +
- '>field[var="muc#roomconfig_whois"]').length) {
-
- console.error('non-anonymous rooms not supported');
- return;
- }
-
- var formSubmit = $iq({to: this.roomjid, type: 'set'})
- .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
-
- formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
-
- formSubmit.c('field', {'var': 'FORM_TYPE'})
- .c('value')
- .t('http://jabber.org/protocol/muc#roomconfig').up().up();
-
- formSubmit.c('field', {'var': 'muc#roomconfig_whois'})
- .c('value').t('anyone').up().up();
-
- self.connection.sendIQ(formSubmit);
-
- }, function (error) {
- console.error("Error getting room configuration form");
- });
- },
- onPresence: function (pres) {
- var from = pres.getAttribute('from');
-
- // What is this for? A workaround for something?
- if (pres.getAttribute('type')) {
- return true;
- }
-
- // Parse etherpad tag.
- var etherpad = $(pres).find('>etherpad');
- if (etherpad.length) {
- if (config.etherpad_base) {
- eventEmitter.emit(XMPPEvents.ETHERPAD, etherpad.text());
- }
- }
-
- // Parse prezi tag.
- var presentation = $(pres).find('>prezi');
- if (presentation.length) {
- var url = presentation.attr('url');
- var current = presentation.find('>current').text();
-
- console.log('presentation info received from', from, url);
-
- if (this.preziMap[from] == null) {
- this.preziMap[from] = url;
-
- $(document).trigger('presentationadded.muc', [from, url, current]);
- }
- else {
- $(document).trigger('gotoslide.muc', [from, url, current]);
- }
- }
- else if (this.preziMap[from] != null) {
- var url = this.preziMap[from];
- delete this.preziMap[from];
- $(document).trigger('presentationremoved.muc', [from, url]);
- }
-
- // Parse audio info tag.
- var audioMuted = $(pres).find('>audiomuted');
- if (audioMuted.length) {
- eventEmitter.emit(XMPPEvents.AUDIO_MUTED,
- from, (audioMuted.text() === "true"));
- }
-
- // Parse video info tag.
- var videoMuted = $(pres).find('>videomuted');
- if (videoMuted.length) {
- eventEmitter.emit(XMPPEvents.VIDEO_MUTED,
- from, (videoMuted.text() === "true"));
- }
-
- var startMuted = $(pres).find('>startmuted');
- if (startMuted.length)
- {
- eventEmitter.emit(XMPPEvents.START_MUTED_SETTING_CHANGED,
- startMuted.attr("audio") === "true", startMuted.attr("video") === "true");
- }
-
- var devices = $(pres).find('>devices');
- if(devices.length)
- {
- var audio = devices.find('>audio');
- var video = devices.find('>video');
- var devicesValues = {audio: false, video: false};
- if(audio.length && audio.text() === "true")
- {
- devicesValues.audio = true;
- }
-
- if(video.length && video.text() === "true")
- {
- devicesValues.video = true;
- }
- eventEmitter.emit(XMPPEvents.DEVICE_AVAILABLE,
- Strophe.getResourceFromJid(from), devicesValues);
- }
-
- var stats = $(pres).find('>stats');
- if (stats.length) {
- var statsObj = {};
- Strophe.forEachChild(stats[0], "stat", function (el) {
- statsObj[el.getAttribute("name")] = el.getAttribute("value");
- });
- eventEmitter.emit(XMPPEvents.REMOTE_STATS, from, statsObj);
- }
-
- // Parse status.
- if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
- this.isOwner = true;
- this.createNonAnonymousRoom();
- }
-
- // Parse roles.
- var member = {};
- member.show = $(pres).find('>show').text();
- member.status = $(pres).find('>status').text();
- var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
- member.affiliation = tmp.attr('affiliation');
- member.role = tmp.attr('role');
-
- // Focus recognition
- member.jid = tmp.attr('jid');
- member.isFocus = false;
- if (member.jid
- && member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) {
- member.isFocus = true;
- }
-
- var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
- member.displayName = (nicktag.length > 0 ? nicktag.html() : null);
-
- if (from == this.myroomjid) {
- if (member.affiliation == 'owner') this.isOwner = true;
- if (this.role !== member.role) {
- this.role = member.role;
-
- eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED,
- from, member, pres, Moderator.isModerator());
- }
- if (!this.joined) {
- this.joined = true;
- eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member);
- this.list_members.push(from);
- }
- } else if (this.members[from] === undefined) {
- // new participant
- this.members[from] = member;
- this.list_members.push(from);
- console.log('entered', from, member);
- if (member.isFocus) {
- this.focusMucJid = from;
- console.info("Ignore focus: " + from + ", real JID: " + member.jid);
- }
- else {
- var id = $(pres).find('>userId').text();
- var email = $(pres).find('>email');
- if (email.length > 0) {
- id = email.text();
- }
- eventEmitter.emit(XMPPEvents.MUC_MEMBER_JOINED, from, id, member.displayName);
- }
- } else {
- // Presence update for existing participant
- // Watch role change:
- if (this.members[from].role != member.role) {
- this.members[from].role = member.role;
- eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED,
- member.role, member.displayName);
- }
- }
-
- // Always trigger presence to update bindings
- this.parsePresence(from, member, pres);
-
- // Trigger status message update
- if (member.status) {
- eventEmitter.emit(XMPPEvents.PRESENCE_STATUS, from, member);
- }
-
- return true;
- },
- onPresenceUnavailable: function (pres) {
- var from = pres.getAttribute('from');
- // room destroyed ?
- if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]' +
- '>destroy').length) {
- var reason;
- var reasonSelect = $(pres).find(
- '>x[xmlns="http://jabber.org/protocol/muc#user"]' +
- '>destroy>reason');
- if (reasonSelect.length) {
- reason = reasonSelect.text();
- }
- XMPP.disposeConference(false);
- eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason);
- return true;
- }
-
- // Status code 110 indicates that this notification is "self-presence".
- if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
- delete this.members[from];
- this.list_members.splice(this.list_members.indexOf(from), 1);
- this.onParticipantLeft(from);
- }
- // If the status code is 110 this means we're leaving and we would like
- // to remove everyone else from our view, so we trigger the event.
- else if (this.list_members.length > 1) {
- for (var i = 0; i < this.list_members.length; i++) {
- var member = this.list_members[i];
- delete this.members[i];
- this.list_members.splice(i, 1);
- this.onParticipantLeft(member);
- }
- }
- if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
- $(document).trigger('kicked.muc', [from]);
- if (this.myroomjid === from) {
- XMPP.disposeConference(false);
- eventEmitter.emit(XMPPEvents.KICKED);
- }
- }
- return true;
- },
- onPresenceError: function (pres) {
- var from = pres.getAttribute('from');
- if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
- console.log('on password required', from);
- var self = this;
- eventEmitter.emit(XMPPEvents.PASSWORD_REQUIRED, function (value) {
- self.doJoin(from, value);
- });
- } else if ($(pres).find(
- '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
- var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
- if (toDomain === config.hosts.anonymousdomain) {
- // enter the room by replying with 'not-authorized'. This would
- // result in reconnection from authorized domain.
- // We're either missing Jicofo/Prosody config for anonymous
- // domains or something is wrong.
-// XMPP.promptLogin();
- eventEmitter.emit(XMPPEvents.ROOM_JOIN_ERROR, pres);
-
- } else {
- console.warn('onPresError ', pres);
- eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
- }
- } else {
- console.warn('onPresError ', pres);
- eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
- }
- return true;
- },
- sendMessage: function (body, nickname) {
- var msg = $msg({to: this.roomjid, type: 'groupchat'});
- msg.c('body', body).up();
- if (nickname) {
- msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
- }
- this.connection.send(msg);
- eventEmitter.emit(XMPPEvents.SENDING_CHAT_MESSAGE, body);
- },
- setSubject: function (subject) {
- var msg = $msg({to: this.roomjid, type: 'groupchat'});
- msg.c('subject', subject);
- this.connection.send(msg);
- console.log("topic changed to " + subject);
- },
- onMessage: function (msg) {
- // FIXME: this is a hack. but jingle on muc makes nickchanges hard
- var from = msg.getAttribute('from');
- var nick =
- $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]')
- .text() ||
- Strophe.getResourceFromJid(from);
-
- var txt = $(msg).find('>body').text();
- var type = msg.getAttribute("type");
- if (type == "error") {
- eventEmitter.emit(XMPPEvents.CHAT_ERROR_RECEIVED,
- $(msg).find('>text').text(), txt);
- return true;
- }
-
- var subject = $(msg).find('>subject');
- if (subject.length) {
- var subjectText = subject.text();
- if (subjectText || subjectText == "") {
- eventEmitter.emit(XMPPEvents.SUBJECT_CHANGED, subjectText);
- console.log("Subject is changed to " + subjectText);
- }
- }
-
- // xep-0203 delay
- var stamp = $(msg).find('>delay').attr('stamp');
-
- if (!stamp) {
- // or xep-0091 delay, UTC timestamp
- stamp = $(msg).find('>[xmlns="jabber:x:delay"]').attr('stamp');
-
- if (stamp) {
- // the format is CCYYMMDDThh:mm:ss
- var dateParts = stamp.match(/(\d{4})(\d{2})(\d{2}T\d{2}:\d{2}:\d{2})/);
- stamp = dateParts[1] + "-" + dateParts[2] + "-" + dateParts[3] + "Z";
- }
- }
-
- if (txt) {
- console.log('chat', nick, txt);
- eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED,
- from, nick, txt, this.myroomjid, stamp);
- }
- return true;
- },
- lockRoom: function (key, onSuccess, onError, onNotSupported) {
- //http://xmpp.org/extensions/xep-0045.html#roomconfig
- var ob = this;
- this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
- function (res) {
- if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
- var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
- formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
- formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
- formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
- // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
- formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
- // FIXME: is muc#roomconfig_passwordprotectedroom required?
- ob.connection.sendIQ(formsubmit,
- onSuccess,
- onError);
- } else {
- onNotSupported();
- }
- }, onError);
- },
- kick: function (jid) {
- var kickIQ = $iq({to: this.roomjid, type: 'set'})
- .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
- .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
- .c('reason').t('You have been kicked.').up().up().up();
-
- this.connection.sendIQ(
- kickIQ,
- function (result) {
- console.log('Kick participant with jid: ', jid, result);
- },
- function (error) {
- console.log('Kick participant error: ', error);
- });
- },
- sendPresence: function () {
- if (!this.presMap['to']) {
- // Too early to send presence - not initialized
- return;
- }
- var pres = $pres({to: this.presMap['to'] });
- pres.c('x', {xmlns: this.presMap['xns']});
-
- if (this.presMap['password']) {
- pres.c('password').t(this.presMap['password']).up();
- }
-
- pres.up();
-
- // Send XEP-0115 'c' stanza that contains our capabilities info
- if (this.connection.caps) {
- this.connection.caps.node = config.clientNode;
- pres.c('c', this.connection.caps.generateCapsAttrs()).up();
- }
-
- pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'})
- .t(navigator.userAgent).up();
-
- if (this.presMap['bridgeIsDown']) {
- pres.c('bridgeIsDown').up();
- }
-
- if (this.presMap['email']) {
- pres.c('email').t(this.presMap['email']).up();
- }
-
- if (this.presMap['userId']) {
- pres.c('userId').t(this.presMap['userId']).up();
- }
-
- if (this.presMap['displayName']) {
- // XEP-0172
- pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'})
- .t(this.presMap['displayName']).up();
- }
-
- if(this.presMap["devices"])
- {
- pres.c('devices').c('audio').t(this.presMap['devices'].audio).up()
- .c('video').t(this.presMap['devices'].video).up().up();
- }
- if (this.presMap['audions']) {
- pres.c('audiomuted', {xmlns: this.presMap['audions']})
- .t(this.presMap['audiomuted']).up();
- }
-
- if (this.presMap['videons']) {
- pres.c('videomuted', {xmlns: this.presMap['videons']})
- .t(this.presMap['videomuted']).up();
- }
-
- if (this.presMap['statsns']) {
- var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
- for (var stat in this.presMap["stats"])
- if (this.presMap["stats"][stat] != null)
- stats.c("stat", {name: stat, value: this.presMap["stats"][stat]}).up();
- pres.up();
- }
-
- if (this.presMap['prezins']) {
- pres.c('prezi',
- {xmlns: this.presMap['prezins'],
- 'url': this.presMap['preziurl']})
- .c('current').t(this.presMap['prezicurrent']).up().up();
- }
-
- if(this.presMap["startMuted"] !== undefined)
- {
- pres.c("startmuted", {audio: this.presMap["startMuted"].audio,
- video: this.presMap["startMuted"].video,
- xmlns: "http://jitsi.org/jitmeet/start-muted"});
- delete this.presMap["startMuted"];
- }
-
- pres.up();
- this.connection.send(pres);
- },
- addDisplayNameToPresence: function (displayName) {
- this.presMap['displayName'] = displayName;
- },
- addDevicesToPresence: function (devices) {
- this.presMap['devices'] = devices;
- },
- addPreziToPresence: function (url, currentSlide) {
- this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
- this.presMap['preziurl'] = url;
- this.presMap['prezicurrent'] = currentSlide;
- },
- removePreziFromPresence: function () {
- delete this.presMap['prezins'];
- delete this.presMap['preziurl'];
- delete this.presMap['prezicurrent'];
- },
- addCurrentSlideToPresence: function (currentSlide) {
- this.presMap['prezicurrent'] = currentSlide;
- },
- getPrezi: function (roomjid) {
- return this.preziMap[roomjid];
- },
- addAudioInfoToPresence: function (isMuted) {
- this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
- this.presMap['audiomuted'] = isMuted.toString();
- },
- addVideoInfoToPresence: function (isMuted) {
- this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
- this.presMap['videomuted'] = isMuted.toString();
- },
- addConnectionInfoToPresence: function (stats) {
- this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
- this.presMap['stats'] = stats;
- },
- findJidFromResource: function (resourceJid) {
- if (resourceJid &&
- resourceJid === Strophe.getResourceFromJid(this.myroomjid)) {
- return this.myroomjid;
- }
- var peerJid = null;
- Object.keys(this.members).some(function (jid) {
- peerJid = jid;
- return Strophe.getResourceFromJid(jid) === resourceJid;
- });
- return peerJid;
- },
- addBridgeIsDownToPresence: function () {
- this.presMap['bridgeIsDown'] = true;
- },
- addEmailToPresence: function (email) {
- this.presMap['email'] = email;
- },
- addUserIdToPresence: function (userId) {
- this.presMap['userId'] = userId;
- },
- addStartMutedToPresence: function (audio, video) {
- this.presMap["startMuted"] = {audio: audio, video: video};
- },
- isModerator: function () {
- return this.role === 'moderator';
- },
- getMemberRole: function (peerJid) {
- if (this.members[peerJid]) {
- return this.members[peerJid].role;
- }
- return null;
- },
- onParticipantLeft: function (jid) {
-
- eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid);
-
- this.connection.jingle.terminateByJid(jid);
-
- if (this.getPrezi(jid)) {
- $(document).trigger('presentationremoved.muc',
- [jid, this.getPrezi(jid)]);
- }
-
- Moderator.onMucMemberLeft(jid);
- },
- parsePresence: function (from, memeber, pres) {
- if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
- bridgeIsDown = true;
- eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
- }
-
- if(memeber.isFocus)
- return;
-
- var displayName = !config.displayJids
- ? memeber.displayName : Strophe.getResourceFromJid(from);
-
- if (displayName && displayName.length > 0)
- {
- eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
- }
-
-
- var id = $(pres).find('>userID').text();
- var email = $(pres).find('>email');
- if(email.length > 0) {
- id = email.text();
- }
-
- eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, id);
- }
- });
-};
-
-
-},{"../../service/xmpp/XMPPEvents":110,"./JingleSession":55,"./moderator":60}],63:[function(require,module,exports){
-/* jshint -W117 */
-
-var JingleSession = require("./JingleSession");
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-var RTCBrowserType = require("../RTC/RTCBrowserType");
-
-
-module.exports = function(XMPP, eventEmitter)
-{
- function CallIncomingJingle(sid, connection) {
- var sess = connection.jingle.sessions[sid];
-
- // TODO: do we check activecall == null?
- connection.jingle.activecall = sess;
-
- eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
-
- // TODO: check affiliation and/or role
- console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
- sess.usedrip = true; // not-so-naive trickle ice
- sess.sendAnswer();
- sess.accept();
-
- };
-
- Strophe.addConnectionPlugin('jingle', {
- connection: null,
- sessions: {},
- jid2session: {},
- ice_config: {iceServers: []},
- pc_constraints: {},
- activecall: null,
- media_constraints: {
- mandatory: {
- 'OfferToReceiveAudio': true,
- 'OfferToReceiveVideo': true
- }
- // MozDontOfferDataChannel: true when this is firefox
- },
- init: function (conn) {
- this.connection = conn;
- if (this.connection.disco) {
- // http://xmpp.org/extensions/xep-0167.html#support
- // http://xmpp.org/extensions/xep-0176.html#support
- this.connection.disco.addFeature('urn:xmpp:jingle:1');
- this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
- this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
- this.connection.disco.addFeature('urn:xmpp:jingle:apps:dtls:0');
- this.connection.disco.addFeature('urn:xmpp:jingle:transports:dtls-sctp:1');
- this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
- this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
-
- if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()
- || RTCBrowserType.isTemasysPluginUsed()) {
- this.connection.disco.addFeature('urn:ietf:rfc:4588');
- }
-
- // this is dealt with by SDP O/A so we don't need to annouce this
- //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
- //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
- if (config.useRtcpMux) {
- this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
- }
- if (config.useBundle) {
- this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
- }
- //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
- }
- this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
- },
- onJingle: function (iq) {
- var sid = $(iq).find('jingle').attr('sid');
- var action = $(iq).find('jingle').attr('action');
- var fromJid = iq.getAttribute('from');
- // send ack first
- var ack = $iq({type: 'result',
- to: fromJid,
- id: iq.getAttribute('id')
- });
- console.log('on jingle ' + action + ' from ' + fromJid, iq);
- var sess = this.sessions[sid];
- if ('session-initiate' != action) {
- if (sess === null) {
- ack.type = 'error';
- ack.c('error', {type: 'cancel'})
- .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
- .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
- this.connection.send(ack);
- return true;
- }
- // compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
- // local jid is not checked
- if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
- console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
- ack.type = 'error';
- ack.c('error', {type: 'cancel'})
- .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
- .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
- this.connection.send(ack);
- return true;
- }
- } else if (sess !== undefined) {
- // existing session with same session id
- // this might be out-of-order if the sess.peerjid is the same as from
- ack.type = 'error';
- ack.c('error', {type: 'cancel'})
- .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
- console.warn('duplicate session id', sid);
- this.connection.send(ack);
- return true;
- }
- // FIXME: check for a defined action
- this.connection.send(ack);
- // see http://xmpp.org/extensions/xep-0166.html#concepts-session
- switch (action) {
- case 'session-initiate':
- var startMuted = $(iq).find('jingle>startmuted');
- if(startMuted && startMuted.length > 0)
- {
- var audioMuted = startMuted.attr("audio");
- var videoMuted = startMuted.attr("video");
- eventEmitter.emit(XMPPEvents.START_MUTED_FROM_FOCUS,
- autioMuted === "true", videoMuted === "true");
- }
- sess = new JingleSession(
- $(iq).attr('to'), $(iq).find('jingle').attr('sid'),
- this.connection, XMPP, eventEmitter);
- // configure session
-
- sess.media_constraints = this.media_constraints;
- sess.pc_constraints = this.pc_constraints;
- sess.ice_config = this.ice_config;
-
- sess.initiate(fromJid, false);
- // FIXME: setRemoteDescription should only be done when this call is to be accepted
- sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
-
- this.sessions[sess.sid] = sess;
- this.jid2session[sess.peerjid] = sess;
-
- // the callback should either
- // .sendAnswer and .accept
- // or .sendTerminate -- not necessarily synchronus
- CallIncomingJingle(sess.sid, this.connection);
- break;
- case 'session-accept':
- sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
- sess.accept();
- $(document).trigger('callaccepted.jingle', [sess.sid]);
- break;
- case 'session-terminate':
- // If this is not the focus sending the terminate, we have
- // nothing more to do here.
- if (Object.keys(this.sessions).length < 1
- || !(this.sessions[Object.keys(this.sessions)[0]]
- instanceof JingleSession))
- {
- break;
- }
- console.log('terminating...', sess.sid);
- sess.terminate();
- this.terminate(sess.sid);
- if ($(iq).find('>jingle>reason').length) {
- $(document).trigger('callterminated.jingle', [
- sess.sid,
- sess.peerjid,
- $(iq).find('>jingle>reason>:first')[0].tagName,
- $(iq).find('>jingle>reason>text').text()
- ]);
- } else {
- $(document).trigger('callterminated.jingle',
- [sess.sid, sess.peerjid]);
- }
- break;
- case 'transport-info':
- sess.addIceCandidate($(iq).find('>jingle>content'));
- break;
- case 'session-info':
- var affected;
- if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
- $(document).trigger('ringing.jingle', [sess.sid]);
- } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
- affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
- $(document).trigger('mute.jingle', [sess.sid, affected]);
- } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
- affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
- $(document).trigger('unmute.jingle', [sess.sid, affected]);
- }
- break;
- case 'addsource': // FIXME: proprietary, un-jingleish
- case 'source-add': // FIXME: proprietary
- sess.addSource($(iq).find('>jingle>content'), fromJid);
- break;
- case 'removesource': // FIXME: proprietary, un-jingleish
- case 'source-remove': // FIXME: proprietary
- sess.removeSource($(iq).find('>jingle>content'), fromJid);
- break;
- default:
- console.warn('jingle action not implemented', action);
- break;
- }
- return true;
- },
- initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
- var sess = new JingleSession(myjid || this.connection.jid,
- Math.random().toString(36).substr(2, 12), // random string
- this.connection, XMPP, eventEmitter);
- // configure session
-
- sess.media_constraints = this.media_constraints;
- sess.pc_constraints = this.pc_constraints;
- sess.ice_config = this.ice_config;
-
- sess.initiate(peerjid, true);
- this.sessions[sess.sid] = sess;
- this.jid2session[sess.peerjid] = sess;
- sess.sendOffer();
- return sess;
- },
- terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
- if (sid === null || sid === undefined) {
- for (sid in this.sessions) {
- if (this.sessions[sid].state != 'ended') {
- this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
- this.sessions[sid].terminate();
- }
- delete this.jid2session[this.sessions[sid].peerjid];
- delete this.sessions[sid];
- }
- } else if (this.sessions.hasOwnProperty(sid)) {
- if (this.sessions[sid].state != 'ended') {
- this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
- this.sessions[sid].terminate();
- }
- delete this.jid2session[this.sessions[sid].peerjid];
- delete this.sessions[sid];
- }
- },
- // Used to terminate a session when an unavailable presence is received.
- terminateByJid: function (jid) {
- if (this.jid2session.hasOwnProperty(jid)) {
- var sess = this.jid2session[jid];
- if (sess) {
- sess.terminate();
- console.log('peer went away silently', jid);
- delete this.sessions[sess.sid];
- delete this.jid2session[jid];
- $(document).trigger('callterminated.jingle',
- [sess.sid, jid], 'gone');
- }
- }
- },
- terminateRemoteByJid: function (jid, reason) {
- if (this.jid2session.hasOwnProperty(jid)) {
- var sess = this.jid2session[jid];
- if (sess) {
- sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
- sess.terminate();
- console.log('terminate peer with jid', sess.sid, jid);
- delete this.sessions[sess.sid];
- delete this.jid2session[jid];
- $(document).trigger('callterminated.jingle',
- [sess.sid, jid, 'kicked']);
- }
- }
- },
- getStunAndTurnCredentials: function () {
- // get stun and turn configuration from server via xep-0215
- // uses time-limited credentials as described in
- // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
- //
- // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
- // for a prosody module which implements this
- //
- // currently, this doesn't work with updateIce and therefore credentials with a long
- // validity have to be fetched before creating the peerconnection
- // TODO: implement refresh via updateIce as described in
- // https://code.google.com/p/webrtc/issues/detail?id=1650
- var self = this;
- this.connection.sendIQ(
- $iq({type: 'get', to: this.connection.domain})
- .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
- function (res) {
- var iceservers = [];
- $(res).find('>services>service').each(function (idx, el) {
- el = $(el);
- var dict = {};
- var type = el.attr('type');
- switch (type) {
- case 'stun':
- dict.url = 'stun:' + el.attr('host');
- if (el.attr('port')) {
- dict.url += ':' + el.attr('port');
- }
- iceservers.push(dict);
- break;
- case 'turn':
- case 'turns':
- dict.url = type + ':';
- if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
- if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
- dict.url += el.attr('username') + '@';
- } else {
- dict.username = el.attr('username'); // only works in M28
- }
- }
- dict.url += el.attr('host');
- if (el.attr('port') && el.attr('port') != '3478') {
- dict.url += ':' + el.attr('port');
- }
- if (el.attr('transport') && el.attr('transport') != 'udp') {
- dict.url += '?transport=' + el.attr('transport');
- }
- if (el.attr('password')) {
- dict.credential = el.attr('password');
- }
- iceservers.push(dict);
- break;
- }
- });
- self.ice_config.iceServers = iceservers;
- },
- function (err) {
- console.warn('getting turn credentials failed', err);
- console.warn('is mod_turncredentials or similar installed?');
- }
- );
- // implement push?
- },
-
- /**
- * Populates the log data
- */
- populateData: function () {
- var data = {};
- var self = this;
- Object.keys(this.sessions).forEach(function (sid) {
- var session = self.sessions[sid];
- if (session.peerconnection && session.peerconnection.updateLog) {
- // FIXME: should probably be a .dump call
- data["jingle_" + session.sid] = {
- updateLog: session.peerconnection.updateLog,
- stats: session.peerconnection.stats,
- url: window.location.href
- };
- }
- });
- return data;
- }
- });
-};
-
-
-},{"../../service/xmpp/XMPPEvents":110,"../RTC/RTCBrowserType":10,"./JingleSession":55}],64:[function(require,module,exports){
-/* global Strophe */
-module.exports = function () {
-
- Strophe.addConnectionPlugin('logger', {
- // logs raw stanzas and makes them available for download as JSON
- connection: null,
- log: [],
- init: function (conn) {
- this.connection = conn;
- this.connection.rawInput = this.log_incoming.bind(this);
- this.connection.rawOutput = this.log_outgoing.bind(this);
- },
- log_incoming: function (stanza) {
- this.log.push([new Date().getTime(), 'incoming', stanza]);
- },
- log_outgoing: function (stanza) {
- this.log.push([new Date().getTime(), 'outgoing', stanza]);
- }
- });
-};
-},{}],65:[function(require,module,exports){
-/* global $, $iq, config, connection, focusMucJid, forceMuted,
- setAudioMuted, Strophe */
-/**
- * Moderate connection plugin.
- */
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-
-module.exports = function (XMPP, eventEmitter) {
- Strophe.addConnectionPlugin('moderate', {
- connection: null,
- init: function (conn) {
- this.connection = conn;
-
- this.connection.addHandler(this.onMute.bind(this),
- 'http://jitsi.org/jitmeet/audio',
- 'iq',
- 'set',
- null,
- null);
- },
- setMute: function (jid, mute) {
- console.info("set mute", mute);
- var iqToFocus = $iq({to: this.connection.emuc.focusMucJid, type: 'set'})
- .c('mute', {
- xmlns: 'http://jitsi.org/jitmeet/audio',
- jid: jid
- })
- .t(mute.toString())
- .up();
-
- this.connection.sendIQ(
- iqToFocus,
- function (result) {
- console.log('set mute', result);
- },
- function (error) {
- console.log('set mute error', error);
- });
- },
- onMute: function (iq) {
- var from = iq.getAttribute('from');
- if (from !== this.connection.emuc.focusMucJid) {
- console.warn("Ignored mute from non focus peer");
- return false;
- }
- var mute = $(iq).find('mute');
- if (mute.length) {
- var doMuteAudio = mute.text() === "true";
- eventEmitter.emit(XMPPEvents.AUDIO_MUTED_BY_FOCUS, doMuteAudio);
- XMPP.forceMuted = doMuteAudio;
- }
- return true;
- },
- eject: function (jid) {
- // We're not the focus, so can't terminate
- //connection.jingle.terminateRemoteByJid(jid, 'kick');
- this.connection.emuc.kick(jid);
- }
- });
+function initCallback (err, msg) {
+ console.log("Initializing Status: err="+err+" msg="+msg);
}
-},{"../../service/xmpp/XMPPEvents":110}],66:[function(require,module,exports){
-/* jshint -W117 */
-module.exports = function() {
- Strophe.addConnectionPlugin('rayo',
- {
- RAYO_XMLNS: 'urn:xmpp:rayo:1',
- connection: null,
- init: function (conn) {
- this.connection = conn;
- if (this.connection.disco) {
- this.connection.disco.addFeature('urn:xmpp:rayo:client:1');
- }
-
- this.connection.addHandler(
- this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null);
- },
- onRayo: function (iq) {
- console.info("Rayo IQ", iq);
- },
- dial: function (to, from, roomName, roomPass) {
- var self = this;
- var req = $iq(
- {
- type: 'set',
- to: this.connection.emuc.focusMucJid
- }
- );
- req.c('dial',
- {
- xmlns: this.RAYO_XMLNS,
- to: to,
- from: from
- });
- req.c('header',
- {
- name: 'JvbRoomName',
- value: roomName
- }).up();
-
- if (roomPass !== null && roomPass.length) {
-
- req.c('header',
- {
- name: 'JvbRoomPassword',
- value: roomPass
- }).up();
- }
-
- this.connection.sendIQ(
- req,
- function (result) {
- console.info('Dial result ', result);
-
- var resource = $(result).find('ref').attr('uri');
- this.call_resource = resource.substr('xmpp:'.length);
- console.info(
- "Received call resource: " + this.call_resource);
- },
- function (error) {
- console.info('Dial error ', error);
- }
- );
- },
- hang_up: function () {
- if (!this.call_resource) {
- console.warn("No call in progress");
- return;
- }
-
- var self = this;
- var req = $iq(
- {
- type: 'set',
- to: this.call_resource
- }
- );
- req.c('hangup',
- {
- xmlns: this.RAYO_XMLNS
- });
-
- this.connection.sendIQ(
- req,
- function (result) {
- console.info('Hangup result ', result);
- self.call_resource = null;
- },
- function (error) {
- console.info('Hangup error ', error);
- self.call_resource = null;
- }
- );
- }
- }
- );
-};
-},{}],67:[function(require,module,exports){
-/**
- * Strophe logger implementation. Logs from level WARN and above.
- */
-module.exports = function () {
-
- Strophe.log = function (level, msg) {
- switch (level) {
- case Strophe.LogLevel.WARN:
- console.warn("Strophe: " + msg);
- break;
- case Strophe.LogLevel.ERROR:
- case Strophe.LogLevel.FATAL:
- console.error("Strophe: " + msg);
- break;
- }
- };
-
- Strophe.getStatusString = function (status) {
- switch (status) {
- case Strophe.Status.ERROR:
- return "ERROR";
- case Strophe.Status.CONNECTING:
- return "CONNECTING";
- case Strophe.Status.CONNFAIL:
- return "CONNFAIL";
- case Strophe.Status.AUTHENTICATING:
- return "AUTHENTICATING";
- case Strophe.Status.AUTHFAIL:
- return "AUTHFAIL";
- case Strophe.Status.CONNECTED:
- return "CONNECTED";
- case Strophe.Status.DISCONNECTED:
- return "DISCONNECTED";
- case Strophe.Status.DISCONNECTING:
- return "DISCONNECTING";
- case Strophe.Status.ATTACHED:
- return "ATTACHED";
- default:
- return "unknown";
- }
- };
-};
+var CallStats = {
+ init: function (jingleSession) {
-},{}],68:[function(require,module,exports){
-/* global $, APP, config, Strophe*/
-var Moderator = require("./moderator");
-var EventEmitter = require("events");
-var Recording = require("./recording");
-var SDP = require("./SDP");
-var Settings = require("../settings/Settings");
-var Pako = require("pako");
-var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
-var RTCEvents = require("../../service/RTC/RTCEvents");
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-var retry = require('retry');
-
-var eventEmitter = new EventEmitter();
-var connection = null;
-var authenticatedUser = false;
-
-function connect(jid, password) {
-
- var faultTolerantConnect = retry.operation({
- retries: 3
- });
-
- // fault tolerant connect
- faultTolerantConnect.attempt(function () {
-
- connection = XMPP.createConnection();
- Moderator.setConnection(connection);
-
- if (connection.disco) {
- // for chrome, add multistream cap
- }
- connection.jingle.pc_constraints = APP.RTC.getPCConstraints();
- if (config.useIPv6) {
- // https://code.google.com/p/webrtc/issues/detail?id=2828
- if (!connection.jingle.pc_constraints.optional)
- connection.jingle.pc_constraints.optional = [];
- connection.jingle.pc_constraints.optional.push({googIPv6: true});
- }
-
- // Include user info in MUC presence
- var settings = Settings.getSettings();
- if (settings.email) {
- connection.emuc.addEmailToPresence(settings.email);
- }
- if (settings.uid) {
- connection.emuc.addUserIdToPresence(settings.uid);
- }
- if (settings.displayName) {
- connection.emuc.addDisplayNameToPresence(settings.displayName);
- }
-
-
- // connection.connect() starts the connection process.
- //
- // As the connection process proceeds, the user supplied callback will
- // be triggered multiple times with status updates. The callback should
- // take two arguments - the status code and the error condition.
- //
- // The status code will be one of the values in the Strophe.Status
- // constants. The error condition will be one of the conditions defined
- // in RFC 3920 or the condition ‘strophe-parsererror’.
- //
- // The Parameters wait, hold and route are optional and only relevant
- // for BOSH connections. Please see XEP 124 for a more detailed
- // explanation of the optional parameters.
- //
- // Connection status constants for use by the connection handler
- // callback.
- //
- // Status.ERROR - An error has occurred (websockets specific)
- // Status.CONNECTING - The connection is currently being made
- // Status.CONNFAIL - The connection attempt failed
- // Status.AUTHENTICATING - The connection is authenticating
- // Status.AUTHFAIL - The authentication attempt failed
- // Status.CONNECTED - The connection has succeeded
- // Status.DISCONNECTED - The connection has been terminated
- // Status.DISCONNECTING - The connection is currently being terminated
- // Status.ATTACHED - The connection has been attached
-
- var anonymousConnectionFailed = false;
- var connectionFailed = false;
- var lastErrorMsg;
- connection.connect(jid, password, function (status, msg) {
- console.log('Strophe status changed to',
- Strophe.getStatusString(status), msg);
- if (status === Strophe.Status.CONNECTED) {
- if (config.useStunTurn) {
- connection.jingle.getStunAndTurnCredentials();
- }
-
- console.info("My Jabber ID: " + connection.jid);
-
- if (password)
- authenticatedUser = true;
- maybeDoJoin();
- } else if (status === Strophe.Status.CONNFAIL) {
- if (msg === 'x-strophe-bad-non-anon-jid') {
- anonymousConnectionFailed = true;
- } else {
- connectionFailed = true;
- }
- lastErrorMsg = msg;
- } else if (status === Strophe.Status.DISCONNECTED) {
- if (anonymousConnectionFailed) {
- // prompt user for username and password
- XMPP.promptLogin();
- } else {
-
- // Strophe already has built-in HTTP/BOSH error handling and
- // request retry logic. Requests are resent automatically
- // until their error count reaches 5. Strophe.js disconnects
- // if the error count is > 5. We are not replicating this
- // here.
- //
- // The "problem" is that failed HTTP/BOSH requests don't
- // trigger a callback with a status update, so when a
- // callback with status Strophe.Status.DISCONNECTED arrives,
- // we can't be sure if it's a graceful disconnect or if it's
- // triggered by some HTTP/BOSH error.
- //
- // But that's a minor issue in Jitsi Meet as we never
- // disconnect anyway, not even when the user closes the
- // browser window (which is kind of wrong, but the point is
- // that we should never ever get disconnected).
- //
- // On the other hand, failed connections due to XMPP layer
- // errors, trigger a callback with status Strophe.Status.CONNFAIL.
- //
- // Here we implement retry logic for failed connections due
- // to XMPP layer errors and we display an error to the user
- // if we get disconnected from the XMPP server permanently.
-
- // If the connection failed, retry.
- if (connectionFailed
- && faultTolerantConnect.retry("connection-failed")) {
- return;
- }
-
- // If we failed to connect to the XMPP server, fire an event
- // to let all the interested module now about it.
- eventEmitter.emit(XMPPEvents.CONNECTION_FAILED,
- msg ? msg : lastErrorMsg);
- }
- } else if (status === Strophe.Status.AUTHFAIL) {
- // wrong password or username, prompt user
- XMPP.promptLogin();
-
- }
- });
- });
-}
-
-
-
-function maybeDoJoin() {
- if (connection && connection.connected &&
- Strophe.getResourceFromJid(connection.jid)
- && (APP.RTC.localAudio || APP.RTC.localVideo)) {
- // .connected is true while connecting?
- doJoin();
- }
-}
-
-function doJoin() {
- eventEmitter.emit(XMPPEvents.READY_TO_JOIN);
-}
-
-function initStrophePlugins()
-{
- require("./strophe.emuc")(XMPP, eventEmitter);
- require("./strophe.jingle")(XMPP, eventEmitter);
- require("./strophe.moderate")(XMPP, eventEmitter);
- require("./strophe.util")();
- require("./strophe.rayo")();
- require("./strophe.logger")();
-}
-
-function registerListeners() {
- APP.RTC.addStreamListener(maybeDoJoin,
- StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
- APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) {
- XMPP.addToPresence("devices", devices);
- });
-}
-
-var unload = (function () {
- var unloaded = false;
-
- return function () {
- if (unloaded) { return; }
- unloaded = true;
-
- if (connection && connection.connected) {
- // ensure signout
- $.ajax({
- type: 'POST',
- url: config.bosh,
- async: false,
- cache: false,
- contentType: 'application/xml',
- data: "" +
- "" +
- "",
- success: function (data) {
- console.log('signed out');
- console.log(data);
- },
- error: function (XMLHttpRequest, textStatus, errorThrown) {
- console.log('signout error',
- textStatus + ' (' + errorThrown + ')');
- }
- });
- }
- XMPP.disposeConference(true);
- };
-})();
-
-function setupEvents() {
- // In recent versions of FF the 'beforeunload' event is not fired when the
- // window or the tab is closed. It is only fired when we leave the page
- // (change URL). If this participant doesn't unload properly, then it
- // becomes a ghost for the rest of the participants that stay in the
- // conference. Thankfully handling the 'unload' event in addition to the
- // 'beforeunload' event seems to garante the execution of the 'unload'
- // method at least once.
- //
- // The 'unload' method can safely be run multiple times, it will actually do
- // something only the first time that it's run, so we're don't have to worry
- // about browsers that fire both events.
-
- $(window).bind('beforeunload', unload);
- $(window).bind('unload', unload);
-}
-
-var XMPP = {
- getConnection: function(){ return connection; },
- sessionTerminated: false,
-
- /**
- * XMPP connection status
- */
- Status: Strophe.Status,
-
- /**
- * Remembers if we were muted by the focus.
- * @type {boolean}
- */
- forceMuted: false,
- start: function () {
- setupEvents();
- initStrophePlugins();
- registerListeners();
- Moderator.init(this, eventEmitter);
- var configDomain = config.hosts.anonymousdomain || config.hosts.domain;
- // Force authenticated domain if room is appended with '?login=true'
- if (config.hosts.anonymousdomain &&
- window.location.search.indexOf("login=true") !== -1) {
- configDomain = config.hosts.domain;
- }
- var jid = configDomain || window.location.hostname;
- connect(jid, null);
- },
- createConnection: function () {
- var bosh = config.bosh || '/http-bind';
-
- return new Strophe.Connection(bosh);
- },
- getStatusString: function (status) {
- return Strophe.getStatusString(status);
- },
- promptLogin: function () {
- eventEmitter.emit(XMPPEvents.PROMPT_FOR_LOGIN);
- },
- joinRoom: function(roomName, useNicks, nick)
- {
- var roomjid;
- roomjid = roomName;
-
- if (useNicks) {
- if (nick) {
- roomjid += '/' + nick;
- } else {
- roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
- }
- } else {
-
- var tmpJid = Strophe.getNodeFromJid(connection.jid);
-
- if(!authenticatedUser)
- tmpJid = tmpJid.substr(0, 8);
-
- roomjid += '/' + tmpJid;
- }
- connection.emuc.doJoin(roomjid);
- },
- myJid: function () {
- if(!connection)
- return null;
- return connection.emuc.myroomjid;
- },
- myResource: function () {
- if(!connection || ! connection.emuc.myroomjid)
- return null;
- return Strophe.getResourceFromJid(connection.emuc.myroomjid);
- },
- disposeConference: function (onUnload) {
- var handler = connection.jingle.activecall;
- if (handler && handler.peerconnection) {
- // FIXME: probably removing streams is not required and close() should
- // be enough
- if (APP.RTC.localAudio) {
- handler.peerconnection.removeStream(
- APP.RTC.localAudio.getOriginalStream(), onUnload);
- }
- if (APP.RTC.localVideo) {
- handler.peerconnection.removeStream(
- APP.RTC.localVideo.getOriginalStream(), onUnload);
- }
- handler.peerconnection.close();
- }
- eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload);
- connection.jingle.activecall = null;
- if(!onUnload)
- {
- this.sessionTerminated = true;
- connection.emuc.doLeave();
- }
- },
- addListener: function(type, listener)
- {
- eventEmitter.on(type, listener);
- },
- removeListener: function (type, listener) {
- eventEmitter.removeListener(type, listener);
- },
- allocateConferenceFocus: function(roomName, callback) {
- Moderator.allocateConferenceFocus(roomName, callback);
- },
- getLoginUrl: function (roomName, callback) {
- Moderator.getLoginUrl(roomName, callback);
- },
- getPopupLoginUrl: function (roomName, callback) {
- Moderator.getPopupLoginUrl(roomName, callback);
- },
- isModerator: function () {
- return Moderator.isModerator();
- },
- isSipGatewayEnabled: function () {
- return Moderator.isSipGatewayEnabled();
- },
- isExternalAuthEnabled: function () {
- return Moderator.isExternalAuthEnabled();
- },
- isConferenceInProgress: function () {
- return connection && connection.jingle.activecall &&
- connection.jingle.activecall.peerconnection;
- },
- switchStreams: function (stream, oldStream, callback, isAudio) {
- if (this.isConferenceInProgress()) {
- // FIXME: will block switchInProgress on true value in case of exception
- connection.jingle.activecall.switchStreams(stream, oldStream, callback, isAudio);
- } else {
- // We are done immediately
- console.warn("No conference handler or conference not started yet");
- callback();
- }
- },
- sendVideoInfoPresence: function (mute) {
- if(!connection)
- return;
- connection.emuc.addVideoInfoToPresence(mute);
- connection.emuc.sendPresence();
- },
- setVideoMute: function (mute, callback, options) {
- if(!connection)
- return;
- var self = this;
- var localCallback = function (mute) {
- self.sendVideoInfoPresence(mute);
- return callback(mute);
- };
-
- if(connection.jingle.activecall)
- {
- connection.jingle.activecall.setVideoMute(
- mute, localCallback, options);
- }
- else {
- localCallback(mute);
- }
-
- },
- setAudioMute: function (mute, callback) {
- if (!(connection && APP.RTC.localAudio)) {
- return false;
- }
-
-
- if (this.forceMuted && !mute) {
- console.info("Asking focus for unmute");
- connection.moderate.setMute(connection.emuc.myroomjid, mute);
- // FIXME: wait for result before resetting muted status
- this.forceMuted = false;
- }
-
- if (mute == APP.RTC.localAudio.isMuted()) {
- // Nothing to do
- return true;
- }
-
- // It is not clear what is the right way to handle multiple tracks.
- // So at least make sure that they are all muted or all unmuted and
- // that we send presence just once.
- APP.RTC.localAudio.setMute(!mute);
- // isMuted is the opposite of audioEnabled
- this.sendAudioInfoPresence(mute, callback);
- return true;
- },
- sendAudioInfoPresence: function(mute, callback)
- {
- if(connection) {
- connection.emuc.addAudioInfoToPresence(mute);
- connection.emuc.sendPresence();
- }
- callback();
- return true;
- },
- // Really mute video, i.e. dont even send black frames
- muteVideo: function (pc, unmute) {
- // FIXME: this probably needs another of those lovely state safeguards...
- // which checks for iceconn == connected and sigstate == stable
- pc.setRemoteDescription(pc.remoteDescription,
- function () {
- pc.createAnswer(
- function (answer) {
- var sdp = new SDP(answer.sdp);
- if (sdp.media.length > 1) {
- if (unmute)
- sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
- else
- sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
- sdp.raw = sdp.session + sdp.media.join('');
- answer.sdp = sdp.raw;
- }
- pc.setLocalDescription(answer,
- function () {
- console.log('mute SLD ok');
- },
- function (error) {
- console.log('mute SLD error');
- eventEmitter.emit(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR);
- }
- );
- },
- function (error) {
- console.log(error);
- eventEmitter.emit(XMPPEvents.CREATE_ANSWER_ERROR);
- }
- );
- },
- function (error) {
- console.log('muteVideo SRD error');
- eventEmitter.emit(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR);
- }
- );
- },
- toggleRecording: function (tokenEmptyCallback,
- startingCallback, startedCallback) {
- Recording.toggleRecording(tokenEmptyCallback,
- startingCallback, startedCallback, connection);
- },
- addToPresence: function (name, value, dontSend) {
- switch (name)
- {
- case "displayName":
- connection.emuc.addDisplayNameToPresence(value);
- break;
- case "prezi":
- connection.emuc.addPreziToPresence(value, 0);
- break;
- case "preziSlide":
- connection.emuc.addCurrentSlideToPresence(value);
- break;
- case "connectionQuality":
- connection.emuc.addConnectionInfoToPresence(value);
- break;
- case "email":
- connection.emuc.addEmailToPresence(value);
- break;
- case "devices":
- connection.emuc.addDevicesToPresence(value);
- break;
- case "startMuted":
- if(!Moderator.isModerator())
- return;
- connection.emuc.addStartMutedToPresence(value[0],
- value[1]);
- break;
- default :
- console.log("Unknown tag for presence: " + name);
- return;
- }
- if (!dontSend)
- connection.emuc.sendPresence();
- },
- /**
- * Sends 'data' as a log message to the focus. Returns true iff a message
- * was sent.
- * @param data
- * @returns {boolean} true iff a message was sent.
- */
- sendLogs: function (data) {
- if(!connection.emuc.focusMucJid)
- return false;
-
- var deflate = true;
-
- var content = JSON.stringify(data);
- if (deflate) {
- content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
- }
- content = Base64.encode(content);
- // XEP-0337-ish
- var message = $msg({to: connection.emuc.focusMucJid, type: 'normal'});
- message.c('log', { xmlns: 'urn:xmpp:eventlog',
- id: 'PeerConnectionStats'});
- message.c('message').t(content).up();
- if (deflate) {
- message.c('tag', {name: "deflated", value: "true"}).up();
- }
- message.up();
-
- connection.send(message);
- return true;
- },
- populateData: function () {
- var data = {};
- if (connection.jingle) {
- data = connection.jingle.populateData();
- }
- return data;
- },
- getLogger: function () {
- if(connection.logger)
- return connection.logger.log;
- return null;
- },
- getPrezi: function () {
- return connection.emuc.getPrezi(this.myJid());
- },
- removePreziFromPresence: function () {
- connection.emuc.removePreziFromPresence();
- connection.emuc.sendPresence();
- },
- sendChatMessage: function (message, nickname) {
- connection.emuc.sendMessage(message, nickname);
- },
- setSubject: function (topic) {
- connection.emuc.setSubject(topic);
- },
- lockRoom: function (key, onSuccess, onError, onNotSupported) {
- connection.emuc.lockRoom(key, onSuccess, onError, onNotSupported);
- },
- dial: function (to, from, roomName,roomPass) {
- connection.rayo.dial(to, from, roomName,roomPass);
- },
- setMute: function (jid, mute) {
- connection.moderate.setMute(jid, mute);
- },
- eject: function (jid) {
- connection.moderate.eject(jid);
- },
- logout: function (callback) {
- Moderator.logout(callback);
- },
- findJidFromResource: function (resource) {
- return connection.emuc.findJidFromResource(resource);
- },
- getMembers: function () {
- return connection.emuc.members;
- },
- getJidFromSSRC: function (ssrc) {
- if (!this.isConferenceInProgress())
- return null;
- return connection.jingle.activecall.getSsrcOwner(ssrc);
- },
- getMUCJoined: function () {
- return connection.emuc.joined;
- },
- getSessions: function () {
- return connection.jingle.sessions;
- },
- removeStream: function (stream) {
- if (!this.isConferenceInProgress())
- return;
- connection.jingle.activecall.peerconnection.removeStream(stream);
- }
-};
-
-module.exports = XMPP;
+ if(!config.callStatsID || !config.callStatsSecret || callStats !== null)
+ return;
-},{"../../service/RTC/RTCEvents":101,"../../service/RTC/StreamEventTypes":103,"../../service/xmpp/XMPPEvents":110,"../settings/Settings":49,"./SDP":56,"./moderator":60,"./recording":61,"./strophe.emuc":62,"./strophe.jingle":63,"./strophe.logger":64,"./strophe.moderate":65,"./strophe.rayo":66,"./strophe.util":67,"events":1,"pako":71,"retry":87}],69:[function(require,module,exports){
+ callStats = new callstats($,io,jsSHA);
+
+ this.session = jingleSession;
+ this.peerconnection = jingleSession.peerconnection.peerconnection;
+
+ this.userID = APP.xmpp.myResource();
+
+ var location = window.location;
+ this.confID = location.protocol + "//" +
+ location.hostname + location.pathname;
+
+ //userID is generated or given by the origin server
+ callStats.initialize(config.callStatsID,
+ config.callStatsSecret,
+ this.userID,
+ initCallback);
+
+ var usage = callStats.fabricUsage.unbundled;
+ if(config.useBundle)
+ usage = callStats.fabricUsage.multiplex;
+
+ callStats.addNewFabric(this.peerconnection,
+ Strophe.getResourceFromJid(jingleSession.peerjid),
+ usage,
+ this.confID,
+ this.pcCallback.bind(this));
+ },
+ pcCallback: function (err, msg) {
+ if(!callStats)
+ return;
+ console.log("Monitoring status: "+ err + " msg: " + msg);
+ callStats.sendFabricEvent(this.peerconnection,
+ callStats.fabricEvent.fabricSetup, this.confID);
+ },
+ sendMuteEvent: function (mute, type) {
+ if(!callStats)
+ return;
+ var event = null;
+ if(type === "video")
+ {
+ event = (mute? callStats.fabricEvent.videoPause :
+ callStats.fabricEvent.videoResume);
+ }
+ else
+ {
+ event = (mute? callStats.fabricEvent.audioMute :
+ callStats.fabricEvent.audioUnmute);
+ }
+ callStats.sendFabricEvent(this.peerconnection, event, this.confID);
+ },
+ sendTerminateEvent: function () {
+ if(!callStats)
+ return;
+ callStats.sendFabricEvent(this.peerconnection,
+ callStats.fabricEvent.fabricTerminated, this.confID);
+ },
+ sendSetupFailedEvent: function () {
+ if(!callStats)
+ return;
+ callStats.sendFabricEvent(this.peerconnection,
+ callStats.fabricEvent.fabricSetupFailed, this.confID);
+ }
+
+};
+module.exports = CallStats;
+},{}],49:[function(require,module,exports){
+/**
+ * Provides statistics for the local stream.
+ */
+
+var RTCBrowserType = require('../RTC/RTCBrowserType');
+
+/**
+ * Size of the webaudio analizer buffer.
+ * @type {number}
+ */
+var WEBAUDIO_ANALIZER_FFT_SIZE = 2048;
+
+/**
+ * Value of the webaudio analizer smoothing time parameter.
+ * @type {number}
+ */
+var WEBAUDIO_ANALIZER_SMOOTING_TIME = 0.8;
+
+/**
+ * Converts time domain data array to audio level.
+ * @param array the time domain data array.
+ * @returns {number} the audio level
+ */
+function timeDomainDataToAudioLevel(samples) {
+
+ var maxVolume = 0;
+
+ var length = samples.length;
+
+ for (var i = 0; i < length; i++) {
+ if (maxVolume < samples[i])
+ maxVolume = samples[i];
+ }
+
+ return parseFloat(((maxVolume - 127) / 128).toFixed(3));
+};
+
+/**
+ * Animates audio level change
+ * @param newLevel the new audio level
+ * @param lastLevel the last audio level
+ * @returns {Number} the audio level to be set
+ */
+function animateLevel(newLevel, lastLevel)
+{
+ var value = 0;
+ var diff = lastLevel - newLevel;
+ if(diff > 0.2)
+ {
+ value = lastLevel - 0.2;
+ }
+ else if(diff < -0.4)
+ {
+ value = lastLevel + 0.4;
+ }
+ else
+ {
+ value = newLevel;
+ }
+
+ return parseFloat(value.toFixed(3));
+}
+
+
+/**
+ * LocalStatsCollector calculates statistics for the local stream.
+ *
+ * @param stream the local stream
+ * @param interval stats refresh interval given in ms.
+ * @param {function(LocalStatsCollector)} updateCallback the callback called on stats
+ * update.
+ * @constructor
+ */
+function LocalStatsCollector(stream, interval, statisticsService, eventEmitter) {
+ window.AudioContext = window.AudioContext || window.webkitAudioContext;
+ this.stream = stream;
+ this.intervalId = null;
+ this.intervalMilis = interval;
+ this.eventEmitter = eventEmitter;
+ this.audioLevel = 0;
+ this.statisticsService = statisticsService;
+}
+
+/**
+ * Starts the collecting the statistics.
+ */
+LocalStatsCollector.prototype.start = function () {
+ if (config.disableAudioLevels || !window.AudioContext
+ || RTCBrowserType.isTemasysPluginUsed())
+ return;
+
+ var context = new AudioContext();
+ var analyser = context.createAnalyser();
+ analyser.smoothingTimeConstant = WEBAUDIO_ANALIZER_SMOOTING_TIME;
+ analyser.fftSize = WEBAUDIO_ANALIZER_FFT_SIZE;
+
+
+ var source = context.createMediaStreamSource(this.stream);
+ source.connect(analyser);
+
+
+ var self = this;
+
+ this.intervalId = setInterval(
+ function () {
+ var array = new Uint8Array(analyser.frequencyBinCount);
+ analyser.getByteTimeDomainData(array);
+ var audioLevel = timeDomainDataToAudioLevel(array);
+ if(audioLevel != self.audioLevel) {
+ self.audioLevel = animateLevel(audioLevel, self.audioLevel);
+ self.eventEmitter.emit(
+ "statistics.audioLevel",
+ self.statisticsService.LOCAL_JID,
+ self.audioLevel);
+ }
+ },
+ this.intervalMilis
+ );
+
+};
+
+/**
+ * Stops collecting the statistics.
+ */
+LocalStatsCollector.prototype.stop = function () {
+ if (this.intervalId) {
+ clearInterval(this.intervalId);
+ this.intervalId = null;
+ }
+};
+
+module.exports = LocalStatsCollector;
+},{"../RTC/RTCBrowserType":8}],50:[function(require,module,exports){
+/* global ssrc2jid */
+/* jshint -W117 */
+var RTCBrowserType = require("../RTC/RTCBrowserType");
+
+
+/**
+ * Calculates packet lost percent using the number of lost packets and the
+ * number of all packet.
+ * @param lostPackets the number of lost packets
+ * @param totalPackets the number of all packets.
+ * @returns {number} packet loss percent
+ */
+function calculatePacketLoss(lostPackets, totalPackets) {
+ if(!totalPackets || totalPackets <= 0 || !lostPackets || lostPackets <= 0)
+ return 0;
+ return Math.round((lostPackets/totalPackets)*100);
+}
+
+function getStatValue(item, name) {
+ var browserType = RTCBrowserType.getBrowserType();
+ if (!keyMap[browserType][name])
+ throw "The property isn't supported!";
+ var key = keyMap[browserType][name];
+ return (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) ?
+ item.stat(key) : item[key];
+}
+
+/**
+ * Peer statistics data holder.
+ * @constructor
+ */
+function PeerStats()
+{
+ this.ssrc2Loss = {};
+ this.ssrc2AudioLevel = {};
+ this.ssrc2bitrate = {};
+ this.ssrc2resolution = {};
+}
+
+/**
+ * The bandwidth
+ * @type {{}}
+ */
+PeerStats.bandwidth = {};
+
+/**
+ * The bit rate
+ * @type {{}}
+ */
+PeerStats.bitrate = {};
+
+
+
+/**
+ * The packet loss rate
+ * @type {{}}
+ */
+PeerStats.packetLoss = null;
+
+/**
+ * Sets packets loss rate for given ssrc that blong to the peer
+ * represented by this instance.
+ * @param ssrc audio or video RTP stream SSRC.
+ * @param lossRate new packet loss rate value to be set.
+ */
+PeerStats.prototype.setSsrcLoss = function (ssrc, lossRate)
+{
+ this.ssrc2Loss[ssrc] = lossRate;
+};
+
+/**
+ * Sets resolution for given ssrc that belong to the peer
+ * represented by this instance.
+ * @param ssrc audio or video RTP stream SSRC.
+ * @param resolution new resolution value to be set.
+ */
+PeerStats.prototype.setSsrcResolution = function (ssrc, resolution)
+{
+ if(resolution === null && this.ssrc2resolution[ssrc])
+ {
+ delete this.ssrc2resolution[ssrc];
+ }
+ else if(resolution !== null)
+ this.ssrc2resolution[ssrc] = resolution;
+};
+
+/**
+ * Sets the bit rate for given ssrc that blong to the peer
+ * represented by this instance.
+ * @param ssrc audio or video RTP stream SSRC.
+ * @param bitrate new bitrate value to be set.
+ */
+PeerStats.prototype.setSsrcBitrate = function (ssrc, bitrate)
+{
+ if(this.ssrc2bitrate[ssrc])
+ {
+ this.ssrc2bitrate[ssrc].download += bitrate.download;
+ this.ssrc2bitrate[ssrc].upload += bitrate.upload;
+ }
+ else {
+ this.ssrc2bitrate[ssrc] = bitrate;
+ }
+};
+
+/**
+ * Sets new audio level(input or output) for given ssrc that identifies
+ * the stream which belongs to the peer represented by this instance.
+ * @param ssrc RTP stream SSRC for which current audio level value will be
+ * updated.
+ * @param audioLevel the new audio level value to be set. Value is truncated to
+ * fit the range from 0 to 1.
+ */
+PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel)
+{
+ // Range limit 0 - 1
+ this.ssrc2AudioLevel[ssrc] = formatAudioLevel(audioLevel);
+};
+
+function formatAudioLevel(audioLevel) {
+ return Math.min(Math.max(audioLevel, 0), 1);
+}
+
+/**
+ * Array with the transport information.
+ * @type {Array}
+ */
+PeerStats.transport = [];
+
+
+/**
+ * StatsCollector registers for stats updates of given
+ * peerconnection in given interval. On each update particular
+ * stats are extracted and put in {@link PeerStats} objects. Once the processing
+ * is done audioLevelsUpdateCallback is called with this
+ * instance as an event source.
+ *
+ * @param peerconnection webRTC peer connection object.
+ * @param interval stats refresh interval given in ms.
+ * @param {function(StatsCollector)} audioLevelsUpdateCallback the callback
+ * called on stats update.
+ * @constructor
+ */
+function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, eventEmitter)
+{
+ this.peerconnection = peerconnection;
+ this.baselineAudioLevelsReport = null;
+ this.currentAudioLevelsReport = null;
+ this.currentStatsReport = null;
+ this.baselineStatsReport = null;
+ this.audioLevelsIntervalId = null;
+ this.eventEmitter = eventEmitter;
+
+ /**
+ * Gather PeerConnection stats once every this many milliseconds.
+ */
+ this.GATHER_INTERVAL = 15000;
+
+ /**
+ * Log stats via the focus once every this many milliseconds.
+ */
+ this.LOG_INTERVAL = 60000;
+
+ /**
+ * Gather stats and store them in this.statsToBeLogged.
+ */
+ this.gatherStatsIntervalId = null;
+
+ /**
+ * Send the stats already saved in this.statsToBeLogged to be logged via
+ * the focus.
+ */
+ this.logStatsIntervalId = null;
+
+ /**
+ * Stores the statistics which will be send to the focus to be logged.
+ */
+ this.statsToBeLogged =
+ {
+ timestamps: [],
+ stats: {}
+ };
+
+ // Updates stats interval
+ this.audioLevelsIntervalMilis = audioLevelsInterval;
+
+ this.statsIntervalId = null;
+ this.statsIntervalMilis = statsInterval;
+ // Map of jids to PeerStats
+ this.jid2stats = {};
+}
+
+module.exports = StatsCollector;
+
+/**
+ * Stops stats updates.
+ */
+StatsCollector.prototype.stop = function () {
+ if (this.audioLevelsIntervalId) {
+ clearInterval(this.audioLevelsIntervalId);
+ this.audioLevelsIntervalId = null;
+ }
+
+ if (this.statsIntervalId)
+ {
+ clearInterval(this.statsIntervalId);
+ this.statsIntervalId = null;
+ }
+
+ if(this.logStatsIntervalId)
+ {
+ clearInterval(this.logStatsIntervalId);
+ this.logStatsIntervalId = null;
+ }
+
+ if(this.gatherStatsIntervalId)
+ {
+ clearInterval(this.gatherStatsIntervalId);
+ this.gatherStatsIntervalId = null;
+ }
+};
+
+/**
+ * Callback passed to getStats method.
+ * @param error an error that occurred on getStats call.
+ */
+StatsCollector.prototype.errorCallback = function (error)
+{
+ console.error("Get stats error", error);
+ this.stop();
+};
+
+/**
+ * Starts stats updates.
+ */
+StatsCollector.prototype.start = function ()
+{
+ var self = this;
+ if(!config.disableAudioLevels) {
+ this.audioLevelsIntervalId = setInterval(
+ function () {
+ // Interval updates
+ self.peerconnection.getStats(
+ function (report) {
+ var results = null;
+ if (!report || !report.result ||
+ typeof report.result != 'function') {
+ results = report;
+ }
+ else {
+ results = report.result();
+ }
+ //console.error("Got interval report", results);
+ self.currentAudioLevelsReport = results;
+ self.processAudioLevelReport();
+ self.baselineAudioLevelsReport =
+ self.currentAudioLevelsReport;
+ },
+ self.errorCallback
+ );
+ },
+ self.audioLevelsIntervalMilis
+ );
+ }
+
+ if(!config.disableStats && !navigator.mozGetUserMedia) {
+ this.statsIntervalId = setInterval(
+ function () {
+ // Interval updates
+ self.peerconnection.getStats(
+ function (report) {
+ var results = null;
+ if (!report || !report.result ||
+ typeof report.result != 'function') {
+ //firefox
+ results = report;
+ }
+ else {
+ //chrome
+ results = report.result();
+ }
+ //console.error("Got interval report", results);
+ self.currentStatsReport = results;
+ try {
+ self.processStatsReport();
+ }
+ catch (e) {
+ console.error("Unsupported key:" + e, e);
+ }
+
+ self.baselineStatsReport = self.currentStatsReport;
+ },
+ self.errorCallback
+ );
+ },
+ self.statsIntervalMilis
+ );
+ }
+
+ if (config.logStats && !navigator.mozGetUserMedia) {
+ this.gatherStatsIntervalId = setInterval(
+ function () {
+ self.peerconnection.getStats(
+ function (report) {
+ self.addStatsToBeLogged(report.result());
+ },
+ function () {
+ }
+ );
+ },
+ this.GATHER_INTERVAL
+ );
+
+ this.logStatsIntervalId = setInterval(
+ function() { self.logStats(); },
+ this.LOG_INTERVAL);
+ }
+};
+
+/**
+ * Checks whether a certain record should be included in the logged statistics.
+ */
+function acceptStat(reportId, reportType, statName) {
+ if (reportType == "googCandidatePair" && statName == "googChannelId")
+ return false;
+
+ if (reportType == "ssrc") {
+ if (statName == "googTrackId" ||
+ statName == "transportId" ||
+ statName == "ssrc")
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Checks whether a certain record should be included in the logged statistics.
+ */
+function acceptReport(id, type) {
+ if (id.substring(0, 15) == "googCertificate" ||
+ id.substring(0, 9) == "googTrack" ||
+ id.substring(0, 20) == "googLibjingleSession")
+ return false;
+
+ if (type == "googComponent")
+ return false;
+
+ return true;
+}
+
+/**
+ * Converts the stats to the format used for logging, and saves the data in
+ * this.statsToBeLogged.
+ * @param reports Reports as given by webkitRTCPerConnection.getStats.
+ */
+StatsCollector.prototype.addStatsToBeLogged = function (reports) {
+ var self = this;
+ var num_records = this.statsToBeLogged.timestamps.length;
+ this.statsToBeLogged.timestamps.push(new Date().getTime());
+ reports.map(function (report) {
+ if (!acceptReport(report.id, report.type))
+ return;
+ var stat = self.statsToBeLogged.stats[report.id];
+ if (!stat) {
+ stat = self.statsToBeLogged.stats[report.id] = {};
+ }
+ stat.type = report.type;
+ report.names().map(function (name) {
+ if (!acceptStat(report.id, report.type, name))
+ return;
+ var values = stat[name];
+ if (!values) {
+ values = stat[name] = [];
+ }
+ while (values.length < num_records) {
+ values.push(null);
+ }
+ values.push(report.stat(name));
+ });
+ });
+};
+
+StatsCollector.prototype.logStats = function () {
+
+ if(!APP.xmpp.sendLogs(this.statsToBeLogged))
+ return;
+ // Reset the stats
+ this.statsToBeLogged.stats = {};
+ this.statsToBeLogged.timestamps = [];
+};
+var keyMap = {};
+keyMap[RTCBrowserType.RTC_BROWSER_FIREFOX] = {
+ "ssrc": "ssrc",
+ "packetsReceived": "packetsReceived",
+ "packetsLost": "packetsLost",
+ "packetsSent": "packetsSent",
+ "bytesReceived": "bytesReceived",
+ "bytesSent": "bytesSent"
+};
+keyMap[RTCBrowserType.RTC_BROWSER_CHROME] = {
+ "receiveBandwidth": "googAvailableReceiveBandwidth",
+ "sendBandwidth": "googAvailableSendBandwidth",
+ "remoteAddress": "googRemoteAddress",
+ "transportType": "googTransportType",
+ "localAddress": "googLocalAddress",
+ "activeConnection": "googActiveConnection",
+ "ssrc": "ssrc",
+ "packetsReceived": "packetsReceived",
+ "packetsSent": "packetsSent",
+ "packetsLost": "packetsLost",
+ "bytesReceived": "bytesReceived",
+ "bytesSent": "bytesSent",
+ "googFrameHeightReceived": "googFrameHeightReceived",
+ "googFrameWidthReceived": "googFrameWidthReceived",
+ "googFrameHeightSent": "googFrameHeightSent",
+ "googFrameWidthSent": "googFrameWidthSent",
+ "audioInputLevel": "audioInputLevel",
+ "audioOutputLevel": "audioOutputLevel"
+};
+keyMap[RTCBrowserType.RTC_BROWSER_OPERA] =
+ keyMap[RTCBrowserType.RTC_BROWSER_CHROME];
+
+
+/**
+ * Stats processing logic.
+ */
+StatsCollector.prototype.processStatsReport = function () {
+ if (!this.baselineStatsReport) {
+ return;
+ }
+
+ for (var idx in this.currentStatsReport) {
+ var now = this.currentStatsReport[idx];
+ try {
+ if (getStatValue(now, 'receiveBandwidth') ||
+ getStatValue(now, 'sendBandwidth')) {
+ PeerStats.bandwidth = {
+ "download": Math.round(
+ (getStatValue(now, 'receiveBandwidth')) / 1000),
+ "upload": Math.round(
+ (getStatValue(now, 'sendBandwidth')) / 1000)
+ };
+ }
+ }
+ catch(e){/*not supported*/}
+
+ if(now.type == 'googCandidatePair')
+ {
+ var ip, type, localIP, active;
+ try {
+ ip = getStatValue(now, 'remoteAddress');
+ type = getStatValue(now, "transportType");
+ localIP = getStatValue(now, "localAddress");
+ active = getStatValue(now, "activeConnection");
+ }
+ catch(e){/*not supported*/}
+ if(!ip || !type || !localIP || active != "true")
+ continue;
+ var addressSaved = false;
+ for(var i = 0; i < PeerStats.transport.length; i++)
+ {
+ if(PeerStats.transport[i].ip == ip &&
+ PeerStats.transport[i].type == type &&
+ PeerStats.transport[i].localip == localIP)
+ {
+ addressSaved = true;
+ }
+ }
+ if(addressSaved)
+ continue;
+ PeerStats.transport.push({localip: localIP, ip: ip, type: type});
+ continue;
+ }
+
+ if(now.type == "candidatepair")
+ {
+ if(now.state == "succeeded")
+ continue;
+
+ var local = this.currentStatsReport[now.localCandidateId];
+ var remote = this.currentStatsReport[now.remoteCandidateId];
+ PeerStats.transport.push({localip: local.ipAddress + ":" + local.portNumber,
+ ip: remote.ipAddress + ":" + remote.portNumber, type: local.transport});
+
+ }
+
+ if (now.type != 'ssrc' && now.type != "outboundrtp" &&
+ now.type != "inboundrtp") {
+ continue;
+ }
+
+ var before = this.baselineStatsReport[idx];
+ if (!before) {
+ console.warn(getStatValue(now, 'ssrc') + ' not enough data');
+ continue;
+ }
+
+ var ssrc = getStatValue(now, 'ssrc');
+ if(!ssrc)
+ continue;
+ var jid = APP.xmpp.getJidFromSSRC(ssrc);
+ if (!jid && (Date.now() - now.timestamp) < 3000) {
+ console.warn("No jid for ssrc: " + ssrc);
+ continue;
+ }
+
+ var jidStats = this.jid2stats[jid];
+ if (!jidStats) {
+ jidStats = new PeerStats();
+ this.jid2stats[jid] = jidStats;
+ }
+
+
+ var isDownloadStream = true;
+ var key = 'packetsReceived';
+ if (!getStatValue(now, key))
+ {
+ isDownloadStream = false;
+ key = 'packetsSent';
+ if (!getStatValue(now, key))
+ {
+ console.warn("No packetsReceived nor packetSent stat found");
+ continue;
+ }
+ }
+ var packetsNow = getStatValue(now, key);
+ if(!packetsNow || packetsNow < 0)
+ packetsNow = 0;
+
+ var packetsBefore = getStatValue(before, key);
+ if(!packetsBefore || packetsBefore < 0)
+ packetsBefore = 0;
+ var packetRate = packetsNow - packetsBefore;
+ if(!packetRate || packetRate < 0)
+ packetRate = 0;
+ var currentLoss = getStatValue(now, 'packetsLost');
+ if(!currentLoss || currentLoss < 0)
+ currentLoss = 0;
+ var previousLoss = getStatValue(before, 'packetsLost');
+ if(!previousLoss || previousLoss < 0)
+ previousLoss = 0;
+ var lossRate = currentLoss - previousLoss;
+ if(!lossRate || lossRate < 0)
+ lossRate = 0;
+ var packetsTotal = (packetRate + lossRate);
+
+ jidStats.setSsrcLoss(ssrc,
+ {"packetsTotal": packetsTotal,
+ "packetsLost": lossRate,
+ "isDownloadStream": isDownloadStream});
+
+
+ var bytesReceived = 0, bytesSent = 0;
+ if(getStatValue(now, "bytesReceived"))
+ {
+ bytesReceived = getStatValue(now, "bytesReceived") -
+ getStatValue(before, "bytesReceived");
+ }
+
+ if(getStatValue(now, "bytesSent"))
+ {
+ bytesSent = getStatValue(now, "bytesSent") -
+ getStatValue(before, "bytesSent");
+ }
+
+ var time = Math.round((now.timestamp - before.timestamp) / 1000);
+ if(bytesReceived <= 0 || time <= 0)
+ {
+ bytesReceived = 0;
+ }
+ else
+ {
+ bytesReceived = Math.round(((bytesReceived * 8) / time) / 1000);
+ }
+
+ if(bytesSent <= 0 || time <= 0)
+ {
+ bytesSent = 0;
+ }
+ else
+ {
+ bytesSent = Math.round(((bytesSent * 8) / time) / 1000);
+ }
+
+ jidStats.setSsrcBitrate(ssrc, {
+ "download": bytesReceived,
+ "upload": bytesSent});
+
+ var resolution = {height: null, width: null};
+ try {
+ if (getStatValue(now, "googFrameHeightReceived") &&
+ getStatValue(now, "googFrameWidthReceived")) {
+ resolution.height = getStatValue(now, "googFrameHeightReceived");
+ resolution.width = getStatValue(now, "googFrameWidthReceived");
+ }
+ else if (getStatValue(now, "googFrameHeightSent") &&
+ getStatValue(now, "googFrameWidthSent")) {
+ resolution.height = getStatValue(now, "googFrameHeightSent");
+ resolution.width = getStatValue(now, "googFrameWidthSent");
+ }
+ }
+ catch(e){/*not supported*/}
+
+ if(resolution.height && resolution.width)
+ {
+ jidStats.setSsrcResolution(ssrc, resolution);
+ }
+ else
+ {
+ jidStats.setSsrcResolution(ssrc, null);
+ }
+
+
+ }
+
+ var self = this;
+ // Jid stats
+ var totalPackets = {download: 0, upload: 0};
+ var lostPackets = {download: 0, upload: 0};
+ var bitrateDownload = 0;
+ var bitrateUpload = 0;
+ var resolutions = {};
+ Object.keys(this.jid2stats).forEach(
+ function (jid)
+ {
+ Object.keys(self.jid2stats[jid].ssrc2Loss).forEach(
+ function (ssrc)
+ {
+ var type = "upload";
+ if(self.jid2stats[jid].ssrc2Loss[ssrc].isDownloadStream)
+ type = "download";
+ totalPackets[type] +=
+ self.jid2stats[jid].ssrc2Loss[ssrc].packetsTotal;
+ lostPackets[type] +=
+ self.jid2stats[jid].ssrc2Loss[ssrc].packetsLost;
+ }
+ );
+ Object.keys(self.jid2stats[jid].ssrc2bitrate).forEach(
+ function (ssrc) {
+ bitrateDownload +=
+ self.jid2stats[jid].ssrc2bitrate[ssrc].download;
+ bitrateUpload +=
+ self.jid2stats[jid].ssrc2bitrate[ssrc].upload;
+
+ delete self.jid2stats[jid].ssrc2bitrate[ssrc];
+ }
+ );
+ resolutions[jid] = self.jid2stats[jid].ssrc2resolution;
+ }
+ );
+
+ PeerStats.bitrate = {"upload": bitrateUpload, "download": bitrateDownload};
+
+ PeerStats.packetLoss = {
+ total:
+ calculatePacketLoss(lostPackets.download + lostPackets.upload,
+ totalPackets.download + totalPackets.upload),
+ download:
+ calculatePacketLoss(lostPackets.download, totalPackets.download),
+ upload:
+ calculatePacketLoss(lostPackets.upload, totalPackets.upload)
+ };
+ this.eventEmitter.emit("statistics.connectionstats",
+ {
+ "bitrate": PeerStats.bitrate,
+ "packetLoss": PeerStats.packetLoss,
+ "bandwidth": PeerStats.bandwidth,
+ "resolution": resolutions,
+ "transport": PeerStats.transport
+ });
+ PeerStats.transport = [];
+
+};
+
+/**
+ * Stats processing logic.
+ */
+StatsCollector.prototype.processAudioLevelReport = function ()
+{
+ if (!this.baselineAudioLevelsReport)
+ {
+ return;
+ }
+
+ for (var idx in this.currentAudioLevelsReport)
+ {
+ var now = this.currentAudioLevelsReport[idx];
+
+ if (now.type != 'ssrc')
+ {
+ continue;
+ }
+
+ var before = this.baselineAudioLevelsReport[idx];
+ if (!before)
+ {
+ console.warn(getStatValue(now, 'ssrc') + ' not enough data');
+ continue;
+ }
+
+ var ssrc = getStatValue(now, 'ssrc');
+ var jid = APP.xmpp.getJidFromSSRC(ssrc);
+ if (!jid)
+ {
+ if((Date.now() - now.timestamp) < 3000)
+ console.warn("No jid for ssrc: " + ssrc);
+ continue;
+ }
+
+ var jidStats = this.jid2stats[jid];
+ if (!jidStats)
+ {
+ jidStats = new PeerStats();
+ this.jid2stats[jid] = jidStats;
+ }
+
+ // Audio level
+ var audioLevel = null;
+
+ try {
+ audioLevel = getStatValue(now, 'audioInputLevel');
+ if (!audioLevel)
+ audioLevel = getStatValue(now, 'audioOutputLevel');
+ }
+ catch(e) {/*not supported*/
+ console.warn("Audio Levels are not available in the statistics.");
+ clearInterval(this.audioLevelsIntervalId);
+ return;
+ }
+
+ if (audioLevel)
+ {
+ // TODO: can't find specs about what this value really is,
+ // but it seems to vary between 0 and around 32k.
+ audioLevel = audioLevel / 32767;
+ jidStats.setSsrcAudioLevel(ssrc, audioLevel);
+ if(jid != APP.xmpp.myJid())
+ this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
+ }
+
+ }
+
+
+};
+
+},{"../RTC/RTCBrowserType":8}],51:[function(require,module,exports){
+/**
+ * Created by hristo on 8/4/14.
+ */
+var LocalStats = require("./LocalStatsCollector.js");
+var RTPStats = require("./RTPStatsCollector.js");
+var EventEmitter = require("events");
+var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+var CallStats = require("./CallStats");
+var RTCEvents = require("../../service/RTC/RTCEvents");
+
+var eventEmitter = new EventEmitter();
+
+var localStats = null;
+
+var rtpStats = null;
+
+function stopLocal()
+{
+ if(localStats)
+ {
+ localStats.stop();
+ localStats = null;
+ }
+}
+
+function stopRemote()
+{
+ if(rtpStats)
+ {
+ rtpStats.stop();
+ eventEmitter.emit("statistics.stop");
+ rtpStats = null;
+ }
+}
+
+function startRemoteStats (peerconnection) {
+ if(rtpStats)
+ {
+ rtpStats.stop();
+ rtpStats = null;
+ }
+
+ rtpStats = new RTPStats(peerconnection, 200, 2000, eventEmitter);
+ rtpStats.start();
+}
+
+function onStreamCreated(stream)
+{
+ if(stream.getOriginalStream().getAudioTracks().length === 0)
+ return;
+
+ localStats = new LocalStats(stream.getOriginalStream(), 200, statistics,
+ eventEmitter);
+ localStats.start();
+}
+
+function onDisposeConference(onUnload) {
+ CallStats.sendTerminateEvent();
+ stopRemote();
+ if(onUnload) {
+ stopLocal();
+ eventEmitter.removeAllListeners();
+ }
+}
+
+
+var statistics =
+{
+ /**
+ * Indicates that this audio level is for local jid.
+ * @type {string}
+ */
+ LOCAL_JID: 'local',
+
+ addAudioLevelListener: function(listener)
+ {
+ eventEmitter.on("statistics.audioLevel", listener);
+ },
+
+ removeAudioLevelListener: function(listener)
+ {
+ eventEmitter.removeListener("statistics.audioLevel", listener);
+ },
+
+ addConnectionStatsListener: function(listener)
+ {
+ eventEmitter.on("statistics.connectionstats", listener);
+ },
+
+ removeConnectionStatsListener: function(listener)
+ {
+ eventEmitter.removeListener("statistics.connectionstats", listener);
+ },
+
+
+ addRemoteStatsStopListener: function(listener)
+ {
+ eventEmitter.on("statistics.stop", listener);
+ },
+
+ removeRemoteStatsStopListener: function(listener)
+ {
+ eventEmitter.removeListener("statistics.stop", listener);
+ },
+
+ stop: function () {
+ stopLocal();
+ stopRemote();
+ if(eventEmitter)
+ {
+ eventEmitter.removeAllListeners();
+ }
+ },
+
+ stopRemoteStatistics: function()
+ {
+ stopRemote();
+ },
+
+ start: function () {
+ APP.RTC.addStreamListener(onStreamCreated,
+ StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
+ APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
+ //FIXME: we may want to change CALL INCOMING event to onnegotiationneeded
+ APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) {
+ startRemoteStats(event.peerconnection);
+// CallStats.init(event);
+ });
+ APP.xmpp.addListener(XMPPEvents.PEERCONNECTION_READY, function (session) {
+ CallStats.init(session);
+ })
+ APP.RTC.addListener(RTCEvents.AUDIO_MUTE, function (mute) {
+ CallStats.sendMuteEvent(mute, "audio");
+ });
+ APP.xmpp.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () {
+ CallStats.sendSetupFailedEvent();
+ })
+ APP.RTC.addListener(RTCEvents.VIDEO_MUTE, function (mute) {
+ CallStats.sendMuteEvent(mute, "video");
+ });
+ }
+
+};
+
+
+
+
+module.exports = statistics;
+},{"../../service/RTC/RTCEvents":103,"../../service/RTC/StreamEventTypes.js":105,"../../service/xmpp/XMPPEvents":112,"./CallStats":48,"./LocalStatsCollector.js":49,"./RTPStatsCollector.js":50,"events":113}],52:[function(require,module,exports){
+var i18n = require("i18next-client");
+var languages = require("../../service/translation/languages");
+var Settings = require("../settings/Settings");
+var DEFAULT_LANG = languages.EN;
+
+i18n.addPostProcessor("resolveAppName", function(value, key, options) {
+ return value.replace("__app__", interfaceConfig.APP_NAME);
+});
+
+
+
+var defaultOptions = {
+ detectLngQS: "lang",
+ useCookie: false,
+ fallbackLng: DEFAULT_LANG,
+ load: "unspecific",
+ resGetPath: 'lang/__ns__-__lng__.json',
+ ns: {
+ namespaces: ['main', 'languages'],
+ defaultNs: 'main'
+ },
+ lngWhitelist : languages.getLanguages(),
+ fallbackOnNull: true,
+ fallbackOnEmpty: true,
+ useDataAttrOptions: true,
+ defaultValueFromContent: false,
+ app: interfaceConfig.APP_NAME,
+ getAsync: false,
+ defaultValueFromContent: false,
+ customLoad: function(lng, ns, options, done) {
+ var resPath = "lang/__ns__-__lng__.json";
+ if(lng === languages.EN)
+ resPath = "lang/__ns__.json";
+ var url = i18n.functions.applyReplacement(resPath, { lng: lng, ns: ns });
+ i18n.functions.ajax({
+ url: url,
+ success: function(data, status, xhr) {
+ i18n.functions.log('loaded: ' + url);
+ done(null, data);
+ },
+ error : function(xhr, status, error) {
+ if ((status && status == 200) ||
+ (xhr && xhr.status && xhr.status == 200)) {
+ // file loaded but invalid json, stop waste time !
+ i18n.functions.error('There is a typo in: ' + url);
+ } else if ((status && status == 404) ||
+ (xhr && xhr.status && xhr.status == 404)) {
+ i18n.functions.log('Does not exist: ' + url);
+ } else {
+ var theStatus = status ? status :
+ ((xhr && xhr.status) ? xhr.status : null);
+ i18n.functions.log(theStatus + ' when loading ' + url);
+ }
+
+ done(error, {});
+ },
+ dataType: "json",
+ async : options.getAsync
+ });
+ }
+ // options for caching
+// useLocalStorage: true,
+// localStorageExpirationTime: 86400000 // in ms, default 1 week
+};
+
+function initCompleted(t)
+{
+ $("[data-i18n]").i18n();
+}
+
+function checkForParameter() {
+ var query = window.location.search.substring(1);
+ var vars = query.split("&");
+ for (var i=0;i";
+ str += this.translateString(key, options);
+ str += "";
+ return str;
+
+ }
+};
+
+},{"../../service/translation/languages":111,"../settings/Settings":47,"i18next-client":68}],53:[function(require,module,exports){
+/* jshint -W117 */
+var TraceablePeerConnection = require("./TraceablePeerConnection");
+var SDPDiffer = require("./SDPDiffer");
+var SDPUtil = require("./SDPUtil");
+var SDP = require("./SDP");
+var async = require("async");
+var transform = require("sdp-transform");
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+var RTCBrowserType = require("../RTC/RTCBrowserType");
+
+// Jingle stuff
+function JingleSession(me, sid, connection, service, eventEmitter) {
+ this.me = me;
+ this.sid = sid;
+ this.connection = connection;
+ this.initiator = null;
+ this.responder = null;
+ this.isInitiator = null;
+ this.peerjid = null;
+ this.state = null;
+ this.localSDP = null;
+ this.remoteSDP = null;
+ this.relayedStreams = [];
+ this.startTime = null;
+ this.stopTime = null;
+ this.media_constraints = null;
+ this.pc_constraints = null;
+ this.ice_config = {};
+ this.drip_container = [];
+ this.service = service;
+ this.eventEmitter = eventEmitter;
+
+ this.usetrickle = true;
+ this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
+ this.usedrip = false; // dripping is sending trickle candidates not one-by-one
+
+ this.hadstuncandidate = false;
+ this.hadturncandidate = false;
+ this.lasticecandidate = false;
+
+ this.statsinterval = null;
+
+ this.reason = null;
+
+ this.addssrc = [];
+ this.removessrc = [];
+ this.pendingop = null;
+ this.switchstreams = false;
+
+ this.wait = true;
+ this.localStreamsSSRC = null;
+ this.ssrcOwners = {};
+ this.ssrcVideoTypes = {};
+ this.eventEmitter = eventEmitter;
+
+ /**
+ * The indicator which determines whether the (local) video has been muted
+ * in response to a user command in contrast to an automatic decision made
+ * by the application logic.
+ */
+ this.videoMuteByUser = false;
+ this.modifySourcesQueue = async.queue(this._modifySources.bind(this), 1);
+ // We start with the queue paused. We resume it when the signaling state is
+ // stable and the ice connection state is connected.
+ this.modifySourcesQueue.pause();
+}
+
+JingleSession.prototype.updateModifySourcesQueue = function() {
+ var signalingState = this.peerconnection.signalingState;
+ var iceConnectionState = this.peerconnection.iceConnectionState;
+ if (signalingState === 'stable' && iceConnectionState === 'connected') {
+ this.modifySourcesQueue.resume();
+ } else {
+ this.modifySourcesQueue.pause();
+ }
+};
+
+JingleSession.prototype.initiate = function (peerjid, isInitiator) {
+ var self = this;
+ if (this.state !== null) {
+ console.error('attempt to initiate on session ' + this.sid +
+ 'in state ' + this.state);
+ return;
+ }
+ this.isInitiator = isInitiator;
+ this.state = 'pending';
+ this.initiator = isInitiator ? this.me : peerjid;
+ this.responder = !isInitiator ? this.me : peerjid;
+ this.peerjid = peerjid;
+ this.hadstuncandidate = false;
+ this.hadturncandidate = false;
+ this.lasticecandidate = false;
+
+ this.peerconnection
+ = new TraceablePeerConnection(
+ this.connection.jingle.ice_config,
+ this.connection.jingle.pc_constraints,
+ this);
+
+ this.peerconnection.onicecandidate = function (event) {
+ self.sendIceCandidate(event.candidate);
+ };
+ this.peerconnection.onaddstream = function (event) {
+ if (event.stream.id !== 'default') {
+ console.log("REMOTE STREAM ADDED: ", event.stream , event.stream.id);
+ self.remoteStreamAdded(event);
+ } else {
+ // This is a recvonly stream. Clients that implement Unified Plan,
+ // such as Firefox use recvonly "streams/channels/tracks" for
+ // receiving remote stream/tracks, as opposed to Plan B where there
+ // are only 3 channels: audio, video and data.
+ console.log("RECVONLY REMOTE STREAM IGNORED: " + event.stream + " - " + event.stream.id);
+ }
+ };
+ this.peerconnection.onremovestream = function (event) {
+ // Remove the stream from remoteStreams
+ // FIXME: remotestreamremoved.jingle not defined anywhere(unused)
+ $(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
+ };
+ this.peerconnection.onsignalingstatechange = function (event) {
+ if (!(self && self.peerconnection)) return;
+ self.updateModifySourcesQueue();
+ };
+ this.peerconnection.oniceconnectionstatechange = function (event) {
+ if (!(self && self.peerconnection)) return;
+ self.updateModifySourcesQueue();
+ switch (self.peerconnection.iceConnectionState) {
+ case 'connected':
+ this.startTime = new Date();
+ break;
+ case 'disconnected':
+ this.stopTime = new Date();
+ break;
+ case 'failed':
+ self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
+ break;
+ }
+ onIceConnectionStateChange(self.sid, self);
+ };
+ this.peerconnection.onnegotiationneeded = function (event) {
+ self.eventEmitter.emit(XMPPEvents.PEERCONNECTION_READY, self);
+ };
+ // add any local and relayed stream
+ APP.RTC.localStreams.forEach(function(stream) {
+ self.peerconnection.addStream(stream.getOriginalStream());
+ });
+ this.relayedStreams.forEach(function(stream) {
+ self.peerconnection.addStream(stream);
+ });
+};
+
+function onIceConnectionStateChange(sid, session) {
+ switch (session.peerconnection.iceConnectionState) {
+ case 'checking':
+ session.timeChecking = (new Date()).getTime();
+ session.firstconnect = true;
+ break;
+ case 'completed': // on caller side
+ case 'connected':
+ if (session.firstconnect) {
+ session.firstconnect = false;
+ var metadata = {};
+ metadata.setupTime
+ = (new Date()).getTime() - session.timeChecking;
+ session.peerconnection.getStats(function (res) {
+ if(res && res.result) {
+ res.result().forEach(function (report) {
+ if (report.type == 'googCandidatePair' &&
+ report.stat('googActiveConnection') == 'true') {
+ metadata.localCandidateType
+ = report.stat('googLocalCandidateType');
+ metadata.remoteCandidateType
+ = report.stat('googRemoteCandidateType');
+
+ // log pair as well so we can get nice pie
+ // charts
+ metadata.candidatePair
+ = report.stat('googLocalCandidateType') +
+ ';' +
+ report.stat('googRemoteCandidateType');
+
+ if (report.stat('googRemoteAddress').indexOf('[') === 0)
+ {
+ metadata.ipv6 = true;
+ }
+ }
+ });
+ }
+ });
+ }
+ break;
+ }
+}
+
+JingleSession.prototype.getVideoType = function () {
+ return APP.desktopsharing.isUsingScreenStream() ? 'screen' : 'camera';
+};
+
+JingleSession.prototype.accept = function () {
+ this.state = 'active';
+
+ var pranswer = this.peerconnection.localDescription;
+ if (!pranswer || pranswer.type != 'pranswer') {
+ return;
+ }
+ console.log('going from pranswer to answer');
+ if (this.usetrickle) {
+ // remove candidates already sent from session-accept
+ var lines = SDPUtil.find_lines(pranswer.sdp, 'a=candidate:');
+ for (var i = 0; i < lines.length; i++) {
+ pranswer.sdp = pranswer.sdp.replace(lines[i] + '\r\n', '');
+ }
+ }
+ while (SDPUtil.find_line(pranswer.sdp, 'a=inactive')) {
+ // FIXME: change any inactive to sendrecv or whatever they were originally
+ pranswer.sdp = pranswer.sdp.replace('a=inactive', 'a=sendrecv');
+ }
+ var prsdp = new SDP(pranswer.sdp);
+ var accept = $iq({to: this.peerjid,
+ type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: 'session-accept',
+ initiator: this.initiator,
+ responder: this.responder,
+ sid: this.sid });
+ // FIXME why do we generate session-accept in 3 different places ?
+ prsdp.toJingle(
+ accept,
+ this.initiator == this.me ? 'initiator' : 'responder',
+ this.localStreamsSSRC,
+ self.getVideoType());
+ var sdp = this.peerconnection.localDescription.sdp;
+ while (SDPUtil.find_line(sdp, 'a=inactive')) {
+ // FIXME: change any inactive to sendrecv or whatever they were originally
+ sdp = sdp.replace('a=inactive', 'a=sendrecv');
+ }
+ var self = this;
+ this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
+ function () {
+ //console.log('setLocalDescription success');
+ self.setLocalDescription();
+
+ self.connection.sendIQ(accept,
+ function () {
+ var ack = {};
+ ack.source = 'answer';
+ $(document).trigger('ack.jingle', [self.sid, ack]);
+ },
+ function (stanza) {
+ var error = ($(stanza).find('error').length) ? {
+ code: $(stanza).find('error').attr('code'),
+ reason: $(stanza).find('error :first')[0].tagName
+ }:{};
+ error.source = 'answer';
+ JingleSession.onJingleError(self.sid, error);
+ },
+ 10000);
+ },
+ function (e) {
+ console.error('setLocalDescription failed', e);
+ self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
+ }
+ );
+};
+
+JingleSession.prototype.terminate = function (reason) {
+ this.state = 'ended';
+ this.reason = reason;
+ this.peerconnection.close();
+ if (this.statsinterval !== null) {
+ window.clearInterval(this.statsinterval);
+ this.statsinterval = null;
+ }
+};
+
+JingleSession.prototype.active = function () {
+ return this.state == 'active';
+};
+
+JingleSession.prototype.sendIceCandidate = function (candidate) {
+ var self = this;
+ if (candidate && !this.lasticecandidate) {
+ var ice = SDPUtil.iceparams(this.localSDP.media[candidate.sdpMLineIndex], this.localSDP.session);
+ var jcand = SDPUtil.candidateToJingle(candidate.candidate);
+ if (!(ice && jcand)) {
+ console.error('failed to get ice && jcand');
+ return;
+ }
+ ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
+
+ if (jcand.type === 'srflx') {
+ this.hadstuncandidate = true;
+ } else if (jcand.type === 'relay') {
+ this.hadturncandidate = true;
+ }
+
+ if (this.usetrickle) {
+ if (this.usedrip) {
+ if (this.drip_container.length === 0) {
+ // start 20ms callout
+ window.setTimeout(function () {
+ if (self.drip_container.length === 0) return;
+ self.sendIceCandidates(self.drip_container);
+ self.drip_container = [];
+ }, 20);
+
+ }
+ this.drip_container.push(candidate);
+ return;
+ } else {
+ self.sendIceCandidate([candidate]);
+ }
+ }
+ } else {
+ //console.log('sendIceCandidate: last candidate.');
+ if (!this.usetrickle) {
+ //console.log('should send full offer now...');
+ //FIXME why do we generate session-accept in 3 different places ?
+ var init = $iq({to: this.peerjid,
+ type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: this.peerconnection.localDescription.type == 'offer' ? 'session-initiate' : 'session-accept',
+ initiator: this.initiator,
+ sid: this.sid});
+ this.localSDP = new SDP(this.peerconnection.localDescription.sdp);
+ var self = this;
+ var sendJingle = function (ssrc) {
+ if(!ssrc)
+ ssrc = {};
+ self.localSDP.toJingle(
+ init,
+ self.initiator == self.me ? 'initiator' : 'responder',
+ ssrc,
+ self.getVideoType());
+ self.connection.sendIQ(init,
+ function () {
+ //console.log('session initiate ack');
+ var ack = {};
+ ack.source = 'offer';
+ $(document).trigger('ack.jingle', [self.sid, ack]);
+ },
+ function (stanza) {
+ self.state = 'error';
+ self.peerconnection.close();
+ var error = ($(stanza).find('error').length) ? {
+ code: $(stanza).find('error').attr('code'),
+ reason: $(stanza).find('error :first')[0].tagName,
+ }:{};
+ error.source = 'offer';
+ JingleSession.onJingleError(self.sid, error);
+ },
+ 10000);
+ }
+ sendJingle();
+ }
+ this.lasticecandidate = true;
+ console.log('Have we encountered any srflx candidates? ' + this.hadstuncandidate);
+ console.log('Have we encountered any relay candidates? ' + this.hadturncandidate);
+
+ if (!(this.hadstuncandidate || this.hadturncandidate) && this.peerconnection.signalingState != 'closed') {
+ $(document).trigger('nostuncandidates.jingle', [this.sid]);
+ }
+ }
+};
+
+JingleSession.prototype.sendIceCandidates = function (candidates) {
+ console.log('sendIceCandidates', candidates);
+ var cand = $iq({to: this.peerjid, type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: 'transport-info',
+ initiator: this.initiator,
+ sid: this.sid});
+ for (var mid = 0; mid < this.localSDP.media.length; mid++) {
+ var cands = candidates.filter(function (el) { return el.sdpMLineIndex == mid; });
+ var mline = SDPUtil.parse_mline(this.localSDP.media[mid].split('\r\n')[0]);
+ if (cands.length > 0) {
+ var ice = SDPUtil.iceparams(this.localSDP.media[mid], this.localSDP.session);
+ ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
+ cand.c('content', {creator: this.initiator == this.me ? 'initiator' : 'responder',
+ name: (cands[0].sdpMid? cands[0].sdpMid : mline.media)
+ }).c('transport', ice);
+ for (var i = 0; i < cands.length; i++) {
+ cand.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
+ }
+ // add fingerprint
+ if (SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)) {
+ var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session));
+ tmp.required = true;
+ cand.c(
+ 'fingerprint',
+ {xmlns: 'urn:xmpp:jingle:apps:dtls:0'})
+ .t(tmp.fingerprint);
+ delete tmp.fingerprint;
+ cand.attrs(tmp);
+ cand.up();
+ }
+ cand.up(); // transport
+ cand.up(); // content
+ }
+ }
+ // might merge last-candidate notification into this, but it is called alot later. See webrtc issue #2340
+ //console.log('was this the last candidate', this.lasticecandidate);
+ this.connection.sendIQ(cand,
+ function () {
+ var ack = {};
+ ack.source = 'transportinfo';
+ $(document).trigger('ack.jingle', [this.sid, ack]);
+ },
+ function (stanza) {
+ var error = ($(stanza).find('error').length) ? {
+ code: $(stanza).find('error').attr('code'),
+ reason: $(stanza).find('error :first')[0].tagName,
+ }:{};
+ error.source = 'transportinfo';
+ JingleSession.onJingleError(this.sid, error);
+ },
+ 10000);
+};
+
+
+JingleSession.prototype.sendOffer = function () {
+ //console.log('sendOffer...');
+ var self = this;
+ this.peerconnection.createOffer(function (sdp) {
+ self.createdOffer(sdp);
+ },
+ function (e) {
+ console.error('createOffer failed', e);
+ },
+ this.media_constraints
+ );
+};
+
+// FIXME createdOffer is never used in jitsi-meet
+JingleSession.prototype.createdOffer = function (sdp) {
+ //console.log('createdOffer', sdp);
+ var self = this;
+ this.localSDP = new SDP(sdp.sdp);
+ //this.localSDP.mangle();
+ var sendJingle = function () {
+ var init = $iq({to: this.peerjid,
+ type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: 'session-initiate',
+ initiator: this.initiator,
+ sid: this.sid});
+ self.localSDP.toJingle(
+ init,
+ this.initiator == this.me ? 'initiator' : 'responder',
+ this.localStreamsSSRC,
+ self.getVideoType());
+ self.connection.sendIQ(init,
+ function () {
+ var ack = {};
+ ack.source = 'offer';
+ $(document).trigger('ack.jingle', [self.sid, ack]);
+ },
+ function (stanza) {
+ self.state = 'error';
+ self.peerconnection.close();
+ var error = ($(stanza).find('error').length) ? {
+ code: $(stanza).find('error').attr('code'),
+ reason: $(stanza).find('error :first')[0].tagName,
+ }:{};
+ error.source = 'offer';
+ JingleSession.onJingleError(self.sid, error);
+ },
+ 10000);
+ }
+ sdp.sdp = this.localSDP.raw;
+ this.peerconnection.setLocalDescription(sdp,
+ function () {
+ if(self.usetrickle)
+ {
+ sendJingle();
+ }
+ self.setLocalDescription();
+ //console.log('setLocalDescription success');
+ },
+ function (e) {
+ console.error('setLocalDescription failed', e);
+ self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
+ }
+ );
+ var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
+ for (var i = 0; i < cands.length; i++) {
+ var cand = SDPUtil.parse_icecandidate(cands[i]);
+ if (cand.type == 'srflx') {
+ this.hadstuncandidate = true;
+ } else if (cand.type == 'relay') {
+ this.hadturncandidate = true;
+ }
+ }
+};
+
+JingleSession.prototype.readSsrcInfo = function (contents) {
+ var self = this;
+ $(contents).each(function (idx, content) {
+ var name = $(content).attr('name');
+ var mediaType = this.getAttribute('name');
+ var ssrcs = $(content).find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
+ ssrcs.each(function () {
+ var ssrc = this.getAttribute('ssrc');
+ $(this).find('>ssrc-info[xmlns="http://jitsi.org/jitmeet"]').each(
+ function () {
+ var owner = this.getAttribute('owner');
+ var videoType = this.getAttribute('video-type');
+ self.ssrcOwners[ssrc] = owner;
+ self.ssrcVideoTypes[ssrc] = videoType;
+ }
+ );
+ });
+ });
+};
+
+JingleSession.prototype.getSsrcOwner = function (ssrc) {
+ return this.ssrcOwners[ssrc];
+};
+
+JingleSession.prototype.setRemoteDescription = function (elem, desctype) {
+ //console.log('setting remote description... ', desctype);
+ this.remoteSDP = new SDP('');
+ this.remoteSDP.fromJingle(elem);
+ this.readSsrcInfo($(elem).find(">content"));
+ if (this.peerconnection.remoteDescription !== null) {
+ console.log('setRemoteDescription when remote description is not null, should be pranswer', this.peerconnection.remoteDescription);
+ if (this.peerconnection.remoteDescription.type == 'pranswer') {
+ var pranswer = new SDP(this.peerconnection.remoteDescription.sdp);
+ for (var i = 0; i < pranswer.media.length; i++) {
+ // make sure we have ice ufrag and pwd
+ if (!SDPUtil.find_line(this.remoteSDP.media[i], 'a=ice-ufrag:', this.remoteSDP.session)) {
+ if (SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session)) {
+ this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session) + '\r\n';
+ } else {
+ console.warn('no ice ufrag?');
+ }
+ if (SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session)) {
+ this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session) + '\r\n';
+ } else {
+ console.warn('no ice pwd?');
+ }
+ }
+ // copy over candidates
+ var lines = SDPUtil.find_lines(pranswer.media[i], 'a=candidate:');
+ for (var j = 0; j < lines.length; j++) {
+ this.remoteSDP.media[i] += lines[j] + '\r\n';
+ }
+ }
+ this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
+ }
+ }
+ var remotedesc = new RTCSessionDescription({type: desctype, sdp: this.remoteSDP.raw});
+
+ this.peerconnection.setRemoteDescription(remotedesc,
+ function () {
+ //console.log('setRemoteDescription success');
+ },
+ function (e) {
+ console.error('setRemoteDescription error', e);
+ JingleSession.onJingleFatalError(self, e);
+ }
+ );
+};
+
+JingleSession.prototype.addIceCandidate = function (elem) {
+ var self = this;
+ if (this.peerconnection.signalingState == 'closed') {
+ return;
+ }
+ if (!this.peerconnection.remoteDescription && this.peerconnection.signalingState == 'have-local-offer') {
+ console.log('trickle ice candidate arriving before session accept...');
+ // create a PRANSWER for setRemoteDescription
+ if (!this.remoteSDP) {
+ var cobbled = 'v=0\r\n' +
+ 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
+ 's=-\r\n' +
+ 't=0 0\r\n';
+ // first, take some things from the local description
+ for (var i = 0; i < this.localSDP.media.length; i++) {
+ cobbled += SDPUtil.find_line(this.localSDP.media[i], 'm=') + '\r\n';
+ cobbled += SDPUtil.find_lines(this.localSDP.media[i], 'a=rtpmap:').join('\r\n') + '\r\n';
+ if (SDPUtil.find_line(this.localSDP.media[i], 'a=mid:')) {
+ cobbled += SDPUtil.find_line(this.localSDP.media[i], 'a=mid:') + '\r\n';
+ }
+ cobbled += 'a=inactive\r\n';
+ }
+ this.remoteSDP = new SDP(cobbled);
+ }
+ // then add things like ice and dtls from remote candidate
+ elem.each(function () {
+ for (var i = 0; i < self.remoteSDP.media.length; i++) {
+ if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
+ self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
+ if (!SDPUtil.find_line(self.remoteSDP.media[i], 'a=ice-ufrag:')) {
+ var tmp = $(this).find('transport');
+ self.remoteSDP.media[i] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
+ self.remoteSDP.media[i] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n';
+ tmp = $(this).find('transport>fingerprint');
+ if (tmp.length) {
+ self.remoteSDP.media[i] += 'a=fingerprint:' + tmp.attr('hash') + ' ' + tmp.text() + '\r\n';
+ } else {
+ console.log('no dtls fingerprint (webrtc issue #1718?)');
+ self.remoteSDP.media[i] += 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:BAADBAADBAADBAADBAADBAADBAADBAADBAADBAAD\r\n';
+ }
+ break;
+ }
+ }
+ }
+ });
+ this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
+
+ // we need a complete SDP with ice-ufrag/ice-pwd in all parts
+ // this makes the assumption that the PRANSWER is constructed such that the ice-ufrag is in all mediaparts
+ // but it could be in the session part as well. since the code above constructs this sdp this can't happen however
+ var iscomplete = this.remoteSDP.media.filter(function (mediapart) {
+ return SDPUtil.find_line(mediapart, 'a=ice-ufrag:');
+ }).length == this.remoteSDP.media.length;
+
+ if (iscomplete) {
+ console.log('setting pranswer');
+ try {
+ this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'pranswer', sdp: this.remoteSDP.raw }),
+ function() {
+ },
+ function(e) {
+ console.log('setRemoteDescription pranswer failed', e.toString());
+ });
+ } catch (e) {
+ console.error('setting pranswer failed', e);
+ }
+ } else {
+ //console.log('not yet setting pranswer');
+ }
+ }
+ // operate on each content element
+ elem.each(function () {
+ // would love to deactivate this, but firefox still requires it
+ var idx = -1;
+ var i;
+ for (i = 0; i < self.remoteSDP.media.length; i++) {
+ if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
+ self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
+ idx = i;
+ break;
+ }
+ }
+ if (idx == -1) { // fall back to localdescription
+ for (i = 0; i < self.localSDP.media.length; i++) {
+ if (SDPUtil.find_line(self.localSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
+ self.localSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
+ idx = i;
+ break;
+ }
+ }
+ }
+ var name = $(this).attr('name');
+ // TODO: check ice-pwd and ice-ufrag?
+ $(this).find('transport>candidate').each(function () {
+ var line, candidate;
+ line = SDPUtil.candidateFromJingle(this);
+ candidate = new RTCIceCandidate({sdpMLineIndex: idx,
+ sdpMid: name,
+ candidate: line});
+ try {
+ self.peerconnection.addIceCandidate(candidate);
+ } catch (e) {
+ console.error('addIceCandidate failed', e.toString(), line);
+ }
+ });
+ });
+};
+
+JingleSession.prototype.sendAnswer = function (provisional) {
+ //console.log('createAnswer', provisional);
+ var self = this;
+ this.peerconnection.createAnswer(
+ function (sdp) {
+ self.createdAnswer(sdp, provisional);
+ },
+ function (e) {
+ console.error('createAnswer failed', e);
+ self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
+ },
+ this.media_constraints
+ );
+};
+
+JingleSession.prototype.createdAnswer = function (sdp, provisional) {
+ //console.log('createAnswer callback');
+ var self = this;
+ this.localSDP = new SDP(sdp.sdp);
+ //this.localSDP.mangle();
+ this.usepranswer = provisional === true;
+ if (this.usetrickle) {
+ if (this.usepranswer) {
+ sdp.type = 'pranswer';
+ for (var i = 0; i < this.localSDP.media.length; i++) {
+ this.localSDP.media[i] = this.localSDP.media[i].replace('a=sendrecv\r\n', 'a=inactive\r\n');
+ }
+ this.localSDP.raw = this.localSDP.session + '\r\n' + this.localSDP.media.join('');
+ }
+ }
+ var self = this;
+ var sendJingle = function (ssrcs) {
+ // FIXME why do we generate session-accept in 3 different places ?
+ var accept = $iq({to: self.peerjid,
+ type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: 'session-accept',
+ initiator: self.initiator,
+ responder: self.responder,
+ sid: self.sid });
+ self.localSDP.toJingle(
+ accept,
+ self.initiator == self.me ? 'initiator' : 'responder',
+ ssrcs,
+ self.getVideoType());
+ self.connection.sendIQ(accept,
+ function () {
+ var ack = {};
+ ack.source = 'answer';
+ $(document).trigger('ack.jingle', [self.sid, ack]);
+ },
+ function (stanza) {
+ var error = ($(stanza).find('error').length) ? {
+ code: $(stanza).find('error').attr('code'),
+ reason: $(stanza).find('error :first')[0].tagName,
+ }:{};
+ error.source = 'answer';
+ JingleSession.onJingleError(self.sid, error);
+ },
+ 10000);
+ }
+ sdp.sdp = this.localSDP.raw;
+ this.peerconnection.setLocalDescription(sdp,
+ function () {
+
+ //console.log('setLocalDescription success');
+ if (self.usetrickle && !self.usepranswer) {
+ sendJingle();
+ }
+ self.setLocalDescription();
+ },
+ function (e) {
+ console.error('setLocalDescription failed', e);
+ self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
+ }
+ );
+ var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
+ for (var j = 0; j < cands.length; j++) {
+ var cand = SDPUtil.parse_icecandidate(cands[j]);
+ if (cand.type == 'srflx') {
+ this.hadstuncandidate = true;
+ } else if (cand.type == 'relay') {
+ this.hadturncandidate = true;
+ }
+ }
+};
+
+JingleSession.prototype.sendTerminate = function (reason, text) {
+ var self = this,
+ term = $iq({to: this.peerjid,
+ type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: 'session-terminate',
+ initiator: this.initiator,
+ sid: this.sid})
+ .c('reason')
+ .c(reason || 'success');
+
+ if (text) {
+ term.up().c('text').t(text);
+ }
+
+ this.connection.sendIQ(term,
+ function () {
+ self.peerconnection.close();
+ self.peerconnection = null;
+ self.terminate();
+ var ack = {};
+ ack.source = 'terminate';
+ $(document).trigger('ack.jingle', [self.sid, ack]);
+ },
+ function (stanza) {
+ var error = ($(stanza).find('error').length) ? {
+ code: $(stanza).find('error').attr('code'),
+ reason: $(stanza).find('error :first')[0].tagName,
+ }:{};
+ $(document).trigger('ack.jingle', [self.sid, error]);
+ },
+ 10000);
+ if (this.statsinterval !== null) {
+ window.clearInterval(this.statsinterval);
+ this.statsinterval = null;
+ }
+};
+
+JingleSession.prototype.addSource = function (elem, fromJid) {
+
+ var self = this;
+ // FIXME: dirty waiting
+ if (!this.peerconnection.localDescription)
+ {
+ console.warn("addSource - localDescription not ready yet")
+ setTimeout(function()
+ {
+ self.addSource(elem, fromJid);
+ },
+ 200
+ );
+ return;
+ }
+
+ console.log('addssrc', new Date().getTime());
+ console.log('ice', this.peerconnection.iceConnectionState);
+
+ this.readSsrcInfo(elem);
+
+ var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
+ var mySdp = new SDP(this.peerconnection.localDescription.sdp);
+
+ $(elem).each(function (idx, content) {
+ var name = $(content).attr('name');
+ var lines = '';
+ $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
+ var semantics = this.getAttribute('semantics');
+ var ssrcs = $(this).find('>source').map(function () {
+ return this.getAttribute('ssrc');
+ }).get();
+
+ if (ssrcs.length != 0) {
+ lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
+ }
+ });
+ var tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
+ tmp.each(function () {
+ var ssrc = $(this).attr('ssrc');
+ if(mySdp.containsSSRC(ssrc)){
+ /**
+ * This happens when multiple participants change their streams at the same time and
+ * ColibriFocus.modifySources have to wait for stable state. In the meantime multiple
+ * addssrc are scheduled for update IQ. See
+ */
+ console.warn("Got add stream request for my own ssrc: "+ssrc);
+ return;
+ }
+ $(this).find('>parameter').each(function () {
+ lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
+ if ($(this).attr('value') && $(this).attr('value').length)
+ lines += ':' + $(this).attr('value');
+ lines += '\r\n';
+ });
+ });
+ sdp.media.forEach(function(media, idx) {
+ if (!SDPUtil.find_line(media, 'a=mid:' + name))
+ return;
+ sdp.media[idx] += lines;
+ if (!self.addssrc[idx]) self.addssrc[idx] = '';
+ self.addssrc[idx] += lines;
+ });
+ sdp.raw = sdp.session + sdp.media.join('');
+ });
+
+ this.modifySourcesQueue.push(function() {
+ // When a source is added and if this is FF, a new channel is allocated
+ // for receiving the added source. We need to diffuse the SSRC of this
+ // new recvonly channel to the rest of the peers.
+ console.log('modify sources done');
+
+ var newSdp = new SDP(self.peerconnection.localDescription.sdp);
+ console.log("SDPs", mySdp, newSdp);
+ self.notifyMySSRCUpdate(mySdp, newSdp);
+ });
+};
+
+JingleSession.prototype.removeSource = function (elem, fromJid) {
+
+ var self = this;
+ // FIXME: dirty waiting
+ if (!this.peerconnection.localDescription)
+ {
+ console.warn("removeSource - localDescription not ready yet")
+ setTimeout(function()
+ {
+ self.removeSource(elem, fromJid);
+ },
+ 200
+ );
+ return;
+ }
+
+ console.log('removessrc', new Date().getTime());
+ console.log('ice', this.peerconnection.iceConnectionState);
+ var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
+ var mySdp = new SDP(this.peerconnection.localDescription.sdp);
+
+ $(elem).each(function (idx, content) {
+ var name = $(content).attr('name');
+ var lines = '';
+ $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
+ var semantics = this.getAttribute('semantics');
+ var ssrcs = $(this).find('>source').map(function () {
+ return this.getAttribute('ssrc');
+ }).get();
+
+ if (ssrcs.length != 0) {
+ lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
+ }
+ });
+ var tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
+ tmp.each(function () {
+ var ssrc = $(this).attr('ssrc');
+ // This should never happen, but can be useful for bug detection
+ if(mySdp.containsSSRC(ssrc)){
+ console.error("Got remove stream request for my own ssrc: "+ssrc);
+ return;
+ }
+ $(this).find('>parameter').each(function () {
+ lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
+ if ($(this).attr('value') && $(this).attr('value').length)
+ lines += ':' + $(this).attr('value');
+ lines += '\r\n';
+ });
+ });
+ sdp.media.forEach(function(media, idx) {
+ if (!SDPUtil.find_line(media, 'a=mid:' + name))
+ return;
+ sdp.media[idx] += lines;
+ if (!self.removessrc[idx]) self.removessrc[idx] = '';
+ self.removessrc[idx] += lines;
+ });
+ sdp.raw = sdp.session + sdp.media.join('');
+ });
+
+ this.modifySourcesQueue.push(function() {
+ // When a source is removed and if this is FF, the recvonly channel that
+ // receives the remote stream is deactivated . We need to diffuse the
+ // recvonly SSRC removal to the rest of the peers.
+ console.log('modify sources done');
+
+ var newSdp = new SDP(self.peerconnection.localDescription.sdp);
+ console.log("SDPs", mySdp, newSdp);
+ self.notifyMySSRCUpdate(mySdp, newSdp);
+ });
+};
+
+JingleSession.prototype._modifySources = function (successCallback, queueCallback) {
+ var self = this;
+
+ if (this.peerconnection.signalingState == 'closed') return;
+ if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
+ // There is nothing to do since scheduled job might have been executed by another succeeding call
+ this.setLocalDescription();
+ if(successCallback){
+ successCallback();
+ }
+ queueCallback();
+ return;
+ }
+
+ // Reset switch streams flag
+ this.switchstreams = false;
+
+ var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
+
+ // add sources
+ this.addssrc.forEach(function(lines, idx) {
+ sdp.media[idx] += lines;
+ });
+ this.addssrc = [];
+
+ // remove sources
+ this.removessrc.forEach(function(lines, idx) {
+ lines = lines.split('\r\n');
+ lines.pop(); // remove empty last element;
+ lines.forEach(function(line) {
+ sdp.media[idx] = sdp.media[idx].replace(line + '\r\n', '');
+ });
+ });
+ this.removessrc = [];
+
+ sdp.raw = sdp.session + sdp.media.join('');
+ this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}),
+ function() {
+
+ if(self.signalingState == 'closed') {
+ console.error("createAnswer attempt on closed state");
+ queueCallback("createAnswer attempt on closed state");
+ return;
+ }
+
+ self.peerconnection.createAnswer(
+ function(modifiedAnswer) {
+ // change video direction, see https://github.com/jitsi/jitmeet/issues/41
+ if (self.pendingop !== null) {
+ var sdp = new SDP(modifiedAnswer.sdp);
+ if (sdp.media.length > 1) {
+ switch(self.pendingop) {
+ case 'mute':
+ sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
+ break;
+ case 'unmute':
+ sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
+ break;
+ }
+ sdp.raw = sdp.session + sdp.media.join('');
+ modifiedAnswer.sdp = sdp.raw;
+ }
+ self.pendingop = null;
+ }
+
+ // FIXME: pushing down an answer while ice connection state
+ // is still checking is bad...
+ //console.log(self.peerconnection.iceConnectionState);
+
+ // trying to work around another chrome bug
+ //modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass');
+ self.peerconnection.setLocalDescription(modifiedAnswer,
+ function() {
+ //console.log('modified setLocalDescription ok');
+ self.setLocalDescription();
+ if(successCallback){
+ successCallback();
+ }
+ queueCallback();
+ },
+ function(error) {
+ console.error('modified setLocalDescription failed', error);
+ queueCallback(error);
+ }
+ );
+ },
+ function(error) {
+ console.error('modified answer failed', error);
+ queueCallback(error);
+ }
+ );
+ },
+ function(error) {
+ console.error('modify failed', error);
+ queueCallback(error);
+ }
+ );
+};
+
+
+/**
+ * Switches video streams.
+ * @param new_stream new stream that will be used as video of this session.
+ * @param oldStream old video stream of this session.
+ * @param success_callback callback executed after successful stream switch.
+ */
+JingleSession.prototype.switchStreams = function (new_stream, oldStream, success_callback, isAudio) {
+
+ var self = this;
+
+ // Remember SDP to figure out added/removed SSRCs
+ var oldSdp = null;
+ if(self.peerconnection) {
+ if(self.peerconnection.localDescription) {
+ oldSdp = new SDP(self.peerconnection.localDescription.sdp);
+ }
+ self.peerconnection.removeStream(oldStream, true);
+ if(new_stream)
+ self.peerconnection.addStream(new_stream);
+ }
+
+ // Conference is not active
+ if(!oldSdp || !self.peerconnection) {
+ success_callback();
+ return;
+ }
+
+ self.switchstreams = true;
+ self.modifySourcesQueue.push(function() {
+ console.log('modify sources done');
+
+ success_callback();
+
+ var newSdp = new SDP(self.peerconnection.localDescription.sdp);
+ console.log("SDPs", oldSdp, newSdp);
+ self.notifyMySSRCUpdate(oldSdp, newSdp);
+ });
+};
+
+/**
+ * Figures out added/removed ssrcs and send update IQs.
+ * @param old_sdp SDP object for old description.
+ * @param new_sdp SDP object for new description.
+ */
+JingleSession.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
+
+ if (!(this.peerconnection.signalingState == 'stable' &&
+ this.peerconnection.iceConnectionState == 'connected')){
+ console.log("Too early to send updates");
+ return;
+ }
+
+ // send source-remove IQ.
+ sdpDiffer = new SDPDiffer(new_sdp, old_sdp);
+ var remove = $iq({to: this.peerjid, type: 'set'})
+ .c('jingle', {
+ xmlns: 'urn:xmpp:jingle:1',
+ action: 'source-remove',
+ initiator: this.initiator,
+ sid: this.sid
+ }
+ );
+ var removed = sdpDiffer.toJingle(remove);
+ if (removed) {
+ this.connection.sendIQ(remove,
+ function (res) {
+ console.info('got remove result', res);
+ },
+ function (err) {
+ console.error('got remove error', err);
+ }
+ );
+ } else {
+ console.log('removal not necessary');
+ }
+
+ // send source-add IQ.
+ var sdpDiffer = new SDPDiffer(old_sdp, new_sdp);
+ var add = $iq({to: this.peerjid, type: 'set'})
+ .c('jingle', {
+ xmlns: 'urn:xmpp:jingle:1',
+ action: 'source-add',
+ initiator: this.initiator,
+ sid: this.sid
+ }
+ );
+ var added = sdpDiffer.toJingle(add, this.getVideoType());
+ if (added) {
+ this.connection.sendIQ(add,
+ function (res) {
+ console.info('got add result', res);
+ },
+ function (err) {
+ console.error('got add error', err);
+ }
+ );
+ } else {
+ console.log('addition not necessary');
+ }
+};
+
+/**
+ * Mutes/unmutes the (local) video i.e. enables/disables all video tracks.
+ *
+ * @param mute true to mute the (local) video i.e. to disable all video
+ * tracks; otherwise, false
+ * @param callback a function to be invoked with mute after all video
+ * tracks have been enabled/disabled. The function may, optionally, return
+ * another function which is to be invoked after the whole mute/unmute operation
+ * has completed successfully.
+ * @param options an object which specifies optional arguments such as the
+ * boolean key byUser with default value true which
+ * specifies whether the method was initiated in response to a user command (in
+ * contrast to an automatic decision made by the application logic)
+ */
+JingleSession.prototype.setVideoMute = function (mute, callback, options) {
+ var byUser;
+
+ if (options) {
+ byUser = options.byUser;
+ if (typeof byUser === 'undefined') {
+ byUser = true;
+ }
+ } else {
+ byUser = true;
+ }
+ // The user's command to mute the (local) video takes precedence over any
+ // automatic decision made by the application logic.
+ if (byUser) {
+ this.videoMuteByUser = mute;
+ } else if (this.videoMuteByUser) {
+ return;
+ }
+
+ this.hardMuteVideo(mute);
+
+ var self = this;
+ var oldSdp = null;
+ if(self.peerconnection) {
+ if(self.peerconnection.localDescription) {
+ oldSdp = new SDP(self.peerconnection.localDescription.sdp);
+ }
+ }
+
+ this.modifySourcesQueue.push(function() {
+ console.log('modify sources done');
+
+ callback(mute);
+
+ var newSdp = new SDP(self.peerconnection.localDescription.sdp);
+ console.log("SDPs", oldSdp, newSdp);
+ self.notifyMySSRCUpdate(oldSdp, newSdp);
+ });
+};
+
+JingleSession.prototype.hardMuteVideo = function (muted) {
+ this.pendingop = muted ? 'mute' : 'unmute';
+};
+
+JingleSession.prototype.sendMute = function (muted, content) {
+ var info = $iq({to: this.peerjid,
+ type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: 'session-info',
+ initiator: this.initiator,
+ sid: this.sid });
+ info.c(muted ? 'mute' : 'unmute', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
+ info.attrs({'creator': this.me == this.initiator ? 'creator' : 'responder'});
+ if (content) {
+ info.attrs({'name': content});
+ }
+ this.connection.send(info);
+};
+
+JingleSession.prototype.sendRinging = function () {
+ var info = $iq({to: this.peerjid,
+ type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: 'session-info',
+ initiator: this.initiator,
+ sid: this.sid });
+ info.c('ringing', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
+ this.connection.send(info);
+};
+
+JingleSession.prototype.getStats = function (interval) {
+ var self = this;
+ var recv = {audio: 0, video: 0};
+ var lost = {audio: 0, video: 0};
+ var lastrecv = {audio: 0, video: 0};
+ var lastlost = {audio: 0, video: 0};
+ var loss = {audio: 0, video: 0};
+ var delta = {audio: 0, video: 0};
+ this.statsinterval = window.setInterval(function () {
+ if (self && self.peerconnection && self.peerconnection.getStats) {
+ self.peerconnection.getStats(function (stats) {
+ var results = stats.result();
+ // TODO: there are so much statistics you can get from this..
+ for (var i = 0; i < results.length; ++i) {
+ if (results[i].type == 'ssrc') {
+ var packetsrecv = results[i].stat('packetsReceived');
+ var packetslost = results[i].stat('packetsLost');
+ if (packetsrecv && packetslost) {
+ packetsrecv = parseInt(packetsrecv, 10);
+ packetslost = parseInt(packetslost, 10);
+
+ if (results[i].stat('googFrameRateReceived')) {
+ lastlost.video = lost.video;
+ lastrecv.video = recv.video;
+ recv.video = packetsrecv;
+ lost.video = packetslost;
+ } else {
+ lastlost.audio = lost.audio;
+ lastrecv.audio = recv.audio;
+ recv.audio = packetsrecv;
+ lost.audio = packetslost;
+ }
+ }
+ }
+ }
+ delta.audio = recv.audio - lastrecv.audio;
+ delta.video = recv.video - lastrecv.video;
+ loss.audio = (delta.audio > 0) ? Math.ceil(100 * (lost.audio - lastlost.audio) / delta.audio) : 0;
+ loss.video = (delta.video > 0) ? Math.ceil(100 * (lost.video - lastlost.video) / delta.video) : 0;
+ $(document).trigger('packetloss.jingle', [self.sid, loss]);
+ });
+ }
+ }, interval || 3000);
+ return this.statsinterval;
+};
+
+JingleSession.onJingleError = function (session, error)
+{
+ console.error("Jingle error", error);
+}
+
+JingleSession.onJingleFatalError = function (session, error)
+{
+ this.service.sessionTerminated = true;
+ this.connection.emuc.doLeave();
+ this.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
+ this.eventEmitter.emit(XMPPEvents.JINGLE_FATAL_ERROR, session, error);
+}
+
+JingleSession.prototype.setLocalDescription = function () {
+ var self = this;
+ var newssrcs = [];
+ var session = transform.parse(this.peerconnection.localDescription.sdp);
+ session.media.forEach(function (media) {
+
+ if (media.ssrcs != null && media.ssrcs.length > 0) {
+ // TODO(gp) maybe exclude FID streams?
+ media.ssrcs.forEach(function (ssrc) {
+ if (ssrc.attribute !== 'cname') {
+ return;
+ }
+ newssrcs.push({
+ 'ssrc': ssrc.id,
+ 'type': media.type
+ });
+ });
+ }
+ else if(self.localStreamsSSRC && self.localStreamsSSRC[media.type])
+ {
+ newssrcs.push({
+ 'ssrc': self.localStreamsSSRC[media.type],
+ 'type': media.type
+ });
+ }
+
+ });
+
+ console.log('new ssrcs', newssrcs);
+
+ // Bind us as local SSRCs owner
+ if (newssrcs.length > 0) {
+ for (var i = 1; i <= newssrcs.length; i ++) {
+ var ssrc = newssrcs[i-1].ssrc;
+ var myJid = self.connection.emuc.myroomjid;
+ self.ssrcOwners[ssrc] = myJid;
+ if (newssrcs[i-1].type === 'video'){
+ self.ssrcVideoTypes[ssrc] = self.getVideoType();
+ }
+ }
+ }
+}
+
+// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
+function sendKeyframe(pc) {
+ console.log('sendkeyframe', pc.iceConnectionState);
+ if (pc.iceConnectionState !== 'connected') return; // safe...
+ var self = this;
+ pc.setRemoteDescription(
+ pc.remoteDescription,
+ function () {
+ pc.createAnswer(
+ function (modifiedAnswer) {
+ pc.setLocalDescription(
+ modifiedAnswer,
+ function () {
+ // noop
+ },
+ function (error) {
+ console.log('triggerKeyframe setLocalDescription failed', error);
+ eventEmitter.emit(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR);
+ }
+ );
+ },
+ function (error) {
+ console.log('triggerKeyframe createAnswer failed', error);
+ eventEmitter.emit(XMPPEvents.CREATE_ANSWER_ERROR);
+ }
+ );
+ },
+ function (error) {
+ console.log('triggerKeyframe setRemoteDescription failed', error);
+ eventEmitter.emit(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR);
+ }
+ );
+}
+
+
+JingleSession.prototype.remoteStreamAdded = function (data, times) {
+ var self = this;
+ var thessrc;
+ var streamId = APP.RTC.getStreamID(data.stream);
+
+ // look up an associated JID for a stream id
+ if (!streamId) {
+ console.error("No stream ID for", data.stream);
+ } else if (streamId && streamId.indexOf('mixedmslabel') === -1) {
+ // look only at a=ssrc: and _not_ at a=ssrc-group: lines
+
+ var ssrclines
+ = SDPUtil.find_lines(this.peerconnection.remoteDescription.sdp, 'a=ssrc:');
+ ssrclines = ssrclines.filter(function (line) {
+ // NOTE(gp) previously we filtered on the mslabel, but that property
+ // is not always present.
+ // return line.indexOf('mslabel:' + data.stream.label) !== -1;
+
+ if (RTCBrowserType.isTemasysPluginUsed()) {
+ return ((line.indexOf('mslabel:' + streamId) !== -1));
+ } else {
+ return ((line.indexOf('msid:' + streamId) !== -1));
+ }
+ });
+ if (ssrclines.length) {
+ thessrc = ssrclines[0].substring(7).split(' ')[0];
+
+ if (!self.ssrcOwners[thessrc]) {
+ console.error("No SSRC owner known for: " + thessrc);
+ return;
+ }
+ data.peerjid = self.ssrcOwners[thessrc];
+ data.videoType = self.ssrcVideoTypes[thessrc]
+ console.log('associated jid', self.ssrcOwners[thessrc],
+ thessrc, data.videoType);
+ } else {
+ console.error("No SSRC lines for ", streamId);
+ }
+ }
+
+ APP.RTC.createRemoteStream(data, this.sid, thessrc);
+
+ var isVideo = data.stream.getVideoTracks().length > 0;
+ // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
+ if (isVideo &&
+ data.peerjid && this.peerjid === data.peerjid &&
+ data.stream.getVideoTracks().length === 0 &&
+ APP.RTC.localVideo.getTracks().length > 0) {
+ window.setTimeout(function () {
+ sendKeyframe(self.peerconnection);
+ }, 3000);
+ }
+}
+
+module.exports = JingleSession;
+
+},{"../../service/xmpp/XMPPEvents":112,"../RTC/RTCBrowserType":8,"./SDP":54,"./SDPDiffer":55,"./SDPUtil":56,"./TraceablePeerConnection":57,"async":67,"sdp-transform":99}],54:[function(require,module,exports){
+/* jshint -W117 */
+var SDPUtil = require("./SDPUtil");
+
+// SDP STUFF
+function SDP(sdp) {
+ this.media = sdp.split('\r\nm=');
+ for (var i = 1; i < this.media.length; i++) {
+ this.media[i] = 'm=' + this.media[i];
+ if (i != this.media.length - 1) {
+ this.media[i] += '\r\n';
+ }
+ }
+ this.session = this.media.shift() + '\r\n';
+ this.raw = this.session + this.media.join('');
+}
+/**
+ * Returns map of MediaChannel mapped per channel idx.
+ */
+SDP.prototype.getMediaSsrcMap = function() {
+ var self = this;
+ var media_ssrcs = {};
+ var tmp;
+ for (var mediaindex = 0; mediaindex < self.media.length; mediaindex++) {
+ tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc:');
+ var mid = SDPUtil.parse_mid(SDPUtil.find_line(self.media[mediaindex], 'a=mid:'));
+ var media = {
+ mediaindex: mediaindex,
+ mid: mid,
+ ssrcs: {},
+ ssrcGroups: []
+ };
+ media_ssrcs[mediaindex] = media;
+ tmp.forEach(function (line) {
+ var linessrc = line.substring(7).split(' ')[0];
+ // allocate new ChannelSsrc
+ if(!media.ssrcs[linessrc]) {
+ media.ssrcs[linessrc] = {
+ ssrc: linessrc,
+ lines: []
+ };
+ }
+ media.ssrcs[linessrc].lines.push(line);
+ });
+ tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc-group:');
+ tmp.forEach(function(line){
+ var semantics = line.substr(0, idx).substr(13);
+ var ssrcs = line.substr(14 + semantics.length).split(' ');
+ if (ssrcs.length != 0) {
+ media.ssrcGroups.push({
+ semantics: semantics,
+ ssrcs: ssrcs
+ });
+ }
+ });
+ }
+ return media_ssrcs;
+};
+/**
+ * Returns true if this SDP contains given SSRC.
+ * @param ssrc the ssrc to check.
+ * @returns {boolean} true if this SDP contains given SSRC.
+ */
+SDP.prototype.containsSSRC = function(ssrc) {
+ var medias = this.getMediaSsrcMap();
+ var contains = false;
+ Object.keys(medias).forEach(function(mediaindex){
+ var media = medias[mediaindex];
+ //console.log("Check", channel, ssrc);
+ if(Object.keys(media.ssrcs).indexOf(ssrc) != -1){
+ contains = true;
+ }
+ });
+ return contains;
+};
+
+
+// remove iSAC and CN from SDP
+SDP.prototype.mangle = function () {
+ var i, j, mline, lines, rtpmap, newdesc;
+ for (i = 0; i < this.media.length; i++) {
+ lines = this.media[i].split('\r\n');
+ lines.pop(); // remove empty last element
+ mline = SDPUtil.parse_mline(lines.shift());
+ if (mline.media != 'audio')
+ continue;
+ newdesc = '';
+ mline.fmt.length = 0;
+ for (j = 0; j < lines.length; j++) {
+ if (lines[j].substr(0, 9) == 'a=rtpmap:') {
+ rtpmap = SDPUtil.parse_rtpmap(lines[j]);
+ if (rtpmap.name == 'CN' || rtpmap.name == 'ISAC')
+ continue;
+ mline.fmt.push(rtpmap.id);
+ newdesc += lines[j] + '\r\n';
+ } else {
+ newdesc += lines[j] + '\r\n';
+ }
+ }
+ this.media[i] = SDPUtil.build_mline(mline) + '\r\n';
+ this.media[i] += newdesc;
+ }
+ this.raw = this.session + this.media.join('');
+};
+
+// remove lines matching prefix from session section
+SDP.prototype.removeSessionLines = function(prefix) {
+ var self = this;
+ var lines = SDPUtil.find_lines(this.session, prefix);
+ lines.forEach(function(line) {
+ self.session = self.session.replace(line + '\r\n', '');
+ });
+ this.raw = this.session + this.media.join('');
+ return lines;
+}
+// remove lines matching prefix from a media section specified by mediaindex
+// TODO: non-numeric mediaindex could match mid
+SDP.prototype.removeMediaLines = function(mediaindex, prefix) {
+ var self = this;
+ var lines = SDPUtil.find_lines(this.media[mediaindex], prefix);
+ lines.forEach(function(line) {
+ self.media[mediaindex] = self.media[mediaindex].replace(line + '\r\n', '');
+ });
+ this.raw = this.session + this.media.join('');
+ return lines;
+}
+
+// add content's to a jingle element
+SDP.prototype.toJingle = function (elem, thecreator, ssrcs, videoType) {
+// console.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]);
+ var i, j, k, mline, ssrc, rtpmap, tmp, line, lines;
+ var self = this;
+ // new bundle plan
+ if (SDPUtil.find_line(this.session, 'a=group:')) {
+ lines = SDPUtil.find_lines(this.session, 'a=group:');
+ for (i = 0; i < lines.length; i++) {
+ tmp = lines[i].split(' ');
+ var semantics = tmp.shift().substr(8);
+ elem.c('group', {xmlns: 'urn:xmpp:jingle:apps:grouping:0', semantics:semantics});
+ for (j = 0; j < tmp.length; j++) {
+ elem.c('content', {name: tmp[j]}).up();
+ }
+ elem.up();
+ }
+ }
+ for (i = 0; i < this.media.length; i++) {
+ mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]);
+ if (!(mline.media === 'audio' ||
+ mline.media === 'video' ||
+ mline.media === 'application'))
+ {
+ continue;
+ }
+ if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) {
+ ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first
+ } else {
+ if(ssrcs && ssrcs[mline.media])
+ {
+ ssrc = ssrcs[mline.media];
+ }
+ else
+ ssrc = false;
+ }
+
+ elem.c('content', {creator: thecreator, name: mline.media});
+ if (SDPUtil.find_line(this.media[i], 'a=mid:')) {
+ // prefer identifier from a=mid if present
+ var mid = SDPUtil.parse_mid(SDPUtil.find_line(this.media[i], 'a=mid:'));
+ elem.attrs({ name: mid });
+ }
+
+ if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length)
+ {
+ elem.c('description',
+ {xmlns: 'urn:xmpp:jingle:apps:rtp:1',
+ media: mline.media });
+ if (ssrc) {
+ elem.attrs({ssrc: ssrc});
+ }
+ for (j = 0; j < mline.fmt.length; j++) {
+ rtpmap = SDPUtil.find_line(this.media[i], 'a=rtpmap:' + mline.fmt[j]);
+ elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
+ // put any 'a=fmtp:' + mline.fmt[j] lines into
+ if (SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])) {
+ tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j]));
+ for (k = 0; k < tmp.length; k++) {
+ elem.c('parameter', tmp[k]).up();
+ }
+ }
+ this.RtcpFbToJingle(i, elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb
+
+ elem.up();
+ }
+ if (SDPUtil.find_line(this.media[i], 'a=crypto:', this.session)) {
+ elem.c('encryption', {required: 1});
+ var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session);
+ crypto.forEach(function(line) {
+ elem.c('crypto', SDPUtil.parse_crypto(line)).up();
+ });
+ elem.up(); // end of encryption
+ }
+
+ if (ssrc) {
+ // new style mapping
+ elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
+ // FIXME: group by ssrc and support multiple different ssrcs
+ var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:');
+ if(ssrclines.length > 0) {
+ ssrclines.forEach(function (line) {
+ idx = line.indexOf(' ');
+ var linessrc = line.substr(0, idx).substr(7);
+ if (linessrc != ssrc) {
+ elem.up();
+ ssrc = linessrc;
+ elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
+ }
+ var kv = line.substr(idx + 1);
+ elem.c('parameter');
+ if (kv.indexOf(':') == -1) {
+ elem.attrs({ name: kv });
+ } else {
+ var k = kv.split(':', 2)[0];
+ elem.attrs({ name: k });
+
+ var v = kv.split(':', 2)[1];
+ v = SDPUtil.filter_special_chars(v);
+ elem.attrs({ value: v });
+ }
+ elem.up();
+ });
+ }
+ else
+ {
+ elem.up();
+ elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
+ elem.c('parameter');
+ elem.attrs({name: "cname", value:Math.random().toString(36).substring(7)});
+ elem.up();
+ var msid = null;
+ if(mline.media == "audio")
+ {
+ msid = APP.RTC.localAudio.getId();
+ }
+ else
+ {
+ msid = APP.RTC.localVideo.getId();
+ }
+ if(msid != null)
+ {
+ msid = SDPUtil.filter_special_chars(msid);
+ elem.c('parameter');
+ elem.attrs({name: "msid", value:msid});
+ elem.up();
+ elem.c('parameter');
+ elem.attrs({name: "mslabel", value:msid});
+ elem.up();
+ elem.c('parameter');
+ elem.attrs({name: "label", value:msid});
+ elem.up();
+ }
+ }
+ // Video type
+ if (videoType && mline.media == "video") {
+ elem.c('ssrc-info',
+ {
+ xmlns: 'http://jitsi.org/jitmeet',
+ 'video-type': videoType
+ }).up();
+ }
+ elem.up();
+
+ // XEP-0339 handle ssrc-group attributes
+ var ssrc_group_lines = SDPUtil.find_lines(this.media[i], 'a=ssrc-group:');
+ ssrc_group_lines.forEach(function(line) {
+ idx = line.indexOf(' ');
+ var semantics = line.substr(0, idx).substr(13);
+ var ssrcs = line.substr(14 + semantics.length).split(' ');
+ if (ssrcs.length != 0) {
+ elem.c('ssrc-group', { semantics: semantics, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
+ ssrcs.forEach(function(ssrc) {
+ elem.c('source', { ssrc: ssrc })
+ .up();
+ });
+ elem.up();
+ }
+ });
+ }
+
+ if (SDPUtil.find_line(this.media[i], 'a=rtcp-mux')) {
+ elem.c('rtcp-mux').up();
+ }
+
+ // XEP-0293 -- map a=rtcp-fb:*
+ this.RtcpFbToJingle(i, elem, '*');
+
+ // XEP-0294
+ if (SDPUtil.find_line(this.media[i], 'a=extmap:')) {
+ lines = SDPUtil.find_lines(this.media[i], 'a=extmap:');
+ for (j = 0; j < lines.length; j++) {
+ tmp = SDPUtil.parse_extmap(lines[j]);
+ elem.c('rtp-hdrext', { xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0',
+ uri: tmp.uri,
+ id: tmp.value });
+ if (tmp.hasOwnProperty('direction')) {
+ switch (tmp.direction) {
+ case 'sendonly':
+ elem.attrs({senders: 'responder'});
+ break;
+ case 'recvonly':
+ elem.attrs({senders: 'initiator'});
+ break;
+ case 'sendrecv':
+ elem.attrs({senders: 'both'});
+ break;
+ case 'inactive':
+ elem.attrs({senders: 'none'});
+ break;
+ }
+ }
+ // TODO: handle params
+ elem.up();
+ }
+ }
+ elem.up(); // end of description
+ }
+
+ // map ice-ufrag/pwd, dtls fingerprint, candidates
+ this.TransportToJingle(i, elem);
+
+ if (SDPUtil.find_line(this.media[i], 'a=sendrecv', this.session)) {
+ elem.attrs({senders: 'both'});
+ } else if (SDPUtil.find_line(this.media[i], 'a=sendonly', this.session)) {
+ elem.attrs({senders: 'initiator'});
+ } else if (SDPUtil.find_line(this.media[i], 'a=recvonly', this.session)) {
+ elem.attrs({senders: 'responder'});
+ } else if (SDPUtil.find_line(this.media[i], 'a=inactive', this.session)) {
+ elem.attrs({senders: 'none'});
+ }
+ if (mline.port == '0') {
+ // estos hack to reject an m-line
+ elem.attrs({senders: 'rejected'});
+ }
+ elem.up(); // end of content
+ }
+ elem.up();
+ return elem;
+};
+
+SDP.prototype.TransportToJingle = function (mediaindex, elem) {
+ var i = mediaindex;
+ var tmp;
+ var self = this;
+ elem.c('transport');
+
+ // XEP-0343 DTLS/SCTP
+ if (SDPUtil.find_line(this.media[mediaindex], 'a=sctpmap:').length)
+ {
+ var sctpmap = SDPUtil.find_line(
+ this.media[i], 'a=sctpmap:', self.session);
+ if (sctpmap)
+ {
+ var sctpAttrs = SDPUtil.parse_sctpmap(sctpmap);
+ elem.c('sctpmap',
+ {
+ xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1',
+ number: sctpAttrs[0], /* SCTP port */
+ protocol: sctpAttrs[1], /* protocol */
+ });
+ // Optional stream count attribute
+ if (sctpAttrs.length > 2)
+ elem.attrs({ streams: sctpAttrs[2]});
+ elem.up();
+ }
+ }
+ // XEP-0320
+ var fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session);
+ fingerprints.forEach(function(line) {
+ tmp = SDPUtil.parse_fingerprint(line);
+ tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0';
+ elem.c('fingerprint').t(tmp.fingerprint);
+ delete tmp.fingerprint;
+ line = SDPUtil.find_line(self.media[mediaindex], 'a=setup:', self.session);
+ if (line) {
+ tmp.setup = line.substr(8);
+ }
+ elem.attrs(tmp);
+ elem.up(); // end of fingerprint
+ });
+ tmp = SDPUtil.iceparams(this.media[mediaindex], this.session);
+ if (tmp) {
+ tmp.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
+ elem.attrs(tmp);
+ // XEP-0176
+ if (SDPUtil.find_line(this.media[mediaindex], 'a=candidate:', this.session)) { // add any a=candidate lines
+ var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=candidate:', this.session);
+ lines.forEach(function (line) {
+ elem.c('candidate', SDPUtil.candidateToJingle(line)).up();
+ });
+ }
+ }
+ elem.up(); // end of transport
+}
+
+SDP.prototype.RtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP-0293
+ var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=rtcp-fb:' + payloadtype);
+ lines.forEach(function (line) {
+ var tmp = SDPUtil.parse_rtcpfb(line);
+ if (tmp.type == 'trr-int') {
+ elem.c('rtcp-fb-trr-int', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', value: tmp.params[0]});
+ elem.up();
+ } else {
+ elem.c('rtcp-fb', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', type: tmp.type});
+ if (tmp.params.length > 0) {
+ elem.attrs({'subtype': tmp.params[0]});
+ }
+ elem.up();
+ }
+ });
+};
+
+SDP.prototype.RtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293
+ var media = '';
+ var tmp = elem.find('>rtcp-fb-trr-int[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
+ if (tmp.length) {
+ media += 'a=rtcp-fb:' + '*' + ' ' + 'trr-int' + ' ';
+ if (tmp.attr('value')) {
+ media += tmp.attr('value');
+ } else {
+ media += '0';
+ }
+ media += '\r\n';
+ }
+ tmp = elem.find('>rtcp-fb[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
+ tmp.each(function () {
+ media += 'a=rtcp-fb:' + payloadtype + ' ' + $(this).attr('type');
+ if ($(this).attr('subtype')) {
+ media += ' ' + $(this).attr('subtype');
+ }
+ media += '\r\n';
+ });
+ return media;
+};
+
+// construct an SDP from a jingle stanza
+SDP.prototype.fromJingle = function (jingle) {
+ var self = this;
+ this.raw = 'v=0\r\n' +
+ 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
+ 's=-\r\n' +
+ 't=0 0\r\n';
+ // http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04#section-8
+ if ($(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').length) {
+ $(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').each(function (idx, group) {
+ var contents = $(group).find('>content').map(function (idx, content) {
+ return content.getAttribute('name');
+ }).get();
+ if (contents.length > 0) {
+ self.raw += 'a=group:' + (group.getAttribute('semantics') || group.getAttribute('type')) + ' ' + contents.join(' ') + '\r\n';
+ }
+ });
+ }
+
+ this.session = this.raw;
+ jingle.find('>content').each(function () {
+ var m = self.jingle2media($(this));
+ self.media.push(m);
+ });
+
+ // reconstruct msid-semantic -- apparently not necessary
+ /*
+ var msid = SDPUtil.parse_ssrc(this.raw);
+ if (msid.hasOwnProperty('mslabel')) {
+ this.session += "a=msid-semantic: WMS " + msid.mslabel + "\r\n";
+ }
+ */
+
+ this.raw = this.session + this.media.join('');
+};
+
+// translate a jingle content element into an an SDP media part
+SDP.prototype.jingle2media = function (content) {
+ var media = '',
+ desc = content.find('description'),
+ ssrc = desc.attr('ssrc'),
+ self = this,
+ tmp;
+ var sctp = content.find(
+ '>transport>sctpmap[xmlns="urn:xmpp:jingle:transports:dtls-sctp:1"]');
+
+ tmp = { media: desc.attr('media') };
+ tmp.port = '1';
+ if (content.attr('senders') == 'rejected') {
+ // estos hack to reject an m-line.
+ tmp.port = '0';
+ }
+ if (content.find('>transport>fingerprint').length || desc.find('encryption').length) {
+ if (sctp.length)
+ tmp.proto = 'DTLS/SCTP';
+ else
+ tmp.proto = 'RTP/SAVPF';
+ } else {
+ tmp.proto = 'RTP/AVPF';
+ }
+ if (!sctp.length)
+ {
+ tmp.fmt = desc.find('payload-type').map(
+ function () { return this.getAttribute('id'); }).get();
+ media += SDPUtil.build_mline(tmp) + '\r\n';
+ }
+ else
+ {
+ media += 'm=application 1 DTLS/SCTP ' + sctp.attr('number') + '\r\n';
+ media += 'a=sctpmap:' + sctp.attr('number') +
+ ' ' + sctp.attr('protocol');
+
+ var streamCount = sctp.attr('streams');
+ if (streamCount)
+ media += ' ' + streamCount + '\r\n';
+ else
+ media += '\r\n';
+ }
+
+ media += 'c=IN IP4 0.0.0.0\r\n';
+ if (!sctp.length)
+ media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n';
+ tmp = content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
+ if (tmp.length) {
+ if (tmp.attr('ufrag')) {
+ media += SDPUtil.build_iceufrag(tmp.attr('ufrag')) + '\r\n';
+ }
+ if (tmp.attr('pwd')) {
+ media += SDPUtil.build_icepwd(tmp.attr('pwd')) + '\r\n';
+ }
+ tmp.find('>fingerprint').each(function () {
+ // FIXME: check namespace at some point
+ media += 'a=fingerprint:' + this.getAttribute('hash');
+ media += ' ' + $(this).text();
+ media += '\r\n';
+ if (this.getAttribute('setup')) {
+ media += 'a=setup:' + this.getAttribute('setup') + '\r\n';
+ }
+ });
+ }
+ switch (content.attr('senders')) {
+ case 'initiator':
+ media += 'a=sendonly\r\n';
+ break;
+ case 'responder':
+ media += 'a=recvonly\r\n';
+ break;
+ case 'none':
+ media += 'a=inactive\r\n';
+ break;
+ case 'both':
+ media += 'a=sendrecv\r\n';
+ break;
+ }
+ media += 'a=mid:' + content.attr('name') + '\r\n';
+
+ //
+ // see http://code.google.com/p/libjingle/issues/detail?id=309 -- no spec though
+ // and http://mail.jabber.org/pipermail/jingle/2011-December/001761.html
+ if (desc.find('rtcp-mux').length) {
+ media += 'a=rtcp-mux\r\n';
+ }
+
+ if (desc.find('encryption').length) {
+ desc.find('encryption>crypto').each(function () {
+ media += 'a=crypto:' + this.getAttribute('tag');
+ media += ' ' + this.getAttribute('crypto-suite');
+ media += ' ' + this.getAttribute('key-params');
+ if (this.getAttribute('session-params')) {
+ media += ' ' + this.getAttribute('session-params');
+ }
+ media += '\r\n';
+ });
+ }
+ desc.find('payload-type').each(function () {
+ media += SDPUtil.build_rtpmap(this) + '\r\n';
+ if ($(this).find('>parameter').length) {
+ media += 'a=fmtp:' + this.getAttribute('id') + ' ';
+ media += $(this).find('parameter').map(function () { return (this.getAttribute('name') ? (this.getAttribute('name') + '=') : '') + this.getAttribute('value'); }).get().join('; ');
+ media += '\r\n';
+ }
+ // xep-0293
+ media += self.RtcpFbFromJingle($(this), this.getAttribute('id'));
+ });
+
+ // xep-0293
+ media += self.RtcpFbFromJingle(desc, '*');
+
+ // xep-0294
+ tmp = desc.find('>rtp-hdrext[xmlns="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"]');
+ tmp.each(function () {
+ media += 'a=extmap:' + this.getAttribute('id') + ' ' + this.getAttribute('uri') + '\r\n';
+ });
+
+ content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]>candidate').each(function () {
+ media += SDPUtil.candidateFromJingle(this);
+ });
+
+ // XEP-0339 handle ssrc-group attributes
+ tmp = content.find('description>ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
+ var semantics = this.getAttribute('semantics');
+ var ssrcs = $(this).find('>source').map(function() {
+ return this.getAttribute('ssrc');
+ }).get();
+
+ if (ssrcs.length != 0) {
+ media += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
+ }
+ });
+
+ tmp = content.find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
+ tmp.each(function () {
+ var ssrc = this.getAttribute('ssrc');
+ $(this).find('>parameter').each(function () {
+ var name = this.getAttribute('name');
+ var value = this.getAttribute('value');
+ value = SDPUtil.filter_special_chars(value);
+ media += 'a=ssrc:' + ssrc + ' ' + name;
+ if (value && value.length)
+ media += ':' + value;
+ media += '\r\n';
+ });
+ });
+
+ return media;
+};
+
+
+module.exports = SDP;
+
+
+},{"./SDPUtil":56}],55:[function(require,module,exports){
+
+var SDPUtil = require("./SDPUtil");
+
+function SDPDiffer(mySDP, otherSDP) {
+ this.mySDP = mySDP;
+ this.otherSDP = otherSDP;
+}
+
+/**
+ * Returns map of MediaChannel that contains only media not contained in otherSdp. Mapped by channel idx.
+ * @param otherSdp the other SDP to check ssrc with.
+ */
+SDPDiffer.prototype.getNewMedia = function() {
+
+ // this could be useful in Array.prototype.
+ function arrayEquals(array) {
+ // if the other array is a falsy value, return
+ if (!array)
+ return false;
+
+ // compare lengths - can save a lot of time
+ if (this.length != array.length)
+ return false;
+
+ for (var i = 0, l=this.length; i < l; i++) {
+ // Check if we have nested arrays
+ if (this[i] instanceof Array && array[i] instanceof Array) {
+ // recurse into the nested arrays
+ if (!this[i].equals(array[i]))
+ return false;
+ }
+ else if (this[i] != array[i]) {
+ // Warning - two different object instances will never be equal: {x:20} != {x:20}
+ return false;
+ }
+ }
+ return true;
+ }
+
+ var myMedias = this.mySDP.getMediaSsrcMap();
+ var othersMedias = this.otherSDP.getMediaSsrcMap();
+ var newMedia = {};
+ Object.keys(othersMedias).forEach(function(othersMediaIdx) {
+ var myMedia = myMedias[othersMediaIdx];
+ var othersMedia = othersMedias[othersMediaIdx];
+ if(!myMedia && othersMedia) {
+ // Add whole channel
+ newMedia[othersMediaIdx] = othersMedia;
+ return;
+ }
+ // Look for new ssrcs accross the channel
+ Object.keys(othersMedia.ssrcs).forEach(function(ssrc) {
+ if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
+ // Allocate channel if we've found ssrc that doesn't exist in our channel
+ if(!newMedia[othersMediaIdx]){
+ newMedia[othersMediaIdx] = {
+ mediaindex: othersMedia.mediaindex,
+ mid: othersMedia.mid,
+ ssrcs: {},
+ ssrcGroups: []
+ };
+ }
+ newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
+ }
+ });
+
+ // Look for new ssrc groups across the channels
+ othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){
+
+ // try to match the other ssrc-group with an ssrc-group of ours
+ var matched = false;
+ for (var i = 0; i < myMedia.ssrcGroups.length; i++) {
+ var mySsrcGroup = myMedia.ssrcGroups[i];
+ if (otherSsrcGroup.semantics == mySsrcGroup.semantics
+ && arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
+
+ matched = true;
+ break;
+ }
+ }
+
+ if (!matched) {
+ // Allocate channel if we've found an ssrc-group that doesn't
+ // exist in our channel
+
+ if(!newMedia[othersMediaIdx]){
+ newMedia[othersMediaIdx] = {
+ mediaindex: othersMedia.mediaindex,
+ mid: othersMedia.mid,
+ ssrcs: {},
+ ssrcGroups: []
+ };
+ }
+ newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
+ }
+ });
+ });
+ return newMedia;
+};
+
+/**
+ * Sends SSRC update IQ.
+ * @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
+ * @param sid session identifier that will be put into the IQ.
+ * @param initiator initiator identifier.
+ * @param toJid destination Jid
+ * @param isAdd indicates if this is remove or add operation.
+ */
+SDPDiffer.prototype.toJingle = function(modify, videoType) {
+ var sdpMediaSsrcs = this.getNewMedia();
+ var self = this;
+
+ // FIXME: only announce video ssrcs since we mix audio and dont need
+ // the audio ssrcs therefore
+ var modified = false;
+ Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){
+ modified = true;
+ var media = sdpMediaSsrcs[mediaindex];
+ modify.c('content', {name: media.mid});
+
+ modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid});
+ // FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
+ // generate sources from lines
+ Object.keys(media.ssrcs).forEach(function(ssrcNum) {
+ var mediaSsrc = media.ssrcs[ssrcNum];
+ modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
+ modify.attrs({ssrc: mediaSsrc.ssrc});
+ // iterate over ssrc lines
+ mediaSsrc.lines.forEach(function (line) {
+ var idx = line.indexOf(' ');
+ var kv = line.substr(idx + 1);
+ modify.c('parameter');
+ if (kv.indexOf(':') == -1) {
+ modify.attrs({ name: kv });
+ } else {
+ var nv = kv.split(':', 2);
+ var name = nv[0];
+ var value = SDPUtil.filter_special_chars(nv[1]);
+ modify.attrs({ name: name });
+ modify.attrs({ value: value });
+ }
+ modify.up(); // end of parameter
+ });
+ // indicate video type
+ if (videoType && media.mid == 'video') {
+ modify.c('ssrc-info',
+ {
+ xmlns: 'http://jitsi.org/jitmeet',
+ 'video-type': videoType
+ })
+ .up();
+ }
+ modify.up(); // end of source
+ });
+
+ // generate source groups from lines
+ media.ssrcGroups.forEach(function(ssrcGroup) {
+ if (ssrcGroup.ssrcs.length != 0) {
+
+ modify.c('ssrc-group', {
+ semantics: ssrcGroup.semantics,
+ xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
+ });
+
+ ssrcGroup.ssrcs.forEach(function (ssrc) {
+ modify.c('source', { ssrc: ssrc })
+ .up(); // end of source
+ });
+ modify.up(); // end of ssrc-group
+ }
+ });
+
+ modify.up(); // end of description
+ modify.up(); // end of content
+ });
+
+ return modified;
+};
+
+module.exports = SDPDiffer;
+},{"./SDPUtil":56}],56:[function(require,module,exports){
+SDPUtil = {
+ filter_special_chars: function (text) {
+ return text.replace(/[\\\/\{,\}\+]/g, "");
+ },
+ iceparams: function (mediadesc, sessiondesc) {
+ var data = null;
+ if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
+ SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
+ data = {
+ ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
+ pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
+ };
+ }
+ return data;
+ },
+ parse_iceufrag: function (line) {
+ return line.substring(12);
+ },
+ build_iceufrag: function (frag) {
+ return 'a=ice-ufrag:' + frag;
+ },
+ parse_icepwd: function (line) {
+ return line.substring(10);
+ },
+ build_icepwd: function (pwd) {
+ return 'a=ice-pwd:' + pwd;
+ },
+ parse_mid: function (line) {
+ return line.substring(6);
+ },
+ parse_mline: function (line) {
+ var parts = line.substring(2).split(' '),
+ data = {};
+ data.media = parts.shift();
+ data.port = parts.shift();
+ data.proto = parts.shift();
+ if (parts[parts.length - 1] === '') { // trailing whitespace
+ parts.pop();
+ }
+ data.fmt = parts;
+ return data;
+ },
+ build_mline: function (mline) {
+ return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
+ },
+ parse_rtpmap: function (line) {
+ var parts = line.substring(9).split(' '),
+ data = {};
+ data.id = parts.shift();
+ parts = parts[0].split('/');
+ data.name = parts.shift();
+ data.clockrate = parts.shift();
+ data.channels = parts.length ? parts.shift() : '1';
+ return data;
+ },
+ /**
+ * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
+ * @param line eg. "a=sctpmap:5000 webrtc-datachannel"
+ * @returns [SCTP port number, protocol, streams]
+ */
+ parse_sctpmap: function (line)
+ {
+ var parts = line.substring(10).split(' ');
+ var sctpPort = parts[0];
+ var protocol = parts[1];
+ // Stream count is optional
+ var streamCount = parts.length > 2 ? parts[2] : null;
+ return [sctpPort, protocol, streamCount];// SCTP port
+ },
+ build_rtpmap: function (el) {
+ var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
+ if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
+ line += '/' + el.getAttribute('channels');
+ }
+ return line;
+ },
+ parse_crypto: function (line) {
+ var parts = line.substring(9).split(' '),
+ data = {};
+ data.tag = parts.shift();
+ data['crypto-suite'] = parts.shift();
+ data['key-params'] = parts.shift();
+ if (parts.length) {
+ data['session-params'] = parts.join(' ');
+ }
+ return data;
+ },
+ parse_fingerprint: function (line) { // RFC 4572
+ var parts = line.substring(14).split(' '),
+ data = {};
+ data.hash = parts.shift();
+ data.fingerprint = parts.shift();
+ // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
+ return data;
+ },
+ parse_fmtp: function (line) {
+ var parts = line.split(' '),
+ i, key, value,
+ data = [];
+ parts.shift();
+ parts = parts.join(' ').split(';');
+ for (i = 0; i < parts.length; i++) {
+ key = parts[i].split('=')[0];
+ while (key.length && key[0] == ' ') {
+ key = key.substring(1);
+ }
+ value = parts[i].split('=')[1];
+ if (key && value) {
+ data.push({name: key, value: value});
+ } else if (key) {
+ // rfc 4733 (DTMF) style stuff
+ data.push({name: '', value: key});
+ }
+ }
+ return data;
+ },
+ parse_icecandidate: function (line) {
+ var candidate = {},
+ elems = line.split(' ');
+ candidate.foundation = elems[0].substring(12);
+ candidate.component = elems[1];
+ candidate.protocol = elems[2].toLowerCase();
+ candidate.priority = elems[3];
+ candidate.ip = elems[4];
+ candidate.port = elems[5];
+ // elems[6] => "typ"
+ candidate.type = elems[7];
+ candidate.generation = 0; // default value, may be overwritten below
+ for (var i = 8; i < elems.length; i += 2) {
+ switch (elems[i]) {
+ case 'raddr':
+ candidate['rel-addr'] = elems[i + 1];
+ break;
+ case 'rport':
+ candidate['rel-port'] = elems[i + 1];
+ break;
+ case 'generation':
+ candidate.generation = elems[i + 1];
+ break;
+ case 'tcptype':
+ candidate.tcptype = elems[i + 1];
+ break;
+ default: // TODO
+ console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
+ }
+ }
+ candidate.network = '1';
+ candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
+ return candidate;
+ },
+ build_icecandidate: function (cand) {
+ var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
+ line += ' ';
+ switch (cand.type) {
+ case 'srflx':
+ case 'prflx':
+ case 'relay':
+ if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
+ line += 'raddr';
+ line += ' ';
+ line += cand['rel-addr'];
+ line += ' ';
+ line += 'rport';
+ line += ' ';
+ line += cand['rel-port'];
+ line += ' ';
+ }
+ break;
+ }
+ if (cand.hasOwnAttribute('tcptype')) {
+ line += 'tcptype';
+ line += ' ';
+ line += cand.tcptype;
+ line += ' ';
+ }
+ line += 'generation';
+ line += ' ';
+ line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
+ return line;
+ },
+ parse_ssrc: function (desc) {
+ // proprietary mapping of a=ssrc lines
+ // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
+ // and parse according to that
+ var lines = desc.split('\r\n'),
+ data = {};
+ for (var i = 0; i < lines.length; i++) {
+ if (lines[i].substring(0, 7) == 'a=ssrc:') {
+ var idx = lines[i].indexOf(' ');
+ data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
+ }
+ }
+ return data;
+ },
+ parse_rtcpfb: function (line) {
+ var parts = line.substr(10).split(' ');
+ var data = {};
+ data.pt = parts.shift();
+ data.type = parts.shift();
+ data.params = parts;
+ return data;
+ },
+ parse_extmap: function (line) {
+ var parts = line.substr(9).split(' ');
+ var data = {};
+ data.value = parts.shift();
+ if (data.value.indexOf('/') != -1) {
+ data.direction = data.value.substr(data.value.indexOf('/') + 1);
+ data.value = data.value.substr(0, data.value.indexOf('/'));
+ } else {
+ data.direction = 'both';
+ }
+ data.uri = parts.shift();
+ data.params = parts;
+ return data;
+ },
+ find_line: function (haystack, needle, sessionpart) {
+ var lines = haystack.split('\r\n');
+ for (var i = 0; i < lines.length; i++) {
+ if (lines[i].substring(0, needle.length) == needle) {
+ return lines[i];
+ }
+ }
+ if (!sessionpart) {
+ return false;
+ }
+ // search session part
+ lines = sessionpart.split('\r\n');
+ for (var j = 0; j < lines.length; j++) {
+ if (lines[j].substring(0, needle.length) == needle) {
+ return lines[j];
+ }
+ }
+ return false;
+ },
+ find_lines: function (haystack, needle, sessionpart) {
+ var lines = haystack.split('\r\n'),
+ needles = [];
+ for (var i = 0; i < lines.length; i++) {
+ if (lines[i].substring(0, needle.length) == needle)
+ needles.push(lines[i]);
+ }
+ if (needles.length || !sessionpart) {
+ return needles;
+ }
+ // search session part
+ lines = sessionpart.split('\r\n');
+ for (var j = 0; j < lines.length; j++) {
+ if (lines[j].substring(0, needle.length) == needle) {
+ needles.push(lines[j]);
+ }
+ }
+ return needles;
+ },
+ candidateToJingle: function (line) {
+ // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
+ //
+ if (line.indexOf('candidate:') === 0) {
+ line = 'a=' + line;
+ } else if (line.substring(0, 12) != 'a=candidate:') {
+ console.log('parseCandidate called with a line that is not a candidate line');
+ console.log(line);
+ return null;
+ }
+ if (line.substring(line.length - 2) == '\r\n') // chomp it
+ line = line.substring(0, line.length - 2);
+ var candidate = {},
+ elems = line.split(' '),
+ i;
+ if (elems[6] != 'typ') {
+ console.log('did not find typ in the right place');
+ console.log(line);
+ return null;
+ }
+ candidate.foundation = elems[0].substring(12);
+ candidate.component = elems[1];
+ candidate.protocol = elems[2].toLowerCase();
+ candidate.priority = elems[3];
+ candidate.ip = elems[4];
+ candidate.port = elems[5];
+ // elems[6] => "typ"
+ candidate.type = elems[7];
+
+ candidate.generation = '0'; // default, may be overwritten below
+ for (i = 8; i < elems.length; i += 2) {
+ switch (elems[i]) {
+ case 'raddr':
+ candidate['rel-addr'] = elems[i + 1];
+ break;
+ case 'rport':
+ candidate['rel-port'] = elems[i + 1];
+ break;
+ case 'generation':
+ candidate.generation = elems[i + 1];
+ break;
+ case 'tcptype':
+ candidate.tcptype = elems[i + 1];
+ break;
+ default: // TODO
+ console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
+ }
+ }
+ candidate.network = '1';
+ candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
+ return candidate;
+ },
+ candidateFromJingle: function (cand) {
+ var line = 'a=candidate:';
+ line += cand.getAttribute('foundation');
+ line += ' ';
+ line += cand.getAttribute('component');
+ line += ' ';
+ line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
+ line += ' ';
+ line += cand.getAttribute('priority');
+ line += ' ';
+ line += cand.getAttribute('ip');
+ line += ' ';
+ line += cand.getAttribute('port');
+ line += ' ';
+ line += 'typ';
+ line += ' ' + cand.getAttribute('type');
+ line += ' ';
+ switch (cand.getAttribute('type')) {
+ case 'srflx':
+ case 'prflx':
+ case 'relay':
+ if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
+ line += 'raddr';
+ line += ' ';
+ line += cand.getAttribute('rel-addr');
+ line += ' ';
+ line += 'rport';
+ line += ' ';
+ line += cand.getAttribute('rel-port');
+ line += ' ';
+ }
+ break;
+ }
+ if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
+ line += 'tcptype';
+ line += ' ';
+ line += cand.getAttribute('tcptype');
+ line += ' ';
+ }
+ line += 'generation';
+ line += ' ';
+ line += cand.getAttribute('generation') || '0';
+ return line + '\r\n';
+ }
+};
+module.exports = SDPUtil;
+},{}],57:[function(require,module,exports){
+var RTC = require('../RTC/RTC');
+var RTCBrowserType = require("../RTC/RTCBrowserType.js");
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+
+function TraceablePeerConnection(ice_config, constraints, session) {
+ var self = this;
+ var RTCPeerconnectionType = null;
+ if (RTCBrowserType.isFirefox()) {
+ RTCPeerconnectionType = mozRTCPeerConnection;
+ } else if (RTCBrowserType.isTemasysPluginUsed()) {
+ RTCPeerconnectionType = RTCPeerConnection;
+ } else {
+ RTCPeerconnectionType = webkitRTCPeerConnection;
+ }
+ this.peerconnection = new RTCPeerconnectionType(ice_config, constraints);
+ this.updateLog = [];
+ this.stats = {};
+ this.statsinterval = null;
+ this.maxstats = 0; // limit to 300 values, i.e. 5 minutes; set to 0 to disable
+ var Interop = require('sdp-interop').Interop;
+ this.interop = new Interop();
+ var Simulcast = require('sdp-simulcast');
+ this.simulcast = new Simulcast({numOfLayers: 3, explodeRemoteSimulcast: false});
+
+ // override as desired
+ this.trace = function (what, info) {
+ /*console.warn('WTRACE', what, info);
+ if (info && RTCBrowserType.isIExplorer()) {
+ if (info.length > 1024) {
+ console.warn('WTRACE', what, info.substr(1024));
+ }
+ if (info.length > 2048) {
+ console.warn('WTRACE', what, info.substr(2048));
+ }
+ }*/
+ self.updateLog.push({
+ time: new Date(),
+ type: what,
+ value: info || ""
+ });
+ };
+ this.onicecandidate = null;
+ this.peerconnection.onicecandidate = function (event) {
+ // FIXME: this causes stack overflow with Temasys Plugin
+ if (!RTCBrowserType.isTemasysPluginUsed())
+ self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
+ if (self.onicecandidate !== null) {
+ self.onicecandidate(event);
+ }
+ };
+ this.onaddstream = null;
+ this.peerconnection.onaddstream = function (event) {
+ self.trace('onaddstream', event.stream.id);
+ if (self.onaddstream !== null) {
+ self.onaddstream(event);
+ }
+ };
+ this.onremovestream = null;
+ this.peerconnection.onremovestream = function (event) {
+ self.trace('onremovestream', event.stream.id);
+ if (self.onremovestream !== null) {
+ self.onremovestream(event);
+ }
+ };
+ this.onsignalingstatechange = null;
+ this.peerconnection.onsignalingstatechange = function (event) {
+ self.trace('onsignalingstatechange', self.signalingState);
+ if (self.onsignalingstatechange !== null) {
+ self.onsignalingstatechange(event);
+ }
+ };
+ this.oniceconnectionstatechange = null;
+ this.peerconnection.oniceconnectionstatechange = function (event) {
+ self.trace('oniceconnectionstatechange', self.iceConnectionState);
+ if (self.oniceconnectionstatechange !== null) {
+ self.oniceconnectionstatechange(event);
+ }
+ };
+ this.onnegotiationneeded = null;
+ this.peerconnection.onnegotiationneeded = function (event) {
+ self.trace('onnegotiationneeded');
+ if (self.onnegotiationneeded !== null) {
+ self.onnegotiationneeded(event);
+ }
+ };
+ self.ondatachannel = null;
+ this.peerconnection.ondatachannel = function (event) {
+ self.trace('ondatachannel', event);
+ if (self.ondatachannel !== null) {
+ self.ondatachannel(event);
+ }
+ };
+ // XXX: do all non-firefox browsers which we support also support this?
+ if (!RTCBrowserType.isFirefox() && this.maxstats) {
+ this.statsinterval = window.setInterval(function() {
+ self.peerconnection.getStats(function(stats) {
+ var results = stats.result();
+ for (var i = 0; i < results.length; ++i) {
+ //console.log(results[i].type, results[i].id, results[i].names())
+ var now = new Date();
+ results[i].names().forEach(function (name) {
+ var id = results[i].id + '-' + name;
+ if (!self.stats[id]) {
+ self.stats[id] = {
+ startTime: now,
+ endTime: now,
+ values: [],
+ times: []
+ };
+ }
+ self.stats[id].values.push(results[i].stat(name));
+ self.stats[id].times.push(now.getTime());
+ if (self.stats[id].values.length > self.maxstats) {
+ self.stats[id].values.shift();
+ self.stats[id].times.shift();
+ }
+ self.stats[id].endTime = now;
+ });
+ }
+ });
+
+ }, 1000);
+ }
+};
+
+dumpSDP = function(description) {
+ if (typeof description === 'undefined' || description == null) {
+ return '';
+ }
+
+ return 'type: ' + description.type + '\r\n' + description.sdp;
+};
+
+/**
+ * Takes a SessionDescription object and returns a "normalized" version.
+ * Currently it only takes care of ordering the a=ssrc lines.
+ */
+normalizePlanB = function(desc) {
+ if (typeof desc !== 'object' || desc === null ||
+ typeof desc.sdp !== 'string') {
+ console.warn('An empty description was passed as an argument.');
+ return desc;
+ }
+
+ var transform = require('sdp-transform');
+ var session = transform.parse(desc.sdp);
+
+ if (typeof session !== 'undefined' && typeof session.media !== 'undefined' &&
+ Array.isArray(session.media)) {
+ session.media.forEach(function (mLine) {
+
+ // Chrome appears to be picky about the order in which a=ssrc lines
+ // are listed in an m-line when rtx is enabled (and thus there are
+ // a=ssrc-group lines with FID semantics). Specifically if we have
+ // "a=ssrc-group:FID S1 S2" and the "a=ssrc:S2" lines appear before
+ // the "a=ssrc:S1" lines, SRD fails.
+ // So, put SSRC which appear as the first SSRC in an FID ssrc-group
+ // first.
+ var firstSsrcs = [];
+ var newSsrcLines = [];
+
+ if (typeof mLine.ssrcGroups !== 'undefined' && Array.isArray(mLine.ssrcGroups)) {
+ mLine.ssrcGroups.forEach(function (group) {
+ if (typeof group.semantics !== 'undefined' &&
+ group.semantics === 'FID') {
+ if (typeof group.ssrcs !== 'undefined') {
+ firstSsrcs.push(Number(group.ssrcs.split(' ')[0]));
+ }
+ }
+ });
+ }
+
+ if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {
+ for (var i = 0; i 0) {
+ // start gathering stats
+ }
+ */
+};
+
+TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
+ this.trace('setRemoteDescription::preTransform', dumpSDP(description));
+ // TODO the focus should squeze or explode the remote simulcast
+ description = this.simulcast.mungeRemoteDescription(description);
+ this.trace('setRemoteDescription::postTransform (simulcast)', dumpSDP(description));
+
+ // if we're running on FF, transform to Plan A first.
+ if (RTCBrowserType.usesUnifiedPlan()) {
+ description = this.interop.toUnifiedPlan(description);
+ this.trace('setRemoteDescription::postTransform (Plan A)', dumpSDP(description));
+ }
+
+ if (RTCBrowserType.usesPlanB()) {
+ description = normalizePlanB(description);
+ }
+
+ var self = this;
+ this.peerconnection.setRemoteDescription(description,
+ function () {
+ self.trace('setRemoteDescriptionOnSuccess');
+ successCallback();
+ },
+ function (err) {
+ self.trace('setRemoteDescriptionOnFailure', err);
+ failureCallback(err);
+ }
+ );
+ /*
+ if (this.statsinterval === null && this.maxstats > 0) {
+ // start gathering stats
+ }
+ */
+};
+
+TraceablePeerConnection.prototype.close = function () {
+ this.trace('stop');
+ if (this.statsinterval !== null) {
+ window.clearInterval(this.statsinterval);
+ this.statsinterval = null;
+ }
+ this.peerconnection.close();
+};
+
+TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
+ var self = this;
+ this.trace('createOffer', JSON.stringify(constraints, null, ' '));
+ this.peerconnection.createOffer(
+ function (offer) {
+ self.trace('createOfferOnSuccess::preTransform', dumpSDP(offer));
+ // if we're running on FF, transform to Plan B first.
+ // NOTE this is not tested because in meet the focus generates the
+ // offer.
+ if (RTCBrowserType.usesUnifiedPlan()) {
+ offer = self.interop.toPlanB(offer);
+ self.trace('createOfferOnSuccess::postTransform (Plan B)', dumpSDP(offer));
+ }
+
+ if (config.enableSimulcast && self.simulcast.isSupported()) {
+ offer = self.simulcast.mungeLocalDescription(offer);
+ self.trace('createOfferOnSuccess::postTransform (simulcast)', dumpSDP(offer));
+ }
+ successCallback(offer);
+ },
+ function(err) {
+ self.trace('createOfferOnFailure', err);
+ failureCallback(err);
+ },
+ constraints
+ );
+};
+
+TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
+ var self = this;
+ this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
+ this.peerconnection.createAnswer(
+ function (answer) {
+ self.trace('createAnswerOnSuccess::preTransfom', dumpSDP(answer));
+ // if we're running on FF, transform to Plan A first.
+ if (RTCBrowserType.usesUnifiedPlan()) {
+ answer = self.interop.toPlanB(answer);
+ self.trace('createAnswerOnSuccess::postTransfom (Plan B)', dumpSDP(answer));
+ }
+ if (config.enableSimulcast && self.simulcast.isSupported()) {
+ answer = self.simulcast.mungeLocalDescription(answer);
+ self.trace('createAnswerOnSuccess::postTransfom (simulcast)', dumpSDP(answer));
+ }
+ successCallback(answer);
+ },
+ function(err) {
+ self.trace('createAnswerOnFailure', err);
+ failureCallback(err);
+ },
+ constraints
+ );
+};
+
+TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
+ var self = this;
+ this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
+ this.peerconnection.addIceCandidate(candidate);
+ /* maybe later
+ this.peerconnection.addIceCandidate(candidate,
+ function () {
+ self.trace('addIceCandidateOnSuccess');
+ successCallback();
+ },
+ function (err) {
+ self.trace('addIceCandidateOnFailure', err);
+ failureCallback(err);
+ }
+ );
+ */
+};
+
+TraceablePeerConnection.prototype.getStats = function(callback, errback) {
+ // TODO: Is this the correct way to handle Opera, Temasys?
+ if (RTCBrowserType.isFirefox()) {
+ // ignore for now...
+ if(!errback)
+ errback = function () {};
+ this.peerconnection.getStats(null,callback,errback);
+ } else {
+ this.peerconnection.getStats(callback);
+ }
+};
+
+module.exports = TraceablePeerConnection;
+
+
+},{"../../service/xmpp/XMPPEvents":112,"../RTC/RTC":7,"../RTC/RTCBrowserType.js":8,"sdp-interop":89,"sdp-simulcast":96,"sdp-transform":99}],58:[function(require,module,exports){
+/* global $, $iq, APP, config, connection, messageHandler,
+ roomName, sessionTerminated, Strophe, Util */
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+var Settings = require("../settings/Settings");
+
+var AuthenticationEvents
+ = require("../../service/authentication/AuthenticationEvents");
+
+/**
+ * Contains logic responsible for enabling/disabling functionality available
+ * only to moderator users.
+ */
+var connection = null;
+var focusUserJid;
+
+function createExpBackoffTimer(step) {
+ var count = 1;
+ return function (reset) {
+ // Reset call
+ if (reset) {
+ count = 1;
+ return;
+ }
+ // Calculate next timeout
+ var timeout = Math.pow(2, count - 1);
+ count += 1;
+ return timeout * step;
+ };
+}
+
+var getNextTimeout = createExpBackoffTimer(1000);
+var getNextErrorTimeout = createExpBackoffTimer(1000);
+// External authentication stuff
+var externalAuthEnabled = false;
+// Sip gateway can be enabled by configuring Jigasi host in config.js or
+// it will be enabled automatically if focus detects the component through
+// service discovery.
+var sipGatewayEnabled = config.hosts.call_control !== undefined;
+
+var eventEmitter = null;
+
+var Moderator = {
+ isModerator: function () {
+ return connection && connection.emuc.isModerator();
+ },
+
+ isPeerModerator: function (peerJid) {
+ return connection &&
+ connection.emuc.getMemberRole(peerJid) === 'moderator';
+ },
+
+ isExternalAuthEnabled: function () {
+ return externalAuthEnabled;
+ },
+
+ isSipGatewayEnabled: function () {
+ return sipGatewayEnabled;
+ },
+
+ setConnection: function (con) {
+ connection = con;
+ },
+
+ init: function (xmpp, emitter) {
+ this.xmppService = xmpp;
+ eventEmitter = emitter;
+
+ // Message listener that talks to POPUP window
+ function listener(event) {
+ if (event.data && event.data.sessionId) {
+ if (event.origin !== window.location.origin) {
+ console.warn(
+ "Ignoring sessionId from different origin: " + event.origin);
+ return;
+ }
+ localStorage.setItem('sessionId', event.data.sessionId);
+ // After popup is closed we will authenticate
+ }
+ }
+ // Register
+ if (window.addEventListener) {
+ window.addEventListener("message", listener, false);
+ } else {
+ window.attachEvent("onmessage", listener);
+ }
+ },
+
+ onMucMemberLeft: function (jid) {
+ console.info("Someone left is it focus ? " + jid);
+ var resource = Strophe.getResourceFromJid(jid);
+ if (resource === 'focus' && !this.xmppService.sessionTerminated) {
+ console.info(
+ "Focus has left the room - leaving conference");
+ //hangUp();
+ // We'd rather reload to have everything re-initialized
+ // FIXME: show some message before reload
+ location.reload();
+ }
+ },
+
+ setFocusUserJid: function (focusJid) {
+ if (!focusUserJid) {
+ focusUserJid = focusJid;
+ console.info("Focus jid set to: " + focusUserJid);
+ }
+ },
+
+ getFocusUserJid: function () {
+ return focusUserJid;
+ },
+
+ getFocusComponent: function () {
+ // Get focus component address
+ var focusComponent = config.hosts.focus;
+ // If not specified use default: 'focus.domain'
+ if (!focusComponent) {
+ focusComponent = 'focus.' + config.hosts.domain;
+ }
+ return focusComponent;
+ },
+
+ createConferenceIq: function (roomName) {
+ // Generate create conference IQ
+ var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'});
+
+ // Session Id used for authentication
+ var sessionId = localStorage.getItem('sessionId');
+ var machineUID = Settings.getSettings().uid;
+
+ console.info(
+ "Session ID: " + sessionId + " machine UID: " + machineUID);
+
+ elem.c('conference', {
+ xmlns: 'http://jitsi.org/protocol/focus',
+ room: roomName,
+ 'machine-uid': machineUID
+ });
+
+ if (sessionId) {
+ elem.attrs({ 'session-id': sessionId});
+ }
+
+ if (config.hosts.bridge !== undefined) {
+ elem.c(
+ 'property',
+ { name: 'bridge', value: config.hosts.bridge})
+ .up();
+ }
+ // Tell the focus we have Jigasi configured
+ if (config.hosts.call_control !== undefined) {
+ elem.c(
+ 'property',
+ { name: 'call_control', value: config.hosts.call_control})
+ .up();
+ }
+ if (config.channelLastN !== undefined) {
+ elem.c(
+ 'property',
+ { name: 'channelLastN', value: config.channelLastN})
+ .up();
+ }
+ if (config.adaptiveLastN !== undefined) {
+ elem.c(
+ 'property',
+ { name: 'adaptiveLastN', value: config.adaptiveLastN})
+ .up();
+ }
+ if (config.adaptiveSimulcast !== undefined) {
+ elem.c(
+ 'property',
+ { name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
+ .up();
+ }
+ if (config.openSctp !== undefined) {
+ elem.c(
+ 'property',
+ { name: 'openSctp', value: config.openSctp})
+ .up();
+ }
+ if(config.startAudioMuted !== undefined)
+ {
+ elem.c(
+ 'property',
+ { name: 'startAudioMuted', value: config.startAudioMuted})
+ .up();
+ }
+ if(config.startVideoMuted !== undefined)
+ {
+ elem.c(
+ 'property',
+ { name: 'startVideoMuted', value: config.startVideoMuted})
+ .up();
+ }
+ elem.c(
+ 'property',
+ { name: 'simulcastMode', value: 'rewriting'})
+ .up();
+ elem.up();
+ return elem;
+ },
+
+ parseSessionId: function (resultIq) {
+ var sessionId = $(resultIq).find('conference').attr('session-id');
+ if (sessionId) {
+ console.info('Received sessionId: ' + sessionId);
+ localStorage.setItem('sessionId', sessionId);
+ }
+ },
+
+ parseConfigOptions: function (resultIq) {
+
+ Moderator.setFocusUserJid(
+ $(resultIq).find('conference').attr('focusjid'));
+
+ var authenticationEnabled
+ = $(resultIq).find(
+ '>conference>property' +
+ '[name=\'authentication\'][value=\'true\']').length > 0;
+
+ console.info("Authentication enabled: " + authenticationEnabled);
+
+ externalAuthEnabled
+ = $(resultIq).find(
+ '>conference>property' +
+ '[name=\'externalAuth\'][value=\'true\']').length > 0;
+
+ console.info('External authentication enabled: ' + externalAuthEnabled);
+
+ if (!externalAuthEnabled) {
+ // We expect to receive sessionId in 'internal' authentication mode
+ Moderator.parseSessionId(resultIq);
+ }
+
+ var authIdentity = $(resultIq).find('>conference').attr('identity');
+
+ eventEmitter.emit(AuthenticationEvents.IDENTITY_UPDATED,
+ authenticationEnabled, authIdentity);
+
+ // Check if focus has auto-detected Jigasi component(this will be also
+ // included if we have passed our host from the config)
+ if ($(resultIq).find(
+ '>conference>property' +
+ '[name=\'sipGatewayEnabled\'][value=\'true\']').length) {
+ sipGatewayEnabled = true;
+ }
+
+ console.info("Sip gateway enabled: " + sipGatewayEnabled);
+ },
+
+ // FIXME: we need to show the fact that we're waiting for the focus
+ // to the user(or that focus is not available)
+ allocateConferenceFocus: function (roomName, callback) {
+ // Try to use focus user JID from the config
+ Moderator.setFocusUserJid(config.focusUserJid);
+ // Send create conference IQ
+ var iq = Moderator.createConferenceIq(roomName);
+ var self = this;
+ connection.sendIQ(
+ iq,
+ function (result) {
+
+ // Setup config options
+ Moderator.parseConfigOptions(result);
+
+ if ('true' === $(result).find('conference').attr('ready')) {
+ // Reset both timers
+ getNextTimeout(true);
+ getNextErrorTimeout(true);
+ // Exec callback
+ callback();
+ } else {
+ var waitMs = getNextTimeout();
+ console.info("Waiting for the focus... " + waitMs);
+ // Reset error timeout
+ getNextErrorTimeout(true);
+ window.setTimeout(
+ function () {
+ Moderator.allocateConferenceFocus(
+ roomName, callback);
+ }, waitMs);
+ }
+ },
+ function (error) {
+ // Invalid session ? remove and try again
+ // without session ID to get a new one
+ var invalidSession
+ = $(error).find('>error>session-invalid').length;
+ if (invalidSession) {
+ console.info("Session expired! - removing");
+ localStorage.removeItem("sessionId");
+ }
+ if ($(error).find('>error>graceful-shutdown').length) {
+ eventEmitter.emit(XMPPEvents.GRACEFUL_SHUTDOWN);
+ return;
+ }
+ // Check for error returned by the reservation system
+ var reservationErr = $(error).find('>error>reservation-error');
+ if (reservationErr.length) {
+ // Trigger error event
+ var errorCode = reservationErr.attr('error-code');
+ var errorMsg;
+ if ($(error).find('>error>text')) {
+ errorMsg = $(error).find('>error>text').text();
+ }
+ eventEmitter.emit(
+ XMPPEvents.RESERVATION_ERROR, errorCode, errorMsg);
+ return;
+ }
+ // Not authorized to create new room
+ if ($(error).find('>error>not-authorized').length) {
+ console.warn("Unauthorized to start the conference", error);
+ var toDomain
+ = Strophe.getDomainFromJid(error.getAttribute('to'));
+ if (toDomain !== config.hosts.anonymousdomain) {
+ // FIXME: "is external" should come either from
+ // the focus or config.js
+ externalAuthEnabled = true;
+ }
+ eventEmitter.emit(
+ XMPPEvents.AUTHENTICATION_REQUIRED,
+ function () {
+ Moderator.allocateConferenceFocus(
+ roomName, callback);
+ });
+ return;
+ }
+ var waitMs = getNextErrorTimeout();
+ console.error("Focus error, retry after " + waitMs, error);
+ // Show message
+ var focusComponent = Moderator.getFocusComponent();
+ var retrySec = waitMs / 1000;
+ // FIXME: message is duplicated ?
+ // Do not show in case of session invalid
+ // which means just a retry
+ if (!invalidSession) {
+ eventEmitter.emit(XMPPEvents.FOCUS_DISCONNECTED, focusComponent, retrySec);
+ }
+ // Reset response timeout
+ getNextTimeout(true);
+ window.setTimeout(
+ function () {
+ Moderator.allocateConferenceFocus(roomName, callback);
+ }, waitMs);
+ }
+ );
+ },
+
+ getLoginUrl: function (roomName, urlCallback) {
+ var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
+ iq.c('login-url', {
+ xmlns: 'http://jitsi.org/protocol/focus',
+ room: roomName,
+ 'machine-uid': Settings.getSettings().uid
+ });
+ connection.sendIQ(
+ iq,
+ function (result) {
+ var url = $(result).find('login-url').attr('url');
+ url = url = decodeURIComponent(url);
+ if (url) {
+ console.info("Got auth url: " + url);
+ urlCallback(url);
+ } else {
+ console.error(
+ "Failed to get auth url from the focus", result);
+ }
+ },
+ function (error) {
+ console.error("Get auth url error", error);
+ }
+ );
+ },
+ getPopupLoginUrl: function (roomName, urlCallback) {
+ var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
+ iq.c('login-url', {
+ xmlns: 'http://jitsi.org/protocol/focus',
+ room: roomName,
+ 'machine-uid': Settings.getSettings().uid,
+ popup: true
+ });
+ connection.sendIQ(
+ iq,
+ function (result) {
+ var url = $(result).find('login-url').attr('url');
+ url = url = decodeURIComponent(url);
+ if (url) {
+ console.info("Got POPUP auth url: " + url);
+ urlCallback(url);
+ } else {
+ console.error(
+ "Failed to get POPUP auth url from the focus", result);
+ }
+ },
+ function (error) {
+ console.error('Get POPUP auth url error', error);
+ }
+ );
+ },
+ logout: function (callback) {
+ var iq = $iq({to: Moderator.getFocusComponent(), type: 'set'});
+ var sessionId = localStorage.getItem('sessionId');
+ if (!sessionId) {
+ callback();
+ return;
+ }
+ iq.c('logout', {
+ xmlns: 'http://jitsi.org/protocol/focus',
+ 'session-id': sessionId
+ });
+ connection.sendIQ(
+ iq,
+ function (result) {
+ var logoutUrl = $(result).find('logout').attr('logout-url');
+ if (logoutUrl) {
+ logoutUrl = decodeURIComponent(logoutUrl);
+ }
+ console.info("Log out OK, url: " + logoutUrl, result);
+ localStorage.removeItem('sessionId');
+ callback(logoutUrl);
+ },
+ function (error) {
+ console.error("Logout error", error);
+ }
+ );
+ }
+};
+
+module.exports = Moderator;
+
+
+
+
+},{"../../service/authentication/AuthenticationEvents":107,"../../service/xmpp/XMPPEvents":112,"../settings/Settings":47}],59:[function(require,module,exports){
+/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator,
+ Toolbar, Util */
+var Moderator = require("./moderator");
+
+
+var recordingToken = null;
+var recordingEnabled;
+
+/**
+ * Whether to use a jirecon component for recording, or use the videobridge
+ * through COLIBRI.
+ */
+var useJirecon = (typeof config.hosts.jirecon != "undefined");
+
+/**
+ * The ID of the jirecon recording session. Jirecon generates it when we
+ * initially start recording, and it needs to be used in subsequent requests
+ * to jirecon.
+ */
+var jireconRid = null;
+
+function setRecordingToken(token) {
+ recordingToken = token;
+}
+
+function setRecording(state, token, callback, connection) {
+ if (useJirecon){
+ setRecordingJirecon(state, token, callback, connection);
+ } else {
+ setRecordingColibri(state, token, callback, connection);
+ }
+}
+
+function setRecordingJirecon(state, token, callback, connection) {
+ if (state == recordingEnabled){
+ return;
+ }
+
+ var iq = $iq({to: config.hosts.jirecon, type: 'set'})
+ .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
+ action: state ? 'start' : 'stop',
+ mucjid: connection.emuc.roomjid});
+ if (!state){
+ iq.attrs({rid: jireconRid});
+ }
+
+ console.log('Start recording');
+
+ connection.sendIQ(
+ iq,
+ function (result) {
+ // TODO wait for an IQ with the real status, since this is
+ // provisional?
+ jireconRid = $(result).find('recording').attr('rid');
+ console.log('Recording ' + (state ? 'started' : 'stopped') +
+ '(jirecon)' + result);
+ recordingEnabled = state;
+ if (!state){
+ jireconRid = null;
+ }
+
+ callback(state);
+ },
+ function (error) {
+ console.log('Failed to start recording, error: ', error);
+ callback(recordingEnabled);
+ });
+}
+
+// Sends a COLIBRI message which enables or disables (according to 'state')
+// the recording on the bridge. Waits for the result IQ and calls 'callback'
+// with the new recording state, according to the IQ.
+function setRecordingColibri(state, token, callback, connection) {
+ var elem = $iq({to: connection.emuc.focusMucJid, type: 'set'});
+ elem.c('conference', {
+ xmlns: 'http://jitsi.org/protocol/colibri'
+ });
+ elem.c('recording', {state: state, token: token});
+
+ connection.sendIQ(elem,
+ function (result) {
+ console.log('Set recording "', state, '". Result:', result);
+ var recordingElem = $(result).find('>conference>recording');
+ var newState = ('true' === recordingElem.attr('state'));
+
+ recordingEnabled = newState;
+ callback(newState);
+ },
+ function (error) {
+ console.warn(error);
+ callback(recordingEnabled);
+ }
+ );
+}
+
+var Recording = {
+ toggleRecording: function (tokenEmptyCallback,
+ startingCallback, startedCallback, connection) {
+ if (!Moderator.isModerator()) {
+ console.log(
+ 'non-focus, or conference not yet organized:' +
+ ' not enabling recording');
+ return;
+ }
+
+ var self = this;
+ // Jirecon does not (currently) support a token.
+ if (!recordingToken && !useJirecon) {
+ tokenEmptyCallback(function (value) {
+ setRecordingToken(value);
+ self.toggleRecording(tokenEmptyCallback,
+ startingCallback, startedCallback, connection);
+ });
+
+ return;
+ }
+
+ var oldState = recordingEnabled;
+ startingCallback(!oldState);
+ setRecording(!oldState,
+ recordingToken,
+ function (state) {
+ console.log("New recording state: ", state);
+ if (state === oldState) {
+ // FIXME: new focus:
+ // this will not work when moderator changes
+ // during active session. Then it will assume that
+ // recording status has changed to true, but it might have
+ // been already true(and we only received actual status from
+ // the focus).
+ //
+ // SO we start with status null, so that it is initialized
+ // here and will fail only after second click, so if invalid
+ // token was used we have to press the button twice before
+ // current status will be fetched and token will be reset.
+ //
+ // Reliable way would be to return authentication error.
+ // Or status update when moderator connects.
+ // Or we have to stop recording session when current
+ // moderator leaves the room.
+
+ // Failed to change, reset the token because it might
+ // have been wrong
+ setRecordingToken(null);
+ }
+ startedCallback(state);
+
+ },
+ connection
+ );
+ }
+
+}
+
+module.exports = Recording;
+},{"./moderator":58}],60:[function(require,module,exports){
+/* jshint -W117 */
+/* a simple MUC connection plugin
+ * can only handle a single MUC room
+ */
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+var Moderator = require("./moderator");
+var JingleSession = require("./JingleSession");
+
+var bridgeIsDown = false;
+
+module.exports = function(XMPP, eventEmitter) {
+ Strophe.addConnectionPlugin('emuc', {
+ connection: null,
+ roomjid: null,
+ myroomjid: null,
+ members: {},
+ list_members: [], // so we can elect a new focus
+ presMap: {},
+ preziMap: {},
+ joined: false,
+ isOwner: false,
+ role: null,
+ focusMucJid: null,
+ init: function (conn) {
+ this.connection = conn;
+ },
+ initPresenceMap: function (myroomjid) {
+ this.presMap['to'] = myroomjid;
+ this.presMap['xns'] = 'http://jabber.org/protocol/muc';
+ if (APP.RTC.localAudio && APP.RTC.localAudio.isMuted())
+ {
+ this.addAudioInfoToPresence(true);
+ }
+ if (APP.RTC.localVideo && APP.RTC.localVideo.isMuted())
+ {
+ this.addVideoInfoToPresence(true);
+ }
+ },
+ doJoin: function (jid, password) {
+ this.myroomjid = jid;
+
+ console.info("Joined MUC as " + this.myroomjid);
+
+ this.initPresenceMap(this.myroomjid);
+
+ if (!this.roomjid) {
+ this.roomjid = Strophe.getBareJidFromJid(jid);
+ // add handlers (just once)
+ this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true});
+ this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true});
+ this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true});
+ this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true});
+ }
+ if (password !== undefined) {
+ this.presMap['password'] = password;
+ }
+ this.sendPresence();
+ },
+ doLeave: function () {
+ console.log("do leave", this.myroomjid);
+ var pres = $pres({to: this.myroomjid, type: 'unavailable' });
+ this.presMap.length = 0;
+ this.connection.send(pres);
+ },
+ createNonAnonymousRoom: function () {
+ // http://xmpp.org/extensions/xep-0045.html#createroom-reserved
+
+ var getForm = $iq({type: 'get', to: this.roomjid})
+ .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
+ .c('x', {xmlns: 'jabber:x:data', type: 'submit'});
+
+ var self = this;
+
+ this.connection.sendIQ(getForm, function (form) {
+
+ if (!$(form).find(
+ '>query>x[xmlns="jabber:x:data"]' +
+ '>field[var="muc#roomconfig_whois"]').length) {
+
+ console.error('non-anonymous rooms not supported');
+ return;
+ }
+
+ var formSubmit = $iq({to: this.roomjid, type: 'set'})
+ .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
+
+ formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
+
+ formSubmit.c('field', {'var': 'FORM_TYPE'})
+ .c('value')
+ .t('http://jabber.org/protocol/muc#roomconfig').up().up();
+
+ formSubmit.c('field', {'var': 'muc#roomconfig_whois'})
+ .c('value').t('anyone').up().up();
+
+ self.connection.sendIQ(formSubmit);
+
+ }, function (error) {
+ console.error("Error getting room configuration form");
+ });
+ },
+ onPresence: function (pres) {
+ var from = pres.getAttribute('from');
+
+ // What is this for? A workaround for something?
+ if (pres.getAttribute('type')) {
+ return true;
+ }
+
+ // Parse etherpad tag.
+ var etherpad = $(pres).find('>etherpad');
+ if (etherpad.length) {
+ if (config.etherpad_base) {
+ eventEmitter.emit(XMPPEvents.ETHERPAD, etherpad.text());
+ }
+ }
+
+ // Parse prezi tag.
+ var presentation = $(pres).find('>prezi');
+ if (presentation.length) {
+ var url = presentation.attr('url');
+ var current = presentation.find('>current').text();
+
+ console.log('presentation info received from', from, url);
+
+ if (this.preziMap[from] == null) {
+ this.preziMap[from] = url;
+
+ $(document).trigger('presentationadded.muc', [from, url, current]);
+ }
+ else {
+ $(document).trigger('gotoslide.muc', [from, url, current]);
+ }
+ }
+ else if (this.preziMap[from] != null) {
+ var url = this.preziMap[from];
+ delete this.preziMap[from];
+ $(document).trigger('presentationremoved.muc', [from, url]);
+ }
+
+ // Parse audio info tag.
+ var audioMuted = $(pres).find('>audiomuted');
+ if (audioMuted.length) {
+ eventEmitter.emit(XMPPEvents.AUDIO_MUTED,
+ from, (audioMuted.text() === "true"));
+ }
+
+ // Parse video info tag.
+ var videoMuted = $(pres).find('>videomuted');
+ if (videoMuted.length) {
+ eventEmitter.emit(XMPPEvents.VIDEO_MUTED,
+ from, (videoMuted.text() === "true"));
+ }
+
+ var startMuted = $(pres).find('>startmuted');
+ if (startMuted.length)
+ {
+ eventEmitter.emit(XMPPEvents.START_MUTED_SETTING_CHANGED,
+ startMuted.attr("audio") === "true", startMuted.attr("video") === "true");
+ }
+
+ var devices = $(pres).find('>devices');
+ if(devices.length)
+ {
+ var audio = devices.find('>audio');
+ var video = devices.find('>video');
+ var devicesValues = {audio: false, video: false};
+ if(audio.length && audio.text() === "true")
+ {
+ devicesValues.audio = true;
+ }
+
+ if(video.length && video.text() === "true")
+ {
+ devicesValues.video = true;
+ }
+ eventEmitter.emit(XMPPEvents.DEVICE_AVAILABLE,
+ Strophe.getResourceFromJid(from), devicesValues);
+ }
+
+ var stats = $(pres).find('>stats');
+ if (stats.length) {
+ var statsObj = {};
+ Strophe.forEachChild(stats[0], "stat", function (el) {
+ statsObj[el.getAttribute("name")] = el.getAttribute("value");
+ });
+ eventEmitter.emit(XMPPEvents.REMOTE_STATS, from, statsObj);
+ }
+
+ // Parse status.
+ if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
+ this.isOwner = true;
+ this.createNonAnonymousRoom();
+ }
+
+ // Parse roles.
+ var member = {};
+ member.show = $(pres).find('>show').text();
+ member.status = $(pres).find('>status').text();
+ var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
+ member.affiliation = tmp.attr('affiliation');
+ member.role = tmp.attr('role');
+
+ // Focus recognition
+ member.jid = tmp.attr('jid');
+ member.isFocus = false;
+ if (member.jid
+ && member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) {
+ member.isFocus = true;
+ }
+
+ var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
+ member.displayName = (nicktag.length > 0 ? nicktag.html() : null);
+
+ if (from == this.myroomjid) {
+ if (member.affiliation == 'owner') this.isOwner = true;
+ if (this.role !== member.role) {
+ this.role = member.role;
+
+ eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED,
+ from, member, pres, Moderator.isModerator());
+ }
+ if (!this.joined) {
+ this.joined = true;
+ eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member);
+ this.list_members.push(from);
+ }
+ } else if (this.members[from] === undefined) {
+ // new participant
+ this.members[from] = member;
+ this.list_members.push(from);
+ console.log('entered', from, member);
+ if (member.isFocus) {
+ this.focusMucJid = from;
+ console.info("Ignore focus: " + from + ", real JID: " + member.jid);
+ }
+ else {
+ var id = $(pres).find('>userId').text();
+ var email = $(pres).find('>email');
+ if (email.length > 0) {
+ id = email.text();
+ }
+ eventEmitter.emit(XMPPEvents.MUC_MEMBER_JOINED, from, id, member.displayName);
+ }
+ } else {
+ // Presence update for existing participant
+ // Watch role change:
+ if (this.members[from].role != member.role) {
+ this.members[from].role = member.role;
+ eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED,
+ member.role, member.displayName);
+ }
+ }
+
+ // Always trigger presence to update bindings
+ this.parsePresence(from, member, pres);
+
+ // Trigger status message update
+ if (member.status) {
+ eventEmitter.emit(XMPPEvents.PRESENCE_STATUS, from, member);
+ }
+
+ return true;
+ },
+ onPresenceUnavailable: function (pres) {
+ var from = pres.getAttribute('from');
+ // room destroyed ?
+ if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]' +
+ '>destroy').length) {
+ var reason;
+ var reasonSelect = $(pres).find(
+ '>x[xmlns="http://jabber.org/protocol/muc#user"]' +
+ '>destroy>reason');
+ if (reasonSelect.length) {
+ reason = reasonSelect.text();
+ }
+ XMPP.disposeConference(false);
+ eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason);
+ return true;
+ }
+
+ // Status code 110 indicates that this notification is "self-presence".
+ if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
+ delete this.members[from];
+ this.list_members.splice(this.list_members.indexOf(from), 1);
+ this.onParticipantLeft(from);
+ }
+ // If the status code is 110 this means we're leaving and we would like
+ // to remove everyone else from our view, so we trigger the event.
+ else if (this.list_members.length > 1) {
+ for (var i = 0; i < this.list_members.length; i++) {
+ var member = this.list_members[i];
+ delete this.members[i];
+ this.list_members.splice(i, 1);
+ this.onParticipantLeft(member);
+ }
+ }
+ if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
+ $(document).trigger('kicked.muc', [from]);
+ if (this.myroomjid === from) {
+ XMPP.disposeConference(false);
+ eventEmitter.emit(XMPPEvents.KICKED);
+ }
+ }
+ return true;
+ },
+ onPresenceError: function (pres) {
+ var from = pres.getAttribute('from');
+ if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
+ console.log('on password required', from);
+ var self = this;
+ eventEmitter.emit(XMPPEvents.PASSWORD_REQUIRED, function (value) {
+ self.doJoin(from, value);
+ });
+ } else if ($(pres).find(
+ '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
+ var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
+ if (toDomain === config.hosts.anonymousdomain) {
+ // enter the room by replying with 'not-authorized'. This would
+ // result in reconnection from authorized domain.
+ // We're either missing Jicofo/Prosody config for anonymous
+ // domains or something is wrong.
+// XMPP.promptLogin();
+ eventEmitter.emit(XMPPEvents.ROOM_JOIN_ERROR, pres);
+
+ } else {
+ console.warn('onPresError ', pres);
+ eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
+ }
+ } else {
+ console.warn('onPresError ', pres);
+ eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
+ }
+ return true;
+ },
+ sendMessage: function (body, nickname) {
+ var msg = $msg({to: this.roomjid, type: 'groupchat'});
+ msg.c('body', body).up();
+ if (nickname) {
+ msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
+ }
+ this.connection.send(msg);
+ eventEmitter.emit(XMPPEvents.SENDING_CHAT_MESSAGE, body);
+ },
+ setSubject: function (subject) {
+ var msg = $msg({to: this.roomjid, type: 'groupchat'});
+ msg.c('subject', subject);
+ this.connection.send(msg);
+ console.log("topic changed to " + subject);
+ },
+ onMessage: function (msg) {
+ // FIXME: this is a hack. but jingle on muc makes nickchanges hard
+ var from = msg.getAttribute('from');
+ var nick =
+ $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]')
+ .text() ||
+ Strophe.getResourceFromJid(from);
+
+ var txt = $(msg).find('>body').text();
+ var type = msg.getAttribute("type");
+ if (type == "error") {
+ eventEmitter.emit(XMPPEvents.CHAT_ERROR_RECEIVED,
+ $(msg).find('>text').text(), txt);
+ return true;
+ }
+
+ var subject = $(msg).find('>subject');
+ if (subject.length) {
+ var subjectText = subject.text();
+ if (subjectText || subjectText == "") {
+ eventEmitter.emit(XMPPEvents.SUBJECT_CHANGED, subjectText);
+ console.log("Subject is changed to " + subjectText);
+ }
+ }
+
+ // xep-0203 delay
+ var stamp = $(msg).find('>delay').attr('stamp');
+
+ if (!stamp) {
+ // or xep-0091 delay, UTC timestamp
+ stamp = $(msg).find('>[xmlns="jabber:x:delay"]').attr('stamp');
+
+ if (stamp) {
+ // the format is CCYYMMDDThh:mm:ss
+ var dateParts = stamp.match(/(\d{4})(\d{2})(\d{2}T\d{2}:\d{2}:\d{2})/);
+ stamp = dateParts[1] + "-" + dateParts[2] + "-" + dateParts[3] + "Z";
+ }
+ }
+
+ if (txt) {
+ console.log('chat', nick, txt);
+ eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED,
+ from, nick, txt, this.myroomjid, stamp);
+ }
+ return true;
+ },
+ lockRoom: function (key, onSuccess, onError, onNotSupported) {
+ //http://xmpp.org/extensions/xep-0045.html#roomconfig
+ var ob = this;
+ this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
+ function (res) {
+ if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
+ var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
+ formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
+ formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
+ formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
+ // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
+ formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
+ // FIXME: is muc#roomconfig_passwordprotectedroom required?
+ ob.connection.sendIQ(formsubmit,
+ onSuccess,
+ onError);
+ } else {
+ onNotSupported();
+ }
+ }, onError);
+ },
+ kick: function (jid) {
+ var kickIQ = $iq({to: this.roomjid, type: 'set'})
+ .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
+ .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
+ .c('reason').t('You have been kicked.').up().up().up();
+
+ this.connection.sendIQ(
+ kickIQ,
+ function (result) {
+ console.log('Kick participant with jid: ', jid, result);
+ },
+ function (error) {
+ console.log('Kick participant error: ', error);
+ });
+ },
+ sendPresence: function () {
+ if (!this.presMap['to']) {
+ // Too early to send presence - not initialized
+ return;
+ }
+ var pres = $pres({to: this.presMap['to'] });
+ pres.c('x', {xmlns: this.presMap['xns']});
+
+ if (this.presMap['password']) {
+ pres.c('password').t(this.presMap['password']).up();
+ }
+
+ pres.up();
+
+ // Send XEP-0115 'c' stanza that contains our capabilities info
+ if (this.connection.caps) {
+ this.connection.caps.node = config.clientNode;
+ pres.c('c', this.connection.caps.generateCapsAttrs()).up();
+ }
+
+ pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'})
+ .t(navigator.userAgent).up();
+
+ if (this.presMap['bridgeIsDown']) {
+ pres.c('bridgeIsDown').up();
+ }
+
+ if (this.presMap['email']) {
+ pres.c('email').t(this.presMap['email']).up();
+ }
+
+ if (this.presMap['userId']) {
+ pres.c('userId').t(this.presMap['userId']).up();
+ }
+
+ if (this.presMap['displayName']) {
+ // XEP-0172
+ pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'})
+ .t(this.presMap['displayName']).up();
+ }
+
+ if(this.presMap["devices"])
+ {
+ pres.c('devices').c('audio').t(this.presMap['devices'].audio).up()
+ .c('video').t(this.presMap['devices'].video).up().up();
+ }
+ if (this.presMap['audions']) {
+ pres.c('audiomuted', {xmlns: this.presMap['audions']})
+ .t(this.presMap['audiomuted']).up();
+ }
+
+ if (this.presMap['videons']) {
+ pres.c('videomuted', {xmlns: this.presMap['videons']})
+ .t(this.presMap['videomuted']).up();
+ }
+
+ if (this.presMap['statsns']) {
+ var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
+ for (var stat in this.presMap["stats"])
+ if (this.presMap["stats"][stat] != null)
+ stats.c("stat", {name: stat, value: this.presMap["stats"][stat]}).up();
+ pres.up();
+ }
+
+ if (this.presMap['prezins']) {
+ pres.c('prezi',
+ {xmlns: this.presMap['prezins'],
+ 'url': this.presMap['preziurl']})
+ .c('current').t(this.presMap['prezicurrent']).up().up();
+ }
+
+ if(this.presMap["startMuted"] !== undefined)
+ {
+ pres.c("startmuted", {audio: this.presMap["startMuted"].audio,
+ video: this.presMap["startMuted"].video,
+ xmlns: "http://jitsi.org/jitmeet/start-muted"});
+ delete this.presMap["startMuted"];
+ }
+
+ pres.up();
+ this.connection.send(pres);
+ },
+ addDisplayNameToPresence: function (displayName) {
+ this.presMap['displayName'] = displayName;
+ },
+ addDevicesToPresence: function (devices) {
+ this.presMap['devices'] = devices;
+ },
+ addPreziToPresence: function (url, currentSlide) {
+ this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
+ this.presMap['preziurl'] = url;
+ this.presMap['prezicurrent'] = currentSlide;
+ },
+ removePreziFromPresence: function () {
+ delete this.presMap['prezins'];
+ delete this.presMap['preziurl'];
+ delete this.presMap['prezicurrent'];
+ },
+ addCurrentSlideToPresence: function (currentSlide) {
+ this.presMap['prezicurrent'] = currentSlide;
+ },
+ getPrezi: function (roomjid) {
+ return this.preziMap[roomjid];
+ },
+ addAudioInfoToPresence: function (isMuted) {
+ this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
+ this.presMap['audiomuted'] = isMuted.toString();
+ },
+ addVideoInfoToPresence: function (isMuted) {
+ this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
+ this.presMap['videomuted'] = isMuted.toString();
+ },
+ addConnectionInfoToPresence: function (stats) {
+ this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
+ this.presMap['stats'] = stats;
+ },
+ findJidFromResource: function (resourceJid) {
+ if (resourceJid &&
+ resourceJid === Strophe.getResourceFromJid(this.myroomjid)) {
+ return this.myroomjid;
+ }
+ var peerJid = null;
+ Object.keys(this.members).some(function (jid) {
+ peerJid = jid;
+ return Strophe.getResourceFromJid(jid) === resourceJid;
+ });
+ return peerJid;
+ },
+ addBridgeIsDownToPresence: function () {
+ this.presMap['bridgeIsDown'] = true;
+ },
+ addEmailToPresence: function (email) {
+ this.presMap['email'] = email;
+ },
+ addUserIdToPresence: function (userId) {
+ this.presMap['userId'] = userId;
+ },
+ addStartMutedToPresence: function (audio, video) {
+ this.presMap["startMuted"] = {audio: audio, video: video};
+ },
+ isModerator: function () {
+ return this.role === 'moderator';
+ },
+ getMemberRole: function (peerJid) {
+ if (this.members[peerJid]) {
+ return this.members[peerJid].role;
+ }
+ return null;
+ },
+ onParticipantLeft: function (jid) {
+
+ eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid);
+
+ this.connection.jingle.terminateByJid(jid);
+
+ if (this.getPrezi(jid)) {
+ $(document).trigger('presentationremoved.muc',
+ [jid, this.getPrezi(jid)]);
+ }
+
+ Moderator.onMucMemberLeft(jid);
+ },
+ parsePresence: function (from, memeber, pres) {
+ if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
+ bridgeIsDown = true;
+ eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
+ }
+
+ if(memeber.isFocus)
+ return;
+
+ var displayName = !config.displayJids
+ ? memeber.displayName : Strophe.getResourceFromJid(from);
+
+ if (displayName && displayName.length > 0)
+ {
+ eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
+ }
+
+
+ var id = $(pres).find('>userID').text();
+ var email = $(pres).find('>email');
+ if(email.length > 0) {
+ id = email.text();
+ }
+
+ eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, id);
+ }
+ });
+};
+
+
+},{"../../service/xmpp/XMPPEvents":112,"./JingleSession":53,"./moderator":58}],61:[function(require,module,exports){
+/* jshint -W117 */
+
+var JingleSession = require("./JingleSession");
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+var RTCBrowserType = require("../RTC/RTCBrowserType");
+
+
+module.exports = function(XMPP, eventEmitter)
+{
+ function CallIncomingJingle(sid, connection) {
+ var sess = connection.jingle.sessions[sid];
+
+ // TODO: do we check activecall == null?
+ connection.jingle.activecall = sess;
+
+ eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
+
+ // TODO: check affiliation and/or role
+ console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
+ sess.usedrip = true; // not-so-naive trickle ice
+ sess.sendAnswer();
+ sess.accept();
+
+ };
+
+ Strophe.addConnectionPlugin('jingle', {
+ connection: null,
+ sessions: {},
+ jid2session: {},
+ ice_config: {iceServers: []},
+ pc_constraints: {},
+ activecall: null,
+ media_constraints: {
+ mandatory: {
+ 'OfferToReceiveAudio': true,
+ 'OfferToReceiveVideo': true
+ }
+ // MozDontOfferDataChannel: true when this is firefox
+ },
+ init: function (conn) {
+ this.connection = conn;
+ if (this.connection.disco) {
+ // http://xmpp.org/extensions/xep-0167.html#support
+ // http://xmpp.org/extensions/xep-0176.html#support
+ this.connection.disco.addFeature('urn:xmpp:jingle:1');
+ this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
+ this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
+ this.connection.disco.addFeature('urn:xmpp:jingle:apps:dtls:0');
+ this.connection.disco.addFeature('urn:xmpp:jingle:transports:dtls-sctp:1');
+ this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
+ this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
+
+ if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()
+ || RTCBrowserType.isTemasysPluginUsed()) {
+ this.connection.disco.addFeature('urn:ietf:rfc:4588');
+ }
+
+ // this is dealt with by SDP O/A so we don't need to annouce this
+ //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
+ //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
+ if (config.useRtcpMux) {
+ this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
+ }
+ if (config.useBundle) {
+ this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
+ }
+ //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
+ }
+ this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
+ },
+ onJingle: function (iq) {
+ var sid = $(iq).find('jingle').attr('sid');
+ var action = $(iq).find('jingle').attr('action');
+ var fromJid = iq.getAttribute('from');
+ // send ack first
+ var ack = $iq({type: 'result',
+ to: fromJid,
+ id: iq.getAttribute('id')
+ });
+ console.log('on jingle ' + action + ' from ' + fromJid, iq);
+ var sess = this.sessions[sid];
+ if ('session-initiate' != action) {
+ if (sess === null) {
+ ack.type = 'error';
+ ack.c('error', {type: 'cancel'})
+ .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
+ .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
+ this.connection.send(ack);
+ return true;
+ }
+ // compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
+ // local jid is not checked
+ if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
+ console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
+ ack.type = 'error';
+ ack.c('error', {type: 'cancel'})
+ .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
+ .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
+ this.connection.send(ack);
+ return true;
+ }
+ } else if (sess !== undefined) {
+ // existing session with same session id
+ // this might be out-of-order if the sess.peerjid is the same as from
+ ack.type = 'error';
+ ack.c('error', {type: 'cancel'})
+ .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
+ console.warn('duplicate session id', sid);
+ this.connection.send(ack);
+ return true;
+ }
+ // FIXME: check for a defined action
+ this.connection.send(ack);
+ // see http://xmpp.org/extensions/xep-0166.html#concepts-session
+ switch (action) {
+ case 'session-initiate':
+ var startMuted = $(iq).find('jingle>startmuted');
+ if(startMuted && startMuted.length > 0)
+ {
+ var audioMuted = startMuted.attr("audio");
+ var videoMuted = startMuted.attr("video");
+ eventEmitter.emit(XMPPEvents.START_MUTED_FROM_FOCUS,
+ autioMuted === "true", videoMuted === "true");
+ }
+ sess = new JingleSession(
+ $(iq).attr('to'), $(iq).find('jingle').attr('sid'),
+ this.connection, XMPP, eventEmitter);
+ // configure session
+
+ sess.media_constraints = this.media_constraints;
+ sess.pc_constraints = this.pc_constraints;
+ sess.ice_config = this.ice_config;
+
+ sess.initiate(fromJid, false);
+ // FIXME: setRemoteDescription should only be done when this call is to be accepted
+ sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
+
+ this.sessions[sess.sid] = sess;
+ this.jid2session[sess.peerjid] = sess;
+
+ // the callback should either
+ // .sendAnswer and .accept
+ // or .sendTerminate -- not necessarily synchronus
+ CallIncomingJingle(sess.sid, this.connection);
+ break;
+ case 'session-accept':
+ sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
+ sess.accept();
+ $(document).trigger('callaccepted.jingle', [sess.sid]);
+ break;
+ case 'session-terminate':
+ // If this is not the focus sending the terminate, we have
+ // nothing more to do here.
+ if (Object.keys(this.sessions).length < 1
+ || !(this.sessions[Object.keys(this.sessions)[0]]
+ instanceof JingleSession))
+ {
+ break;
+ }
+ console.log('terminating...', sess.sid);
+ sess.terminate();
+ this.terminate(sess.sid);
+ if ($(iq).find('>jingle>reason').length) {
+ $(document).trigger('callterminated.jingle', [
+ sess.sid,
+ sess.peerjid,
+ $(iq).find('>jingle>reason>:first')[0].tagName,
+ $(iq).find('>jingle>reason>text').text()
+ ]);
+ } else {
+ $(document).trigger('callterminated.jingle',
+ [sess.sid, sess.peerjid]);
+ }
+ break;
+ case 'transport-info':
+ sess.addIceCandidate($(iq).find('>jingle>content'));
+ break;
+ case 'session-info':
+ var affected;
+ if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
+ $(document).trigger('ringing.jingle', [sess.sid]);
+ } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
+ affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
+ $(document).trigger('mute.jingle', [sess.sid, affected]);
+ } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
+ affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
+ $(document).trigger('unmute.jingle', [sess.sid, affected]);
+ }
+ break;
+ case 'addsource': // FIXME: proprietary, un-jingleish
+ case 'source-add': // FIXME: proprietary
+ sess.addSource($(iq).find('>jingle>content'), fromJid);
+ break;
+ case 'removesource': // FIXME: proprietary, un-jingleish
+ case 'source-remove': // FIXME: proprietary
+ sess.removeSource($(iq).find('>jingle>content'), fromJid);
+ break;
+ default:
+ console.warn('jingle action not implemented', action);
+ break;
+ }
+ return true;
+ },
+ initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
+ var sess = new JingleSession(myjid || this.connection.jid,
+ Math.random().toString(36).substr(2, 12), // random string
+ this.connection, XMPP, eventEmitter);
+ // configure session
+
+ sess.media_constraints = this.media_constraints;
+ sess.pc_constraints = this.pc_constraints;
+ sess.ice_config = this.ice_config;
+
+ sess.initiate(peerjid, true);
+ this.sessions[sess.sid] = sess;
+ this.jid2session[sess.peerjid] = sess;
+ sess.sendOffer();
+ return sess;
+ },
+ terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
+ if (sid === null || sid === undefined) {
+ for (sid in this.sessions) {
+ if (this.sessions[sid].state != 'ended') {
+ this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
+ this.sessions[sid].terminate();
+ }
+ delete this.jid2session[this.sessions[sid].peerjid];
+ delete this.sessions[sid];
+ }
+ } else if (this.sessions.hasOwnProperty(sid)) {
+ if (this.sessions[sid].state != 'ended') {
+ this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
+ this.sessions[sid].terminate();
+ }
+ delete this.jid2session[this.sessions[sid].peerjid];
+ delete this.sessions[sid];
+ }
+ },
+ // Used to terminate a session when an unavailable presence is received.
+ terminateByJid: function (jid) {
+ if (this.jid2session.hasOwnProperty(jid)) {
+ var sess = this.jid2session[jid];
+ if (sess) {
+ sess.terminate();
+ console.log('peer went away silently', jid);
+ delete this.sessions[sess.sid];
+ delete this.jid2session[jid];
+ $(document).trigger('callterminated.jingle',
+ [sess.sid, jid], 'gone');
+ }
+ }
+ },
+ terminateRemoteByJid: function (jid, reason) {
+ if (this.jid2session.hasOwnProperty(jid)) {
+ var sess = this.jid2session[jid];
+ if (sess) {
+ sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
+ sess.terminate();
+ console.log('terminate peer with jid', sess.sid, jid);
+ delete this.sessions[sess.sid];
+ delete this.jid2session[jid];
+ $(document).trigger('callterminated.jingle',
+ [sess.sid, jid, 'kicked']);
+ }
+ }
+ },
+ getStunAndTurnCredentials: function () {
+ // get stun and turn configuration from server via xep-0215
+ // uses time-limited credentials as described in
+ // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
+ //
+ // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
+ // for a prosody module which implements this
+ //
+ // currently, this doesn't work with updateIce and therefore credentials with a long
+ // validity have to be fetched before creating the peerconnection
+ // TODO: implement refresh via updateIce as described in
+ // https://code.google.com/p/webrtc/issues/detail?id=1650
+ var self = this;
+ this.connection.sendIQ(
+ $iq({type: 'get', to: this.connection.domain})
+ .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
+ function (res) {
+ var iceservers = [];
+ $(res).find('>services>service').each(function (idx, el) {
+ el = $(el);
+ var dict = {};
+ var type = el.attr('type');
+ switch (type) {
+ case 'stun':
+ dict.url = 'stun:' + el.attr('host');
+ if (el.attr('port')) {
+ dict.url += ':' + el.attr('port');
+ }
+ iceservers.push(dict);
+ break;
+ case 'turn':
+ case 'turns':
+ dict.url = type + ':';
+ if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
+ if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
+ dict.url += el.attr('username') + '@';
+ } else {
+ dict.username = el.attr('username'); // only works in M28
+ }
+ }
+ dict.url += el.attr('host');
+ if (el.attr('port') && el.attr('port') != '3478') {
+ dict.url += ':' + el.attr('port');
+ }
+ if (el.attr('transport') && el.attr('transport') != 'udp') {
+ dict.url += '?transport=' + el.attr('transport');
+ }
+ if (el.attr('password')) {
+ dict.credential = el.attr('password');
+ }
+ iceservers.push(dict);
+ break;
+ }
+ });
+ self.ice_config.iceServers = iceservers;
+ },
+ function (err) {
+ console.warn('getting turn credentials failed', err);
+ console.warn('is mod_turncredentials or similar installed?');
+ }
+ );
+ // implement push?
+ },
+
+ /**
+ * Populates the log data
+ */
+ populateData: function () {
+ var data = {};
+ var self = this;
+ Object.keys(this.sessions).forEach(function (sid) {
+ var session = self.sessions[sid];
+ if (session.peerconnection && session.peerconnection.updateLog) {
+ // FIXME: should probably be a .dump call
+ data["jingle_" + session.sid] = {
+ updateLog: session.peerconnection.updateLog,
+ stats: session.peerconnection.stats,
+ url: window.location.href
+ };
+ }
+ });
+ return data;
+ }
+ });
+};
+
+
+},{"../../service/xmpp/XMPPEvents":112,"../RTC/RTCBrowserType":8,"./JingleSession":53}],62:[function(require,module,exports){
+/* global Strophe */
+module.exports = function () {
+
+ Strophe.addConnectionPlugin('logger', {
+ // logs raw stanzas and makes them available for download as JSON
+ connection: null,
+ log: [],
+ init: function (conn) {
+ this.connection = conn;
+ this.connection.rawInput = this.log_incoming.bind(this);
+ this.connection.rawOutput = this.log_outgoing.bind(this);
+ },
+ log_incoming: function (stanza) {
+ this.log.push([new Date().getTime(), 'incoming', stanza]);
+ },
+ log_outgoing: function (stanza) {
+ this.log.push([new Date().getTime(), 'outgoing', stanza]);
+ }
+ });
+};
+},{}],63:[function(require,module,exports){
+/* global $, $iq, config, connection, focusMucJid, forceMuted,
+ setAudioMuted, Strophe */
+/**
+ * Moderate connection plugin.
+ */
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+
+module.exports = function (XMPP, eventEmitter) {
+ Strophe.addConnectionPlugin('moderate', {
+ connection: null,
+ init: function (conn) {
+ this.connection = conn;
+
+ this.connection.addHandler(this.onMute.bind(this),
+ 'http://jitsi.org/jitmeet/audio',
+ 'iq',
+ 'set',
+ null,
+ null);
+ },
+ setMute: function (jid, mute) {
+ console.info("set mute", mute);
+ var iqToFocus = $iq({to: this.connection.emuc.focusMucJid, type: 'set'})
+ .c('mute', {
+ xmlns: 'http://jitsi.org/jitmeet/audio',
+ jid: jid
+ })
+ .t(mute.toString())
+ .up();
+
+ this.connection.sendIQ(
+ iqToFocus,
+ function (result) {
+ console.log('set mute', result);
+ },
+ function (error) {
+ console.log('set mute error', error);
+ });
+ },
+ onMute: function (iq) {
+ var from = iq.getAttribute('from');
+ if (from !== this.connection.emuc.focusMucJid) {
+ console.warn("Ignored mute from non focus peer");
+ return false;
+ }
+ var mute = $(iq).find('mute');
+ if (mute.length) {
+ var doMuteAudio = mute.text() === "true";
+ eventEmitter.emit(XMPPEvents.AUDIO_MUTED_BY_FOCUS, doMuteAudio);
+ XMPP.forceMuted = doMuteAudio;
+ }
+ return true;
+ },
+ eject: function (jid) {
+ // We're not the focus, so can't terminate
+ //connection.jingle.terminateRemoteByJid(jid, 'kick');
+ this.connection.emuc.kick(jid);
+ }
+ });
+}
+},{"../../service/xmpp/XMPPEvents":112}],64:[function(require,module,exports){
+/* jshint -W117 */
+module.exports = function() {
+ Strophe.addConnectionPlugin('rayo',
+ {
+ RAYO_XMLNS: 'urn:xmpp:rayo:1',
+ connection: null,
+ init: function (conn) {
+ this.connection = conn;
+ if (this.connection.disco) {
+ this.connection.disco.addFeature('urn:xmpp:rayo:client:1');
+ }
+
+ this.connection.addHandler(
+ this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null);
+ },
+ onRayo: function (iq) {
+ console.info("Rayo IQ", iq);
+ },
+ dial: function (to, from, roomName, roomPass) {
+ var self = this;
+ var req = $iq(
+ {
+ type: 'set',
+ to: this.connection.emuc.focusMucJid
+ }
+ );
+ req.c('dial',
+ {
+ xmlns: this.RAYO_XMLNS,
+ to: to,
+ from: from
+ });
+ req.c('header',
+ {
+ name: 'JvbRoomName',
+ value: roomName
+ }).up();
+
+ if (roomPass !== null && roomPass.length) {
+
+ req.c('header',
+ {
+ name: 'JvbRoomPassword',
+ value: roomPass
+ }).up();
+ }
+
+ this.connection.sendIQ(
+ req,
+ function (result) {
+ console.info('Dial result ', result);
+
+ var resource = $(result).find('ref').attr('uri');
+ this.call_resource = resource.substr('xmpp:'.length);
+ console.info(
+ "Received call resource: " + this.call_resource);
+ },
+ function (error) {
+ console.info('Dial error ', error);
+ }
+ );
+ },
+ hang_up: function () {
+ if (!this.call_resource) {
+ console.warn("No call in progress");
+ return;
+ }
+
+ var self = this;
+ var req = $iq(
+ {
+ type: 'set',
+ to: this.call_resource
+ }
+ );
+ req.c('hangup',
+ {
+ xmlns: this.RAYO_XMLNS
+ });
+
+ this.connection.sendIQ(
+ req,
+ function (result) {
+ console.info('Hangup result ', result);
+ self.call_resource = null;
+ },
+ function (error) {
+ console.info('Hangup error ', error);
+ self.call_resource = null;
+ }
+ );
+ }
+ }
+ );
+};
+
+},{}],65:[function(require,module,exports){
+/**
+ * Strophe logger implementation. Logs from level WARN and above.
+ */
+module.exports = function () {
+
+ Strophe.log = function (level, msg) {
+ switch (level) {
+ case Strophe.LogLevel.WARN:
+ console.warn("Strophe: " + msg);
+ break;
+ case Strophe.LogLevel.ERROR:
+ case Strophe.LogLevel.FATAL:
+ console.error("Strophe: " + msg);
+ break;
+ }
+ };
+
+ Strophe.getStatusString = function (status) {
+ switch (status) {
+ case Strophe.Status.ERROR:
+ return "ERROR";
+ case Strophe.Status.CONNECTING:
+ return "CONNECTING";
+ case Strophe.Status.CONNFAIL:
+ return "CONNFAIL";
+ case Strophe.Status.AUTHENTICATING:
+ return "AUTHENTICATING";
+ case Strophe.Status.AUTHFAIL:
+ return "AUTHFAIL";
+ case Strophe.Status.CONNECTED:
+ return "CONNECTED";
+ case Strophe.Status.DISCONNECTED:
+ return "DISCONNECTED";
+ case Strophe.Status.DISCONNECTING:
+ return "DISCONNECTING";
+ case Strophe.Status.ATTACHED:
+ return "ATTACHED";
+ default:
+ return "unknown";
+ }
+ };
+};
+
+},{}],66:[function(require,module,exports){
+/* global $, APP, config, Strophe*/
+var Moderator = require("./moderator");
+var EventEmitter = require("events");
+var Recording = require("./recording");
+var SDP = require("./SDP");
+var Settings = require("../settings/Settings");
+var Pako = require("pako");
+var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
+var RTCEvents = require("../../service/RTC/RTCEvents");
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+var retry = require('retry');
+
+var eventEmitter = new EventEmitter();
+var connection = null;
+var authenticatedUser = false;
+
+function connect(jid, password) {
+
+ var faultTolerantConnect = retry.operation({
+ retries: 3
+ });
+
+ // fault tolerant connect
+ faultTolerantConnect.attempt(function () {
+
+ connection = XMPP.createConnection();
+ Moderator.setConnection(connection);
+
+ if (connection.disco) {
+ // for chrome, add multistream cap
+ }
+ connection.jingle.pc_constraints = APP.RTC.getPCConstraints();
+ if (config.useIPv6) {
+ // https://code.google.com/p/webrtc/issues/detail?id=2828
+ if (!connection.jingle.pc_constraints.optional)
+ connection.jingle.pc_constraints.optional = [];
+ connection.jingle.pc_constraints.optional.push({googIPv6: true});
+ }
+
+ // Include user info in MUC presence
+ var settings = Settings.getSettings();
+ if (settings.email) {
+ connection.emuc.addEmailToPresence(settings.email);
+ }
+ if (settings.uid) {
+ connection.emuc.addUserIdToPresence(settings.uid);
+ }
+ if (settings.displayName) {
+ connection.emuc.addDisplayNameToPresence(settings.displayName);
+ }
+
+
+ // connection.connect() starts the connection process.
+ //
+ // As the connection process proceeds, the user supplied callback will
+ // be triggered multiple times with status updates. The callback should
+ // take two arguments - the status code and the error condition.
+ //
+ // The status code will be one of the values in the Strophe.Status
+ // constants. The error condition will be one of the conditions defined
+ // in RFC 3920 or the condition ‘strophe-parsererror’.
+ //
+ // The Parameters wait, hold and route are optional and only relevant
+ // for BOSH connections. Please see XEP 124 for a more detailed
+ // explanation of the optional parameters.
+ //
+ // Connection status constants for use by the connection handler
+ // callback.
+ //
+ // Status.ERROR - An error has occurred (websockets specific)
+ // Status.CONNECTING - The connection is currently being made
+ // Status.CONNFAIL - The connection attempt failed
+ // Status.AUTHENTICATING - The connection is authenticating
+ // Status.AUTHFAIL - The authentication attempt failed
+ // Status.CONNECTED - The connection has succeeded
+ // Status.DISCONNECTED - The connection has been terminated
+ // Status.DISCONNECTING - The connection is currently being terminated
+ // Status.ATTACHED - The connection has been attached
+
+ var anonymousConnectionFailed = false;
+ var connectionFailed = false;
+ var lastErrorMsg;
+ connection.connect(jid, password, function (status, msg) {
+ console.log('Strophe status changed to',
+ Strophe.getStatusString(status), msg);
+ if (status === Strophe.Status.CONNECTED) {
+ if (config.useStunTurn) {
+ connection.jingle.getStunAndTurnCredentials();
+ }
+
+ console.info("My Jabber ID: " + connection.jid);
+
+ if (password)
+ authenticatedUser = true;
+ maybeDoJoin();
+ } else if (status === Strophe.Status.CONNFAIL) {
+ if (msg === 'x-strophe-bad-non-anon-jid') {
+ anonymousConnectionFailed = true;
+ } else {
+ connectionFailed = true;
+ }
+ lastErrorMsg = msg;
+ } else if (status === Strophe.Status.DISCONNECTED) {
+ if (anonymousConnectionFailed) {
+ // prompt user for username and password
+ XMPP.promptLogin();
+ } else {
+
+ // Strophe already has built-in HTTP/BOSH error handling and
+ // request retry logic. Requests are resent automatically
+ // until their error count reaches 5. Strophe.js disconnects
+ // if the error count is > 5. We are not replicating this
+ // here.
+ //
+ // The "problem" is that failed HTTP/BOSH requests don't
+ // trigger a callback with a status update, so when a
+ // callback with status Strophe.Status.DISCONNECTED arrives,
+ // we can't be sure if it's a graceful disconnect or if it's
+ // triggered by some HTTP/BOSH error.
+ //
+ // But that's a minor issue in Jitsi Meet as we never
+ // disconnect anyway, not even when the user closes the
+ // browser window (which is kind of wrong, but the point is
+ // that we should never ever get disconnected).
+ //
+ // On the other hand, failed connections due to XMPP layer
+ // errors, trigger a callback with status Strophe.Status.CONNFAIL.
+ //
+ // Here we implement retry logic for failed connections due
+ // to XMPP layer errors and we display an error to the user
+ // if we get disconnected from the XMPP server permanently.
+
+ // If the connection failed, retry.
+ if (connectionFailed
+ && faultTolerantConnect.retry("connection-failed")) {
+ return;
+ }
+
+ // If we failed to connect to the XMPP server, fire an event
+ // to let all the interested module now about it.
+ eventEmitter.emit(XMPPEvents.CONNECTION_FAILED,
+ msg ? msg : lastErrorMsg);
+ }
+ } else if (status === Strophe.Status.AUTHFAIL) {
+ // wrong password or username, prompt user
+ XMPP.promptLogin();
+
+ }
+ });
+ });
+}
+
+
+
+function maybeDoJoin() {
+ if (connection && connection.connected &&
+ Strophe.getResourceFromJid(connection.jid)
+ && (APP.RTC.localAudio || APP.RTC.localVideo)) {
+ // .connected is true while connecting?
+ doJoin();
+ }
+}
+
+function doJoin() {
+ eventEmitter.emit(XMPPEvents.READY_TO_JOIN);
+}
+
+function initStrophePlugins()
+{
+ require("./strophe.emuc")(XMPP, eventEmitter);
+ require("./strophe.jingle")(XMPP, eventEmitter);
+ require("./strophe.moderate")(XMPP, eventEmitter);
+ require("./strophe.util")();
+ require("./strophe.rayo")();
+ require("./strophe.logger")();
+}
+
+function registerListeners() {
+ APP.RTC.addStreamListener(maybeDoJoin,
+ StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
+ APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) {
+ XMPP.addToPresence("devices", devices);
+ });
+}
+
+var unload = (function () {
+ var unloaded = false;
+
+ return function () {
+ if (unloaded) { return; }
+ unloaded = true;
+
+ if (connection && connection.connected) {
+ // ensure signout
+ $.ajax({
+ type: 'POST',
+ url: config.bosh,
+ async: false,
+ cache: false,
+ contentType: 'application/xml',
+ data: "" +
+ "" +
+ "",
+ success: function (data) {
+ console.log('signed out');
+ console.log(data);
+ },
+ error: function (XMLHttpRequest, textStatus, errorThrown) {
+ console.log('signout error',
+ textStatus + ' (' + errorThrown + ')');
+ }
+ });
+ }
+ XMPP.disposeConference(true);
+ };
+})();
+
+function setupEvents() {
+ // In recent versions of FF the 'beforeunload' event is not fired when the
+ // window or the tab is closed. It is only fired when we leave the page
+ // (change URL). If this participant doesn't unload properly, then it
+ // becomes a ghost for the rest of the participants that stay in the
+ // conference. Thankfully handling the 'unload' event in addition to the
+ // 'beforeunload' event seems to garante the execution of the 'unload'
+ // method at least once.
+ //
+ // The 'unload' method can safely be run multiple times, it will actually do
+ // something only the first time that it's run, so we're don't have to worry
+ // about browsers that fire both events.
+
+ $(window).bind('beforeunload', unload);
+ $(window).bind('unload', unload);
+}
+
+var XMPP = {
+ getConnection: function(){ return connection; },
+ sessionTerminated: false,
+
+ /**
+ * XMPP connection status
+ */
+ Status: Strophe.Status,
+
+ /**
+ * Remembers if we were muted by the focus.
+ * @type {boolean}
+ */
+ forceMuted: false,
+ start: function () {
+ setupEvents();
+ initStrophePlugins();
+ registerListeners();
+ Moderator.init(this, eventEmitter);
+ var configDomain = config.hosts.anonymousdomain || config.hosts.domain;
+ // Force authenticated domain if room is appended with '?login=true'
+ if (config.hosts.anonymousdomain &&
+ window.location.search.indexOf("login=true") !== -1) {
+ configDomain = config.hosts.domain;
+ }
+ var jid = configDomain || window.location.hostname;
+ connect(jid, null);
+ },
+ createConnection: function () {
+ var bosh = config.bosh || '/http-bind';
+
+ return new Strophe.Connection(bosh);
+ },
+ getStatusString: function (status) {
+ return Strophe.getStatusString(status);
+ },
+ promptLogin: function () {
+ eventEmitter.emit(XMPPEvents.PROMPT_FOR_LOGIN);
+ },
+ joinRoom: function(roomName, useNicks, nick)
+ {
+ var roomjid;
+ roomjid = roomName;
+
+ if (useNicks) {
+ if (nick) {
+ roomjid += '/' + nick;
+ } else {
+ roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
+ }
+ } else {
+
+ var tmpJid = Strophe.getNodeFromJid(connection.jid);
+
+ if(!authenticatedUser)
+ tmpJid = tmpJid.substr(0, 8);
+
+ roomjid += '/' + tmpJid;
+ }
+ connection.emuc.doJoin(roomjid);
+ },
+ myJid: function () {
+ if(!connection)
+ return null;
+ return connection.emuc.myroomjid;
+ },
+ myResource: function () {
+ if(!connection || ! connection.emuc.myroomjid)
+ return null;
+ return Strophe.getResourceFromJid(connection.emuc.myroomjid);
+ },
+ disposeConference: function (onUnload) {
+ var handler = connection.jingle.activecall;
+ if (handler && handler.peerconnection) {
+ // FIXME: probably removing streams is not required and close() should
+ // be enough
+ if (APP.RTC.localAudio) {
+ handler.peerconnection.removeStream(
+ APP.RTC.localAudio.getOriginalStream(), onUnload);
+ }
+ if (APP.RTC.localVideo) {
+ handler.peerconnection.removeStream(
+ APP.RTC.localVideo.getOriginalStream(), onUnload);
+ }
+ handler.peerconnection.close();
+ }
+ eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload);
+ connection.jingle.activecall = null;
+ if(!onUnload)
+ {
+ this.sessionTerminated = true;
+ connection.emuc.doLeave();
+ }
+ },
+ addListener: function(type, listener)
+ {
+ eventEmitter.on(type, listener);
+ },
+ removeListener: function (type, listener) {
+ eventEmitter.removeListener(type, listener);
+ },
+ allocateConferenceFocus: function(roomName, callback) {
+ Moderator.allocateConferenceFocus(roomName, callback);
+ },
+ getLoginUrl: function (roomName, callback) {
+ Moderator.getLoginUrl(roomName, callback);
+ },
+ getPopupLoginUrl: function (roomName, callback) {
+ Moderator.getPopupLoginUrl(roomName, callback);
+ },
+ isModerator: function () {
+ return Moderator.isModerator();
+ },
+ isSipGatewayEnabled: function () {
+ return Moderator.isSipGatewayEnabled();
+ },
+ isExternalAuthEnabled: function () {
+ return Moderator.isExternalAuthEnabled();
+ },
+ isConferenceInProgress: function () {
+ return connection && connection.jingle.activecall &&
+ connection.jingle.activecall.peerconnection;
+ },
+ switchStreams: function (stream, oldStream, callback, isAudio) {
+ if (this.isConferenceInProgress()) {
+ // FIXME: will block switchInProgress on true value in case of exception
+ connection.jingle.activecall.switchStreams(stream, oldStream, callback, isAudio);
+ } else {
+ // We are done immediately
+ console.warn("No conference handler or conference not started yet");
+ callback();
+ }
+ },
+ sendVideoInfoPresence: function (mute) {
+ if(!connection)
+ return;
+ connection.emuc.addVideoInfoToPresence(mute);
+ connection.emuc.sendPresence();
+ },
+ setVideoMute: function (mute, callback, options) {
+ if(!connection)
+ return;
+ var self = this;
+ var localCallback = function (mute) {
+ self.sendVideoInfoPresence(mute);
+ return callback(mute);
+ };
+
+ if(connection.jingle.activecall)
+ {
+ connection.jingle.activecall.setVideoMute(
+ mute, localCallback, options);
+ }
+ else {
+ localCallback(mute);
+ }
+
+ },
+ setAudioMute: function (mute, callback) {
+ if (!(connection && APP.RTC.localAudio)) {
+ return false;
+ }
+
+
+ if (this.forceMuted && !mute) {
+ console.info("Asking focus for unmute");
+ connection.moderate.setMute(connection.emuc.myroomjid, mute);
+ // FIXME: wait for result before resetting muted status
+ this.forceMuted = false;
+ }
+
+ if (mute == APP.RTC.localAudio.isMuted()) {
+ // Nothing to do
+ return true;
+ }
+
+ // It is not clear what is the right way to handle multiple tracks.
+ // So at least make sure that they are all muted or all unmuted and
+ // that we send presence just once.
+ APP.RTC.localAudio.setMute(!mute);
+ // isMuted is the opposite of audioEnabled
+ this.sendAudioInfoPresence(mute, callback);
+ return true;
+ },
+ sendAudioInfoPresence: function(mute, callback)
+ {
+ if(connection) {
+ connection.emuc.addAudioInfoToPresence(mute);
+ connection.emuc.sendPresence();
+ }
+ callback();
+ return true;
+ },
+ // Really mute video, i.e. dont even send black frames
+ muteVideo: function (pc, unmute) {
+ // FIXME: this probably needs another of those lovely state safeguards...
+ // which checks for iceconn == connected and sigstate == stable
+ pc.setRemoteDescription(pc.remoteDescription,
+ function () {
+ pc.createAnswer(
+ function (answer) {
+ var sdp = new SDP(answer.sdp);
+ if (sdp.media.length > 1) {
+ if (unmute)
+ sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
+ else
+ sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
+ sdp.raw = sdp.session + sdp.media.join('');
+ answer.sdp = sdp.raw;
+ }
+ pc.setLocalDescription(answer,
+ function () {
+ console.log('mute SLD ok');
+ },
+ function (error) {
+ console.log('mute SLD error');
+ eventEmitter.emit(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR);
+ }
+ );
+ },
+ function (error) {
+ console.log(error);
+ eventEmitter.emit(XMPPEvents.CREATE_ANSWER_ERROR);
+ }
+ );
+ },
+ function (error) {
+ console.log('muteVideo SRD error');
+ eventEmitter.emit(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR);
+ }
+ );
+ },
+ toggleRecording: function (tokenEmptyCallback,
+ startingCallback, startedCallback) {
+ Recording.toggleRecording(tokenEmptyCallback,
+ startingCallback, startedCallback, connection);
+ },
+ addToPresence: function (name, value, dontSend) {
+ switch (name)
+ {
+ case "displayName":
+ connection.emuc.addDisplayNameToPresence(value);
+ break;
+ case "prezi":
+ connection.emuc.addPreziToPresence(value, 0);
+ break;
+ case "preziSlide":
+ connection.emuc.addCurrentSlideToPresence(value);
+ break;
+ case "connectionQuality":
+ connection.emuc.addConnectionInfoToPresence(value);
+ break;
+ case "email":
+ connection.emuc.addEmailToPresence(value);
+ break;
+ case "devices":
+ connection.emuc.addDevicesToPresence(value);
+ break;
+ case "startMuted":
+ if(!Moderator.isModerator())
+ return;
+ connection.emuc.addStartMutedToPresence(value[0],
+ value[1]);
+ break;
+ default :
+ console.log("Unknown tag for presence: " + name);
+ return;
+ }
+ if (!dontSend)
+ connection.emuc.sendPresence();
+ },
+ /**
+ * Sends 'data' as a log message to the focus. Returns true iff a message
+ * was sent.
+ * @param data
+ * @returns {boolean} true iff a message was sent.
+ */
+ sendLogs: function (data) {
+ if(!connection.emuc.focusMucJid)
+ return false;
+
+ var deflate = true;
+
+ var content = JSON.stringify(data);
+ if (deflate) {
+ content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
+ }
+ content = Base64.encode(content);
+ // XEP-0337-ish
+ var message = $msg({to: connection.emuc.focusMucJid, type: 'normal'});
+ message.c('log', { xmlns: 'urn:xmpp:eventlog',
+ id: 'PeerConnectionStats'});
+ message.c('message').t(content).up();
+ if (deflate) {
+ message.c('tag', {name: "deflated", value: "true"}).up();
+ }
+ message.up();
+
+ connection.send(message);
+ return true;
+ },
+ populateData: function () {
+ var data = {};
+ if (connection.jingle) {
+ data = connection.jingle.populateData();
+ }
+ return data;
+ },
+ getLogger: function () {
+ if(connection.logger)
+ return connection.logger.log;
+ return null;
+ },
+ getPrezi: function () {
+ return connection.emuc.getPrezi(this.myJid());
+ },
+ removePreziFromPresence: function () {
+ connection.emuc.removePreziFromPresence();
+ connection.emuc.sendPresence();
+ },
+ sendChatMessage: function (message, nickname) {
+ connection.emuc.sendMessage(message, nickname);
+ },
+ setSubject: function (topic) {
+ connection.emuc.setSubject(topic);
+ },
+ lockRoom: function (key, onSuccess, onError, onNotSupported) {
+ connection.emuc.lockRoom(key, onSuccess, onError, onNotSupported);
+ },
+ dial: function (to, from, roomName,roomPass) {
+ connection.rayo.dial(to, from, roomName,roomPass);
+ },
+ setMute: function (jid, mute) {
+ connection.moderate.setMute(jid, mute);
+ },
+ eject: function (jid) {
+ connection.moderate.eject(jid);
+ },
+ logout: function (callback) {
+ Moderator.logout(callback);
+ },
+ findJidFromResource: function (resource) {
+ return connection.emuc.findJidFromResource(resource);
+ },
+ getMembers: function () {
+ return connection.emuc.members;
+ },
+ getJidFromSSRC: function (ssrc) {
+ if (!this.isConferenceInProgress())
+ return null;
+ return connection.jingle.activecall.getSsrcOwner(ssrc);
+ },
+ getMUCJoined: function () {
+ return connection.emuc.joined;
+ },
+ getSessions: function () {
+ return connection.jingle.sessions;
+ },
+ removeStream: function (stream) {
+ if (!this.isConferenceInProgress())
+ return;
+ connection.jingle.activecall.peerconnection.removeStream(stream);
+ }
+};
+
+module.exports = XMPP;
+
+},{"../../service/RTC/RTCEvents":103,"../../service/RTC/StreamEventTypes":105,"../../service/xmpp/XMPPEvents":112,"../settings/Settings":47,"./SDP":54,"./moderator":58,"./recording":59,"./strophe.emuc":60,"./strophe.jingle":61,"./strophe.logger":62,"./strophe.moderate":63,"./strophe.rayo":64,"./strophe.util":65,"events":113,"pako":69,"retry":85}],67:[function(require,module,exports){
(function (process){
/*!
* async
@@ -20081,7 +19873,7 @@ module.exports = XMPP;
}());
}).call(this,require('_process'))
-},{"_process":2}],70:[function(require,module,exports){
+},{"_process":114}],68:[function(require,module,exports){
// i18next, v1.7.7
// Copyright (c)2014 Jan Mühlemann (jamuhl).
// Distributed under MIT license
@@ -22204,7 +21996,7 @@ module.exports = XMPP;
i18n.options = o;
})();
-},{"jquery":"jquery"}],71:[function(require,module,exports){
+},{"jquery":"jquery"}],69:[function(require,module,exports){
// Top level file is just a mixin of submodules & constants
'use strict';
@@ -22220,7 +22012,7 @@ assign(pako, deflate, inflate, constants);
module.exports = pako;
-},{"./lib/deflate":72,"./lib/inflate":73,"./lib/utils/common":74,"./lib/zlib/constants":77}],72:[function(require,module,exports){
+},{"./lib/deflate":70,"./lib/inflate":71,"./lib/utils/common":72,"./lib/zlib/constants":75}],70:[function(require,module,exports){
'use strict';
@@ -22598,7 +22390,7 @@ exports.deflate = deflate;
exports.deflateRaw = deflateRaw;
exports.gzip = gzip;
-},{"./utils/common":74,"./utils/strings":75,"./zlib/deflate.js":79,"./zlib/messages":84,"./zlib/zstream":86}],73:[function(require,module,exports){
+},{"./utils/common":72,"./utils/strings":73,"./zlib/deflate.js":77,"./zlib/messages":82,"./zlib/zstream":84}],71:[function(require,module,exports){
'use strict';
@@ -22979,7 +22771,7 @@ exports.inflate = inflate;
exports.inflateRaw = inflateRaw;
exports.ungzip = inflate;
-},{"./utils/common":74,"./utils/strings":75,"./zlib/constants":77,"./zlib/gzheader":80,"./zlib/inflate.js":82,"./zlib/messages":84,"./zlib/zstream":86}],74:[function(require,module,exports){
+},{"./utils/common":72,"./utils/strings":73,"./zlib/constants":75,"./zlib/gzheader":78,"./zlib/inflate.js":80,"./zlib/messages":82,"./zlib/zstream":84}],72:[function(require,module,exports){
'use strict';
@@ -23083,7 +22875,7 @@ exports.setTyped = function (on) {
exports.setTyped(TYPED_OK);
-},{}],75:[function(require,module,exports){
+},{}],73:[function(require,module,exports){
// String encode/decode helpers
'use strict';
@@ -23270,7 +23062,7 @@ exports.utf8border = function(buf, max) {
return (pos + _utf8len[buf[pos]] > max) ? pos : max;
};
-},{"./common":74}],76:[function(require,module,exports){
+},{"./common":72}],74:[function(require,module,exports){
'use strict';
// Note: adler32 takes 12% for level 0 and 2% for level 6.
@@ -23304,7 +23096,7 @@ function adler32(adler, buf, len, pos) {
module.exports = adler32;
-},{}],77:[function(require,module,exports){
+},{}],75:[function(require,module,exports){
module.exports = {
/* Allowed flush values; see deflate() and inflate() below for details */
@@ -23353,7 +23145,7 @@ module.exports = {
//Z_NULL: null // Use -1 or null inline, depending on var type
};
-},{}],78:[function(require,module,exports){
+},{}],76:[function(require,module,exports){
'use strict';
// Note: we can't get significant speed boost here.
@@ -23396,7 +23188,7 @@ function crc32(crc, buf, len, pos) {
module.exports = crc32;
-},{}],79:[function(require,module,exports){
+},{}],77:[function(require,module,exports){
'use strict';
var utils = require('../utils/common');
@@ -25163,7 +24955,7 @@ exports.deflatePrime = deflatePrime;
exports.deflateTune = deflateTune;
*/
-},{"../utils/common":74,"./adler32":76,"./crc32":78,"./messages":84,"./trees":85}],80:[function(require,module,exports){
+},{"../utils/common":72,"./adler32":74,"./crc32":76,"./messages":82,"./trees":83}],78:[function(require,module,exports){
'use strict';
@@ -25205,7 +24997,7 @@ function GZheader() {
module.exports = GZheader;
-},{}],81:[function(require,module,exports){
+},{}],79:[function(require,module,exports){
'use strict';
// See state defs from inflate.js
@@ -25532,7 +25324,7 @@ module.exports = function inflate_fast(strm, start) {
return;
};
-},{}],82:[function(require,module,exports){
+},{}],80:[function(require,module,exports){
'use strict';
@@ -27037,7 +26829,7 @@ exports.inflateSyncPoint = inflateSyncPoint;
exports.inflateUndermine = inflateUndermine;
*/
-},{"../utils/common":74,"./adler32":76,"./crc32":78,"./inffast":81,"./inftrees":83}],83:[function(require,module,exports){
+},{"../utils/common":72,"./adler32":74,"./crc32":76,"./inffast":79,"./inftrees":81}],81:[function(require,module,exports){
'use strict';
@@ -27366,7 +27158,7 @@ module.exports = function inflate_table(type, lens, lens_index, codes, table, ta
return 0;
};
-},{"../utils/common":74}],84:[function(require,module,exports){
+},{"../utils/common":72}],82:[function(require,module,exports){
'use strict';
module.exports = {
@@ -27381,7 +27173,7 @@ module.exports = {
'-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */
};
-},{}],85:[function(require,module,exports){
+},{}],83:[function(require,module,exports){
'use strict';
@@ -28582,7 +28374,7 @@ exports._tr_flush_block = _tr_flush_block;
exports._tr_tally = _tr_tally;
exports._tr_align = _tr_align;
-},{"../utils/common":74}],86:[function(require,module,exports){
+},{"../utils/common":72}],84:[function(require,module,exports){
'use strict';
@@ -28613,9 +28405,9 @@ function ZStream() {
module.exports = ZStream;
-},{}],87:[function(require,module,exports){
+},{}],85:[function(require,module,exports){
module.exports = require('./lib/retry');
-},{"./lib/retry":88}],88:[function(require,module,exports){
+},{"./lib/retry":86}],86:[function(require,module,exports){
var RetryOperation = require('./retry_operation');
exports.operation = function(options) {
@@ -28666,7 +28458,7 @@ exports._createTimeout = function(attempt, opts) {
return timeout;
};
-},{"./retry_operation":89}],89:[function(require,module,exports){
+},{"./retry_operation":87}],87:[function(require,module,exports){
function RetryOperation(timeouts) {
this._timeouts = timeouts;
this._fn = null;
@@ -28776,7 +28568,7 @@ RetryOperation.prototype.mainError = function() {
return mainError;
};
-},{}],90:[function(require,module,exports){
+},{}],88:[function(require,module,exports){
module.exports = function arrayEquals(array) {
// if the other array is a falsy value, return
if (!array)
@@ -28802,10 +28594,10 @@ module.exports = function arrayEquals(array) {
}
-},{}],91:[function(require,module,exports){
+},{}],89:[function(require,module,exports){
exports.Interop = require('./interop');
-},{"./interop":92}],92:[function(require,module,exports){
+},{"./interop":90}],90:[function(require,module,exports){
"use strict";
var transform = require('./transform');
@@ -29387,7 +29179,7 @@ Interop.prototype.toUnifiedPlan = function(desc) {
//#endregion
};
-},{"./array-equals":90,"./transform":93}],93:[function(require,module,exports){
+},{"./array-equals":88,"./transform":91}],91:[function(require,module,exports){
var transform = require('sdp-transform');
exports.write = function(session, opts) {
@@ -29486,7 +29278,477 @@ exports.parse = function(sdp) {
};
-},{"sdp-transform":97}],94:[function(require,module,exports){
+},{"sdp-transform":93}],92:[function(require,module,exports){
+var grammar = module.exports = {
+ v: [{
+ name: 'version',
+ reg: /^(\d*)$/
+ }],
+ o: [{ //o=- 20518 0 IN IP4 203.0.113.1
+ // NB: sessionId will be a String in most cases because it is huge
+ name: 'origin',
+ reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/,
+ names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],
+ format: "%s %s %d %s IP%d %s"
+ }],
+ // default parsing of these only (though some of these feel outdated)
+ s: [{ name: 'name' }],
+ i: [{ name: 'description' }],
+ u: [{ name: 'uri' }],
+ e: [{ name: 'email' }],
+ p: [{ name: 'phone' }],
+ z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly..
+ r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly
+ //k: [{}], // outdated thing ignored
+ t: [{ //t=0 0
+ name: 'timing',
+ reg: /^(\d*) (\d*)/,
+ names: ['start', 'stop'],
+ format: "%d %d"
+ }],
+ c: [{ //c=IN IP4 10.47.197.26
+ name: 'connection',
+ reg: /^IN IP(\d) (\S*)/,
+ names: ['version', 'ip'],
+ format: "IN IP%d %s"
+ }],
+ b: [{ //b=AS:4000
+ push: 'bandwidth',
+ reg: /^(TIAS|AS|CT|RR|RS):(\d*)/,
+ names: ['type', 'limit'],
+ format: "%s:%s"
+ }],
+ m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31
+ // NB: special - pushes to session
+ // TODO: rtp/fmtp should be filtered by the payloads found here?
+ reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/,
+ names: ['type', 'port', 'protocol', 'payloads'],
+ format: "%s %d %s %s"
+ }],
+ a: [
+ { //a=rtpmap:110 opus/48000/2
+ push: 'rtp',
+ reg: /^rtpmap:(\d*) ([\w\-]*)\/(\d*)(?:\s*\/(\S*))?/,
+ names: ['payload', 'codec', 'rate', 'encoding'],
+ format: function (o) {
+ return (o.encoding) ?
+ "rtpmap:%d %s/%s/%s":
+ "rtpmap:%d %s/%s";
+ }
+ },
+ { //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000
+ push: 'fmtp',
+ reg: /^fmtp:(\d*) (\S*)/,
+ names: ['payload', 'config'],
+ format: "fmtp:%d %s"
+ },
+ { //a=control:streamid=0
+ name: 'control',
+ reg: /^control:(.*)/,
+ format: "control:%s"
+ },
+ { //a=rtcp:65179 IN IP4 193.84.77.194
+ name: 'rtcp',
+ reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/,
+ names: ['port', 'netType', 'ipVer', 'address'],
+ format: function (o) {
+ return (o.address != null) ?
+ "rtcp:%d %s IP%d %s":
+ "rtcp:%d";
+ }
+ },
+ { //a=rtcp-fb:98 trr-int 100
+ push: 'rtcpFbTrrInt',
+ reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/,
+ names: ['payload', 'value'],
+ format: "rtcp-fb:%d trr-int %d"
+ },
+ { //a=rtcp-fb:98 nack rpsi
+ push: 'rtcpFb',
+ reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/,
+ names: ['payload', 'type', 'subtype'],
+ format: function (o) {
+ return (o.subtype != null) ?
+ "rtcp-fb:%s %s %s":
+ "rtcp-fb:%s %s";
+ }
+ },
+ { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
+ //a=extmap:1/recvonly URI-gps-string
+ push: 'ext',
+ reg: /^extmap:([\w_\/]*) (\S*)(?: (\S*))?/,
+ names: ['value', 'uri', 'config'], // value may include "/direction" suffix
+ format: function (o) {
+ return (o.config != null) ?
+ "extmap:%s %s %s":
+ "extmap:%s %s";
+ }
+ },
+ {
+ //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
+ push: 'crypto',
+ reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/,
+ names: ['id', 'suite', 'config', 'sessionConfig'],
+ format: function (o) {
+ return (o.sessionConfig != null) ?
+ "crypto:%d %s %s %s":
+ "crypto:%d %s %s";
+ }
+ },
+ { //a=setup:actpass
+ name: 'setup',
+ reg: /^setup:(\w*)/,
+ format: "setup:%s"
+ },
+ { //a=mid:1
+ name: 'mid',
+ reg: /^mid:([^\s]*)/,
+ format: "mid:%s"
+ },
+ { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a
+ name: 'msid',
+ reg: /^msid:(.*)/,
+ format: "msid:%s"
+ },
+ { //a=ptime:20
+ name: 'ptime',
+ reg: /^ptime:(\d*)/,
+ format: "ptime:%d"
+ },
+ { //a=maxptime:60
+ name: 'maxptime',
+ reg: /^maxptime:(\d*)/,
+ format: "maxptime:%d"
+ },
+ { //a=sendrecv
+ name: 'direction',
+ reg: /^(sendrecv|recvonly|sendonly|inactive)/
+ },
+ { //a=ice-lite
+ name: 'icelite',
+ reg: /^(ice-lite)/
+ },
+ { //a=ice-ufrag:F7gI
+ name: 'iceUfrag',
+ reg: /^ice-ufrag:(\S*)/,
+ format: "ice-ufrag:%s"
+ },
+ { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g
+ name: 'icePwd',
+ reg: /^ice-pwd:(\S*)/,
+ format: "ice-pwd:%s"
+ },
+ { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33
+ name: 'fingerprint',
+ reg: /^fingerprint:(\S*) (\S*)/,
+ names: ['type', 'hash'],
+ format: "fingerprint:%s %s"
+ },
+ {
+ //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
+ //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0
+ //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0
+ push:'candidates',
+ reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: generation (\d*))?/,
+ names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'generation'],
+ format: function (o) {
+ var str = "candidate:%s %d %s %d %s %d typ %s";
+ // NB: candidate has two optional chunks, so %void middle one if it's missing
+ str += (o.raddr != null) ? " raddr %s rport %d" : "%v%v";
+ if (o.generation != null) {
+ str += " generation %d";
+ }
+ return str;
+ }
+ },
+ { //a=end-of-candidates (keep after the candidates line for readability)
+ name: 'endOfCandidates',
+ reg: /^(end-of-candidates)/
+ },
+ { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...
+ name: 'remoteCandidates',
+ reg: /^remote-candidates:(.*)/,
+ format: "remote-candidates:%s"
+ },
+ { //a=ice-options:google-ice
+ name: 'iceOptions',
+ reg: /^ice-options:(\S*)/,
+ format: "ice-options:%s"
+ },
+ { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1
+ push: "ssrcs",
+ reg: /^ssrc:(\d*) ([\w_]*):(.*)/,
+ names: ['id', 'attribute', 'value'],
+ format: "ssrc:%d %s:%s"
+ },
+ { //a=ssrc-group:FEC 1 2
+ push: "ssrcGroups",
+ reg: /^ssrc-group:(\w*) (.*)/,
+ names: ['semantics', 'ssrcs'],
+ format: "ssrc-group:%s %s"
+ },
+ { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV
+ name: "msidSemantic",
+ reg: /^msid-semantic:\s?(\w*) (\S*)/,
+ names: ['semantic', 'token'],
+ format: "msid-semantic: %s %s" // space after ":" is not accidental
+ },
+ { //a=group:BUNDLE audio video
+ push: 'groups',
+ reg: /^group:(\w*) (.*)/,
+ names: ['type', 'mids'],
+ format: "group:%s %s"
+ },
+ { //a=rtcp-mux
+ name: 'rtcpMux',
+ reg: /^(rtcp-mux)/
+ },
+ { //a=rtcp-rsize
+ name: 'rtcpRsize',
+ reg: /^(rtcp-rsize)/
+ },
+ { // any a= that we don't understand is kepts verbatim on media.invalid
+ push: 'invalid',
+ names: ["value"]
+ }
+ ]
+};
+
+// set sensible defaults to avoid polluting the grammar with boring details
+Object.keys(grammar).forEach(function (key) {
+ var objs = grammar[key];
+ objs.forEach(function (obj) {
+ if (!obj.reg) {
+ obj.reg = /(.*)/;
+ }
+ if (!obj.format) {
+ obj.format = "%s";
+ }
+ });
+});
+
+},{}],93:[function(require,module,exports){
+var parser = require('./parser');
+var writer = require('./writer');
+
+exports.write = writer;
+exports.parse = parser.parse;
+exports.parseFmtpConfig = parser.parseFmtpConfig;
+exports.parsePayloads = parser.parsePayloads;
+exports.parseRemoteCandidates = parser.parseRemoteCandidates;
+
+},{"./parser":94,"./writer":95}],94:[function(require,module,exports){
+var toIntIfInt = function (v) {
+ return String(Number(v)) === v ? Number(v) : v;
+};
+
+var attachProperties = function (match, location, names, rawName) {
+ if (rawName && !names) {
+ location[rawName] = toIntIfInt(match[1]);
+ }
+ else {
+ for (var i = 0; i < names.length; i += 1) {
+ if (match[i+1] != null) {
+ location[names[i]] = toIntIfInt(match[i+1]);
+ }
+ }
+ }
+};
+
+var parseReg = function (obj, location, content) {
+ var needsBlank = obj.name && obj.names;
+ if (obj.push && !location[obj.push]) {
+ location[obj.push] = [];
+ }
+ else if (needsBlank && !location[obj.name]) {
+ location[obj.name] = {};
+ }
+ var keyLocation = obj.push ?
+ {} : // blank object that will be pushed
+ needsBlank ? location[obj.name] : location; // otherwise, named location or root
+
+ attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);
+
+ if (obj.push) {
+ location[obj.push].push(keyLocation);
+ }
+};
+
+var grammar = require('./grammar');
+var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/);
+
+exports.parse = function (sdp) {
+ var session = {}
+ , media = []
+ , location = session; // points at where properties go under (one of the above)
+
+ // parse lines we understand
+ sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) {
+ var type = l[0];
+ var content = l.slice(2);
+ if (type === 'm') {
+ media.push({rtp: [], fmtp: []});
+ location = media[media.length-1]; // point at latest media line
+ }
+
+ for (var j = 0; j < (grammar[type] || []).length; j += 1) {
+ var obj = grammar[type][j];
+ if (obj.reg.test(content)) {
+ return parseReg(obj, location, content);
+ }
+ }
+ });
+
+ session.media = media; // link it up
+ return session;
+};
+
+var fmtpReducer = function (acc, expr) {
+ var s = expr.split('=');
+ if (s.length === 2) {
+ acc[s[0]] = toIntIfInt(s[1]);
+ }
+ return acc;
+};
+
+exports.parseFmtpConfig = function (str) {
+ return str.split(';').reduce(fmtpReducer, {});
+};
+
+exports.parsePayloads = function (str) {
+ return str.split(' ').map(Number);
+};
+
+exports.parseRemoteCandidates = function (str) {
+ var candidates = [];
+ var parts = str.split(' ').map(toIntIfInt);
+ for (var i = 0; i < parts.length; i += 3) {
+ candidates.push({
+ component: parts[i],
+ ip: parts[i + 1],
+ port: parts[i + 2]
+ });
+ }
+ return candidates;
+};
+
+},{"./grammar":92}],95:[function(require,module,exports){
+var grammar = require('./grammar');
+
+// customized util.format - discards excess arguments and can void middle ones
+var formatRegExp = /%[sdv%]/g;
+var format = function (formatStr) {
+ var i = 1;
+ var args = arguments;
+ var len = args.length;
+ return formatStr.replace(formatRegExp, function (x) {
+ if (i >= len) {
+ return x; // missing argument
+ }
+ var arg = args[i];
+ i += 1;
+ switch (x) {
+ case '%%':
+ return '%';
+ case '%s':
+ return String(arg);
+ case '%d':
+ return Number(arg);
+ case '%v':
+ return '';
+ }
+ });
+ // NB: we discard excess arguments - they are typically undefined from makeLine
+};
+
+var makeLine = function (type, obj, location) {
+ var str = obj.format instanceof Function ?
+ (obj.format(obj.push ? location : location[obj.name])) :
+ obj.format;
+
+ var args = [type + '=' + str];
+ if (obj.names) {
+ for (var i = 0; i < obj.names.length; i += 1) {
+ var n = obj.names[i];
+ if (obj.name) {
+ args.push(location[obj.name][n]);
+ }
+ else { // for mLine and push attributes
+ args.push(location[obj.names[i]]);
+ }
+ }
+ }
+ else {
+ args.push(location[obj.name]);
+ }
+ return format.apply(null, args);
+};
+
+// RFC specified order
+// TODO: extend this with all the rest
+var defaultOuterOrder = [
+ 'v', 'o', 's', 'i',
+ 'u', 'e', 'p', 'c',
+ 'b', 't', 'r', 'z', 'a'
+];
+var defaultInnerOrder = ['i', 'c', 'b', 'a'];
+
+
+module.exports = function (session, opts) {
+ opts = opts || {};
+ // ensure certain properties exist
+ if (session.version == null) {
+ session.version = 0; // "v=0" must be there (only defined version atm)
+ }
+ if (session.name == null) {
+ session.name = " "; // "s= " must be there if no meaningful name set
+ }
+ session.media.forEach(function (mLine) {
+ if (mLine.payloads == null) {
+ mLine.payloads = "";
+ }
+ });
+
+ var outerOrder = opts.outerOrder || defaultOuterOrder;
+ var innerOrder = opts.innerOrder || defaultInnerOrder;
+ var sdp = [];
+
+ // loop through outerOrder for matching properties on session
+ outerOrder.forEach(function (type) {
+ grammar[type].forEach(function (obj) {
+ if (obj.name in session && session[obj.name] != null) {
+ sdp.push(makeLine(type, obj, session));
+ }
+ else if (obj.push in session && session[obj.push] != null) {
+ session[obj.push].forEach(function (el) {
+ sdp.push(makeLine(type, obj, el));
+ });
+ }
+ });
+ });
+
+ // then for each media line, follow the innerOrder
+ session.media.forEach(function (mLine) {
+ sdp.push(makeLine('m', grammar.m[0], mLine));
+
+ innerOrder.forEach(function (type) {
+ grammar[type].forEach(function (obj) {
+ if (obj.name in mLine && mLine[obj.name] != null) {
+ sdp.push(makeLine(type, obj, mLine));
+ }
+ else if (obj.push in mLine && mLine[obj.push] != null) {
+ mLine[obj.push].forEach(function (el) {
+ sdp.push(makeLine(type, obj, el));
+ });
+ }
+ });
+ });
+ });
+
+ return sdp.join('\r\n') + '\r\n';
+};
+
+},{"./grammar":92}],96:[function(require,module,exports){
var transform = require('sdp-transform');
var transformUtils = require('./transform-utils');
var parseSsrcs = transformUtils.parseSsrcs;
@@ -29888,7 +30150,7 @@ Simulcast.prototype.mungeLocalDescription = function (desc) {
module.exports = Simulcast;
-},{"./transform-utils":95,"sdp-transform":97}],95:[function(require,module,exports){
+},{"./transform-utils":97,"sdp-transform":99}],97:[function(require,module,exports){
exports.writeSsrcs = function(sources, order) {
var ssrcs = [];
@@ -29939,678 +30201,579 @@ exports.parseSsrcs = function (mLine) {
};
-},{}],96:[function(require,module,exports){
-var grammar = module.exports = {
- v: [{
- name: 'version',
- reg: /^(\d*)$/
- }],
- o: [{ //o=- 20518 0 IN IP4 203.0.113.1
- // NB: sessionId will be a String in most cases because it is huge
- name: 'origin',
- reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/,
- names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],
- format: "%s %s %d %s IP%d %s"
- }],
- // default parsing of these only (though some of these feel outdated)
- s: [{ name: 'name' }],
- i: [{ name: 'description' }],
- u: [{ name: 'uri' }],
- e: [{ name: 'email' }],
- p: [{ name: 'phone' }],
- z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly..
- r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly
- //k: [{}], // outdated thing ignored
- t: [{ //t=0 0
- name: 'timing',
- reg: /^(\d*) (\d*)/,
- names: ['start', 'stop'],
- format: "%d %d"
- }],
- c: [{ //c=IN IP4 10.47.197.26
- name: 'connection',
- reg: /^IN IP(\d) (\S*)/,
- names: ['version', 'ip'],
- format: "IN IP%d %s"
- }],
- b: [{ //b=AS:4000
- push: 'bandwidth',
- reg: /^(TIAS|AS|CT|RR|RS):(\d*)/,
- names: ['type', 'limit'],
- format: "%s:%s"
- }],
- m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31
- // NB: special - pushes to session
- // TODO: rtp/fmtp should be filtered by the payloads found here?
- reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/,
- names: ['type', 'port', 'protocol', 'payloads'],
- format: "%s %d %s %s"
- }],
- a: [
- { //a=rtpmap:110 opus/48000/2
- push: 'rtp',
- reg: /^rtpmap:(\d*) ([\w\-]*)\/(\d*)(?:\s*\/(\S*))?/,
- names: ['payload', 'codec', 'rate', 'encoding'],
- format: function (o) {
- return (o.encoding) ?
- "rtpmap:%d %s/%s/%s":
- "rtpmap:%d %s/%s";
- }
- },
- { //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000
- push: 'fmtp',
- reg: /^fmtp:(\d*) (\S*)/,
- names: ['payload', 'config'],
- format: "fmtp:%d %s"
- },
- { //a=control:streamid=0
- name: 'control',
- reg: /^control:(.*)/,
- format: "control:%s"
- },
- { //a=rtcp:65179 IN IP4 193.84.77.194
- name: 'rtcp',
- reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/,
- names: ['port', 'netType', 'ipVer', 'address'],
- format: function (o) {
- return (o.address != null) ?
- "rtcp:%d %s IP%d %s":
- "rtcp:%d";
- }
- },
- { //a=rtcp-fb:98 trr-int 100
- push: 'rtcpFbTrrInt',
- reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/,
- names: ['payload', 'value'],
- format: "rtcp-fb:%d trr-int %d"
- },
- { //a=rtcp-fb:98 nack rpsi
- push: 'rtcpFb',
- reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/,
- names: ['payload', 'type', 'subtype'],
- format: function (o) {
- return (o.subtype != null) ?
- "rtcp-fb:%s %s %s":
- "rtcp-fb:%s %s";
- }
- },
- { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
- //a=extmap:1/recvonly URI-gps-string
- push: 'ext',
- reg: /^extmap:([\w_\/]*) (\S*)(?: (\S*))?/,
- names: ['value', 'uri', 'config'], // value may include "/direction" suffix
- format: function (o) {
- return (o.config != null) ?
- "extmap:%s %s %s":
- "extmap:%s %s";
- }
- },
- {
- //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
- push: 'crypto',
- reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/,
- names: ['id', 'suite', 'config', 'sessionConfig'],
- format: function (o) {
- return (o.sessionConfig != null) ?
- "crypto:%d %s %s %s":
- "crypto:%d %s %s";
- }
- },
- { //a=setup:actpass
- name: 'setup',
- reg: /^setup:(\w*)/,
- format: "setup:%s"
- },
- { //a=mid:1
- name: 'mid',
- reg: /^mid:([^\s]*)/,
- format: "mid:%s"
- },
- { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a
- name: 'msid',
- reg: /^msid:(.*)/,
- format: "msid:%s"
- },
- { //a=ptime:20
- name: 'ptime',
- reg: /^ptime:(\d*)/,
- format: "ptime:%d"
- },
- { //a=maxptime:60
- name: 'maxptime',
- reg: /^maxptime:(\d*)/,
- format: "maxptime:%d"
- },
- { //a=sendrecv
- name: 'direction',
- reg: /^(sendrecv|recvonly|sendonly|inactive)/
- },
- { //a=ice-lite
- name: 'icelite',
- reg: /^(ice-lite)/
- },
- { //a=ice-ufrag:F7gI
- name: 'iceUfrag',
- reg: /^ice-ufrag:(\S*)/,
- format: "ice-ufrag:%s"
- },
- { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g
- name: 'icePwd',
- reg: /^ice-pwd:(\S*)/,
- format: "ice-pwd:%s"
- },
- { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33
- name: 'fingerprint',
- reg: /^fingerprint:(\S*) (\S*)/,
- names: ['type', 'hash'],
- format: "fingerprint:%s %s"
- },
- {
- //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
- //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0
- //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0
- push:'candidates',
- reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: generation (\d*))?/,
- names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'generation'],
- format: function (o) {
- var str = "candidate:%s %d %s %d %s %d typ %s";
- // NB: candidate has two optional chunks, so %void middle one if it's missing
- str += (o.raddr != null) ? " raddr %s rport %d" : "%v%v";
- if (o.generation != null) {
- str += " generation %d";
- }
- return str;
- }
- },
- { //a=end-of-candidates (keep after the candidates line for readability)
- name: 'endOfCandidates',
- reg: /^(end-of-candidates)/
- },
- { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...
- name: 'remoteCandidates',
- reg: /^remote-candidates:(.*)/,
- format: "remote-candidates:%s"
- },
- { //a=ice-options:google-ice
- name: 'iceOptions',
- reg: /^ice-options:(\S*)/,
- format: "ice-options:%s"
- },
- { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1
- push: "ssrcs",
- reg: /^ssrc:(\d*) ([\w_]*):(.*)/,
- names: ['id', 'attribute', 'value'],
- format: "ssrc:%d %s:%s"
- },
- { //a=ssrc-group:FEC 1 2
- push: "ssrcGroups",
- reg: /^ssrc-group:(\w*) (.*)/,
- names: ['semantics', 'ssrcs'],
- format: "ssrc-group:%s %s"
- },
- { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV
- name: "msidSemantic",
- reg: /^msid-semantic:\s?(\w*) (\S*)/,
- names: ['semantic', 'token'],
- format: "msid-semantic: %s %s" // space after ":" is not accidental
- },
- { //a=group:BUNDLE audio video
- push: 'groups',
- reg: /^group:(\w*) (.*)/,
- names: ['type', 'mids'],
- format: "group:%s %s"
- },
- { //a=rtcp-mux
- name: 'rtcpMux',
- reg: /^(rtcp-mux)/
- },
- { //a=rtcp-rsize
- name: 'rtcpRsize',
- reg: /^(rtcp-rsize)/
- },
- { // any a= that we don't understand is kepts verbatim on media.invalid
- push: 'invalid',
- names: ["value"]
- }
- ]
+},{}],98:[function(require,module,exports){
+arguments[4][92][0].apply(exports,arguments)
+},{"dup":92}],99:[function(require,module,exports){
+arguments[4][93][0].apply(exports,arguments)
+},{"./parser":100,"./writer":101,"dup":93}],100:[function(require,module,exports){
+arguments[4][94][0].apply(exports,arguments)
+},{"./grammar":98,"dup":94}],101:[function(require,module,exports){
+arguments[4][95][0].apply(exports,arguments)
+},{"./grammar":98,"dup":95}],102:[function(require,module,exports){
+var MediaStreamType = {
+ VIDEO_TYPE: "Video",
+
+ AUDIO_TYPE: "Audio"
};
-
-// set sensible defaults to avoid polluting the grammar with boring details
-Object.keys(grammar).forEach(function (key) {
- var objs = grammar[key];
- objs.forEach(function (obj) {
- if (!obj.reg) {
- obj.reg = /(.*)/;
- }
- if (!obj.format) {
- obj.format = "%s";
- }
- });
-});
-
-},{}],97:[function(require,module,exports){
-var parser = require('./parser');
-var writer = require('./writer');
-
-exports.write = writer;
-exports.parse = parser.parse;
-exports.parseFmtpConfig = parser.parseFmtpConfig;
-exports.parsePayloads = parser.parsePayloads;
-exports.parseRemoteCandidates = parser.parseRemoteCandidates;
-
-},{"./parser":98,"./writer":99}],98:[function(require,module,exports){
-var toIntIfInt = function (v) {
- return String(Number(v)) === v ? Number(v) : v;
-};
-
-var attachProperties = function (match, location, names, rawName) {
- if (rawName && !names) {
- location[rawName] = toIntIfInt(match[1]);
- }
- else {
- for (var i = 0; i < names.length; i += 1) {
- if (match[i+1] != null) {
- location[names[i]] = toIntIfInt(match[i+1]);
- }
- }
- }
-};
-
-var parseReg = function (obj, location, content) {
- var needsBlank = obj.name && obj.names;
- if (obj.push && !location[obj.push]) {
- location[obj.push] = [];
- }
- else if (needsBlank && !location[obj.name]) {
- location[obj.name] = {};
- }
- var keyLocation = obj.push ?
- {} : // blank object that will be pushed
- needsBlank ? location[obj.name] : location; // otherwise, named location or root
-
- attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);
-
- if (obj.push) {
- location[obj.push].push(keyLocation);
- }
-};
-
-var grammar = require('./grammar');
-var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/);
-
-exports.parse = function (sdp) {
- var session = {}
- , media = []
- , location = session; // points at where properties go under (one of the above)
-
- // parse lines we understand
- sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) {
- var type = l[0];
- var content = l.slice(2);
- if (type === 'm') {
- media.push({rtp: [], fmtp: []});
- location = media[media.length-1]; // point at latest media line
- }
-
- for (var j = 0; j < (grammar[type] || []).length; j += 1) {
- var obj = grammar[type][j];
- if (obj.reg.test(content)) {
- return parseReg(obj, location, content);
- }
- }
- });
-
- session.media = media; // link it up
- return session;
-};
-
-var fmtpReducer = function (acc, expr) {
- var s = expr.split('=');
- if (s.length === 2) {
- acc[s[0]] = toIntIfInt(s[1]);
- }
- return acc;
-};
-
-exports.parseFmtpConfig = function (str) {
- return str.split(';').reduce(fmtpReducer, {});
-};
-
-exports.parsePayloads = function (str) {
- return str.split(' ').map(Number);
-};
-
-exports.parseRemoteCandidates = function (str) {
- var candidates = [];
- var parts = str.split(' ').map(toIntIfInt);
- for (var i = 0; i < parts.length; i += 3) {
- candidates.push({
- component: parts[i],
- ip: parts[i + 1],
- port: parts[i + 2]
- });
- }
- return candidates;
-};
-
-},{"./grammar":96}],99:[function(require,module,exports){
-var grammar = require('./grammar');
-
-// customized util.format - discards excess arguments and can void middle ones
-var formatRegExp = /%[sdv%]/g;
-var format = function (formatStr) {
- var i = 1;
- var args = arguments;
- var len = args.length;
- return formatStr.replace(formatRegExp, function (x) {
- if (i >= len) {
- return x; // missing argument
- }
- var arg = args[i];
- i += 1;
- switch (x) {
- case '%%':
- return '%';
- case '%s':
- return String(arg);
- case '%d':
- return Number(arg);
- case '%v':
- return '';
- }
- });
- // NB: we discard excess arguments - they are typically undefined from makeLine
-};
-
-var makeLine = function (type, obj, location) {
- var str = obj.format instanceof Function ?
- (obj.format(obj.push ? location : location[obj.name])) :
- obj.format;
-
- var args = [type + '=' + str];
- if (obj.names) {
- for (var i = 0; i < obj.names.length; i += 1) {
- var n = obj.names[i];
- if (obj.name) {
- args.push(location[obj.name][n]);
- }
- else { // for mLine and push attributes
- args.push(location[obj.names[i]]);
- }
- }
- }
- else {
- args.push(location[obj.name]);
- }
- return format.apply(null, args);
-};
-
-// RFC specified order
-// TODO: extend this with all the rest
-var defaultOuterOrder = [
- 'v', 'o', 's', 'i',
- 'u', 'e', 'p', 'c',
- 'b', 't', 'r', 'z', 'a'
-];
-var defaultInnerOrder = ['i', 'c', 'b', 'a'];
-
-
-module.exports = function (session, opts) {
- opts = opts || {};
- // ensure certain properties exist
- if (session.version == null) {
- session.version = 0; // "v=0" must be there (only defined version atm)
- }
- if (session.name == null) {
- session.name = " "; // "s= " must be there if no meaningful name set
- }
- session.media.forEach(function (mLine) {
- if (mLine.payloads == null) {
- mLine.payloads = "";
- }
- });
-
- var outerOrder = opts.outerOrder || defaultOuterOrder;
- var innerOrder = opts.innerOrder || defaultInnerOrder;
- var sdp = [];
-
- // loop through outerOrder for matching properties on session
- outerOrder.forEach(function (type) {
- grammar[type].forEach(function (obj) {
- if (obj.name in session && session[obj.name] != null) {
- sdp.push(makeLine(type, obj, session));
- }
- else if (obj.push in session && session[obj.push] != null) {
- session[obj.push].forEach(function (el) {
- sdp.push(makeLine(type, obj, el));
- });
- }
- });
- });
-
- // then for each media line, follow the innerOrder
- session.media.forEach(function (mLine) {
- sdp.push(makeLine('m', grammar.m[0], mLine));
-
- innerOrder.forEach(function (type) {
- grammar[type].forEach(function (obj) {
- if (obj.name in mLine && mLine[obj.name] != null) {
- sdp.push(makeLine(type, obj, mLine));
- }
- else if (obj.push in mLine && mLine[obj.push] != null) {
- mLine[obj.push].forEach(function (el) {
- sdp.push(makeLine(type, obj, el));
- });
- }
- });
- });
- });
-
- return sdp.join('\r\n') + '\r\n';
-};
-
-},{"./grammar":96}],100:[function(require,module,exports){
-var MediaStreamType = {
- VIDEO_TYPE: "Video",
-
- AUDIO_TYPE: "Audio"
-};
module.exports = MediaStreamType;
-},{}],101:[function(require,module,exports){
-var RTCEvents = {
- RTC_READY: "rtc.ready",
- DATA_CHANNEL_OPEN: "rtc.data_channel_open",
- LASTN_CHANGED: "rtc.lastn_changed",
- DOMINANTSPEAKER_CHANGED: "rtc.dominantspeaker_changed",
- LASTN_ENDPOINT_CHANGED: "rtc.lastn_endpoint_changed",
- AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed",
- AUDIO_MUTE: "rtc.audio_mute",
- VIDEO_MUTE: "rtc.video_mute"
-};
-
-module.exports = RTCEvents;
-},{}],102:[function(require,module,exports){
-var Resolutions = {
- "1080": {
- width: 1920,
- height: 1080,
- order: 7
- },
- "fullhd": {
- width: 1920,
- height: 1080,
- order: 7
- },
- "720": {
- width: 1280,
- height: 720,
- order: 6
- },
- "hd": {
- width: 1280,
- height: 720,
- order: 6
- },
- "960": {
- width: 960,
- height: 720,
- order: 5
- },
- "640": {
- width: 640,
- height: 480,
- order: 4
- },
- "vga": {
- width: 640,
- height: 480,
- order: 4
- },
- "360": {
- width: 640,
- height: 360,
- order: 3
- },
- "320": {
- width: 320,
- height: 240,
- order: 2
- },
- "180": {
- width: 320,
- height: 180,
- order: 1
- }
-};
-module.exports = Resolutions;
},{}],103:[function(require,module,exports){
-var StreamEventTypes = {
- EVENT_TYPE_LOCAL_CREATED: "stream.local_created",
-
- EVENT_TYPE_LOCAL_CHANGED: "stream.local_changed",
-
- EVENT_TYPE_LOCAL_ENDED: "stream.local_ended",
-
- EVENT_TYPE_REMOTE_CREATED: "stream.remote_created",
-
- EVENT_TYPE_REMOTE_ENDED: "stream.remote_ended",
-
- EVENT_TYPE_REMOTE_CHANGED: "stream.changed"
-};
-
-module.exports = StreamEventTypes;
+var RTCEvents = {
+ RTC_READY: "rtc.ready",
+ DATA_CHANNEL_OPEN: "rtc.data_channel_open",
+ LASTN_CHANGED: "rtc.lastn_changed",
+ DOMINANTSPEAKER_CHANGED: "rtc.dominantspeaker_changed",
+ LASTN_ENDPOINT_CHANGED: "rtc.lastn_endpoint_changed",
+ AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed",
+ AUDIO_MUTE: "rtc.audio_mute",
+ VIDEO_MUTE: "rtc.video_mute"
+};
+
+module.exports = RTCEvents;
},{}],104:[function(require,module,exports){
-var UIEvents = {
- NICKNAME_CHANGED: "UI.nickname_changed",
- SELECTED_ENDPOINT: "UI.selected_endpoint",
- PINNED_ENDPOINT: "UI.pinned_endpoint"
-};
-module.exports = UIEvents;
+var Resolutions = {
+ "1080": {
+ width: 1920,
+ height: 1080,
+ order: 7
+ },
+ "fullhd": {
+ width: 1920,
+ height: 1080,
+ order: 7
+ },
+ "720": {
+ width: 1280,
+ height: 720,
+ order: 6
+ },
+ "hd": {
+ width: 1280,
+ height: 720,
+ order: 6
+ },
+ "960": {
+ width: 960,
+ height: 720,
+ order: 5
+ },
+ "640": {
+ width: 640,
+ height: 480,
+ order: 4
+ },
+ "vga": {
+ width: 640,
+ height: 480,
+ order: 4
+ },
+ "360": {
+ width: 640,
+ height: 360,
+ order: 3
+ },
+ "320": {
+ width: 320,
+ height: 240,
+ order: 2
+ },
+ "180": {
+ width: 320,
+ height: 180,
+ order: 1
+ }
+};
+module.exports = Resolutions;
},{}],105:[function(require,module,exports){
-var AuthenticationEvents = {
- /**
- * Event callback arguments:
- * function(authenticationEnabled, userIdentity)
- * authenticationEnabled - indicates whether authentication has been enabled
- * in this session
- * userIdentity - if user has been logged in then it contains user name. If
- * contains 'null' or 'undefined' then user is not logged in.
- */
- IDENTITY_UPDATED: "authentication.identity_updated"
-};
-module.exports = AuthenticationEvents;
+var StreamEventTypes = {
+ EVENT_TYPE_LOCAL_CREATED: "stream.local_created",
+ EVENT_TYPE_LOCAL_CHANGED: "stream.local_changed",
+
+ EVENT_TYPE_LOCAL_ENDED: "stream.local_ended",
+
+ EVENT_TYPE_REMOTE_CREATED: "stream.remote_created",
+
+ EVENT_TYPE_REMOTE_ENDED: "stream.remote_ended",
+
+ EVENT_TYPE_REMOTE_CHANGED: "stream.changed"
+};
+
+module.exports = StreamEventTypes;
},{}],106:[function(require,module,exports){
-var CQEvents = {
- LOCALSTATS_UPDATED: "cq.localstats_updated",
- REMOTESTATS_UPDATED: "cq.remotestats_updated",
- STOP: "cq.stop"
-};
-
-module.exports = CQEvents;
+var UIEvents = {
+ NICKNAME_CHANGED: "UI.nickname_changed",
+ SELECTED_ENDPOINT: "UI.selected_endpoint",
+ PINNED_ENDPOINT: "UI.pinned_endpoint",
+ LARGEVIDEO_INIT: "UI.largevideo_init"
+};
+module.exports = UIEvents;
},{}],107:[function(require,module,exports){
-var DesktopSharingEventTypes = {
- INIT: "ds.init",
-
- SWITCHING_DONE: "ds.switching_done",
-
- NEW_STREAM_CREATED: "ds.new_stream_created"
-};
-
-module.exports = DesktopSharingEventTypes;
-},{}],108:[function(require,module,exports){
-var Events = {
- DTMF_SUPPORT_CHANGED: "members.dtmf_support_changed"
-};
-
-module.exports = Events;
+var AuthenticationEvents = {
+ /**
+ * Event callback arguments:
+ * function(authenticationEnabled, userIdentity)
+ * authenticationEnabled - indicates whether authentication has been enabled
+ * in this session
+ * userIdentity - if user has been logged in then it contains user name. If
+ * contains 'null' or 'undefined' then user is not logged in.
+ */
+ IDENTITY_UPDATED: "authentication.identity_updated"
+};
+module.exports = AuthenticationEvents;
+},{}],108:[function(require,module,exports){
+var CQEvents = {
+ LOCALSTATS_UPDATED: "cq.localstats_updated",
+ REMOTESTATS_UPDATED: "cq.remotestats_updated",
+ STOP: "cq.stop"
+};
+
+module.exports = CQEvents;
},{}],109:[function(require,module,exports){
-module.exports = {
- getLanguages : function () {
- var languages = [];
- for(var lang in this)
- {
- if(typeof this[lang] === "string")
- languages.push(this[lang]);
- }
- return languages;
- },
- EN: "en",
- BG: "bg",
- DE: "de",
- TR: "tr",
- FR: "fr"
-}
+var DesktopSharingEventTypes = {
+ INIT: "ds.init",
+
+ SWITCHING_DONE: "ds.switching_done",
+
+ NEW_STREAM_CREATED: "ds.new_stream_created"
+};
+
+module.exports = DesktopSharingEventTypes;
},{}],110:[function(require,module,exports){
-var XMPPEvents = {
- CONNECTION_FAILED: "xmpp.connection.failed",
- CONFERENCE_CREATED: "xmpp.conferenceCreated.jingle",
- CALL_INCOMING: "xmpp.callincoming.jingle",
- DISPOSE_CONFERENCE: "xmpp.dispose_conference",
- GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown",
- KICKED: "xmpp.kicked",
- BRIDGE_DOWN: "xmpp.bridge_down",
- USER_ID_CHANGED: "xmpp.user_id_changed",
- // We joined the MUC
- MUC_JOINED: "xmpp.muc_joined",
- // A member joined the MUC
- MUC_MEMBER_JOINED: "xmpp.muc_member_joined",
- // A member left the MUC
- MUC_MEMBER_LEFT: "xmpp.muc_member_left",
- MUC_ROLE_CHANGED: "xmpp.muc_role_changed",
- MUC_DESTROYED: "xmpp.muc_destroyed",
- DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
- REMOTE_STATS: "xmpp.remote_stats",
- LOCAL_ROLE_CHANGED: "xmpp.localrole_changed",
- PRESENCE_STATUS: "xmpp.presence_status",
- RESERVATION_ERROR: "xmpp.room_reservation_error",
- SUBJECT_CHANGED: "xmpp.subject_changed",
- MESSAGE_RECEIVED: "xmpp.message_received",
- SENDING_CHAT_MESSAGE: "xmpp.sending_chat_message",
- PASSWORD_REQUIRED: "xmpp.password_required",
- AUTHENTICATION_REQUIRED: "xmpp.authentication_required",
- CHAT_ERROR_RECEIVED: "xmpp.chat_error_received",
- ETHERPAD: "xmpp.etherpad",
- DEVICE_AVAILABLE: "xmpp.device_available",
- PEERCONNECTION_READY: "xmpp.peerconnection_ready",
- CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed",
- AUDIO_MUTED: "xmpp.audio_muted",
- VIDEO_MUTED: "xmpp.video_muted",
- AUDIO_MUTED_BY_FOCUS: "xmpp.audio_muted_by_focus",
- START_MUTED_SETTING_CHANGED: "xmpp.start_muted_setting_changed",
- START_MUTED_FROM_FOCUS: "xmpp.start_muted_from_focus",
- SET_LOCAL_DESCRIPTION_ERROR: 'xmpp.set_local_description_error',
- SET_REMOTE_DESCRIPTION_ERROR: 'xmpp.set_remote_description_error',
- CREATE_ANSWER_ERROR: 'xmpp.create_answer_error',
- JINGLE_FATAL_ERROR: 'xmpp.jingle_fatal_error',
- PROMPT_FOR_LOGIN: 'xmpp.prompt_for_login',
- FOCUS_DISCONNECTED: 'xmpp.focus_disconnected',
- ROOM_JOIN_ERROR: 'xmpp.room_join_error',
- ROOM_CONNECT_ERROR: 'xmpp.room_connect_error',
- // xmpp is connected and obtained user media
- READY_TO_JOIN: 'xmpp.ready_to_join'
-};
+var Events = {
+ DTMF_SUPPORT_CHANGED: "members.dtmf_support_changed"
+};
+
+module.exports = Events;
+
+},{}],111:[function(require,module,exports){
+module.exports = {
+ getLanguages : function () {
+ var languages = [];
+ for(var lang in this)
+ {
+ if(typeof this[lang] === "string")
+ languages.push(this[lang]);
+ }
+ return languages;
+ },
+ EN: "en",
+ BG: "bg",
+ DE: "de",
+ TR: "tr",
+ FR: "fr"
+}
+},{}],112:[function(require,module,exports){
+var XMPPEvents = {
+ CONNECTION_FAILED: "xmpp.connection.failed",
+ CONFERENCE_CREATED: "xmpp.conferenceCreated.jingle",
+ CALL_INCOMING: "xmpp.callincoming.jingle",
+ DISPOSE_CONFERENCE: "xmpp.dispose_conference",
+ GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown",
+ KICKED: "xmpp.kicked",
+ BRIDGE_DOWN: "xmpp.bridge_down",
+ USER_ID_CHANGED: "xmpp.user_id_changed",
+ // We joined the MUC
+ MUC_JOINED: "xmpp.muc_joined",
+ // A member joined the MUC
+ MUC_MEMBER_JOINED: "xmpp.muc_member_joined",
+ // A member left the MUC
+ MUC_MEMBER_LEFT: "xmpp.muc_member_left",
+ MUC_ROLE_CHANGED: "xmpp.muc_role_changed",
+ MUC_DESTROYED: "xmpp.muc_destroyed",
+ DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
+ REMOTE_STATS: "xmpp.remote_stats",
+ LOCAL_ROLE_CHANGED: "xmpp.localrole_changed",
+ PRESENCE_STATUS: "xmpp.presence_status",
+ RESERVATION_ERROR: "xmpp.room_reservation_error",
+ SUBJECT_CHANGED: "xmpp.subject_changed",
+ MESSAGE_RECEIVED: "xmpp.message_received",
+ SENDING_CHAT_MESSAGE: "xmpp.sending_chat_message",
+ PASSWORD_REQUIRED: "xmpp.password_required",
+ AUTHENTICATION_REQUIRED: "xmpp.authentication_required",
+ CHAT_ERROR_RECEIVED: "xmpp.chat_error_received",
+ ETHERPAD: "xmpp.etherpad",
+ DEVICE_AVAILABLE: "xmpp.device_available",
+ PEERCONNECTION_READY: "xmpp.peerconnection_ready",
+ CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed",
+ AUDIO_MUTED: "xmpp.audio_muted",
+ VIDEO_MUTED: "xmpp.video_muted",
+ AUDIO_MUTED_BY_FOCUS: "xmpp.audio_muted_by_focus",
+ START_MUTED_SETTING_CHANGED: "xmpp.start_muted_setting_changed",
+ START_MUTED_FROM_FOCUS: "xmpp.start_muted_from_focus",
+ SET_LOCAL_DESCRIPTION_ERROR: 'xmpp.set_local_description_error',
+ SET_REMOTE_DESCRIPTION_ERROR: 'xmpp.set_remote_description_error',
+ CREATE_ANSWER_ERROR: 'xmpp.create_answer_error',
+ JINGLE_FATAL_ERROR: 'xmpp.jingle_fatal_error',
+ PROMPT_FOR_LOGIN: 'xmpp.prompt_for_login',
+ FOCUS_DISCONNECTED: 'xmpp.focus_disconnected',
+ ROOM_JOIN_ERROR: 'xmpp.room_join_error',
+ ROOM_CONNECT_ERROR: 'xmpp.room_connect_error',
+ // xmpp is connected and obtained user media
+ READY_TO_JOIN: 'xmpp.ready_to_join'
+};
module.exports = XMPPEvents;
-},{}]},{},[3])(3)
+},{}],113:[function(require,module,exports){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+function EventEmitter() {
+ this._events = this._events || {};
+ this._maxListeners = this._maxListeners || undefined;
+}
+module.exports = EventEmitter;
+
+// Backwards-compat with node 0.10.x
+EventEmitter.EventEmitter = EventEmitter;
+
+EventEmitter.prototype._events = undefined;
+EventEmitter.prototype._maxListeners = undefined;
+
+// By default EventEmitters will print a warning if more than 10 listeners are
+// added to it. This is a useful default which helps finding memory leaks.
+EventEmitter.defaultMaxListeners = 10;
+
+// Obviously not all Emitters should be limited to 10. This function allows
+// that to be increased. Set to zero for unlimited.
+EventEmitter.prototype.setMaxListeners = function(n) {
+ if (!isNumber(n) || n < 0 || isNaN(n))
+ throw TypeError('n must be a positive number');
+ this._maxListeners = n;
+ return this;
+};
+
+EventEmitter.prototype.emit = function(type) {
+ var er, handler, len, args, i, listeners;
+
+ if (!this._events)
+ this._events = {};
+
+ // If there is no 'error' event listener then throw.
+ if (type === 'error') {
+ if (!this._events.error ||
+ (isObject(this._events.error) && !this._events.error.length)) {
+ er = arguments[1];
+ if (er instanceof Error) {
+ throw er; // Unhandled 'error' event
+ }
+ throw TypeError('Uncaught, unspecified "error" event.');
+ }
+ }
+
+ handler = this._events[type];
+
+ if (isUndefined(handler))
+ return false;
+
+ if (isFunction(handler)) {
+ switch (arguments.length) {
+ // fast cases
+ case 1:
+ handler.call(this);
+ break;
+ case 2:
+ handler.call(this, arguments[1]);
+ break;
+ case 3:
+ handler.call(this, arguments[1], arguments[2]);
+ break;
+ // slower
+ default:
+ len = arguments.length;
+ args = new Array(len - 1);
+ for (i = 1; i < len; i++)
+ args[i - 1] = arguments[i];
+ handler.apply(this, args);
+ }
+ } else if (isObject(handler)) {
+ len = arguments.length;
+ args = new Array(len - 1);
+ for (i = 1; i < len; i++)
+ args[i - 1] = arguments[i];
+
+ listeners = handler.slice();
+ len = listeners.length;
+ for (i = 0; i < len; i++)
+ listeners[i].apply(this, args);
+ }
+
+ return true;
+};
+
+EventEmitter.prototype.addListener = function(type, listener) {
+ var m;
+
+ if (!isFunction(listener))
+ throw TypeError('listener must be a function');
+
+ if (!this._events)
+ this._events = {};
+
+ // To avoid recursion in the case that type === "newListener"! Before
+ // adding it to the listeners, first emit "newListener".
+ if (this._events.newListener)
+ this.emit('newListener', type,
+ isFunction(listener.listener) ?
+ listener.listener : listener);
+
+ if (!this._events[type])
+ // Optimize the case of one listener. Don't need the extra array object.
+ this._events[type] = listener;
+ else if (isObject(this._events[type]))
+ // If we've already got an array, just append.
+ this._events[type].push(listener);
+ else
+ // Adding the second element, need to change to array.
+ this._events[type] = [this._events[type], listener];
+
+ // Check for listener leak
+ if (isObject(this._events[type]) && !this._events[type].warned) {
+ var m;
+ if (!isUndefined(this._maxListeners)) {
+ m = this._maxListeners;
+ } else {
+ m = EventEmitter.defaultMaxListeners;
+ }
+
+ if (m && m > 0 && this._events[type].length > m) {
+ this._events[type].warned = true;
+ console.error('(node) warning: possible EventEmitter memory ' +
+ 'leak detected. %d listeners added. ' +
+ 'Use emitter.setMaxListeners() to increase limit.',
+ this._events[type].length);
+ if (typeof console.trace === 'function') {
+ // not supported in IE 10
+ console.trace();
+ }
+ }
+ }
+
+ return this;
+};
+
+EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+
+EventEmitter.prototype.once = function(type, listener) {
+ if (!isFunction(listener))
+ throw TypeError('listener must be a function');
+
+ var fired = false;
+
+ function g() {
+ this.removeListener(type, g);
+
+ if (!fired) {
+ fired = true;
+ listener.apply(this, arguments);
+ }
+ }
+
+ g.listener = listener;
+ this.on(type, g);
+
+ return this;
+};
+
+// emits a 'removeListener' event iff the listener was removed
+EventEmitter.prototype.removeListener = function(type, listener) {
+ var list, position, length, i;
+
+ if (!isFunction(listener))
+ throw TypeError('listener must be a function');
+
+ if (!this._events || !this._events[type])
+ return this;
+
+ list = this._events[type];
+ length = list.length;
+ position = -1;
+
+ if (list === listener ||
+ (isFunction(list.listener) && list.listener === listener)) {
+ delete this._events[type];
+ if (this._events.removeListener)
+ this.emit('removeListener', type, listener);
+
+ } else if (isObject(list)) {
+ for (i = length; i-- > 0;) {
+ if (list[i] === listener ||
+ (list[i].listener && list[i].listener === listener)) {
+ position = i;
+ break;
+ }
+ }
+
+ if (position < 0)
+ return this;
+
+ if (list.length === 1) {
+ list.length = 0;
+ delete this._events[type];
+ } else {
+ list.splice(position, 1);
+ }
+
+ if (this._events.removeListener)
+ this.emit('removeListener', type, listener);
+ }
+
+ return this;
+};
+
+EventEmitter.prototype.removeAllListeners = function(type) {
+ var key, listeners;
+
+ if (!this._events)
+ return this;
+
+ // not listening for removeListener, no need to emit
+ if (!this._events.removeListener) {
+ if (arguments.length === 0)
+ this._events = {};
+ else if (this._events[type])
+ delete this._events[type];
+ return this;
+ }
+
+ // emit removeListener for all listeners on all events
+ if (arguments.length === 0) {
+ for (key in this._events) {
+ if (key === 'removeListener') continue;
+ this.removeAllListeners(key);
+ }
+ this.removeAllListeners('removeListener');
+ this._events = {};
+ return this;
+ }
+
+ listeners = this._events[type];
+
+ if (isFunction(listeners)) {
+ this.removeListener(type, listeners);
+ } else {
+ // LIFO order
+ while (listeners.length)
+ this.removeListener(type, listeners[listeners.length - 1]);
+ }
+ delete this._events[type];
+
+ return this;
+};
+
+EventEmitter.prototype.listeners = function(type) {
+ var ret;
+ if (!this._events || !this._events[type])
+ ret = [];
+ else if (isFunction(this._events[type]))
+ ret = [this._events[type]];
+ else
+ ret = this._events[type].slice();
+ return ret;
+};
+
+EventEmitter.listenerCount = function(emitter, type) {
+ var ret;
+ if (!emitter._events || !emitter._events[type])
+ ret = 0;
+ else if (isFunction(emitter._events[type]))
+ ret = 1;
+ else
+ ret = emitter._events[type].length;
+ return ret;
+};
+
+function isFunction(arg) {
+ return typeof arg === 'function';
+}
+
+function isNumber(arg) {
+ return typeof arg === 'number';
+}
+
+function isObject(arg) {
+ return typeof arg === 'object' && arg !== null;
+}
+
+function isUndefined(arg) {
+ return arg === void 0;
+}
+
+},{}],114:[function(require,module,exports){
+// shim for using process in browser
+
+var process = module.exports = {};
+var queue = [];
+var draining = false;
+
+function drainQueue() {
+ if (draining) {
+ return;
+ }
+ draining = true;
+ var currentQueue;
+ var len = queue.length;
+ while(len) {
+ currentQueue = queue;
+ queue = [];
+ var i = -1;
+ while (++i < len) {
+ currentQueue[i]();
+ }
+ len = queue.length;
+ }
+ draining = false;
+}
+process.nextTick = function (fun) {
+ queue.push(fun);
+ if (!draining) {
+ setTimeout(drainQueue, 0);
+ }
+};
+
+process.title = 'browser';
+process.browser = true;
+process.env = {};
+process.argv = [];
+process.version = ''; // empty string to avoid regexp issues
+
+function noop() {}
+
+process.on = noop;
+process.addListener = noop;
+process.once = noop;
+process.off = noop;
+process.removeListener = noop;
+process.removeAllListeners = noop;
+process.emit = noop;
+
+process.binding = function (name) {
+ throw new Error('process.binding is not supported');
+};
+
+// TODO(shtylman)
+process.cwd = function () { return '/' };
+process.chdir = function (dir) {
+ throw new Error('process.chdir is not supported');
+};
+process.umask = function() { return 0; };
+
+},{}]},{},[1])(1)
});
\ No newline at end of file
diff --git a/modules/UI/UI.js b/modules/UI/UI.js
index a78878b94..0bc1d2801 100644
--- a/modules/UI/UI.js
+++ b/modules/UI/UI.js
@@ -339,6 +339,10 @@ function registerListeners() {
UI.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) {
APP.xmpp.addToPresence("displayName", nickname);
});
+
+ UI.addListener(UIEvents.LARGEVIDEO_INIT, function () {
+ AudioLevels.init();
+ });
}
@@ -386,29 +390,6 @@ UI.start = function (init) {
return;
}
- if (interfaceConfig.SHOW_JITSI_WATERMARK) {
- var leftWatermarkDiv
- = $("#largeVideoContainer div[class='watermark leftwatermark']");
-
- leftWatermarkDiv.css({display: 'block'});
- leftWatermarkDiv.parent().get(0).href
- = interfaceConfig.JITSI_WATERMARK_LINK;
- }
-
- if (interfaceConfig.SHOW_BRAND_WATERMARK) {
- var rightWatermarkDiv
- = $("#largeVideoContainer div[class='watermark rightwatermark']");
-
- rightWatermarkDiv.css({display: 'block'});
- rightWatermarkDiv.parent().get(0).href
- = interfaceConfig.BRAND_WATERMARK_LINK;
- rightWatermarkDiv.get(0).style.backgroundImage
- = "url(images/rightwatermark.png)";
- }
-
- if (interfaceConfig.SHOW_POWERED_BY) {
- $("#largeVideoContainer>a[class='poweredby']").css({display: 'block'});
- }
$("#welcome_page").hide();
@@ -418,10 +399,12 @@ UI.start = function (init) {
// Set the defaults for prompt dialogs.
jQuery.prompt.setDefaults({persistent: false});
- VideoLayout.init(eventEmitter);
- AudioLevels.init();
- NicknameHandler.init(eventEmitter);
+
registerListeners();
+
+ VideoLayout.init(eventEmitter);
+ NicknameHandler.init(eventEmitter);
+
bindEvents();
setupPrezi();
setupToolbars();
@@ -452,10 +435,6 @@ UI.start = function (init) {
$('#notice').css({display: 'block'});
}
- if (!RTCBrowserType.isIExplorer()) {
- document.getElementById('largeVideo').volume = 0;
- }
-
if(config.requireDisplayName) {
var currentSettings = Settings.getSettings();
if (!currentSettings.displayName) {
diff --git a/modules/UI/audio_levels/AudioLevels.js b/modules/UI/audio_levels/AudioLevels.js
index 9bc9b264d..1cf83078d 100644
--- a/modules/UI/audio_levels/AudioLevels.js
+++ b/modules/UI/audio_levels/AudioLevels.js
@@ -1,6 +1,6 @@
var CanvasUtil = require("./CanvasUtils");
-var ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
+var ASDrawContext = null;
function initActiveSpeakerAudioLevels() {
var ASRadius = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2;
@@ -24,6 +24,7 @@ var AudioLevels = (function(my) {
my.LOCAL_LEVEL = 'local';
my.init = function () {
+ ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
initActiveSpeakerAudioLevels();
}
@@ -120,7 +121,7 @@ var AudioLevels = (function(my) {
};
my.updateActiveSpeakerAudioLevel = function(audioLevel) {
- if($("#activeSpeaker").css("visibility") == "hidden")
+ if($("#activeSpeaker").css("visibility") == "hidden" || ASDrawContext === null)
return;
diff --git a/modules/UI/etherpad/Etherpad.js b/modules/UI/etherpad/Etherpad.js
index 383f2e413..f8a69d408 100644
--- a/modules/UI/etherpad/Etherpad.js
+++ b/modules/UI/etherpad/Etherpad.js
@@ -38,31 +38,22 @@ function enableEtherpadButton() {
* Creates the IFrame for the etherpad.
*/
function createIFrame() {
- etherpadIFrame = document.createElement('iframe');
- etherpadIFrame.src = domain + etherpadName + options;
- etherpadIFrame.frameBorder = 0;
- etherpadIFrame.scrolling = "no";
- etherpadIFrame.width = $('#largeVideoContainer').width() || 640;
- etherpadIFrame.height = $('#largeVideoContainer').height() || 480;
- etherpadIFrame.setAttribute('style', 'visibility: hidden;');
+ etherpadIFrame = VideoLayout.createEtherpadIframe(
+ domain + etherpadName + options, function() {
- document.getElementById('etherpad').appendChild(etherpadIFrame);
-
- etherpadIFrame.onload = function() {
-
- document.domain = document.domain;
- bubbleIframeMouseMove(etherpadIFrame);
- setTimeout(function() {
- // the iframes inside of the etherpad are
- // not yet loaded when the etherpad iframe is loaded
- var outer = etherpadIFrame.
- contentDocument.getElementsByName("ace_outer")[0];
- bubbleIframeMouseMove(outer);
- var inner = outer.
- contentDocument.getElementsByName("ace_inner")[0];
- bubbleIframeMouseMove(inner);
- }, 2000);
- };
+ document.domain = document.domain;
+ bubbleIframeMouseMove(etherpadIFrame);
+ setTimeout(function() {
+ // the iframes inside of the etherpad are
+ // not yet loaded when the etherpad iframe is loaded
+ var outer = etherpadIFrame.
+ contentDocument.getElementsByName("ace_outer")[0];
+ bubbleIframeMouseMove(outer);
+ var inner = outer.
+ contentDocument.getElementsByName("ace_inner")[0];
+ bubbleIframeMouseMove(inner);
+ }, 2000);
+ });
}
function bubbleIframeMouseMove(iframe){
@@ -93,15 +84,6 @@ function bubbleIframeMouseMove(iframe){
}
-/**
- * On video selected event.
- */
-$(document).bind('video.selected', function (event, isPresentation) {
- if (config.etherpad_base && etherpadIFrame && etherpadIFrame.style.visibility !== 'hidden')
- Etherpad.toggleEtherpad(isPresentation);
-});
-
-
var Etherpad = {
/**
* Initializes the etherpad.
@@ -132,49 +114,17 @@ var Etherpad = {
if (!etherpadIFrame)
createIFrame();
- var largeVideo = null;
- if (Prezi.isPresentationVisible())
- largeVideo = $('#presentation>iframe');
- else
- largeVideo = $('#largeVideo');
- if ($('#etherpad>iframe').css('visibility') === 'hidden') {
- $('#activeSpeaker').css('visibility', 'hidden');
- largeVideo.fadeOut(300, function () {
- if (Prezi.isPresentationVisible()) {
- largeVideo.css({opacity: '0'});
- } else {
- VideoLayout.setLargeVideoVisible(false);
- }
- });
-
- $('#etherpad>iframe').fadeIn(300, function () {
- document.body.style.background = '#eeeeee';
- $('#etherpad>iframe').css({visibility: 'visible'});
- $('#etherpad').css({zIndex: 2});
- });
+ if(VideoLayout.getLargeVideoState() === "etherpad")
+ {
+ VideoLayout.setLargeVideoState("video");
}
- else if ($('#etherpad>iframe')) {
- $('#etherpad>iframe').fadeOut(300, function () {
- $('#etherpad>iframe').css({visibility: 'hidden'});
- $('#etherpad').css({zIndex: 0});
- document.body.style.background = 'black';
- });
-
- if (!isPresentation) {
- $('#largeVideo').fadeIn(300, function () {
- VideoLayout.setLargeVideoVisible(true);
- });
- }
+ else
+ {
+ VideoLayout.setLargeVideoState("etherpad");
}
resize();
- },
-
- isVisible: function() {
- var etherpadIframe = $('#etherpad>iframe');
- return etherpadIframe && etherpadIframe.is(':visible');
}
-
};
module.exports = Etherpad;
diff --git a/modules/UI/prezi/Prezi.js b/modules/UI/prezi/Prezi.js
index 3935d6fe3..9e2259a2d 100644
--- a/modules/UI/prezi/Prezi.js
+++ b/modules/UI/prezi/Prezi.js
@@ -6,6 +6,20 @@ var PreziPlayer = require("./PreziPlayer");
var preziPlayer = null;
+
+/**
+ * Shows/hides a presentation.
+ */
+function setPresentationVisible(visible) {
+
+ if (visible) {
+ VideoLayout.setLargeVideoState("prezi");
+ }
+ else {
+ VideoLayout.setLargeVideoState("video");
+ }
+}
+
var Prezi = {
@@ -165,18 +179,14 @@ function presentationAdded(event, jid, presUrl, currentSlide) {
+ Strophe.getResourceFromJid(jid)
+ '_' + presId;
-
-
-
VideoLayout.addPreziContainer(elementId);
- VideoLayout.resizeThumbnails();
var controlsEnabled = false;
if (jid === APP.xmpp.myJid())
controlsEnabled = true;
setPresentationVisible(true);
- $('#largeVideoContainer').hover(
+ VideoLayout.setLargeVideoHover(
function (event) {
if (Prezi.isPresentationVisible()) {
var reloadButtonRight = window.innerWidth
@@ -302,38 +312,6 @@ function resize() {
}
}
-/**
- * Shows/hides a presentation.
- */
-function setPresentationVisible(visible) {
- var prezi = $('#presentation>iframe');
- if (visible) {
- // Trigger the video.selected event to indicate a change in the
- // large video.
- $(document).trigger("video.selected", [true]);
-
- $('#largeVideo').fadeOut(300);
- prezi.fadeIn(300, function() {
- prezi.css({opacity:'1'});
- ToolbarToggler.dockToolbar(true);
- VideoLayout.setLargeVideoVisible(false);
- });
- $('#activeSpeaker').css('visibility', 'hidden');
- }
- else {
- if (prezi.css('opacity') == '1') {
- prezi.fadeOut(300, function () {
- prezi.css({opacity:'0'});
- $('#reloadPresentation').css({display:'none'});
- $('#largeVideo').fadeIn(300, function() {
- VideoLayout.setLargeVideoVisible(true);
- ToolbarToggler.dockToolbar(false);
- });
- });
- }
- }
-}
-
/**
* Presentation has been removed.
*/
@@ -358,15 +336,6 @@ $(document).bind('gotoslide.muc', function (event, jid, presUrl, current) {
}
});
-/**
- * On video selected event.
- */
-$(document).bind('video.selected', function (event, isPresentation) {
- if (!isPresentation && $('#presentation>iframe')) {
- setPresentationVisible(false);
- }
-});
-
$(window).resize(function () {
resize();
});
diff --git a/modules/UI/videolayout/LargeVideo.js b/modules/UI/videolayout/LargeVideo.js
index 1a9c4aa1f..c45e1f1cd 100644
--- a/modules/UI/videolayout/LargeVideo.js
+++ b/modules/UI/videolayout/LargeVideo.js
@@ -3,6 +3,7 @@ var RTCBrowserType = require("../../RTC/RTCBrowserType");
var UIUtil = require("../util/UIUtil");
var UIEvents = require("../../../service/UI/UIEvents");
var xmpp = require("../../xmpp/xmpp");
+var ToolbarToggler = require("../toolbars/ToolbarToggler");
// FIXME: With Temasys we have to re-select everytime
//var video = $('#largeVideo');
@@ -12,9 +13,45 @@ var currentVideoHeight = null;
// By default we use camera
var getVideoSize = getCameraVideoSize;
var getVideoPosition = getCameraVideoPosition;
+/**
+ * The small video instance that is displayed in the large video
+ * @type {SmallVideo}
+ */
var currentSmallVideo = null;
+/**
+ * Indicates whether the large video is enabled.
+ * @type {boolean}
+ */
+var isEnabled = true;
+/**
+ * Current large video state.
+ * Possible values - video, prezi or etherpad.
+ * @type {string}
+ */
+var state = "video";
-
+/**
+ * Returns the html element associated with the passed state of large video
+ * @param state the state.
+ * @returns {JQuery|*|jQuery|HTMLElement} the container.
+ */
+function getContainerByState(state)
+{
+ var selector = null;
+ switch (state)
+ {
+ case "video":
+ selector = "#largeVideo";
+ break;
+ case "etherpad":
+ selector = "#etherpad>iframe";
+ break;
+ case "prezi":
+ selector = "#presentation>iframe";
+ break;
+ }
+ return (selector !== null)? $(selector) : null;
+}
/**
* Sets the size and position of the given video element.
@@ -204,6 +241,10 @@ function updateActiveSpeakerAvatarSrc() {
}
}
+/**
+ * Change the video source of the large video.
+ * @param isVisible
+ */
function changeVideo(isVisible) {
if (!currentSmallVideo) {
@@ -248,11 +289,66 @@ function changeVideo(isVisible) {
}
}
+/**
+ * Creates the html elements for the large video.
+ */
+function createLargeVideoHTML()
+{
+ var html = '';
+ $(html).prependTo("#videospace");
+
+ if (interfaceConfig.SHOW_JITSI_WATERMARK) {
+ var leftWatermarkDiv
+ = $("#largeVideoContainer div[class='watermark leftwatermark']");
+
+ leftWatermarkDiv.css({display: 'block'});
+ leftWatermarkDiv.parent().get(0).href
+ = interfaceConfig.JITSI_WATERMARK_LINK;
+ }
+
+ if (interfaceConfig.SHOW_BRAND_WATERMARK) {
+ var rightWatermarkDiv
+ = $("#largeVideoContainer div[class='watermark rightwatermark']");
+
+ rightWatermarkDiv.css({display: 'block'});
+ rightWatermarkDiv.parent().get(0).href
+ = interfaceConfig.BRAND_WATERMARK_LINK;
+ rightWatermarkDiv.get(0).style.backgroundImage
+ = "url(images/rightwatermark.png)";
+ }
+
+ if (interfaceConfig.SHOW_POWERED_BY) {
+ $("#largeVideoContainer>a[class='poweredby']").css({display: 'block'});
+ }
+
+ if (!RTCBrowserType.isIExplorer()) {
+ $('#largeVideo').volume = 0;
+ }
+}
+
var LargeVideo = {
init: function (VideoLayout, emitter) {
+ if(!isEnabled)
+ return;
+ createLargeVideoHTML();
+
this.VideoLayout = VideoLayout;
this.eventEmitter = emitter;
+ this.eventEmitter.emit(UIEvents.LARGEVIDEO_INIT);
var self = this;
// Listen for large video size updates
var largeVideo = $('#largeVideo')[0];
@@ -285,6 +381,8 @@ var LargeVideo = {
* Updates the large video with the given new video source.
*/
updateLargeVideo: function (resourceJid, forceUpdate) {
+ if(!isEnabled)
+ return;
var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid);
console.log('hover in ' + resourceJid + ', video: ', newSmallVideo);
@@ -324,6 +422,8 @@ var LargeVideo = {
* Shows/hides the large video.
*/
setLargeVideoVisible: function(isVisible) {
+ if(!isEnabled)
+ return;
if (isVisible) {
$('#largeVideo').css({visibility: 'visible'});
$('.watermark').css({visibility: 'visible'});
@@ -339,6 +439,8 @@ var LargeVideo = {
}
},
onVideoTypeChanged: function (jid) {
+ if(!isEnabled)
+ return;
var resourceJid = Strophe.getResourceFromJid(jid);
if (LargeVideo.isCurrentlyOnLarge(resourceJid))
{
@@ -360,6 +462,8 @@ var LargeVideo = {
*/
position: function (videoWidth, videoHeight,
videoSpaceWidth, videoSpaceHeight, animate) {
+ if(!isEnabled)
+ return;
if(!videoSpaceWidth)
videoSpaceWidth = $('#videospace').width();
if(!videoSpaceHeight)
@@ -386,13 +490,9 @@ var LargeVideo = {
largeVideoHeight,
horizontalIndent, verticalIndent, animate);
},
-
- isLargeVideoOnTop: function () {
- var Etherpad = require("../etherpad/Etherpad");
- var Prezi = require("../prezi/Prezi");
- return !Prezi.isPresentationVisible() && !Etherpad.isVisible();
- },
resize: function (animate, isVisible, completeFunction) {
+ if(!isEnabled)
+ return;
var availableHeight = window.innerHeight;
var availableWidth = UIUtil.getAvailableVideoWidth(isVisible);
@@ -437,6 +537,8 @@ var LargeVideo = {
},
resizeVideoAreaAnimated: function (isVisible, completeFunction) {
+ if(!isEnabled)
+ return;
var size = this.resize(true, isVisible, completeFunction);
this.position(null, null, size[0], size[1], true);
},
@@ -444,22 +546,152 @@ var LargeVideo = {
return currentSmallVideo ? currentSmallVideo.getResourceJid() : null;
},
updateAvatar: function (resourceJid) {
+ if(!isEnabled)
+ return;
if (resourceJid === this.getResourceJid()) {
updateActiveSpeakerAvatarSrc();
}
},
showAvatar: function (resourceJid, show) {
+ if(!isEnabled)
+ return;
if(this.getResourceJid() === resourceJid
- && LargeVideo.isLargeVideoOnTop())
+ && state === "video")
{
$("#largeVideo").css("visibility", show ? "hidden" : "visible");
$('#activeSpeaker').css("visibility", show ? "visible" : "hidden");
return true;
}
return false;
+ },
+ /**
+ * Disables the large video
+ */
+ disable: function () {
+ isEnabled = false;
+ },
+ /**
+ * Enables the large video
+ */
+ enable: function () {
+ isEnabled = true;
+ },
+ /**
+ * Returns true if the video is enabled.
+ */
+ isEnabled: function () {
+ return isEnabled;
+ },
+ /**
+ * Creates the iframe used by the etherpad
+ * @param src the value for src attribute
+ * @param onloadHandler handler executed when the iframe loads it content
+ * @returns {HTMLElement} the iframe
+ */
+ createEtherpadIframe: function (src, onloadHandler) {
+ if(!isEnabled)
+ return;
+
+ var etherpadIFrame = document.createElement('iframe');
+ etherpadIFrame.src = src;
+ etherpadIFrame.frameBorder = 0;
+ etherpadIFrame.scrolling = "no";
+ etherpadIFrame.width = $('#largeVideoContainer').width() || 640;
+ etherpadIFrame.height = $('#largeVideoContainer').height() || 480;
+ etherpadIFrame.setAttribute('style', 'visibility: hidden;');
+
+ document.getElementById('etherpad').appendChild(etherpadIFrame);
+
+ etherpadIFrame.onload = onloadHandler;
+
+ return etherpadIFrame;
+ },
+ /**
+ * Changes the state of the large video.
+ * Possible values - video, prezi, etherpad.
+ * @param newState - the new state
+ */
+ setState: function (newState) {
+ if(state === newState)
+ return;
+ var currentContainer = getContainerByState(state);
+ if(!currentContainer)
+ return;
+
+ var self = this;
+ var oldState = state;
+
+ switch (newState)
+ {
+ case "etherpad":
+ $('#activeSpeaker').css('visibility', 'hidden');
+ currentContainer.fadeOut(300, function () {
+ if (oldState === "prezi") {
+ currentContainer.css({opacity: '0'});
+ $('#reloadPresentation').css({display: 'none'});
+ }
+ else {
+ self.setLargeVideoVisible(false);
+ }
+ });
+
+ $('#etherpad>iframe').fadeIn(300, function () {
+ document.body.style.background = '#eeeeee';
+ $('#etherpad>iframe').css({visibility: 'visible'});
+ $('#etherpad').css({zIndex: 2});
+ });
+ break;
+ case "prezi":
+ var prezi = $('#presentation>iframe');
+ currentContainer.fadeOut(300, function() {
+ document.body.style.background = 'black';
+ });
+ prezi.fadeIn(300, function() {
+ prezi.css({opacity:'1'});
+ ToolbarToggler.dockToolbar(true);//fix that
+ self.setLargeVideoVisible(false);
+ $('#etherpad>iframe').css({visibility: 'hidden'});
+ $('#etherpad').css({zIndex: 0});
+ });
+ $('#activeSpeaker').css('visibility', 'hidden');
+ break;
+
+ case "video":
+ currentContainer.fadeOut(300, function () {
+ $('#presentation>iframe').css({opacity:'0'});
+ $('#reloadPresentation').css({display:'none'});
+ $('#etherpad>iframe').css({visibility: 'hidden'});
+ $('#etherpad').css({zIndex: 0});
+ document.body.style.background = 'black';
+ ToolbarToggler.dockToolbar(false);//fix that
+ });
+ $('#largeVideo').fadeIn(300, function () {
+ self.setLargeVideoVisible(true);
+ });
+ break;
+ }
+
+ state = newState;
+
+ },
+ /**
+ * Returns the current state of the large video.
+ * @returns {string} the current state - video, prezi or etherpad.
+ */
+ getState: function () {
+ return state;
+ },
+ /**
+ * Sets hover handlers for the large video container div.
+ * @param inHandler
+ * @param outHandler
+ */
+ setHover: function(inHandler, outHandler)
+ {
+ $('#largeVideoContainer').hover(inHandler, outHandler);
}
-}
+};
module.exports = LargeVideo;
\ No newline at end of file
diff --git a/modules/UI/videolayout/SmallVideo.js b/modules/UI/videolayout/SmallVideo.js
index 26df81eab..cbe2d619a 100644
--- a/modules/UI/videolayout/SmallVideo.js
+++ b/modules/UI/videolayout/SmallVideo.js
@@ -210,7 +210,7 @@ SmallVideo.prototype.enableDominantSpeaker = function (isEnable)
}
if (isEnable) {
- this.showDisplayName(LargeVideo.isLargeVideoOnTop());
+ this.showDisplayName(LargeVideo.getState() === "video");
if (!this.container.classList.contains("dominantspeaker"))
this.container.classList.add("dominantspeaker");
diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js
index f1f1a95f6..76c46dd0a 100644
--- a/modules/UI/videolayout/VideoLayout.js
+++ b/modules/UI/videolayout/VideoLayout.js
@@ -1,5 +1,4 @@
var AudioLevels = require("../audio_levels/AudioLevels");
-var Avatar = require("../avatar/Avatar");
var ContactList = require("../side_pannels/contactlist/ContactList");
var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
var UIEvents = require("../../../service/UI/UIEvents");
@@ -14,9 +13,6 @@ var LocalVideo = require("./LocalVideo");
var remoteVideos = {};
var localVideoThumbnail = null;
-
-
-
var currentDominantSpeaker = null;
var lastNCount = config.channelLastN;
var localLastNCount = config.channelLastN;
@@ -26,6 +22,8 @@ var lastNPickupJid = null;
var eventEmitter = null;
+var showLargeVideo = true;
+
/**
* Currently focused video jid
@@ -37,8 +35,10 @@ var VideoLayout = (function (my) {
my.init = function (emitter) {
eventEmitter = emitter;
localVideoThumbnail = new LocalVideo(VideoLayout);
- VideoLayout.resizeLargeVideoContainer();
+
LargeVideo.init(VideoLayout, emitter);
+ VideoLayout.resizeLargeVideoContainer();
+
};
my.isInLastN = function(resource) {
@@ -232,9 +232,7 @@ var VideoLayout = (function (my) {
}
}
- // Triggers a "video.selected" event. The "false" parameter indicates
- // this isn't a prezi.
- $(document).trigger("video.selected", [false]);
+ LargeVideo.setState("video");
LargeVideo.updateLargeVideo(resourceJid);
@@ -583,11 +581,11 @@ var VideoLayout = (function (my) {
var currentJID = APP.xmpp.findJidFromResource(currentDominantSpeaker);
var newJID = APP.xmpp.findJidFromResource(resourceJid);
if(currentDominantSpeaker && (!members || !members[currentJID] ||
- !members[currentJID].displayName)) {
+ !members[currentJID].displayName) && remoteVideos[resourceJid]) {
remoteVideos[resourceJid].setDisplayName(null);
}
if(resourceJid && (!members || !members[newJID] ||
- !members[newJID].displayName)) {
+ !members[newJID].displayName) && remoteVideos[resourceJid]) {
remoteVideos[resourceJid].setDisplayName(null,
interfaceConfig.DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME);
}
@@ -855,7 +853,9 @@ var VideoLayout = (function (my) {
};
my.addPreziContainer = function (id) {
- return RemoteVideo.createContainer(id);
+ var container = RemoteVideo.createContainer(id);
+ VideoLayout.resizeThumbnails();
+ return container;
};
my.setLargeVideoVisible = function (isVisible) {
@@ -905,6 +905,23 @@ var VideoLayout = (function (my) {
LargeVideo.updateAvatar(resourceJid, thumbUrl);
};
+ my.createEtherpadIframe = function(src, onloadHandler)
+ {
+ return LargeVideo.createEtherpadIframe(src, onloadHandler);
+ };
+
+ my.setLargeVideoState = function (state) {
+ LargeVideo.setState(state);
+ };
+
+ my.getLargeVideoState = function () {
+ return LargeVideo.getState();
+ };
+
+ my.setLargeVideoHover = function (inHandler, outHandler) {
+ LargeVideo.setHover(inHandler, outHandler);
+ };
+
return my;
}(VideoLayout || {}));
diff --git a/service/UI/UIEvents.js b/service/UI/UIEvents.js
index 79260f465..4c3c36e49 100644
--- a/service/UI/UIEvents.js
+++ b/service/UI/UIEvents.js
@@ -1,6 +1,7 @@
var UIEvents = {
NICKNAME_CHANGED: "UI.nickname_changed",
SELECTED_ENDPOINT: "UI.selected_endpoint",
- PINNED_ENDPOINT: "UI.pinned_endpoint"
+ PINNED_ENDPOINT: "UI.pinned_endpoint",
+ LARGEVIDEO_INIT: "UI.largevideo_init"
};
module.exports = UIEvents;
\ No newline at end of file