From 6b94d3fe474744a64f8dbfc873f668f7540aa1cd Mon Sep 17 00:00:00 2001 From: damencho Date: Wed, 2 Sep 2015 16:05:00 -0500 Subject: [PATCH] Stores info from last presence received for participant and dispatch it if any on creating remote streams to reflect video muted state. Race condition detected by tests where we miss presence info cause stream was not created when we receive presence packet. --- index.html | 2 +- libs/app.bundle.js | 40006 +++++++++++++++++---------------- modules/RTC/MediaStream.js | 5 +- modules/RTC/RTC.js | 13 +- modules/xmpp/strophe.emuc.js | 14 +- modules/xmpp/xmpp.js | 5 + 6 files changed, 20054 insertions(+), 19991 deletions(-) diff --git a/index.html b/index.html index fc5ce10f5..84af69383 100644 --- a/index.html +++ b/index.html @@ -20,7 +20,7 @@ - + diff --git a/libs/app.bundle.js b/libs/app.bundle.js index b46351486..57e5def8f 100644 --- a/libs/app.bundle.js +++ b/libs/app.bundle.js @@ -1,19691 +1,19340 @@ (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'; } -function isNumber(arg) { - return typeof arg === 'number'; + +$(document).ready(function () { + + var URLProcessor = require("./modules/config/URLProcessor"); + URLProcessor.setConfigParametersFromUrl(); + APP.init(); + + APP.translation.init(); + + if(APP.API.isEnabled()) + APP.API.init(); + + APP.UI.start(obtainConfigAndInit); + +}); + +$(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/config/HttpConfigFetch":42,"./modules/config/URLProcessor":43,"./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":71}],2:[function(require,module,exports){ +/* global APP */ +/** + * 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, + * toggleAudio: toggleAudio, + * toggleVideo: toggleVideo, + * toggleFilmStrip: toggleFilmStrip, + * toggleChat: toggleChat, + * toggleContactList: toggleContactList + * }} + */ +var commands = {}; + +function initCommands() { + commands = { + displayName: APP.UI.inputDisplayNameHandler, + toggleAudio: APP.UI.toggleAudio, + toggleVideo: 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 application. + * @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) { + var 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); + } } }; -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; -process.version = ''; // empty string to avoid regexp issues -process.versions = {}; - -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; }; - -},{}],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"); - this.configFetch = require("./modules/config/HttpConfigFetch"); - } -}; - -function init() { - - APP.desktopsharing.init(); - APP.RTC.start(); - APP.xmpp.start(); - APP.statistics.start(); - APP.connectionquality.init(); - APP.keyboardshortcut.init(); - APP.members.start(); -} - -/** - * If we have HTTP endpoint for getting confgi.json configured we're going to - * read it and override properties from config.js and interfaceConfig.js. - * If there is no endpoint we'll just continue with initialization. - * Keep in mind that if the endpoint has been configured and we fail to obtain - * the config for any reason then the conference won't start and error message - * will be displayed to the user. - */ -function obtainConfigAndInit() { - if (config.configLocation) { - APP.configFetch.obtainConfig( - config.configLocation, APP.UI.getRoomNode(), - // Get config result callback - function(success, error) { - if (success) { - init(); - } else { - // Show obtain config error, - // pass the error object for report - APP.UI.messageHandler.openReportDialog( - null, "dialog.connectError", error); - } - }); - } else { - init(); - } -} - - -$(document).ready(function () { - - var URLProcessor = require("./modules/config/URLProcessor"); - URLProcessor.setConfigParametersFromUrl(); - APP.init(); - - APP.translation.init(); - - if(APP.API.isEnabled()) - APP.API.init(); - - APP.UI.start(obtainConfigAndInit); - -}); - -$(window).bind('beforeunload', function () { - if(APP.API.isEnabled()) - APP.API.dispose(); -}); - -module.exports = APP; - - -},{"./modules/API/API":4,"./modules/DTMF/DTMF":5,"./modules/RTC/RTC":9,"./modules/UI/UI":13,"./modules/config/HttpConfigFetch":44,"./modules/config/URLProcessor":45,"./modules/connectionquality/connectionquality":47,"./modules/desktopsharing/desktopsharing":48,"./modules/keyboardshortcut/keyboardshortcut":49,"./modules/members/MemberList":50,"./modules/settings/Settings":51,"./modules/statistics/statistics":55,"./modules/translation/translation":56,"./modules/xmpp/xmpp":73}],4:[function(require,module,exports){ -/* global APP */ -/** - * 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, - * toggleAudio: toggleAudio, - * toggleVideo: toggleVideo, - * toggleFilmStrip: toggleFilmStrip, - * toggleChat: toggleChat, - * toggleContactList: toggleContactList - * }} - */ -var commands = {}; - -function initCommands() { - commands = { - displayName: APP.UI.inputDisplayNameHandler, - toggleAudio: APP.UI.toggleAudio, - toggleVideo: 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 application. - * @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) { - var 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":177}],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; - +},{"../../service/xmpp/XMPPEvents":172}],3:[function(require,module,exports){ +/* global APP */ -},{}],6:[function(require,module,exports){ -/* global config, APP, Strophe */ - -// 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; - - 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; - +/** + * 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; -},{"../../service/RTC/RTCEvents":168}],7:[function(require,module,exports){ -/* global APP */ -var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); -var RTCEvents = require("../../service/RTC/RTCEvents"); -var RTCBrowserType = require("./RTCBrowserType"); - -/** - * This implements 'onended' callback normally fired by WebRTC after the stream - * is stopped. There is no such behaviour yet in FF, so we have to add it. - * @param stream original WebRTC stream object to which 'onended' handling - * will be added. - */ -function implementOnEndedHandling(stream) { - var originalStop = stream.stop; - stream.stop = function () { - originalStop.apply(stream); - if (!stream.ended) { - stream.ended = true; - stream.onended(); - } - }; -} - -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(); - }; - if (RTCBrowserType.isFirefox()) { - implementOnEndedHandling(this.stream); - } -} - -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) -{ - var isAudio = this.isAudioStream(); - var eventType = isAudio ? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE; - - if ((window.location.protocol != "https:" && this.isGUMStream) || - (isAudio && this.isGUMStream) || this.videoType === "screen" || - // FIXME FF does not support 'removeStream' method used to mute - RTCBrowserType.isFirefox()) { - - var tracks = this.getTracks(); - for (var idx = 0; idx < tracks.length; idx++) { - tracks[idx].enabled = !mute; - } - this.eventEmitter.emit(eventType, mute); - } else { - if (mute) { - APP.xmpp.removeStream(this.stream); - this.stream.stop(); - this.eventEmitter.emit(eventType, true); - } else { - var self = this; - APP.RTC.rtcUtils.obtainAudioAndVideoPermissions( - (this.isAudioStream() ? ["audio"] : ["video"]), - function (stream) { - if (isAudio) { - APP.RTC.changeLocalAudio(stream, - function () { - self.eventEmitter.emit(eventType, false); - }); - } else { - APP.RTC.changeLocalVideo(stream, false, - function () { - self.eventEmitter.emit(eventType, false); - }); - } - }); - } - } -}; - -LocalStream.prototype.isMuted = function () { - var tracks = []; - if (this.isAudioStream()) { - 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":168,"../../service/RTC/StreamEventTypes.js":170,"./RTCBrowserType":10}],8:[function(require,module,exports){ -var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); - -/** - * 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":167}],9:[function(require,module,exports){ -/* global APP */ -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(true); - - 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" : "camera"); - 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; - } - }, - 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":167,"../../service/RTC/RTCEvents.js":168,"../../service/RTC/StreamEventTypes.js":170,"../../service/UI/UIEvents":171,"../../service/desktopsharing/DesktopSharingEventTypes":174,"../../service/xmpp/XMPPEvents":177,"./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 isAndroid; - -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(); - }, - - /** - * Whether the browser is running on an android device. - */ - isAndroid: function() { - return isAndroid; - } - - // 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(); -isAndroid = navigator.userAgent.indexOf('Android') != -1; - -module.exports = RTCBrowserType; -},{}],11:[function(require,module,exports){ -/* global config, require, attachMediaStream, getUserMedia */ -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) { - var isAndroid = RTCBrowserType.isAndroid(); - - if (Resolutions[resolution]) { - constraints.video.mandatory.minWidth = Resolutions[resolution].width; - constraints.video.mandatory.minHeight = Resolutions[resolution].height; - } - else if (isAndroid) { - // FIXME can't remember if the purpose of this was to always request - // low resolution on Android ? if yes it should be moved up front - 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) { - var constraints = {audio: false, video: false}; - - if (um.indexOf('video') >= 0) { - // same behaviour as true - constraints.video = { mandatory: {}, optional: [] }; - - constraints.video.optional.push({ googLeakyBucket: true }); - - setResolutionConstraints(constraints, resolution); - } - if (um.indexOf('audio') >= 0) { - if (!RTCBrowserType.isFirefox()) { - // same behaviour as true - constraints.audio = { mandatory: {}, optional: []}; - // 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} - ); - } else { - constraints.audio = 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 (bandwidth) { - if (!constraints.video) { - //same behaviour as true - constraints.video = {mandatory: {}, optional: []}; - } - 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) { - // same behaviour as true; - constraints.video = {mandatory: {}, optional: []}; - } - constraints.video.mandatory.minFrameRate = fps; - } - - // we turn audio for both audio and video tracks, the fake audio & video seems to work - // only when enabled in one getUserMedia call, we cannot get fake audio separate by fake video - // this later can be a problem with some of the tests - if(RTCBrowserType.isFirefox() && config.firefox_fake_device) - { - constraints.audio = true; - constraints.fake = true; - } - - return constraints; -} - - -function RTCUtils(RTCService, onTemasysPluginReady) -{ - var self = this; - this.service = RTCService; - if (RTCBrowserType.isFirefox()) { - var FFversion = RTCBrowserType.getFirefoxVersion(); - if (FFversion >= 40) { - 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 version too old: " + FFversion + ". Required >= 40."); - 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 (RTCBrowserType.isAndroid()) { - 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 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; - - var constraints = getConstraints( - um, resolution, bandwidth, fps, desktopStream); - - 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, 'camera', - 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 (var 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":169,"../xmpp/SDPUtil":62,"./RTCBrowserType":10,"./adapter.screenshare":12}],12:[function(require,module,exports){ -/*! adapterjs - custom version from - 2015-08-19 */ - -// 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.12.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'; -} - -AdapterJS.WebRTCPlugin.TAGS = { - NONE : 'none', - AUDIO : 'audio', - VIDEO : 'video' -}; - -// 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) { } - - var pluginInstallInterval = setInterval(function(){ - if(! isIE) { - navigator.plugins.refresh(false); - } - AdapterJS.WebRTCPlugin.isPluginInstalled( - AdapterJS.WebRTCPlugin.pluginInfo.prefix, - AdapterJS.WebRTCPlugin.pluginInfo.plugName, - function() { // plugin now installed - clearInterval(pluginInstallInterval); - AdapterJS.WebRTCPlugin.defineWebRTCInterface(); - }, - function() { - // still no plugin detected, nothing to do - }); - } , 500); - }); - } 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 if (navigator.mediaDevices && navigator.userAgent.match( - /Edge\/(\d+).(\d+)$/)) { - webrtcDetectedBrowser = 'edge'; - - webrtcDetectedVersion = - parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10); - - // the minimum version still supported by adapter. - webrtcMinimumVersion = 12; - - getUserMedia = navigator.getUserMedia; - - attachMediaStream = function(element, stream) { - element.srcObject = stream; - return element; - }; - reattachMediaStream = function(to, from) { - to.srcObject = from.srcObject; - 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 = '' + - ' ' + - ' ' + - ' ' + - '' + - '' + - // uncomment to be able to use virtual cams - (AdapterJS.options.getAllCams ? '':'') + - - ''; - 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 () { - if (AdapterJS.WebRTCPlugin.pluginState === - AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { - console.error("AdapterJS - WebRTC interface has already been defined"); - return; - } - - 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); // TODO: remove on 0.12.0 - streamId = stream.id; - } - - var elementId = element.id.length === 0 ? Math.random().toString(36).slice(2) : element.id; - var nodeName = element.nodeName.toLowerCase(); - if (nodeName !== 'object') { // not a plugin tag yet - var tag; - switch(nodeName) { - case 'audio': - tag = AdapterJS.WebRTCPlugin.TAGS.AUDIO; - break; - case 'video': - tag = AdapterJS.WebRTCPlugin.TAGS.VIDEO; - break; - default: - tag = AdapterJS.WebRTCPlugin.TAGS.NONE; - } - - 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 { // already an tag, just change the stream id - 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) {}; - newElement.onclick = (element.onclick) ? element.onclick : function (arg) {}; - if (isIE) { // on IE the event needs to be plugged manually - newElement.attachEvent('onplaying', newElement.onplaying); - newElement._TemOnClick = function (id) { - var arg = { - srcElement : document.getElementById(id) - }; - newElement.onclick(arg); - }; - } - - return newElement; - }; - - 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); -} - -},{}],13:[function(require,module,exports){ -/* global Strophe, APP, $, config, interfaceConfig, toastr */ -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 JitsiPopover = require("./util/JitsiPopover"); -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 roomNode = null; -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.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 userResource = APP.UI.getLargeVideoResource(); - eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, userResource); - }); - 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.getLargeVideoResource()); - }); - 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.MUC_MEMBER_LEFT, onMucMemberLeft); - APP.xmpp.addListener(XMPPEvents.PASSWORD_REQUIRED, onPasswordRequired); - APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad); - APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED, - onAuthenticationRequired); - APP.xmpp.addListener(XMPPEvents.PARTICIPANT_VIDEO_TYPE_CHANGED, - onPeerVideoTypeChanged); - APP.xmpp.addListener(XMPPEvents.DEVICE_AVAILABLE, - function (resource, devices) { - VideoLayout.setDeviceAvailabilityIcons(resource, devices); - }); - - APP.xmpp.addListener(XMPPEvents.PARTICIPANT_AUDIO_MUTED, - VideoLayout.onAudioMute); - APP.xmpp.addListener(XMPPEvents.PARTICIPANT_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.connectError", 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(); - }); - - if (!interfaceConfig.filmStripOnly) { - APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation); - APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError); - // Listens for video interruption events. - APP.xmpp.addListener(XMPPEvents.CONNECTION_INTERRUPTED, VideoLayout.onVideoInterrupted); - // Listens for video restores events. - APP.xmpp.addListener(XMPPEvents.CONNECTION_RESTORED, VideoLayout.onVideoRestored); - } -} - - -/** - * 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; - var setupWelcomePage = null; - if(config.enableWelcomePage && window.location.pathname == "/" && - (!window.localStorage.welcomePageDisabled || - window.localStorage.welcomePageDisabled == "false")) { - $("#videoconference_page").hide(); - if (!setupWelcomePage) - setupWelcomePage = require("./welcome_page/WelcomePage"); - setupWelcomePage(); - - return; - } - - $("#welcome_page").hide(); - - // Set the defaults for prompt dialogs. - $.prompt.setDefaults({persistent: false}); - - - registerListeners(); - - VideoLayout.init(eventEmitter); - NicknameHandler.init(eventEmitter); - - bindEvents(); - setupPrezi(); - if (!interfaceConfig.filmStripOnly) { - $("#videospace").mousemove(function () { - return ToolbarToggler.showToolbar(); - }); - setupToolbars(); - setupChat(); - // Display notice message at the top of the toolbar - if (config.noticeMessage) { - $('#noticeText').text(config.noticeMessage); - $('#notice').css({display: 'block'}); - } - $("#downloadlog").click(function (event) { - dump(event.target); - }); - } - else - { - $("#header").css("display", "none"); - $("#bottomToolbar").css("display", "none"); - $("#downloadlog").css("display", "none"); - $("#remoteVideos").css("padding", "0px 0px 18px 0px"); - $("#remoteVideos").css("right", "0px"); - messageHandler.disableNotifications(); - $('body').popover("disable"); -// $("[data-toggle=popover]").popover("disable"); - JitsiPopover.enabled = false; - } - - document.title = interfaceConfig.APP_NAME; - - - - - - if(config.requireDisplayName) { - var currentSettings = Settings.getSettings(); - if (!currentSettings.displayName) { - promptDisplayName(); - } - } - - init(); - - if (!interfaceConfig.filmStripOnly) { - 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 ? Strophe.getResourceFromJid(jid) : info.displayName; - - 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"); - - Toolbar.checkAutoRecord(); - } -} - -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 onPeerVideoTypeChanged(resourceJid, newVideoType) { - VideoLayout.onVideoTypeChanged(resourceJid, newVideoType); -} - -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.getLargeVideoResource = function () { - return VideoLayout.getLargeVideoResource(); -}; - -UI.getRoomNode = function () { - if (roomNode) - return roomNode; - 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); - } - } - return roomNode; -}; - -UI.generateRoomName = function () { - if (roomName) - return roomName; - var roomNode = UI.getRoomNode(); - 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.getJingleLog(); - var metadata = {}; - metadata.time = new Date(); - metadata.url = window.location.href; - metadata.ua = navigator.userAgent; - var log = APP.xmpp.getXmppLog(); - 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("#toolbar_button_mute", "icon-microphone icon-mic-disabled"); - })) { - // We still click the button. - UIUtil.buttonClick("#toolbar_button_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 = $('#toolbar_button_camera'); - 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":168,"../../service/RTC/StreamEventTypes":170,"../../service/UI/UIEvents":171,"../../service/connectionquality/CQEvents":173,"../../service/desktopsharing/DesktopSharingEventTypes":174,"../../service/members/Events":175,"../../service/xmpp/XMPPEvents":177,"../RTC/RTCBrowserType":10,"./../settings/Settings":51,"./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/JitsiPopover":32,"./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){ -/* global APP, interfaceConfig, $, Strophe */ -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":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.isMUCJoined()) { - 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":64,"./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":64,"../../xmpp/xmpp":73}],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 + " - using JID as ID"); - id = jid; - } - return 'https://www.gravatar.com/avatar/' + - MD5.hexdigest(id.trim().toLowerCase()) + - "?d=wavatar&size=" + (size || "30"); - } - -}; - - -module.exports = Avatar; -},{"../../settings/Settings":51}],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 (!$('#toolbar_button_etherpad').is(":visible")) - $('#toolbar_button_etherpad').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":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; - - -/** - * 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":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; - -},{}],22:[function(require,module,exports){ -/* global require, $ */ -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': '#toolbar_button_settings' - }; - - /** - * 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":51,"./chat/Chat":23,"./contactlist/ContactList":27,"./settings/SettingsMenu":28}],23:[function(require,module,exports){ -/* global APP, $, 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 = $('#toolbar_button_chat'); - var bottomGlower = $('#chatBottomButton'); - - if (unreadMessages) { - unreadMsgElement.innerHTML = unreadMessages.toString(); - unreadMsgBottomElement.innerHTML = unreadMessages.toString(); - - ToolbarToggler.dockToolbar(true); - - var chatButtonElement - = document.getElementById('toolbar_button_chat'); - 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; - } - } - }); - - var usermsg = $('#usermsg'); - 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 = - '
'+ - '' + - '
' + 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":171,"../../toolbars/ToolbarToggler":31,"../../util/NicknameHandler":34,"../../util/UIUtil":35,"../SidePanelToggler":22,"./Commands":24,"./Replacement":25,"./smileys.json":26}],24:[function(require,module,exports){ -/* global APP, require */ -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 a 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 -}; - -},{"./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|&lt;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 - } -} - -},{}],27:[function(require,module,exports){ -/* global $, APP, Strophe */ -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 {object} 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){ -/* global APP, $ */ -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 () { - var startMutedSelector = $("#startMutedOptions"); - startMutedSelector.before(generateLanguagesSelectBox()); - APP.translation.translateElement($("#languages_selectbox")); - $('#settingsmenu>input').keyup(function(event){ - if(event.keyCode === 13) {//enter - SettingsMenu.update(); - } - }); - - if (APP.xmpp.isModerator()) { - startMutedSelector.css("display", "block"); - } - else { - startMutedSelector.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":176,"../../avatar/Avatar":18,"../../util/UIUtil":35,"./../../../settings/Settings":51}],29:[function(require,module,exports){ -/* global $ */ -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":22}],30:[function(require,module,exports){ -/* global APP, $, buttonClick, config, lockRoom, interfaceConfig, 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 recordingToaster = 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("#toolbar_button_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(predefinedToken) { - APP.xmpp.toggleRecording(function (callback) { - if (predefinedToken) { - callback(UIUtil.escapeHtml(predefinedToken)); - return; - } - - 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); -} - -/** - * 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 box -} - -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.isMUCJoined()) { - 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) { - $("#toolbar_button_prezi").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 ($("#toolbar_button_security").hasClass("icon-security-locked")) - UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked"); - }; - /** - * Updates the lock button state to locked. - */ - my.lockLockButton = function () { - if ($("#toolbar_button_security").hasClass("icon-security")) - UIUtil.buttonClick("#toolbar_button_security", "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) { - $('#toolbar_button_record').css({display: "inline-block"}); - } - else { - $('#toolbar_button_record').css({display: "none"}); - } - }; - - // Sets the state of the recording button - my.setRecordingButtonState = function (recordingState) { - var selector = $('#toolbar_button_record'); - - if (recordingState === 'on') { - selector.removeClass("icon-recEnable"); - selector.addClass("icon-recEnable active"); - - $("#largeVideo").toggleClass("videoMessageFilter", true); - var recordOnKey = "recording.on"; - $('#videoConnectionMessage').attr("data-i18n", recordOnKey); - $('#videoConnectionMessage').text(APP.translation.translateString(recordOnKey)); - - setTimeout(function(){ - $("#largeVideo").toggleClass("videoMessageFilter", false); - $('#videoConnectionMessage').css({display: "none"}); - }, 1500); - - recordingToaster = messageHandler.notify(null, "recording.toaster", null, - null, null, {timeOut: 0, closeButton: null, tapToDismiss: false}); - } else if (recordingState === 'off') { - selector.removeClass("icon-recEnable active"); - selector.addClass("icon-recEnable"); - - $("#largeVideo").toggleClass("videoMessageFilter", false); - $('#videoConnectionMessage').css({display: "none"}); - - if (recordingToaster) - messageHandler.remove(recordingToaster); - - } else if (recordingState === 'pending') { - selector.removeClass("icon-recEnable active"); - selector.addClass("icon-recEnable"); - - $("#largeVideo").toggleClass("videoMessageFilter", true); - var recordPendingKey = "recording.pending"; - $('#videoConnectionMessage').attr("data-i18n", recordPendingKey); - $('#videoConnectionMessage').text(APP.translation.translateString(recordPendingKey)); - $('#videoConnectionMessage').css({display: "block"}); - } - }; - - // checks whether recording is enabled and whether we have params to start automatically recording - my.checkAutoRecord = function () { - if (config.enableRecording && config.autoRecord) { - toggleRecording(config.autoRecordToken); - } - } - - // Shows or hides SIP calls button - my.showSipCallButton = function (show) { - if (APP.xmpp.isSipGatewayEnabled() && show) { - $('#toolbar_button_sip').css({display: "inline-block"}); - } else { - $('#toolbar_button_sip').css({display: "none"}); - } - }; - - // Shows or hides the dialpad button - my.showDialPadButton = function (show) { - if (show) { - $('#toolbar_button_dialpad').css({display: "inline-block"}); - } else { - $('#toolbar_button_dialpad').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 = $("#toolbar_button_desktopsharing"); - if (active) { - button.addClass("glow"); - } else { - button.removeClass("glow"); - } - }; - - return my; -}(Toolbar || {})); - -module.exports = Toolbar; -},{"../../../service/authentication/AuthenticationEvents":172,"../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 APP, config, $, interfaceConfig, Moderator, - DesktopStreaming.showDesktopSharingButton */ - -var toolbarTimeoutObject, - toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT; - -function showDesktopSharingButton() { - if (APP.desktopsharing.isDesktopSharingEnabled()) { - $('#toolbar_button_desktopsharing').css({display: "inline-block"}); - } else { - $('#toolbar_button_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 () { - if (interfaceConfig.filmStripOnly) - return; - 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 (interfaceConfig.filmStripOnly) - return; - - 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){ -/* global $ */ -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 () { - if(!JitsiPopover.enabled) - return; - 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(); - }; - - JitsiPopover.enabled = true; - - return JitsiPopover; -})(); - -module.exports = JitsiPopover; -},{}],33:[function(require,module,exports){ -/* global $, APP, jQuery, toastr */ - -/** - * Flag for enable/disable of the notifications. - * @type {boolean} - */ -var notificationsEnabled = true; - -var messageHandler = (function(my) { - - /** - * Shows a message to the user. - * - * @param titleKey the title of the message - * @param messageKey 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 {object} 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 titleKey the title of the message - * @param msgKey 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 titleKey the title of the message. - * @param msgKey the text of the message. - */ - my.showError = function(titleKey, msgKey) { - - if (!titleKey) { - titleKey = "dialog.oops"; - } - if (!msgKey) { - msgKey = "dialog.defaultError"; - } - messageHandler.openMessageDialog(titleKey, msgKey); - }; - - /** - * Displayes notification. - * @param displayName display name of the participant that is associated with the notification. - * @param displayNameKey the key from the language file for the display name. - * @param cls css class for the notification - * @param messageKey the key from the language file for the text of the message. - * @param messageArguments object with the arguments for the message. - * @param options object with language options. - */ - my.notify = function(displayName, displayNameKey, - cls, messageKey, messageArguments, options) { - if(!notificationsEnabled) - return; - var displayNameSpan = '" + APP.translation.translateString(displayNameKey); - } - displayNameSpan += ""; - return toastr.info( - displayNameSpan + '
' + - '" + - APP.translation.translateString(messageKey, - messageArguments) + - '', null, options); - }; - - /** - * Removes the toaster. - * @param toasterElement - */ - my.remove = function(toasterElement) { - toasterElement.remove(); - }; - - /** - * Disables notifications. - */ - my.disableNotifications = function () { - notificationsEnabled = false; - }; - - /** - * Enables notifications. - */ - my.enableNotifications = function () { - notificationsEnabled = true; - }; - - return my; -}(messageHandler || {})); - -module.exports = messageHandler; - - - -},{}],34:[function(require,module,exports){ -var UIEvents = require("../../../service/UI/UIEvents"); - -var nickname = null; -var eventEmitter = null; - -var NicknameHandler = { - 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 = NicknameHandler; -},{"../../../service/UI/UIEvents":171}],35:[function(require,module,exports){ -/* global $ */ -/** - * 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] * 0.3 + pixels[i+1] * 0.59 + pixels[i+2] * 0.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"); - }, - - /** - * Inserts given child element as the first one into the container. - * @param container the container to which new child element will be added - * @param newChild the new element that will be inserted into the container - */ - prependChild: function (container, newChild) { - var firstChild = container.childNodes[0]; - if (firstChild) { - container.insertBefore(newChild, firstChild); - } else { - container.appendChild(newChild); - } - } + 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."); + } }; -},{"../side_pannels/SidePanelToggler":22}],36:[function(require,module,exports){ -/* global APP, $ */ -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 += "" + - "" + - ""; - - result += transport + "
" + - "" + - translate("connectionindicator.bandwidth") + "" + - "" + - "" + - downloadBandwidth + - " " + - uploadBandwidth + "
"; - } - - 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(); -}; - + +var DTMF = { + sendTones: function (tones, duration, pause) { + if (!DTMFSender) + initDtmfSender(); + + if (DTMFSender){ + DTMFSender.insertDTMF(tones, + (duration || 200), + (pause || 200)); + } + } +}; + +module.exports = DTMF; + + +},{}],4:[function(require,module,exports){ +/* global config, APP, Strophe */ + +// 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; + + 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; + + +},{"../../service/RTC/RTCEvents":163}],5:[function(require,module,exports){ +/* global APP */ +var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); +var RTCEvents = require("../../service/RTC/RTCEvents"); +var RTCBrowserType = require("./RTCBrowserType"); + +/** + * This implements 'onended' callback normally fired by WebRTC after the stream + * is stopped. There is no such behaviour yet in FF, so we have to add it. + * @param stream original WebRTC stream object to which 'onended' handling + * will be added. + */ +function implementOnEndedHandling(stream) { + var originalStop = stream.stop; + stream.stop = function () { + originalStop.apply(stream); + if (!stream.ended) { + stream.ended = true; + stream.onended(); + } + }; +} + +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(); + }; + if (RTCBrowserType.isFirefox()) { + implementOnEndedHandling(this.stream); + } +} + +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) +{ + var isAudio = this.isAudioStream(); + var eventType = isAudio ? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE; + + if ((window.location.protocol != "https:" && this.isGUMStream) || + (isAudio && this.isGUMStream) || this.videoType === "screen" || + // FIXME FF does not support 'removeStream' method used to mute + RTCBrowserType.isFirefox()) { + + var tracks = this.getTracks(); + for (var idx = 0; idx < tracks.length; idx++) { + tracks[idx].enabled = !mute; + } + this.eventEmitter.emit(eventType, mute); + } else { + if (mute) { + APP.xmpp.removeStream(this.stream); + this.stream.stop(); + this.eventEmitter.emit(eventType, true); + } else { + var self = this; + APP.RTC.rtcUtils.obtainAudioAndVideoPermissions( + (this.isAudioStream() ? ["audio"] : ["video"]), + function (stream) { + if (isAudio) { + APP.RTC.changeLocalAudio(stream, + function () { + self.eventEmitter.emit(eventType, false); + }); + } else { + APP.RTC.changeLocalVideo(stream, false, + function () { + self.eventEmitter.emit(eventType, false); + }); + } + }); + } + } +}; + +LocalStream.prototype.isMuted = function () { + var tracks = []; + if (this.isAudioStream()) { + 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":163,"../../service/RTC/StreamEventTypes.js":165,"./RTCBrowserType":8}],6:[function(require,module,exports){ +var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); + +/** + * 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 + * @param mute the whether this MediaStream is muted + * + * @constructor + */ +function MediaStream(data, sid, ssrc, browser, eventEmitter, mute) { + + // 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 = mute; + 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":162}],7:[function(require,module,exports){ +/* global APP */ +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(true); + + 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 jid = data.peerjid || APP.xmpp.myJid(); + + // check the video muted state from last stored presence if any + var muted = false; + var pres = APP.xmpp.getLastPresence(jid); + if(pres != null && pres.videoMuted) { + muted = pres.videoMuted; + } + + var remoteStream = new MediaStream(data, sid, thessrc, + RTCBrowserType.getBrowserType(), eventEmitter, muted); + + 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" : "camera"); + 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; + } + }, + 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":162,"../../service/RTC/RTCEvents.js":163,"../../service/RTC/StreamEventTypes.js":165,"../../service/UI/UIEvents":166,"../../service/desktopsharing/DesktopSharingEventTypes":169,"../../service/xmpp/XMPPEvents":172,"./DataChannels":4,"./LocalStream.js":5,"./MediaStream.js":6,"./RTCBrowserType":8,"./RTCUtils.js":9,"events":173}],8:[function(require,module,exports){ + +var currentBrowser; + +var browserVersion; + +var isAndroid; + +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(); + }, + + /** + * Whether the browser is running on an android device. + */ + isAndroid: function() { + return isAndroid; + } + + // 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(); +isAndroid = navigator.userAgent.indexOf('Android') != -1; + +module.exports = RTCBrowserType; +},{}],9:[function(require,module,exports){ +/* global APP, config, require, attachMediaStream, getUserMedia */ +var RTCBrowserType = require("./RTCBrowserType"); +var Resolutions = require("../../service/RTC/Resolutions"); +var AdapterJS = require("./adapter.screenshare"); + +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) { + var isAndroid = RTCBrowserType.isAndroid(); + + if (Resolutions[resolution]) { + constraints.video.mandatory.minWidth = Resolutions[resolution].width; + constraints.video.mandatory.minHeight = Resolutions[resolution].height; + } + else if (isAndroid) { + // FIXME can't remember if the purpose of this was to always request + // low resolution on Android ? if yes it should be moved up front + 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) { + var constraints = {audio: false, video: false}; + + if (um.indexOf('video') >= 0) { + // same behaviour as true + constraints.video = { mandatory: {}, optional: [] }; + + constraints.video.optional.push({ googLeakyBucket: true }); + + setResolutionConstraints(constraints, resolution); + } + if (um.indexOf('audio') >= 0) { + if (!RTCBrowserType.isFirefox()) { + // same behaviour as true + constraints.audio = { mandatory: {}, optional: []}; + // 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} + ); + } else { + constraints.audio = 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 (bandwidth) { + if (!constraints.video) { + //same behaviour as true + constraints.video = {mandatory: {}, optional: []}; + } + 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) { + // same behaviour as true; + constraints.video = {mandatory: {}, optional: []}; + } + constraints.video.mandatory.minFrameRate = fps; + } + + // we turn audio for both audio and video tracks, the fake audio & video seems to work + // only when enabled in one getUserMedia call, we cannot get fake audio separate by fake video + // this later can be a problem with some of the tests + if(RTCBrowserType.isFirefox() && config.firefox_fake_device) + { + constraints.audio = true; + constraints.fake = true; + } + + return constraints; +} + + +function RTCUtils(RTCService, onTemasysPluginReady) +{ + var self = this; + this.service = RTCService; + if (RTCBrowserType.isFirefox()) { + var FFversion = RTCBrowserType.getFirefoxVersion(); + if (FFversion >= 40) { + 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 APP.xmpp.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 version too old: " + FFversion + ". Required >= 40."); + 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 APP.xmpp.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 (RTCBrowserType.isAndroid()) { + 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 = APP.xmpp.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 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; + + var constraints = getConstraints( + um, resolution, bandwidth, fps, desktopStream); + + 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, 'camera', + 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 (var 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":164,"./RTCBrowserType":8,"./adapter.screenshare":10}],10:[function(require,module,exports){ +/*! adapterjs - custom version from - 2015-08-19 */ + +// 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.12.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'; +} + +AdapterJS.WebRTCPlugin.TAGS = { + NONE : 'none', + AUDIO : 'audio', + VIDEO : 'video' +}; + +// 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) { } + + var pluginInstallInterval = setInterval(function(){ + if(! isIE) { + navigator.plugins.refresh(false); + } + AdapterJS.WebRTCPlugin.isPluginInstalled( + AdapterJS.WebRTCPlugin.pluginInfo.prefix, + AdapterJS.WebRTCPlugin.pluginInfo.plugName, + function() { // plugin now installed + clearInterval(pluginInstallInterval); + AdapterJS.WebRTCPlugin.defineWebRTCInterface(); + }, + function() { + // still no plugin detected, nothing to do + }); + } , 500); + }); + } 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 if (navigator.mediaDevices && navigator.userAgent.match( + /Edge\/(\d+).(\d+)$/)) { + webrtcDetectedBrowser = 'edge'; + + webrtcDetectedVersion = + parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10); + + // the minimum version still supported by adapter. + webrtcMinimumVersion = 12; + + getUserMedia = navigator.getUserMedia; + + attachMediaStream = function(element, stream) { + element.srcObject = stream; + return element; + }; + reattachMediaStream = function(to, from) { + to.srcObject = from.srcObject; + 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 = '' + + ' ' + + ' ' + + ' ' + + '' + + '' + + // uncomment to be able to use virtual cams + (AdapterJS.options.getAllCams ? '':'') + + + ''; + 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 () { + if (AdapterJS.WebRTCPlugin.pluginState === + AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { + console.error("AdapterJS - WebRTC interface has already been defined"); + return; + } + + 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); // TODO: remove on 0.12.0 + streamId = stream.id; + } + + var elementId = element.id.length === 0 ? Math.random().toString(36).slice(2) : element.id; + var nodeName = element.nodeName.toLowerCase(); + if (nodeName !== 'object') { // not a plugin tag yet + var tag; + switch(nodeName) { + case 'audio': + tag = AdapterJS.WebRTCPlugin.TAGS.AUDIO; + break; + case 'video': + tag = AdapterJS.WebRTCPlugin.TAGS.VIDEO; + break; + default: + tag = AdapterJS.WebRTCPlugin.TAGS.NONE; + } + + 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 { // already an tag, just change the stream id + 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) {}; + newElement.onclick = (element.onclick) ? element.onclick : function (arg) {}; + if (isIE) { // on IE the event needs to be plugged manually + newElement.attachEvent('onplaying', newElement.onplaying); + newElement._TemOnClick = function (id) { + var arg = { + srcElement : document.getElementById(id) + }; + newElement.onclick(arg); + }; + } + + return newElement; + }; + + 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); +} + +},{}],11:[function(require,module,exports){ +/* global Strophe, APP, $, config, interfaceConfig, toastr */ +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 JitsiPopover = require("./util/JitsiPopover"); +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 roomNode = null; +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.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 userResource = APP.UI.getLargeVideoResource(); + eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, userResource); + }); + 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.getLargeVideoResource()); + }); + 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.MUC_MEMBER_LEFT, onMucMemberLeft); + APP.xmpp.addListener(XMPPEvents.PASSWORD_REQUIRED, onPasswordRequired); + APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad); + APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED, + onAuthenticationRequired); + APP.xmpp.addListener(XMPPEvents.PARTICIPANT_VIDEO_TYPE_CHANGED, + onPeerVideoTypeChanged); + APP.xmpp.addListener(XMPPEvents.DEVICE_AVAILABLE, + function (resource, devices) { + VideoLayout.setDeviceAvailabilityIcons(resource, devices); + }); + + APP.xmpp.addListener(XMPPEvents.PARTICIPANT_AUDIO_MUTED, + VideoLayout.onAudioMute); + APP.xmpp.addListener(XMPPEvents.PARTICIPANT_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.connectError", 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(); + }); + + if (!interfaceConfig.filmStripOnly) { + APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation); + APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError); + // Listens for video interruption events. + APP.xmpp.addListener(XMPPEvents.CONNECTION_INTERRUPTED, VideoLayout.onVideoInterrupted); + // Listens for video restores events. + APP.xmpp.addListener(XMPPEvents.CONNECTION_RESTORED, VideoLayout.onVideoRestored); + } +} + + +/** + * 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; + var setupWelcomePage = null; + if(config.enableWelcomePage && window.location.pathname == "/" && + (!window.localStorage.welcomePageDisabled || + window.localStorage.welcomePageDisabled == "false")) { + $("#videoconference_page").hide(); + if (!setupWelcomePage) + setupWelcomePage = require("./welcome_page/WelcomePage"); + setupWelcomePage(); + + return; + } + + $("#welcome_page").hide(); + + // Set the defaults for prompt dialogs. + $.prompt.setDefaults({persistent: false}); + + + registerListeners(); + + VideoLayout.init(eventEmitter); + NicknameHandler.init(eventEmitter); + + bindEvents(); + setupPrezi(); + if (!interfaceConfig.filmStripOnly) { + $("#videospace").mousemove(function () { + return ToolbarToggler.showToolbar(); + }); + setupToolbars(); + setupChat(); + // Display notice message at the top of the toolbar + if (config.noticeMessage) { + $('#noticeText').text(config.noticeMessage); + $('#notice').css({display: 'block'}); + } + $("#downloadlog").click(function (event) { + dump(event.target); + }); + } + else + { + $("#header").css("display", "none"); + $("#bottomToolbar").css("display", "none"); + $("#downloadlog").css("display", "none"); + $("#remoteVideos").css("padding", "0px 0px 18px 0px"); + $("#remoteVideos").css("right", "0px"); + messageHandler.disableNotifications(); + $('body').popover("disable"); +// $("[data-toggle=popover]").popover("disable"); + JitsiPopover.enabled = false; + } + + document.title = interfaceConfig.APP_NAME; + + + + + + if(config.requireDisplayName) { + var currentSettings = Settings.getSettings(); + if (!currentSettings.displayName) { + promptDisplayName(); + } + } + + init(); + + if (!interfaceConfig.filmStripOnly) { + 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 ? Strophe.getResourceFromJid(jid) : info.displayName; + + 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"); + + Toolbar.checkAutoRecord(); + } +} + +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 onPeerVideoTypeChanged(resourceJid, newVideoType) { + VideoLayout.onVideoTypeChanged(resourceJid, newVideoType); +} + +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.getLargeVideoResource = function () { + return VideoLayout.getLargeVideoResource(); +}; + +UI.getRoomNode = function () { + if (roomNode) + return roomNode; + 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); + } + } + return roomNode; +}; + +UI.generateRoomName = function () { + if (roomName) + return roomName; + var roomNode = UI.getRoomNode(); + 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.getJingleLog(); + var metadata = {}; + metadata.time = new Date(); + metadata.url = window.location.href; + metadata.ua = navigator.userAgent; + var log = APP.xmpp.getXmppLog(); + 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("#toolbar_button_mute", "icon-microphone icon-mic-disabled"); + })) { + // We still click the button. + UIUtil.buttonClick("#toolbar_button_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 = $('#toolbar_button_camera'); + 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":163,"../../service/RTC/StreamEventTypes":165,"../../service/UI/UIEvents":166,"../../service/connectionquality/CQEvents":168,"../../service/desktopsharing/DesktopSharingEventTypes":169,"../../service/members/Events":170,"../../service/xmpp/XMPPEvents":172,"../RTC/RTCBrowserType":8,"./../settings/Settings":49,"./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/JitsiPopover":30,"./util/MessageHandler":31,"./util/NicknameHandler":32,"./util/UIUtil":33,"./videolayout/VideoLayout.js":39,"./welcome_page/RoomnameGenerator":40,"./welcome_page/WelcomePage":41,"events":173}],12:[function(require,module,exports){ +/* global APP, interfaceConfig, $, Strophe */ +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.isMUCJoined()) { + 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":62,"./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":62,"../../xmpp/xmpp":71}],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 + " - using JID as ID"); + id = jid; + } + return 'https://www.gravatar.com/avatar/' + + MD5.hexdigest(id.trim().toLowerCase()) + + "?d=wavatar&size=" + (size || "30"); + } + +}; + + +module.exports = Avatar; +},{"../../settings/Settings":49}],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 (!$('#toolbar_button_etherpad').is(":visible")) + $('#toolbar_button_etherpad').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){ +/* global require, $ */ +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': '#toolbar_button_settings' + }; + + /** + * 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":49,"./chat/Chat":21,"./contactlist/ContactList":25,"./settings/SettingsMenu":26}],21:[function(require,module,exports){ +/* global APP, $, 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 = $('#toolbar_button_chat'); + var bottomGlower = $('#chatBottomButton'); + + if (unreadMessages) { + unreadMsgElement.innerHTML = unreadMessages.toString(); + unreadMsgBottomElement.innerHTML = unreadMessages.toString(); + + ToolbarToggler.dockToolbar(true); + + var chatButtonElement + = document.getElementById('toolbar_button_chat'); + 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; + } + } + }); + + var usermsg = $('#usermsg'); + 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 = + '
'+ + '' + + '
' + 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":166,"../../toolbars/ToolbarToggler":29,"../../util/NicknameHandler":32,"../../util/UIUtil":33,"../SidePanelToggler":20,"./Commands":22,"./Replacement":23,"./smileys.json":24}],22:[function(require,module,exports){ +/* global APP, require */ +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 a 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|&lt;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){ +/* global $, APP, Strophe */ +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 {object} 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){ +/* global APP, $ */ +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 () { + var startMutedSelector = $("#startMutedOptions"); + startMutedSelector.before(generateLanguagesSelectBox()); + APP.translation.translateElement($("#languages_selectbox")); + $('#settingsmenu>input').keyup(function(event){ + if(event.keyCode === 13) {//enter + SettingsMenu.update(); + } + }); + + if (APP.xmpp.isModerator()) { + startMutedSelector.css("display", "block"); + } + else { + startMutedSelector.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":171,"../../avatar/Avatar":16,"../../util/UIUtil":33,"./../../../settings/Settings":49}],27:[function(require,module,exports){ +/* global $ */ +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, interfaceConfig, 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 recordingToaster = 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("#toolbar_button_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(predefinedToken) { + APP.xmpp.toggleRecording(function (callback) { + if (predefinedToken) { + callback(UIUtil.escapeHtml(predefinedToken)); + return; + } + + 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); +} + +/** + * 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 box +} + +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.isMUCJoined()) { + 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) { + $("#toolbar_button_prezi").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 ($("#toolbar_button_security").hasClass("icon-security-locked")) + UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked"); + }; + /** + * Updates the lock button state to locked. + */ + my.lockLockButton = function () { + if ($("#toolbar_button_security").hasClass("icon-security")) + UIUtil.buttonClick("#toolbar_button_security", "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) { + $('#toolbar_button_record').css({display: "inline-block"}); + } + else { + $('#toolbar_button_record').css({display: "none"}); + } + }; + + // Sets the state of the recording button + my.setRecordingButtonState = function (recordingState) { + var selector = $('#toolbar_button_record'); + + if (recordingState === 'on') { + selector.removeClass("icon-recEnable"); + selector.addClass("icon-recEnable active"); + + $("#largeVideo").toggleClass("videoMessageFilter", true); + var recordOnKey = "recording.on"; + $('#videoConnectionMessage').attr("data-i18n", recordOnKey); + $('#videoConnectionMessage').text(APP.translation.translateString(recordOnKey)); + + setTimeout(function(){ + $("#largeVideo").toggleClass("videoMessageFilter", false); + $('#videoConnectionMessage').css({display: "none"}); + }, 1500); + + recordingToaster = messageHandler.notify(null, "recording.toaster", null, + null, null, {timeOut: 0, closeButton: null, tapToDismiss: false}); + } else if (recordingState === 'off') { + selector.removeClass("icon-recEnable active"); + selector.addClass("icon-recEnable"); + + $("#largeVideo").toggleClass("videoMessageFilter", false); + $('#videoConnectionMessage').css({display: "none"}); + + if (recordingToaster) + messageHandler.remove(recordingToaster); + + } else if (recordingState === 'pending') { + selector.removeClass("icon-recEnable active"); + selector.addClass("icon-recEnable"); + + $("#largeVideo").toggleClass("videoMessageFilter", true); + var recordPendingKey = "recording.pending"; + $('#videoConnectionMessage').attr("data-i18n", recordPendingKey); + $('#videoConnectionMessage').text(APP.translation.translateString(recordPendingKey)); + $('#videoConnectionMessage').css({display: "block"}); + } + }; + + // checks whether recording is enabled and whether we have params to start automatically recording + my.checkAutoRecord = function () { + if (config.enableRecording && config.autoRecord) { + toggleRecording(config.autoRecordToken); + } + } + + // Shows or hides SIP calls button + my.showSipCallButton = function (show) { + if (APP.xmpp.isSipGatewayEnabled() && show) { + $('#toolbar_button_sip').css({display: "inline-block"}); + } else { + $('#toolbar_button_sip').css({display: "none"}); + } + }; + + // Shows or hides the dialpad button + my.showDialPadButton = function (show) { + if (show) { + $('#toolbar_button_dialpad').css({display: "inline-block"}); + } else { + $('#toolbar_button_dialpad').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 = $("#toolbar_button_desktopsharing"); + if (active) { + button.addClass("glow"); + } else { + button.removeClass("glow"); + } + }; + + return my; +}(Toolbar || {})); + +module.exports = Toolbar; +},{"../../../service/authentication/AuthenticationEvents":167,"../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 APP, config, $, interfaceConfig, Moderator, + DesktopStreaming.showDesktopSharingButton */ + +var toolbarTimeoutObject, + toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT; + +function showDesktopSharingButton() { + if (APP.desktopsharing.isDesktopSharingEnabled()) { + $('#toolbar_button_desktopsharing').css({display: "inline-block"}); + } else { + $('#toolbar_button_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 () { + if (interfaceConfig.filmStripOnly) + return; + 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 (interfaceConfig.filmStripOnly) + return; + + 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){ +/* global $ */ +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 () { + if(!JitsiPopover.enabled) + return; + 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(); + }; + + JitsiPopover.enabled = true; + + return JitsiPopover; +})(); + +module.exports = JitsiPopover; +},{}],31:[function(require,module,exports){ +/* global $, APP, jQuery, toastr */ + +/** + * Flag for enable/disable of the notifications. + * @type {boolean} + */ +var notificationsEnabled = true; + +var messageHandler = (function(my) { + + /** + * Shows a message to the user. + * + * @param titleKey the title of the message + * @param messageKey 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 {object} 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 titleKey the title of the message + * @param msgKey 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 titleKey the title of the message. + * @param msgKey the text of the message. + */ + my.showError = function(titleKey, msgKey) { + + if (!titleKey) { + titleKey = "dialog.oops"; + } + if (!msgKey) { + msgKey = "dialog.defaultError"; + } + messageHandler.openMessageDialog(titleKey, msgKey); + }; + + /** + * Displayes notification. + * @param displayName display name of the participant that is associated with the notification. + * @param displayNameKey the key from the language file for the display name. + * @param cls css class for the notification + * @param messageKey the key from the language file for the text of the message. + * @param messageArguments object with the arguments for the message. + * @param options object with language options. + */ + my.notify = function(displayName, displayNameKey, + cls, messageKey, messageArguments, options) { + if(!notificationsEnabled) + return; + var displayNameSpan = '" + APP.translation.translateString(displayNameKey); + } + displayNameSpan += ""; + return toastr.info( + displayNameSpan + '
' + + '" + + APP.translation.translateString(messageKey, + messageArguments) + + '', null, options); + }; + + /** + * Removes the toaster. + * @param toasterElement + */ + my.remove = function(toasterElement) { + toasterElement.remove(); + }; + + /** + * Disables notifications. + */ + my.disableNotifications = function () { + notificationsEnabled = false; + }; + + /** + * Enables notifications. + */ + my.enableNotifications = function () { + notificationsEnabled = true; + }; + + return my; +}(messageHandler || {})); + +module.exports = messageHandler; + + + +},{}],32:[function(require,module,exports){ +var UIEvents = require("../../../service/UI/UIEvents"); + +var nickname = null; +var eventEmitter = null; + +var NicknameHandler = { + 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 = NicknameHandler; +},{"../../../service/UI/UIEvents":166}],33:[function(require,module,exports){ +/* global $ */ +/** + * 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] * 0.3 + pixels[i+1] * 0.59 + pixels[i+2] * 0.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"); + }, + + /** + * Inserts given child element as the first one into the container. + * @param container the container to which new child element will be added + * @param newChild the new element that will be inserted into the container + */ + prependChild: function (container, newChild) { + var firstChild = container.childNodes[0]; + if (firstChild) { + container.insertBefore(newChild, firstChild); + } else { + container.appendChild(newChild); + } + } +}; +},{"../side_pannels/SidePanelToggler":20}],34:[function(require,module,exports){ +/* global APP, $ */ +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 += "" + + "" + + ""; + + result += transport + "
" + + "" + + translate("connectionindicator.bandwidth") + "" + + "" + + "" + + downloadBandwidth + + " " + + uploadBandwidth + "
"; + } + + 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){ -/* global $, APP, Strophe, interfaceConfig */ -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 = "#largeVideoWrapper"; - 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; - if (jid) { - avatar.src = url; - currentSmallVideo.showAvatar(); - } -} - -/** - * 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 = currentSmallVideo.getVideoType() === 'screen'; - // 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); - - $('#largeVideoWrapper').fadeTo(300, 1); - } -} - -/** - * Creates the html elements for the large video. - */ -function createLargeVideoHTML() -{ - var html = '
'; - html += '
' + - '
' + - '
' + - '
' + - '' + - ' jitsi.org' + - ''+ - '
' + - '' + - '' + - '
' + - '
' + - '' + - '
' + - ''; - 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 $('#largeVideoWrapper').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 (!newSmallVideo) { - console.error("Small video not found for: " + resourceJid); - return; - } - - 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); - } - // We are doing fadeOut/fadeIn animations on parent div which wraps - // largeVideo, because when Temasys plugin is in use it replaces - //