(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'; } function isObject(arg) { return typeof arg === 'object' && arg !== null; } function isUndefined(arg) { return arg === void 0; } },{}],2:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; var queue = []; var draining = false; function drainQueue() { if (draining) { return; } draining = true; var currentQueue; var len = queue.length; while(len) { currentQueue = queue; queue = []; var i = -1; while (++i < len) { currentQueue[i](); } len = queue.length; } draining = false; } process.nextTick = function (fun) { queue.push(fun); if (!draining) { setTimeout(drainQueue, 0); } }; process.title = 'browser'; process.browser = true; process.env = {}; process.argv = []; process.version = ''; // empty string to avoid regexp issues 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"); } }; function init() { APP.desktopsharing.init(); APP.RTC.start(); APP.xmpp.start(); APP.statistics.start(); APP.connectionquality.init(); APP.keyboardshortcut.init(); APP.members.start(); } $(document).ready(function () { var URLPRocessor = require("./modules/URLProcessor/URLProcessor"); URLPRocessor.setConfigParametersFromUrl(); APP.init(); APP.translation.init(); if(APP.API.isEnabled()) APP.API.init(); APP.UI.start(init); }); $(window).bind('beforeunload', function () { if(APP.API.isEnabled()) APP.API.dispose(); }); module.exports = APP; },{"./modules/API/API":4,"./modules/DTMF/DTMF":5,"./modules/RTC/RTC":9,"./modules/UI/UI":13,"./modules/URLProcessor/URLProcessor":44,"./modules/connectionquality/connectionquality":45,"./modules/desktopsharing/desktopsharing":46,"./modules/keyboardshortcut/keyboardshortcut":47,"./modules/members/MemberList":48,"./modules/settings/Settings":49,"./modules/statistics/statistics":53,"./modules/translation/translation":54,"./modules/xmpp/xmpp":68}],4:[function(require,module,exports){ /** * Implements API class that communicates with external api class * and provides interface to access Jitsi Meet features by external * applications that embed Jitsi Meet */ var XMPPEvents = require("../../service/xmpp/XMPPEvents"); /** * List of the available commands. * @type {{ * displayName: inputDisplayNameHandler, * muteAudio: toggleAudio, * muteVideo: toggleVideo, * filmStrip: toggleFilmStrip * }} */ var commands = {}; function initCommands() { commands = { displayName: APP.UI.inputDisplayNameHandler, muteAudio: APP.UI.toggleAudio, muteVideo: APP.UI.toggleVideo, toggleFilmStrip: APP.UI.toggleFilmStrip, toggleChat: APP.UI.toggleChat, toggleContactList: APP.UI.toggleContactList }; } /** * Maps the supported events and their status * (true it the event is enabled and false if it is disabled) * @type {{ * incomingMessage: boolean, * outgoingMessage: boolean, * displayNameChange: boolean, * participantJoined: boolean, * participantLeft: boolean * }} */ var events = { incomingMessage: false, outgoingMessage:false, displayNameChange: false, participantJoined: false, participantLeft: false }; var displayName = {}; /** * Processes commands from external applicaiton. * @param message the object with the command */ function processCommand(message) { if(message.action != "execute") { console.error("Unknown action of the message"); return; } for(var key in message) { if(commands[key]) commands[key].apply(null, message[key]); } } /** * Processes events objects from external applications * @param event the event */ function processEvent(event) { if(!event.action) { console.error("Event with no action is received."); return; } var i = 0; switch(event.action) { case "add": for(; i < event.events.length; i++) { events[event.events[i]] = true; } break; case "remove": for(; i < event.events.length; i++) { events[event.events[i]] = false; } break; default: console.error("Unknown action for event."); } } /** * Sends message to the external application. * @param object */ function sendMessage(object) { window.parent.postMessage(JSON.stringify(object), "*"); } /** * Processes a message event from the external application * @param event the message event */ function processMessage(event) { var message; try { message = JSON.parse(event.data); } catch (e) {} if(!message.type) return; switch (message.type) { case "command": processCommand(message); break; case "event": processEvent(message); break; default: console.error("Unknown type of the message"); return; } } function setupListeners() { APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, function (from) { API.triggerEvent("participantJoined", {jid: from}); }); APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, function (from, nick, txt, myjid, stamp) { if (from != myjid) API.triggerEvent("incomingMessage", {"from": from, "nick": nick, "message": txt, "stamp": stamp}); }); APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, function (jid) { API.triggerEvent("participantLeft", {jid: jid}); }); APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, function (jid, newDisplayName) { name = displayName[jid]; if(!name || name != newDisplayName) { API.triggerEvent("displayNameChange", {jid: jid, displayname: newDisplayName}); displayName[jid] = newDisplayName; } }); APP.xmpp.addListener(XMPPEvents.SENDING_CHAT_MESSAGE, function (body) { APP.API.triggerEvent("outgoingMessage", {"message": body}); }); } var API = { /** * Check whether the API should be enabled or not. * @returns {boolean} */ isEnabled: function () { var hash = location.hash; if(hash && hash.indexOf("external") > -1 && window.postMessage) return true; return false; }, /** * Initializes the APIConnector. Setups message event listeners that will * receive information from external applications that embed Jitsi Meet. * It also sends a message to the external application that APIConnector * is initialized. */ init: function () { initCommands(); if (window.addEventListener) { window.addEventListener('message', processMessage, false); } else { window.attachEvent('onmessage', processMessage); } sendMessage({type: "system", loaded: true}); setupListeners(); }, /** * Checks whether the event is enabled ot not. * @param name the name of the event. * @returns {*} */ isEventEnabled: function (name) { return events[name]; }, /** * Sends event object to the external application that has been subscribed * for that event. * @param name the name event * @param object data associated with the event */ triggerEvent: function (name, object) { if(this.isEnabled() && this.isEventEnabled(name)) sendMessage({ type: "event", action: "result", event: name, result: object}); }, /** * Removes the listeners. */ dispose: function () { if(window.removeEventListener) { window.removeEventListener("message", processMessage, false); } else { window.detachEvent('onmessage', processMessage); } } }; module.exports = API; },{"../../service/xmpp/XMPPEvents":110}],5:[function(require,module,exports){ /* global APP */ /** * A module for sending DTMF tones. */ var DTMFSender; var initDtmfSender = function() { // TODO: This needs to reset this if the peerconnection changes // (e.g. the call is re-made) if (DTMFSender) return; var localAudio = APP.RTC.localAudio; if (localAudio && localAudio.getTracks().length > 0) { var peerconnection = APP.xmpp.getConnection().jingle.activecall.peerconnection; if (peerconnection) { DTMFSender = peerconnection.peerconnection .createDTMFSender(localAudio.getTracks()[0]); console.log("Initialized DTMFSender"); } else { console.log("Failed to initialize DTMFSender: no PeerConnection."); } } else { console.log("Failed to initialize DTMFSender: no audio track."); } }; var DTMF = { sendTones: function (tones, duration, pause) { if (!DTMFSender) initDtmfSender(); if (DTMFSender){ DTMFSender.insertDTMF(tones, (duration || 200), (pause || 200)); } } }; module.exports = DTMF; },{}],6:[function(require,module,exports){ /* global Strophe, focusedVideoSrc*/ // cache datachannels to avoid garbage collection // https://code.google.com/p/chromium/issues/detail?id=405545 var RTCEvents = require("../../service/RTC/RTCEvents"); var _dataChannels = []; var eventEmitter = null; var DataChannels = { /** * Callback triggered by PeerConnection when new data channel is opened * on the bridge. * @param event the event info object. */ onDataChannel: function (event) { var dataChannel = event.channel; dataChannel.onopen = function () { console.info("Data channel opened by the Videobridge!", dataChannel); // Code sample for sending string and/or binary data // Sends String message to the bridge //dataChannel.send("Hello bridge!"); // Sends 12 bytes binary message to the bridge //dataChannel.send(new ArrayBuffer(12)); eventEmitter.emit(RTCEvents.DATA_CHANNEL_OPEN); }; dataChannel.onerror = function (error) { console.error("Data Channel Error:", error, dataChannel); }; dataChannel.onmessage = function (event) { var data = event.data; // JSON var obj; try { obj = JSON.parse(data); } catch (e) { console.error( "Failed to parse data channel message as JSON: ", data, dataChannel); } if (('undefined' !== typeof(obj)) && (null !== obj)) { var colibriClass = obj.colibriClass; if ("DominantSpeakerEndpointChangeEvent" === colibriClass) { // Endpoint ID from the Videobridge. var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint; console.info( "Data channel new dominant speaker event: ", dominantSpeakerEndpoint); eventEmitter.emit(RTCEvents.DOMINANTSPEAKER_CHANGED, dominantSpeakerEndpoint); } else if ("InLastNChangeEvent" === colibriClass) { var oldValue = obj.oldValue; var newValue = obj.newValue; // Make sure that oldValue and newValue are of type boolean. var type; if ((type = typeof oldValue) !== 'boolean') { if (type === 'string') { oldValue = (oldValue == "true"); } else { oldValue = new Boolean(oldValue).valueOf(); } } if ((type = typeof newValue) !== 'boolean') { if (type === 'string') { newValue = (newValue == "true"); } else { newValue = new Boolean(newValue).valueOf(); } } eventEmitter.emit(RTCEvents.LASTN_CHANGED, oldValue, newValue); } else if ("LastNEndpointsChangeEvent" === colibriClass) { // The new/latest list of last-n endpoint IDs. var lastNEndpoints = obj.lastNEndpoints; // The list of endpoint IDs which are entering the list of // last-n at this time i.e. were not in the old list of last-n // endpoint IDs. var endpointsEnteringLastN = obj.endpointsEnteringLastN; var stream = obj.stream; console.log( "Data channel new last-n event: ", lastNEndpoints, endpointsEnteringLastN, obj); eventEmitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED, lastNEndpoints, endpointsEnteringLastN, obj); } else { console.debug("Data channel JSON-formatted message: ", obj); } } }; dataChannel.onclose = function () { console.info("The Data Channel closed", dataChannel); var idx = _dataChannels.indexOf(dataChannel); if (idx > -1) _dataChannels = _dataChannels.splice(idx, 1); }; _dataChannels.push(dataChannel); }, /** * Binds "ondatachannel" event listener to given PeerConnection instance. * @param peerConnection WebRTC peer connection instance. */ init: function (peerConnection, emitter) { if(!config.openSctp) return; peerConnection.ondatachannel = this.onDataChannel; eventEmitter = emitter; // Sample code for opening new data channel from Jitsi Meet to the bridge. // Although it's not a requirement to open separate channels from both bridge // and peer as single channel can be used for sending and receiving data. // So either channel opened by the bridge or the one opened here is enough // for communication with the bridge. /*var dataChannelOptions = { reliable: true }; var dataChannel = peerConnection.createDataChannel("myChannel", dataChannelOptions); // Can be used only when is in open state dataChannel.onopen = function () { dataChannel.send("My channel !!!"); }; dataChannel.onmessage = function (event) { var msgData = event.data; console.info("Got My Data Channel Message:", msgData, dataChannel); };*/ }, handleSelectedEndpointEvent: onSelectedEndpointChanged, handlePinnedEndpointEvent: onPinnedEndpointChanged }; function onSelectedEndpointChanged(userResource) { console.log('selected endpoint changed: ', userResource); if (_dataChannels && _dataChannels.length != 0) { _dataChannels.some(function (dataChannel) { if (dataChannel.readyState == 'open') { console.log('sending selected endpoint changed ' + 'notification to the bridge: ', userResource); dataChannel.send(JSON.stringify({ 'colibriClass': 'SelectedEndpointChangedEvent', 'selectedEndpoint': (!userResource || userResource === null)? null : userResource })); return true; } }); } } function onPinnedEndpointChanged(userResource) { console.log('pinned endpoint changed: ', userResource); if (_dataChannels && _dataChannels.length != 0) { _dataChannels.some(function (dataChannel) { if (dataChannel.readyState == 'open') { dataChannel.send(JSON.stringify({ 'colibriClass': 'PinnedEndpointChangedEvent', 'pinnedEndpoint': (!userResource || userResource == null)? null : userResource })); return true; } }); } } module.exports = DataChannels; },{"../../service/RTC/RTCEvents":101}],7:[function(require,module,exports){ var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); var RTCEvents = require("../../service/RTC/RTCEvents"); function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) { this.stream = stream; this.eventEmitter = eventEmitter; this.type = type; this.videoType = videoType; this.isGUMStream = true; if(isGUMStream === false) this.isGUMStream = isGUMStream; var self = this; if(type == "audio") { this.getTracks = function () { return self.stream.getAudioTracks(); }; } else { this.getTracks = function () { return self.stream.getVideoTracks(); }; } this.stream.onended = function() { self.streamEnded(); }; } LocalStream.prototype.streamEnded = function () { this.eventEmitter.emit(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, this); } LocalStream.prototype.getOriginalStream = function() { return this.stream; } LocalStream.prototype.isAudioStream = function () { return this.type === "audio"; }; LocalStream.prototype.setMute = function (mute) { 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") { 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.type == "audio") { tracks = this.stream.getAudioTracks(); } else { if(this.stream.ended) return true; tracks = this.stream.getVideoTracks(); } for (var idx = 0; idx < tracks.length; idx++) { if(tracks[idx].enabled) return false; } return true; } LocalStream.prototype.getId = function () { return this.stream.getTracks()[0].id; } module.exports = LocalStream; },{"../../service/RTC/RTCEvents":101,"../../service/RTC/StreamEventTypes.js":103}],8:[function(require,module,exports){ ////These lines should be uncommented when require works in app.js var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); var StreamEventType = require("../../service/RTC/StreamEventTypes"); /** * Creates a MediaStream object for the given data, session id and ssrc. * It is a wrapper class for the MediaStream. * * @param data the data object from which we obtain the stream, * the peerjid, etc. * @param sid the session id * @param ssrc the ssrc corresponding to this MediaStream * * @constructor */ function MediaStream(data, sid, ssrc, browser, eventEmitter) { // XXX(gp) to minimize headaches in the future, we should build our // abstractions around tracks and not streams. ORTC is track based API. // Mozilla expects m-lines to represent media tracks. // // Practically, what I'm saying is that we should have a MediaTrack class // and not a MediaStream class. // // Also, we should be able to associate multiple SSRCs with a MediaTrack as // a track might have an associated RTX and FEC sources. this.sid = sid; this.stream = data.stream; this.peerjid = data.peerjid; this.videoType = data.videoType; this.ssrc = ssrc; this.type = (this.stream.getVideoTracks().length > 0)? MediaStreamType.VIDEO_TYPE : MediaStreamType.AUDIO_TYPE; this.muted = false; this.eventEmitter = eventEmitter; } MediaStream.prototype.getOriginalStream = function() { return this.stream; }; MediaStream.prototype.setMute = function (value) { this.stream.muted = value; this.muted = value; }; module.exports = MediaStream; },{"../../service/RTC/MediaStreamTypes":100,"../../service/RTC/StreamEventTypes":103}],9:[function(require,module,exports){ var EventEmitter = require("events"); var RTCBrowserType = require("./RTCBrowserType"); var RTCUtils = require("./RTCUtils.js"); var LocalStream = require("./LocalStream.js"); var DataChannels = require("./DataChannels"); var MediaStream = require("./MediaStream.js"); var DesktopSharingEventTypes = require("../../service/desktopsharing/DesktopSharingEventTypes"); var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); var RTCEvents = require("../../service/RTC/RTCEvents.js"); var XMPPEvents = require("../../service/xmpp/XMPPEvents"); var UIEvents = require("../../service/UI/UIEvents"); var eventEmitter = new EventEmitter(); function getMediaStreamUsage() { var result = { audio: true, video: true }; /** There are some issues with the desktop sharing * when this property is enabled. * WARNING: We must change the implementation to start video/audio if we * receive from the focus that the peer is not muted. var isSecureConnection = window.location.protocol == "https:"; if(config.disableEarlyMediaPermissionRequests || !isSecureConnection) { result = { audio: false, video: false }; } **/ return result; } var RTC = { rtcUtils: null, devices: { audio: true, video: true }, localStreams: [], remoteStreams: {}, localAudio: null, localVideo: null, addStreamListener: function (listener, eventType) { eventEmitter.on(eventType, listener); }, addListener: function (type, listener) { eventEmitter.on(type, listener); }, removeStreamListener: function (listener, eventType) { if(!(eventType instanceof StreamEventTypes)) throw "Illegal argument"; eventEmitter.removeListener(eventType, listener); }, createLocalStream: function (stream, type, change, videoType, isMuted, isGUMStream) { var localStream = new LocalStream(stream, type, eventEmitter, videoType, isGUMStream); //in firefox we have only one stream object if(this.localStreams.length == 0 || this.localStreams[0].getOriginalStream() != stream) this.localStreams.push(localStream); if(isMuted === true) localStream.setMute(false); if(type == "audio") { this.localAudio = localStream; } else { this.localVideo = localStream; } var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED; if(change) eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED; eventEmitter.emit(eventType, localStream, isMuted); return localStream; }, removeLocalStream: function (stream) { for(var i = 0; i < this.localStreams.length; i++) { if(this.localStreams[i].getOriginalStream() === stream) { delete this.localStreams[i]; return; } } }, createRemoteStream: function (data, sid, thessrc) { var remoteStream = new MediaStream(data, sid, thessrc, RTCBrowserType.getBrowserType(), eventEmitter); var jid = data.peerjid || APP.xmpp.myJid(); if(!this.remoteStreams[jid]) { this.remoteStreams[jid] = {}; } this.remoteStreams[jid][remoteStream.type]= remoteStream; eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, remoteStream); return remoteStream; }, getPCConstraints: function () { return this.rtcUtils.pc_constraints; }, getUserMediaWithConstraints:function(um, success_callback, failure_callback, resolution, bandwidth, fps, desktopStream) { return this.rtcUtils.getUserMediaWithConstraints(um, success_callback, failure_callback, resolution, bandwidth, fps, desktopStream); }, attachMediaStream: function (elSelector, stream) { this.rtcUtils.attachMediaStream(elSelector, stream); }, getStreamID: function (stream) { return this.rtcUtils.getStreamID(stream); }, getVideoSrc: function (element) { return this.rtcUtils.getVideoSrc(element); }, setVideoSrc: function (element, src) { this.rtcUtils.setVideoSrc(element, src); }, getVideoElementName: function () { return RTCBrowserType.isTemasysPluginUsed() ? 'object' : 'video'; }, dispose: function() { if (this.rtcUtils) { this.rtcUtils = null; } }, stop: function () { this.dispose(); }, start: function () { var self = this; APP.desktopsharing.addListener( function (stream, isUsingScreenStream, callback) { self.changeLocalVideo(stream, isUsingScreenStream, callback); }, DesktopSharingEventTypes.NEW_STREAM_CREATED); APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function(event) { DataChannels.init(event.peerconnection, eventEmitter); }); APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, DataChannels.handleSelectedEndpointEvent); APP.UI.addListener(UIEvents.PINNED_ENDPOINT, DataChannels.handlePinnedEndpointEvent); // In case of IE we continue from 'onReady' callback // passed to RTCUtils constructor. It will be invoked by Temasys plugin // once it is initialized. var onReady = function () { eventEmitter.emit(RTCEvents.RTC_READY, true); self.rtcUtils.obtainAudioAndVideoPermissions( null, null, getMediaStreamUsage()); }; this.rtcUtils = new RTCUtils(this, onReady); // Call onReady() if Temasys plugin is not used if (!RTCBrowserType.isTemasysPluginUsed()) { onReady(); } }, muteRemoteVideoStream: function (jid, value) { var stream; if(this.remoteStreams[jid] && this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) { stream = this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]; } if(!stream) return true; if (value != stream.muted) { stream.setMute(value); return true; } return false; }, switchVideoStreams: function (new_stream) { this.localVideo.stream = new_stream; this.localStreams = []; //in firefox we have only one stream object if (this.localAudio.getOriginalStream() != new_stream) this.localStreams.push(this.localAudio); this.localStreams.push(this.localVideo); }, changeLocalVideo: function (stream, isUsingScreenStream, callback) { var oldStream = this.localVideo.getOriginalStream(); var type = (isUsingScreenStream? "screen" : "video"); var localCallback = callback; if(this.localVideo.isMuted() && this.localVideo.videoType !== type) { localCallback = function() { APP.xmpp.setVideoMute(false, function(mute) { eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute); }); callback(); }; } // FIXME: Workaround for FF/IE/Safari if (stream && stream.videoStream) { stream = stream.videoStream; } var videoStream = this.rtcUtils.createStream(stream, true); this.localVideo = this.createLocalStream(videoStream, "video", true, type); // Stop the stream to trigger onended event for old stream oldStream.stop(); this.switchVideoStreams(videoStream, oldStream); APP.xmpp.switchStreams(videoStream, oldStream,localCallback); }, changeLocalAudio: function (stream, callback) { var oldStream = this.localAudio.getOriginalStream(); var newStream = this.rtcUtils.createStream(stream); this.localAudio = this.createLocalStream(newStream, "audio", true); // Stop the stream to trigger onended event for old stream oldStream.stop(); APP.xmpp.switchStreams(newStream, oldStream, callback, true); }, isVideoMuted: function (jid) { if (jid === APP.xmpp.myJid()) { var localVideo = APP.RTC.localVideo; return (!localVideo || localVideo.isMuted()); } else { if (!APP.RTC.remoteStreams[jid] || !APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) { return null; } return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted; } }, /** * Checks if video identified by given src is desktop stream. * @param videoSrc eg. * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395 * @returns {boolean} */ isVideoSrcDesktop: function (jid) { if(!jid) return false; var isDesktop = false; var stream = null; if (APP.xmpp.myJid() === jid) { // local video stream = this.localVideo; } else { var peerStreams = this.remoteStreams[jid]; if(!peerStreams) return false; stream = peerStreams[MediaStreamType.VIDEO_TYPE]; } if(stream) isDesktop = (stream.videoType === "screen"); return isDesktop; }, setVideoMute: function (mute, callback, options) { if (!this.localVideo) return; if (mute == APP.RTC.localVideo.isMuted()) { APP.xmpp.sendVideoInfoPresence(mute); if (callback) callback(mute); } else { APP.RTC.localVideo.setMute(mute); APP.xmpp.setVideoMute( mute, callback, options); } }, setDeviceAvailability: function (devices) { if(!devices) return; if(devices.audio === true || devices.audio === false) this.devices.audio = devices.audio; if(devices.video === true || devices.video === false) this.devices.video = devices.video; eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, this.devices); } }; module.exports = RTC; },{"../../service/RTC/MediaStreamTypes":100,"../../service/RTC/RTCEvents.js":101,"../../service/RTC/StreamEventTypes.js":103,"../../service/UI/UIEvents":104,"../../service/desktopsharing/DesktopSharingEventTypes":107,"../../service/xmpp/XMPPEvents":110,"./DataChannels":6,"./LocalStream.js":7,"./MediaStream.js":8,"./RTCBrowserType":10,"./RTCUtils.js":11,"events":1}],10:[function(require,module,exports){ var currentBrowser; var browserVersion; var RTCBrowserType = { RTC_BROWSER_CHROME: "rtc_browser.chrome", RTC_BROWSER_OPERA: "rtc_browser.opera", RTC_BROWSER_FIREFOX: "rtc_browser.firefox", RTC_BROWSER_IEXPLORER: "rtc_browser.iexplorer", RTC_BROWSER_SAFARI: "rtc_browser.safari", getBrowserType: function () { return currentBrowser; }, isChrome: function () { return currentBrowser === RTCBrowserType.RTC_BROWSER_CHROME; }, isOpera: function () { return currentBrowser === RTCBrowserType.RTC_BROWSER_OPERA; }, isFirefox: function () { return currentBrowser === RTCBrowserType.RTC_BROWSER_FIREFOX; }, isIExplorer: function () { return currentBrowser === RTCBrowserType.RTC_BROWSER_IEXPLORER; }, isSafari: function () { return currentBrowser === RTCBrowserType.RTC_BROWSER_SAFARI; }, isTemasysPluginUsed: function () { return RTCBrowserType.isIExplorer() || RTCBrowserType.isSafari(); }, getFirefoxVersion: function () { return RTCBrowserType.isFirefox() ? browserVersion : null; }, getChromeVersion: function () { return RTCBrowserType.isChrome() ? browserVersion : null; }, usesPlanB: function() { return RTCBrowserType.isChrome() || RTCBrowserType.isOpera() || RTCBrowserType.isTemasysPluginUsed(); }, usesUnifiedPlan: function() { return RTCBrowserType.isFirefox(); } // Add version getters for other browsers when needed }; // detectOpera() must be called before detectChrome() !!! // otherwise Opera wil be detected as Chrome function detectChrome() { if (navigator.webkitGetUserMedia) { currentBrowser = RTCBrowserType.RTC_BROWSER_CHROME; var userAgent = navigator.userAgent.toLowerCase(); // We can assume that user agent is chrome, because it's // enforced when 'ext' streaming method is set var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10); console.log("This appears to be Chrome, ver: " + ver); return ver; } return null; } function detectOpera() { var userAgent = navigator.userAgent; if (userAgent.match(/Opera|OPR/)) { currentBrowser = RTCBrowserType.RTC_BROWSER_OPERA; var version = userAgent.match(/(Opera|OPR) ?\/?(\d+)\.?/)[2]; console.info("This appears to be Opera, ver: " + version); return version; } return null; } function detectFirefox() { if (navigator.mozGetUserMedia) { currentBrowser = RTCBrowserType.RTC_BROWSER_FIREFOX; var version = parseInt( navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); console.log('This appears to be Firefox, ver: ' + version); return version; } return null; } function detectSafari() { if ((/^((?!chrome).)*safari/i.test(navigator.userAgent))) { currentBrowser = RTCBrowserType.RTC_BROWSER_SAFARI; console.info("This appears to be Safari"); // FIXME detect Safari version when needed return 1; } return null; } function detectIE() { var version; var ua = window.navigator.userAgent; var msie = ua.indexOf('MSIE '); if (msie > 0) { // IE 10 or older => return version number version = parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); } var trident = ua.indexOf('Trident/'); if (!version && trident > 0) { // IE 11 => return version number var rv = ua.indexOf('rv:'); version = parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10); } var edge = ua.indexOf('Edge/'); if (!version && edge > 0) { // IE 12 => return version number version = parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10); } if (version) { currentBrowser = RTCBrowserType.RTC_BROWSER_IEXPLORER; console.info("This appears to be IExplorer, ver: " + version); } return version; } function detectBrowser() { var version; var detectors = [ detectOpera, detectChrome, detectFirefox, detectIE, detectSafari ]; // Try all browser detectors for (var i = 0; i < detectors.length; i++) { version = detectors[i](); if (version) return version; } console.error("Failed to detect browser type"); return undefined; } browserVersion = detectBrowser(); module.exports = RTCBrowserType; },{}],11:[function(require,module,exports){ var RTCBrowserType = require("./RTCBrowserType"); var Resolutions = require("../../service/RTC/Resolutions"); var AdapterJS = require("./adapter.screenshare"); var SDPUtil = require("../xmpp/SDPUtil"); var currentResolution = null; function getPreviousResolution(resolution) { if(!Resolutions[resolution]) return null; var order = Resolutions[resolution].order; var res = null; var resName = null; for(var i in Resolutions) { var tmp = Resolutions[i]; if(res == null || (res.order < tmp.order && tmp.order < order)) { resName = i; res = tmp; } } return resName; } function setResolutionConstraints(constraints, resolution, isAndroid) { if (resolution && !constraints.video || isAndroid) { constraints.video = { mandatory: {}, optional: [] };// same behaviour as true } if(Resolutions[resolution]) { constraints.video.mandatory.minWidth = Resolutions[resolution].width; constraints.video.mandatory.minHeight = Resolutions[resolution].height; } else { if (isAndroid) { constraints.video.mandatory.minWidth = 320; constraints.video.mandatory.minHeight = 240; constraints.video.mandatory.maxFrameRate = 15; } } if (constraints.video.mandatory.minWidth) constraints.video.mandatory.maxWidth = constraints.video.mandatory.minWidth; if (constraints.video.mandatory.minHeight) constraints.video.mandatory.maxHeight = constraints.video.mandatory.minHeight; } function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid) { var constraints = {audio: false, video: false}; if (um.indexOf('video') >= 0) { constraints.video = { mandatory: {}, optional: [] };// same behaviour as true } if (um.indexOf('audio') >= 0) { constraints.audio = { mandatory: {}, optional: []};// same behaviour as true } if (um.indexOf('screen') >= 0) { if (RTCBrowserType.isChrome()) { constraints.video = { mandatory: { chromeMediaSource: "screen", googLeakyBucket: true, maxWidth: window.screen.width, maxHeight: window.screen.height, maxFrameRate: 3 }, optional: [] }; } else if (RTCBrowserType.isTemasysPluginUsed()) { constraints.video = { optional: [ { sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey } ] }; } else { console.error( "'screen' WebRTC media source is supported only in Chrome" + " and with Temasys plugin"); } } if (um.indexOf('desktop') >= 0) { constraints.video = { mandatory: { chromeMediaSource: "desktop", chromeMediaSourceId: desktopStream, googLeakyBucket: true, maxWidth: window.screen.width, maxHeight: window.screen.height, maxFrameRate: 3 }, optional: [] }; } if (constraints.audio) { // if it is good enough for hangouts... constraints.audio.optional.push( {googEchoCancellation: true}, {googAutoGainControl: true}, {googNoiseSupression: true}, {googHighpassFilter: true}, {googNoisesuppression2: true}, {googEchoCancellation2: true}, {googAutoGainControl2: true} ); } if (constraints.video) { constraints.video.optional.push( {googNoiseReduction: false} // chrome 37 workaround for issue 3807, reenable in M38 ); if (um.indexOf('video') >= 0) { constraints.video.optional.push( {googLeakyBucket: true} ); } } if (um.indexOf('video') >= 0) { setResolutionConstraints(constraints, resolution, isAndroid); } if (bandwidth) { // doesn't work currently, see webrtc issue 1846 if (!constraints.video) constraints.video = {mandatory: {}, optional: []};//same behaviour as true constraints.video.optional.push({bandwidth: bandwidth}); } if (fps) { // for some cameras it might be necessary to request 30fps // so they choose 30fps mjpg over 10fps yuy2 if (!constraints.video) constraints.video = {mandatory: {}, optional: []};// same behaviour as true; constraints.video.mandatory.minFrameRate = fps; } return constraints; } function RTCUtils(RTCService, onTemasysPluginReady) { var self = this; this.service = RTCService; if (RTCBrowserType.isFirefox()) { var FFversion = RTCBrowserType.getFirefoxVersion(); if (FFversion >= 40 && config.useBundle && config.useRtcpMux) { this.peerconnection = mozRTCPeerConnection; this.getUserMedia = navigator.mozGetUserMedia.bind(navigator); this.pc_constraints = {}; this.attachMediaStream = function (element, stream) { // srcObject is being standardized and FF will eventually // support that unprefixed. FF also supports the // "element.src = URL.createObjectURL(...)" combo, but that // will be deprecated in favour of srcObject. // // https://groups.google.com/forum/#!topic/mozilla.dev.media/pKOiioXonJg // https://github.com/webrtc/samples/issues/302 if(!element[0]) return; element[0].mozSrcObject = stream; element[0].play(); }; this.getStreamID = function (stream) { var id = stream.id; if (!id) { var tracks = stream.getVideoTracks(); if (!tracks || tracks.length === 0) { tracks = stream.getAudioTracks(); } id = tracks[0].id; } return SDPUtil.filter_special_chars(id); }; this.getVideoSrc = function (element) { if(!element) return null; return element.mozSrcObject; }; this.setVideoSrc = function (element, src) { if(element) element.mozSrcObject = src; }; RTCSessionDescription = mozRTCSessionDescription; RTCIceCandidate = mozRTCIceCandidate; } else { console.error( "Firefox requirements not met, ver: " + FFversion + ", bundle: " + config.useBundle + ", rtcp-mux: " + config.useRtcpMux); window.location.href = 'unsupported_browser.html'; return; } } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) { this.peerconnection = webkitRTCPeerConnection; this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); this.attachMediaStream = function (element, stream) { element.attr('src', webkitURL.createObjectURL(stream)); }; this.getStreamID = function (stream) { // streams from FF endpoints have the characters '{' and '}' // that make jQuery choke. return SDPUtil.filter_special_chars(stream.id); }; this.getVideoSrc = function (element) { if(!element) return null; return element.getAttribute("src"); }; this.setVideoSrc = function (element, src) { if(element) element.setAttribute("src", src); }; // DTLS should now be enabled by default but.. this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]}; if (navigator.userAgent.indexOf('Android') != -1) { this.pc_constraints = {}; // disable DTLS on Android } if (!webkitMediaStream.prototype.getVideoTracks) { webkitMediaStream.prototype.getVideoTracks = function () { return this.videoTracks; }; } if (!webkitMediaStream.prototype.getAudioTracks) { webkitMediaStream.prototype.getAudioTracks = function () { return this.audioTracks; }; } } // Detect IE/Safari else if (RTCBrowserType.isTemasysPluginUsed()) { //AdapterJS.WebRTCPlugin.setLogLevel( // AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE); AdapterJS.webRTCReady(function (isPlugin) { self.peerconnection = RTCPeerConnection; self.getUserMedia = getUserMedia; self.attachMediaStream = function (elSel, stream) { if (stream.id === "dummyAudio" || stream.id === "dummyVideo") { return; } attachMediaStream(elSel[0], stream); }; self.getStreamID = function (stream) { var id = SDPUtil.filter_special_chars(stream.label); return id; }; self.getVideoSrc = function (element) { if (!element) { console.warn("Attempt to get video SRC of null element"); return null; } var src = null; var children = element.children; for (var i = 0; i !== children.length; ++i) { if (children[i].name === 'streamId') { return children[i].value; } } //console.info(element.id + " SRC: " + src); return null; }; self.setVideoSrc = function (element, src) { //console.info("Set video src: ", element, src); if (!src) { console.warn("Not attaching video stream, 'src' is null"); return; } AdapterJS.WebRTCPlugin.WaitForPluginReady(); var stream = AdapterJS.WebRTCPlugin.plugin .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src); attachMediaStream(element, stream); }; onTemasysPluginReady(isPlugin); }); } else { try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { } window.location.href = 'unsupported_browser.html'; } } RTCUtils.prototype.getUserMediaWithConstraints = function( um, success_callback, failure_callback, resolution,bandwidth, fps, desktopStream) { currentResolution = resolution; // Check if we are running on Android device var isAndroid = navigator.userAgent.indexOf('Android') != -1; var constraints = getConstraints( um, resolution, bandwidth, fps, desktopStream, isAndroid); console.info("Get media constraints", constraints); var self = this; try { this.getUserMedia(constraints, function (stream) { console.log('onUserMediaSuccess'); self.setAvailableDevices(um, true); success_callback(stream); }, function (error) { self.setAvailableDevices(um, false); console.warn('Failed to get access to local media. Error ', error, constraints); if (failure_callback) { failure_callback(error); } }); } catch (e) { console.error('GUM failed: ', e); if(failure_callback) { failure_callback(e); } } }; RTCUtils.prototype.setAvailableDevices = function (um, available) { var devices = {}; if(um.indexOf("video") != -1) { devices.video = available; } if(um.indexOf("audio") != -1) { devices.audio = available; } this.service.setDeviceAvailability(devices); } /** * We ask for audio and video combined stream in order to get permissions and * not to ask twice. */ RTCUtils.prototype.obtainAudioAndVideoPermissions = function(devices, callback, usageOptions) { var self = this; // Get AV var successCallback = function (stream) { if(callback) callback(stream, usageOptions); else self.successCallback(stream, usageOptions); }; if(!devices) devices = ['audio', 'video']; var newDevices = []; if(usageOptions) for(var i = 0; i < devices.length; i++) { var device = devices[i]; if(usageOptions[device] === true) newDevices.push(device); } else newDevices = devices; if(newDevices.length === 0) { successCallback(); return; } if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) { // With FF/IE we can't split the stream into audio and video because FF // doesn't support media stream constructors. So, we need to get the // audio stream separately from the video stream using two distinct GUM // calls. Not very user friendly :-( but we don't have many other // options neither. // // Note that we pack those 2 streams in a single object and pass it to // the successCallback method. var obtainVideo = function (audioStream) { self.getUserMediaWithConstraints( ['video'], function (videoStream) { return successCallback({ audioStream: audioStream, videoStream: videoStream }); }, function (error) { console.error( 'failed to obtain video stream - stop', error); self.errorCallback(error); }, config.resolution || '360'); }; var obtainAudio = function () { self.getUserMediaWithConstraints( ['audio'], function (audioStream) { if (newDevices.indexOf('video') !== -1) obtainVideo(audioStream); }, function (error) { console.error( 'failed to obtain audio stream - stop', error); self.errorCallback(error); } ); }; if (newDevices.indexOf('audio') !== -1) { obtainAudio(); } else { obtainVideo(null); } } else { this.getUserMediaWithConstraints( newDevices, function (stream) { successCallback(stream); }, function (error) { self.errorCallback(error); }, config.resolution || '360'); } }; RTCUtils.prototype.successCallback = function (stream, usageOptions) { // If this is FF or IE, the stream parameter is *not* a MediaStream object, // it's an object with two properties: audioStream, videoStream. if (stream && stream.getAudioTracks && stream.getVideoTracks) console.log('got', stream, stream.getAudioTracks().length, stream.getVideoTracks().length); this.handleLocalStream(stream, usageOptions); }; RTCUtils.prototype.errorCallback = function (error) { var self = this; console.error('failed to obtain audio/video stream - trying audio only', error); var resolution = getPreviousResolution(currentResolution); if(typeof error == "object" && error.constraintName && error.name && (error.name == "ConstraintNotSatisfiedError" || error.name == "OverconstrainedError") && (error.constraintName == "minWidth" || error.constraintName == "maxWidth" || error.constraintName == "minHeight" || error.constraintName == "maxHeight") && resolution != null) { self.getUserMediaWithConstraints(['audio', 'video'], function (stream) { return self.successCallback(stream); }, function (error) { return self.errorCallback(error); }, resolution); } else { self.getUserMediaWithConstraints( ['audio'], function (stream) { return self.successCallback(stream); }, function (error) { console.error('failed to obtain audio/video stream - stop', error); return self.successCallback(null); } ); } } RTCUtils.prototype.handleLocalStream = function(stream, usageOptions) { // If this is FF, the stream parameter is *not* a MediaStream object, it's // an object with two properties: audioStream, videoStream. var audioStream, videoStream; if(window.webkitMediaStream) { audioStream = new webkitMediaStream(); videoStream = new webkitMediaStream(); if(stream) { var audioTracks = stream.getAudioTracks(); for (var i = 0; i < audioTracks.length; i++) { audioStream.addTrack(audioTracks[i]); } var videoTracks = stream.getVideoTracks(); for (i = 0; i < videoTracks.length; i++) { videoStream.addTrack(videoTracks[i]); } } } else if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) { // Firefox and Temasys plugin if (stream && stream.audioStream) audioStream = stream.audioStream; else audioStream = new DummyMediaStream("dummyAudio"); if (stream && stream.videoStream) videoStream = stream.videoStream; else videoStream = new DummyMediaStream("dummyVideo"); } var audioMuted = (usageOptions && usageOptions.audio === false), videoMuted = (usageOptions && usageOptions.video === false); var audioGUM = (!usageOptions || usageOptions.audio !== false), videoGUM = (!usageOptions || usageOptions.video !== false); this.service.createLocalStream(audioStream, "audio", null, null, audioMuted, audioGUM); this.service.createLocalStream(videoStream, "video", null, null, videoMuted, videoGUM); }; function DummyMediaStream(id) { this.id = id; this.label = id; this.stop = function() { }; this.getAudioTracks = function() { return []; } this.getVideoTracks = function() { return []; } } RTCUtils.prototype.createStream = function(stream, isVideo) { var newStream = null; if (window.webkitMediaStream) { newStream = new webkitMediaStream(); if (newStream) { var tracks = (isVideo ? stream.getVideoTracks() : stream.getAudioTracks()); for (i = 0; i < tracks.length; i++) { newStream.addTrack(tracks[i]); } } } else { // FIXME: this is duplicated with 'handleLocalStream' !!! if (stream) { newStream = stream; } else { newStream = new DummyMediaStream(isVideo ? "dummyVideo" : "dummyAudio"); } } return newStream; }; module.exports = RTCUtils; },{"../../service/RTC/Resolutions":102,"../xmpp/SDPUtil":58,"./RTCBrowserType":10,"./adapter.screenshare":12}],12:[function(require,module,exports){ /*! adapterjs - v0.11.0 - 2015-06-08 */ // Adapter's interface. var AdapterJS = AdapterJS || {}; // Browserify compatibility if(typeof exports !== 'undefined') { module.exports = AdapterJS; } AdapterJS.options = AdapterJS.options || {}; // uncomment to get virtual webcams // AdapterJS.options.getAllCams = true; // uncomment to prevent the install prompt when the plugin in not yet installed // AdapterJS.options.hidePluginInstallPrompt = true; // AdapterJS version AdapterJS.VERSION = '0.11.0'; // This function will be called when the WebRTC API is ready to be used // Whether it is the native implementation (Chrome, Firefox, Opera) or // the plugin // You may Override this function to synchronise the start of your application // with the WebRTC API being ready. // If you decide not to override use this synchronisation, it may result in // an extensive CPU usage on the plugin start (once per tab loaded) // Params: // - isUsingPlugin: true is the WebRTC plugin is being used, false otherwise // AdapterJS.onwebrtcready = AdapterJS.onwebrtcready || function(isUsingPlugin) { // The WebRTC API is ready. // Override me and do whatever you want here }; // Sets a callback function to be called when the WebRTC interface is ready. // The first argument is the function to callback.\ // Throws an error if the first argument is not a function AdapterJS.webRTCReady = function (callback) { if (typeof callback !== 'function') { throw new Error('Callback provided is not a function'); } if (true === AdapterJS.onwebrtcreadyDone) { // All WebRTC interfaces are ready, just call the callback callback(null !== AdapterJS.WebRTCPlugin.plugin); } else { // will be triggered automatically when your browser/plugin is ready. AdapterJS.onwebrtcready = callback; } }; // Plugin namespace AdapterJS.WebRTCPlugin = AdapterJS.WebRTCPlugin || {}; // The object to store plugin information AdapterJS.WebRTCPlugin.pluginInfo = { prefix : 'Tem', plugName : 'TemWebRTCPlugin', pluginId : 'plugin0', type : 'application/x-temwebrtcplugin', onload : '__TemWebRTCReady0', portalLink : 'http://skylink.io/plugin/', downloadLink : null, //set below companyName: 'Temasys' }; if(!!navigator.platform.match(/^Mac/i)) { AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1n77hco'; } else if(!!navigator.platform.match(/^Win/i)) { AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1kkS4FN'; } // Unique identifier of each opened page AdapterJS.WebRTCPlugin.pageId = Math.random().toString(36).slice(2); // Use this whenever you want to call the plugin. AdapterJS.WebRTCPlugin.plugin = null; // Set log level for the plugin once it is ready. // The different values are // This is an asynchronous function that will run when the plugin is ready AdapterJS.WebRTCPlugin.setLogLevel = null; // Defines webrtc's JS interface according to the plugin's implementation. // Define plugin Browsers as WebRTC Interface. AdapterJS.WebRTCPlugin.defineWebRTCInterface = null; // This function detects whether or not a plugin is installed. // Checks if Not IE (firefox, for example), else if it's IE, // we're running IE and do something. If not it is not supported. AdapterJS.WebRTCPlugin.isPluginInstalled = null; // Lets adapter.js wait until the the document is ready before injecting the plugin AdapterJS.WebRTCPlugin.pluginInjectionInterval = null; // Inject the HTML DOM object element into the page. AdapterJS.WebRTCPlugin.injectPlugin = null; // States of readiness that the plugin goes through when // being injected and stated AdapterJS.WebRTCPlugin.PLUGIN_STATES = { NONE : 0, // no plugin use INITIALIZING : 1, // Detected need for plugin INJECTING : 2, // Injecting plugin INJECTED: 3, // Plugin element injected but not usable yet READY: 4 // Plugin ready to be used }; // Current state of the plugin. You cannot use the plugin before this is // equal to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.NONE; // True is AdapterJS.onwebrtcready was already called, false otherwise // Used to make sure AdapterJS.onwebrtcready is only called once AdapterJS.onwebrtcreadyDone = false; // Log levels for the plugin. // To be set by calling AdapterJS.WebRTCPlugin.setLogLevel /* Log outputs are prefixed in some cases. INFO: Information reported by the plugin. ERROR: Errors originating from within the plugin. WEBRTC: Error originating from within the libWebRTC library */ // From the least verbose to the most verbose AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS = { NONE : 'NONE', ERROR : 'ERROR', WARNING : 'WARNING', INFO: 'INFO', VERBOSE: 'VERBOSE', SENSITIVE: 'SENSITIVE' }; // Does a waiting check before proceeding to load the plugin. AdapterJS.WebRTCPlugin.WaitForPluginReady = null; // This methid will use an interval to wait for the plugin to be ready. AdapterJS.WebRTCPlugin.callWhenPluginReady = null; // !!!! WARNING: DO NOT OVERRIDE THIS FUNCTION. !!! // This function will be called when plugin is ready. It sends necessary // details to the plugin. // The function will wait for the document to be ready and the set the // plugin state to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY, // indicating that it can start being requested. // This function is not in the IE/Safari condition brackets so that // TemPluginLoaded function might be called on Chrome/Firefox. // This function is the only private function that is not encapsulated to // allow the plugin method to be called. __TemWebRTCReady0 = function () { if (document.readyState === 'complete') { AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; AdapterJS.maybeThroughWebRTCReady(); } else { AdapterJS.WebRTCPlugin.documentReadyInterval = setInterval(function () { if (document.readyState === 'complete') { // TODO: update comments, we wait for the document to be ready clearInterval(AdapterJS.WebRTCPlugin.documentReadyInterval); AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; AdapterJS.maybeThroughWebRTCReady(); } }, 100); } }; AdapterJS.maybeThroughWebRTCReady = function() { if (!AdapterJS.onwebrtcreadyDone) { AdapterJS.onwebrtcreadyDone = true; if (typeof(AdapterJS.onwebrtcready) === 'function') { AdapterJS.onwebrtcready(AdapterJS.WebRTCPlugin.plugin !== null); } } }; // Text namespace AdapterJS.TEXT = { PLUGIN: { REQUIRE_INSTALLATION: 'This website requires you to install a WebRTC-enabling plugin ' + 'to work on this browser.', NOT_SUPPORTED: 'Your browser does not support WebRTC.', BUTTON: 'Install Now' }, REFRESH: { REQUIRE_REFRESH: 'Please refresh page', BUTTON: 'Refresh Page' } }; // The result of ice connection states. // - starting: Ice connection is starting. // - checking: Ice connection is checking. // - connected Ice connection is connected. // - completed Ice connection is connected. // - done Ice connection has been completed. // - disconnected Ice connection has been disconnected. // - failed Ice connection has failed. // - closed Ice connection is closed. AdapterJS._iceConnectionStates = { starting : 'starting', checking : 'checking', connected : 'connected', completed : 'connected', done : 'completed', disconnected : 'disconnected', failed : 'failed', closed : 'closed' }; //The IceConnection states that has been fired for each peer. AdapterJS._iceConnectionFiredStates = []; // Check if WebRTC Interface is defined. AdapterJS.isDefined = null; // This function helps to retrieve the webrtc detected browser information. // This sets: // - webrtcDetectedBrowser: The browser agent name. // - webrtcDetectedVersion: The browser version. // - webrtcDetectedType: The types of webRTC support. // - 'moz': Mozilla implementation of webRTC. // - 'webkit': WebKit implementation of webRTC. // - 'plugin': Using the plugin implementation. AdapterJS.parseWebrtcDetectedBrowser = function () { var hasMatch, checkMatch = navigator.userAgent.match( /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; if (/trident/i.test(checkMatch[1])) { hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; webrtcDetectedBrowser = 'IE'; webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); } else if (checkMatch[1] === 'Chrome') { hasMatch = navigator.userAgent.match(/\bOPR\/(\d+)/); if (hasMatch !== null) { webrtcDetectedBrowser = 'opera'; webrtcDetectedVersion = parseInt(hasMatch[1], 10); } } if (navigator.userAgent.indexOf('Safari')) { if (typeof InstallTrigger !== 'undefined') { webrtcDetectedBrowser = 'firefox'; } else if (/*@cc_on!@*/ false || !!document.documentMode) { webrtcDetectedBrowser = 'IE'; } else if ( Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) { webrtcDetectedBrowser = 'safari'; } else if (!!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0) { webrtcDetectedBrowser = 'opera'; } else if (!!window.chrome) { webrtcDetectedBrowser = 'chrome'; } } if (!webrtcDetectedBrowser) { webrtcDetectedVersion = checkMatch[1]; } if (!webrtcDetectedVersion) { try { checkMatch = (checkMatch[2]) ? [checkMatch[1], checkMatch[2]] : [navigator.appName, navigator.appVersion, '-?']; if ((hasMatch = navigator.userAgent.match(/version\/(\d+)/i)) !== null) { checkMatch.splice(1, 1, hasMatch[1]); } webrtcDetectedVersion = parseInt(checkMatch[1], 10); } catch (error) { } } }; // To fix configuration as some browsers does not support // the 'urls' attribute. AdapterJS.maybeFixConfiguration = function (pcConfig) { if (pcConfig === null) { return; } for (var i = 0; i < pcConfig.iceServers.length; i++) { if (pcConfig.iceServers[i].hasOwnProperty('urls')) { pcConfig.iceServers[i].url = pcConfig.iceServers[i].urls; delete pcConfig.iceServers[i].urls; } } }; AdapterJS.addEvent = function(elem, evnt, func) { if (elem.addEventListener) { // W3C DOM elem.addEventListener(evnt, func, false); } else if (elem.attachEvent) {// OLD IE DOM elem.attachEvent('on'+evnt, func); } else { // No much to do elem[evnt] = func; } }; AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNewTab, displayRefreshBar) { // only inject once the page is ready if (document.readyState !== 'complete') { return; } var w = window; var i = document.createElement('iframe'); i.style.position = 'fixed'; i.style.top = '-41px'; i.style.left = 0; i.style.right = 0; i.style.width = '100%'; i.style.height = '40px'; i.style.backgroundColor = '#ffffe1'; i.style.border = 'none'; i.style.borderBottom = '1px solid #888888'; i.style.zIndex = '9999999'; if(typeof i.style.webkitTransition === 'string') { i.style.webkitTransition = 'all .5s ease-out'; } else if(typeof i.style.transition === 'string') { i.style.transition = 'all .5s ease-out'; } document.body.appendChild(i); c = (i.contentWindow) ? i.contentWindow : (i.contentDocument.document) ? i.contentDocument.document : i.contentDocument; c.document.open(); c.document.write('' + text + ''); if(buttonText && buttonLink) { c.document.write(''); c.document.close(); AdapterJS.addEvent(c.document.getElementById('okay'), 'click', function(e) { if (!!displayRefreshBar) { AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION ? AdapterJS.TEXT.EXTENSION.REQUIRE_REFRESH : AdapterJS.TEXT.REFRESH.REQUIRE_REFRESH, AdapterJS.TEXT.REFRESH.BUTTON, 'javascript:location.reload()'); } window.open(buttonLink, !!openNewTab ? '_blank' : '_top'); e.preventDefault(); try { event.cancelBubble = true; } catch(error) { } }); } else { c.document.close(); } AdapterJS.addEvent(c.document, 'click', function() { w.document.body.removeChild(i); }); setTimeout(function() { if(typeof i.style.webkitTransform === 'string') { i.style.webkitTransform = 'translateY(40px)'; } else if(typeof i.style.transform === 'string') { i.style.transform = 'translateY(40px)'; } else { i.style.top = '0px'; } }, 300); }; // ----------------------------------------------------------- // Detected webrtc implementation. Types are: // - 'moz': Mozilla implementation of webRTC. // - 'webkit': WebKit implementation of webRTC. // - 'plugin': Using the plugin implementation. webrtcDetectedType = null; // Detected webrtc datachannel support. Types are: // - 'SCTP': SCTP datachannel support. // - 'RTP': RTP datachannel support. webrtcDetectedDCSupport = null; // Set the settings for creating DataChannels, MediaStream for // Cross-browser compability. // - This is only for SCTP based support browsers. // the 'urls' attribute. checkMediaDataChannelSettings = function (peerBrowserAgent, peerBrowserVersion, callback, constraints) { if (typeof callback !== 'function') { return; } var beOfferer = true; var isLocalFirefox = webrtcDetectedBrowser === 'firefox'; // Nightly version does not require MozDontOfferDataChannel for interop var isLocalFirefoxInterop = webrtcDetectedType === 'moz' && webrtcDetectedVersion > 30; var isPeerFirefox = peerBrowserAgent === 'firefox'; var isPeerFirefoxInterop = peerBrowserAgent === 'firefox' && ((peerBrowserVersion) ? (peerBrowserVersion > 30) : false); // Resends an updated version of constraints for MozDataChannel to work // If other userAgent is firefox and user is firefox, remove MozDataChannel if ((isLocalFirefox && isPeerFirefox) || (isLocalFirefoxInterop)) { try { delete constraints.mandatory.MozDontOfferDataChannel; } catch (error) { console.error('Failed deleting MozDontOfferDataChannel'); console.error(error); } } else if ((isLocalFirefox && !isPeerFirefox)) { constraints.mandatory.MozDontOfferDataChannel = true; } if (!isLocalFirefox) { // temporary measure to remove Moz* constraints in non Firefox browsers for (var prop in constraints.mandatory) { if (constraints.mandatory.hasOwnProperty(prop)) { if (prop.indexOf('Moz') !== -1) { delete constraints.mandatory[prop]; } } } } // Firefox (not interopable) cannot offer DataChannel as it will cause problems to the // interopability of the media stream if (isLocalFirefox && !isPeerFirefox && !isLocalFirefoxInterop) { beOfferer = false; } callback(beOfferer, constraints); }; // Handles the differences for all browsers ice connection state output. // - Tested outcomes are: // - Chrome (offerer) : 'checking' > 'completed' > 'completed' // - Chrome (answerer) : 'checking' > 'connected' // - Firefox (offerer) : 'checking' > 'connected' // - Firefox (answerer): 'checking' > 'connected' checkIceConnectionState = function (peerId, iceConnectionState, callback) { if (typeof callback !== 'function') { console.warn('No callback specified in checkIceConnectionState. Aborted.'); return; } peerId = (peerId) ? peerId : 'peer'; if (!AdapterJS._iceConnectionFiredStates[peerId] || iceConnectionState === AdapterJS._iceConnectionStates.disconnected || iceConnectionState === AdapterJS._iceConnectionStates.failed || iceConnectionState === AdapterJS._iceConnectionStates.closed) { AdapterJS._iceConnectionFiredStates[peerId] = []; } iceConnectionState = AdapterJS._iceConnectionStates[iceConnectionState]; if (AdapterJS._iceConnectionFiredStates[peerId].indexOf(iceConnectionState) < 0) { AdapterJS._iceConnectionFiredStates[peerId].push(iceConnectionState); if (iceConnectionState === AdapterJS._iceConnectionStates.connected) { setTimeout(function () { AdapterJS._iceConnectionFiredStates[peerId] .push(AdapterJS._iceConnectionStates.done); callback(AdapterJS._iceConnectionStates.done); }, 1000); } callback(iceConnectionState); } return; }; // Firefox: // - Creates iceServer from the url for Firefox. // - Create iceServer with stun url. // - Create iceServer with turn url. // - Ignore the transport parameter from TURN url for FF version <=27. // - Return null for createIceServer if transport=tcp. // - FF 27 and above supports transport parameters in TURN url, // - So passing in the full url to create iceServer. // Chrome: // - Creates iceServer from the url for Chrome M33 and earlier. // - Create iceServer with stun url. // - Chrome M28 & above uses below TURN format. // Plugin: // - Creates Ice Server for Plugin Browsers // - If Stun - Create iceServer with stun url. // - Else - Create iceServer with turn url // - This is a WebRTC Function createIceServer = null; // Firefox: // - Creates IceServers for Firefox // - Use .url for FireFox. // - Multiple Urls support // Chrome: // - Creates iceServers from the urls for Chrome M34 and above. // - .urls is supported since Chrome M34. // - Multiple Urls support // Plugin: // - Creates Ice Servers for Plugin Browsers // - Multiple Urls support // - This is a WebRTC Function createIceServers = null; //------------------------------------------------------------ //The RTCPeerConnection object. RTCPeerConnection = null; // Creates RTCSessionDescription object for Plugin Browsers RTCSessionDescription = (typeof RTCSessionDescription === 'function') ? RTCSessionDescription : null; // Creates RTCIceCandidate object for Plugin Browsers RTCIceCandidate = (typeof RTCIceCandidate === 'function') ? RTCIceCandidate : null; // Get UserMedia (only difference is the prefix). // Code from Adam Barth. getUserMedia = null; // Attach a media stream to an element. attachMediaStream = null; // Re-attach a media stream to an element. reattachMediaStream = null; // Detected browser agent name. Types are: // - 'firefox': Firefox browser. // - 'chrome': Chrome browser. // - 'opera': Opera browser. // - 'safari': Safari browser. // - 'IE' - Internet Explorer browser. webrtcDetectedBrowser = null; // Detected browser version. webrtcDetectedVersion = null; // Check for browser types and react accordingly if (navigator.mozGetUserMedia) { webrtcDetectedBrowser = 'firefox'; webrtcDetectedVersion = parseInt(navigator .userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); webrtcDetectedType = 'moz'; webrtcDetectedDCSupport = 'SCTP'; RTCPeerConnection = function (pcConfig, pcConstraints) { AdapterJS.maybeFixConfiguration(pcConfig); return new mozRTCPeerConnection(pcConfig, pcConstraints); }; // The RTCSessionDescription object. RTCSessionDescription = mozRTCSessionDescription; window.RTCSessionDescription = RTCSessionDescription; // The RTCIceCandidate object. RTCIceCandidate = mozRTCIceCandidate; window.RTCIceCandidate = RTCIceCandidate; window.getUserMedia = navigator.mozGetUserMedia.bind(navigator); navigator.getUserMedia = window.getUserMedia; // Shim for MediaStreamTrack.getSources. MediaStreamTrack.getSources = function(successCb) { setTimeout(function() { var infos = [ { kind: 'audio', id: 'default', label:'', facing:'' }, { kind: 'video', id: 'default', label:'', facing:'' } ]; successCb(infos); }, 0); }; createIceServer = function (url, username, password) { var iceServer = null; var url_parts = url.split(':'); if (url_parts[0].indexOf('stun') === 0) { iceServer = { url : url }; } else if (url_parts[0].indexOf('turn') === 0) { if (webrtcDetectedVersion < 27) { var turn_url_parts = url.split('?'); if (turn_url_parts.length === 1 || turn_url_parts[1].indexOf('transport=udp') === 0) { iceServer = { url : turn_url_parts[0], credential : password, username : username }; } } else { iceServer = { url : url, credential : password, username : username }; } } return iceServer; }; createIceServers = function (urls, username, password) { var iceServers = []; for (i = 0; i < urls.length; i++) { var iceServer = createIceServer(urls[i], username, password); if (iceServer !== null) { iceServers.push(iceServer); } } return iceServers; }; attachMediaStream = function (element, stream) { element.mozSrcObject = stream; if (stream !== null) element.play(); return element; }; reattachMediaStream = function (to, from) { to.mozSrcObject = from.mozSrcObject; to.play(); return to; }; MediaStreamTrack.getSources = MediaStreamTrack.getSources || function (callback) { if (!callback) { throw new TypeError('Failed to execute \'getSources\' on \'MediaStreamTrack\'' + ': 1 argument required, but only 0 present.'); } return callback([]); }; // Fake get{Video,Audio}Tracks if (!MediaStream.prototype.getVideoTracks) { MediaStream.prototype.getVideoTracks = function () { return []; }; } if (!MediaStream.prototype.getAudioTracks) { MediaStream.prototype.getAudioTracks = function () { return []; }; } AdapterJS.maybeThroughWebRTCReady(); } else if (navigator.webkitGetUserMedia) { webrtcDetectedBrowser = 'chrome'; webrtcDetectedType = 'webkit'; webrtcDetectedVersion = parseInt(navigator .userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10); // check if browser is opera 20+ var checkIfOpera = navigator.userAgent.match(/\bOPR\/(\d+)/); if (checkIfOpera !== null) { webrtcDetectedBrowser = 'opera'; webrtcDetectedVersion = parseInt(checkIfOpera[1], 10); } // check browser datachannel support if ((webrtcDetectedBrowser === 'chrome' && webrtcDetectedVersion >= 31) || (webrtcDetectedBrowser === 'opera' && webrtcDetectedVersion >= 20)) { webrtcDetectedDCSupport = 'SCTP'; } else if (webrtcDetectedBrowser === 'chrome' && webrtcDetectedVersion < 30 && webrtcDetectedVersion > 24) { webrtcDetectedDCSupport = 'RTP'; } else { webrtcDetectedDCSupport = ''; } createIceServer = function (url, username, password) { var iceServer = null; var url_parts = url.split(':'); if (url_parts[0].indexOf('stun') === 0) { iceServer = { 'url' : url }; } else if (url_parts[0].indexOf('turn') === 0) { iceServer = { 'url' : url, 'credential' : password, 'username' : username }; } return iceServer; }; createIceServers = function (urls, username, password) { var iceServers = []; if (webrtcDetectedVersion >= 34) { iceServers = { 'urls' : urls, 'credential' : password, 'username' : username }; } else { for (i = 0; i < urls.length; i++) { var iceServer = createIceServer(urls[i], username, password); if (iceServer !== null) { iceServers.push(iceServer); } } } return iceServers; }; RTCPeerConnection = function (pcConfig, pcConstraints) { if (webrtcDetectedVersion < 34) { AdapterJS.maybeFixConfiguration(pcConfig); } return new webkitRTCPeerConnection(pcConfig, pcConstraints); }; window.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); navigator.getUserMedia = window.getUserMedia; attachMediaStream = function (element, stream) { if (typeof element.srcObject !== 'undefined') { element.srcObject = stream; } else if (typeof element.mozSrcObject !== 'undefined') { element.mozSrcObject = stream; } else if (typeof element.src !== 'undefined') { element.src = (stream === null ? '' : URL.createObjectURL(stream)); } else { console.log('Error attaching stream to element.'); } return element; }; reattachMediaStream = function (to, from) { to.src = from.src; return to; }; AdapterJS.maybeThroughWebRTCReady(); } else { // TRY TO USE PLUGIN // IE 9 is not offering an implementation of console.log until you open a console if (typeof console !== 'object' || typeof console.log !== 'function') { /* jshint -W020 */ console = {} || console; // Implemented based on console specs from MDN // You may override these functions console.log = function (arg) {}; console.info = function (arg) {}; console.error = function (arg) {}; console.dir = function (arg) {}; console.exception = function (arg) {}; console.trace = function (arg) {}; console.warn = function (arg) {}; console.count = function (arg) {}; console.debug = function (arg) {}; console.count = function (arg) {}; console.time = function (arg) {}; console.timeEnd = function (arg) {}; console.group = function (arg) {}; console.groupCollapsed = function (arg) {}; console.groupEnd = function (arg) {}; /* jshint +W020 */ } webrtcDetectedType = 'plugin'; webrtcDetectedDCSupport = 'plugin'; AdapterJS.parseWebrtcDetectedBrowser(); isIE = webrtcDetectedBrowser === 'IE'; /* jshint -W035 */ AdapterJS.WebRTCPlugin.WaitForPluginReady = function() { while (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { /* empty because it needs to prevent the function from running. */ } }; /* jshint +W035 */ AdapterJS.WebRTCPlugin.callWhenPluginReady = function (callback) { if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { // Call immediately if possible // Once the plugin is set, the code will always take this path callback(); } else { // otherwise start a 100ms interval var checkPluginReadyState = setInterval(function () { if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { clearInterval(checkPluginReadyState); callback(); } }, 100); } }; AdapterJS.WebRTCPlugin.setLogLevel = function(logLevel) { AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { AdapterJS.WebRTCPlugin.plugin.setLogLevel(logLevel); }); }; AdapterJS.WebRTCPlugin.injectPlugin = function () { // only inject once the page is ready if (document.readyState !== 'complete') { return; } // Prevent multiple injections if (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING) { return; } AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTING; if (webrtcDetectedBrowser === 'IE' && webrtcDetectedVersion <= 10) { var frag = document.createDocumentFragment(); AdapterJS.WebRTCPlugin.plugin = document.createElement('div'); AdapterJS.WebRTCPlugin.plugin.innerHTML = '' + ' ' + ' ' + ' ' + '' + // 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 () { AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING; AdapterJS.isDefined = function (variable) { return variable !== null && variable !== undefined; }; createIceServer = function (url, username, password) { var iceServer = null; var url_parts = url.split(':'); if (url_parts[0].indexOf('stun') === 0) { iceServer = { 'url' : url, 'hasCredentials' : false }; } else if (url_parts[0].indexOf('turn') === 0) { iceServer = { 'url' : url, 'hasCredentials' : true, 'credential' : password, 'username' : username }; } return iceServer; }; createIceServers = function (urls, username, password) { var iceServers = []; for (var i = 0; i < urls.length; ++i) { iceServers.push(createIceServer(urls[i], username, password)); } return iceServers; }; RTCSessionDescription = function (info) { AdapterJS.WebRTCPlugin.WaitForPluginReady(); return AdapterJS.WebRTCPlugin.plugin. ConstructSessionDescription(info.type, info.sdp); }; RTCPeerConnection = function (servers, constraints) { var iceServers = null; if (servers) { iceServers = servers.iceServers; for (var i = 0; i < iceServers.length; i++) { if (iceServers[i].urls && !iceServers[i].url) { iceServers[i].url = iceServers[i].urls; } iceServers[i].hasCredentials = AdapterJS. isDefined(iceServers[i].username) && AdapterJS.isDefined(iceServers[i].credential); } } var mandatory = (constraints && constraints.mandatory) ? constraints.mandatory : null; var optional = (constraints && constraints.optional) ? constraints.optional : null; AdapterJS.WebRTCPlugin.WaitForPluginReady(); return AdapterJS.WebRTCPlugin.plugin. PeerConnection(AdapterJS.WebRTCPlugin.pageId, iceServers, mandatory, optional); }; MediaStreamTrack = {}; MediaStreamTrack.getSources = function (callback) { AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { AdapterJS.WebRTCPlugin.plugin.GetSources(callback); }); }; window.getUserMedia = function (constraints, successCallback, failureCallback) { constraints.audio = constraints.audio || false; constraints.video = constraints.video || false; AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { AdapterJS.WebRTCPlugin.plugin. getUserMedia(constraints, successCallback, failureCallback); }); }; window.navigator.getUserMedia = window.getUserMedia; attachMediaStream = function (element, stream) { if (!element || !element.parentNode) { return; } var streamId if (stream === null) { streamId = ''; } else { stream.enableSoundTracks(true); streamId = stream.id; } if (element.nodeName.toLowerCase() !== 'audio') { var elementId = element.id.length === 0 ? Math.random().toString(36).slice(2) : element.id; if (!element.isWebRTCPlugin || !element.isWebRTCPlugin()) { var frag = document.createDocumentFragment(); var temp = document.createElement('div'); var classHTML = ''; if (element.className) { classHTML = 'class="' + element.className + '" '; } else if (element.attributes && element.attributes['class']) { classHTML = 'class="' + element.attributes['class'].value + '" '; } temp.innerHTML = '' + ' ' + ' ' + ' ' + ' ' + ''; while (temp.firstChild) { frag.appendChild(temp.firstChild); } var height = ''; var width = ''; if (element.getBoundingClientRect) { var rectObject = element.getBoundingClientRect(); width = rectObject.width + 'px'; height = rectObject.height + 'px'; } else if (element.width) { width = element.width; height = element.height; } else { // TODO: What scenario could bring us here? } element.parentNode.insertBefore(frag, element); frag = document.getElementById(elementId); frag.width = width; frag.height = height; element.parentNode.removeChild(element); } else { var children = element.children; for (var i = 0; i !== children.length; ++i) { if (children[i].name === 'streamId') { children[i].value = streamId; break; } } element.setStreamId(streamId); } var newElement = document.getElementById(elementId); newElement.onplaying = (element.onplaying) ? element.onplaying : function (arg) {}; if (isIE) { // on IE the event needs to be plugged manually newElement.attachEvent('onplaying', newElement.onplaying); newElement.onclick = (element.onclick) ? element.onclick : function (arg) {}; newElement._TemOnClick = function (id) { var arg = { srcElement : document.getElementById(id) }; newElement.onclick(arg); }; } return newElement; } else { return element; } }; reattachMediaStream = function (to, from) { var stream = null; var children = from.children; for (var i = 0; i !== children.length; ++i) { if (children[i].name === 'streamId') { AdapterJS.WebRTCPlugin.WaitForPluginReady(); stream = AdapterJS.WebRTCPlugin.plugin .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, children[i].value); break; } } if (stream !== null) { return attachMediaStream(to, stream); } else { console.log('Could not find the stream associated with this element'); } }; RTCIceCandidate = function (candidate) { if (!candidate.sdpMid) { candidate.sdpMid = ''; } AdapterJS.WebRTCPlugin.WaitForPluginReady(); return AdapterJS.WebRTCPlugin.plugin.ConstructIceCandidate( candidate.sdpMid, candidate.sdpMLineIndex, candidate.candidate ); }; // inject plugin AdapterJS.addEvent(document, 'readystatechange', AdapterJS.WebRTCPlugin.injectPlugin); AdapterJS.WebRTCPlugin.injectPlugin(); }; // This function will be called if the plugin is needed (browser different // from Chrome or Firefox), but the plugin is not installed. AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb = AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb || function() { AdapterJS.addEvent(document, 'readystatechange', AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv); AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv(); }; AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv = function () { if (AdapterJS.options.hidePluginInstallPrompt) { return; } var downloadLink = AdapterJS.WebRTCPlugin.pluginInfo.downloadLink; if(downloadLink) { // if download link var popupString; if (AdapterJS.WebRTCPlugin.pluginInfo.portalLink) { // is portal link popupString = 'This website requires you to install the ' + ' ' + AdapterJS.WebRTCPlugin.pluginInfo.companyName + ' WebRTC Plugin' + ' to work on this browser.'; } else { // no portal link, just print a generic explanation popupString = AdapterJS.TEXT.PLUGIN.REQUIRE_INSTALLATION; } AdapterJS.renderNotificationBar(popupString, AdapterJS.TEXT.PLUGIN.BUTTON, downloadLink); } else { // no download link, just print a generic explanation AdapterJS.renderNotificationBar(AdapterJS.TEXT.PLUGIN.NOT_SUPPORTED); } }; // Try to detect the plugin and act accordingly AdapterJS.WebRTCPlugin.isPluginInstalled( AdapterJS.WebRTCPlugin.pluginInfo.prefix, AdapterJS.WebRTCPlugin.pluginInfo.plugName, AdapterJS.WebRTCPlugin.defineWebRTCInterface, AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb); } (function () { 'use strict'; var baseGetUserMedia = null; AdapterJS.TEXT.EXTENSION = { REQUIRE_INSTALLATION_FF: 'To enable screensharing you need to install the Skylink WebRTC tools Firefox Add-on.', REQUIRE_INSTALLATION_CHROME: 'To enable screensharing you need to install the Skylink WebRTC tools Chrome Extension.', REQUIRE_REFRESH: 'Please refresh this page after the Skylink WebRTC tools extension has been installed.', BUTTON_FF: 'Install Now', BUTTON_CHROME: 'Go to Chrome Web Store' }; var clone = function(obj) { if (null == obj || "object" != typeof obj) return obj; var copy = obj.constructor(); for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; } return copy; }; if (window.navigator.mozGetUserMedia) { baseGetUserMedia = window.navigator.getUserMedia; navigator.getUserMedia = function (constraints, successCb, failureCb) { if (constraints && constraints.video && !!constraints.video.mediaSource) { // intercepting screensharing requests if (constraints.video.mediaSource !== 'screen' && constraints.video.mediaSource !== 'window') { throw new Error('Only "screen" and "window" option is available as mediaSource'); } var updatedConstraints = clone(constraints); //constraints.video.mediaSource = constraints.video.mediaSource; updatedConstraints.video.mozMediaSource = updatedConstraints.video.mediaSource; // so generally, it requires for document.readyState to be completed before the getUserMedia could be invoked. // strange but this works anyway var checkIfReady = setInterval(function () { if (document.readyState === 'complete') { clearInterval(checkIfReady); baseGetUserMedia(updatedConstraints, successCb, function (error) { if (error.name === 'PermissionDeniedError' && window.parent.location.protocol === 'https:') { AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_FF, AdapterJS.TEXT.EXTENSION.BUTTON_FF, 'http://skylink.io/screensharing/ff_addon.php?domain=' + window.location.hostname, false, true); //window.location.href = 'http://skylink.io/screensharing/ff_addon.php?domain=' + window.location.hostname; } else { failureCb(error); } }); } }, 1); } else { // regular GetUserMediaRequest baseGetUserMedia(constraints, successCb, failureCb); } }; getUserMedia = navigator.getUserMedia; } else if (window.navigator.webkitGetUserMedia) { baseGetUserMedia = window.navigator.getUserMedia; navigator.getUserMedia = function (constraints, successCb, failureCb) { if (constraints && constraints.video && !!constraints.video.mediaSource) { if (window.webrtcDetectedBrowser !== 'chrome') { throw new Error('Current browser does not support screensharing'); } // would be fine since no methods var updatedConstraints = clone(constraints); var chromeCallback = function(error, sourceId) { if(!error) { updatedConstraints.video.mandatory = updatedConstraints.video.mandatory || {}; updatedConstraints.video.mandatory.chromeMediaSource = 'desktop'; updatedConstraints.video.mandatory.maxWidth = window.screen.width > 1920 ? window.screen.width : 1920; updatedConstraints.video.mandatory.maxHeight = window.screen.height > 1080 ? window.screen.height : 1080; if (sourceId) { updatedConstraints.video.mandatory.chromeMediaSourceId = sourceId; } delete updatedConstraints.video.mediaSource; baseGetUserMedia(updatedConstraints, successCb, failureCb); } else { if (error === 'permission-denied') { throw new Error('Permission denied for screen retrieval'); } else { throw new Error('Failed retrieving selected screen'); } } }; var onIFrameCallback = function (event) { if (!event.data) { return; } if (event.data.chromeMediaSourceId) { if (event.data.chromeMediaSourceId === 'PermissionDeniedError') { chromeCallback('permission-denied'); } else { chromeCallback(null, event.data.chromeMediaSourceId); } } if (event.data.chromeExtensionStatus) { if (event.data.chromeExtensionStatus === 'not-installed') { AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_CHROME, AdapterJS.TEXT.EXTENSION.BUTTON_CHROME, event.data.data, true, true); } else { chromeCallback(event.data.chromeExtensionStatus, null); } } // this event listener is no more needed window.removeEventListener('message', onIFrameCallback); }; window.addEventListener('message', onIFrameCallback); postFrameMessage({ captureSourceId: true }); } else { baseGetUserMedia(constraints, successCb, failureCb); } }; getUserMedia = navigator.getUserMedia; } else { baseGetUserMedia = window.navigator.getUserMedia; navigator.getUserMedia = function (constraints, successCb, failureCb) { if (constraints && constraints.video && !!constraints.video.mediaSource) { // would be fine since no methods var updatedConstraints = clone(constraints); // wait for plugin to be ready AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { // check if screensharing feature is available if (!!AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature && !!AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable) { // set the constraints updatedConstraints.video.optional = updatedConstraints.video.optional || []; updatedConstraints.video.optional.push({ sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey || 'Screensharing' }); delete updatedConstraints.video.mediaSource; } else { throw new Error('Your WebRTC plugin does not support screensharing'); } baseGetUserMedia(updatedConstraints, successCb, failureCb); }); } else { baseGetUserMedia(constraints, successCb, failureCb); } }; getUserMedia = window.navigator.getUserMedia; } if (window.webrtcDetectedBrowser === 'chrome') { var iframe = document.createElement('iframe'); iframe.onload = function() { iframe.isLoaded = true; }; iframe.src = 'https://cdn.temasys.com.sg/skylink/extensions/detectRTC.html'; //'https://temasys-cdn.s3.amazonaws.com/skylink/extensions/detection-script-dev/detectRTC.html'; iframe.style.display = 'none'; (document.body || document.documentElement).appendChild(iframe); var postFrameMessage = function (object) { object = object || {}; if (!iframe.isLoaded) { setTimeout(function () { iframe.contentWindow.postMessage(object, '*'); }, 100); return; } iframe.contentWindow.postMessage(object, '*'); }; } })(); },{}],13:[function(require,module,exports){ var UI = {}; var VideoLayout = require("./videolayout/VideoLayout.js"); var AudioLevels = require("./audio_levels/AudioLevels.js"); var Prezi = require("./prezi/Prezi.js"); var Etherpad = require("./etherpad/Etherpad.js"); var Chat = require("./side_pannels/chat/Chat.js"); var Toolbar = require("./toolbars/Toolbar"); var ToolbarToggler = require("./toolbars/ToolbarToggler"); var BottomToolbar = require("./toolbars/BottomToolbar"); var ContactList = require("./side_pannels/contactlist/ContactList"); var Avatar = require("./avatar/Avatar"); var EventEmitter = require("events"); var SettingsMenu = require("./side_pannels/settings/SettingsMenu"); var Settings = require("./../settings/Settings"); var PanelToggler = require("./side_pannels/SidePanelToggler"); var RoomNameGenerator = require("./welcome_page/RoomnameGenerator"); UI.messageHandler = require("./util/MessageHandler"); var messageHandler = UI.messageHandler; var Authentication = require("./authentication/Authentication"); var UIUtil = require("./util/UIUtil"); var NicknameHandler = require("./util/NicknameHandler"); var CQEvents = require("../../service/connectionquality/CQEvents"); var DesktopSharingEventTypes = require("../../service/desktopsharing/DesktopSharingEventTypes"); var RTCEvents = require("../../service/RTC/RTCEvents"); var RTCBrowserType = require("../RTC/RTCBrowserType"); var StreamEventTypes = require("../../service/RTC/StreamEventTypes"); var XMPPEvents = require("../../service/xmpp/XMPPEvents"); var UIEvents = require("../../service/UI/UIEvents"); var MemberEvents = require("../../service/members/Events"); var eventEmitter = new EventEmitter(); var roomName = null; function promptDisplayName() { var message = '

'; message += APP.translation.translateString( "dialog.displayNameRequired"); message += '

' + ''; var buttonTxt = APP.translation.generateTranslationHTML("dialog.Ok"); var buttons = []; buttons.push({title: buttonTxt, value: "ok"}); messageHandler.openDialog(null, message, true, buttons, function (e, v, m, f) { if (v == "ok") { var displayName = f.displayName; if (displayName) { VideoLayout.inputDisplayNameHandler(displayName); return true; } } e.preventDefault(); }, function () { var form = $.prompt.getPrompt(); var input = form.find("input[name='displayName']"); input.focus(); var button = form.find("button"); button.attr("disabled", "disabled"); input.keyup(function () { if(!input.val()) button.attr("disabled", "disabled"); else button.removeAttr("disabled"); }); } ); } function notifyForInitialMute() { messageHandler.notify(null, "notify.mutedTitle", "connected", "notify.muted", null, {timeOut: 120000}); } function setupPrezi() { $("#reloadPresentationLink").click(function() { Prezi.reloadPresentation(); }); } function setupChat() { Chat.init(); $("#toggle_smileys").click(function() { Chat.toggleSmileys(); }); } function setupToolbars() { Toolbar.init(UI); Toolbar.setupButtonsFromConfig(); BottomToolbar.init(); } function streamHandler(stream, isMuted) { switch (stream.type) { case "audio": VideoLayout.changeLocalAudio(stream, isMuted); break; case "video": VideoLayout.changeLocalVideo(stream, isMuted); break; case "stream": VideoLayout.changeLocalStream(stream, isMuted); break; } } function onXmppConnectionFailed(stropheErrorMsg) { var title = APP.translation.generateTranslationHTML( "dialog.error"); var message; if (stropheErrorMsg) { message = APP.translation.generateTranslationHTML( "dialog.connectErrorWithMsg", {msg: stropheErrorMsg}); } else { message = APP.translation.generateTranslationHTML( "dialog.connectError"); } messageHandler.openDialog( title, message, true, {}, function (e, v, m, f) { return false; }); } function onDisposeConference(unload) { Toolbar.showAuthenticateButton(false); } function onDisplayNameChanged(jid, displayName) { ContactList.onDisplayNameChange(jid, displayName); SettingsMenu.onDisplayNameChange(jid, displayName); VideoLayout.onDisplayNameChanged(jid, displayName); } function registerListeners() { APP.RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED); APP.RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED); APP.RTC.addStreamListener(function (stream) { VideoLayout.onRemoteStreamAdded(stream); }, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED); APP.RTC.addStreamListener(function (jid) { VideoLayout.onVideoTypeChanged(jid); }, StreamEventTypes.EVENT_TYPE_REMOTE_CHANGED); APP.RTC.addListener(RTCEvents.LASTN_CHANGED, onLastNChanged); APP.RTC.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED, function (resourceJid) { VideoLayout.onDominantSpeakerChanged(resourceJid); }); APP.RTC.addListener(RTCEvents.LASTN_ENDPOINT_CHANGED, function (lastNEndpoints, endpointsEnteringLastN, stream) { VideoLayout.onLastNEndpointsChanged(lastNEndpoints, endpointsEnteringLastN, stream); }); APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) { VideoLayout.setDeviceAvailabilityIcons(null, devices); }); APP.RTC.addListener(RTCEvents.VIDEO_MUTE, UI.setVideoMuteButtonsState); APP.RTC.addListener(RTCEvents.DATA_CHANNEL_OPEN, function() { // when the data channel becomes available, tell the bridge about video // selections so that it can do adaptive simulcast, // we want the notification to trigger even if userJid is undefined, // or null. var userJid = APP.UI.getLargeVideoJid(); eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, userJid); }); APP.statistics.addAudioLevelListener(function(jid, audioLevel) { var resourceJid; if(jid === APP.statistics.LOCAL_JID) { resourceJid = AudioLevels.LOCAL_LEVEL; if(APP.RTC.localAudio.isMuted()) { audioLevel = 0; } } else { resourceJid = Strophe.getResourceFromJid(jid); } AudioLevels.updateAudioLevel(resourceJid, audioLevel, UI.getLargeVideoJid()); }); APP.desktopsharing.addListener(function () { ToolbarToggler.showDesktopSharingButton(); }, DesktopSharingEventTypes.INIT); APP.desktopsharing.addListener( Toolbar.changeDesktopSharingButtonState, DesktopSharingEventTypes.SWITCHING_DONE); APP.connectionquality.addListener(CQEvents.LOCALSTATS_UPDATED, VideoLayout.updateLocalConnectionStats); APP.connectionquality.addListener(CQEvents.REMOTESTATS_UPDATED, VideoLayout.updateConnectionStats); APP.connectionquality.addListener(CQEvents.STOP, VideoLayout.onStatsStop); APP.xmpp.addListener(XMPPEvents.CONNECTION_FAILED, onXmppConnectionFailed); APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference); APP.xmpp.addListener(XMPPEvents.GRACEFUL_SHUTDOWN, function () { messageHandler.openMessageDialog( 'dialog.serviceUnavailable', 'dialog.gracefulShutdown' ); }); APP.xmpp.addListener(XMPPEvents.RESERVATION_ERROR, function (code, msg) { var title = APP.translation.generateTranslationHTML( "dialog.reservationError"); var message = APP.translation.generateTranslationHTML( "dialog.reservationErrorMsg", {code: code, msg: msg}); messageHandler.openDialog( title, message, true, {}, function (event, value, message, formVals) { return false; } ); }); APP.xmpp.addListener(XMPPEvents.KICKED, function () { messageHandler.openMessageDialog("dialog.sessTerminated", "dialog.kickMessage"); }); APP.xmpp.addListener(XMPPEvents.MUC_DESTROYED, function (reason) { //FIXME: use Session Terminated from translation, but // 'reason' text comes from XMPP packet and is not translated var title = APP.translation.generateTranslationHTML("dialog.sessTerminated"); messageHandler.openDialog( title, reason, true, {}, function (event, value, message, formVals) { return false; } ); }); APP.xmpp.addListener(XMPPEvents.BRIDGE_DOWN, function () { messageHandler.showError("dialog.error", "dialog.bridgeUnavailable"); }); APP.xmpp.addListener(XMPPEvents.USER_ID_CHANGED, function (from, id) { Avatar.setUserAvatar(from, id); }); APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, onDisplayNameChanged); APP.xmpp.addListener(XMPPEvents.MUC_JOINED, onMucJoined); APP.xmpp.addListener(XMPPEvents.LOCAL_ROLE_CHANGED, onLocalRoleChanged); APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, onMucMemberJoined); APP.xmpp.addListener(XMPPEvents.MUC_ROLE_CHANGED, onMucRoleChanged); APP.xmpp.addListener(XMPPEvents.PRESENCE_STATUS, onMucPresenceStatus); APP.xmpp.addListener(XMPPEvents.SUBJECT_CHANGED, chatSetSubject); APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation); APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, onMucMemberLeft); APP.xmpp.addListener(XMPPEvents.PASSWORD_REQUIRED, onPasswordRequired); APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError); APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad); APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED, onAuthenticationRequired); APP.xmpp.addListener(XMPPEvents.DEVICE_AVAILABLE, function (resource, devices) { VideoLayout.setDeviceAvailabilityIcons(resource, devices); }); APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED, VideoLayout.onAudioMute); APP.xmpp.addListener(XMPPEvents.VIDEO_MUTED, VideoLayout.onVideoMute); APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS, function(doMuteAudio) { UI.setAudioMuted(doMuteAudio); }); APP.members.addListener(MemberEvents.DTMF_SUPPORT_CHANGED, onDtmfSupportChanged); APP.xmpp.addListener(XMPPEvents.START_MUTED_SETTING_CHANGED, function (audio, video) { SettingsMenu.setStartMuted(audio, video); }); APP.xmpp.addListener(XMPPEvents.START_MUTED_FROM_FOCUS, function (audio, video) { UI.setInitialMuteFromFocus(audio, video); }); APP.xmpp.addListener(XMPPEvents.JINGLE_FATAL_ERROR, function (session, error) { UI.messageHandler.showError("dialog.sorry", "dialog.internalError"); }); APP.xmpp.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR, function() { messageHandler.showError("dialog.error", "dialog.SLDFailure"); }); APP.xmpp.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR, function() { messageHandler.showError("dialog.error", "dialog.SRDFailure"); }); APP.xmpp.addListener(XMPPEvents.CREATE_ANSWER_ERROR, function() { messageHandler.showError(); }); APP.xmpp.addListener(XMPPEvents.PROMPT_FOR_LOGIN, function() { // FIXME: re-use LoginDialog which supports retries UI.showLoginPopup(connect); }); APP.xmpp.addListener(XMPPEvents.FOCUS_DISCONNECTED, function(focusComponent, retrySec) { UI.messageHandler.notify( null, "notify.focus", 'disconnected', "notify.focusFail", {component: focusComponent, ms: retrySec}); }); APP.xmpp.addListener(XMPPEvents.ROOM_JOIN_ERROR, function(pres) { UI.messageHandler.openReportDialog(null, "dialog.joinError", pres); }); APP.xmpp.addListener(XMPPEvents.ROOM_CONNECT_ERROR, function(pres) { UI.messageHandler.openReportDialog(null, "dialog.connectError", pres); }); APP.xmpp.addListener(XMPPEvents.READY_TO_JOIN, function() { var roomName = UI.generateRoomName(); APP.xmpp.allocateConferenceFocus(roomName, UI.checkForNicknameAndJoin); }); //NicknameHandler emits this event UI.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) { APP.xmpp.addToPresence("displayName", nickname); }); UI.addListener(UIEvents.LARGEVIDEO_INIT, function () { AudioLevels.init(); }); } /** * Mutes/unmutes the local video. * * @param mute true to mute the local video; otherwise, false * @param options an object which specifies optional arguments such as the * boolean key byUser with default value true which * specifies whether the method was initiated in response to a user command (in * contrast to an automatic decision taken by the application logic) */ function setVideoMute(mute, options) { APP.RTC.setVideoMute(mute, UI.setVideoMuteButtonsState, options); } function onResize() { Chat.resizeChat(); VideoLayout.resizeLargeVideoContainer(); } function bindEvents() { /** * Resizes and repositions videos in full screen mode. */ $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange', onResize); $(window).resize(onResize); } UI.start = function (init) { document.title = interfaceConfig.APP_NAME; if(config.enableWelcomePage && window.location.pathname == "/" && (!window.localStorage.welcomePageDisabled || window.localStorage.welcomePageDisabled == "false")) { $("#videoconference_page").hide(); var setupWelcomePage = require("./welcome_page/WelcomePage"); setupWelcomePage(); return; } $("#welcome_page").hide(); $("#videospace").mousemove(function () { return ToolbarToggler.showToolbar(); }); // Set the defaults for prompt dialogs. jQuery.prompt.setDefaults({persistent: false}); registerListeners(); VideoLayout.init(eventEmitter); NicknameHandler.init(eventEmitter); bindEvents(); setupPrezi(); setupToolbars(); setupChat(); document.title = interfaceConfig.APP_NAME; $("#downloadlog").click(function (event) { dump(event.target); }); if(config.enableWelcomePage && window.location.pathname == "/" && (!window.localStorage.welcomePageDisabled || window.localStorage.welcomePageDisabled == "false")) { $("#videoconference_page").hide(); var setupWelcomePage = require("./welcome_page/WelcomePage"); setupWelcomePage(); return; } $("#welcome_page").hide(); // Display notice message at the top of the toolbar if (config.noticeMessage) { $('#noticeText').text(config.noticeMessage); $('#notice').css({display: 'block'}); } if(config.requireDisplayName) { var currentSettings = Settings.getSettings(); if (!currentSettings.displayName) { promptDisplayName(); } } init(); toastr.options = { "closeButton": true, "debug": false, "positionClass": "notification-bottom-right", "onclick": null, "showDuration": "300", "hideDuration": "1000", "timeOut": "2000", "extendedTimeOut": "1000", "showEasing": "swing", "hideEasing": "linear", "showMethod": "fadeIn", "hideMethod": "fadeOut", "reposition": function() { if(PanelToggler.isVisible()) { $("#toast-container").addClass("notification-bottom-right-center"); } else { $("#toast-container").removeClass("notification-bottom-right-center"); } }, "newestOnTop": false }; SettingsMenu.init(); }; function chatAddError(errorMessage, originalText) { return Chat.chatAddError(errorMessage, originalText); }; function chatSetSubject(text) { return Chat.chatSetSubject(text); }; function updateChatConversation(from, displayName, message, myjid, stamp) { return Chat.updateChatConversation(from, displayName, message, myjid, stamp); } function onMucJoined(jid, info) { Toolbar.updateRoomUrl(window.location.href); var meHTML = APP.translation.generateTranslationHTML("me"); $("#localNick").html(Strophe.getResourceFromJid(jid) + " (" + meHTML + ")"); var settings = Settings.getSettings(); // Make sure we configure our avatar id, before creating avatar for us Avatar.setUserAvatar(jid, settings.email || settings.uid); // Add myself to the contact list. ContactList.addContact(jid); // Once we've joined the muc show the toolbar ToolbarToggler.showToolbar(); var displayName = !config.displayJids ? info.displayName : Strophe.getResourceFromJid(jid); if (displayName) onDisplayNameChanged('localVideoContainer', displayName); VideoLayout.mucJoined(); } function initEtherpad(name) { Etherpad.init(name); } function onMucMemberLeft(jid) { console.log('left.muc', jid); var displayName = $('#participant_' + Strophe.getResourceFromJid(jid) + '>.displayname').html(); messageHandler.notify(displayName,'notify.somebody', 'disconnected', 'notify.disconnected'); if(!config.startAudioMuted || config.startAudioMuted > APP.members.size()) UIUtil.playSoundNotification('userLeft'); ContactList.removeContact(jid); VideoLayout.participantLeft(jid); }; function onLocalRoleChanged(jid, info, pres, isModerator) { console.info("My role changed, new role: " + info.role); onModeratorStatusChanged(isModerator); VideoLayout.showModeratorIndicator(); SettingsMenu.onRoleChanged(); if (isModerator) { Authentication.closeAuthenticationWindow(); messageHandler.notify(null, "notify.me", 'connected', "notify.moderator"); } } function onModeratorStatusChanged(isModerator) { Toolbar.showSipCallButton(isModerator); Toolbar.showRecordingButton( isModerator); //&& // FIXME: // Recording visible if // there are at least 2(+ 1 focus) participants //Object.keys(connection.emuc.members).length >= 3); } function onPasswordRequired(callback) { // password is required Toolbar.lockLockButton(); var message = '

'; message += APP.translation.translateString( "dialog.passwordRequired"); message += '

' + ''; messageHandler.openTwoButtonDialog(null, null, null, message, true, "dialog.Ok", function (e, v, m, f) {}, null, function (e, v, m, f) { if (v) { var lockKey = f.lockKey; if (lockKey) { Toolbar.setSharedKey(lockKey); callback(lockKey); } } }, ':input:first' ); } /** * The dialpad button is shown iff there is at least one member that supports * DTMF (e.g. jigasi). */ function onDtmfSupportChanged(dtmfSupport) { //TODO: enable when the UI is ready //Toolbar.showDialPadButton(dtmfSupport); } function onMucMemberJoined(jid, id, displayName) { messageHandler.notify(displayName,'notify.somebody', 'connected', 'notify.connected'); if(!config.startAudioMuted || config.startAudioMuted > APP.members.size()) UIUtil.playSoundNotification('userJoined'); // Configure avatar Avatar.setUserAvatar(jid, id); // Add Peer's container VideoLayout.ensurePeerContainerExists(jid); } function onMucPresenceStatus(jid, info) { VideoLayout.setPresenceStatus(Strophe.getResourceFromJid(jid), info.status); } function onMucRoleChanged(role, displayName) { VideoLayout.showModeratorIndicator(); if (role === 'moderator') { var messageKey, messageOptions = {}; if (!displayName) { messageKey = "notify.grantedToUnknown"; } else { messageKey = "notify.grantedTo"; messageOptions = {to: displayName}; } messageHandler.notify( displayName,'notify.somebody', 'connected', messageKey, messageOptions); } } function onAuthenticationRequired(intervalCallback) { Authentication.openAuthenticationDialog( roomName, intervalCallback, function () { Toolbar.authenticateClicked(); }); }; function onLastNChanged(oldValue, newValue) { if (config.muteLocalVideoIfNotInLastN) { setVideoMute(!newValue, { 'byUser': false }); } } UI.toggleSmileys = function () { Chat.toggleSmileys(); }; UI.getSettings = function () { return Settings.getSettings(); }; UI.toggleFilmStrip = function () { return BottomToolbar.toggleFilmStrip(); }; UI.toggleChat = function () { return BottomToolbar.toggleChat(); }; UI.toggleContactList = function () { return BottomToolbar.toggleContactList(); }; UI.inputDisplayNameHandler = function (value) { VideoLayout.inputDisplayNameHandler(value); }; UI.getLargeVideoJid = function() { return VideoLayout.getLargeVideoJid(); }; UI.generateRoomName = function() { if(roomName) return roomName; var roomnode = null; var path = window.location.pathname; // determinde the room node from the url // TODO: just the roomnode or the whole bare jid? if (config.getroomnode && typeof config.getroomnode === 'function') { // custom function might be responsible for doing the pushstate roomnode = config.getroomnode(path); } else { /* fall back to default strategy * this is making assumptions about how the URL->room mapping happens. * It currently assumes deployment at root, with a rewrite like the * following one (for nginx): location ~ ^/([a-zA-Z0-9]+)$ { rewrite ^/(.*)$ / break; } */ if (path.length > 1) { roomnode = path.substr(1).toLowerCase(); } else { var word = RoomNameGenerator.generateRoomWithoutSeparator(); roomnode = word.toLowerCase(); window.history.pushState('VideoChat', 'Room: ' + word, window.location.pathname + word); } } roomName = roomnode + '@' + config.hosts.muc; return roomName; }; UI.connectionIndicatorShowMore = function(jid) { return VideoLayout.showMore(jid); }; UI.showLoginPopup = function(callback) { console.log('password is required'); var message = '

'; message += APP.translation.translateString( "dialog.passwordRequired"); message += '

' + '' + ''; UI.messageHandler.openTwoButtonDialog(null, null, null, message, true, "dialog.Ok", function (e, v, m, f) { if (v) { if (f.username !== null && f.password != null) { callback(f.username, f.password); } } }, null, null, ':input:first' ); } UI.checkForNicknameAndJoin = function () { Authentication.closeAuthenticationDialog(); Authentication.stopInterval(); var nick = null; if (config.useNicks) { nick = window.prompt('Your nickname (optional)'); } APP.xmpp.joinRoom(roomName, config.useNicks, nick); }; function dump(elem, filename) { elem = elem.parentNode; elem.download = filename || 'meetlog.json'; elem.href = 'data:application/json;charset=utf-8,\n'; var data = APP.xmpp.populateData(); var metadata = {}; metadata.time = new Date(); metadata.url = window.location.href; metadata.ua = navigator.userAgent; var log = APP.xmpp.getLogger(); if (log) { metadata.xmpp = log; } data.metadata = metadata; elem.href += encodeURIComponent(JSON.stringify(data, null, ' ')); return false; } UI.getRoomName = function () { return roomName; }; UI.setInitialMuteFromFocus = function (muteAudio, muteVideo) { if(muteAudio || muteVideo) notifyForInitialMute(); if(muteAudio) UI.setAudioMuted(true); if(muteVideo) UI.setVideoMute(true); } /** * Mutes/unmutes the local video. */ UI.toggleVideo = function () { setVideoMute(!APP.RTC.localVideo.isMuted()); }; /** * Mutes / unmutes audio for the local participant. */ UI.toggleAudio = function() { UI.setAudioMuted(!APP.RTC.localAudio.isMuted()); }; /** * Sets muted audio state for the local participant. */ UI.setAudioMuted = function (mute, earlyMute) { var audioMute = null; if(earlyMute) audioMute = function (mute, cb) { return APP.xmpp.sendAudioInfoPresence(mute, cb); }; else audioMute = function (mute, cb) { return APP.xmpp.setAudioMute(mute, cb); } if(!audioMute(mute, function () { VideoLayout.showLocalAudioIndicator(mute); UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled"); })) { // We still click the button. UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled"); return; } } UI.addListener = function (type, listener) { eventEmitter.on(type, listener); } UI.clickOnVideo = function (videoNumber) { var remoteVideos = $(".videocontainer:not(#mixedstream)"); if (remoteVideos.length > videoNumber) { remoteVideos[videoNumber].click(); } } //Used by torture UI.showToolbar = function () { return ToolbarToggler.showToolbar(); } //Used by torture UI.dockToolbar = function (isDock) { return ToolbarToggler.dockToolbar(isDock); } UI.setVideoMuteButtonsState = function (mute) { var video = $('#video'); var communicativeClass = "icon-camera"; var muteClass = "icon-camera icon-camera-disabled"; if (mute) { video.removeClass(communicativeClass); video.addClass(muteClass); } else { video.removeClass(muteClass); video.addClass(communicativeClass); } } UI.userAvatarChanged = function (resourceJid, thumbUrl, contactListUrl) { VideoLayout.userAvatarChanged(resourceJid, thumbUrl); ContactList.userAvatarChanged(resourceJid, contactListUrl); if(resourceJid === APP.xmpp.myResource()) SettingsMenu.changeAvatar(thumbUrl); } UI.setVideoMute = setVideoMute; module.exports = UI; },{"../../service/RTC/RTCEvents":101,"../../service/RTC/StreamEventTypes":103,"../../service/UI/UIEvents":104,"../../service/connectionquality/CQEvents":106,"../../service/desktopsharing/DesktopSharingEventTypes":107,"../../service/members/Events":108,"../../service/xmpp/XMPPEvents":110,"../RTC/RTCBrowserType":10,"./../settings/Settings":49,"./audio_levels/AudioLevels.js":14,"./authentication/Authentication":16,"./avatar/Avatar":18,"./etherpad/Etherpad.js":19,"./prezi/Prezi.js":20,"./side_pannels/SidePanelToggler":22,"./side_pannels/chat/Chat.js":23,"./side_pannels/contactlist/ContactList":27,"./side_pannels/settings/SettingsMenu":28,"./toolbars/BottomToolbar":29,"./toolbars/Toolbar":30,"./toolbars/ToolbarToggler":31,"./util/MessageHandler":33,"./util/NicknameHandler":34,"./util/UIUtil":35,"./videolayout/VideoLayout.js":41,"./welcome_page/RoomnameGenerator":42,"./welcome_page/WelcomePage":43,"events":1}],14:[function(require,module,exports){ var CanvasUtil = require("./CanvasUtils"); var ASDrawContext = 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.getMUCJoined()) { APP.UI.checkForNicknameAndJoin(); } }); } }, true); }, focusAuthenticationWindow: function () { // If auth window exists just bring it to the front if (authenticationWindow) { authenticationWindow.focus(); return; } }, closeAuthenticationDialog: function () { // Close authentication dialog if opened if (authDialog) { authDialog.close(); authDialog = null; } }, createAuthenticationWindow: function (callback, url) { authenticationWindow = APP.UI.messageHandler.openCenteredPopup( url, 910, 660, // On closed function () { // Close authentication dialog if opened Authentication.closeAuthenticationDialog(); callback(); authenticationWindow = null; }); return authenticationWindow; }, stopInterval: function () { // Clear retry interval, so that we don't call 'doJoinAfterFocus' twice if (authRetryId) { window.clearTimeout(authRetryId); authRetryId = null; } } }; module.exports = Authentication; },{"../../xmpp/moderator":60,"./LoginDialog":17}],17:[function(require,module,exports){ /* global $, APP, config*/ var XMPP = require('../../xmpp/xmpp'); var Moderator = require('../../xmpp/moderator'); //FIXME: use LoginDialog to add retries to XMPP.connect method used when // anonymous domain is not enabled /** * Creates new Dialog instance. * @param callback function(Strophe.Connection, Strophe.Status) called * when we either fail to connect or succeed(check Strophe.Status). * @param obtainSession true if we want to send ConferenceIQ to Jicofo * in order to create session-id after the connection is established. * @constructor */ function Dialog(callback, obtainSession) { var self = this; var stop = false; var connection = APP.xmpp.createConnection(); var message = '

'; message += APP.translation.translateString("dialog.passwordRequired"); message += '

' + '' + ''; var okButton = APP.translation.generateTranslationHTML("dialog.Ok"); var cancelButton = APP.translation.generateTranslationHTML("dialog.Cancel"); var states = { login: { html: message, buttons: [ { title: okButton, value: true}, { title: cancelButton, value: false} ], focus: ':input:first', submit: function (e, v, m, f) { e.preventDefault(); if (v) { var jid = f.username; var password = f.password; if (jid && password) { stop = false; connection.reset(); connDialog.goToState('connecting'); connection.connect(jid, password, stateHandler); } } else { // User cancelled stop = true; callback(); } } }, connecting: { title: APP.translation.translateString('dialog.connecting'), html: '
', buttons: [], defaultButton: 0 }, finished: { title: APP.translation.translateString('dialog.error'), html: '
', buttons: [ { title: APP.translation.translateString('dialog.retry'), value: 'retry' }, { title: APP.translation.translateString('dialog.Cancel'), value: 'cancel' }, ], defaultButton: 0, submit: function (e, v, m, f) { e.preventDefault(); if (v === 'retry') connDialog.goToState('login'); else callback(); } } }; var connDialog = APP.UI.messageHandler.openDialogWithStates(states, { persistent: true, closeText: '' }, null); var stateHandler = function (status, message) { if (stop) { return; } var translateKey = "connection." + XMPP.getStatusString(status); var statusStr = APP.translation.translateString(translateKey); // Display current state var connectionStatus = connDialog.getState('connecting').find('#connectionStatus'); connectionStatus.text(statusStr); switch (status) { case XMPP.Status.CONNECTED: stop = true; if (!obtainSession) { callback(connection, status); return; } // Obtaining session-id status connectionStatus.text( APP.translation.translateString( 'connection.FETCH_SESSION_ID')); // Authenticate with Jicofo and obtain session-id var roomName = APP.UI.generateRoomName(); // Jicofo will return new session-id when connected // from authenticated domain connection.sendIQ( Moderator.createConferenceIq(roomName), function (result) { connectionStatus.text( APP.translation.translateString( 'connection.GOT_SESSION_ID')); stop = true; // Parse session-id Moderator.parseSessionId(result); callback(connection, status); }, function (error) { console.error("Auth on the fly failed", error); stop = true; var errorMsg = APP.translation.translateString( 'connection.GET_SESSION_ID_ERROR') + $(error).find('>error').attr('code'); self.displayError(errorMsg); connection.disconnect(); }); break; case XMPP.Status.AUTHFAIL: case XMPP.Status.CONNFAIL: case XMPP.Status.DISCONNECTED: stop = true; callback(connection, status); var errorMessage = statusStr; if (message) { errorMessage += ': ' + message; } self.displayError(errorMessage); break; default: break; } }; /** * Displays error message in 'finished' state which allows either to cancel * or retry. * @param message the final message to be displayed. */ this.displayError = function (message) { var finishedState = connDialog.getState('finished'); var errorMessageElem = finishedState.find('#errorMessage'); errorMessageElem.text(message); connDialog.goToState('finished'); }; /** * Closes LoginDialog. */ this.close = function () { stop = true; connDialog.close(); }; } var LoginDialog = { /** * Displays login prompt used to establish new XMPP connection. Given * callback(Strophe.Connection, Strophe.Status) function will be * called when we connect successfully(status === CONNECTED) or when we fail * to do so. On connection failure program can call Dialog.close() method in * order to cancel or do nothing to let the user retry. * @param callback function(Strophe.Connection, Strophe.Status) * called when we either fail to connect or succeed(check * Strophe.Status). * @param obtainSession true if we want to send ConferenceIQ to * Jicofo in order to create session-id after the connection is * established. * @returns {Dialog} */ show: function (callback, obtainSession) { return new Dialog(callback, obtainSession); } }; module.exports = LoginDialog; },{"../../xmpp/moderator":60,"../../xmpp/xmpp":68}],18:[function(require,module,exports){ var Settings = require("../../settings/Settings"); var users = {}; var Avatar = { /** * Sets the user's avatar in the settings menu(if local user), contact list * and thumbnail * @param jid jid of the user * @param id email or userID to be used as a hash */ setUserAvatar: function (jid, id) { if (id) { if (users[jid] === id) { return; } users[jid] = id; } var thumbUrl = this.getThumbUrl(jid); var contactListUrl = this.getContactListUrl(jid); var resourceJid = Strophe.getResourceFromJid(jid); APP.UI.userAvatarChanged(resourceJid, thumbUrl, contactListUrl); }, /** * Returns image URL for the avatar to be displayed on large video area * where current active speaker is presented. * @param jid full MUC jid of the user for whom we want to obtain avatar URL */ getActiveSpeakerUrl: function (jid) { return this.getGravatarUrl(jid, 100); }, /** * Returns image URL for the avatar to be displayed on small video thumbnail * @param jid full MUC jid of the user for whom we want to obtain avatar URL */ getThumbUrl: function (jid) { return this.getGravatarUrl(jid, 100); }, /** * Returns the URL for the avatar to be displayed as contactlist item * @param jid full MUC jid of the user for whom we want to obtain avatar URL */ getContactListUrl: function (jid) { return this.getGravatarUrl(jid, 30); }, getGravatarUrl: function (jid, size) { if (!jid) { console.error("Get gravatar - jid is undefined"); return null; } var id = users[jid]; if (!id) { console.warn( "No avatar stored yet for " + jid + " - 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}],19:[function(require,module,exports){ /* global $, config, setLargeVideoVisible, Util */ var VideoLayout = require("../videolayout/VideoLayout"); var Prezi = require("../prezi/Prezi"); var UIUtil = require("../util/UIUtil"); var etherpadName = null; var etherpadIFrame = null; var domain = null; var options = "?showControls=true&showChat=false&showLineNumbers=true&useMonospaceFont=false"; /** * Resizes the etherpad. */ function resize() { if ($('#etherpad>iframe').length) { var remoteVideos = $('#remoteVideos'); var availableHeight = window.innerHeight - remoteVideos.outerHeight(); var availableWidth = UIUtil.getAvailableVideoWidth(); $('#etherpad>iframe').width(availableWidth); $('#etherpad>iframe').height(availableHeight); } } /** * Creates the Etherpad button and adds it to the toolbar. */ function enableEtherpadButton() { if (!$('#etherpadButton').is(":visible")) $('#etherpadButton').css({display: 'inline-block'}); } /** * Creates the IFrame for the etherpad. */ function createIFrame() { etherpadIFrame = 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){ var Chat = require("./chat/Chat"); var ContactList = require("./contactlist/ContactList"); var Settings = require("./../../settings/Settings"); var SettingsMenu = require("./settings/SettingsMenu"); var VideoLayout = require("../videolayout/VideoLayout"); var ToolbarToggler = require("../toolbars/ToolbarToggler"); var UIUtil = require("../util/UIUtil"); var LargeVideo = require("../videolayout/LargeVideo"); /** * Toggler for the chat, contact list, settings menu, etc.. */ var PanelToggler = (function(my) { var currentlyOpen = null; var buttons = { '#chatspace': '#chatBottomButton', '#contactlist': '#contactListButton', '#settingsmenu': '#settingsButton' }; /** * Toggles the windows in the side panel * @param object the window that should be shown * @param selector the selector for the element containing the panel * @param onOpenComplete function to be called when the panel is opened * @param onOpen function to be called if the window is going to be opened * @param onClose function to be called if the window is going to be closed */ var toggle = function(object, selector, onOpenComplete, onOpen, onClose) { UIUtil.buttonClick(buttons[selector], "active"); if (object.isVisible()) { $("#toast-container").animate({ right: '5px' }, { queue: false, duration: 500 }); $(selector).hide("slide", { direction: "right", queue: false, duration: 500 }); if(typeof onClose === "function") { onClose(); } currentlyOpen = null; } else { // Undock the toolbar when the chat is shown and if we're in a // video mode. if (LargeVideo.isLargeVideoVisible()) { ToolbarToggler.dockToolbar(false); } if(currentlyOpen) { var current = $(currentlyOpen); UIUtil.buttonClick(buttons[currentlyOpen], "active"); current.css('z-index', 4); setTimeout(function () { current.css('display', 'none'); current.css('z-index', 5); }, 500); } $("#toast-container").animate({ right: (PanelToggler.getPanelSize()[0] + 5) + 'px' }, { queue: false, duration: 500 }); $(selector).show("slide", { direction: "right", queue: false, duration: 500, complete: onOpenComplete }); if(typeof onOpen === "function") { onOpen(); } currentlyOpen = selector; } }; /** * Opens / closes the chat area. */ my.toggleChat = function() { var chatCompleteFunction = Chat.isVisible() ? function() {} : function () { Chat.scrollChatToBottom(); $('#chatspace').trigger('shown'); }; VideoLayout.resizeVideoArea(!Chat.isVisible(), chatCompleteFunction); toggle(Chat, '#chatspace', function () { // Request the focus in the nickname field or the chat input field. if ($('#nickname').css('visibility') === 'visible') { $('#nickinput').focus(); } else { $('#usermsg').focus(); } }, null, Chat.resizeChat, null); }; /** * Opens / closes the contact list area. */ my.toggleContactList = function () { var completeFunction = ContactList.isVisible() ? function() {} : function () { $('#contactlist').trigger('shown');}; VideoLayout.resizeVideoArea(!ContactList.isVisible(), completeFunction); toggle(ContactList, '#contactlist', null, function() { ContactList.setVisualNotification(false); }, null); }; /** * Opens / closes the settings menu */ my.toggleSettingsMenu = function() { VideoLayout.resizeVideoArea(!SettingsMenu.isVisible(), function (){}); toggle(SettingsMenu, '#settingsmenu', null, function() { var settings = Settings.getSettings(); $('#setDisplayName').get(0).value = settings.displayName; $('#setEmail').get(0).value = settings.email; }, null); }; /** * Returns the size of the side panel. */ my.getPanelSize = function () { var availableHeight = window.innerHeight; var availableWidth = window.innerWidth; var panelWidth = 200; if (availableWidth * 0.2 < 200) { panelWidth = availableWidth * 0.2; } return [panelWidth, availableHeight]; }; my.isVisible = function() { return (Chat.isVisible() || ContactList.isVisible() || SettingsMenu.isVisible()); }; return my; }(PanelToggler || {})); module.exports = PanelToggler; },{"../toolbars/ToolbarToggler":31,"../util/UIUtil":35,"../videolayout/LargeVideo":37,"../videolayout/VideoLayout":41,"./../../settings/Settings":49,"./chat/Chat":23,"./contactlist/ContactList":27,"./settings/SettingsMenu":28}],23:[function(require,module,exports){ /* global $, Util, nickname:true */ var Replacement = require("./Replacement"); var CommandsProcessor = require("./Commands"); var ToolbarToggler = require("../../toolbars/ToolbarToggler"); var smileys = require("./smileys.json").smileys; var NicknameHandler = require("../../util/NicknameHandler"); var UIUtil = require("../../util/UIUtil"); var UIEvents = require("../../../../service/UI/UIEvents"); var notificationInterval = false; var unreadMessages = 0; /** * Shows/hides a visual notification, indicating that a message has arrived. */ function setVisualNotification(show) { var unreadMsgElement = document.getElementById('unreadMessages'); var unreadMsgBottomElement = document.getElementById('bottomUnreadMessages'); var glower = $('#chatButton'); var bottomGlower = $('#chatBottomButton'); if (unreadMessages) { unreadMsgElement.innerHTML = unreadMessages.toString(); unreadMsgBottomElement.innerHTML = unreadMessages.toString(); ToolbarToggler.dockToolbar(true); var chatButtonElement = document.getElementById('chatButton').parentNode; var leftIndent = (UIUtil.getTextWidth(chatButtonElement) - UIUtil.getTextWidth(unreadMsgElement)) / 2; var topIndent = (UIUtil.getTextHeight(chatButtonElement) - UIUtil.getTextHeight(unreadMsgElement)) / 2 - 3; unreadMsgElement.setAttribute( 'style', 'top:' + topIndent + '; left:' + leftIndent + ';'); var chatBottomButtonElement = document.getElementById('chatBottomButton').parentNode; var bottomLeftIndent = (UIUtil.getTextWidth(chatBottomButtonElement) - UIUtil.getTextWidth(unreadMsgBottomElement)) / 2; var bottomTopIndent = (UIUtil.getTextHeight(chatBottomButtonElement) - UIUtil.getTextHeight(unreadMsgBottomElement)) / 2 - 2; unreadMsgBottomElement.setAttribute( 'style', 'top:' + bottomTopIndent + '; left:' + bottomLeftIndent + ';'); if (!glower.hasClass('icon-chat-simple')) { glower.removeClass('icon-chat'); glower.addClass('icon-chat-simple'); } } else { unreadMsgElement.innerHTML = ''; unreadMsgBottomElement.innerHTML = ''; glower.removeClass('icon-chat-simple'); glower.addClass('icon-chat'); } if (show && !notificationInterval) { notificationInterval = window.setInterval(function () { glower.toggleClass('active'); bottomGlower.toggleClass('active glowing'); }, 800); } else if (!show && notificationInterval) { window.clearInterval(notificationInterval); notificationInterval = false; glower.removeClass('active'); bottomGlower.removeClass('glowing'); bottomGlower.addClass('active'); } } /** * Returns the current time in the format it is shown to the user * @returns {string} */ function getCurrentTime(stamp) { var now = (stamp? new Date(stamp): new Date()); var hour = now.getHours(); var minute = now.getMinutes(); var second = now.getSeconds(); if(hour.toString().length === 1) { hour = '0'+hour; } if(minute.toString().length === 1) { minute = '0'+minute; } if(second.toString().length === 1) { second = '0'+second; } return hour+':'+minute+':'+second; } function toggleSmileys() { var smileys = $('#smileysContainer'); if(!smileys.is(':visible')) { smileys.show("slide", { direction: "down", duration: 300}); } else { smileys.hide("slide", { direction: "down", duration: 300}); } $('#usermsg').focus(); } function addClickFunction(smiley, number) { smiley.onclick = function addSmileyToMessage() { var usermsg = $('#usermsg'); var message = usermsg.val(); message += smileys['smiley' + number]; usermsg.val(message); usermsg.get(0).setSelectionRange(message.length, message.length); toggleSmileys(); usermsg.focus(); }; } /** * Adds the smileys container to the chat */ function addSmileys() { var smileysContainer = document.createElement('div'); smileysContainer.id = 'smileysContainer'; for(var i = 1; i <= 21; i++) { var smileyContainer = document.createElement('div'); smileyContainer.id = 'smiley' + i; smileyContainer.className = 'smileyContainer'; var smiley = document.createElement('img'); smiley.src = 'images/smileys/smiley' + i + '.svg'; smiley.className = 'smiley'; addClickFunction(smiley, i); smileyContainer.appendChild(smiley); smileysContainer.appendChild(smileyContainer); } $("#chatspace").append(smileysContainer); } /** * Resizes the chat conversation. */ function resizeChatConversation() { var msgareaHeight = $('#usermsg').outerHeight(); var chatspace = $('#chatspace'); var width = chatspace.width(); var chat = $('#chatconversation'); var smileys = $('#smileysarea'); smileys.height(msgareaHeight); $("#smileys").css('bottom', (msgareaHeight - 26) / 2); $('#smileysContainer').css('bottom', msgareaHeight); chat.width(width - 10); chat.height(window.innerHeight - 15 - msgareaHeight); } /** * Chat related user interface. */ var Chat = (function (my) { /** * Initializes chat related interface. */ my.init = function () { if(NicknameHandler.getNickname()) Chat.setChatConversationMode(true); NicknameHandler.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) { Chat.setChatConversationMode(true); }); $('#nickinput').keydown(function (event) { if (event.keyCode === 13) { event.preventDefault(); var val = UIUtil.escapeHtml(this.value); this.value = ''; if (!NicknameHandler.getNickname()) { NicknameHandler.setNickname(val); return; } } }); $('#usermsg').keydown(function (event) { if (event.keyCode === 13) { event.preventDefault(); var value = this.value; $('#usermsg').val('').trigger('autosize.resize'); this.focus(); var command = new CommandsProcessor(value); if(command.isCommand()) { command.processCommand(); } else { var message = UIUtil.escapeHtml(value); APP.xmpp.sendChatMessage(message, NicknameHandler.getNickname()); } } }); var onTextAreaResize = function () { resizeChatConversation(); Chat.scrollChatToBottom(); }; $('#usermsg').autosize({callback: onTextAreaResize}); $("#chatspace").bind("shown", function () { unreadMessages = 0; setVisualNotification(false); }); addSmileys(); }; /** * Appends the given message to the chat conversation. */ my.updateChatConversation = function (from, displayName, message, myjid, stamp) { var divClassName = ''; if (APP.xmpp.myJid() === from) { divClassName = "localuser"; } else { divClassName = "remoteuser"; if (!Chat.isVisible()) { unreadMessages++; UIUtil.playSoundNotification('chatNotification'); setVisualNotification(true); } } // replace links and smileys // Strophe already escapes special symbols on sending, // so we escape here only tags to avoid double & var escMessage = message.replace(//g, '>').replace(/\n/g, '
'); var escDisplayName = UIUtil.escapeHtml(displayName); message = Replacement.processReplacements(escMessage); var messageContainer = '
'+ '' + '
' + escDisplayName + '
' + '
' + getCurrentTime(stamp) + '
' + '
' + message + '
' + '
'; $('#chatconversation').append(messageContainer); $('#chatconversation').animate( { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000); }; /** * Appends error message to the conversation * @param errorMessage the received error message. * @param originalText the original message. */ my.chatAddError = function(errorMessage, originalText) { errorMessage = UIUtil.escapeHtml(errorMessage); originalText = UIUtil.escapeHtml(originalText); $('#chatconversation').append( '
Error: ' + 'Your message' + (originalText? (' \"'+ originalText + '\"') : "") + ' was not sent.' + (errorMessage? (' Reason: ' + errorMessage) : '') + '
'); $('#chatconversation').animate( { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000); }; /** * Sets the subject to the UI * @param subject the subject */ my.chatSetSubject = function(subject) { if(subject) subject = subject.trim(); $('#subject').html(Replacement.linkify(UIUtil.escapeHtml(subject))); if(subject === "") { $("#subject").css({display: "none"}); } else { $("#subject").css({display: "block"}); } }; /** * Sets the chat conversation mode. */ my.setChatConversationMode = function (isConversationMode) { if (isConversationMode) { $('#nickname').css({visibility: 'hidden'}); $('#chatconversation').css({visibility: 'visible'}); $('#usermsg').css({visibility: 'visible'}); $('#smileysarea').css({visibility: 'visible'}); $('#usermsg').focus(); } }; /** * Resizes the chat area. */ my.resizeChat = function () { var chatSize = require("../SidePanelToggler").getPanelSize(); $('#chatspace').width(chatSize[0]); $('#chatspace').height(chatSize[1]); resizeChatConversation(); }; /** * Indicates if the chat is currently visible. */ my.isVisible = function () { return $('#chatspace').is(":visible"); }; /** * Shows and hides the window with the smileys */ my.toggleSmileys = toggleSmileys; /** * Scrolls chat to the bottom. */ my.scrollChatToBottom = function() { setTimeout(function () { $('#chatconversation').scrollTop( $('#chatconversation')[0].scrollHeight); }, 5); }; return my; }(Chat || {})); module.exports = Chat; },{"../../../../service/UI/UIEvents":104,"../../toolbars/ToolbarToggler":31,"../../util/NicknameHandler":34,"../../util/UIUtil":35,"../SidePanelToggler":22,"./Commands":24,"./Replacement":25,"./smileys.json":26}],24:[function(require,module,exports){ var UIUtil = require("../../util/UIUtil"); /** * List with supported commands. The keys are the names of the commands and * the value is the function that processes the message. * @type {{String: function}} */ var commands = { "topic" : processTopic }; /** * Extracts the command from the message. * @param message the received message * @returns {string} the command */ function getCommand(message) { if(message) { for(var command in commands) { if(message.indexOf("/" + command) == 0) return command; } } return ""; }; /** * Processes the data for topic command. * @param commandArguments the arguments of the topic command. */ function processTopic(commandArguments) { var topic = UIUtil.escapeHtml(commandArguments); APP.xmpp.setSubject(topic); } /** * Constructs new CommandProccessor instance from a message that * handles commands received via chat messages. * @param message the message * @constructor */ function CommandsProcessor(message) { var command = getCommand(message); /** * Returns the name of the command. * @returns {String} the command */ this.getCommand = function() { return command; }; var messageArgument = message.substr(command.length + 2); /** * Returns the arguments of the command. * @returns {string} */ this.getArgument = function() { return messageArgument; }; } /** * Checks whether this instance is valid command or not. * @returns {boolean} */ CommandsProcessor.prototype.isCommand = function() { if(this.getCommand()) return true; return false; }; /** * Processes the command. */ CommandsProcessor.prototype.processCommand = function() { if(!this.isCommand()) return; commands[this.getCommand()](this.getArgument()); }; module.exports = CommandsProcessor; },{"../../util/UIUtil":35}],25:[function(require,module,exports){ var Smileys = require("./smileys.json"); /** * Processes links and smileys in "body" */ function processReplacements(body) { //make links clickable body = linkify(body); //add smileys body = smilify(body); return body; } /** * Finds and replaces all links in the links in "body" * with their */ function linkify(inputText) { var replacedText, replacePattern1, replacePattern2, replacePattern3; //URLs starting with http://, https://, or ftp:// replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; replacedText = inputText.replace(replacePattern1, '$1'); //URLs starting with "www." (without // before it, or it'd re-link the ones done above). replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; replacedText = replacedText.replace(replacePattern2, '$1$2'); //Change email addresses to mailto:: links. replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim; replacedText = replacedText.replace(replacePattern3, '$1'); return replacedText; } /** * Replaces common smiley strings with images */ function smilify(body) { if(!body) { return body; } var regexs = Smileys["regexs"]; for(var smiley in regexs) { if(regexs.hasOwnProperty(smiley)) { body = body.replace(regexs[smiley], ''); } } return body; } module.exports = { processReplacements: processReplacements, linkify: linkify }; },{"./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){ var Avatar = require('../../avatar/Avatar'); var numberOfContacts = 0; var notificationInterval; /** * Updates the number of participants in the contact list button and sets * the glow * @param delta indicates whether a new user has joined (1) or someone has * left(-1) */ function updateNumberOfParticipants(delta) { numberOfContacts += delta; if (numberOfContacts === 1) { // when the user is alone we don't show the number of participants $("#numberOfParticipants").text(''); ContactList.setVisualNotification(false); } else if (numberOfContacts > 1) { ContactList.setVisualNotification(!ContactList.isVisible()); $("#numberOfParticipants").text(numberOfContacts); } else { console.error("Invalid number of participants: " + numberOfContacts); } } /** * Creates the avatar element. * * @return the newly created avatar element */ function createAvatar(jid) { var avatar = document.createElement('img'); avatar.className = "icon-avatar avatar"; avatar.src = Avatar.getContactListUrl(jid); return avatar; } /** * Creates the display name paragraph. * * @param displayName the display name to set */ function createDisplayNameParagraph(key, displayName) { var p = document.createElement('p'); if(displayName) p.innerText = displayName; else if(key) { p.setAttribute("data-i18n",key); p.innerText = APP.translation.translateString(key); } return p; } function stopGlowing(glower) { window.clearInterval(notificationInterval); notificationInterval = false; glower.removeClass('glowing'); if (!ContactList.isVisible()) { glower.removeClass('active'); } } /** * Contact list. */ var ContactList = { /** * Indicates if the chat is currently visible. * * @return true if the chat is currently visible, false - * otherwise */ isVisible: function () { return $('#contactlist').is(":visible"); }, /** * Adds a contact for the given peerJid if such doesn't yet exist. * * @param peerJid the peerJid corresponding to the contact */ ensureAddContact: function (peerJid) { var resourceJid = Strophe.getResourceFromJid(peerJid); var contact = $('#contacts>li[id="' + resourceJid + '"]'); if (!contact || contact.length <= 0) ContactList.addContact(peerJid); }, /** * Adds a contact for the given peer jid. * * @param peerJid the jid of the contact to add */ addContact: function (peerJid) { var resourceJid = Strophe.getResourceFromJid(peerJid); var contactlist = $('#contacts'); var newContact = document.createElement('li'); newContact.id = resourceJid; newContact.className = "clickable"; newContact.onclick = function (event) { if (event.currentTarget.className === "clickable") { $(ContactList).trigger('contactclicked', [peerJid]); } }; newContact.appendChild(createAvatar(peerJid)); newContact.appendChild(createDisplayNameParagraph("participant")); if (resourceJid === APP.xmpp.myResource()) { contactlist.prepend(newContact); } else { contactlist.append(newContact); } updateNumberOfParticipants(1); }, /** * Removes a contact for the given peer jid. * * @param peerJid the peerJid corresponding to the contact to remove */ removeContact: function (peerJid) { var resourceJid = Strophe.getResourceFromJid(peerJid); var contact = $('#contacts>li[id="' + resourceJid + '"]'); if (contact && contact.length > 0) { var contactlist = $('#contactlist>ul'); contactlist.get(0).removeChild(contact.get(0)); updateNumberOfParticipants(-1); } }, setVisualNotification: function (show, stopGlowingIn) { var glower = $('#contactListButton'); if (show && !notificationInterval) { notificationInterval = window.setInterval(function () { glower.toggleClass('active glowing'); }, 800); } else if (!show && notificationInterval) { stopGlowing(glower); } if (stopGlowingIn) { setTimeout(function () { stopGlowing(glower); }, stopGlowingIn); } }, setClickable: function (resourceJid, isClickable) { var contact = $('#contacts>li[id="' + resourceJid + '"]'); if (isClickable) { contact.addClass('clickable'); } else { contact.removeClass('clickable'); } }, onDisplayNameChange: function (peerJid, displayName) { if (peerJid === 'localVideoContainer') peerJid = APP.xmpp.myJid(); var resourceJid = Strophe.getResourceFromJid(peerJid); var contactName = $('#contacts #' + resourceJid + '>p'); if (contactName && displayName && displayName.length > 0) contactName.html(displayName); }, userAvatarChanged: function (resourceJid, contactListUrl) { // set the avatar in the contact list var contact = $('#' + resourceJid + '>img'); if (contact && contact.length > 0) { contact.get(0).src = contactListUrl; } } }; module.exports = ContactList; },{"../../avatar/Avatar":18}],28:[function(require,module,exports){ var Avatar = require("../../avatar/Avatar"); var Settings = require("./../../../settings/Settings"); var UIUtil = require("../../util/UIUtil"); var languages = require("../../../../service/translation/languages"); function generateLanguagesSelectBox() { var currentLang = APP.translation.getCurrentLanguage(); var html = ""; } var SettingsMenu = { init: function () { $("#startMutedOptions").before(generateLanguagesSelectBox()); APP.translation.translateElement($("#languages_selectbox")); $('#settingsmenu>input').keyup(function(event){ if(event.keyCode === 13) {//enter SettingsMenu.update(); } }); if(APP.xmpp.isModerator()) { $("#startMutedOptions").css("display", "block"); } else { $("#startMutedOptions").css("display", "none"); } $("#updateSettings").click(function () { SettingsMenu.update(); }); }, onRoleChanged: function () { if(APP.xmpp.isModerator()) { $("#startMutedOptions").css("display", "block"); } else { $("#startMutedOptions").css("display", "none"); } }, setStartMuted: function (audio, video) { $("#startAudioMuted").attr("checked", audio); $("#startVideoMuted").attr("checked", video); }, update: function() { var newDisplayName = UIUtil.escapeHtml($('#setDisplayName').get(0).value); var newEmail = UIUtil.escapeHtml($('#setEmail').get(0).value); if(newDisplayName) { var displayName = Settings.setDisplayName(newDisplayName); APP.xmpp.addToPresence("displayName", displayName, true); } var language = $("#languages_selectbox").val(); APP.translation.setLanguage(language); Settings.setLanguage(language); APP.xmpp.addToPresence("email", newEmail); var email = Settings.setEmail(newEmail); var startAudioMuted = ($("#startAudioMuted").is(":checked")); var startVideoMuted = ($("#startVideoMuted").is(":checked")); APP.xmpp.addToPresence("startMuted", [startAudioMuted, startVideoMuted]); Avatar.setUserAvatar(APP.xmpp.myJid(), email); }, isVisible: function() { return $('#settingsmenu').is(':visible'); }, setDisplayName: function(newDisplayName) { var displayName = Settings.setDisplayName(newDisplayName); $('#setDisplayName').get(0).value = displayName; }, onDisplayNameChange: function(peerJid, newDisplayName) { if(peerJid === 'localVideoContainer' || peerJid === APP.xmpp.myJid()) { this.setDisplayName(newDisplayName); } }, changeAvatar: function (thumbUrl) { $('#avatar').get(0).src = thumbUrl; } }; module.exports = SettingsMenu; },{"../../../../service/translation/languages":109,"../../avatar/Avatar":18,"../../util/UIUtil":35,"./../../../settings/Settings":49}],29:[function(require,module,exports){ var PanelToggler = require("../side_pannels/SidePanelToggler"); var buttonHandlers = { "bottom_toolbar_contact_list": function () { BottomToolbar.toggleContactList(); }, "bottom_toolbar_film_strip": function () { BottomToolbar.toggleFilmStrip(); }, "bottom_toolbar_chat": function () { BottomToolbar.toggleChat(); } }; var BottomToolbar = (function (my) { my.init = function () { for(var k in buttonHandlers) $("#" + k).click(buttonHandlers[k]); }; my.toggleChat = function() { PanelToggler.toggleChat(); }; my.toggleContactList = function() { PanelToggler.toggleContactList(); }; my.toggleFilmStrip = function() { var filmstrip = $("#remoteVideos"); filmstrip.toggleClass("hidden"); }; $(document).bind("remotevideo.resized", function (event, width, height) { var bottom = (height - $('#bottomToolbar').outerHeight())/2 + 18; $('#bottomToolbar').css({bottom: bottom + 'px'}); }); return my; }(BottomToolbar || {})); module.exports = BottomToolbar; },{"../side_pannels/SidePanelToggler":22}],30:[function(require,module,exports){ /* global APP,$, buttonClick, config, lockRoom, setSharedKey, Util */ var messageHandler = require("../util/MessageHandler"); var BottomToolbar = require("./BottomToolbar"); var Prezi = require("../prezi/Prezi"); var Etherpad = require("../etherpad/Etherpad"); var PanelToggler = require("../side_pannels/SidePanelToggler"); var Authentication = require("../authentication/Authentication"); var UIUtil = require("../util/UIUtil"); var AuthenticationEvents = require("../../../service/authentication/AuthenticationEvents"); var roomUrl = null; var sharedKey = ''; var UI = null; var buttonHandlers = { "toolbar_button_mute": function () { return APP.UI.toggleAudio(); }, "toolbar_button_camera": function () { return APP.UI.toggleVideo(); }, /*"toolbar_button_authentication": function () { return Toolbar.authenticateClicked(); },*/ "toolbar_button_record": function () { return toggleRecording(); }, "toolbar_button_security": function () { return Toolbar.openLockDialog(); }, "toolbar_button_link": function () { return Toolbar.openLinkDialog(); }, "toolbar_button_chat": function () { return BottomToolbar.toggleChat(); }, "toolbar_button_prezi": function () { return Prezi.openPreziDialog(); }, "toolbar_button_etherpad": function () { return Etherpad.toggleEtherpad(0); }, "toolbar_button_desktopsharing": function () { return APP.desktopsharing.toggleScreenSharing(); }, "toolbar_button_fullScreen": function() { UIUtil.buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen"); return Toolbar.toggleFullScreen(); }, "toolbar_button_sip": function () { return callSipButtonClicked(); }, "toolbar_button_dialpad": function () { return dialpadButtonClicked(); }, "toolbar_button_settings": function () { PanelToggler.toggleSettingsMenu(); }, "toolbar_button_hangup": function () { return hangup(); }, "toolbar_button_login": function () { Toolbar.authenticateClicked(); }, "toolbar_button_logout": function () { // Ask for confirmation messageHandler.openTwoButtonDialog( "dialog.logoutTitle", null, "dialog.logoutQuestion", null, false, "dialog.Yes", function (evt, yes) { if (yes) { APP.xmpp.logout(function (url) { if (url) { window.location.href = url; } else { hangup(); } }); } }); } }; function hangup() { APP.xmpp.disposeConference(); if(config.enableWelcomePage) { setTimeout(function() { window.localStorage.welcomePageDisabled = false; window.location.pathname = "/"; }, 10000); } var title = APP.translation.generateTranslationHTML( "dialog.sessTerminated"); var msg = APP.translation.generateTranslationHTML( "dialog.hungUp"); var button = APP.translation.generateTranslationHTML( "dialog.joinAgain"); var buttons = []; buttons.push({title: button, value: true}); UI.messageHandler.openDialog( title, msg, true, buttons, function(event, value, message, formVals) { window.location.reload(); return false; } ); } /** * Starts or stops the recording for the conference. */ function toggleRecording() { APP.xmpp.toggleRecording(function (callback) { var msg = APP.translation.generateTranslationHTML( "dialog.recordingToken"); var token = APP.translation.translateString("dialog.token"); APP.UI.messageHandler.openTwoButtonDialog(null, null, null, '

' + msg + '

' + '', false, "dialog.Save", function (e, v, m, f) { if (v) { var token = f.recordingToken; if (token) { callback(UIUtil.escapeHtml(token)); } } }, null, function () { }, ':input:first' ); }, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState); } /** * Locks / unlocks the room. */ function lockRoom(lock) { var currentSharedKey = ''; if (lock) currentSharedKey = sharedKey; APP.xmpp.lockRoom(currentSharedKey, function (res) { // password is required if (sharedKey) { console.log('set room password'); Toolbar.lockLockButton(); } else { console.log('removed room password'); Toolbar.unlockLockButton(); } }, function (err) { console.warn('setting password failed', err); messageHandler.showError("dialog.lockTitle", "dialog.lockMessage"); Toolbar.setSharedKey(''); }, function () { console.warn('room passwords not supported'); messageHandler.showError("dialog.warning", "dialog.passwordNotSupported"); Toolbar.setSharedKey(''); }); }; /** * Invite participants to conference. */ function inviteParticipants() { if (roomUrl === null) return; var sharedKeyText = ""; if (sharedKey && sharedKey.length > 0) { sharedKeyText = APP.translation.translateString("email.sharedKey", {sharedKey: sharedKey}); sharedKeyText = sharedKeyText.replace(/\n/g, "%0D%0A"); } var supportedBrowsers = "Chromium, Google Chrome " + APP.translation.translateString("email.and") + " Opera"; var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1); var subject = APP.translation.translateString("email.subject", {appName:interfaceConfig.APP_NAME, conferenceName: conferenceName}); var body = APP.translation.translateString("email.body", {appName:interfaceConfig.APP_NAME, sharedKeyText: sharedKeyText, roomUrl: roomUrl, supportedBrowsers: supportedBrowsers}); body = body.replace(/\n/g, "%0D%0A"); if (window.localStorage.displayname) { body += "%0D%0A%0D%0A" + window.localStorage.displayname; } if (interfaceConfig.INVITATION_POWERED_BY) { body += "%0D%0A%0D%0A--%0D%0Apowered by jitsi.org"; } window.open("mailto:?subject=" + subject + "&body=" + body, '_blank'); } function dialpadButtonClicked() { //TODO show the dialpad window } function callSipButtonClicked() { var defaultNumber = config.defaultSipNumber ? config.defaultSipNumber : ''; var sipMsg = APP.translation.generateTranslationHTML( "dialog.sipMsg"); messageHandler.openTwoButtonDialog(null, null, null, '

' + sipMsg + '

' + '', false, "dialog.Dial", function (e, v, m, f) { if (v) { var numberInput = f.sipNumber; if (numberInput) { APP.xmpp.dial( numberInput, 'fromnumber', UI.getRoomName(), sharedKey); } } }, null, null, ':input:first' ); } var Toolbar = (function (my) { my.init = function (ui) { for(var k in buttonHandlers) $("#" + k).click(buttonHandlers[k]); UI = ui; // Update login info APP.xmpp.addListener( AuthenticationEvents.IDENTITY_UPDATED, function (authenticationEnabled, userIdentity) { var loggedIn = false; if (userIdentity) { loggedIn = true; } Toolbar.showAuthenticateButton(authenticationEnabled); if (authenticationEnabled) { Toolbar.setAuthenticatedIdentity(userIdentity); Toolbar.showLoginButton(!loggedIn); Toolbar.showLogoutButton(loggedIn); } } ); }, /** * Sets shared key * @param sKey the shared key */ my.setSharedKey = function (sKey) { sharedKey = sKey; }; my.authenticateClicked = function () { Authentication.focusAuthenticationWindow(); if (!APP.xmpp.isExternalAuthEnabled()) { Authentication.xmppAuthenticate(); return; } // Get authentication URL if (!APP.xmpp.getMUCJoined()) { APP.xmpp.getLoginUrl(UI.getRoomName(), function (url) { // If conference has not been started yet - redirect to login page window.location.href = url; }); } else { APP.xmpp.getPopupLoginUrl(UI.getRoomName(), function (url) { // Otherwise - open popup with authentication URL var authenticationWindow = Authentication.createAuthenticationWindow( function () { // On popup closed - retry room allocation APP.xmpp.allocateConferenceFocus( APP.UI.getRoomName(), function () { console.info("AUTH DONE"); } ); }, url); if (!authenticationWindow) { messageHandler.openMessageDialog( null, "dialog.popupError"); } }); } }; /** * Updates the room invite url. */ my.updateRoomUrl = function (newRoomUrl) { roomUrl = newRoomUrl; // If the invite dialog has been already opened we update the information. var inviteLink = document.getElementById('inviteLinkRef'); if (inviteLink) { inviteLink.value = roomUrl; inviteLink.select(); $('#inviteLinkRef').parent() .find('button[value=true]').prop('disabled', false); } }; /** * Disables and enables some of the buttons. */ my.setupButtonsFromConfig = function () { if (config.disablePrezi) { $("#prezi_button").css({display: "none"}); } }; /** * Opens the lock room dialog. */ my.openLockDialog = function () { // Only the focus is able to set a shared key. if (!APP.xmpp.isModerator()) { if (sharedKey) { messageHandler.openMessageDialog(null, "dialog.passwordError"); } else { messageHandler.openMessageDialog(null, "dialog.passwordError2"); } } else { if (sharedKey) { messageHandler.openTwoButtonDialog(null, null, "dialog.passwordCheck", null, false, "dialog.Remove", function (e, v) { if (v) { Toolbar.setSharedKey(''); lockRoom(false); } }); } else { var msg = APP.translation.generateTranslationHTML( "dialog.passwordMsg"); var yourPassword = APP.translation.translateString( "dialog.yourPassword"); messageHandler.openTwoButtonDialog(null, null, null, '

' + msg + '

' + '', false, "dialog.Save", function (e, v, m, f) { if (v) { var lockKey = f.lockKey; if (lockKey) { Toolbar.setSharedKey( UIUtil.escapeHtml(lockKey)); lockRoom(true); } } }, null, null, 'input:first' ); } } }; /** * Opens the invite link dialog. */ my.openLinkDialog = function () { var inviteAttreibutes; if (roomUrl === null) { inviteAttreibutes = 'data-i18n="[value]roomUrlDefaultMsg" value="' + APP.translation.translateString("roomUrlDefaultMsg") + '"'; } else { inviteAttreibutes = "value=\"" + encodeURI(roomUrl) + "\""; } messageHandler.openTwoButtonDialog("dialog.shareLink", null, null, '', false, "dialog.Invite", function (e, v) { if (v) { if (roomUrl) { inviteParticipants(); } } }, function (event) { if (roomUrl) { document.getElementById('inviteLinkRef').select(); } else { if (event && event.target) $(event.target) .find('button[value=true]').prop('disabled', true); } } ); }; /** * Opens the settings dialog. * FIXME: not used ? */ my.openSettingsDialog = function () { var settings1 = APP.translation.generateTranslationHTML( "dialog.settings1"); var settings2 = APP.translation.generateTranslationHTML( "dialog.settings2"); var settings3 = APP.translation.generateTranslationHTML( "dialog.settings3"); var yourPassword = APP.translation.translateString( "dialog.yourPassword"); messageHandler.openTwoButtonDialog(null, '

' + settings1 + '

' + '' + settings2 + '
' + '' + settings3 + '', null, null, false, "dialog.Save", function () { document.getElementById('lockKey').focus(); }, function (e, v) { if (v) { if ($('#initMuted').is(":checked")) { // it is checked } if ($('#requireNicknames').is(":checked")) { // it is checked } /* var lockKey = document.getElementById('lockKey'); if (lockKey.value) { setSharedKey(lockKey.value); lockRoom(true); } */ } } ); }; /** * Toggles the application in and out of full screen mode * (a.k.a. presentation mode in Chrome). */ my.toggleFullScreen = function () { var fsElement = document.documentElement; if (!document.mozFullScreen && !document.webkitIsFullScreen) { //Enter Full Screen if (fsElement.mozRequestFullScreen) { fsElement.mozRequestFullScreen(); } else { fsElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT); } } else { //Exit Full Screen if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else { document.webkitCancelFullScreen(); } } }; /** * Unlocks the lock button state. */ my.unlockLockButton = function () { if ($("#lockIcon").hasClass("icon-security-locked")) UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked"); }; /** * Updates the lock button state to locked. */ my.lockLockButton = function () { if ($("#lockIcon").hasClass("icon-security")) UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked"); }; /** * Shows or hides authentication button * @param show true to show or false to hide */ my.showAuthenticateButton = function (show) { if (show) { $('#authentication').css({display: "inline"}); } else { $('#authentication').css({display: "none"}); } }; // Shows or hides the 'recording' button. my.showRecordingButton = function (show) { if (!config.enableRecording) { return; } if (show) { $('#recording').css({display: "inline"}); } else { $('#recording').css({display: "none"}); } }; // Sets the state of the recording button my.setRecordingButtonState = function (isRecording) { var selector = $('#recordButton'); if (isRecording) { selector.removeClass("icon-recEnable"); selector.addClass("icon-recEnable active"); } else { selector.removeClass("icon-recEnable active"); selector.addClass("icon-recEnable"); } }; // Shows or hides SIP calls button my.showSipCallButton = function (show) { if (APP.xmpp.isSipGatewayEnabled() && show) { $('#sipCallButton').css({display: "inline-block"}); } else { $('#sipCallButton').css({display: "none"}); } }; // Shows or hides the dialpad button my.showDialPadButton = function (show) { if (show) { $('#dialPadButton').css({display: "inline-block"}); } else { $('#dialPadButton').css({display: "none"}); } }; /** * Displays user authenticated identity name(login). * @param authIdentity identity name to be displayed. */ my.setAuthenticatedIdentity = function (authIdentity) { if (authIdentity) { var selector = $('#toolbar_auth_identity'); selector.css({display: "list-item"}); selector.text(authIdentity); } else { $('#toolbar_auth_identity').css({display: "none"}); } }; /** * Shows/hides login button. * @param show true to show */ my.showLoginButton = function (show) { if (show) { $('#toolbar_button_login').css({display: "list-item"}); } else { $('#toolbar_button_login').css({display: "none"}); } }; /** * Shows/hides logout button. * @param show true to show */ my.showLogoutButton = function (show) { if (show) { $('#toolbar_button_logout').css({display: "list-item"}); } else { $('#toolbar_button_logout').css({display: "none"}); } }; /** * Sets the state of the button. The button has blue glow if desktop * streaming is active. * @param active the state of the desktop streaming. */ my.changeDesktopSharingButtonState = function (active) { var button = $("#desktopsharing > a"); if (active) { button.addClass("glow"); } else { button.removeClass("glow"); } }; return my; }(Toolbar || {})); module.exports = Toolbar; },{"../../../service/authentication/AuthenticationEvents":105,"../authentication/Authentication":16,"../etherpad/Etherpad":19,"../prezi/Prezi":20,"../side_pannels/SidePanelToggler":22,"../util/MessageHandler":33,"../util/UIUtil":35,"./BottomToolbar":29}],31:[function(require,module,exports){ /* global $, interfaceConfig, Moderator, DesktopStreaming.showDesktopSharingButton */ var toolbarTimeoutObject, toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT; function showDesktopSharingButton() { if (APP.desktopsharing.isDesktopSharingEnabled()) { $('#desktopsharing').css({display: "inline"}); } else { $('#desktopsharing').css({display: "none"}); } } /** * Hides the toolbar. */ function hideToolbar() { if(config.alwaysVisibleToolbar) return; var header = $("#header"), bottomToolbar = $("#bottomToolbar"); var isToolbarHover = false; header.find('*').each(function () { var id = $(this).attr('id'); if ($("#" + id + ":hover").length > 0) { isToolbarHover = true; } }); if ($("#bottomToolbar:hover").length > 0) { isToolbarHover = true; } clearTimeout(toolbarTimeoutObject); toolbarTimeoutObject = null; if (!isToolbarHover) { header.hide("slide", { direction: "up", duration: 300}); $('#subject').animate({top: "-=40"}, 300); if ($("#remoteVideos").hasClass("hidden")) { bottomToolbar.hide( "slide", {direction: "right", duration: 300}); } } else { toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout); } } var ToolbarToggler = { /** * Shows the main toolbar. */ showToolbar: function () { var header = $("#header"), bottomToolbar = $("#bottomToolbar"); if (!header.is(':visible') || !bottomToolbar.is(":visible")) { header.show("slide", { direction: "up", duration: 300}); $('#subject').animate({top: "+=40"}, 300); if (!bottomToolbar.is(":visible")) { bottomToolbar.show( "slide", {direction: "right", duration: 300}); } if (toolbarTimeoutObject) { clearTimeout(toolbarTimeoutObject); toolbarTimeoutObject = null; } toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout); toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT; } if (APP.xmpp.isModerator()) { // TODO: Enable settings functionality. // Need to uncomment the settings button in index.html. // $('#settingsButton').css({visibility:"visible"}); } // Show/hide desktop sharing button showDesktopSharingButton(); }, /** * Docks/undocks the toolbar. * * @param isDock indicates what operation to perform */ dockToolbar: function (isDock) { if (isDock) { // First make sure the toolbar is shown. if (!$('#header').is(':visible')) { this.showToolbar(); } // Then clear the time out, to dock the toolbar. if (toolbarTimeoutObject) { clearTimeout(toolbarTimeoutObject); toolbarTimeoutObject = null; } } else { if (!$('#header').is(':visible')) { this.showToolbar(); } else { toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout); } } }, showDesktopSharingButton: showDesktopSharingButton }; module.exports = ToolbarToggler; },{}],32:[function(require,module,exports){ var JitsiPopover = (function () { /** * Constructs new JitsiPopover and attaches it to the element * @param element jquery selector * @param options the options for the popover. * @constructor */ function JitsiPopover(element, options) { this.options = { skin: "white", content: "" }; if(options) { if(options.skin) this.options.skin = options.skin; if(options.content) this.options.content = options.content; } this.elementIsHovered = false; this.popoverIsHovered = false; this.popoverShown = false; element.data("jitsi_popover", this); this.element = element; this.template = '
' + '
'; var self = this; this.element.on("mouseenter", function () { self.elementIsHovered = true; self.show(); }).on("mouseleave", function () { self.elementIsHovered = false; setTimeout(function () { self.hide(); }, 10); }); } /** * Shows the popover */ JitsiPopover.prototype.show = function () { this.createPopover(); this.popoverShown = true; }; /** * Hides the popover */ JitsiPopover.prototype.hide = function () { if(!this.elementIsHovered && !this.popoverIsHovered && this.popoverShown) { this.forceHide(); } }; /** * Hides the popover */ JitsiPopover.prototype.forceHide = function () { $(".jitsipopover").remove(); this.popoverShown = false; }; /** * Creates the popover html */ JitsiPopover.prototype.createPopover = function () { $("body").append(this.template); $(".jitsipopover > .jitsipopover-content").html(this.options.content); var self = this; $(".jitsipopover").on("mouseenter", function () { self.popoverIsHovered = true; }).on("mouseleave", function () { self.popoverIsHovered = false; self.hide(); }); this.refreshPosition(); }; /** * Refreshes the position of the popover */ JitsiPopover.prototype.refreshPosition = function () { $(".jitsipopover").position({ my: "bottom", at: "top", collision: "fit", of: this.element, using: function (position, elements) { var calcLeft = elements.target.left - elements.element.left + elements.target.width/2; $(".jitsipopover").css({top: position.top, left: position.left, display: "table"}); $(".jitsipopover > .arrow").css({left: calcLeft}); $(".jitsipopover > .jitsiPopupmenuPadding").css({left: calcLeft - 50}); } }); }; /** * Updates the content of popover. * @param content new content */ JitsiPopover.prototype.updateContent = function (content) { this.options.content = content; if(!this.popoverShown) return; $(".jitsipopover").remove(); this.createPopover(); }; return JitsiPopover; })(); module.exports = JitsiPopover; },{}],33:[function(require,module,exports){ /* global $, APP, jQuery, toastr */ var messageHandler = (function(my) { /** * Shows a message to the user. * * @param titleString the title of the message * @param messageString the text of the message */ my.openMessageDialog = function(titleKey, messageKey) { var title = null; if(titleKey) { title = APP.translation.generateTranslationHTML(titleKey); } var message = APP.translation.generateTranslationHTML(messageKey); $.prompt(message, { title: title, persistent: false } ); }; /** * Shows a message to the user with two buttons: first is given as a parameter and the second is Cancel. * * @param titleString the title of the message * @param msgString the text of the message * @param persistent boolean value which determines whether the message is persistent or not * @param leftButton the fist button's text * @param submitFunction function to be called on submit * @param loadedFunction function to be called after the prompt is fully loaded * @param closeFunction function to be called after the prompt is closed * @param focus optional focus selector or button index to be focused after * the dialog is opened * @param defaultButton index of default button which will be activated when * the user press 'enter'. Indexed from 0. */ my.openTwoButtonDialog = function(titleKey, titleString, msgKey, msgString, persistent, leftButtonKey, submitFunction, loadedFunction, closeFunction, focus, defaultButton) { var buttons = []; var leftButton = APP.translation.generateTranslationHTML(leftButtonKey); buttons.push({ title: leftButton, value: true}); var cancelButton = APP.translation.generateTranslationHTML("dialog.Cancel"); buttons.push({title: cancelButton, value: false}); var message = msgString, title = titleString; if (titleKey) { title = APP.translation.generateTranslationHTML(titleKey); } if (msgKey) { message = APP.translation.generateTranslationHTML(msgKey); } $.prompt(message, { title: title, persistent: false, buttons: buttons, defaultButton: defaultButton, focus: focus, loaded: loadedFunction, submit: submitFunction, close: closeFunction }); }; /** * Shows a message to the user with two buttons: first is given as a parameter and the second is Cancel. * * @param titleString the title of the message * @param msgString the text of the message * @param persistent boolean value which determines whether the message is persistent or not * @param buttons object with the buttons. The keys must be the name of the button and value is the value * that will be passed to submitFunction * @param submitFunction function to be called on submit * @param loadedFunction function to be called after the prompt is fully loaded */ my.openDialog = function (titleString, msgString, persistent, buttons, submitFunction, loadedFunction) { var args = { title: titleString, persistent: persistent, buttons: buttons, defaultButton: 1, loaded: loadedFunction, submit: submitFunction }; if (persistent) { args.closeText = ''; } return new Impromptu(msgString, args); }; /** * Closes currently opened dialog. */ my.closeDialog = function () { $.prompt.close(); }; /** * Shows a dialog with different states to the user. * * @param statesObject object containing all the states of the dialog */ my.openDialogWithStates = function (statesObject, options) { return new Impromptu(statesObject, options); }; /** * Opens new popup window for given url centered over current * window. * * @param url the URL to be displayed in the popup window * @param w the width of the popup window * @param h the height of the popup window * @param onPopupClosed optional callback function called when popup window * has been closed. * * @returns popup window object if opened successfully or undefined * in case we failed to open it(popup blocked) */ my.openCenteredPopup = function (url, w, h, onPopupClosed) { var l = window.screenX + (window.innerWidth / 2) - (w / 2); var t = window.screenY + (window.innerHeight / 2) - (h / 2); var popup = window.open( url, '_blank', 'top=' + t + ', left=' + l + ', width=' + w + ', height=' + h + ''); if (popup && onPopupClosed) { var pollTimer = window.setInterval(function () { if (popup.closed !== false) { window.clearInterval(pollTimer); onPopupClosed(); } }, 200); } return popup; }; /** * Shows a dialog prompting the user to send an error report. * * @param titleString the title of the message * @param msgString the text of the message * @param error the error that is being reported */ my.openReportDialog = function(titleKey, msgKey, error) { my.openMessageDialog(titleKey, msgKey); console.log(error); //FIXME send the error to the server }; /** * Shows an error dialog to the user. * @param title the title of the message * @param message the text of the messafe */ my.showError = function(titleKey, msgKey) { if(!titleKey) { titleKey = "dialog.oops"; } if(!msgKey) { msgKey = "dialog.defaultError"; } messageHandler.openMessageDialog(titleKey, msgKey); }; my.notify = function(displayName, displayNameKey, cls, messageKey, messageArguments, options) { var displayNameSpan = '" + APP.translation.translateString(displayNameKey); } displayNameSpan += ""; toastr.info( displayNameSpan + '
' + '" + APP.translation.translateString(messageKey, messageArguments) + '', null, options); }; return my; }(messageHandler || {})); module.exports = messageHandler; },{}],34:[function(require,module,exports){ var UIEvents = require("../../../service/UI/UIEvents"); var nickname = null; var eventEmitter = null; var NickanameHandler = { init: function (emitter) { eventEmitter = emitter; var storedDisplayName = window.localStorage.displayname; if (storedDisplayName) { nickname = storedDisplayName; } }, setNickname: function (newNickname) { if (!newNickname || nickname === newNickname) return; nickname = newNickname; window.localStorage.displayname = nickname; eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newNickname); }, getNickname: function () { return nickname; }, addListener: function (type, listener) { eventEmitter.on(type, listener); } }; module.exports = NickanameHandler; },{"../../../service/UI/UIEvents":104}],35:[function(require,module,exports){ /** * Created by hristo on 12/22/14. */ module.exports = { /** * Returns the available video width. */ getAvailableVideoWidth: function (isVisible) { var PanelToggler = require("../side_pannels/SidePanelToggler"); if(typeof isVisible === "undefined" || isVisible === null) isVisible = PanelToggler.isVisible(); var rightPanelWidth = isVisible ? PanelToggler.getPanelSize()[0] : 0; return window.innerWidth - rightPanelWidth; }, /** * Changes the style class of the element given by id. */ buttonClick: function(id, classname) { $(id).toggleClass(classname); // add the class to the clicked element }, /** * Returns the text width for the given element. * * @param el the element */ getTextWidth: function (el) { return (el.clientWidth + 1); }, /** * Returns the text height for the given element. * * @param el the element */ getTextHeight: function (el) { return (el.clientHeight + 1); }, /** * Plays the sound given by id. * * @param id the identifier of the audio element. */ playSoundNotification: function (id) { document.getElementById(id).play(); }, /** * Escapes the given text. */ escapeHtml: function (unsafeText) { return $('
').text(unsafeText).html(); }, imageToGrayScale: function (canvas) { var context = canvas.getContext('2d'); var imgData = context.getImageData(0, 0, canvas.width, canvas.height); var pixels = imgData.data; for (var i = 0, n = pixels.length; i < n; i += 4) { var grayscale = pixels[i] * .3 + pixels[i+1] * .59 + pixels[i+2] * .11; pixels[i ] = grayscale; // red pixels[i+1] = grayscale; // green pixels[i+2] = grayscale; // blue // pixels[i+3] is alpha } // redraw the image in black & white context.putImageData(imgData, 0, 0); }, setTooltip: function (element, key, position) { element.setAttribute("data-i18n", "[data-content]" + key); element.setAttribute("data-toggle", "popover"); element.setAttribute("data-placement", position); element.setAttribute("data-html", true); element.setAttribute("data-container", "body"); } }; },{"../side_pannels/SidePanelToggler":22}],36:[function(require,module,exports){ var JitsiPopover = require("../util/JitsiPopover"); /** * Constructs new connection indicator. * @param videoContainer the video container associated with the indicator. * @constructor */ function ConnectionIndicator(videoContainer, jid) { this.videoContainer = videoContainer; this.bandwidth = null; this.packetLoss = null; this.bitrate = null; this.showMoreValue = false; this.resolution = null; this.transport = []; this.popover = null; this.jid = jid; this.create(); } /** * Values for the connection quality * @type {{98: string, * 81: string, * 64: string, * 47: string, * 30: string, * 0: string}} */ ConnectionIndicator.connectionQualityValues = { 98: "18px", //full 81: "15px",//4 bars 64: "11px",//3 bars 47: "7px",//2 bars 30: "3px",//1 bar 0: "0px"//empty }; ConnectionIndicator.getIP = function(value) { return value.substring(0, value.lastIndexOf(":")); }; ConnectionIndicator.getPort = function(value) { return value.substring(value.lastIndexOf(":") + 1, value.length); }; ConnectionIndicator.getStringFromArray = function (array) { var res = ""; for(var i = 0; i < array.length; i++) { res += (i === 0? "" : ", ") + array[i]; } return res; }; /** * Generates the html content. * @returns {string} the html content. */ ConnectionIndicator.prototype.generateText = function () { var downloadBitrate, uploadBitrate, packetLoss, resolution, i; var translate = APP.translation.translateString; if(this.bitrate === null) { downloadBitrate = "N/A"; uploadBitrate = "N/A"; } else { downloadBitrate = this.bitrate.download? this.bitrate.download + " Kbps" : "N/A"; uploadBitrate = this.bitrate.upload? this.bitrate.upload + " Kbps" : "N/A"; } if(this.packetLoss === null) { packetLoss = "N/A"; } else { packetLoss = "" + (this.packetLoss.download !== null? this.packetLoss.download : "N/A") + "% " + (this.packetLoss.upload !== null? this.packetLoss.upload : "N/A") + "%"; } var resolutionValue = null; if(this.resolution && this.jid != null) { var keys = Object.keys(this.resolution); for(var ssrc in this.resolution) { resolutionValue = this.resolution[ssrc]; } } if(this.jid === null) { resolution = ""; if(this.resolution === null || !Object.keys(this.resolution) || Object.keys(this.resolution).length === 0) { resolution = "N/A"; } else for(i in this.resolution) { resolutionValue = this.resolution[i]; if(resolutionValue) { if(resolutionValue.height && resolutionValue.width) { resolution += (resolution === ""? "" : ", ") + resolutionValue.width + "x" + resolutionValue.height; } } } } else if(!resolutionValue || !resolutionValue.height || !resolutionValue.width) { resolution = "N/A"; } else { resolution = resolutionValue.width + "x" + resolutionValue.height; } var result = "" + "" + "" + "" + "" + "" + "" + "" + "" + "
" + translate("connectionindicator.bitrate") + "" + downloadBitrate + " " + uploadBitrate + "
" + translate("connectionindicator.packetloss") + "" + packetLoss + "
" + translate("connectionindicator.resolution") + "" + resolution + "
"; if(this.videoContainer.videoSpanId == "localVideoContainer") { result += "
" + translate("connectionindicator." + (this.showMoreValue ? "less" : "more")) + "

"; } if(this.showMoreValue) { var downloadBandwidth, uploadBandwidth, transport; if(this.bandwidth === null) { downloadBandwidth = "N/A"; uploadBandwidth = "N/A"; } else { downloadBandwidth = this.bandwidth.download? this.bandwidth.download + " Kbps" : "N/A"; uploadBandwidth = this.bandwidth.upload? this.bandwidth.upload + " Kbps" : "N/A"; } if(!this.transport || this.transport.length === 0) { transport = "" + "" + translate("connectionindicator.address") + "" + " N/A"; } else { var data = {remoteIP: [], localIP:[], remotePort:[], localPort:[]}; for(i = 0; i < this.transport.length; i++) { var ip = ConnectionIndicator.getIP(this.transport[i].ip); var port = ConnectionIndicator.getPort(this.transport[i].ip); var localIP = ConnectionIndicator.getIP(this.transport[i].localip); var localPort = ConnectionIndicator.getPort(this.transport[i].localip); if(data.remoteIP.indexOf(ip) == -1) { data.remoteIP.push(ip); } if(data.remotePort.indexOf(port) == -1) { data.remotePort.push(port); } if(data.localIP.indexOf(localIP) == -1) { data.localIP.push(localIP); } if(data.localPort.indexOf(localPort) == -1) { data.localPort.push(localPort); } } var local_address_key = "connectionindicator.localaddress"; var remote_address_key = "connectionindicator.remoteaddress"; var localTransport = "" + translate(local_address_key, {count: data.localIP.length}) + " " + ConnectionIndicator.getStringFromArray(data.localIP) + ""; transport = "" + translate(remote_address_key, {count: data.remoteIP.length}) + " " + ConnectionIndicator.getStringFromArray(data.remoteIP) + ""; var key_remote = "connectionindicator.remoteport", key_local = "connectionindicator.localport"; transport += "" + "" + "" + translate(key_remote, {count: this.transport.length}) + ""; localTransport += "" + "" + "" + translate(key_local, {count: this.transport.length}) + ""; transport += ConnectionIndicator.getStringFromArray(data.remotePort); localTransport += ConnectionIndicator.getStringFromArray(data.localPort); transport += ""; transport += localTransport + ""; transport +="" + "" + translate("connectionindicator.transport") + "" + "" + this.transport[0].type + ""; } result += "" + "" + ""; 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){ var Avatar = require("../avatar/Avatar"); var RTCBrowserType = require("../../RTC/RTCBrowserType"); var UIUtil = require("../util/UIUtil"); var UIEvents = require("../../../service/UI/UIEvents"); var xmpp = require("../../xmpp/xmpp"); var ToolbarToggler = require("../toolbars/ToolbarToggler"); // FIXME: With Temasys we have to re-select everytime //var video = $('#largeVideo'); var currentVideoWidth = null; var currentVideoHeight = null; // By default we use camera var getVideoSize = getCameraVideoSize; var getVideoPosition = getCameraVideoPosition; /** * The small video instance that is displayed in the large video * @type {SmallVideo} */ var currentSmallVideo = null; /** * Indicates whether the large video is enabled. * @type {boolean} */ var isEnabled = true; /** * Current large video state. * Possible values - video, prezi or etherpad. * @type {string} */ var state = "video"; /** * Returns the html element associated with the passed state of large video * @param state the state. * @returns {JQuery|*|jQuery|HTMLElement} the container. */ function getContainerByState(state) { var selector = null; switch (state) { case "video": selector = "#largeVideo"; break; case "etherpad": selector = "#etherpad>iframe"; break; case "prezi": selector = "#presentation>iframe"; break; } return (selector !== null)? $(selector) : null; } /** * Sets the size and position of the given video element. * * @param video the video element to position * @param width the desired video width * @param height the desired video height * @param horizontalIndent the left and right indent * @param verticalIndent the top and bottom indent */ function positionVideo(video, width, height, horizontalIndent, verticalIndent, animate) { if(animate) { video.animate({ width: width, height: height, top: verticalIndent, bottom: verticalIndent, left: horizontalIndent, right: horizontalIndent }, { queue: false, duration: 500 }); } else { video.width(width); video.height(height); video.css({ top: verticalIndent + 'px', bottom: verticalIndent + 'px', left: horizontalIndent + 'px', right: horizontalIndent + 'px'}); } } /** * Returns an array of the video dimensions, so that it keeps it's aspect * ratio and fits available area with it's larger dimension. This method * ensures that whole video will be visible and can leave empty areas. * * @return an array with 2 elements, the video width and the video height */ function getDesktopVideoSize(videoWidth, videoHeight, videoSpaceWidth, videoSpaceHeight) { if (!videoWidth) videoWidth = currentVideoWidth; if (!videoHeight) videoHeight = currentVideoHeight; var aspectRatio = videoWidth / videoHeight; var availableWidth = Math.max(videoWidth, videoSpaceWidth); var availableHeight = Math.max(videoHeight, videoSpaceHeight); videoSpaceHeight -= $('#remoteVideos').outerHeight(); if (availableWidth / aspectRatio >= videoSpaceHeight) { availableHeight = videoSpaceHeight; availableWidth = availableHeight * aspectRatio; } if (availableHeight * aspectRatio >= videoSpaceWidth) { availableWidth = videoSpaceWidth; availableHeight = availableWidth / aspectRatio; } return [availableWidth, availableHeight]; } /** * Returns an array of the video horizontal and vertical indents, * so that if fits its parent. * * @return an array with 2 elements, the horizontal indent and the vertical * indent */ function getCameraVideoPosition(videoWidth, videoHeight, videoSpaceWidth, videoSpaceHeight) { // Parent height isn't completely calculated when we position the video in // full screen mode and this is why we use the screen height in this case. // Need to think it further at some point and implement it properly. var isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; if (isFullScreen) videoSpaceHeight = window.innerHeight; var horizontalIndent = (videoSpaceWidth - videoWidth) / 2; var verticalIndent = (videoSpaceHeight - videoHeight) / 2; return [horizontalIndent, verticalIndent]; } /** * Returns an array of the video horizontal and vertical indents. * Centers horizontally and top aligns vertically. * * @return an array with 2 elements, the horizontal indent and the vertical * indent */ function getDesktopVideoPosition(videoWidth, videoHeight, videoSpaceWidth, videoSpaceHeight) { var horizontalIndent = (videoSpaceWidth - videoWidth) / 2; var verticalIndent = 0;// Top aligned return [horizontalIndent, verticalIndent]; } /** * Returns an array of the video dimensions, so that it covers the screen. * It leaves no empty areas, but some parts of the video might not be visible. * * @return an array with 2 elements, the video width and the video height */ function getCameraVideoSize(videoWidth, videoHeight, videoSpaceWidth, videoSpaceHeight) { if (!videoWidth) videoWidth = currentVideoWidth; if (!videoHeight) videoHeight = currentVideoHeight; var aspectRatio = videoWidth / videoHeight; var availableWidth = Math.max(videoWidth, videoSpaceWidth); var availableHeight = Math.max(videoHeight, videoSpaceHeight); if (availableWidth / aspectRatio < videoSpaceHeight) { availableHeight = videoSpaceHeight; availableWidth = availableHeight * aspectRatio; } if (availableHeight * aspectRatio < videoSpaceWidth) { availableWidth = videoSpaceWidth; availableHeight = availableWidth / aspectRatio; } return [availableWidth, availableHeight]; } /** * Updates the src of the active speaker avatar * @param jid of the current active speaker */ function updateActiveSpeakerAvatarSrc() { var avatar = $("#activeSpeakerAvatar")[0]; var jid = currentSmallVideo.peerJid; var url = Avatar.getActiveSpeakerUrl(jid); if (avatar.src === url) return; 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 = APP.RTC.isVideoSrcDesktop(currentSmallVideo.peerJid); // Change the way we'll be measuring and positioning large video getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize; getVideoPosition = isDesktop ? getDesktopVideoPosition : getCameraVideoPosition; // Only if the large video is currently visible. if (isVisible) { LargeVideo.VideoLayout.largeVideoUpdated(currentSmallVideo); $('#largeVideo').fadeIn(300); } } /** * Creates the html elements for the large video. */ function createLargeVideoHTML() { var html = '
'; html += '
' + '
' + '
' + '
' + '' + ' 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 $('#largeVideo').is(':visible'); }, /** * Returns true if the user is currently displayed on large video. */ isCurrentlyOnLarge: function (resourceJid) { return currentSmallVideo && resourceJid && currentSmallVideo.getResourceJid() === resourceJid; }, /** * Updates the large video with the given new video source. */ updateLargeVideo: function (resourceJid, forceUpdate) { if(!isEnabled) return; var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid); console.log('hover in ' + resourceJid + ', video: ', newSmallVideo); if (!LargeVideo.isCurrentlyOnLarge(resourceJid) || forceUpdate) { $('#activeSpeaker').css('visibility', 'hidden'); var oldSmallVideo = null; if (currentSmallVideo) { oldSmallVideo = currentSmallVideo; } currentSmallVideo = newSmallVideo; var oldJid = null; if (oldSmallVideo) oldJid = oldSmallVideo.peerJid; if (oldJid !== resourceJid) { // we want the notification to trigger even if userJid is undefined, // or null. this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, resourceJid); } if (RTCBrowserType.isSafari()) { // FIXME In Safari fadeOut works only for the first time changeVideo(this.isLargeVideoVisible()); } else { $('#largeVideo').fadeOut(300, changeVideo.bind($('#largeVideo'), this.isLargeVideoVisible())); } } else { if (currentSmallVideo) { currentSmallVideo.showAvatar(); } } }, /** * Shows/hides the large video. */ setLargeVideoVisible: function(isVisible) { if(!isEnabled) return; if (isVisible) { $('#largeVideo').css({visibility: 'visible'}); $('.watermark').css({visibility: 'visible'}); if(currentSmallVideo) currentSmallVideo.enableDominantSpeaker(true); } else { $('#largeVideo').css({visibility: 'hidden'}); $('#activeSpeaker').css('visibility', 'hidden'); $('.watermark').css({visibility: 'hidden'}); if(currentSmallVideo) currentSmallVideo.enableDominantSpeaker(false); } }, onVideoTypeChanged: function (jid) { if(!isEnabled) return; var resourceJid = Strophe.getResourceFromJid(jid); if (LargeVideo.isCurrentlyOnLarge(resourceJid)) { var isDesktop = APP.RTC.isVideoSrcDesktop(jid); getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize; getVideoPosition = isDesktop ? getDesktopVideoPosition : getCameraVideoPosition; this.position(null, null); } }, /** * Positions the large video. * * @param videoWidth the stream video width * @param videoHeight the stream video height */ position: function (videoWidth, videoHeight, videoSpaceWidth, videoSpaceHeight, animate) { if(!isEnabled) return; if(!videoSpaceWidth) videoSpaceWidth = $('#videospace').width(); if(!videoSpaceHeight) videoSpaceHeight = window.innerHeight; var videoSize = getVideoSize(videoWidth, videoHeight, videoSpaceWidth, videoSpaceHeight); var largeVideoWidth = videoSize[0]; var largeVideoHeight = videoSize[1]; var videoPosition = getVideoPosition(largeVideoWidth, largeVideoHeight, videoSpaceWidth, videoSpaceHeight); var horizontalIndent = videoPosition[0]; var verticalIndent = videoPosition[1]; positionVideo($('#largeVideo'), largeVideoWidth, largeVideoHeight, horizontalIndent, verticalIndent, animate); }, resize: function (animate, isVisible, completeFunction) { if(!isEnabled) return; var availableHeight = window.innerHeight; var availableWidth = UIUtil.getAvailableVideoWidth(isVisible); if (availableWidth < 0 || availableHeight < 0) return; var avatarSize = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE; var top = availableHeight / 2 - avatarSize / 4 * 3; $('#activeSpeaker').css('top', top); if(animate) { $('#videospace').animate({ right: window.innerWidth - availableWidth, width: availableWidth, height: availableHeight }, { queue: false, duration: 500, complete: completeFunction }); $('#largeVideoContainer').animate({ width: availableWidth, height: availableHeight }, { queue: false, duration: 500 }); } else { $('#videospace').width(availableWidth); $('#videospace').height(availableHeight); $('#largeVideoContainer').width(availableWidth); $('#largeVideoContainer').height(availableHeight); } return [availableWidth, availableHeight]; }, resizeVideoAreaAnimated: function (isVisible, completeFunction) { if(!isEnabled) return; var size = this.resize(true, isVisible, completeFunction); this.position(null, null, size[0], size[1], true); }, getResourceJid: function () { return currentSmallVideo ? currentSmallVideo.getResourceJid() : null; }, updateAvatar: function (resourceJid) { if(!isEnabled) return; if (resourceJid === this.getResourceJid()) { updateActiveSpeakerAvatarSrc(); } }, showAvatar: function (resourceJid, show) { if(!isEnabled) return; if(this.getResourceJid() === resourceJid && state === "video") { $("#largeVideo").css("visibility", show ? "hidden" : "visible"); $('#activeSpeaker').css("visibility", show ? "visible" : "hidden"); return true; } return false; }, /** * Disables the large video */ disable: function () { isEnabled = false; }, /** * Enables the large video */ enable: function () { isEnabled = true; }, /** * Returns true if the video is enabled. */ isEnabled: function () { return isEnabled; }, /** * Creates the iframe used by the etherpad * @param src the value for src attribute * @param onloadHandler handler executed when the iframe loads it content * @returns {HTMLElement} the iframe */ createEtherpadIframe: function (src, onloadHandler) { if(!isEnabled) return; var etherpadIFrame = document.createElement('iframe'); etherpadIFrame.src = src; etherpadIFrame.frameBorder = 0; etherpadIFrame.scrolling = "no"; etherpadIFrame.width = $('#largeVideoContainer').width() || 640; etherpadIFrame.height = $('#largeVideoContainer').height() || 480; etherpadIFrame.setAttribute('style', 'visibility: hidden;'); document.getElementById('etherpad').appendChild(etherpadIFrame); etherpadIFrame.onload = onloadHandler; return etherpadIFrame; }, /** * Changes the state of the large video. * Possible values - video, prezi, etherpad. * @param newState - the new state */ setState: function (newState) { if(state === newState) return; var currentContainer = getContainerByState(state); if(!currentContainer) return; var self = this; var oldState = state; switch (newState) { case "etherpad": $('#activeSpeaker').css('visibility', 'hidden'); currentContainer.fadeOut(300, function () { if (oldState === "prezi") { currentContainer.css({opacity: '0'}); $('#reloadPresentation').css({display: 'none'}); } else { self.setLargeVideoVisible(false); } }); $('#etherpad>iframe').fadeIn(300, function () { document.body.style.background = '#eeeeee'; $('#etherpad>iframe').css({visibility: 'visible'}); $('#etherpad').css({zIndex: 2}); }); break; case "prezi": var prezi = $('#presentation>iframe'); currentContainer.fadeOut(300, function() { document.body.style.background = 'black'; }); prezi.fadeIn(300, function() { prezi.css({opacity:'1'}); ToolbarToggler.dockToolbar(true);//fix that self.setLargeVideoVisible(false); $('#etherpad>iframe').css({visibility: 'hidden'}); $('#etherpad').css({zIndex: 0}); }); $('#activeSpeaker').css('visibility', 'hidden'); break; case "video": currentContainer.fadeOut(300, function () { $('#presentation>iframe').css({opacity:'0'}); $('#reloadPresentation').css({display:'none'}); $('#etherpad>iframe').css({visibility: 'hidden'}); $('#etherpad').css({zIndex: 0}); document.body.style.background = 'black'; ToolbarToggler.dockToolbar(false);//fix that }); $('#largeVideo').fadeIn(300, function () { self.setLargeVideoVisible(true); }); break; } state = newState; }, /** * Returns the current state of the large video. * @returns {string} the current state - video, prezi or etherpad. */ getState: function () { return state; }, /** * Sets hover handlers for the large video container div. * @param inHandler * @param outHandler */ setHover: function(inHandler, outHandler) { $('#largeVideoContainer').hover(inHandler, outHandler); } }; module.exports = LargeVideo; },{"../../../service/UI/UIEvents":104,"../../RTC/RTCBrowserType":10,"../../xmpp/xmpp":68,"../avatar/Avatar":18,"../toolbars/ToolbarToggler":31,"../util/UIUtil":35}],38:[function(require,module,exports){ var SmallVideo = require("./SmallVideo"); var ConnectionIndicator = require("./ConnectionIndicator"); var NicknameHandler = require("../util/NicknameHandler"); var UIUtil = require("../util/UIUtil"); var LargeVideo = require("./LargeVideo"); var RTCBrowserType = require("../../RTC/RTCBrowserType"); function LocalVideo(VideoLayout) { this.videoSpanId = "localVideoContainer"; this.container = $("#localVideoContainer").get(0); this.VideoLayout = VideoLayout; this.flipX = true; this.isLocal = true; this.peerJid = null; } LocalVideo.prototype = Object.create(SmallVideo.prototype); LocalVideo.prototype.constructor = LocalVideo; /** * Creates the edit display name button. * * @returns the edit button */ function createEditDisplayNameButton() { var editButton = document.createElement('a'); editButton.className = 'displayname'; UIUtil.setTooltip(editButton, "videothumbnail.editnickname", "top"); editButton.innerHTML = ''; return editButton; } /** * Sets the display name for the given video span id. */ LocalVideo.prototype.setDisplayName = function(displayName, key) { if (!this.container) { console.warn( "Unable to set displayName - " + this.videoSpanId + " does not exist"); return; } var nameSpan = $('#' + this.videoSpanId + '>span.displayname'); var defaultLocalDisplayName = APP.translation.generateTranslationHTML( interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME); // If we already have a display name for this video. if (nameSpan.length > 0) { if (nameSpan.text() !== displayName) { if (displayName && displayName.length > 0) { var meHTML = APP.translation.generateTranslationHTML("me"); $('#localDisplayName').html(displayName + ' (' + meHTML + ')'); } else $('#localDisplayName').html(defaultLocalDisplayName); } } else { var editButton = createEditDisplayNameButton(); nameSpan = document.createElement('span'); nameSpan.className = 'displayname'; $('#' + this.videoSpanId)[0].appendChild(nameSpan); if (displayName && displayName.length > 0) { var meHTML = APP.translation.generateTranslationHTML("me"); nameSpan.innerHTML = displayName + meHTML; } else { nameSpan.innerHTML = defaultLocalDisplayName; } nameSpan.id = 'localDisplayName'; this.container.appendChild(editButton); //translates popover of edit button APP.translation.translateElement($("a.displayname")); var editableText = document.createElement('input'); editableText.className = 'displayname'; editableText.type = 'text'; editableText.id = 'editDisplayName'; if (displayName && displayName.length) { editableText.value = displayName; } var defaultNickname = APP.translation.translateString( "defaultNickname", {name: "Jane Pink"}); editableText.setAttribute('style', 'display:none;'); editableText.setAttribute('data-18n', '[placeholder]defaultNickname'); editableText.setAttribute("data-i18n-options", JSON.stringify({name: "Jane Pink"})); editableText.setAttribute("placeholder", defaultNickname); this.container.appendChild(editableText); var self = this; $('#localVideoContainer .displayname') .bind("click", function (e) { e.preventDefault(); e.stopPropagation(); $('#localDisplayName').hide(); $('#editDisplayName').show(); $('#editDisplayName').focus(); $('#editDisplayName').select(); $('#editDisplayName').one("focusout", function (e) { self.VideoLayout.inputDisplayNameHandler(this.value); }); $('#editDisplayName').on('keydown', function (e) { if (e.keyCode === 13) { e.preventDefault(); self.VideoLayout.inputDisplayNameHandler(this.value); } }); }); } } LocalVideo.prototype.inputDisplayNameHandler = function (name) { NicknameHandler.setNickname(name); if (!$('#localDisplayName').is(":visible")) { if (NicknameHandler.getNickname()) { var meHTML = APP.translation.generateTranslationHTML("me"); $('#localDisplayName').html(NicknameHandler.getNickname() + " (" + meHTML + ")"); } else { var defaultHTML = APP.translation.generateTranslationHTML( interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME); $('#localDisplayName') .html(defaultHTML); } $('#localDisplayName').show(); } $('#editDisplayName').hide(); } LocalVideo.prototype.createConnectionIndicator = function() { if(this.connectionIndicator) return; this.connectionIndicator = new ConnectionIndicator(this, null); } LocalVideo.prototype.changeVideo = function (stream, isMuted) { var self = this; function localVideoClick(event) { // FIXME: with Temasys plugin event arg is not an event, but // the clicked object itself, so we have to skip this call if (event.stopPropagation) { event.stopPropagation(); } self.VideoLayout.handleVideoThumbClicked( false, APP.xmpp.myResource()); } $('#localVideoContainer').off('click'); $('#localVideoContainer').on('click', localVideoClick); // Add hover handler $('#localVideoContainer').hover( function() { self.showDisplayName(true); }, function() { if (!LargeVideo.isLargeVideoVisible() || !LargeVideo.isCurrentlyOnLarge(self.getResourceJid())) self.showDisplayName(false); } ); if(isMuted) { APP.UI.setVideoMute(true); return; } this.flipX = (stream.videoType == "screen")? false : true; var localVideo = document.createElement('video'); localVideo.id = 'localVideo_' + APP.RTC.getStreamID(stream.getOriginalStream()); if (!RTCBrowserType.isIExplorer()) { localVideo.autoplay = true; localVideo.volume = 0; // is it required if audio is separated ? } localVideo.oncontextmenu = function () { return false; }; var localVideoContainer = document.getElementById('localVideoWrapper'); localVideoContainer.appendChild(localVideo); var localVideoSelector = $('#' + localVideo.id); // Add click handler to both video and video wrapper elements in case // there's no video. // onclick has to be used with Temasys plugin localVideo.onclick = localVideoClick; if (this.flipX) { localVideoSelector.addClass("flipVideoX"); } // Attach WebRTC stream APP.RTC.attachMediaStream(localVideoSelector, stream.getOriginalStream()); // Add stream ended handler stream.getOriginalStream().onended = function () { // We have to re-select after attach when Temasys plugin is used, // because