diff --git a/index.html b/index.html index d43fbb04c..306497d76 100644 --- a/index.html +++ b/index.html @@ -20,7 +20,7 @@ - + diff --git a/libs/app.bundle.js b/libs/app.bundle.js index bc8e57ed4..f6f025ab4 100644 --- a/libs/app.bundle.js +++ b/libs/app.bundle.js @@ -1,19147 +1,19331 @@ (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 -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":161}],3:[function(require,module,exports){ -/* global APP */ - -/** - * A module for sending DTMF tones. - */ -var DTMFSender; -var initDtmfSender = function() { - // TODO: This needs to reset this if the peerconnection changes - // (e.g. the call is re-made) - if (DTMFSender) - return; - - var localAudio = APP.RTC.localAudio; - if (localAudio && localAudio.getTracks().length > 0) - { - var peerconnection - = APP.xmpp.getConnection().jingle.activecall.peerconnection; - if (peerconnection) { - DTMFSender = - peerconnection.peerconnection - .createDTMFSender(localAudio.getTracks()[0]); - console.log("Initialized DTMFSender"); - } - else { - console.log("Failed to initialize DTMFSender: no PeerConnection."); - } - } - else { - console.log("Failed to initialize DTMFSender: no audio track."); - } -}; - -var DTMF = { - sendTones: function (tones, duration, pause) { - if (!DTMFSender) - initDtmfSender(); - - if (DTMFSender){ - DTMFSender.insertDTMF(tones, - (duration || 200), - (pause || 200)); - } - } -}; - -module.exports = DTMF; - - -},{}],4:[function(require,module,exports){ -/* global config, APP, Strophe */ - -// cache datachannels to avoid garbage collection -// https://code.google.com/p/chromium/issues/detail?id=405545 -var RTCEvents = require("../../service/RTC/RTCEvents"); - -var _dataChannels = []; -var eventEmitter = null; - - -var DataChannels = { - /** - * Callback triggered by PeerConnection when new data channel is opened - * on the bridge. - * @param event the event info object. - */ - onDataChannel: function (event) { - var dataChannel = event.channel; - - dataChannel.onopen = function () { - console.info("Data channel opened by the Videobridge!", dataChannel); - - // Code sample for sending string and/or binary data - // Sends String message to the bridge - //dataChannel.send("Hello bridge!"); - // Sends 12 bytes binary message to the bridge - //dataChannel.send(new ArrayBuffer(12)); - - eventEmitter.emit(RTCEvents.DATA_CHANNEL_OPEN); - }; - - dataChannel.onerror = function (error) { - console.error("Data Channel Error:", error, dataChannel); - }; - - dataChannel.onmessage = function (event) { - var data = event.data; - // JSON - var obj; - - try { - obj = JSON.parse(data); - } - catch (e) { - console.error( - "Failed to parse data channel message as JSON: ", - data, - dataChannel); - } - if (('undefined' !== typeof(obj)) && (null !== obj)) { - var colibriClass = obj.colibriClass; - - if ("DominantSpeakerEndpointChangeEvent" === colibriClass) { - // Endpoint ID from the Videobridge. - var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint; - - console.info( - "Data channel new dominant speaker event: ", - dominantSpeakerEndpoint); - eventEmitter.emit(RTCEvents.DOMINANTSPEAKER_CHANGED, dominantSpeakerEndpoint); - } - else if ("InLastNChangeEvent" === colibriClass) { - var oldValue = obj.oldValue; - var newValue = obj.newValue; - // Make sure that oldValue and newValue are of type boolean. - var type; - - if ((type = typeof oldValue) !== 'boolean') { - if (type === 'string') { - oldValue = (oldValue == "true"); - } else { - oldValue = new Boolean(oldValue).valueOf(); - } - } - if ((type = typeof newValue) !== 'boolean') { - if (type === 'string') { - newValue = (newValue == "true"); - } else { - newValue = new Boolean(newValue).valueOf(); - } - } - - eventEmitter.emit(RTCEvents.LASTN_CHANGED, oldValue, newValue); - } - else if ("LastNEndpointsChangeEvent" === colibriClass) { - // The new/latest list of last-n endpoint IDs. - var lastNEndpoints = obj.lastNEndpoints; - // The list of endpoint IDs which are entering the list of - // last-n at this time i.e. were not in the old list of last-n - // endpoint IDs. - var endpointsEnteringLastN = obj.endpointsEnteringLastN; - - console.log( - "Data channel new last-n event: ", - lastNEndpoints, endpointsEnteringLastN, obj); - eventEmitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED, - lastNEndpoints, endpointsEnteringLastN, obj); - } - else { - console.debug("Data channel JSON-formatted message: ", obj); - } - } - }; - - dataChannel.onclose = function () { - console.info("The Data Channel closed", dataChannel); - var idx = _dataChannels.indexOf(dataChannel); - if (idx > -1) - _dataChannels = _dataChannels.splice(idx, 1); - }; - _dataChannels.push(dataChannel); - }, - - /** - * Binds "ondatachannel" event listener to given PeerConnection instance. - * @param peerConnection WebRTC peer connection instance. - */ - init: function (peerConnection, emitter) { - if(!config.openSctp) - return; - - peerConnection.ondatachannel = this.onDataChannel; - eventEmitter = emitter; - - // Sample code for opening new data channel from Jitsi Meet to the bridge. - // Although it's not a requirement to open separate channels from both bridge - // and peer as single channel can be used for sending and receiving data. - // So either channel opened by the bridge or the one opened here is enough - // for communication with the bridge. - /*var dataChannelOptions = - { - reliable: true - }; - var dataChannel - = peerConnection.createDataChannel("myChannel", dataChannelOptions); - - // Can be used only when is in open state - dataChannel.onopen = function () - { - dataChannel.send("My channel !!!"); - }; - dataChannel.onmessage = function (event) - { - var msgData = event.data; - console.info("Got My Data Channel Message:", msgData, dataChannel); - };*/ - }, - handleSelectedEndpointEvent: onSelectedEndpointChanged, - handlePinnedEndpointEvent: onPinnedEndpointChanged -}; - -function onSelectedEndpointChanged(userResource) { - console.log('selected endpoint changed: ', userResource); - if (_dataChannels && _dataChannels.length != 0) { - _dataChannels.some(function (dataChannel) { - if (dataChannel.readyState == 'open') { - console.log('sending selected endpoint changed ' + - 'notification to the bridge: ', userResource); - dataChannel.send(JSON.stringify({ - 'colibriClass': 'SelectedEndpointChangedEvent', - 'selectedEndpoint': - (!userResource || userResource === null)? - null : userResource - })); - - return true; - } - }); - } -} - -function onPinnedEndpointChanged(userResource) { - console.log('pinned endpoint changed: ', userResource); - if (_dataChannels && _dataChannels.length != 0) { - _dataChannels.some(function (dataChannel) { - if (dataChannel.readyState == 'open') { - dataChannel.send(JSON.stringify({ - 'colibriClass': 'PinnedEndpointChangedEvent', - 'pinnedEndpoint': - (!userResource || userResource == null)? - null : userResource - })); - - return true; - } - }); - } -} - -module.exports = DataChannels; - - -},{"../../service/RTC/RTCEvents":152}],5:[function(require,module,exports){ -/* global APP */ -var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); -var RTCEvents = require("../../service/RTC/RTCEvents"); -var RTCBrowserType = require("./RTCBrowserType"); - -/** - * This implements 'onended' callback normally fired by WebRTC after the stream - * is stopped. There is no such behaviour yet in FF, so we have to add it. - * @param stream original WebRTC stream object to which 'onended' handling - * will be added. - */ -function implementOnEndedHandling(stream) { - var originalStop = stream.stop; - stream.stop = function () { - originalStop.apply(stream); - if (!stream.ended) { - stream.ended = true; - stream.onended(); - } - }; -} - -function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) { - this.stream = stream; - this.eventEmitter = eventEmitter; - this.type = type; - this.videoType = videoType; - this.isGUMStream = true; - if(isGUMStream === false) - this.isGUMStream = isGUMStream; - var self = this; - if(type == "audio") { - this.getTracks = function () { - return self.stream.getAudioTracks(); - }; - } else { - this.getTracks = function () { - return self.stream.getVideoTracks(); - }; - } - - this.stream.onended = function () { - self.streamEnded(); - }; - if (RTCBrowserType.isFirefox()) { - implementOnEndedHandling(this.stream); - } -} - -LocalStream.prototype.streamEnded = function () { - this.eventEmitter.emit(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, this); -}; - -LocalStream.prototype.getOriginalStream = function() -{ - return this.stream; -}; - -LocalStream.prototype.isAudioStream = function () { - return this.type === "audio"; -}; - -LocalStream.prototype.setMute = function (mute) -{ - var isAudio = this.isAudioStream(); - var eventType = isAudio ? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE; - - if ((window.location.protocol != "https:" && this.isGUMStream) || - (isAudio && this.isGUMStream) || this.videoType === "screen" || - // FIXME FF does not support 'removeStream' method used to mute - RTCBrowserType.isFirefox()) { - - var tracks = this.getTracks(); - for (var idx = 0; idx < tracks.length; idx++) { - tracks[idx].enabled = !mute; - } - this.eventEmitter.emit(eventType, mute); - } else { - if (mute) { - APP.xmpp.removeStream(this.stream); - this.stream.stop(); - this.eventEmitter.emit(eventType, true); - } else { - var self = this; - APP.RTC.rtcUtils.obtainAudioAndVideoPermissions( - (this.isAudioStream() ? ["audio"] : ["video"]), - function (stream) { - if (isAudio) { - APP.RTC.changeLocalAudio(stream, - function () { - self.eventEmitter.emit(eventType, false); - }); - } else { - APP.RTC.changeLocalVideo(stream, false, - function () { - self.eventEmitter.emit(eventType, false); - }); - } - }); - } - } -}; - -LocalStream.prototype.isMuted = function () { - var tracks = []; - if (this.isAudioStream()) { - tracks = this.stream.getAudioTracks(); - } else { - if (this.stream.ended) - return true; - tracks = this.stream.getVideoTracks(); - } - for (var idx = 0; idx < tracks.length; idx++) { - if(tracks[idx].enabled) - return false; - } - return true; -}; - -LocalStream.prototype.getId = function () { - return this.stream.getTracks()[0].id; -}; - -module.exports = LocalStream; - -},{"../../service/RTC/RTCEvents":152,"../../service/RTC/StreamEventTypes.js":154,"./RTCBrowserType":8}],6:[function(require,module,exports){ -var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); - -/** - * Creates a MediaStream object for the given data, session id and ssrc. - * It is a wrapper class for the MediaStream. - * - * @param data the data object from which we obtain the stream, - * the peerjid, etc. - * @param sid the session id - * @param ssrc the ssrc corresponding to this MediaStream - * - * @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":151}],7:[function(require,module,exports){ -/* global APP */ -var EventEmitter = require("events"); -var RTCBrowserType = require("./RTCBrowserType"); -var RTCUtils = require("./RTCUtils.js"); -var LocalStream = require("./LocalStream.js"); -var DataChannels = require("./DataChannels"); -var MediaStream = require("./MediaStream.js"); -var DesktopSharingEventTypes - = require("../../service/desktopsharing/DesktopSharingEventTypes"); -var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); -var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); -var RTCEvents = require("../../service/RTC/RTCEvents.js"); -var XMPPEvents = require("../../service/xmpp/XMPPEvents"); -var UIEvents = require("../../service/UI/UIEvents"); - -var eventEmitter = new EventEmitter(); - - -function getMediaStreamUsage() -{ - var result = { - audio: true, - video: true - }; - - /** There are some issues with the desktop sharing - * when this property is enabled. - * WARNING: We must change the implementation to start video/audio if we - * receive from the focus that the peer is not muted. - - var isSecureConnection = window.location.protocol == "https:"; - - if(config.disableEarlyMediaPermissionRequests || !isSecureConnection) - { - result = { - audio: false, - video: false - }; - - } - **/ - - return result; -} - -var RTC = { - rtcUtils: null, - devices: { - audio: true, - video: true - }, - localStreams: [], - remoteStreams: {}, - localAudio: null, - localVideo: null, - addStreamListener: function (listener, eventType) { - eventEmitter.on(eventType, listener); - }, - addListener: function (type, listener) { - eventEmitter.on(type, listener); - }, - removeStreamListener: function (listener, eventType) { - if(!(eventType instanceof StreamEventTypes)) - throw "Illegal argument"; - - eventEmitter.removeListener(eventType, listener); - }, - createLocalStream: function (stream, type, change, videoType, isMuted, isGUMStream) { - - var localStream = new LocalStream(stream, type, eventEmitter, videoType, isGUMStream); - //in firefox we have only one stream object - if(this.localStreams.length == 0 || - this.localStreams[0].getOriginalStream() != stream) - this.localStreams.push(localStream); - if(isMuted === true) - localStream.setMute(true); - - if(type == "audio") { - this.localAudio = localStream; - } else { - this.localVideo = localStream; - } - var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED; - if(change) - eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED; - - eventEmitter.emit(eventType, localStream, isMuted); - return localStream; - }, - removeLocalStream: function (stream) { - for(var i = 0; i < this.localStreams.length; i++) { - if(this.localStreams[i].getOriginalStream() === stream) { - delete this.localStreams[i]; - return; - } - } - }, - createRemoteStream: function (data, sid, thessrc) { - var remoteStream = new MediaStream(data, sid, thessrc, - RTCBrowserType.getBrowserType(), eventEmitter); - var jid = data.peerjid || APP.xmpp.myJid(); - if(!this.remoteStreams[jid]) { - this.remoteStreams[jid] = {}; - } - this.remoteStreams[jid][remoteStream.type]= remoteStream; - eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, remoteStream); - return remoteStream; - }, - getPCConstraints: function () { - return this.rtcUtils.pc_constraints; - }, - getUserMediaWithConstraints:function(um, success_callback, - failure_callback, resolution, - bandwidth, fps, desktopStream) - { - return this.rtcUtils.getUserMediaWithConstraints(um, success_callback, - failure_callback, resolution, bandwidth, fps, desktopStream); - }, - attachMediaStream: function (elSelector, stream) { - this.rtcUtils.attachMediaStream(elSelector, stream); - }, - getStreamID: function (stream) { - return this.rtcUtils.getStreamID(stream); - }, - getVideoSrc: function (element) { - return this.rtcUtils.getVideoSrc(element); - }, - setVideoSrc: function (element, src) { - this.rtcUtils.setVideoSrc(element, src); - }, - getVideoElementName: function () { - return RTCBrowserType.isTemasysPluginUsed() ? 'object' : 'video'; - }, - dispose: function() { - if (this.rtcUtils) { - this.rtcUtils = null; - } - }, - stop: function () { - this.dispose(); - }, - start: function () { - var self = this; - APP.desktopsharing.addListener( - function (stream, isUsingScreenStream, callback) { - self.changeLocalVideo(stream, isUsingScreenStream, callback); - }, DesktopSharingEventTypes.NEW_STREAM_CREATED); - APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function(event) { - DataChannels.init(event.peerconnection, eventEmitter); - }); - APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, - DataChannels.handleSelectedEndpointEvent); - APP.UI.addListener(UIEvents.PINNED_ENDPOINT, - DataChannels.handlePinnedEndpointEvent); - - // In case of IE we continue from 'onReady' callback - // passed to RTCUtils constructor. It will be invoked by Temasys plugin - // once it is initialized. - var onReady = function () { - eventEmitter.emit(RTCEvents.RTC_READY, true); - self.rtcUtils.obtainAudioAndVideoPermissions( - null, null, getMediaStreamUsage()); - }; - - this.rtcUtils = new RTCUtils(this, onReady); - - // Call onReady() if Temasys plugin is not used - if (!RTCBrowserType.isTemasysPluginUsed()) { - onReady(); - } - }, - muteRemoteVideoStream: function (jid, value) { - var stream; - - if(this.remoteStreams[jid] && - this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) { - stream = this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]; - } - - if(!stream) - return true; - - if (value != stream.muted) { - stream.setMute(value); - return true; - } - return false; - }, - switchVideoStreams: function (new_stream) { - this.localVideo.stream = new_stream; - - this.localStreams = []; - - //in firefox we have only one stream object - if (this.localAudio.getOriginalStream() != new_stream) - this.localStreams.push(this.localAudio); - this.localStreams.push(this.localVideo); - }, - changeLocalVideo: function (stream, isUsingScreenStream, callback) { - var oldStream = this.localVideo.getOriginalStream(); - var type = (isUsingScreenStream ? "screen" : "camera"); - var localCallback = callback; - if(this.localVideo.isMuted() && this.localVideo.videoType !== type) { - localCallback = function() { - APP.xmpp.setVideoMute(false, function(mute) { - eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute); - }); - - callback(); - }; - } - // FIXME: Workaround for FF/IE/Safari - if (stream && stream.videoStream) { - stream = stream.videoStream; - } - var videoStream = this.rtcUtils.createStream(stream, true); - this.localVideo = this.createLocalStream(videoStream, "video", true, type); - // Stop the stream to trigger onended event for old stream - oldStream.stop(); - - this.switchVideoStreams(videoStream, oldStream); - - APP.xmpp.switchStreams(videoStream, oldStream,localCallback); - }, - changeLocalAudio: function (stream, callback) { - var oldStream = this.localAudio.getOriginalStream(); - var newStream = this.rtcUtils.createStream(stream); - this.localAudio = this.createLocalStream(newStream, "audio", true); - // Stop the stream to trigger onended event for old stream - oldStream.stop(); - APP.xmpp.switchStreams(newStream, oldStream, callback, true); - }, - isVideoMuted: function (jid) { - if (jid === APP.xmpp.myJid()) { - var localVideo = APP.RTC.localVideo; - return (!localVideo || localVideo.isMuted()); - } else { - if (!APP.RTC.remoteStreams[jid] || - !APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) { - return null; - } - return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted; - } - }, - setVideoMute: function (mute, callback, options) { - if (!this.localVideo) - return; - - if (mute == APP.RTC.localVideo.isMuted()) - { - APP.xmpp.sendVideoInfoPresence(mute); - if (callback) - callback(mute); - } - else - { - APP.RTC.localVideo.setMute(mute); - APP.xmpp.setVideoMute( - mute, - callback, - options); - } - }, - setDeviceAvailability: function (devices) { - if(!devices) - return; - if(devices.audio === true || devices.audio === false) - this.devices.audio = devices.audio; - if(devices.video === true || devices.video === false) - this.devices.video = devices.video; - eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, this.devices); - } -}; - -module.exports = RTC; - -},{"../../service/RTC/MediaStreamTypes":151,"../../service/RTC/RTCEvents.js":152,"../../service/RTC/StreamEventTypes.js":154,"../../service/UI/UIEvents":155,"../../service/desktopsharing/DesktopSharingEventTypes":158,"../../service/xmpp/XMPPEvents":161,"./DataChannels":4,"./LocalStream.js":5,"./MediaStream.js":6,"./RTCBrowserType":8,"./RTCUtils.js":9,"events":162}],8:[function(require,module,exports){ - -var currentBrowser; - -var browserVersion; - -var isAndroid; - -var RTCBrowserType = { - - RTC_BROWSER_CHROME: "rtc_browser.chrome", - - RTC_BROWSER_OPERA: "rtc_browser.opera", - - RTC_BROWSER_FIREFOX: "rtc_browser.firefox", - - RTC_BROWSER_IEXPLORER: "rtc_browser.iexplorer", - - RTC_BROWSER_SAFARI: "rtc_browser.safari", - - getBrowserType: function () { - return currentBrowser; - }, - - isChrome: function () { - return currentBrowser === RTCBrowserType.RTC_BROWSER_CHROME; - }, - - isOpera: function () { - return currentBrowser === RTCBrowserType.RTC_BROWSER_OPERA; - }, - isFirefox: function () { - return currentBrowser === RTCBrowserType.RTC_BROWSER_FIREFOX; - }, - - isIExplorer: function () { - return currentBrowser === RTCBrowserType.RTC_BROWSER_IEXPLORER; - }, - - isSafari: function () { - return currentBrowser === RTCBrowserType.RTC_BROWSER_SAFARI; - }, - isTemasysPluginUsed: function () { - return RTCBrowserType.isIExplorer() || RTCBrowserType.isSafari(); - }, - getFirefoxVersion: function () { - return RTCBrowserType.isFirefox() ? browserVersion : null; - }, - - getChromeVersion: function () { - return RTCBrowserType.isChrome() ? browserVersion : null; - }, - - usesPlanB: function() { - return RTCBrowserType.isChrome() || RTCBrowserType.isOpera() || - RTCBrowserType.isTemasysPluginUsed(); - }, - - usesUnifiedPlan: function() { - return RTCBrowserType.isFirefox(); - }, - - /** - * Whether the browser is running on an android device. - */ - isAndroid: function() { - return isAndroid; - } - - // Add version getters for other browsers when needed -}; - -// detectOpera() must be called before detectChrome() !!! -// otherwise Opera wil be detected as Chrome -function detectChrome() { - if (navigator.webkitGetUserMedia) { - currentBrowser = RTCBrowserType.RTC_BROWSER_CHROME; - var userAgent = navigator.userAgent.toLowerCase(); - // We can assume that user agent is chrome, because it's - // enforced when 'ext' streaming method is set - var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10); - console.log("This appears to be Chrome, ver: " + ver); - return ver; - } - return null; -} - -function detectOpera() { - var userAgent = navigator.userAgent; - if (userAgent.match(/Opera|OPR/)) { - currentBrowser = RTCBrowserType.RTC_BROWSER_OPERA; - var version = userAgent.match(/(Opera|OPR) ?\/?(\d+)\.?/)[2]; - console.info("This appears to be Opera, ver: " + version); - return version; - } - return null; -} - -function detectFirefox() { - if (navigator.mozGetUserMedia) { - currentBrowser = RTCBrowserType.RTC_BROWSER_FIREFOX; - var version = parseInt( - navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); - console.log('This appears to be Firefox, ver: ' + version); - return version; - } - return null; -} - -function detectSafari() { - if ((/^((?!chrome).)*safari/i.test(navigator.userAgent))) { - currentBrowser = RTCBrowserType.RTC_BROWSER_SAFARI; - console.info("This appears to be Safari"); - // FIXME detect Safari version when needed - return 1; - } - return null; -} - -function detectIE() { - var version; - var ua = window.navigator.userAgent; - - var msie = ua.indexOf('MSIE '); - if (msie > 0) { - // IE 10 or older => return version number - version = parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); - } - - var trident = ua.indexOf('Trident/'); - if (!version && trident > 0) { - // IE 11 => return version number - var rv = ua.indexOf('rv:'); - version = parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10); - } - - var edge = ua.indexOf('Edge/'); - if (!version && edge > 0) { - // IE 12 => return version number - version = parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10); - } - - if (version) { - currentBrowser = RTCBrowserType.RTC_BROWSER_IEXPLORER; - console.info("This appears to be IExplorer, ver: " + version); - } - return version; -} - -function detectBrowser() { - var version; - var detectors = [ - detectOpera, - detectChrome, - detectFirefox, - detectIE, - detectSafari - ]; - // Try all browser detectors - for (var i = 0; i < detectors.length; i++) { - version = detectors[i](); - if (version) - return version; - } - console.error("Failed to detect browser type"); - return undefined; -} - -browserVersion = detectBrowser(); -isAndroid = navigator.userAgent.indexOf('Android') != -1; - -module.exports = RTCBrowserType; -},{}],9:[function(require,module,exports){ -/* global config, require, attachMediaStream, getUserMedia */ -var RTCBrowserType = require("./RTCBrowserType"); -var Resolutions = require("../../service/RTC/Resolutions"); -var AdapterJS = require("./adapter.screenshare"); -var SDPUtil = require("../xmpp/SDPUtil"); - -var currentResolution = null; - -function getPreviousResolution(resolution) { - if(!Resolutions[resolution]) - return null; - var order = Resolutions[resolution].order; - var res = null; - var resName = null; - for(var i in Resolutions) { - var tmp = Resolutions[i]; - if(res == null || (res.order < tmp.order && tmp.order < order)) { - resName = i; - res = tmp; - } - } - return resName; -} - -function setResolutionConstraints(constraints, resolution) { - var isAndroid = RTCBrowserType.isAndroid(); - - if (Resolutions[resolution]) { - constraints.video.mandatory.minWidth = Resolutions[resolution].width; - constraints.video.mandatory.minHeight = Resolutions[resolution].height; - } - else if (isAndroid) { - // FIXME can't remember if the purpose of this was to always request - // low resolution on Android ? if yes it should be moved up front - constraints.video.mandatory.minWidth = 320; - constraints.video.mandatory.minHeight = 240; - constraints.video.mandatory.maxFrameRate = 15; - } - - if (constraints.video.mandatory.minWidth) - constraints.video.mandatory.maxWidth = - constraints.video.mandatory.minWidth; - if (constraints.video.mandatory.minHeight) - constraints.video.mandatory.maxHeight = - constraints.video.mandatory.minHeight; -} - -function getConstraints(um, resolution, bandwidth, fps, desktopStream) { - var constraints = {audio: false, video: false}; - - if (um.indexOf('video') >= 0) { - // same behaviour as true - constraints.video = { mandatory: {}, optional: [] }; - - constraints.video.optional.push({ googLeakyBucket: true }); - - setResolutionConstraints(constraints, resolution); - } - if (um.indexOf('audio') >= 0) { - if (!RTCBrowserType.isFirefox()) { - // same behaviour as true - constraints.audio = { mandatory: {}, optional: []}; - // if it is good enough for hangouts... - constraints.audio.optional.push( - {googEchoCancellation: true}, - {googAutoGainControl: true}, - {googNoiseSupression: true}, - {googHighpassFilter: true}, - {googNoisesuppression2: true}, - {googEchoCancellation2: true}, - {googAutoGainControl2: true} - ); - } else { - constraints.audio = true; - } - } - if (um.indexOf('screen') >= 0) { - if (RTCBrowserType.isChrome()) { - constraints.video = { - mandatory: { - chromeMediaSource: "screen", - googLeakyBucket: true, - maxWidth: window.screen.width, - maxHeight: window.screen.height, - maxFrameRate: 3 - }, - optional: [] - }; - } else if (RTCBrowserType.isTemasysPluginUsed()) { - constraints.video = { - optional: [ - { - sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey - } - ] - }; - } else { - console.error( - "'screen' WebRTC media source is supported only in Chrome" + - " and with Temasys plugin"); - } - } - if (um.indexOf('desktop') >= 0) { - constraints.video = { - mandatory: { - chromeMediaSource: "desktop", - chromeMediaSourceId: desktopStream, - googLeakyBucket: true, - maxWidth: window.screen.width, - maxHeight: window.screen.height, - maxFrameRate: 3 - }, - optional: [] - }; - } - - if (bandwidth) { - if (!constraints.video) { - //same behaviour as true - constraints.video = {mandatory: {}, optional: []}; - } - constraints.video.optional.push({bandwidth: bandwidth}); - } - if (fps) { - // for some cameras it might be necessary to request 30fps - // so they choose 30fps mjpg over 10fps yuy2 - if (!constraints.video) { - // same behaviour as true; - constraints.video = {mandatory: {}, optional: []}; - } - constraints.video.mandatory.minFrameRate = fps; - } - - return constraints; -} - - -function RTCUtils(RTCService, onTemasysPluginReady) -{ - var self = this; - this.service = RTCService; - if (RTCBrowserType.isFirefox()) { - var FFversion = RTCBrowserType.getFirefoxVersion(); - if (FFversion >= 40) { - this.peerconnection = mozRTCPeerConnection; - this.getUserMedia = navigator.mozGetUserMedia.bind(navigator); - this.pc_constraints = {}; - this.attachMediaStream = function (element, stream) { - // srcObject is being standardized and FF will eventually - // support that unprefixed. FF also supports the - // "element.src = URL.createObjectURL(...)" combo, but that - // will be deprecated in favour of srcObject. - // - // https://groups.google.com/forum/#!topic/mozilla.dev.media/pKOiioXonJg - // https://github.com/webrtc/samples/issues/302 - if(!element[0]) - return; - element[0].mozSrcObject = stream; - element[0].play(); - }; - this.getStreamID = function (stream) { - var id = stream.id; - if (!id) { - var tracks = stream.getVideoTracks(); - if (!tracks || tracks.length === 0) { - tracks = stream.getAudioTracks(); - } - id = tracks[0].id; - } - return SDPUtil.filter_special_chars(id); - }; - this.getVideoSrc = function (element) { - if(!element) - return null; - return element.mozSrcObject; - }; - this.setVideoSrc = function (element, src) { - if(element) - element.mozSrcObject = src; - }; - RTCSessionDescription = mozRTCSessionDescription; - RTCIceCandidate = mozRTCIceCandidate; - } else { - console.error( - "Firefox version too old: " + FFversion + ". Required >= 40."); - window.location.href = 'unsupported_browser.html'; - return; - } - - } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) { - this.peerconnection = webkitRTCPeerConnection; - this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); - this.attachMediaStream = function (element, stream) { - element.attr('src', webkitURL.createObjectURL(stream)); - }; - this.getStreamID = function (stream) { - // streams from FF endpoints have the characters '{' and '}' - // that make jQuery choke. - return SDPUtil.filter_special_chars(stream.id); - }; - this.getVideoSrc = function (element) { - if(!element) - return null; - return element.getAttribute("src"); - }; - this.setVideoSrc = function (element, src) { - if(element) - element.setAttribute("src", src); - }; - // DTLS should now be enabled by default but.. - this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]}; - if (RTCBrowserType.isAndroid()) { - this.pc_constraints = {}; // disable DTLS on Android - } - if (!webkitMediaStream.prototype.getVideoTracks) { - webkitMediaStream.prototype.getVideoTracks = function () { - return this.videoTracks; - }; - } - if (!webkitMediaStream.prototype.getAudioTracks) { - webkitMediaStream.prototype.getAudioTracks = function () { - return this.audioTracks; - }; - } - } - // Detect IE/Safari - else if (RTCBrowserType.isTemasysPluginUsed()) { - - //AdapterJS.WebRTCPlugin.setLogLevel( - // AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE); - - AdapterJS.webRTCReady(function (isPlugin) { - - self.peerconnection = RTCPeerConnection; - self.getUserMedia = getUserMedia; - self.attachMediaStream = function (elSel, stream) { - - if (stream.id === "dummyAudio" || stream.id === "dummyVideo") { - return; - } - - attachMediaStream(elSel[0], stream); - }; - self.getStreamID = function (stream) { - var id = SDPUtil.filter_special_chars(stream.label); - return id; - }; - self.getVideoSrc = function (element) { - if (!element) { - console.warn("Attempt to get video SRC of null element"); - return null; - } - var children = element.children; - for (var i = 0; i !== children.length; ++i) { - if (children[i].name === 'streamId') { - return children[i].value; - } - } - //console.info(element.id + " SRC: " + src); - return null; - }; - self.setVideoSrc = function (element, src) { - //console.info("Set video src: ", element, src); - if (!src) { - console.warn("Not attaching video stream, 'src' is null"); - return; - } - AdapterJS.WebRTCPlugin.WaitForPluginReady(); - var stream = AdapterJS.WebRTCPlugin.plugin - .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src); - attachMediaStream(element, stream); - }; - - onTemasysPluginReady(isPlugin); - }); - } else { - try { - console.log('Browser does not appear to be WebRTC-capable'); - } catch (e) { } - window.location.href = 'unsupported_browser.html'; - } -} - - -RTCUtils.prototype.getUserMediaWithConstraints = function( - um, success_callback, failure_callback, resolution,bandwidth, fps, - desktopStream) { - currentResolution = resolution; - - var constraints = getConstraints( - um, resolution, bandwidth, fps, desktopStream); - - console.info("Get media constraints", constraints); - - var self = this; - - try { - this.getUserMedia(constraints, - function (stream) { - console.log('onUserMediaSuccess'); - self.setAvailableDevices(um, true); - success_callback(stream); - }, - function (error) { - self.setAvailableDevices(um, false); - console.warn('Failed to get access to local media. Error ', - error, constraints); - if (failure_callback) { - failure_callback(error); - } - }); - } catch (e) { - console.error('GUM failed: ', e); - if(failure_callback) { - failure_callback(e); - } - } -}; - -RTCUtils.prototype.setAvailableDevices = function (um, available) { - var devices = {}; - if(um.indexOf("video") != -1) { - devices.video = available; - } - if(um.indexOf("audio") != -1) { - devices.audio = available; - } - this.service.setDeviceAvailability(devices); -}; - -/** - * We ask for audio and video combined stream in order to get permissions and - * not to ask twice. - */ -RTCUtils.prototype.obtainAudioAndVideoPermissions = - function(devices, callback, usageOptions) -{ - var self = this; - // Get AV - - var successCallback = function (stream) { - if(callback) - callback(stream, usageOptions); - else - self.successCallback(stream, usageOptions); - }; - - if(!devices) - devices = ['audio', 'video']; - - var newDevices = []; - - - if(usageOptions) - for(var i = 0; i < devices.length; i++) { - var device = devices[i]; - if(usageOptions[device] === true) - newDevices.push(device); - } - else - newDevices = devices; - - if(newDevices.length === 0) { - successCallback(); - return; - } - - if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) { - - // With FF/IE we can't split the stream into audio and video because FF - // doesn't support media stream constructors. So, we need to get the - // audio stream separately from the video stream using two distinct GUM - // calls. Not very user friendly :-( but we don't have many other - // options neither. - // - // Note that we pack those 2 streams in a single object and pass it to - // the successCallback method. - var obtainVideo = function (audioStream) { - self.getUserMediaWithConstraints( - ['video'], - function (videoStream) { - return successCallback({ - audioStream: audioStream, - videoStream: videoStream - }); - }, - function (error) { - console.error( - 'failed to obtain video stream - stop', error); - self.errorCallback(error); - }, - config.resolution || '360'); - }; - var obtainAudio = function () { - self.getUserMediaWithConstraints( - ['audio'], - function (audioStream) { - if (newDevices.indexOf('video') !== -1) - obtainVideo(audioStream); - }, - function (error) { - console.error( - 'failed to obtain audio stream - stop', error); - self.errorCallback(error); - } - ); - }; - if (newDevices.indexOf('audio') !== -1) { - obtainAudio(); - } else { - obtainVideo(null); - } - } else { - this.getUserMediaWithConstraints( - newDevices, - function (stream) { - successCallback(stream); - }, - function (error) { - self.errorCallback(error); - }, - config.resolution || '360'); - } -}; - -RTCUtils.prototype.successCallback = function (stream, usageOptions) { - // If this is FF or IE, the stream parameter is *not* a MediaStream object, - // it's an object with two properties: audioStream, videoStream. - if (stream && stream.getAudioTracks && stream.getVideoTracks) - console.log('got', stream, stream.getAudioTracks().length, - stream.getVideoTracks().length); - this.handleLocalStream(stream, usageOptions); -}; - -RTCUtils.prototype.errorCallback = function (error) { - var self = this; - console.error('failed to obtain audio/video stream - trying audio only', error); - var resolution = getPreviousResolution(currentResolution); - if(typeof error == "object" && error.constraintName && error.name - && (error.name == "ConstraintNotSatisfiedError" || - error.name == "OverconstrainedError") && - (error.constraintName == "minWidth" || error.constraintName == "maxWidth" || - error.constraintName == "minHeight" || error.constraintName == "maxHeight") - && resolution != null) - { - self.getUserMediaWithConstraints(['audio', 'video'], - function (stream) { - return self.successCallback(stream); - }, function (error) { - return self.errorCallback(error); - }, resolution); - } - else { - self.getUserMediaWithConstraints( - ['audio'], - function (stream) { - return self.successCallback(stream); - }, - function (error) { - console.error('failed to obtain audio/video stream - stop', - error); - return self.successCallback(null); - } - ); - } -}; - -RTCUtils.prototype.handleLocalStream = function(stream, usageOptions) { - // If this is FF, the stream parameter is *not* a MediaStream object, it's - // an object with two properties: audioStream, videoStream. - var audioStream, videoStream; - if(window.webkitMediaStream) - { - audioStream = new webkitMediaStream(); - videoStream = new webkitMediaStream(); - if(stream) { - var audioTracks = stream.getAudioTracks(); - - for (var i = 0; i < audioTracks.length; i++) { - audioStream.addTrack(audioTracks[i]); - } - - var videoTracks = stream.getVideoTracks(); - - for (i = 0; i < videoTracks.length; i++) { - videoStream.addTrack(videoTracks[i]); - } - } - } - else if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) - { // Firefox and Temasys plugin - if (stream && stream.audioStream) - audioStream = stream.audioStream; - else - audioStream = new DummyMediaStream("dummyAudio"); - - if (stream && stream.videoStream) - videoStream = stream.videoStream; - else - videoStream = new DummyMediaStream("dummyVideo"); - } - - var audioMuted = (usageOptions && usageOptions.audio === false), - videoMuted = (usageOptions && usageOptions.video === false); - - var audioGUM = (!usageOptions || usageOptions.audio !== false), - videoGUM = (!usageOptions || usageOptions.video !== false); - - - this.service.createLocalStream(audioStream, "audio", null, null, - audioMuted, audioGUM); - - this.service.createLocalStream(videoStream, "video", null, 'camera', - videoMuted, videoGUM); -}; - -function DummyMediaStream(id) { - this.id = id; - this.label = id; - this.stop = function() { }; - this.getAudioTracks = function() { return []; }; - this.getVideoTracks = function() { return []; }; -} - -RTCUtils.prototype.createStream = function(stream, isVideo) { - var newStream = null; - if (window.webkitMediaStream) { - newStream = new webkitMediaStream(); - if (newStream) { - var tracks = (isVideo ? stream.getVideoTracks() : stream.getAudioTracks()); - - for (var i = 0; i < tracks.length; i++) { - newStream.addTrack(tracks[i]); - } - } - - } - else { - // FIXME: this is duplicated with 'handleLocalStream' !!! - if (stream) { - newStream = stream; - } else { - newStream = - new DummyMediaStream(isVideo ? "dummyVideo" : "dummyAudio"); - } - } - - return newStream; -}; - -module.exports = RTCUtils; - -},{"../../service/RTC/Resolutions":153,"../xmpp/SDPUtil":58,"./RTCBrowserType":8,"./adapter.screenshare":10}],10:[function(require,module,exports){ -/*! adapterjs - custom version from - 2015-08-12 */ - -// 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.1'; - -// 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 +// Copyright Joyent, Inc. and other Node contributors. // -AdapterJS.onwebrtcready = AdapterJS.onwebrtcready || function(isUsingPlugin) { - // The WebRTC API is ready. - // Override me and do whatever you want here -}; +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. -// 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'; +function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; } -else if(!!navigator.platform.match(/^Win/i)) { - AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1kkS4FN'; +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +EventEmitter.defaultMaxListeners = 10; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function(n) { + if (!isNumber(n) || n < 0 || isNaN(n)) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; +}; + +EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || + (isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } + throw TypeError('Uncaught, unspecified "error" event.'); + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) + return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + handler.apply(this, args); + } + } else if (isObject(handler)) { + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; +}; + +EventEmitter.prototype.addListener = function(type, listener) { + var m; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + isFunction(listener.listener) ? + listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + var m; + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } + } + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; +}; + +// emits a 'removeListener' event iff the listener was removed +EventEmitter.prototype.removeListener = function(type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) + return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (isFunction(list.listener) && list.listener === listener)) { + delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; +}; + +EventEmitter.listenerCount = function(emitter, type) { + var ret; + if (!emitter._events || !emitter._events[type]) + ret = 0; + else if (isFunction(emitter._events[type])) + ret = 1; + else + ret = emitter._events[type].length; + return ret; +}; + +function isFunction(arg) { + return typeof arg === 'function'; } -// Unique identifier of each opened page -AdapterJS.WebRTCPlugin.pageId = Math.random().toString(36).slice(2); - -// Use this whenever you want to call the plugin. -AdapterJS.WebRTCPlugin.plugin = null; - -// Set log level for the plugin once it is ready. -// The different values are -// This is an asynchronous function that will run when the plugin is ready -AdapterJS.WebRTCPlugin.setLogLevel = null; - -// Defines webrtc's JS interface according to the plugin's implementation. -// Define plugin Browsers as WebRTC Interface. -AdapterJS.WebRTCPlugin.defineWebRTCInterface = null; - -// This function detects whether or not a plugin is installed. -// Checks if Not IE (firefox, for example), else if it's IE, -// we're running IE and do something. If not it is not supported. -AdapterJS.WebRTCPlugin.isPluginInstalled = null; - - // Lets adapter.js wait until the the document is ready before injecting the plugin -AdapterJS.WebRTCPlugin.pluginInjectionInterval = null; - -// Inject the HTML DOM object element into the page. -AdapterJS.WebRTCPlugin.injectPlugin = null; - -// States of readiness that the plugin goes through when -// being injected and stated -AdapterJS.WebRTCPlugin.PLUGIN_STATES = { - NONE : 0, // no plugin use - INITIALIZING : 1, // Detected need for plugin - INJECTING : 2, // Injecting plugin - INJECTED: 3, // Plugin element injected but not usable yet - READY: 4 // Plugin ready to be used -}; - -// Current state of the plugin. You cannot use the plugin before this is -// equal to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY -AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.NONE; - -// True is AdapterJS.onwebrtcready was already called, false otherwise -// Used to make sure AdapterJS.onwebrtcready is only called once -AdapterJS.onwebrtcreadyDone = false; - -// Log levels for the plugin. -// To be set by calling AdapterJS.WebRTCPlugin.setLogLevel -/* -Log outputs are prefixed in some cases. - INFO: Information reported by the plugin. - ERROR: Errors originating from within the plugin. - WEBRTC: Error originating from within the libWebRTC library -*/ -// From the least verbose to the most verbose -AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS = { - NONE : 'NONE', - ERROR : 'ERROR', - WARNING : 'WARNING', - INFO: 'INFO', - VERBOSE: 'VERBOSE', - SENSITIVE: 'SENSITIVE' -}; - -// Does a waiting check before proceeding to load the plugin. -AdapterJS.WebRTCPlugin.WaitForPluginReady = null; - -// This methid will use an interval to wait for the plugin to be ready. -AdapterJS.WebRTCPlugin.callWhenPluginReady = null; - -// !!!! WARNING: DO NOT OVERRIDE THIS FUNCTION. !!! -// This function will be called when plugin is ready. It sends necessary -// details to the plugin. -// The function will wait for the document to be ready and the set the -// plugin state to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY, -// indicating that it can start being requested. -// This function is not in the IE/Safari condition brackets so that -// TemPluginLoaded function might be called on Chrome/Firefox. -// This function is the only private function that is not encapsulated to -// allow the plugin method to be called. -__TemWebRTCReady0 = function () { - if (document.readyState === 'complete') { - AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; - - AdapterJS.maybeThroughWebRTCReady(); - } else { - AdapterJS.WebRTCPlugin.documentReadyInterval = setInterval(function () { - if (document.readyState === 'complete') { - // TODO: update comments, we wait for the document to be ready - clearInterval(AdapterJS.WebRTCPlugin.documentReadyInterval); - AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; - - AdapterJS.maybeThroughWebRTCReady(); - } - }, 100); - } -}; - -AdapterJS.maybeThroughWebRTCReady = function() { - if (!AdapterJS.onwebrtcreadyDone) { - AdapterJS.onwebrtcreadyDone = true; - - if (typeof(AdapterJS.onwebrtcready) === 'function') { - AdapterJS.onwebrtcready(AdapterJS.WebRTCPlugin.plugin !== null); - } - } -}; - -// Text namespace -AdapterJS.TEXT = { - PLUGIN: { - REQUIRE_INSTALLATION: 'This website requires you to install a WebRTC-enabling plugin ' + - 'to work on this browser.', - NOT_SUPPORTED: 'Your browser does not support WebRTC.', - BUTTON: 'Install Now' - }, - REFRESH: { - REQUIRE_REFRESH: 'Please refresh page', - BUTTON: 'Refresh Page' - } -}; - -// The result of ice connection states. -// - starting: Ice connection is starting. -// - checking: Ice connection is checking. -// - connected Ice connection is connected. -// - completed Ice connection is connected. -// - done Ice connection has been completed. -// - disconnected Ice connection has been disconnected. -// - failed Ice connection has failed. -// - closed Ice connection is closed. -AdapterJS._iceConnectionStates = { - starting : 'starting', - checking : 'checking', - connected : 'connected', - completed : 'connected', - done : 'completed', - disconnected : 'disconnected', - failed : 'failed', - closed : 'closed' -}; - -//The IceConnection states that has been fired for each peer. -AdapterJS._iceConnectionFiredStates = []; - - -// Check if WebRTC Interface is defined. -AdapterJS.isDefined = null; - -// This function helps to retrieve the webrtc detected browser information. -// This sets: -// - webrtcDetectedBrowser: The browser agent name. -// - webrtcDetectedVersion: The browser version. -// - webrtcDetectedType: The types of webRTC support. -// - 'moz': Mozilla implementation of webRTC. -// - 'webkit': WebKit implementation of webRTC. -// - 'plugin': Using the plugin implementation. -AdapterJS.parseWebrtcDetectedBrowser = function () { - var hasMatch, checkMatch = navigator.userAgent.match( - /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; - if (/trident/i.test(checkMatch[1])) { - hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; - webrtcDetectedBrowser = 'IE'; - webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); - } else if (checkMatch[1] === 'Chrome') { - hasMatch = navigator.userAgent.match(/\bOPR\/(\d+)/); - if (hasMatch !== null) { - webrtcDetectedBrowser = 'opera'; - webrtcDetectedVersion = parseInt(hasMatch[1], 10); - } - } - if (navigator.userAgent.indexOf('Safari')) { - if (typeof InstallTrigger !== 'undefined') { - webrtcDetectedBrowser = 'firefox'; - } else if (/*@cc_on!@*/ false || !!document.documentMode) { - webrtcDetectedBrowser = 'IE'; - } else if ( - Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) { - webrtcDetectedBrowser = 'safari'; - } else if (!!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0) { - webrtcDetectedBrowser = 'opera'; - } else if (!!window.chrome) { - webrtcDetectedBrowser = 'chrome'; - } - } - if (!webrtcDetectedBrowser) { - webrtcDetectedVersion = checkMatch[1]; - } - if (!webrtcDetectedVersion) { - try { - checkMatch = (checkMatch[2]) ? [checkMatch[1], checkMatch[2]] : - [navigator.appName, navigator.appVersion, '-?']; - if ((hasMatch = navigator.userAgent.match(/version\/(\d+)/i)) !== null) { - checkMatch.splice(1, 1, hasMatch[1]); - } - webrtcDetectedVersion = parseInt(checkMatch[1], 10); - } catch (error) { } - } -}; - -// To fix configuration as some browsers does not support -// the 'urls' attribute. -AdapterJS.maybeFixConfiguration = function (pcConfig) { - if (pcConfig === null) { - return; - } - for (var i = 0; i < pcConfig.iceServers.length; i++) { - if (pcConfig.iceServers[i].hasOwnProperty('urls')) { - pcConfig.iceServers[i].url = pcConfig.iceServers[i].urls; - delete pcConfig.iceServers[i].urls; - } - } -}; - -AdapterJS.addEvent = function(elem, evnt, func) { - if (elem.addEventListener) { // W3C DOM - elem.addEventListener(evnt, func, false); - } else if (elem.attachEvent) {// OLD IE DOM - elem.attachEvent('on'+evnt, func); - } else { // No much to do - elem[evnt] = func; - } -}; - -AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNewTab, displayRefreshBar) { - // only inject once the page is ready - if (document.readyState !== 'complete') { - return; - } - - var w = window; - var i = document.createElement('iframe'); - i.style.position = 'fixed'; - i.style.top = '-41px'; - i.style.left = 0; - i.style.right = 0; - i.style.width = '100%'; - i.style.height = '40px'; - i.style.backgroundColor = '#ffffe1'; - i.style.border = 'none'; - i.style.borderBottom = '1px solid #888888'; - i.style.zIndex = '9999999'; - if(typeof i.style.webkitTransition === 'string') { - i.style.webkitTransition = 'all .5s ease-out'; - } else if(typeof i.style.transition === 'string') { - i.style.transition = 'all .5s ease-out'; - } - document.body.appendChild(i); - c = (i.contentWindow) ? i.contentWindow : - (i.contentDocument.document) ? i.contentDocument.document : i.contentDocument; - c.document.open(); - c.document.write('' + text + ''); - if(buttonText && buttonLink) { - c.document.write(''); - c.document.close(); - - AdapterJS.addEvent(c.document.getElementById('okay'), 'click', function(e) { - if (!!displayRefreshBar) { - AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION ? - AdapterJS.TEXT.EXTENSION.REQUIRE_REFRESH : AdapterJS.TEXT.REFRESH.REQUIRE_REFRESH, - AdapterJS.TEXT.REFRESH.BUTTON, 'javascript:location.reload()'); - } - window.open(buttonLink, !!openNewTab ? '_blank' : '_top'); - - e.preventDefault(); - try { - event.cancelBubble = true; - } catch(error) { } - - var pluginInstallInterval = setInterval(function(){ - if(! isIE) { - navigator.plugins.refresh(false); - } - AdapterJS.WebRTCPlugin.isPluginInstalled( - AdapterJS.WebRTCPlugin.pluginInfo.prefix, - AdapterJS.WebRTCPlugin.pluginInfo.plugName, - function() { - clearInterval(pluginInstallInterval); - AdapterJS.WebRTCPlugin.defineWebRTCInterface(); - }, - function() { //Does nothing because not used here - }); - } , 500); - }); - - }else { - c.document.close(); - } - AdapterJS.addEvent(c.document, 'click', function() { - w.document.body.removeChild(i); - }); - setTimeout(function() { - if(typeof i.style.webkitTransform === 'string') { - i.style.webkitTransform = 'translateY(40px)'; - } else if(typeof i.style.transform === 'string') { - i.style.transform = 'translateY(40px)'; - } else { - i.style.top = '0px'; - } - }, 300); -}; - -// ----------------------------------------------------------- -// Detected webrtc implementation. Types are: -// - 'moz': Mozilla implementation of webRTC. -// - 'webkit': WebKit implementation of webRTC. -// - 'plugin': Using the plugin implementation. -webrtcDetectedType = null; - -// Detected webrtc datachannel support. Types are: -// - 'SCTP': SCTP datachannel support. -// - 'RTP': RTP datachannel support. -webrtcDetectedDCSupport = null; - -// Set the settings for creating DataChannels, MediaStream for -// Cross-browser compability. -// - This is only for SCTP based support browsers. -// the 'urls' attribute. -checkMediaDataChannelSettings = - function (peerBrowserAgent, peerBrowserVersion, callback, constraints) { - if (typeof callback !== 'function') { - return; - } - var beOfferer = true; - var isLocalFirefox = webrtcDetectedBrowser === 'firefox'; - // Nightly version does not require MozDontOfferDataChannel for interop - var isLocalFirefoxInterop = webrtcDetectedType === 'moz' && webrtcDetectedVersion > 30; - var isPeerFirefox = peerBrowserAgent === 'firefox'; - var isPeerFirefoxInterop = peerBrowserAgent === 'firefox' && - ((peerBrowserVersion) ? (peerBrowserVersion > 30) : false); - - // Resends an updated version of constraints for MozDataChannel to work - // If other userAgent is firefox and user is firefox, remove MozDataChannel - if ((isLocalFirefox && isPeerFirefox) || (isLocalFirefoxInterop)) { - try { - delete constraints.mandatory.MozDontOfferDataChannel; - } catch (error) { - console.error('Failed deleting MozDontOfferDataChannel'); - console.error(error); - } - } else if ((isLocalFirefox && !isPeerFirefox)) { - constraints.mandatory.MozDontOfferDataChannel = true; - } - if (!isLocalFirefox) { - // temporary measure to remove Moz* constraints in non Firefox browsers - for (var prop in constraints.mandatory) { - if (constraints.mandatory.hasOwnProperty(prop)) { - if (prop.indexOf('Moz') !== -1) { - delete constraints.mandatory[prop]; - } - } - } - } - // Firefox (not interopable) cannot offer DataChannel as it will cause problems to the - // interopability of the media stream - if (isLocalFirefox && !isPeerFirefox && !isLocalFirefoxInterop) { - beOfferer = false; - } - callback(beOfferer, constraints); -}; - -// Handles the differences for all browsers ice connection state output. -// - Tested outcomes are: -// - Chrome (offerer) : 'checking' > 'completed' > 'completed' -// - Chrome (answerer) : 'checking' > 'connected' -// - Firefox (offerer) : 'checking' > 'connected' -// - Firefox (answerer): 'checking' > 'connected' -checkIceConnectionState = function (peerId, iceConnectionState, callback) { - if (typeof callback !== 'function') { - console.warn('No callback specified in checkIceConnectionState. Aborted.'); - return; - } - peerId = (peerId) ? peerId : 'peer'; - - if (!AdapterJS._iceConnectionFiredStates[peerId] || - iceConnectionState === AdapterJS._iceConnectionStates.disconnected || - iceConnectionState === AdapterJS._iceConnectionStates.failed || - iceConnectionState === AdapterJS._iceConnectionStates.closed) { - AdapterJS._iceConnectionFiredStates[peerId] = []; - } - iceConnectionState = AdapterJS._iceConnectionStates[iceConnectionState]; - if (AdapterJS._iceConnectionFiredStates[peerId].indexOf(iceConnectionState) < 0) { - AdapterJS._iceConnectionFiredStates[peerId].push(iceConnectionState); - if (iceConnectionState === AdapterJS._iceConnectionStates.connected) { - setTimeout(function () { - AdapterJS._iceConnectionFiredStates[peerId] - .push(AdapterJS._iceConnectionStates.done); - callback(AdapterJS._iceConnectionStates.done); - }, 1000); - } - callback(iceConnectionState); - } - return; -}; - -// Firefox: -// - Creates iceServer from the url for Firefox. -// - Create iceServer with stun url. -// - Create iceServer with turn url. -// - Ignore the transport parameter from TURN url for FF version <=27. -// - Return null for createIceServer if transport=tcp. -// - FF 27 and above supports transport parameters in TURN url, -// - So passing in the full url to create iceServer. -// Chrome: -// - Creates iceServer from the url for Chrome M33 and earlier. -// - Create iceServer with stun url. -// - Chrome M28 & above uses below TURN format. -// Plugin: -// - Creates Ice Server for Plugin Browsers -// - If Stun - Create iceServer with stun url. -// - Else - Create iceServer with turn url -// - This is a WebRTC Function -createIceServer = null; - -// Firefox: -// - Creates IceServers for Firefox -// - Use .url for FireFox. -// - Multiple Urls support -// Chrome: -// - Creates iceServers from the urls for Chrome M34 and above. -// - .urls is supported since Chrome M34. -// - Multiple Urls support -// Plugin: -// - Creates Ice Servers for Plugin Browsers -// - Multiple Urls support -// - This is a WebRTC Function -createIceServers = null; -//------------------------------------------------------------ - -//The RTCPeerConnection object. -RTCPeerConnection = null; - -// Creates RTCSessionDescription object for Plugin Browsers -RTCSessionDescription = (typeof RTCSessionDescription === 'function') ? - RTCSessionDescription : null; - -// Creates RTCIceCandidate object for Plugin Browsers -RTCIceCandidate = (typeof RTCIceCandidate === 'function') ? - RTCIceCandidate : null; - -// Get UserMedia (only difference is the prefix). -// Code from Adam Barth. -getUserMedia = null; - -// Attach a media stream to an element. -attachMediaStream = null; - -// Re-attach a media stream to an element. -reattachMediaStream = null; - - -// Detected browser agent name. Types are: -// - 'firefox': Firefox browser. -// - 'chrome': Chrome browser. -// - 'opera': Opera browser. -// - 'safari': Safari browser. -// - 'IE' - Internet Explorer browser. -webrtcDetectedBrowser = null; - -// Detected browser version. -webrtcDetectedVersion = null; - -// Check for browser types and react accordingly -if (navigator.mozGetUserMedia) { - webrtcDetectedBrowser = 'firefox'; - webrtcDetectedVersion = parseInt(navigator - .userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); - webrtcDetectedType = 'moz'; - webrtcDetectedDCSupport = 'SCTP'; - - RTCPeerConnection = function (pcConfig, pcConstraints) { - AdapterJS.maybeFixConfiguration(pcConfig); - return new mozRTCPeerConnection(pcConfig, pcConstraints); - }; - - // The RTCSessionDescription object. - RTCSessionDescription = mozRTCSessionDescription; - window.RTCSessionDescription = RTCSessionDescription; - - // The RTCIceCandidate object. - RTCIceCandidate = mozRTCIceCandidate; - window.RTCIceCandidate = RTCIceCandidate; - - window.getUserMedia = navigator.mozGetUserMedia.bind(navigator); - navigator.getUserMedia = window.getUserMedia; - - // Shim for MediaStreamTrack.getSources. - MediaStreamTrack.getSources = function(successCb) { - setTimeout(function() { - var infos = [ - { kind: 'audio', id: 'default', label:'', facing:'' }, - { kind: 'video', id: 'default', label:'', facing:'' } - ]; - successCb(infos); - }, 0); - }; - - createIceServer = function (url, username, password) { - var iceServer = null; - var url_parts = url.split(':'); - if (url_parts[0].indexOf('stun') === 0) { - iceServer = { url : url }; - } else if (url_parts[0].indexOf('turn') === 0) { - if (webrtcDetectedVersion < 27) { - var turn_url_parts = url.split('?'); - if (turn_url_parts.length === 1 || - turn_url_parts[1].indexOf('transport=udp') === 0) { - iceServer = { - url : turn_url_parts[0], - credential : password, - username : username - }; - } - } else { - iceServer = { - url : url, - credential : password, - username : username - }; - } - } - return iceServer; - }; - - createIceServers = function (urls, username, password) { - var iceServers = []; - for (i = 0; i < urls.length; i++) { - var iceServer = createIceServer(urls[i], username, password); - if (iceServer !== null) { - iceServers.push(iceServer); - } - } - return iceServers; - }; - - attachMediaStream = function (element, stream) { - element.mozSrcObject = stream; - if (stream !== null) - element.play(); - - return element; - }; - - reattachMediaStream = function (to, from) { - to.mozSrcObject = from.mozSrcObject; - to.play(); - return to; - }; - - MediaStreamTrack.getSources = MediaStreamTrack.getSources || function (callback) { - if (!callback) { - throw new TypeError('Failed to execute \'getSources\' on \'MediaStreamTrack\'' + - ': 1 argument required, but only 0 present.'); - } - return callback([]); - }; - - // Fake get{Video,Audio}Tracks - if (!MediaStream.prototype.getVideoTracks) { - MediaStream.prototype.getVideoTracks = function () { - return []; - }; - } - if (!MediaStream.prototype.getAudioTracks) { - MediaStream.prototype.getAudioTracks = function () { - return []; - }; - } - - AdapterJS.maybeThroughWebRTCReady(); -} else if (navigator.webkitGetUserMedia) { - webrtcDetectedBrowser = 'chrome'; - webrtcDetectedType = 'webkit'; - webrtcDetectedVersion = parseInt(navigator - .userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10); - // check if browser is opera 20+ - var checkIfOpera = navigator.userAgent.match(/\bOPR\/(\d+)/); - if (checkIfOpera !== null) { - webrtcDetectedBrowser = 'opera'; - webrtcDetectedVersion = parseInt(checkIfOpera[1], 10); - } - // check browser datachannel support - if ((webrtcDetectedBrowser === 'chrome' && webrtcDetectedVersion >= 31) || - (webrtcDetectedBrowser === 'opera' && webrtcDetectedVersion >= 20)) { - webrtcDetectedDCSupport = 'SCTP'; - } else if (webrtcDetectedBrowser === 'chrome' && webrtcDetectedVersion < 30 && - webrtcDetectedVersion > 24) { - webrtcDetectedDCSupport = 'RTP'; - } else { - webrtcDetectedDCSupport = ''; - } - - createIceServer = function (url, username, password) { - var iceServer = null; - var url_parts = url.split(':'); - if (url_parts[0].indexOf('stun') === 0) { - iceServer = { 'url' : url }; - } else if (url_parts[0].indexOf('turn') === 0) { - iceServer = { - 'url' : url, - 'credential' : password, - 'username' : username - }; - } - return iceServer; - }; - - createIceServers = function (urls, username, password) { - var iceServers = []; - if (webrtcDetectedVersion >= 34) { - iceServers = { - 'urls' : urls, - 'credential' : password, - 'username' : username - }; - } else { - for (i = 0; i < urls.length; i++) { - var iceServer = createIceServer(urls[i], username, password); - if (iceServer !== null) { - iceServers.push(iceServer); - } - } - } - return iceServers; - }; - - RTCPeerConnection = function (pcConfig, pcConstraints) { - if (webrtcDetectedVersion < 34) { - AdapterJS.maybeFixConfiguration(pcConfig); - } - return new webkitRTCPeerConnection(pcConfig, pcConstraints); - }; - - window.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); - navigator.getUserMedia = window.getUserMedia; - - attachMediaStream = function (element, stream) { - if (typeof element.srcObject !== 'undefined') { - element.srcObject = stream; - } else if (typeof element.mozSrcObject !== 'undefined') { - element.mozSrcObject = stream; - } else if (typeof element.src !== 'undefined') { - element.src = (stream === null ? '' : URL.createObjectURL(stream)); - } else { - console.log('Error attaching stream to element.'); - } - return element; - }; - - reattachMediaStream = function (to, from) { - to.src = from.src; - return to; - }; - - AdapterJS.maybeThroughWebRTCReady(); -} else { // TRY TO USE PLUGIN - // IE 9 is not offering an implementation of console.log until you open a console - if (typeof console !== 'object' || typeof console.log !== 'function') { - /* jshint -W020 */ - console = {} || console; - // Implemented based on console specs from MDN - // You may override these functions - console.log = function (arg) {}; - console.info = function (arg) {}; - console.error = function (arg) {}; - console.dir = function (arg) {}; - console.exception = function (arg) {}; - console.trace = function (arg) {}; - console.warn = function (arg) {}; - console.count = function (arg) {}; - console.debug = function (arg) {}; - console.count = function (arg) {}; - console.time = function (arg) {}; - console.timeEnd = function (arg) {}; - console.group = function (arg) {}; - console.groupCollapsed = function (arg) {}; - console.groupEnd = function (arg) {}; - /* jshint +W020 */ - } - webrtcDetectedType = 'plugin'; - webrtcDetectedDCSupport = 'plugin'; - AdapterJS.parseWebrtcDetectedBrowser(); - isIE = webrtcDetectedBrowser === 'IE'; - - /* jshint -W035 */ - AdapterJS.WebRTCPlugin.WaitForPluginReady = function() { - while (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { - /* empty because it needs to prevent the function from running. */ - } - }; - /* jshint +W035 */ - - AdapterJS.WebRTCPlugin.callWhenPluginReady = function (callback) { - if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { - // Call immediately if possible - // Once the plugin is set, the code will always take this path - callback(); - } else { - // otherwise start a 100ms interval - var checkPluginReadyState = setInterval(function () { - if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { - clearInterval(checkPluginReadyState); - callback(); - } - }, 100); - } - }; - - AdapterJS.WebRTCPlugin.setLogLevel = function(logLevel) { - AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { - AdapterJS.WebRTCPlugin.plugin.setLogLevel(logLevel); - }); - }; - - AdapterJS.WebRTCPlugin.injectPlugin = function () { - // only inject once the page is ready - if (document.readyState !== 'complete') { - return; - } - - // Prevent multiple injections - if (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING) { - return; - } - - AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTING; - - if (webrtcDetectedBrowser === 'IE' && webrtcDetectedVersion <= 10) { - var frag = document.createDocumentFragment(); - AdapterJS.WebRTCPlugin.plugin = document.createElement('div'); - AdapterJS.WebRTCPlugin.plugin.innerHTML = '' + - ' ' + - ' ' + - ' ' + - '' + - // uncomment to be able to use virtual cams - (AdapterJS.options.getAllCams ? '':'') + - - ''; - while (AdapterJS.WebRTCPlugin.plugin.firstChild) { - frag.appendChild(AdapterJS.WebRTCPlugin.plugin.firstChild); - } - document.body.appendChild(frag); - - // Need to re-fetch the plugin - AdapterJS.WebRTCPlugin.plugin = - document.getElementById(AdapterJS.WebRTCPlugin.pluginInfo.pluginId); - } else { - // Load Plugin - AdapterJS.WebRTCPlugin.plugin = document.createElement('object'); - AdapterJS.WebRTCPlugin.plugin.id = - AdapterJS.WebRTCPlugin.pluginInfo.pluginId; - // IE will only start the plugin if it's ACTUALLY visible - if (isIE) { - AdapterJS.WebRTCPlugin.plugin.width = '1px'; - AdapterJS.WebRTCPlugin.plugin.height = '1px'; - } else { // The size of the plugin on Safari should be 0x0px - // so that the autorisation prompt is at the top - AdapterJS.WebRTCPlugin.plugin.width = '0px'; - AdapterJS.WebRTCPlugin.plugin.height = '0px'; - } - AdapterJS.WebRTCPlugin.plugin.type = AdapterJS.WebRTCPlugin.pluginInfo.type; - AdapterJS.WebRTCPlugin.plugin.innerHTML = '' + - '' + - ' ' + - (AdapterJS.options.getAllCams ? '':'') + - ''; - document.body.appendChild(AdapterJS.WebRTCPlugin.plugin); - } - - - AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTED; - }; - - AdapterJS.WebRTCPlugin.isPluginInstalled = - function (comName, plugName, installedCb, notInstalledCb) { - if (!isIE) { - var pluginArray = navigator.plugins; - for (var i = 0; i < pluginArray.length; i++) { - if (pluginArray[i].name.indexOf(plugName) >= 0) { - installedCb(); - return; - } - } - notInstalledCb(); - } else { - try { - var axo = new ActiveXObject(comName + '.' + plugName); - } catch (e) { - notInstalledCb(); - return; - } - installedCb(); - } - }; - - AdapterJS.WebRTCPlugin.defineWebRTCInterface = function () { - if (AdapterJS.WebRTCPlugin.pluginState === - AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { - console.error("WebRTC interface has been defined already"); - return; - } - AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING; - - AdapterJS.isDefined = function (variable) { - return variable !== null && variable !== undefined; - }; - - createIceServer = function (url, username, password) { - var iceServer = null; - var url_parts = url.split(':'); - if (url_parts[0].indexOf('stun') === 0) { - iceServer = { - 'url' : url, - 'hasCredentials' : false - }; - } else if (url_parts[0].indexOf('turn') === 0) { - iceServer = { - 'url' : url, - 'hasCredentials' : true, - 'credential' : password, - 'username' : username - }; - } - return iceServer; - }; - - createIceServers = function (urls, username, password) { - var iceServers = []; - for (var i = 0; i < urls.length; ++i) { - iceServers.push(createIceServer(urls[i], username, password)); - } - return iceServers; - }; - - RTCSessionDescription = function (info) { - AdapterJS.WebRTCPlugin.WaitForPluginReady(); - return AdapterJS.WebRTCPlugin.plugin. - ConstructSessionDescription(info.type, info.sdp); - }; - - RTCPeerConnection = function (servers, constraints) { - var iceServers = null; - if (servers) { - iceServers = servers.iceServers; - for (var i = 0; i < iceServers.length; i++) { - if (iceServers[i].urls && !iceServers[i].url) { - iceServers[i].url = iceServers[i].urls; - } - iceServers[i].hasCredentials = AdapterJS. - isDefined(iceServers[i].username) && - AdapterJS.isDefined(iceServers[i].credential); - } - } - var mandatory = (constraints && constraints.mandatory) ? - constraints.mandatory : null; - var optional = (constraints && constraints.optional) ? - constraints.optional : null; - - AdapterJS.WebRTCPlugin.WaitForPluginReady(); - return AdapterJS.WebRTCPlugin.plugin. - PeerConnection(AdapterJS.WebRTCPlugin.pageId, - iceServers, mandatory, optional); - }; - - MediaStreamTrack = {}; - MediaStreamTrack.getSources = function (callback) { - AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { - AdapterJS.WebRTCPlugin.plugin.GetSources(callback); - }); - }; - - window.getUserMedia = function (constraints, successCallback, failureCallback) { - constraints.audio = constraints.audio || false; - constraints.video = constraints.video || false; - - AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { - AdapterJS.WebRTCPlugin.plugin. - getUserMedia(constraints, successCallback, failureCallback); - }); - }; - window.navigator.getUserMedia = window.getUserMedia; - - attachMediaStream = function (element, stream) { - if (!element || !element.parentNode) { - return; - } - - var streamId - if (stream === null) { - streamId = ''; - } - else { - stream.enableSoundTracks(true); - 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 isNumber(arg) { + return typeof arg === 'number'; } +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +function isUndefined(arg) { + return arg === void 0; +} -(function () { +},{}],2:[function(require,module,exports){ +// shim for using process in browser - 'use strict'; +var process = module.exports = {}; +var queue = []; +var draining = false; - 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); +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); + } +}; - iframe.contentWindow.postMessage(object, '*'); - }; - } -})(); +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":70}],4:[function(require,module,exports){ +/* global APP */ +/** + * Implements API class that communicates with external api class + * and provides interface to access Jitsi Meet features by external + * applications that embed Jitsi Meet + */ + +var XMPPEvents = require("../../service/xmpp/XMPPEvents"); + +/** + * List of the available commands. + * @type {{ + * displayName: inputDisplayNameHandler, + * toggleAudio: toggleAudio, + * toggleVideo: toggleVideo, + * toggleFilmStrip: toggleFilmStrip, + * toggleChat: toggleChat, + * toggleContactList: toggleContactList + * }} + */ +var commands = {}; + +function initCommands() { + commands = { + displayName: APP.UI.inputDisplayNameHandler, + toggleAudio: APP.UI.toggleAudio, + toggleVideo: APP.UI.toggleVideo, + toggleFilmStrip: APP.UI.toggleFilmStrip, + toggleChat: APP.UI.toggleChat, + toggleContactList: APP.UI.toggleContactList + }; +} + + +/** + * Maps the supported events and their status + * (true it the event is enabled and false if it is disabled) + * @type {{ + * incomingMessage: boolean, + * outgoingMessage: boolean, + * displayNameChange: boolean, + * participantJoined: boolean, + * participantLeft: boolean + * }} + */ +var events = { + incomingMessage: false, + outgoingMessage:false, + displayNameChange: false, + participantJoined: false, + participantLeft: false +}; + +var displayName = {}; + +/** + * Processes commands from external application. + * @param message the object with the command + */ +function processCommand(message) { + if (message.action != "execute") { + console.error("Unknown action of the message"); + return; + } + for (var key in message) { + if(commands[key]) + commands[key].apply(null, message[key]); + } +} + +/** + * Processes events objects from external applications + * @param event the event + */ +function processEvent(event) { + if (!event.action) { + console.error("Event with no action is received."); + return; + } + + var i = 0; + switch(event.action) { + case "add": + for (; i < event.events.length; i++) { + events[event.events[i]] = true; + } + break; + case "remove": + for (; i < event.events.length; i++) { + events[event.events[i]] = false; + } + break; + default: + console.error("Unknown action for event."); + } +} + +/** + * Sends message to the external application. + * @param object + */ +function sendMessage(object) { + window.parent.postMessage(JSON.stringify(object), "*"); +} + +/** + * Processes a message event from the external application + * @param event the message event + */ +function processMessage(event) { + var message; + try { + message = JSON.parse(event.data); + } catch (e) {} + + if(!message.type) + return; + switch (message.type) { + case "command": + processCommand(message); + break; + case "event": + processEvent(message); + break; + default: + console.error("Unknown type of the message"); + return; + } +} + +function setupListeners() { + APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, function (from) { + API.triggerEvent("participantJoined", {jid: from}); + }); + APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, function (from, nick, txt, myjid, stamp) { + if (from != myjid) + API.triggerEvent("incomingMessage", + {"from": from, "nick": nick, "message": txt, "stamp": stamp}); + }); + APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, function (jid) { + API.triggerEvent("participantLeft", {jid: jid}); + }); + APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, function (jid, newDisplayName) { + var name = displayName[jid]; + if(!name || name != newDisplayName) { + API.triggerEvent("displayNameChange", {jid: jid, displayname: newDisplayName}); + displayName[jid] = newDisplayName; + } + }); + APP.xmpp.addListener(XMPPEvents.SENDING_CHAT_MESSAGE, function (body) { + APP.API.triggerEvent("outgoingMessage", {"message": body}); + }); +} + +var API = { + /** + * Check whether the API should be enabled or not. + * @returns {boolean} + */ + isEnabled: function () { + var hash = location.hash; + if (hash && hash.indexOf("external") > -1 && window.postMessage) + return true; + return false; + }, + /** + * Initializes the APIConnector. Setups message event listeners that will + * receive information from external applications that embed Jitsi Meet. + * It also sends a message to the external application that APIConnector + * is initialized. + */ + init: function () { + initCommands(); + if (window.addEventListener) { + window.addEventListener('message', + processMessage, false); + } + else { + window.attachEvent('onmessage', processMessage); + } + sendMessage({type: "system", loaded: true}); + setupListeners(); + }, + /** + * Checks whether the event is enabled ot not. + * @param name the name of the event. + * @returns {*} + */ + isEventEnabled: function (name) { + return events[name]; + }, + + /** + * Sends event object to the external application that has been subscribed + * for that event. + * @param name the name event + * @param object data associated with the event + */ + triggerEvent: function (name, object) { + if(this.isEnabled() && this.isEventEnabled(name)) + sendMessage({ + type: "event", action: "result", event: name, result: object}); + }, + + /** + * Removes the listeners. + */ + dispose: function () { + if(window.removeEventListener) { + window.removeEventListener("message", + processMessage, false); + } + else { + window.detachEvent('onmessage', processMessage); + } + } +}; + +module.exports = API; +},{"../../service/xmpp/XMPPEvents":166}],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 config, APP, Strophe */ + +// cache datachannels to avoid garbage collection +// https://code.google.com/p/chromium/issues/detail?id=405545 +var RTCEvents = require("../../service/RTC/RTCEvents"); + +var _dataChannels = []; +var eventEmitter = null; + + +var DataChannels = { + /** + * Callback triggered by PeerConnection when new data channel is opened + * on the bridge. + * @param event the event info object. + */ + onDataChannel: function (event) { + var dataChannel = event.channel; + + dataChannel.onopen = function () { + console.info("Data channel opened by the Videobridge!", dataChannel); + + // Code sample for sending string and/or binary data + // Sends String message to the bridge + //dataChannel.send("Hello bridge!"); + // Sends 12 bytes binary message to the bridge + //dataChannel.send(new ArrayBuffer(12)); + + eventEmitter.emit(RTCEvents.DATA_CHANNEL_OPEN); + }; + + dataChannel.onerror = function (error) { + console.error("Data Channel Error:", error, dataChannel); + }; + + dataChannel.onmessage = function (event) { + var data = event.data; + // JSON + var obj; + + try { + obj = JSON.parse(data); + } + catch (e) { + console.error( + "Failed to parse data channel message as JSON: ", + data, + dataChannel); + } + if (('undefined' !== typeof(obj)) && (null !== obj)) { + var colibriClass = obj.colibriClass; + + if ("DominantSpeakerEndpointChangeEvent" === colibriClass) { + // Endpoint ID from the Videobridge. + var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint; + + console.info( + "Data channel new dominant speaker event: ", + dominantSpeakerEndpoint); + eventEmitter.emit(RTCEvents.DOMINANTSPEAKER_CHANGED, dominantSpeakerEndpoint); + } + else if ("InLastNChangeEvent" === colibriClass) { + var oldValue = obj.oldValue; + var newValue = obj.newValue; + // Make sure that oldValue and newValue are of type boolean. + var type; + + if ((type = typeof oldValue) !== 'boolean') { + if (type === 'string') { + oldValue = (oldValue == "true"); + } else { + oldValue = new Boolean(oldValue).valueOf(); + } + } + if ((type = typeof newValue) !== 'boolean') { + if (type === 'string') { + newValue = (newValue == "true"); + } else { + newValue = new Boolean(newValue).valueOf(); + } + } + + eventEmitter.emit(RTCEvents.LASTN_CHANGED, oldValue, newValue); + } + else if ("LastNEndpointsChangeEvent" === colibriClass) { + // The new/latest list of last-n endpoint IDs. + var lastNEndpoints = obj.lastNEndpoints; + // The list of endpoint IDs which are entering the list of + // last-n at this time i.e. were not in the old list of last-n + // endpoint IDs. + var endpointsEnteringLastN = obj.endpointsEnteringLastN; + + console.log( + "Data channel new last-n event: ", + lastNEndpoints, endpointsEnteringLastN, obj); + eventEmitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED, + lastNEndpoints, endpointsEnteringLastN, obj); + } + else { + console.debug("Data channel JSON-formatted message: ", obj); + } + } + }; + + dataChannel.onclose = function () { + console.info("The Data Channel closed", dataChannel); + var idx = _dataChannels.indexOf(dataChannel); + if (idx > -1) + _dataChannels = _dataChannels.splice(idx, 1); + }; + _dataChannels.push(dataChannel); + }, + + /** + * Binds "ondatachannel" event listener to given PeerConnection instance. + * @param peerConnection WebRTC peer connection instance. + */ + init: function (peerConnection, emitter) { + if(!config.openSctp) + return; + + peerConnection.ondatachannel = this.onDataChannel; + eventEmitter = emitter; + + // Sample code for opening new data channel from Jitsi Meet to the bridge. + // Although it's not a requirement to open separate channels from both bridge + // and peer as single channel can be used for sending and receiving data. + // So either channel opened by the bridge or the one opened here is enough + // for communication with the bridge. + /*var dataChannelOptions = + { + reliable: true + }; + var dataChannel + = peerConnection.createDataChannel("myChannel", dataChannelOptions); + + // Can be used only when is in open state + dataChannel.onopen = function () + { + dataChannel.send("My channel !!!"); + }; + dataChannel.onmessage = function (event) + { + var msgData = event.data; + console.info("Got My Data Channel Message:", msgData, dataChannel); + };*/ + }, + handleSelectedEndpointEvent: onSelectedEndpointChanged, + handlePinnedEndpointEvent: onPinnedEndpointChanged +}; + +function onSelectedEndpointChanged(userResource) { + console.log('selected endpoint changed: ', userResource); + if (_dataChannels && _dataChannels.length != 0) { + _dataChannels.some(function (dataChannel) { + if (dataChannel.readyState == 'open') { + console.log('sending selected endpoint changed ' + + 'notification to the bridge: ', userResource); + dataChannel.send(JSON.stringify({ + 'colibriClass': 'SelectedEndpointChangedEvent', + 'selectedEndpoint': + (!userResource || userResource === null)? + null : userResource + })); + + return true; + } + }); + } +} + +function onPinnedEndpointChanged(userResource) { + console.log('pinned endpoint changed: ', userResource); + if (_dataChannels && _dataChannels.length != 0) { + _dataChannels.some(function (dataChannel) { + if (dataChannel.readyState == 'open') { + dataChannel.send(JSON.stringify({ + 'colibriClass': 'PinnedEndpointChangedEvent', + 'pinnedEndpoint': + (!userResource || userResource == null)? + null : userResource + })); + + return true; + } + }); + } +} + +module.exports = DataChannels; + + +},{"../../service/RTC/RTCEvents":157}],7:[function(require,module,exports){ +/* global APP */ +var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); +var RTCEvents = require("../../service/RTC/RTCEvents"); +var RTCBrowserType = require("./RTCBrowserType"); + +/** + * This implements 'onended' callback normally fired by WebRTC after the stream + * is stopped. There is no such behaviour yet in FF, so we have to add it. + * @param stream original WebRTC stream object to which 'onended' handling + * will be added. + */ +function implementOnEndedHandling(stream) { + var originalStop = stream.stop; + stream.stop = function () { + originalStop.apply(stream); + if (!stream.ended) { + stream.ended = true; + stream.onended(); + } + }; +} + +function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) { + this.stream = stream; + this.eventEmitter = eventEmitter; + this.type = type; + this.videoType = videoType; + this.isGUMStream = true; + if(isGUMStream === false) + this.isGUMStream = isGUMStream; + var self = this; + if(type == "audio") { + this.getTracks = function () { + return self.stream.getAudioTracks(); + }; + } else { + this.getTracks = function () { + return self.stream.getVideoTracks(); + }; + } + + this.stream.onended = function () { + self.streamEnded(); + }; + if (RTCBrowserType.isFirefox()) { + implementOnEndedHandling(this.stream); + } +} + +LocalStream.prototype.streamEnded = function () { + this.eventEmitter.emit(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, this); +}; + +LocalStream.prototype.getOriginalStream = function() +{ + return this.stream; +}; + +LocalStream.prototype.isAudioStream = function () { + return this.type === "audio"; +}; + +LocalStream.prototype.setMute = function (mute) +{ + var isAudio = this.isAudioStream(); + var eventType = isAudio ? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE; + + if ((window.location.protocol != "https:" && this.isGUMStream) || + (isAudio && this.isGUMStream) || this.videoType === "screen" || + // FIXME FF does not support 'removeStream' method used to mute + RTCBrowserType.isFirefox()) { + + var tracks = this.getTracks(); + for (var idx = 0; idx < tracks.length; idx++) { + tracks[idx].enabled = !mute; + } + this.eventEmitter.emit(eventType, mute); + } else { + if (mute) { + APP.xmpp.removeStream(this.stream); + this.stream.stop(); + this.eventEmitter.emit(eventType, true); + } else { + var self = this; + APP.RTC.rtcUtils.obtainAudioAndVideoPermissions( + (this.isAudioStream() ? ["audio"] : ["video"]), + function (stream) { + if (isAudio) { + APP.RTC.changeLocalAudio(stream, + function () { + self.eventEmitter.emit(eventType, false); + }); + } else { + APP.RTC.changeLocalVideo(stream, false, + function () { + self.eventEmitter.emit(eventType, false); + }); + } + }); + } + } +}; + +LocalStream.prototype.isMuted = function () { + var tracks = []; + if (this.isAudioStream()) { + tracks = this.stream.getAudioTracks(); + } else { + if (this.stream.ended) + return true; + tracks = this.stream.getVideoTracks(); + } + for (var idx = 0; idx < tracks.length; idx++) { + if(tracks[idx].enabled) + return false; + } + return true; +}; + +LocalStream.prototype.getId = function () { + return this.stream.getTracks()[0].id; +}; + +module.exports = LocalStream; + +},{"../../service/RTC/RTCEvents":157,"../../service/RTC/StreamEventTypes.js":159,"./RTCBrowserType":10}],8:[function(require,module,exports){ +var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); + +/** + * Creates a MediaStream object for the given data, session id and ssrc. + * It is a wrapper class for the MediaStream. + * + * @param data the data object from which we obtain the stream, + * the peerjid, etc. + * @param sid the session id + * @param ssrc the ssrc corresponding to this MediaStream + * + * @constructor + */ +function MediaStream(data, sid, ssrc, browser, eventEmitter) { + + // XXX(gp) to minimize headaches in the future, we should build our + // abstractions around tracks and not streams. ORTC is track based API. + // Mozilla expects m-lines to represent media tracks. + // + // Practically, what I'm saying is that we should have a MediaTrack class + // and not a MediaStream class. + // + // Also, we should be able to associate multiple SSRCs with a MediaTrack as + // a track might have an associated RTX and FEC sources. + + this.sid = sid; + this.stream = data.stream; + this.peerjid = data.peerjid; + this.videoType = data.videoType; + this.ssrc = ssrc; + this.type = (this.stream.getVideoTracks().length > 0)? + MediaStreamType.VIDEO_TYPE : MediaStreamType.AUDIO_TYPE; + this.muted = false; + this.eventEmitter = eventEmitter; +} + + +MediaStream.prototype.getOriginalStream = function() { + return this.stream; +}; + +MediaStream.prototype.setMute = function (value) { + this.stream.muted = value; + this.muted = value; +}; + +module.exports = MediaStream; + +},{"../../service/RTC/MediaStreamTypes":156}],9:[function(require,module,exports){ +/* global APP */ +var EventEmitter = require("events"); +var RTCBrowserType = require("./RTCBrowserType"); +var RTCUtils = require("./RTCUtils.js"); +var LocalStream = require("./LocalStream.js"); +var DataChannels = require("./DataChannels"); +var MediaStream = require("./MediaStream.js"); +var DesktopSharingEventTypes + = require("../../service/desktopsharing/DesktopSharingEventTypes"); +var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); +var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); +var RTCEvents = require("../../service/RTC/RTCEvents.js"); +var XMPPEvents = require("../../service/xmpp/XMPPEvents"); +var UIEvents = require("../../service/UI/UIEvents"); + +var eventEmitter = new EventEmitter(); + + +function getMediaStreamUsage() +{ + var result = { + audio: true, + video: true + }; + + /** There are some issues with the desktop sharing + * when this property is enabled. + * WARNING: We must change the implementation to start video/audio if we + * receive from the focus that the peer is not muted. + + var isSecureConnection = window.location.protocol == "https:"; + + if(config.disableEarlyMediaPermissionRequests || !isSecureConnection) + { + result = { + audio: false, + video: false + }; + + } + **/ + + return result; +} + +var RTC = { + rtcUtils: null, + devices: { + audio: true, + video: true + }, + localStreams: [], + remoteStreams: {}, + localAudio: null, + localVideo: null, + addStreamListener: function (listener, eventType) { + eventEmitter.on(eventType, listener); + }, + addListener: function (type, listener) { + eventEmitter.on(type, listener); + }, + removeStreamListener: function (listener, eventType) { + if(!(eventType instanceof StreamEventTypes)) + throw "Illegal argument"; + + eventEmitter.removeListener(eventType, listener); + }, + createLocalStream: function (stream, type, change, videoType, isMuted, isGUMStream) { + + var localStream = new LocalStream(stream, type, eventEmitter, videoType, isGUMStream); + //in firefox we have only one stream object + if(this.localStreams.length == 0 || + this.localStreams[0].getOriginalStream() != stream) + this.localStreams.push(localStream); + if(isMuted === true) + localStream.setMute(true); + + if(type == "audio") { + this.localAudio = localStream; + } else { + this.localVideo = localStream; + } + var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED; + if(change) + eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED; + + eventEmitter.emit(eventType, localStream, isMuted); + return localStream; + }, + removeLocalStream: function (stream) { + for(var i = 0; i < this.localStreams.length; i++) { + if(this.localStreams[i].getOriginalStream() === stream) { + delete this.localStreams[i]; + return; + } + } + }, + createRemoteStream: function (data, sid, thessrc) { + var remoteStream = new MediaStream(data, sid, thessrc, + RTCBrowserType.getBrowserType(), eventEmitter); + var jid = data.peerjid || APP.xmpp.myJid(); + if(!this.remoteStreams[jid]) { + this.remoteStreams[jid] = {}; + } + this.remoteStreams[jid][remoteStream.type]= remoteStream; + eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, remoteStream); + return remoteStream; + }, + getPCConstraints: function () { + return this.rtcUtils.pc_constraints; + }, + getUserMediaWithConstraints:function(um, success_callback, + failure_callback, resolution, + bandwidth, fps, desktopStream) + { + return this.rtcUtils.getUserMediaWithConstraints(um, success_callback, + failure_callback, resolution, bandwidth, fps, desktopStream); + }, + attachMediaStream: function (elSelector, stream) { + this.rtcUtils.attachMediaStream(elSelector, stream); + }, + getStreamID: function (stream) { + return this.rtcUtils.getStreamID(stream); + }, + getVideoSrc: function (element) { + return this.rtcUtils.getVideoSrc(element); + }, + setVideoSrc: function (element, src) { + this.rtcUtils.setVideoSrc(element, src); + }, + getVideoElementName: function () { + return RTCBrowserType.isTemasysPluginUsed() ? 'object' : 'video'; + }, + dispose: function() { + if (this.rtcUtils) { + this.rtcUtils = null; + } + }, + stop: function () { + this.dispose(); + }, + start: function () { + var self = this; + APP.desktopsharing.addListener( + function (stream, isUsingScreenStream, callback) { + self.changeLocalVideo(stream, isUsingScreenStream, callback); + }, DesktopSharingEventTypes.NEW_STREAM_CREATED); + APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function(event) { + DataChannels.init(event.peerconnection, eventEmitter); + }); + APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, + DataChannels.handleSelectedEndpointEvent); + APP.UI.addListener(UIEvents.PINNED_ENDPOINT, + DataChannels.handlePinnedEndpointEvent); + + // In case of IE we continue from 'onReady' callback + // passed to RTCUtils constructor. It will be invoked by Temasys plugin + // once it is initialized. + var onReady = function () { + eventEmitter.emit(RTCEvents.RTC_READY, true); + self.rtcUtils.obtainAudioAndVideoPermissions( + null, null, getMediaStreamUsage()); + }; + + this.rtcUtils = new RTCUtils(this, onReady); + + // Call onReady() if Temasys plugin is not used + if (!RTCBrowserType.isTemasysPluginUsed()) { + onReady(); + } + }, + muteRemoteVideoStream: function (jid, value) { + var stream; + + if(this.remoteStreams[jid] && + this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) { + stream = this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]; + } + + if(!stream) + return true; + + if (value != stream.muted) { + stream.setMute(value); + return true; + } + return false; + }, + switchVideoStreams: function (new_stream) { + this.localVideo.stream = new_stream; + + this.localStreams = []; + + //in firefox we have only one stream object + if (this.localAudio.getOriginalStream() != new_stream) + this.localStreams.push(this.localAudio); + this.localStreams.push(this.localVideo); + }, + changeLocalVideo: function (stream, isUsingScreenStream, callback) { + var oldStream = this.localVideo.getOriginalStream(); + var type = (isUsingScreenStream ? "screen" : "camera"); + var localCallback = callback; + if(this.localVideo.isMuted() && this.localVideo.videoType !== type) { + localCallback = function() { + APP.xmpp.setVideoMute(false, function(mute) { + eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute); + }); + + callback(); + }; + } + // FIXME: Workaround for FF/IE/Safari + if (stream && stream.videoStream) { + stream = stream.videoStream; + } + var videoStream = this.rtcUtils.createStream(stream, true); + this.localVideo = this.createLocalStream(videoStream, "video", true, type); + // Stop the stream to trigger onended event for old stream + oldStream.stop(); + + this.switchVideoStreams(videoStream, oldStream); + + APP.xmpp.switchStreams(videoStream, oldStream,localCallback); + }, + changeLocalAudio: function (stream, callback) { + var oldStream = this.localAudio.getOriginalStream(); + var newStream = this.rtcUtils.createStream(stream); + this.localAudio = this.createLocalStream(newStream, "audio", true); + // Stop the stream to trigger onended event for old stream + oldStream.stop(); + APP.xmpp.switchStreams(newStream, oldStream, callback, true); + }, + isVideoMuted: function (jid) { + if (jid === APP.xmpp.myJid()) { + var localVideo = APP.RTC.localVideo; + return (!localVideo || localVideo.isMuted()); + } else { + if (!APP.RTC.remoteStreams[jid] || + !APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) { + return null; + } + return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted; + } + }, + setVideoMute: function (mute, callback, options) { + if (!this.localVideo) + return; + + if (mute == APP.RTC.localVideo.isMuted()) + { + APP.xmpp.sendVideoInfoPresence(mute); + if (callback) + callback(mute); + } + else + { + APP.RTC.localVideo.setMute(mute); + APP.xmpp.setVideoMute( + mute, + callback, + options); + } + }, + setDeviceAvailability: function (devices) { + if(!devices) + return; + if(devices.audio === true || devices.audio === false) + this.devices.audio = devices.audio; + if(devices.video === true || devices.video === false) + this.devices.video = devices.video; + eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, this.devices); + } +}; + +module.exports = RTC; + +},{"../../service/RTC/MediaStreamTypes":156,"../../service/RTC/RTCEvents.js":157,"../../service/RTC/StreamEventTypes.js":159,"../../service/UI/UIEvents":160,"../../service/desktopsharing/DesktopSharingEventTypes":163,"../../service/xmpp/XMPPEvents":166,"./DataChannels":6,"./LocalStream.js":7,"./MediaStream.js":8,"./RTCBrowserType":10,"./RTCUtils.js":11,"events":1}],10:[function(require,module,exports){ + +var currentBrowser; + +var browserVersion; + +var isAndroid; + +var RTCBrowserType = { + + RTC_BROWSER_CHROME: "rtc_browser.chrome", + + RTC_BROWSER_OPERA: "rtc_browser.opera", + + RTC_BROWSER_FIREFOX: "rtc_browser.firefox", + + RTC_BROWSER_IEXPLORER: "rtc_browser.iexplorer", + + RTC_BROWSER_SAFARI: "rtc_browser.safari", + + getBrowserType: function () { + return currentBrowser; + }, + + isChrome: function () { + return currentBrowser === RTCBrowserType.RTC_BROWSER_CHROME; + }, + + isOpera: function () { + return currentBrowser === RTCBrowserType.RTC_BROWSER_OPERA; + }, + isFirefox: function () { + return currentBrowser === RTCBrowserType.RTC_BROWSER_FIREFOX; + }, + + isIExplorer: function () { + return currentBrowser === RTCBrowserType.RTC_BROWSER_IEXPLORER; + }, + + isSafari: function () { + return currentBrowser === RTCBrowserType.RTC_BROWSER_SAFARI; + }, + isTemasysPluginUsed: function () { + return RTCBrowserType.isIExplorer() || RTCBrowserType.isSafari(); + }, + getFirefoxVersion: function () { + return RTCBrowserType.isFirefox() ? browserVersion : null; + }, + + getChromeVersion: function () { + return RTCBrowserType.isChrome() ? browserVersion : null; + }, + + usesPlanB: function() { + return RTCBrowserType.isChrome() || RTCBrowserType.isOpera() || + RTCBrowserType.isTemasysPluginUsed(); + }, + + usesUnifiedPlan: function() { + return RTCBrowserType.isFirefox(); + }, + + /** + * Whether the browser is running on an android device. + */ + isAndroid: function() { + return isAndroid; + } + + // Add version getters for other browsers when needed +}; + +// detectOpera() must be called before detectChrome() !!! +// otherwise Opera wil be detected as Chrome +function detectChrome() { + if (navigator.webkitGetUserMedia) { + currentBrowser = RTCBrowserType.RTC_BROWSER_CHROME; + var userAgent = navigator.userAgent.toLowerCase(); + // We can assume that user agent is chrome, because it's + // enforced when 'ext' streaming method is set + var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10); + console.log("This appears to be Chrome, ver: " + ver); + return ver; + } + return null; +} + +function detectOpera() { + var userAgent = navigator.userAgent; + if (userAgent.match(/Opera|OPR/)) { + currentBrowser = RTCBrowserType.RTC_BROWSER_OPERA; + var version = userAgent.match(/(Opera|OPR) ?\/?(\d+)\.?/)[2]; + console.info("This appears to be Opera, ver: " + version); + return version; + } + return null; +} + +function detectFirefox() { + if (navigator.mozGetUserMedia) { + currentBrowser = RTCBrowserType.RTC_BROWSER_FIREFOX; + var version = parseInt( + navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); + console.log('This appears to be Firefox, ver: ' + version); + return version; + } + return null; +} + +function detectSafari() { + if ((/^((?!chrome).)*safari/i.test(navigator.userAgent))) { + currentBrowser = RTCBrowserType.RTC_BROWSER_SAFARI; + console.info("This appears to be Safari"); + // FIXME detect Safari version when needed + return 1; + } + return null; +} + +function detectIE() { + var version; + var ua = window.navigator.userAgent; + + var msie = ua.indexOf('MSIE '); + if (msie > 0) { + // IE 10 or older => return version number + version = parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); + } + + var trident = ua.indexOf('Trident/'); + if (!version && trident > 0) { + // IE 11 => return version number + var rv = ua.indexOf('rv:'); + version = parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10); + } + + var edge = ua.indexOf('Edge/'); + if (!version && edge > 0) { + // IE 12 => return version number + version = parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10); + } + + if (version) { + currentBrowser = RTCBrowserType.RTC_BROWSER_IEXPLORER; + console.info("This appears to be IExplorer, ver: " + version); + } + return version; +} + +function detectBrowser() { + var version; + var detectors = [ + detectOpera, + detectChrome, + detectFirefox, + detectIE, + detectSafari + ]; + // Try all browser detectors + for (var i = 0; i < detectors.length; i++) { + version = detectors[i](); + if (version) + return version; + } + console.error("Failed to detect browser type"); + return undefined; +} + +browserVersion = detectBrowser(); +isAndroid = navigator.userAgent.indexOf('Android') != -1; + +module.exports = RTCBrowserType; },{}],11:[function(require,module,exports){ -/* global Strophe, APP, $, config, interfaceConfig, toastr */ -var UI = {}; - -var VideoLayout = require("./videolayout/VideoLayout.js"); -var AudioLevels = require("./audio_levels/AudioLevels.js"); -var Prezi = require("./prezi/Prezi.js"); -var Etherpad = require("./etherpad/Etherpad.js"); -var Chat = require("./side_pannels/chat/Chat.js"); -var Toolbar = require("./toolbars/Toolbar"); -var ToolbarToggler = require("./toolbars/ToolbarToggler"); -var BottomToolbar = require("./toolbars/BottomToolbar"); -var ContactList = require("./side_pannels/contactlist/ContactList"); -var Avatar = require("./avatar/Avatar"); -var EventEmitter = require("events"); -var SettingsMenu = require("./side_pannels/settings/SettingsMenu"); -var Settings = require("./../settings/Settings"); -var PanelToggler = require("./side_pannels/SidePanelToggler"); -var RoomNameGenerator = require("./welcome_page/RoomnameGenerator"); -UI.messageHandler = require("./util/MessageHandler"); -var messageHandler = UI.messageHandler; -var Authentication = require("./authentication/Authentication"); -var UIUtil = require("./util/UIUtil"); -var NicknameHandler = require("./util/NicknameHandler"); -var JitsiPopover = require("./util/JitsiPopover"); -var CQEvents = require("../../service/connectionquality/CQEvents"); -var DesktopSharingEventTypes - = require("../../service/desktopsharing/DesktopSharingEventTypes"); -var RTCEvents = require("../../service/RTC/RTCEvents"); -var RTCBrowserType = require("../RTC/RTCBrowserType"); -var StreamEventTypes = require("../../service/RTC/StreamEventTypes"); -var XMPPEvents = require("../../service/xmpp/XMPPEvents"); -var UIEvents = require("../../service/UI/UIEvents"); -var MemberEvents = require("../../service/members/Events"); - -var eventEmitter = new EventEmitter(); -var roomName = null; - - -function promptDisplayName() { - var message = '

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

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

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

' + - ''; - - messageHandler.openTwoButtonDialog(null, null, null, message, - true, - "dialog.Ok", - function (e, v, m, f) {}, - null, - function (e, v, m, f) { - if (v) { - var lockKey = f.lockKey; - if (lockKey) { - Toolbar.setSharedKey(lockKey); - callback(lockKey); - } - } - }, - ':input:first' - ); -} - -/** - * The dialpad button is shown iff there is at least one member that supports - * DTMF (e.g. jigasi). - */ -function onDtmfSupportChanged(dtmfSupport) { - //TODO: enable when the UI is ready - //Toolbar.showDialPadButton(dtmfSupport); -} - -function onMucMemberJoined(jid, id, displayName) { - messageHandler.notify(displayName,'notify.somebody', - 'connected', - 'notify.connected'); - - if (!config.startAudioMuted || - config.startAudioMuted > APP.members.size()) - UIUtil.playSoundNotification('userJoined'); - - // Configure avatar - Avatar.setUserAvatar(jid, id); - - // Add Peer's container - VideoLayout.ensurePeerContainerExists(jid); -} - -function onMucPresenceStatus(jid, info) { - VideoLayout.setPresenceStatus(Strophe.getResourceFromJid(jid), info.status); -} - -function onPeerVideoTypeChanged(resourceJid, newVideoType) { - VideoLayout.onVideoTypeChanged(resourceJid, newVideoType); -} - -function onMucRoleChanged(role, displayName) { - VideoLayout.showModeratorIndicator(); - - if (role === 'moderator') { - var messageKey, messageOptions = {}; - if (!displayName) { - messageKey = "notify.grantedToUnknown"; - } - else { - messageKey = "notify.grantedTo"; - messageOptions = {to: displayName}; - } - messageHandler.notify( - displayName,'notify.somebody', - 'connected', messageKey, - messageOptions); - } -} - -function onAuthenticationRequired(intervalCallback) { - Authentication.openAuthenticationDialog( - roomName, intervalCallback, function () { - Toolbar.authenticateClicked(); - }); -} - - -function onLastNChanged(oldValue, newValue) { - if (config.muteLocalVideoIfNotInLastN) { - setVideoMute(!newValue, { 'byUser': false }); - } -} - - -UI.toggleSmileys = function () { - Chat.toggleSmileys(); -}; - -UI.getSettings = function () { - return Settings.getSettings(); -}; - -UI.toggleFilmStrip = function () { - return BottomToolbar.toggleFilmStrip(); -}; - -UI.toggleChat = function () { - return BottomToolbar.toggleChat(); -}; - -UI.toggleContactList = function () { - return BottomToolbar.toggleContactList(); -}; - -UI.inputDisplayNameHandler = function (value) { - VideoLayout.inputDisplayNameHandler(value); -}; - -UI.getLargeVideoResource = function () { - return VideoLayout.getLargeVideoResource(); -}; - -UI.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.getJingleLog(); - var metadata = {}; - metadata.time = new Date(); - metadata.url = window.location.href; - metadata.ua = navigator.userAgent; - var log = APP.xmpp.getXmppLog(); - if (log) { - metadata.xmpp = log; - } - data.metadata = metadata; - elem.href += encodeURIComponent(JSON.stringify(data, null, ' ')); - return false; -} - -UI.getRoomName = function () { - return roomName; -}; - -UI.setInitialMuteFromFocus = function (muteAudio, muteVideo) { - if (muteAudio || muteVideo) - notifyForInitialMute(); - if (muteAudio) - UI.setAudioMuted(true); - if (muteVideo) - UI.setVideoMute(true); -}; - -/** - * Mutes/unmutes the local video. - */ -UI.toggleVideo = function () { - setVideoMute(!APP.RTC.localVideo.isMuted()); -}; - -/** - * Mutes / unmutes audio for the local participant. - */ -UI.toggleAudio = function() { - UI.setAudioMuted(!APP.RTC.localAudio.isMuted()); -}; - -/** - * Sets muted audio state for the local participant. - */ -UI.setAudioMuted = function (mute, earlyMute) { - var audioMute = null; - if (earlyMute) - audioMute = function (mute, cb) { - return APP.xmpp.sendAudioInfoPresence(mute, cb); - }; - else - audioMute = function (mute, cb) { - return APP.xmpp.setAudioMute(mute, cb); - }; - if (!audioMute(mute, function () { - VideoLayout.showLocalAudioIndicator(mute); - - UIUtil.buttonClick("#toolbar_button_mute", "icon-microphone icon-mic-disabled"); - })) { - // We still click the button. - UIUtil.buttonClick("#toolbar_button_mute", "icon-microphone icon-mic-disabled"); - return; - } -}; - -UI.addListener = function (type, listener) { - eventEmitter.on(type, listener); -}; - -UI.clickOnVideo = function (videoNumber) { - var remoteVideos = $(".videocontainer:not(#mixedstream)"); - if (remoteVideos.length > videoNumber) { - remoteVideos[videoNumber].click(); - } -}; - -//Used by torture -UI.showToolbar = function () { - return ToolbarToggler.showToolbar(); -}; - -//Used by torture -UI.dockToolbar = function (isDock) { - return ToolbarToggler.dockToolbar(isDock); -}; - -UI.setVideoMuteButtonsState = function (mute) { - var video = $('#toolbar_button_camera'); - var communicativeClass = "icon-camera"; - var muteClass = "icon-camera icon-camera-disabled"; - - if (mute) { - video.removeClass(communicativeClass); - video.addClass(muteClass); - } else { - video.removeClass(muteClass); - video.addClass(communicativeClass); - } -}; - -UI.userAvatarChanged = function (resourceJid, thumbUrl, contactListUrl) { - VideoLayout.userAvatarChanged(resourceJid, thumbUrl); - ContactList.userAvatarChanged(resourceJid, contactListUrl); - if(resourceJid === APP.xmpp.myResource()) - SettingsMenu.changeAvatar(thumbUrl); -}; - -UI.setVideoMute = setVideoMute; - -module.exports = UI; - - -},{"../../service/RTC/RTCEvents":152,"../../service/RTC/StreamEventTypes":154,"../../service/UI/UIEvents":155,"../../service/connectionquality/CQEvents":157,"../../service/desktopsharing/DesktopSharingEventTypes":158,"../../service/members/Events":159,"../../service/xmpp/XMPPEvents":161,"../RTC/RTCBrowserType":8,"./../settings/Settings":47,"./audio_levels/AudioLevels.js":12,"./authentication/Authentication":14,"./avatar/Avatar":16,"./etherpad/Etherpad.js":17,"./prezi/Prezi.js":18,"./side_pannels/SidePanelToggler":20,"./side_pannels/chat/Chat.js":21,"./side_pannels/contactlist/ContactList":25,"./side_pannels/settings/SettingsMenu":26,"./toolbars/BottomToolbar":27,"./toolbars/Toolbar":28,"./toolbars/ToolbarToggler":29,"./util/JitsiPopover":30,"./util/MessageHandler":31,"./util/NicknameHandler":32,"./util/UIUtil":33,"./videolayout/VideoLayout.js":39,"./welcome_page/RoomnameGenerator":40,"./welcome_page/WelcomePage":41,"events":162}],12:[function(require,module,exports){ -/* global APP, interfaceConfig, $, Strophe */ -var CanvasUtil = require("./CanvasUtils"); - -var ASDrawContext = null; - -function initActiveSpeakerAudioLevels() { - var ASRadius = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2; - var ASCenter = (interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE + ASRadius) / 2; - - // Draw a circle. - ASDrawContext.arc(ASCenter, ASCenter, ASRadius, 0, 2 * Math.PI); - - // Add a shadow around the circle - ASDrawContext.shadowColor = interfaceConfig.SHADOW_COLOR; - ASDrawContext.shadowOffsetX = 0; - ASDrawContext.shadowOffsetY = 0; -} - -/** - * The audio Levels plugin. - */ -var AudioLevels = (function(my) { - var audioLevelCanvasCache = {}; - - my.LOCAL_LEVEL = 'local'; - - my.init = function () { - ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d'); - initActiveSpeakerAudioLevels(); - }; - - /** - * Updates the audio level canvas for the given peerJid. If the canvas - * didn't exist we create it. - */ - my.updateAudioLevelCanvas = function (peerJid, VideoLayout) { - var resourceJid = null; - var videoSpanId = null; - if (!peerJid) - videoSpanId = 'localVideoContainer'; - else { - resourceJid = Strophe.getResourceFromJid(peerJid); - - videoSpanId = 'participant_' + resourceJid; - } - - var videoSpan = document.getElementById(videoSpanId); - - if (!videoSpan) { - if (resourceJid) - console.error("No video element for jid", resourceJid); - else - console.error("No video element for local video."); - - return; - } - - var audioLevelCanvas = $('#' + videoSpanId + '>canvas'); - - var videoSpaceWidth = $('#remoteVideos').width(); - var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth); - var thumbnailWidth = thumbnailSize[0]; - var thumbnailHeight = thumbnailSize[1]; - - if (!audioLevelCanvas || audioLevelCanvas.length === 0) { - - audioLevelCanvas = document.createElement('canvas'); - audioLevelCanvas.className = "audiolevel"; - audioLevelCanvas.style.bottom = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px"; - audioLevelCanvas.style.left = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px"; - resizeAudioLevelCanvas( audioLevelCanvas, - thumbnailWidth, - thumbnailHeight); - - videoSpan.appendChild(audioLevelCanvas); - } else { - audioLevelCanvas = audioLevelCanvas.get(0); - - resizeAudioLevelCanvas( audioLevelCanvas, - thumbnailWidth, - thumbnailHeight); - } - }; - - /** - * Updates the audio level UI for the given resourceJid. - * - * @param resourceJid the resource jid indicating the video element for - * which we draw the audio level - * @param audioLevel the newAudio level to render - */ - my.updateAudioLevel = function (resourceJid, audioLevel, largeVideoResourceJid) { - drawAudioLevelCanvas(resourceJid, audioLevel); - - var videoSpanId = getVideoSpanId(resourceJid); - - var audioLevelCanvas = $('#' + videoSpanId + '>canvas').get(0); - - if (!audioLevelCanvas) - return; - - var drawContext = audioLevelCanvas.getContext('2d'); - - var canvasCache = audioLevelCanvasCache[resourceJid]; - - drawContext.clearRect (0, 0, - audioLevelCanvas.width, audioLevelCanvas.height); - drawContext.drawImage(canvasCache, 0, 0); - - if(resourceJid === AudioLevels.LOCAL_LEVEL) { - if(!APP.xmpp.myJid()) { - return; - } - resourceJid = APP.xmpp.myResource(); - } - - if(resourceJid === largeVideoResourceJid) { - window.requestAnimationFrame(function () { - AudioLevels.updateActiveSpeakerAudioLevel(audioLevel); - }); - } - }; - - my.updateActiveSpeakerAudioLevel = function(audioLevel) { - if($("#activeSpeaker").css("visibility") == "hidden" || ASDrawContext === null) - return; - - ASDrawContext.clearRect(0, 0, 300, 300); - if(audioLevel == 0) - return; - - ASDrawContext.shadowBlur = getShadowLevel(audioLevel); - - - // Fill the shape. - ASDrawContext.fill(); - }; - - /** - * Resizes the given audio level canvas to match the given thumbnail size. - */ - function resizeAudioLevelCanvas(audioLevelCanvas, - thumbnailWidth, - thumbnailHeight) { - audioLevelCanvas.width = thumbnailWidth + interfaceConfig.CANVAS_EXTRA; - audioLevelCanvas.height = thumbnailHeight + interfaceConfig.CANVAS_EXTRA; - } - - /** - * Draws the audio level canvas into the cached canvas object. - * - * @param resourceJid the resource jid indicating the video element for - * which we draw the audio level - * @param audioLevel the newAudio level to render - */ - function drawAudioLevelCanvas(resourceJid, audioLevel) { - if (!audioLevelCanvasCache[resourceJid]) { - - var videoSpanId = getVideoSpanId(resourceJid); - - var audioLevelCanvasOrig = $('#' + videoSpanId + '>canvas').get(0); - - /* - * FIXME Testing has shown that audioLevelCanvasOrig may not exist. - * In such a case, the method CanvasUtil.cloneCanvas may throw an - * error. Since audio levels are frequently updated, the errors have - * been observed to pile into the console, strain the CPU. - */ - if (audioLevelCanvasOrig) { - audioLevelCanvasCache[resourceJid] = - CanvasUtil.cloneCanvas(audioLevelCanvasOrig); - } - } - - var canvas = audioLevelCanvasCache[resourceJid]; - - if (!canvas) - return; - - var drawContext = canvas.getContext('2d'); - - drawContext.clearRect(0, 0, canvas.width, canvas.height); - - var shadowLevel = getShadowLevel(audioLevel); - - if (shadowLevel > 0) { - // drawContext, x, y, w, h, r, shadowColor, shadowLevel - CanvasUtil.drawRoundRectGlow(drawContext, - interfaceConfig.CANVAS_EXTRA / 2, interfaceConfig.CANVAS_EXTRA / 2, - canvas.width - interfaceConfig.CANVAS_EXTRA, - canvas.height - interfaceConfig.CANVAS_EXTRA, - interfaceConfig.CANVAS_RADIUS, - interfaceConfig.SHADOW_COLOR, - shadowLevel); - } - } - - /** - * Returns the shadow/glow level for the given audio level. - * - * @param audioLevel the audio level from which we determine the shadow - * level - */ - function getShadowLevel (audioLevel) { - var shadowLevel = 0; - - if (audioLevel <= 0.3) { - shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*(audioLevel/0.3)); - } - else if (audioLevel <= 0.6) { - shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3)); - } - else { - shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4)); - } - return shadowLevel; - } - - /** - * Returns the video span id corresponding to the given resourceJid or local - * user. - */ - function getVideoSpanId(resourceJid) { - var videoSpanId = null; - if (resourceJid === AudioLevels.LOCAL_LEVEL || - (APP.xmpp.myResource() && resourceJid === APP.xmpp.myResource())) - videoSpanId = 'localVideoContainer'; - else - videoSpanId = 'participant_' + resourceJid; - - return videoSpanId; - } - - /** - * Indicates that the remote video has been resized. - */ - $(document).bind('remotevideo.resized', function (event, width, height) { - var resized = false; - $('#remoteVideos>span>canvas').each(function() { - var canvas = $(this).get(0); - if (canvas.width !== width + interfaceConfig.CANVAS_EXTRA) { - canvas.width = width + interfaceConfig.CANVAS_EXTRA; - resized = true; - } - - if (canvas.heigh !== height + interfaceConfig.CANVAS_EXTRA) { - canvas.height = height + interfaceConfig.CANVAS_EXTRA; - resized = true; - } - }); - - if (resized) - Object.keys(audioLevelCanvasCache).forEach(function (resourceJid) { - audioLevelCanvasCache[resourceJid].width = - width + interfaceConfig.CANVAS_EXTRA; - audioLevelCanvasCache[resourceJid].height = - height + interfaceConfig.CANVAS_EXTRA; - }); - }); - - return my; - -})(AudioLevels || {}); - +/* global config, require, attachMediaStream, getUserMedia */ +var RTCBrowserType = require("./RTCBrowserType"); +var Resolutions = require("../../service/RTC/Resolutions"); +var AdapterJS = require("./adapter.screenshare"); +var SDPUtil = require("../xmpp/SDPUtil"); + +var currentResolution = null; + +function getPreviousResolution(resolution) { + if(!Resolutions[resolution]) + return null; + var order = Resolutions[resolution].order; + var res = null; + var resName = null; + for(var i in Resolutions) { + var tmp = Resolutions[i]; + if(res == null || (res.order < tmp.order && tmp.order < order)) { + resName = i; + res = tmp; + } + } + return resName; +} + +function setResolutionConstraints(constraints, resolution) { + var isAndroid = RTCBrowserType.isAndroid(); + + if (Resolutions[resolution]) { + constraints.video.mandatory.minWidth = Resolutions[resolution].width; + constraints.video.mandatory.minHeight = Resolutions[resolution].height; + } + else if (isAndroid) { + // FIXME can't remember if the purpose of this was to always request + // low resolution on Android ? if yes it should be moved up front + constraints.video.mandatory.minWidth = 320; + constraints.video.mandatory.minHeight = 240; + constraints.video.mandatory.maxFrameRate = 15; + } + + if (constraints.video.mandatory.minWidth) + constraints.video.mandatory.maxWidth = + constraints.video.mandatory.minWidth; + if (constraints.video.mandatory.minHeight) + constraints.video.mandatory.maxHeight = + constraints.video.mandatory.minHeight; +} + +function getConstraints(um, resolution, bandwidth, fps, desktopStream) { + var constraints = {audio: false, video: false}; + + if (um.indexOf('video') >= 0) { + // same behaviour as true + constraints.video = { mandatory: {}, optional: [] }; + + constraints.video.optional.push({ googLeakyBucket: true }); + + setResolutionConstraints(constraints, resolution); + } + if (um.indexOf('audio') >= 0) { + if (!RTCBrowserType.isFirefox()) { + // same behaviour as true + constraints.audio = { mandatory: {}, optional: []}; + // if it is good enough for hangouts... + constraints.audio.optional.push( + {googEchoCancellation: true}, + {googAutoGainControl: true}, + {googNoiseSupression: true}, + {googHighpassFilter: true}, + {googNoisesuppression2: true}, + {googEchoCancellation2: true}, + {googAutoGainControl2: true} + ); + } else { + constraints.audio = true; + } + } + if (um.indexOf('screen') >= 0) { + if (RTCBrowserType.isChrome()) { + constraints.video = { + mandatory: { + chromeMediaSource: "screen", + googLeakyBucket: true, + maxWidth: window.screen.width, + maxHeight: window.screen.height, + maxFrameRate: 3 + }, + optional: [] + }; + } else if (RTCBrowserType.isTemasysPluginUsed()) { + constraints.video = { + optional: [ + { + sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey + } + ] + }; + } else { + console.error( + "'screen' WebRTC media source is supported only in Chrome" + + " and with Temasys plugin"); + } + } + if (um.indexOf('desktop') >= 0) { + constraints.video = { + mandatory: { + chromeMediaSource: "desktop", + chromeMediaSourceId: desktopStream, + googLeakyBucket: true, + maxWidth: window.screen.width, + maxHeight: window.screen.height, + maxFrameRate: 3 + }, + optional: [] + }; + } + + if (bandwidth) { + if (!constraints.video) { + //same behaviour as true + constraints.video = {mandatory: {}, optional: []}; + } + constraints.video.optional.push({bandwidth: bandwidth}); + } + if (fps) { + // for some cameras it might be necessary to request 30fps + // so they choose 30fps mjpg over 10fps yuy2 + if (!constraints.video) { + // same behaviour as true; + constraints.video = {mandatory: {}, optional: []}; + } + constraints.video.mandatory.minFrameRate = fps; + } + + // we turn audio for both audio and video tracks, the fake audio & video seems to work + // only when enabled in one getUserMedia call, we cannot get fake audio separate by fake video + // this later can be a problem with some of the tests + if(RTCBrowserType.isFirefox() && config.firefox_fake_device) + { + constraints.audio = true; + constraints.fake = true; + } + + return constraints; +} + + +function RTCUtils(RTCService, onTemasysPluginReady) +{ + var self = this; + this.service = RTCService; + if (RTCBrowserType.isFirefox()) { + var FFversion = RTCBrowserType.getFirefoxVersion(); + if (FFversion >= 40) { + this.peerconnection = mozRTCPeerConnection; + this.getUserMedia = navigator.mozGetUserMedia.bind(navigator); + this.pc_constraints = {}; + this.attachMediaStream = function (element, stream) { + // srcObject is being standardized and FF will eventually + // support that unprefixed. FF also supports the + // "element.src = URL.createObjectURL(...)" combo, but that + // will be deprecated in favour of srcObject. + // + // https://groups.google.com/forum/#!topic/mozilla.dev.media/pKOiioXonJg + // https://github.com/webrtc/samples/issues/302 + if(!element[0]) + return; + element[0].mozSrcObject = stream; + element[0].play(); + }; + this.getStreamID = function (stream) { + var id = stream.id; + if (!id) { + var tracks = stream.getVideoTracks(); + if (!tracks || tracks.length === 0) { + tracks = stream.getAudioTracks(); + } + id = tracks[0].id; + } + return SDPUtil.filter_special_chars(id); + }; + this.getVideoSrc = function (element) { + if(!element) + return null; + return element.mozSrcObject; + }; + this.setVideoSrc = function (element, src) { + if(element) + element.mozSrcObject = src; + }; + RTCSessionDescription = mozRTCSessionDescription; + RTCIceCandidate = mozRTCIceCandidate; + } else { + console.error( + "Firefox version too old: " + FFversion + ". Required >= 40."); + window.location.href = 'unsupported_browser.html'; + return; + } + + } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) { + this.peerconnection = webkitRTCPeerConnection; + this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); + this.attachMediaStream = function (element, stream) { + element.attr('src', webkitURL.createObjectURL(stream)); + }; + this.getStreamID = function (stream) { + // streams from FF endpoints have the characters '{' and '}' + // that make jQuery choke. + return SDPUtil.filter_special_chars(stream.id); + }; + this.getVideoSrc = function (element) { + if(!element) + return null; + return element.getAttribute("src"); + }; + this.setVideoSrc = function (element, src) { + if(element) + element.setAttribute("src", src); + }; + // DTLS should now be enabled by default but.. + this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]}; + if (RTCBrowserType.isAndroid()) { + this.pc_constraints = {}; // disable DTLS on Android + } + if (!webkitMediaStream.prototype.getVideoTracks) { + webkitMediaStream.prototype.getVideoTracks = function () { + return this.videoTracks; + }; + } + if (!webkitMediaStream.prototype.getAudioTracks) { + webkitMediaStream.prototype.getAudioTracks = function () { + return this.audioTracks; + }; + } + } + // Detect IE/Safari + else if (RTCBrowserType.isTemasysPluginUsed()) { + + //AdapterJS.WebRTCPlugin.setLogLevel( + // AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE); + + AdapterJS.webRTCReady(function (isPlugin) { + + self.peerconnection = RTCPeerConnection; + self.getUserMedia = getUserMedia; + self.attachMediaStream = function (elSel, stream) { + + if (stream.id === "dummyAudio" || stream.id === "dummyVideo") { + return; + } + + attachMediaStream(elSel[0], stream); + }; + self.getStreamID = function (stream) { + var id = SDPUtil.filter_special_chars(stream.label); + return id; + }; + self.getVideoSrc = function (element) { + if (!element) { + console.warn("Attempt to get video SRC of null element"); + return null; + } + var children = element.children; + for (var i = 0; i !== children.length; ++i) { + if (children[i].name === 'streamId') { + return children[i].value; + } + } + //console.info(element.id + " SRC: " + src); + return null; + }; + self.setVideoSrc = function (element, src) { + //console.info("Set video src: ", element, src); + if (!src) { + console.warn("Not attaching video stream, 'src' is null"); + return; + } + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + var stream = AdapterJS.WebRTCPlugin.plugin + .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src); + attachMediaStream(element, stream); + }; + + onTemasysPluginReady(isPlugin); + }); + } else { + try { + console.log('Browser does not appear to be WebRTC-capable'); + } catch (e) { } + window.location.href = 'unsupported_browser.html'; + } +} + + +RTCUtils.prototype.getUserMediaWithConstraints = function( + um, success_callback, failure_callback, resolution,bandwidth, fps, + desktopStream) { + currentResolution = resolution; + + var constraints = getConstraints( + um, resolution, bandwidth, fps, desktopStream); + + console.info("Get media constraints", constraints); + + var self = this; + + try { + this.getUserMedia(constraints, + function (stream) { + console.log('onUserMediaSuccess'); + self.setAvailableDevices(um, true); + success_callback(stream); + }, + function (error) { + self.setAvailableDevices(um, false); + console.warn('Failed to get access to local media. Error ', + error, constraints); + if (failure_callback) { + failure_callback(error); + } + }); + } catch (e) { + console.error('GUM failed: ', e); + if(failure_callback) { + failure_callback(e); + } + } +}; + +RTCUtils.prototype.setAvailableDevices = function (um, available) { + var devices = {}; + if(um.indexOf("video") != -1) { + devices.video = available; + } + if(um.indexOf("audio") != -1) { + devices.audio = available; + } + this.service.setDeviceAvailability(devices); +}; + +/** + * We ask for audio and video combined stream in order to get permissions and + * not to ask twice. + */ +RTCUtils.prototype.obtainAudioAndVideoPermissions = + function(devices, callback, usageOptions) +{ + var self = this; + // Get AV + + var successCallback = function (stream) { + if(callback) + callback(stream, usageOptions); + else + self.successCallback(stream, usageOptions); + }; + + if(!devices) + devices = ['audio', 'video']; + + var newDevices = []; + + + if(usageOptions) + for(var i = 0; i < devices.length; i++) { + var device = devices[i]; + if(usageOptions[device] === true) + newDevices.push(device); + } + else + newDevices = devices; + + if(newDevices.length === 0) { + successCallback(); + return; + } + + if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) { + + // With FF/IE we can't split the stream into audio and video because FF + // doesn't support media stream constructors. So, we need to get the + // audio stream separately from the video stream using two distinct GUM + // calls. Not very user friendly :-( but we don't have many other + // options neither. + // + // Note that we pack those 2 streams in a single object and pass it to + // the successCallback method. + var obtainVideo = function (audioStream) { + self.getUserMediaWithConstraints( + ['video'], + function (videoStream) { + return successCallback({ + audioStream: audioStream, + videoStream: videoStream + }); + }, + function (error) { + console.error( + 'failed to obtain video stream - stop', error); + self.errorCallback(error); + }, + config.resolution || '360'); + }; + var obtainAudio = function () { + self.getUserMediaWithConstraints( + ['audio'], + function (audioStream) { + if (newDevices.indexOf('video') !== -1) + obtainVideo(audioStream); + }, + function (error) { + console.error( + 'failed to obtain audio stream - stop', error); + self.errorCallback(error); + } + ); + }; + if (newDevices.indexOf('audio') !== -1) { + obtainAudio(); + } else { + obtainVideo(null); + } + } else { + this.getUserMediaWithConstraints( + newDevices, + function (stream) { + successCallback(stream); + }, + function (error) { + self.errorCallback(error); + }, + config.resolution || '360'); + } +}; + +RTCUtils.prototype.successCallback = function (stream, usageOptions) { + // If this is FF or IE, the stream parameter is *not* a MediaStream object, + // it's an object with two properties: audioStream, videoStream. + if (stream && stream.getAudioTracks && stream.getVideoTracks) + console.log('got', stream, stream.getAudioTracks().length, + stream.getVideoTracks().length); + this.handleLocalStream(stream, usageOptions); +}; + +RTCUtils.prototype.errorCallback = function (error) { + var self = this; + console.error('failed to obtain audio/video stream - trying audio only', error); + var resolution = getPreviousResolution(currentResolution); + if(typeof error == "object" && error.constraintName && error.name + && (error.name == "ConstraintNotSatisfiedError" || + error.name == "OverconstrainedError") && + (error.constraintName == "minWidth" || error.constraintName == "maxWidth" || + error.constraintName == "minHeight" || error.constraintName == "maxHeight") + && resolution != null) + { + self.getUserMediaWithConstraints(['audio', 'video'], + function (stream) { + return self.successCallback(stream); + }, function (error) { + return self.errorCallback(error); + }, resolution); + } + else { + self.getUserMediaWithConstraints( + ['audio'], + function (stream) { + return self.successCallback(stream); + }, + function (error) { + console.error('failed to obtain audio/video stream - stop', + error); + return self.successCallback(null); + } + ); + } +}; + +RTCUtils.prototype.handleLocalStream = function(stream, usageOptions) { + // If this is FF, the stream parameter is *not* a MediaStream object, it's + // an object with two properties: audioStream, videoStream. + var audioStream, videoStream; + if(window.webkitMediaStream) + { + audioStream = new webkitMediaStream(); + videoStream = new webkitMediaStream(); + if(stream) { + var audioTracks = stream.getAudioTracks(); + + for (var i = 0; i < audioTracks.length; i++) { + audioStream.addTrack(audioTracks[i]); + } + + var videoTracks = stream.getVideoTracks(); + + for (i = 0; i < videoTracks.length; i++) { + videoStream.addTrack(videoTracks[i]); + } + } + } + else if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) + { // Firefox and Temasys plugin + if (stream && stream.audioStream) + audioStream = stream.audioStream; + else + audioStream = new DummyMediaStream("dummyAudio"); + + if (stream && stream.videoStream) + videoStream = stream.videoStream; + else + videoStream = new DummyMediaStream("dummyVideo"); + } + + var audioMuted = (usageOptions && usageOptions.audio === false), + videoMuted = (usageOptions && usageOptions.video === false); + + var audioGUM = (!usageOptions || usageOptions.audio !== false), + videoGUM = (!usageOptions || usageOptions.video !== false); + + + this.service.createLocalStream(audioStream, "audio", null, null, + audioMuted, audioGUM); + + this.service.createLocalStream(videoStream, "video", null, 'camera', + videoMuted, videoGUM); +}; + +function DummyMediaStream(id) { + this.id = id; + this.label = id; + this.stop = function() { }; + this.getAudioTracks = function() { return []; }; + this.getVideoTracks = function() { return []; }; +} + +RTCUtils.prototype.createStream = function(stream, isVideo) { + var newStream = null; + if (window.webkitMediaStream) { + newStream = new webkitMediaStream(); + if (newStream) { + var tracks = (isVideo ? stream.getVideoTracks() : stream.getAudioTracks()); + + for (var i = 0; i < tracks.length; i++) { + newStream.addTrack(tracks[i]); + } + } + + } + else { + // FIXME: this is duplicated with 'handleLocalStream' !!! + if (stream) { + newStream = stream; + } else { + newStream = + new DummyMediaStream(isVideo ? "dummyVideo" : "dummyAudio"); + } + } + + return newStream; +}; + +module.exports = RTCUtils; + +},{"../../service/RTC/Resolutions":158,"../xmpp/SDPUtil":60,"./RTCBrowserType":10,"./adapter.screenshare":12}],12:[function(require,module,exports){ +/*! adapterjs - custom version from - 2015-08-18 */ + +// Adapter's interface. +var AdapterJS = AdapterJS || {}; + +// Browserify compatibility +if(typeof exports !== 'undefined') { + module.exports = AdapterJS; +} + +AdapterJS.options = AdapterJS.options || {}; + +// uncomment to get virtual webcams +// AdapterJS.options.getAllCams = true; + +// uncomment to prevent the install prompt when the plugin in not yet installed +// AdapterJS.options.hidePluginInstallPrompt = true; + +// AdapterJS version +AdapterJS.VERSION = '0.12.0'; + +// This function will be called when the WebRTC API is ready to be used +// Whether it is the native implementation (Chrome, Firefox, Opera) or +// the plugin +// You may Override this function to synchronise the start of your application +// with the WebRTC API being ready. +// If you decide not to override use this synchronisation, it may result in +// an extensive CPU usage on the plugin start (once per tab loaded) +// Params: +// - isUsingPlugin: true is the WebRTC plugin is being used, false otherwise +// +AdapterJS.onwebrtcready = AdapterJS.onwebrtcready || function(isUsingPlugin) { + // The WebRTC API is ready. + // Override me and do whatever you want here +}; + +// Sets a callback function to be called when the WebRTC interface is ready. +// The first argument is the function to callback.\ +// Throws an error if the first argument is not a function +AdapterJS.webRTCReady = function (callback) { + if (typeof callback !== 'function') { + throw new Error('Callback provided is not a function'); + } + + if (true === AdapterJS.onwebrtcreadyDone) { + // All WebRTC interfaces are ready, just call the callback + callback(null !== AdapterJS.WebRTCPlugin.plugin); + } else { + // will be triggered automatically when your browser/plugin is ready. + AdapterJS.onwebrtcready = callback; + } +}; + +// Plugin namespace +AdapterJS.WebRTCPlugin = AdapterJS.WebRTCPlugin || {}; + +// The object to store plugin information +AdapterJS.WebRTCPlugin.pluginInfo = { + prefix : 'Tem', + plugName : 'TemWebRTCPlugin', + pluginId : 'plugin0', + type : 'application/x-temwebrtcplugin', + onload : '__TemWebRTCReady0', + portalLink : 'http://skylink.io/plugin/', + downloadLink : null, //set below + companyName: 'Temasys' +}; +if(!!navigator.platform.match(/^Mac/i)) { + AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1n77hco'; +} +else if(!!navigator.platform.match(/^Win/i)) { + AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1kkS4FN'; +} + +AdapterJS.WebRTCPlugin.TAGS = { + NONE : 'none', + AUDIO : 'audio', + VIDEO : 'video' +}; + +// Unique identifier of each opened page +AdapterJS.WebRTCPlugin.pageId = Math.random().toString(36).slice(2); + +// Use this whenever you want to call the plugin. +AdapterJS.WebRTCPlugin.plugin = null; + +// Set log level for the plugin once it is ready. +// The different values are +// This is an asynchronous function that will run when the plugin is ready +AdapterJS.WebRTCPlugin.setLogLevel = null; + +// Defines webrtc's JS interface according to the plugin's implementation. +// Define plugin Browsers as WebRTC Interface. +AdapterJS.WebRTCPlugin.defineWebRTCInterface = null; + +// This function detects whether or not a plugin is installed. +// Checks if Not IE (firefox, for example), else if it's IE, +// we're running IE and do something. If not it is not supported. +AdapterJS.WebRTCPlugin.isPluginInstalled = null; + + // Lets adapter.js wait until the the document is ready before injecting the plugin +AdapterJS.WebRTCPlugin.pluginInjectionInterval = null; + +// Inject the HTML DOM object element into the page. +AdapterJS.WebRTCPlugin.injectPlugin = null; + +// States of readiness that the plugin goes through when +// being injected and stated +AdapterJS.WebRTCPlugin.PLUGIN_STATES = { + NONE : 0, // no plugin use + INITIALIZING : 1, // Detected need for plugin + INJECTING : 2, // Injecting plugin + INJECTED: 3, // Plugin element injected but not usable yet + READY: 4 // Plugin ready to be used +}; + +// Current state of the plugin. You cannot use the plugin before this is +// equal to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY +AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.NONE; + +// True is AdapterJS.onwebrtcready was already called, false otherwise +// Used to make sure AdapterJS.onwebrtcready is only called once +AdapterJS.onwebrtcreadyDone = false; + +// Log levels for the plugin. +// To be set by calling AdapterJS.WebRTCPlugin.setLogLevel +/* +Log outputs are prefixed in some cases. + INFO: Information reported by the plugin. + ERROR: Errors originating from within the plugin. + WEBRTC: Error originating from within the libWebRTC library +*/ +// From the least verbose to the most verbose +AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS = { + NONE : 'NONE', + ERROR : 'ERROR', + WARNING : 'WARNING', + INFO: 'INFO', + VERBOSE: 'VERBOSE', + SENSITIVE: 'SENSITIVE' +}; + +// Does a waiting check before proceeding to load the plugin. +AdapterJS.WebRTCPlugin.WaitForPluginReady = null; + +// This methid will use an interval to wait for the plugin to be ready. +AdapterJS.WebRTCPlugin.callWhenPluginReady = null; + +// !!!! WARNING: DO NOT OVERRIDE THIS FUNCTION. !!! +// This function will be called when plugin is ready. It sends necessary +// details to the plugin. +// The function will wait for the document to be ready and the set the +// plugin state to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY, +// indicating that it can start being requested. +// This function is not in the IE/Safari condition brackets so that +// TemPluginLoaded function might be called on Chrome/Firefox. +// This function is the only private function that is not encapsulated to +// allow the plugin method to be called. +__TemWebRTCReady0 = function () { + if (document.readyState === 'complete') { + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; + + AdapterJS.maybeThroughWebRTCReady(); + } else { + AdapterJS.WebRTCPlugin.documentReadyInterval = setInterval(function () { + if (document.readyState === 'complete') { + // TODO: update comments, we wait for the document to be ready + clearInterval(AdapterJS.WebRTCPlugin.documentReadyInterval); + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; + + AdapterJS.maybeThroughWebRTCReady(); + } + }, 100); + } +}; + +AdapterJS.maybeThroughWebRTCReady = function() { + if (!AdapterJS.onwebrtcreadyDone) { + AdapterJS.onwebrtcreadyDone = true; + + if (typeof(AdapterJS.onwebrtcready) === 'function') { + AdapterJS.onwebrtcready(AdapterJS.WebRTCPlugin.plugin !== null); + } + } +}; + +// Text namespace +AdapterJS.TEXT = { + PLUGIN: { + REQUIRE_INSTALLATION: 'This website requires you to install a WebRTC-enabling plugin ' + + 'to work on this browser.', + NOT_SUPPORTED: 'Your browser does not support WebRTC.', + BUTTON: 'Install Now' + }, + REFRESH: { + REQUIRE_REFRESH: 'Please refresh page', + BUTTON: 'Refresh Page' + } +}; + +// The result of ice connection states. +// - starting: Ice connection is starting. +// - checking: Ice connection is checking. +// - connected Ice connection is connected. +// - completed Ice connection is connected. +// - done Ice connection has been completed. +// - disconnected Ice connection has been disconnected. +// - failed Ice connection has failed. +// - closed Ice connection is closed. +AdapterJS._iceConnectionStates = { + starting : 'starting', + checking : 'checking', + connected : 'connected', + completed : 'connected', + done : 'completed', + disconnected : 'disconnected', + failed : 'failed', + closed : 'closed' +}; + +//The IceConnection states that has been fired for each peer. +AdapterJS._iceConnectionFiredStates = []; + + +// Check if WebRTC Interface is defined. +AdapterJS.isDefined = null; + +// This function helps to retrieve the webrtc detected browser information. +// This sets: +// - webrtcDetectedBrowser: The browser agent name. +// - webrtcDetectedVersion: The browser version. +// - webrtcDetectedType: The types of webRTC support. +// - 'moz': Mozilla implementation of webRTC. +// - 'webkit': WebKit implementation of webRTC. +// - 'plugin': Using the plugin implementation. +AdapterJS.parseWebrtcDetectedBrowser = function () { + var hasMatch, checkMatch = navigator.userAgent.match( + /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; + if (/trident/i.test(checkMatch[1])) { + hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; + webrtcDetectedBrowser = 'IE'; + webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); + } else if (checkMatch[1] === 'Chrome') { + hasMatch = navigator.userAgent.match(/\bOPR\/(\d+)/); + if (hasMatch !== null) { + webrtcDetectedBrowser = 'opera'; + webrtcDetectedVersion = parseInt(hasMatch[1], 10); + } + } + if (navigator.userAgent.indexOf('Safari')) { + if (typeof InstallTrigger !== 'undefined') { + webrtcDetectedBrowser = 'firefox'; + } else if (/*@cc_on!@*/ false || !!document.documentMode) { + webrtcDetectedBrowser = 'IE'; + } else if ( + Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) { + webrtcDetectedBrowser = 'safari'; + } else if (!!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0) { + webrtcDetectedBrowser = 'opera'; + } else if (!!window.chrome) { + webrtcDetectedBrowser = 'chrome'; + } + } + if (!webrtcDetectedBrowser) { + webrtcDetectedVersion = checkMatch[1]; + } + if (!webrtcDetectedVersion) { + try { + checkMatch = (checkMatch[2]) ? [checkMatch[1], checkMatch[2]] : + [navigator.appName, navigator.appVersion, '-?']; + if ((hasMatch = navigator.userAgent.match(/version\/(\d+)/i)) !== null) { + checkMatch.splice(1, 1, hasMatch[1]); + } + webrtcDetectedVersion = parseInt(checkMatch[1], 10); + } catch (error) { } + } +}; + +// To fix configuration as some browsers does not support +// the 'urls' attribute. +AdapterJS.maybeFixConfiguration = function (pcConfig) { + if (pcConfig === null) { + return; + } + for (var i = 0; i < pcConfig.iceServers.length; i++) { + if (pcConfig.iceServers[i].hasOwnProperty('urls')) { + pcConfig.iceServers[i].url = pcConfig.iceServers[i].urls; + delete pcConfig.iceServers[i].urls; + } + } +}; + +AdapterJS.addEvent = function(elem, evnt, func) { + if (elem.addEventListener) { // W3C DOM + elem.addEventListener(evnt, func, false); + } else if (elem.attachEvent) {// OLD IE DOM + elem.attachEvent('on'+evnt, func); + } else { // No much to do + elem[evnt] = func; + } +}; + +AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNewTab, displayRefreshBar) { + // only inject once the page is ready + if (document.readyState !== 'complete') { + return; + } + + var w = window; + var i = document.createElement('iframe'); + i.style.position = 'fixed'; + i.style.top = '-41px'; + i.style.left = 0; + i.style.right = 0; + i.style.width = '100%'; + i.style.height = '40px'; + i.style.backgroundColor = '#ffffe1'; + i.style.border = 'none'; + i.style.borderBottom = '1px solid #888888'; + i.style.zIndex = '9999999'; + if(typeof i.style.webkitTransition === 'string') { + i.style.webkitTransition = 'all .5s ease-out'; + } else if(typeof i.style.transition === 'string') { + i.style.transition = 'all .5s ease-out'; + } + document.body.appendChild(i); + c = (i.contentWindow) ? i.contentWindow : + (i.contentDocument.document) ? i.contentDocument.document : i.contentDocument; + c.document.open(); + c.document.write('' + text + ''); + if(buttonText && buttonLink) { + c.document.write(''); + c.document.close(); + + AdapterJS.addEvent(c.document.getElementById('okay'), 'click', function(e) { + if (!!displayRefreshBar) { + AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION ? + AdapterJS.TEXT.EXTENSION.REQUIRE_REFRESH : AdapterJS.TEXT.REFRESH.REQUIRE_REFRESH, + AdapterJS.TEXT.REFRESH.BUTTON, 'javascript:location.reload()'); + } + window.open(buttonLink, !!openNewTab ? '_blank' : '_top'); + + e.preventDefault(); + try { + event.cancelBubble = true; + } catch(error) { } + + var pluginInstallInterval = setInterval(function(){ + if(! isIE) { + navigator.plugins.refresh(false); + } + AdapterJS.WebRTCPlugin.isPluginInstalled( + AdapterJS.WebRTCPlugin.pluginInfo.prefix, + AdapterJS.WebRTCPlugin.pluginInfo.plugName, + AdapterJS.WebRTCPlugin.defineWebRTCInterface, + function() { + clearInterval(pluginInstallInterval); + AdapterJS.WebRTCPlugin.defineWebRTCInterface(); + }, + function() { + //Does nothing because not used here + }); + } , 500); + }); + } else { + c.document.close(); + } + AdapterJS.addEvent(c.document, 'click', function() { + w.document.body.removeChild(i); + }); + setTimeout(function() { + if(typeof i.style.webkitTransform === 'string') { + i.style.webkitTransform = 'translateY(40px)'; + } else if(typeof i.style.transform === 'string') { + i.style.transform = 'translateY(40px)'; + } else { + i.style.top = '0px'; + } + }, 300); +}; + +// ----------------------------------------------------------- +// Detected webrtc implementation. Types are: +// - 'moz': Mozilla implementation of webRTC. +// - 'webkit': WebKit implementation of webRTC. +// - 'plugin': Using the plugin implementation. +webrtcDetectedType = null; + +// Detected webrtc datachannel support. Types are: +// - 'SCTP': SCTP datachannel support. +// - 'RTP': RTP datachannel support. +webrtcDetectedDCSupport = null; + +// Set the settings for creating DataChannels, MediaStream for +// Cross-browser compability. +// - This is only for SCTP based support browsers. +// the 'urls' attribute. +checkMediaDataChannelSettings = + function (peerBrowserAgent, peerBrowserVersion, callback, constraints) { + if (typeof callback !== 'function') { + return; + } + var beOfferer = true; + var isLocalFirefox = webrtcDetectedBrowser === 'firefox'; + // Nightly version does not require MozDontOfferDataChannel for interop + var isLocalFirefoxInterop = webrtcDetectedType === 'moz' && webrtcDetectedVersion > 30; + var isPeerFirefox = peerBrowserAgent === 'firefox'; + var isPeerFirefoxInterop = peerBrowserAgent === 'firefox' && + ((peerBrowserVersion) ? (peerBrowserVersion > 30) : false); + + // Resends an updated version of constraints for MozDataChannel to work + // If other userAgent is firefox and user is firefox, remove MozDataChannel + if ((isLocalFirefox && isPeerFirefox) || (isLocalFirefoxInterop)) { + try { + delete constraints.mandatory.MozDontOfferDataChannel; + } catch (error) { + console.error('Failed deleting MozDontOfferDataChannel'); + console.error(error); + } + } else if ((isLocalFirefox && !isPeerFirefox)) { + constraints.mandatory.MozDontOfferDataChannel = true; + } + if (!isLocalFirefox) { + // temporary measure to remove Moz* constraints in non Firefox browsers + for (var prop in constraints.mandatory) { + if (constraints.mandatory.hasOwnProperty(prop)) { + if (prop.indexOf('Moz') !== -1) { + delete constraints.mandatory[prop]; + } + } + } + } + // Firefox (not interopable) cannot offer DataChannel as it will cause problems to the + // interopability of the media stream + if (isLocalFirefox && !isPeerFirefox && !isLocalFirefoxInterop) { + beOfferer = false; + } + callback(beOfferer, constraints); +}; + +// Handles the differences for all browsers ice connection state output. +// - Tested outcomes are: +// - Chrome (offerer) : 'checking' > 'completed' > 'completed' +// - Chrome (answerer) : 'checking' > 'connected' +// - Firefox (offerer) : 'checking' > 'connected' +// - Firefox (answerer): 'checking' > 'connected' +checkIceConnectionState = function (peerId, iceConnectionState, callback) { + if (typeof callback !== 'function') { + console.warn('No callback specified in checkIceConnectionState. Aborted.'); + return; + } + peerId = (peerId) ? peerId : 'peer'; + + if (!AdapterJS._iceConnectionFiredStates[peerId] || + iceConnectionState === AdapterJS._iceConnectionStates.disconnected || + iceConnectionState === AdapterJS._iceConnectionStates.failed || + iceConnectionState === AdapterJS._iceConnectionStates.closed) { + AdapterJS._iceConnectionFiredStates[peerId] = []; + } + iceConnectionState = AdapterJS._iceConnectionStates[iceConnectionState]; + if (AdapterJS._iceConnectionFiredStates[peerId].indexOf(iceConnectionState) < 0) { + AdapterJS._iceConnectionFiredStates[peerId].push(iceConnectionState); + if (iceConnectionState === AdapterJS._iceConnectionStates.connected) { + setTimeout(function () { + AdapterJS._iceConnectionFiredStates[peerId] + .push(AdapterJS._iceConnectionStates.done); + callback(AdapterJS._iceConnectionStates.done); + }, 1000); + } + callback(iceConnectionState); + } + return; +}; + +// Firefox: +// - Creates iceServer from the url for Firefox. +// - Create iceServer with stun url. +// - Create iceServer with turn url. +// - Ignore the transport parameter from TURN url for FF version <=27. +// - Return null for createIceServer if transport=tcp. +// - FF 27 and above supports transport parameters in TURN url, +// - So passing in the full url to create iceServer. +// Chrome: +// - Creates iceServer from the url for Chrome M33 and earlier. +// - Create iceServer with stun url. +// - Chrome M28 & above uses below TURN format. +// Plugin: +// - Creates Ice Server for Plugin Browsers +// - If Stun - Create iceServer with stun url. +// - Else - Create iceServer with turn url +// - This is a WebRTC Function +createIceServer = null; + +// Firefox: +// - Creates IceServers for Firefox +// - Use .url for FireFox. +// - Multiple Urls support +// Chrome: +// - Creates iceServers from the urls for Chrome M34 and above. +// - .urls is supported since Chrome M34. +// - Multiple Urls support +// Plugin: +// - Creates Ice Servers for Plugin Browsers +// - Multiple Urls support +// - This is a WebRTC Function +createIceServers = null; +//------------------------------------------------------------ + +//The RTCPeerConnection object. +RTCPeerConnection = null; + +// Creates RTCSessionDescription object for Plugin Browsers +RTCSessionDescription = (typeof RTCSessionDescription === 'function') ? + RTCSessionDescription : null; + +// Creates RTCIceCandidate object for Plugin Browsers +RTCIceCandidate = (typeof RTCIceCandidate === 'function') ? + RTCIceCandidate : null; + +// Get UserMedia (only difference is the prefix). +// Code from Adam Barth. +getUserMedia = null; + +// Attach a media stream to an element. +attachMediaStream = null; + +// Re-attach a media stream to an element. +reattachMediaStream = null; + + +// Detected browser agent name. Types are: +// - 'firefox': Firefox browser. +// - 'chrome': Chrome browser. +// - 'opera': Opera browser. +// - 'safari': Safari browser. +// - 'IE' - Internet Explorer browser. +webrtcDetectedBrowser = null; + +// Detected browser version. +webrtcDetectedVersion = null; + +// Check for browser types and react accordingly +if (navigator.mozGetUserMedia) { + webrtcDetectedBrowser = 'firefox'; + webrtcDetectedVersion = parseInt(navigator + .userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); + webrtcDetectedType = 'moz'; + webrtcDetectedDCSupport = 'SCTP'; + + RTCPeerConnection = function (pcConfig, pcConstraints) { + AdapterJS.maybeFixConfiguration(pcConfig); + return new mozRTCPeerConnection(pcConfig, pcConstraints); + }; + + // The RTCSessionDescription object. + RTCSessionDescription = mozRTCSessionDescription; + window.RTCSessionDescription = RTCSessionDescription; + + // The RTCIceCandidate object. + RTCIceCandidate = mozRTCIceCandidate; + window.RTCIceCandidate = RTCIceCandidate; + + window.getUserMedia = navigator.mozGetUserMedia.bind(navigator); + navigator.getUserMedia = window.getUserMedia; + + // Shim for MediaStreamTrack.getSources. + MediaStreamTrack.getSources = function(successCb) { + setTimeout(function() { + var infos = [ + { kind: 'audio', id: 'default', label:'', facing:'' }, + { kind: 'video', id: 'default', label:'', facing:'' } + ]; + successCb(infos); + }, 0); + }; + + createIceServer = function (url, username, password) { + var iceServer = null; + var url_parts = url.split(':'); + if (url_parts[0].indexOf('stun') === 0) { + iceServer = { url : url }; + } else if (url_parts[0].indexOf('turn') === 0) { + if (webrtcDetectedVersion < 27) { + var turn_url_parts = url.split('?'); + if (turn_url_parts.length === 1 || + turn_url_parts[1].indexOf('transport=udp') === 0) { + iceServer = { + url : turn_url_parts[0], + credential : password, + username : username + }; + } + } else { + iceServer = { + url : url, + credential : password, + username : username + }; + } + } + return iceServer; + }; + + createIceServers = function (urls, username, password) { + var iceServers = []; + for (i = 0; i < urls.length; i++) { + var iceServer = createIceServer(urls[i], username, password); + if (iceServer !== null) { + iceServers.push(iceServer); + } + } + return iceServers; + }; + + attachMediaStream = function (element, stream) { + element.mozSrcObject = stream; + if (stream !== null) + element.play(); + + return element; + }; + + reattachMediaStream = function (to, from) { + to.mozSrcObject = from.mozSrcObject; + to.play(); + return to; + }; + + MediaStreamTrack.getSources = MediaStreamTrack.getSources || function (callback) { + if (!callback) { + throw new TypeError('Failed to execute \'getSources\' on \'MediaStreamTrack\'' + + ': 1 argument required, but only 0 present.'); + } + return callback([]); + }; + + // Fake get{Video,Audio}Tracks + if (!MediaStream.prototype.getVideoTracks) { + MediaStream.prototype.getVideoTracks = function () { + return []; + }; + } + if (!MediaStream.prototype.getAudioTracks) { + MediaStream.prototype.getAudioTracks = function () { + return []; + }; + } + + AdapterJS.maybeThroughWebRTCReady(); +} else if (navigator.webkitGetUserMedia) { + webrtcDetectedBrowser = 'chrome'; + webrtcDetectedType = 'webkit'; + webrtcDetectedVersion = parseInt(navigator + .userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10); + // check if browser is opera 20+ + var checkIfOpera = navigator.userAgent.match(/\bOPR\/(\d+)/); + if (checkIfOpera !== null) { + webrtcDetectedBrowser = 'opera'; + webrtcDetectedVersion = parseInt(checkIfOpera[1], 10); + } + // check browser datachannel support + if ((webrtcDetectedBrowser === 'chrome' && webrtcDetectedVersion >= 31) || + (webrtcDetectedBrowser === 'opera' && webrtcDetectedVersion >= 20)) { + webrtcDetectedDCSupport = 'SCTP'; + } else if (webrtcDetectedBrowser === 'chrome' && webrtcDetectedVersion < 30 && + webrtcDetectedVersion > 24) { + webrtcDetectedDCSupport = 'RTP'; + } else { + webrtcDetectedDCSupport = ''; + } + + createIceServer = function (url, username, password) { + var iceServer = null; + var url_parts = url.split(':'); + if (url_parts[0].indexOf('stun') === 0) { + iceServer = { 'url' : url }; + } else if (url_parts[0].indexOf('turn') === 0) { + iceServer = { + 'url' : url, + 'credential' : password, + 'username' : username + }; + } + return iceServer; + }; + + createIceServers = function (urls, username, password) { + var iceServers = []; + if (webrtcDetectedVersion >= 34) { + iceServers = { + 'urls' : urls, + 'credential' : password, + 'username' : username + }; + } else { + for (i = 0; i < urls.length; i++) { + var iceServer = createIceServer(urls[i], username, password); + if (iceServer !== null) { + iceServers.push(iceServer); + } + } + } + return iceServers; + }; + + RTCPeerConnection = function (pcConfig, pcConstraints) { + if (webrtcDetectedVersion < 34) { + AdapterJS.maybeFixConfiguration(pcConfig); + } + return new webkitRTCPeerConnection(pcConfig, pcConstraints); + }; + + window.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); + navigator.getUserMedia = window.getUserMedia; + + attachMediaStream = function (element, stream) { + if (typeof element.srcObject !== 'undefined') { + element.srcObject = stream; + } else if (typeof element.mozSrcObject !== 'undefined') { + element.mozSrcObject = stream; + } else if (typeof element.src !== 'undefined') { + element.src = (stream === null ? '' : URL.createObjectURL(stream)); + } else { + console.log('Error attaching stream to element.'); + } + return element; + }; + + reattachMediaStream = function (to, from) { + to.src = from.src; + return to; + }; + + AdapterJS.maybeThroughWebRTCReady(); +} else { // TRY TO USE PLUGIN + // IE 9 is not offering an implementation of console.log until you open a console + if (typeof console !== 'object' || typeof console.log !== 'function') { + /* jshint -W020 */ + console = {} || console; + // Implemented based on console specs from MDN + // You may override these functions + console.log = function (arg) {}; + console.info = function (arg) {}; + console.error = function (arg) {}; + console.dir = function (arg) {}; + console.exception = function (arg) {}; + console.trace = function (arg) {}; + console.warn = function (arg) {}; + console.count = function (arg) {}; + console.debug = function (arg) {}; + console.count = function (arg) {}; + console.time = function (arg) {}; + console.timeEnd = function (arg) {}; + console.group = function (arg) {}; + console.groupCollapsed = function (arg) {}; + console.groupEnd = function (arg) {}; + /* jshint +W020 */ + } + webrtcDetectedType = 'plugin'; + webrtcDetectedDCSupport = 'plugin'; + AdapterJS.parseWebrtcDetectedBrowser(); + isIE = webrtcDetectedBrowser === 'IE'; + + /* jshint -W035 */ + AdapterJS.WebRTCPlugin.WaitForPluginReady = function() { + while (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { + /* empty because it needs to prevent the function from running. */ + } + }; + /* jshint +W035 */ + + AdapterJS.WebRTCPlugin.callWhenPluginReady = function (callback) { + if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { + // Call immediately if possible + // Once the plugin is set, the code will always take this path + callback(); + } else { + // otherwise start a 100ms interval + var checkPluginReadyState = setInterval(function () { + if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { + clearInterval(checkPluginReadyState); + callback(); + } + }, 100); + } + }; + + AdapterJS.WebRTCPlugin.setLogLevel = function(logLevel) { + AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { + AdapterJS.WebRTCPlugin.plugin.setLogLevel(logLevel); + }); + }; + + AdapterJS.WebRTCPlugin.injectPlugin = function () { + // only inject once the page is ready + if (document.readyState !== 'complete') { + return; + } + + // Prevent multiple injections + if (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING) { + return; + } + + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTING; + + if (webrtcDetectedBrowser === 'IE' && webrtcDetectedVersion <= 10) { + var frag = document.createDocumentFragment(); + AdapterJS.WebRTCPlugin.plugin = document.createElement('div'); + AdapterJS.WebRTCPlugin.plugin.innerHTML = '' + + ' ' + + ' ' + + ' ' + + '' + + '' + + // uncomment to be able to use virtual cams + (AdapterJS.options.getAllCams ? '':'') + + + ''; + while (AdapterJS.WebRTCPlugin.plugin.firstChild) { + frag.appendChild(AdapterJS.WebRTCPlugin.plugin.firstChild); + } + document.body.appendChild(frag); + + // Need to re-fetch the plugin + AdapterJS.WebRTCPlugin.plugin = + document.getElementById(AdapterJS.WebRTCPlugin.pluginInfo.pluginId); + } else { + // Load Plugin + AdapterJS.WebRTCPlugin.plugin = document.createElement('object'); + AdapterJS.WebRTCPlugin.plugin.id = + AdapterJS.WebRTCPlugin.pluginInfo.pluginId; + // IE will only start the plugin if it's ACTUALLY visible + if (isIE) { + AdapterJS.WebRTCPlugin.plugin.width = '1px'; + AdapterJS.WebRTCPlugin.plugin.height = '1px'; + } else { // The size of the plugin on Safari should be 0x0px + // so that the autorisation prompt is at the top + AdapterJS.WebRTCPlugin.plugin.width = '0px'; + AdapterJS.WebRTCPlugin.plugin.height = '0px'; + } + AdapterJS.WebRTCPlugin.plugin.type = AdapterJS.WebRTCPlugin.pluginInfo.type; + AdapterJS.WebRTCPlugin.plugin.innerHTML = '' + + '' + + ' ' + + (AdapterJS.options.getAllCams ? '':'') + + '' + + ''; + document.body.appendChild(AdapterJS.WebRTCPlugin.plugin); + } + + + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTED; + }; + + AdapterJS.WebRTCPlugin.isPluginInstalled = + function (comName, plugName, installedCb, notInstalledCb) { + if (!isIE) { + var pluginArray = navigator.plugins; + for (var i = 0; i < pluginArray.length; i++) { + if (pluginArray[i].name.indexOf(plugName) >= 0) { + installedCb(); + return; + } + } + notInstalledCb(); + } else { + try { + var axo = new ActiveXObject(comName + '.' + plugName); + } catch (e) { + notInstalledCb(); + return; + } + installedCb(); + } + }; + + AdapterJS.WebRTCPlugin.defineWebRTCInterface = function () { + if (AdapterJS.WebRTCPlugin.pluginState === + AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { + console.error("WebRTC interface has been defined already"); + return; + } + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING; + + AdapterJS.isDefined = function (variable) { + return variable !== null && variable !== undefined; + }; + + createIceServer = function (url, username, password) { + var iceServer = null; + var url_parts = url.split(':'); + if (url_parts[0].indexOf('stun') === 0) { + iceServer = { + 'url' : url, + 'hasCredentials' : false + }; + } else if (url_parts[0].indexOf('turn') === 0) { + iceServer = { + 'url' : url, + 'hasCredentials' : true, + 'credential' : password, + 'username' : username + }; + } + return iceServer; + }; + + createIceServers = function (urls, username, password) { + var iceServers = []; + for (var i = 0; i < urls.length; ++i) { + iceServers.push(createIceServer(urls[i], username, password)); + } + return iceServers; + }; + + RTCSessionDescription = function (info) { + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + return AdapterJS.WebRTCPlugin.plugin. + ConstructSessionDescription(info.type, info.sdp); + }; + + RTCPeerConnection = function (servers, constraints) { + var iceServers = null; + if (servers) { + iceServers = servers.iceServers; + for (var i = 0; i < iceServers.length; i++) { + if (iceServers[i].urls && !iceServers[i].url) { + iceServers[i].url = iceServers[i].urls; + } + iceServers[i].hasCredentials = AdapterJS. + isDefined(iceServers[i].username) && + AdapterJS.isDefined(iceServers[i].credential); + } + } + var mandatory = (constraints && constraints.mandatory) ? + constraints.mandatory : null; + var optional = (constraints && constraints.optional) ? + constraints.optional : null; + + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + return AdapterJS.WebRTCPlugin.plugin. + PeerConnection(AdapterJS.WebRTCPlugin.pageId, + iceServers, mandatory, optional); + }; + + MediaStreamTrack = {}; + MediaStreamTrack.getSources = function (callback) { + AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { + AdapterJS.WebRTCPlugin.plugin.GetSources(callback); + }); + }; + + window.getUserMedia = function (constraints, successCallback, failureCallback) { + constraints.audio = constraints.audio || false; + constraints.video = constraints.video || false; + + AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { + AdapterJS.WebRTCPlugin.plugin. + getUserMedia(constraints, successCallback, failureCallback); + }); + }; + window.navigator.getUserMedia = window.getUserMedia; + + attachMediaStream = function (element, stream) { + if (!element || !element.parentNode) { + return; + } + + var streamId + if (stream === null) { + streamId = ''; + } + else { + stream.enableSoundTracks(true); // TODO: remove on 0.12.0 + streamId = stream.id; + } + + var elementId = element.id.length === 0 ? Math.random().toString(36).slice(2) : element.id; + if (!element.isWebRTCPlugin || !element.isWebRTCPlugin()) { + var tag; + switch(element.nodeName.toLowerCase()) { + case 'audio': + tag = AdapterJS.WebRTCPlugin.TAGS.AUDIO; + break; + case 'video': + tag = AdapterJS.WebRTCPlugin.TAGS.VIDEO; + break; + default: + tag = AdapterJS.WebRTCPlugin.TAGS.NONE; + } + + var frag = document.createDocumentFragment(); + var temp = document.createElement('div'); + var classHTML = ''; + if (element.className) { + classHTML = 'class="' + element.className + '" '; + } else if (element.attributes && element.attributes['class']) { + classHTML = 'class="' + element.attributes['class'].value + '" '; + } + + temp.innerHTML = '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + while (temp.firstChild) { + frag.appendChild(temp.firstChild); + } + + var height = ''; + var width = ''; + if (element.getBoundingClientRect) { + var rectObject = element.getBoundingClientRect(); + width = rectObject.width + 'px'; + height = rectObject.height + 'px'; + } + else if (element.width) { + width = element.width; + height = element.height; + } else { + // TODO: What scenario could bring us here? + } + + element.parentNode.insertBefore(frag, element); + frag = document.getElementById(elementId); + frag.width = width; + frag.height = height; + element.parentNode.removeChild(element); + } else { + 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; + }; + + reattachMediaStream = function (to, from) { + var stream = null; + var children = from.children; + for (var i = 0; i !== children.length; ++i) { + if (children[i].name === 'streamId') { + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + stream = AdapterJS.WebRTCPlugin.plugin + .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, children[i].value); + break; + } + } + if (stream !== null) { + return attachMediaStream(to, stream); + } else { + console.log('Could not find the stream associated with this element'); + } + }; + + RTCIceCandidate = function (candidate) { + if (!candidate.sdpMid) { + candidate.sdpMid = ''; + } + + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + return AdapterJS.WebRTCPlugin.plugin.ConstructIceCandidate( + candidate.sdpMid, candidate.sdpMLineIndex, candidate.candidate + ); + }; + + // inject plugin + AdapterJS.addEvent(document, 'readystatechange', AdapterJS.WebRTCPlugin.injectPlugin); + AdapterJS.WebRTCPlugin.injectPlugin(); + }; + + // This function will be called if the plugin is needed (browser different + // from Chrome or Firefox), but the plugin is not installed. + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb = AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb || + function() { + AdapterJS.addEvent(document, + 'readystatechange', + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv); + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv(); + }; + + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv = function () { + if (AdapterJS.options.hidePluginInstallPrompt) { + return; + } + + var downloadLink = AdapterJS.WebRTCPlugin.pluginInfo.downloadLink; + if(downloadLink) { // if download link + var popupString; + if (AdapterJS.WebRTCPlugin.pluginInfo.portalLink) { // is portal link + popupString = 'This website requires you to install the ' + + ' ' + AdapterJS.WebRTCPlugin.pluginInfo.companyName + + ' WebRTC Plugin' + + ' to work on this browser.'; + } else { // no portal link, just print a generic explanation + popupString = AdapterJS.TEXT.PLUGIN.REQUIRE_INSTALLATION; + } + + AdapterJS.renderNotificationBar(popupString, AdapterJS.TEXT.PLUGIN.BUTTON, downloadLink); + } else { // no download link, just print a generic explanation + AdapterJS.renderNotificationBar(AdapterJS.TEXT.PLUGIN.NOT_SUPPORTED); + } + }; + + // Try to detect the plugin and act accordingly + AdapterJS.WebRTCPlugin.isPluginInstalled( + AdapterJS.WebRTCPlugin.pluginInfo.prefix, + AdapterJS.WebRTCPlugin.pluginInfo.plugName, + AdapterJS.WebRTCPlugin.defineWebRTCInterface, + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb); +} + +},{}],13:[function(require,module,exports){ +/* global Strophe, APP, $, config, interfaceConfig, toastr */ +var UI = {}; + +var VideoLayout = require("./videolayout/VideoLayout.js"); +var AudioLevels = require("./audio_levels/AudioLevels.js"); +var Prezi = require("./prezi/Prezi.js"); +var Etherpad = require("./etherpad/Etherpad.js"); +var Chat = require("./side_pannels/chat/Chat.js"); +var Toolbar = require("./toolbars/Toolbar"); +var ToolbarToggler = require("./toolbars/ToolbarToggler"); +var BottomToolbar = require("./toolbars/BottomToolbar"); +var ContactList = require("./side_pannels/contactlist/ContactList"); +var Avatar = require("./avatar/Avatar"); +var EventEmitter = require("events"); +var SettingsMenu = require("./side_pannels/settings/SettingsMenu"); +var Settings = require("./../settings/Settings"); +var PanelToggler = require("./side_pannels/SidePanelToggler"); +var RoomNameGenerator = require("./welcome_page/RoomnameGenerator"); +UI.messageHandler = require("./util/MessageHandler"); +var messageHandler = UI.messageHandler; +var Authentication = require("./authentication/Authentication"); +var UIUtil = require("./util/UIUtil"); +var NicknameHandler = require("./util/NicknameHandler"); +var JitsiPopover = require("./util/JitsiPopover"); +var CQEvents = require("../../service/connectionquality/CQEvents"); +var DesktopSharingEventTypes + = require("../../service/desktopsharing/DesktopSharingEventTypes"); +var RTCEvents = require("../../service/RTC/RTCEvents"); +var RTCBrowserType = require("../RTC/RTCBrowserType"); +var StreamEventTypes = require("../../service/RTC/StreamEventTypes"); +var XMPPEvents = require("../../service/xmpp/XMPPEvents"); +var UIEvents = require("../../service/UI/UIEvents"); +var MemberEvents = require("../../service/members/Events"); + +var eventEmitter = new EventEmitter(); +var roomName = null; + + +function promptDisplayName() { + var message = '

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

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

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

' + + ''; + + messageHandler.openTwoButtonDialog(null, null, null, message, + true, + "dialog.Ok", + function (e, v, m, f) {}, + null, + function (e, v, m, f) { + if (v) { + var lockKey = f.lockKey; + if (lockKey) { + Toolbar.setSharedKey(lockKey); + callback(lockKey); + } + } + }, + ':input:first' + ); +} + +/** + * The dialpad button is shown iff there is at least one member that supports + * DTMF (e.g. jigasi). + */ +function onDtmfSupportChanged(dtmfSupport) { + //TODO: enable when the UI is ready + //Toolbar.showDialPadButton(dtmfSupport); +} + +function onMucMemberJoined(jid, id, displayName) { + messageHandler.notify(displayName,'notify.somebody', + 'connected', + 'notify.connected'); + + if (!config.startAudioMuted || + config.startAudioMuted > APP.members.size()) + UIUtil.playSoundNotification('userJoined'); + + // Configure avatar + Avatar.setUserAvatar(jid, id); + + // Add Peer's container + VideoLayout.ensurePeerContainerExists(jid); +} + +function onMucPresenceStatus(jid, info) { + VideoLayout.setPresenceStatus(Strophe.getResourceFromJid(jid), info.status); +} + +function onPeerVideoTypeChanged(resourceJid, newVideoType) { + VideoLayout.onVideoTypeChanged(resourceJid, newVideoType); +} + +function onMucRoleChanged(role, displayName) { + VideoLayout.showModeratorIndicator(); + + if (role === 'moderator') { + var messageKey, messageOptions = {}; + if (!displayName) { + messageKey = "notify.grantedToUnknown"; + } + else { + messageKey = "notify.grantedTo"; + messageOptions = {to: displayName}; + } + messageHandler.notify( + displayName,'notify.somebody', + 'connected', messageKey, + messageOptions); + } +} + +function onAuthenticationRequired(intervalCallback) { + Authentication.openAuthenticationDialog( + roomName, intervalCallback, function () { + Toolbar.authenticateClicked(); + }); +} + + +function onLastNChanged(oldValue, newValue) { + if (config.muteLocalVideoIfNotInLastN) { + setVideoMute(!newValue, { 'byUser': false }); + } +} + + +UI.toggleSmileys = function () { + Chat.toggleSmileys(); +}; + +UI.getSettings = function () { + return Settings.getSettings(); +}; + +UI.toggleFilmStrip = function () { + return BottomToolbar.toggleFilmStrip(); +}; + +UI.toggleChat = function () { + return BottomToolbar.toggleChat(); +}; + +UI.toggleContactList = function () { + return BottomToolbar.toggleContactList(); +}; + +UI.inputDisplayNameHandler = function (value) { + VideoLayout.inputDisplayNameHandler(value); +}; + +UI.getLargeVideoResource = function () { + return VideoLayout.getLargeVideoResource(); +}; + +UI.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.getJingleLog(); + var metadata = {}; + metadata.time = new Date(); + metadata.url = window.location.href; + metadata.ua = navigator.userAgent; + var log = APP.xmpp.getXmppLog(); + if (log) { + metadata.xmpp = log; + } + data.metadata = metadata; + elem.href += encodeURIComponent(JSON.stringify(data, null, ' ')); + return false; +} + +UI.getRoomName = function () { + return roomName; +}; + +UI.setInitialMuteFromFocus = function (muteAudio, muteVideo) { + if (muteAudio || muteVideo) + notifyForInitialMute(); + if (muteAudio) + UI.setAudioMuted(true); + if (muteVideo) + UI.setVideoMute(true); +}; + +/** + * Mutes/unmutes the local video. + */ +UI.toggleVideo = function () { + setVideoMute(!APP.RTC.localVideo.isMuted()); +}; + +/** + * Mutes / unmutes audio for the local participant. + */ +UI.toggleAudio = function() { + UI.setAudioMuted(!APP.RTC.localAudio.isMuted()); +}; + +/** + * Sets muted audio state for the local participant. + */ +UI.setAudioMuted = function (mute, earlyMute) { + var audioMute = null; + if (earlyMute) + audioMute = function (mute, cb) { + return APP.xmpp.sendAudioInfoPresence(mute, cb); + }; + else + audioMute = function (mute, cb) { + return APP.xmpp.setAudioMute(mute, cb); + }; + if (!audioMute(mute, function () { + VideoLayout.showLocalAudioIndicator(mute); + + UIUtil.buttonClick("#toolbar_button_mute", "icon-microphone icon-mic-disabled"); + })) { + // We still click the button. + UIUtil.buttonClick("#toolbar_button_mute", "icon-microphone icon-mic-disabled"); + return; + } +}; + +UI.addListener = function (type, listener) { + eventEmitter.on(type, listener); +}; + +UI.clickOnVideo = function (videoNumber) { + var remoteVideos = $(".videocontainer:not(#mixedstream)"); + if (remoteVideos.length > videoNumber) { + remoteVideos[videoNumber].click(); + } +}; + +//Used by torture +UI.showToolbar = function () { + return ToolbarToggler.showToolbar(); +}; + +//Used by torture +UI.dockToolbar = function (isDock) { + return ToolbarToggler.dockToolbar(isDock); +}; + +UI.setVideoMuteButtonsState = function (mute) { + var video = $('#toolbar_button_camera'); + var communicativeClass = "icon-camera"; + var muteClass = "icon-camera icon-camera-disabled"; + + if (mute) { + video.removeClass(communicativeClass); + video.addClass(muteClass); + } else { + video.removeClass(muteClass); + video.addClass(communicativeClass); + } +}; + +UI.userAvatarChanged = function (resourceJid, thumbUrl, contactListUrl) { + VideoLayout.userAvatarChanged(resourceJid, thumbUrl); + ContactList.userAvatarChanged(resourceJid, contactListUrl); + if(resourceJid === APP.xmpp.myResource()) + SettingsMenu.changeAvatar(thumbUrl); +}; + +UI.setVideoMute = setVideoMute; + +module.exports = UI; + + +},{"../../service/RTC/RTCEvents":157,"../../service/RTC/StreamEventTypes":159,"../../service/UI/UIEvents":160,"../../service/connectionquality/CQEvents":162,"../../service/desktopsharing/DesktopSharingEventTypes":163,"../../service/members/Events":164,"../../service/xmpp/XMPPEvents":166,"../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/JitsiPopover":32,"./util/MessageHandler":33,"./util/NicknameHandler":34,"./util/UIUtil":35,"./videolayout/VideoLayout.js":41,"./welcome_page/RoomnameGenerator":42,"./welcome_page/WelcomePage":43,"events":1}],14:[function(require,module,exports){ +/* global APP, interfaceConfig, $, Strophe */ +var CanvasUtil = require("./CanvasUtils"); + +var ASDrawContext = null; + +function initActiveSpeakerAudioLevels() { + var ASRadius = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2; + var ASCenter = (interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE + ASRadius) / 2; + + // Draw a circle. + ASDrawContext.arc(ASCenter, ASCenter, ASRadius, 0, 2 * Math.PI); + + // Add a shadow around the circle + ASDrawContext.shadowColor = interfaceConfig.SHADOW_COLOR; + ASDrawContext.shadowOffsetX = 0; + ASDrawContext.shadowOffsetY = 0; +} + +/** + * The audio Levels plugin. + */ +var AudioLevels = (function(my) { + var audioLevelCanvasCache = {}; + + my.LOCAL_LEVEL = 'local'; + + my.init = function () { + ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d'); + initActiveSpeakerAudioLevels(); + }; + + /** + * Updates the audio level canvas for the given peerJid. If the canvas + * didn't exist we create it. + */ + my.updateAudioLevelCanvas = function (peerJid, VideoLayout) { + var resourceJid = null; + var videoSpanId = null; + if (!peerJid) + videoSpanId = 'localVideoContainer'; + else { + resourceJid = Strophe.getResourceFromJid(peerJid); + + videoSpanId = 'participant_' + resourceJid; + } + + var videoSpan = document.getElementById(videoSpanId); + + if (!videoSpan) { + if (resourceJid) + console.error("No video element for jid", resourceJid); + else + console.error("No video element for local video."); + + return; + } + + var audioLevelCanvas = $('#' + videoSpanId + '>canvas'); + + var videoSpaceWidth = $('#remoteVideos').width(); + var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth); + var thumbnailWidth = thumbnailSize[0]; + var thumbnailHeight = thumbnailSize[1]; + + if (!audioLevelCanvas || audioLevelCanvas.length === 0) { + + audioLevelCanvas = document.createElement('canvas'); + audioLevelCanvas.className = "audiolevel"; + audioLevelCanvas.style.bottom = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px"; + audioLevelCanvas.style.left = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px"; + resizeAudioLevelCanvas( audioLevelCanvas, + thumbnailWidth, + thumbnailHeight); + + videoSpan.appendChild(audioLevelCanvas); + } else { + audioLevelCanvas = audioLevelCanvas.get(0); + + resizeAudioLevelCanvas( audioLevelCanvas, + thumbnailWidth, + thumbnailHeight); + } + }; + + /** + * Updates the audio level UI for the given resourceJid. + * + * @param resourceJid the resource jid indicating the video element for + * which we draw the audio level + * @param audioLevel the newAudio level to render + */ + my.updateAudioLevel = function (resourceJid, audioLevel, largeVideoResourceJid) { + drawAudioLevelCanvas(resourceJid, audioLevel); + + var videoSpanId = getVideoSpanId(resourceJid); + + var audioLevelCanvas = $('#' + videoSpanId + '>canvas').get(0); + + if (!audioLevelCanvas) + return; + + var drawContext = audioLevelCanvas.getContext('2d'); + + var canvasCache = audioLevelCanvasCache[resourceJid]; + + drawContext.clearRect (0, 0, + audioLevelCanvas.width, audioLevelCanvas.height); + drawContext.drawImage(canvasCache, 0, 0); + + if(resourceJid === AudioLevels.LOCAL_LEVEL) { + if(!APP.xmpp.myJid()) { + return; + } + resourceJid = APP.xmpp.myResource(); + } + + if(resourceJid === largeVideoResourceJid) { + window.requestAnimationFrame(function () { + AudioLevels.updateActiveSpeakerAudioLevel(audioLevel); + }); + } + }; + + my.updateActiveSpeakerAudioLevel = function(audioLevel) { + if($("#activeSpeaker").css("visibility") == "hidden" || ASDrawContext === null) + return; + + ASDrawContext.clearRect(0, 0, 300, 300); + if(audioLevel == 0) + return; + + ASDrawContext.shadowBlur = getShadowLevel(audioLevel); + + + // Fill the shape. + ASDrawContext.fill(); + }; + + /** + * Resizes the given audio level canvas to match the given thumbnail size. + */ + function resizeAudioLevelCanvas(audioLevelCanvas, + thumbnailWidth, + thumbnailHeight) { + audioLevelCanvas.width = thumbnailWidth + interfaceConfig.CANVAS_EXTRA; + audioLevelCanvas.height = thumbnailHeight + interfaceConfig.CANVAS_EXTRA; + } + + /** + * Draws the audio level canvas into the cached canvas object. + * + * @param resourceJid the resource jid indicating the video element for + * which we draw the audio level + * @param audioLevel the newAudio level to render + */ + function drawAudioLevelCanvas(resourceJid, audioLevel) { + if (!audioLevelCanvasCache[resourceJid]) { + + var videoSpanId = getVideoSpanId(resourceJid); + + var audioLevelCanvasOrig = $('#' + videoSpanId + '>canvas').get(0); + + /* + * FIXME Testing has shown that audioLevelCanvasOrig may not exist. + * In such a case, the method CanvasUtil.cloneCanvas may throw an + * error. Since audio levels are frequently updated, the errors have + * been observed to pile into the console, strain the CPU. + */ + if (audioLevelCanvasOrig) { + audioLevelCanvasCache[resourceJid] = + CanvasUtil.cloneCanvas(audioLevelCanvasOrig); + } + } + + var canvas = audioLevelCanvasCache[resourceJid]; + + if (!canvas) + return; + + var drawContext = canvas.getContext('2d'); + + drawContext.clearRect(0, 0, canvas.width, canvas.height); + + var shadowLevel = getShadowLevel(audioLevel); + + if (shadowLevel > 0) { + // drawContext, x, y, w, h, r, shadowColor, shadowLevel + CanvasUtil.drawRoundRectGlow(drawContext, + interfaceConfig.CANVAS_EXTRA / 2, interfaceConfig.CANVAS_EXTRA / 2, + canvas.width - interfaceConfig.CANVAS_EXTRA, + canvas.height - interfaceConfig.CANVAS_EXTRA, + interfaceConfig.CANVAS_RADIUS, + interfaceConfig.SHADOW_COLOR, + shadowLevel); + } + } + + /** + * Returns the shadow/glow level for the given audio level. + * + * @param audioLevel the audio level from which we determine the shadow + * level + */ + function getShadowLevel (audioLevel) { + var shadowLevel = 0; + + if (audioLevel <= 0.3) { + shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*(audioLevel/0.3)); + } + else if (audioLevel <= 0.6) { + shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3)); + } + else { + shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4)); + } + return shadowLevel; + } + + /** + * Returns the video span id corresponding to the given resourceJid or local + * user. + */ + function getVideoSpanId(resourceJid) { + var videoSpanId = null; + if (resourceJid === AudioLevels.LOCAL_LEVEL || + (APP.xmpp.myResource() && resourceJid === APP.xmpp.myResource())) + videoSpanId = 'localVideoContainer'; + else + videoSpanId = 'participant_' + resourceJid; + + return videoSpanId; + } + + /** + * Indicates that the remote video has been resized. + */ + $(document).bind('remotevideo.resized', function (event, width, height) { + var resized = false; + $('#remoteVideos>span>canvas').each(function() { + var canvas = $(this).get(0); + if (canvas.width !== width + interfaceConfig.CANVAS_EXTRA) { + canvas.width = width + interfaceConfig.CANVAS_EXTRA; + resized = true; + } + + if (canvas.heigh !== height + interfaceConfig.CANVAS_EXTRA) { + canvas.height = height + interfaceConfig.CANVAS_EXTRA; + resized = true; + } + }); + + if (resized) + Object.keys(audioLevelCanvasCache).forEach(function (resourceJid) { + audioLevelCanvasCache[resourceJid].width = + width + interfaceConfig.CANVAS_EXTRA; + audioLevelCanvasCache[resourceJid].height = + height + interfaceConfig.CANVAS_EXTRA; + }); + }); + + return my; + +})(AudioLevels || {}); + module.exports = AudioLevels; -},{"./CanvasUtils":13}],13:[function(require,module,exports){ -/** - * Utility class for drawing canvas shapes. - */ -var CanvasUtil = (function(my) { - - /** - * Draws a round rectangle with a glow. The glowWidth indicates the depth - * of the glow. - * - * @param drawContext the context of the canvas to draw to - * @param x the x coordinate of the round rectangle - * @param y the y coordinate of the round rectangle - * @param w the width of the round rectangle - * @param h the height of the round rectangle - * @param glowColor the color of the glow - * @param glowWidth the width of the glow - */ - my.drawRoundRectGlow - = function(drawContext, x, y, w, h, r, glowColor, glowWidth) { - - // Save the previous state of the context. - drawContext.save(); - - if (w < 2 * r) r = w / 2; - if (h < 2 * r) r = h / 2; - - // Draw a round rectangle. - drawContext.beginPath(); - drawContext.moveTo(x+r, y); - drawContext.arcTo(x+w, y, x+w, y+h, r); - drawContext.arcTo(x+w, y+h, x, y+h, r); - drawContext.arcTo(x, y+h, x, y, r); - drawContext.arcTo(x, y, x+w, y, r); - drawContext.closePath(); - - // Add a shadow around the rectangle - drawContext.shadowColor = glowColor; - drawContext.shadowBlur = glowWidth; - drawContext.shadowOffsetX = 0; - drawContext.shadowOffsetY = 0; - - // Fill the shape. - drawContext.fill(); - - drawContext.save(); - - drawContext.restore(); - -// 1) Uncomment this line to use Composite Operation, which is doing the -// same as the clip function below and is also antialiasing the round -// border, but is said to be less fast performance wise. - -// drawContext.globalCompositeOperation='destination-out'; - - drawContext.beginPath(); - drawContext.moveTo(x+r, y); - drawContext.arcTo(x+w, y, x+w, y+h, r); - drawContext.arcTo(x+w, y+h, x, y+h, r); - drawContext.arcTo(x, y+h, x, y, r); - drawContext.arcTo(x, y, x+w, y, r); - drawContext.closePath(); - -// 2) Uncomment this line to use Composite Operation, which is doing the -// same as the clip function below and is also antialiasing the round -// border, but is said to be less fast performance wise. - -// drawContext.fill(); - - // Comment these two lines if choosing to do the same with composite - // operation above 1 and 2. - drawContext.clip(); - drawContext.clearRect(0, 0, 277, 200); - - // Restore the previous context state. - drawContext.restore(); - }; - - /** - * Clones the given canvas. - * - * @return the new cloned canvas. - */ - my.cloneCanvas = function (oldCanvas) { - /* - * FIXME Testing has shown that oldCanvas may not exist. In such a case, - * the method CanvasUtil.cloneCanvas may throw an error. Since audio - * levels are frequently updated, the errors have been observed to pile - * into the console, strain the CPU. - */ - if (!oldCanvas) - return oldCanvas; - - //create a new canvas - var newCanvas = document.createElement('canvas'); - var context = newCanvas.getContext('2d'); - - //set dimensions - newCanvas.width = oldCanvas.width; - newCanvas.height = oldCanvas.height; - - //apply the old canvas to the new one - context.drawImage(oldCanvas, 0, 0); - - //return the new canvas - return newCanvas; - }; - - return my; -})(CanvasUtil || {}); - +},{"./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; -},{}],14:[function(require,module,exports){ -/* global $, APP*/ - -var LoginDialog = require('./LoginDialog'); -var Moderator = require('../../xmpp/moderator'); - -/* Initial "authentication required" dialog */ -var authDialog = null; -/* Loop retry ID that wits for other user to create the room */ -var authRetryId = null; -var authenticationWindow = null; - -var Authentication = { - openAuthenticationDialog: function (roomName, intervalCallback, callback) { - // This is the loop that will wait for the room to be created by - // someone else. 'auth_required.moderator' will bring us back here. - authRetryId = window.setTimeout(intervalCallback, 5000); - // Show prompt only if it's not open - if (authDialog !== null) { - return; - } - // extract room name from 'room@muc.server.net' - var room = roomName.substr(0, roomName.indexOf('@')); - - var title - = APP.translation.generateTranslationHTML("dialog.WaitingForHost"); - var msg - = APP.translation.generateTranslationHTML( - "dialog.WaitForHostMsg", {room: room}); - - var buttonTxt - = APP.translation.generateTranslationHTML("dialog.IamHost"); - var buttons = []; - buttons.push({title: buttonTxt, value: "authNow"}); - - authDialog = APP.UI.messageHandler.openDialog( - title, - msg, - true, - buttons, - function (onSubmitEvent, submitValue) { - - // Do not close the dialog yet - onSubmitEvent.preventDefault(); - - // Open login popup - if (submitValue === 'authNow') { - callback(); - } - } - ); - }, - closeAuthenticationWindow: function () { - if (authenticationWindow) { - authenticationWindow.close(); - authenticationWindow = null; - } - }, - xmppAuthenticate: function () { - - var loginDialog = LoginDialog.show( - function (connection, state) { - if (!state) { - // User cancelled - loginDialog.close(); - return; - } else if (state == APP.xmpp.Status.CONNECTED) { - - loginDialog.close(); - - Authentication.stopInterval(); - Authentication.closeAuthenticationDialog(); - - // Close the connection as anonymous one will be used - // to create the conference. Session-id will authorize - // the request. - connection.disconnect(); - - var roomName = APP.UI.generateRoomName(); - Moderator.allocateConferenceFocus(roomName, function () { - // If it's not "on the fly" authentication now join - // the conference room - if (!APP.xmpp.isMUCJoined()) { - APP.UI.checkForNicknameAndJoin(); - } - }); - } - }, true); - }, - focusAuthenticationWindow: function () { - // If auth window exists just bring it to the front - if (authenticationWindow) { - authenticationWindow.focus(); - return; - } - }, - closeAuthenticationDialog: function () { - // Close authentication dialog if opened - if (authDialog) { - authDialog.close(); - authDialog = null; - } - }, - createAuthenticationWindow: function (callback, url) { - authenticationWindow = APP.UI.messageHandler.openCenteredPopup( - url, 910, 660, - // On closed - function () { - // Close authentication dialog if opened - Authentication.closeAuthenticationDialog(); - callback(); - authenticationWindow = null; - }); - return authenticationWindow; - }, - stopInterval: function () { - // Clear retry interval, so that we don't call 'doJoinAfterFocus' twice - if (authRetryId) { - window.clearTimeout(authRetryId); - authRetryId = null; - } - } -}; - +},{}],16:[function(require,module,exports){ +/* global $, APP*/ + +var LoginDialog = require('./LoginDialog'); +var Moderator = require('../../xmpp/moderator'); + +/* Initial "authentication required" dialog */ +var authDialog = null; +/* Loop retry ID that wits for other user to create the room */ +var authRetryId = null; +var authenticationWindow = null; + +var Authentication = { + openAuthenticationDialog: function (roomName, intervalCallback, callback) { + // This is the loop that will wait for the room to be created by + // someone else. 'auth_required.moderator' will bring us back here. + authRetryId = window.setTimeout(intervalCallback, 5000); + // Show prompt only if it's not open + if (authDialog !== null) { + return; + } + // extract room name from 'room@muc.server.net' + var room = roomName.substr(0, roomName.indexOf('@')); + + var title + = APP.translation.generateTranslationHTML("dialog.WaitingForHost"); + var msg + = APP.translation.generateTranslationHTML( + "dialog.WaitForHostMsg", {room: room}); + + var buttonTxt + = APP.translation.generateTranslationHTML("dialog.IamHost"); + var buttons = []; + buttons.push({title: buttonTxt, value: "authNow"}); + + authDialog = APP.UI.messageHandler.openDialog( + title, + msg, + true, + buttons, + function (onSubmitEvent, submitValue) { + + // Do not close the dialog yet + onSubmitEvent.preventDefault(); + + // Open login popup + if (submitValue === 'authNow') { + callback(); + } + } + ); + }, + closeAuthenticationWindow: function () { + if (authenticationWindow) { + authenticationWindow.close(); + authenticationWindow = null; + } + }, + xmppAuthenticate: function () { + + var loginDialog = LoginDialog.show( + function (connection, state) { + if (!state) { + // User cancelled + loginDialog.close(); + return; + } else if (state == APP.xmpp.Status.CONNECTED) { + + loginDialog.close(); + + Authentication.stopInterval(); + Authentication.closeAuthenticationDialog(); + + // Close the connection as anonymous one will be used + // to create the conference. Session-id will authorize + // the request. + connection.disconnect(); + + var roomName = APP.UI.generateRoomName(); + Moderator.allocateConferenceFocus(roomName, function () { + // If it's not "on the fly" authentication now join + // the conference room + if (!APP.xmpp.isMUCJoined()) { + APP.UI.checkForNicknameAndJoin(); + } + }); + } + }, true); + }, + focusAuthenticationWindow: function () { + // If auth window exists just bring it to the front + if (authenticationWindow) { + authenticationWindow.focus(); + return; + } + }, + closeAuthenticationDialog: function () { + // Close authentication dialog if opened + if (authDialog) { + authDialog.close(); + authDialog = null; + } + }, + createAuthenticationWindow: function (callback, url) { + authenticationWindow = APP.UI.messageHandler.openCenteredPopup( + url, 910, 660, + // On closed + function () { + // Close authentication dialog if opened + Authentication.closeAuthenticationDialog(); + callback(); + authenticationWindow = null; + }); + return authenticationWindow; + }, + stopInterval: function () { + // Clear retry interval, so that we don't call 'doJoinAfterFocus' twice + if (authRetryId) { + window.clearTimeout(authRetryId); + authRetryId = null; + } + } +}; + module.exports = Authentication; -},{"../../xmpp/moderator":60,"./LoginDialog":15}],15:[function(require,module,exports){ -/* global $, APP, config*/ - -var XMPP = require('../../xmpp/xmpp'); -var Moderator = require('../../xmpp/moderator'); - -//FIXME: use LoginDialog to add retries to XMPP.connect method used when -// anonymous domain is not enabled - -/** - * Creates new Dialog instance. - * @param callback function(Strophe.Connection, Strophe.Status) called - * when we either fail to connect or succeed(check Strophe.Status). - * @param obtainSession true if we want to send ConferenceIQ to Jicofo - * in order to create session-id after the connection is established. - * @constructor - */ -function Dialog(callback, obtainSession) { - - var self = this; - - var stop = false; - - var connection = APP.xmpp.createConnection(); - - var message = '

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

' + - '' + - ''; - - var okButton = APP.translation.generateTranslationHTML("dialog.Ok"); - - var cancelButton = APP.translation.generateTranslationHTML("dialog.Cancel"); - - var states = { - login: { - html: message, - buttons: [ - { title: okButton, value: true}, - { title: cancelButton, value: false} - ], - focus: ':input:first', - submit: function (e, v, m, f) { - e.preventDefault(); - if (v) { - var jid = f.username; - var password = f.password; - if (jid && password) { - stop = false; - connection.reset(); - connDialog.goToState('connecting'); - connection.connect(jid, password, stateHandler); - } - } else { - // User cancelled - stop = true; - callback(); - } - } - }, - connecting: { - title: APP.translation.translateString('dialog.connecting'), - html: '
', - buttons: [], - defaultButton: 0 - }, - finished: { - title: APP.translation.translateString('dialog.error'), - html: '
', - buttons: [ - { - title: APP.translation.translateString('dialog.retry'), - value: 'retry' - }, - { - title: APP.translation.translateString('dialog.Cancel'), - value: 'cancel' - }, - ], - defaultButton: 0, - submit: function (e, v, m, f) { - e.preventDefault(); - if (v === 'retry') - connDialog.goToState('login'); - else - callback(); - } - } - }; - - var connDialog - = APP.UI.messageHandler.openDialogWithStates(states, - { persistent: true, closeText: '' }, null); - - var stateHandler = function (status, message) { - if (stop) { - return; - } - - var translateKey = "connection." + XMPP.getStatusString(status); - var statusStr = APP.translation.translateString(translateKey); - - // Display current state - var connectionStatus = - connDialog.getState('connecting').find('#connectionStatus'); - - connectionStatus.text(statusStr); - - switch (status) { - case XMPP.Status.CONNECTED: - - stop = true; - if (!obtainSession) { - callback(connection, status); - return; - } - // Obtaining session-id status - connectionStatus.text( - APP.translation.translateString( - 'connection.FETCH_SESSION_ID')); - - // Authenticate with Jicofo and obtain session-id - var roomName = APP.UI.generateRoomName(); - - // Jicofo will return new session-id when connected - // from authenticated domain - connection.sendIQ( - Moderator.createConferenceIq(roomName), - function (result) { - - connectionStatus.text( - APP.translation.translateString( - 'connection.GOT_SESSION_ID')); - - stop = true; - - // Parse session-id - Moderator.parseSessionId(result); - - callback(connection, status); - }, - function (error) { - console.error("Auth on the fly failed", error); - - stop = true; - - var errorMsg = - APP.translation.translateString( - 'connection.GET_SESSION_ID_ERROR') + - $(error).find('>error').attr('code'); - - self.displayError(errorMsg); - - connection.disconnect(); - }); - - break; - case XMPP.Status.AUTHFAIL: - case XMPP.Status.CONNFAIL: - case XMPP.Status.DISCONNECTED: - - stop = true; - - callback(connection, status); - - var errorMessage = statusStr; - - if (message) - { - errorMessage += ': ' + message; - } - self.displayError(errorMessage); - - break; - default: - break; - } - }; - - /** - * Displays error message in 'finished' state which allows either to cancel - * or retry. - * @param message the final message to be displayed. - */ - this.displayError = function (message) { - - var finishedState = connDialog.getState('finished'); - - var errorMessageElem = finishedState.find('#errorMessage'); - errorMessageElem.text(message); - - connDialog.goToState('finished'); - }; - - /** - * Closes LoginDialog. - */ - this.close = function () { - stop = true; - connDialog.close(); - }; -} - -var LoginDialog = { - - /** - * Displays login prompt used to establish new XMPP connection. Given - * callback(Strophe.Connection, Strophe.Status) function will be - * called when we connect successfully(status === CONNECTED) or when we fail - * to do so. On connection failure program can call Dialog.close() method in - * order to cancel or do nothing to let the user retry. - * @param callback function(Strophe.Connection, Strophe.Status) - * called when we either fail to connect or succeed(check - * Strophe.Status). - * @param obtainSession true if we want to send ConferenceIQ to - * Jicofo in order to create session-id after the connection is - * established. - * @returns {Dialog} - */ - show: function (callback, obtainSession) { - return new Dialog(callback, obtainSession); - } -}; - +},{"../../xmpp/moderator":62,"./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}],16:[function(require,module,exports){ -var Settings = require("../../settings/Settings"); - -var users = {}; - -var Avatar = { - - /** - * Sets the user's avatar in the settings menu(if local user), contact list - * and thumbnail - * @param jid jid of the user - * @param id email or userID to be used as a hash - */ - setUserAvatar: function (jid, id) { - if (id) { - if (users[jid] === id) { - return; - } - users[jid] = id; - } - var thumbUrl = this.getThumbUrl(jid); - var contactListUrl = this.getContactListUrl(jid); - var resourceJid = Strophe.getResourceFromJid(jid); - - APP.UI.userAvatarChanged(resourceJid, thumbUrl, contactListUrl); - }, - /** - * Returns image URL for the avatar to be displayed on large video area - * where current active speaker is presented. - * @param jid full MUC jid of the user for whom we want to obtain avatar URL - */ - getActiveSpeakerUrl: function (jid) { - return this.getGravatarUrl(jid, 100); - }, - /** - * Returns image URL for the avatar to be displayed on small video thumbnail - * @param jid full MUC jid of the user for whom we want to obtain avatar URL - */ - getThumbUrl: function (jid) { - return this.getGravatarUrl(jid, 100); - }, - /** - * Returns the URL for the avatar to be displayed as contactlist item - * @param jid full MUC jid of the user for whom we want to obtain avatar URL - */ - getContactListUrl: function (jid) { - return this.getGravatarUrl(jid, 30); - }, - getGravatarUrl: function (jid, size) { - if (!jid) { - console.error("Get gravatar - jid is undefined"); - return null; - } - var id = users[jid]; - if (!id) { - console.warn( - "No avatar stored yet for " + jid + " - using JID as ID"); - id = jid; - } - return 'https://www.gravatar.com/avatar/' + - MD5.hexdigest(id.trim().toLowerCase()) + - "?d=wavatar&size=" + (size || "30"); - } - -}; - - +},{"../../xmpp/moderator":62,"../../xmpp/xmpp":70}],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":47}],17:[function(require,module,exports){ -/* global $, config, - setLargeVideoVisible, Util */ - -var VideoLayout = require("../videolayout/VideoLayout"); -var Prezi = require("../prezi/Prezi"); -var UIUtil = require("../util/UIUtil"); - -var etherpadName = null; -var etherpadIFrame = null; -var domain = null; -var options = "?showControls=true&showChat=false&showLineNumbers=true&useMonospaceFont=false"; - - -/** - * Resizes the etherpad. - */ -function resize() { - if ($('#etherpad>iframe').length) { - var remoteVideos = $('#remoteVideos'); - var availableHeight - = window.innerHeight - remoteVideos.outerHeight(); - var availableWidth = UIUtil.getAvailableVideoWidth(); - - $('#etherpad>iframe').width(availableWidth); - $('#etherpad>iframe').height(availableHeight); - } -} - -/** - * Creates the Etherpad button and adds it to the toolbar. - */ -function enableEtherpadButton() { - if (!$('#toolbar_button_etherpad').is(":visible")) - $('#toolbar_button_etherpad').css({display: 'inline-block'}); -} - -/** - * Creates the IFrame for the etherpad. - */ -function createIFrame() { - etherpadIFrame = VideoLayout.createEtherpadIframe( - domain + etherpadName + options, function() { - - document.domain = document.domain; - bubbleIframeMouseMove(etherpadIFrame); - setTimeout(function() { - // the iframes inside of the etherpad are - // not yet loaded when the etherpad iframe is loaded - var outer = etherpadIFrame. - contentDocument.getElementsByName("ace_outer")[0]; - bubbleIframeMouseMove(outer); - var inner = outer. - contentDocument.getElementsByName("ace_inner")[0]; - bubbleIframeMouseMove(inner); - }, 2000); - }); -} - -function bubbleIframeMouseMove(iframe){ - var existingOnMouseMove = iframe.contentWindow.onmousemove; - iframe.contentWindow.onmousemove = function(e){ - if(existingOnMouseMove) existingOnMouseMove(e); - var evt = document.createEvent("MouseEvents"); - var boundingClientRect = iframe.getBoundingClientRect(); - evt.initMouseEvent( - "mousemove", - true, // bubbles - false, // not cancelable - window, - e.detail, - e.screenX, - e.screenY, - e.clientX + boundingClientRect.left, - e.clientY + boundingClientRect.top, - e.ctrlKey, - e.altKey, - e.shiftKey, - e.metaKey, - e.button, - null // no related element - ); - iframe.dispatchEvent(evt); - }; -} - - -var Etherpad = { - /** - * Initializes the etherpad. - */ - init: function (name) { - - if (config.etherpad_base && !etherpadName && name) { - - domain = config.etherpad_base; - - etherpadName = name; - - enableEtherpadButton(); - - /** - * Resizes the etherpad, when the window is resized. - */ - $(window).resize(function () { - resize(); - }); - } - }, - - /** - * Opens/hides the Etherpad. - */ - toggleEtherpad: function (isPresentation) { - if (!etherpadIFrame) - createIFrame(); - - - if(VideoLayout.getLargeVideoState() === "etherpad") - { - VideoLayout.setLargeVideoState("video"); - } - else - { - VideoLayout.setLargeVideoState("etherpad"); - } - resize(); - } -}; - -module.exports = Etherpad; - -},{"../prezi/Prezi":18,"../util/UIUtil":33,"../videolayout/VideoLayout":39}],18:[function(require,module,exports){ -var ToolbarToggler = require("../toolbars/ToolbarToggler"); -var UIUtil = require("../util/UIUtil"); -var VideoLayout = require("../videolayout/VideoLayout"); -var messageHandler = require("../util/MessageHandler"); -var PreziPlayer = require("./PreziPlayer"); - -var preziPlayer = null; - - -/** - * Shows/hides a presentation. - */ -function setPresentationVisible(visible) { - - if (visible) { - VideoLayout.setLargeVideoState("prezi"); - } - else { - VideoLayout.setLargeVideoState("video"); - } -} - -var Prezi = { - - - /** - * Reloads the current presentation. - */ - reloadPresentation: function() { - var iframe = document.getElementById(preziPlayer.options.preziId); - iframe.src = iframe.src; - }, - - /** - * Returns true if the presentation is visible, false - - * otherwise. - */ - isPresentationVisible: function () { - return ($('#presentation>iframe') != null - && $('#presentation>iframe').css('opacity') == 1); - }, - - /** - * Opens the Prezi dialog, from which the user could choose a presentation - * to load. - */ - openPreziDialog: function() { - var myprezi = APP.xmpp.getPrezi(); - if (myprezi) { - messageHandler.openTwoButtonDialog("dialog.removePreziTitle", - null, - "dialog.removePreziMsg", - null, - false, - "dialog.Remove", - function(e,v,m,f) { - if(v) { - APP.xmpp.removePreziFromPresence(); - } - } - ); - } - else if (preziPlayer != null) { - messageHandler.openTwoButtonDialog("dialog.sharePreziTitle", - null, "dialog.sharePreziMsg", - null, - false, - "dialog.Ok", - function(e,v,m,f) { - $.prompt.close(); - } - ); - } - else { - var html = APP.translation.generateTranslationHTML( - "dialog.sharePreziTitle"); - var cancelButton = APP.translation.generateTranslationHTML( - "dialog.Cancel"); - var shareButton = APP.translation.generateTranslationHTML( - "dialog.Share"); - var backButton = APP.translation.generateTranslationHTML( - "dialog.Back"); - var buttons = []; - var buttons1 = []; - // Cancel button to both states - buttons.push({title: cancelButton, value: false}); - buttons1.push({title: cancelButton, value: false}); - // Share button - buttons.push({title: shareButton, value: true}); - // Back button - buttons1.push({title: backButton, value: true}); - var linkError = APP.translation.generateTranslationHTML( - "dialog.preziLinkError"); - var defaultUrl = APP.translation.translateString("defaultPreziLink", - {url: "http://prezi.com/wz7vhjycl7e6/my-prezi"}); - var openPreziState = { - state0: { - html: '

' + html + '

' + - '', - persistent: false, - buttons: buttons, - focus: ':input:first', - defaultButton: 0, - submit: function (e, v, m, f) { - e.preventDefault(); - if(v) - { - var preziUrl = f.preziUrl; - - if (preziUrl) - { - var urlValue - = encodeURI(UIUtil.escapeHtml(preziUrl)); - - if (urlValue.indexOf('http://prezi.com/') != 0 - && urlValue.indexOf('https://prezi.com/') != 0) - { - $.prompt.goToState('state1'); - return false; - } - else { - var presIdTmp = urlValue.substring( - urlValue.indexOf("prezi.com/") + 10); - if (!isAlphanumeric(presIdTmp) - || presIdTmp.indexOf('/') < 2) { - $.prompt.goToState('state1'); - return false; - } - else { - APP.xmpp.addToPresence("prezi", urlValue); - $.prompt.close(); - } - } - } - } - else - $.prompt.close(); - } - }, - state1: { - html: '

' + html + '

' + - linkError, - persistent: false, - buttons: buttons1, - focus: ':input:first', - defaultButton: 1, - submit: function (e, v, m, f) { - e.preventDefault(); - if (v === 0) - $.prompt.close(); - else - $.prompt.goToState('state0'); - } - } - }; - messageHandler.openDialogWithStates(openPreziState); - } - } - -}; - -/** - * A new presentation has been added. - * - * @param event the event indicating the add of a presentation - * @param jid the jid from which the presentation was added - * @param presUrl url of the presentation - * @param currentSlide the current slide to which we should move - */ -function presentationAdded(event, jid, presUrl, currentSlide) { - console.log("presentation added", presUrl); - - var presId = getPresentationId(presUrl); - - var elementId = 'participant_' - + Strophe.getResourceFromJid(jid) - + '_' + presId; - - VideoLayout.addPreziContainer(elementId); - - var controlsEnabled = false; - if (jid === APP.xmpp.myJid()) - controlsEnabled = true; - - setPresentationVisible(true); - VideoLayout.setLargeVideoHover( - function (event) { - if (Prezi.isPresentationVisible()) { - var reloadButtonRight = window.innerWidth - - $('#presentation>iframe').offset().left - - $('#presentation>iframe').width(); - - $('#reloadPresentation').css({ right: reloadButtonRight, - display:'inline-block'}); - } - }, - function (event) { - if (!Prezi.isPresentationVisible()) - $('#reloadPresentation').css({display:'none'}); - else { - var e = event.toElement || event.relatedTarget; - - if (e && e.id != 'reloadPresentation' && e.id != 'header') - $('#reloadPresentation').css({display:'none'}); - } - }); - - preziPlayer = new PreziPlayer( - 'presentation', - {preziId: presId, - width: getPresentationWidth(), - height: getPresentationHeihgt(), - controls: controlsEnabled, - debug: true - }); - - $('#presentation>iframe').attr('id', preziPlayer.options.preziId); - - preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) { - console.log("prezi status", event.value); - if (event.value == PreziPlayer.STATUS_CONTENT_READY) { - if (jid != APP.xmpp.myJid()) - preziPlayer.flyToStep(currentSlide); - } - }); - - preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) { - console.log("event value", event.value); - APP.xmpp.addToPresence("preziSlide", event.value); - }); - - $("#" + elementId).css( 'background-image', - 'url(../images/avatarprezi.png)'); - $("#" + elementId).click( - function () { - setPresentationVisible(true); - } - ); -}; - -/** - * A presentation has been removed. - * - * @param event the event indicating the remove of a presentation - * @param jid the jid for which the presentation was removed - * @param the url of the presentation - */ -function presentationRemoved(event, jid, presUrl) { - console.log('presentation removed', presUrl); - var presId = getPresentationId(presUrl); - setPresentationVisible(false); - $('#participant_' - + Strophe.getResourceFromJid(jid) - + '_' + presId).remove(); - $('#presentation>iframe').remove(); - if (preziPlayer != null) { - preziPlayer.destroy(); - preziPlayer = null; - } -}; - -/** - * Indicates if the given string is an alphanumeric string. - * Note that some special characters are also allowed (-, _ , /, &, ?, =, ;) for the - * purpose of checking URIs. - */ -function isAlphanumeric(unsafeText) { - var regex = /^[a-z0-9-_\/&\?=;]+$/i; - return regex.test(unsafeText); -} - -/** - * Returns the presentation id from the given url. - */ -function getPresentationId (presUrl) { - var presIdTmp = presUrl.substring(presUrl.indexOf("prezi.com/") + 10); - return presIdTmp.substring(0, presIdTmp.indexOf('/')); -} - -/** - * Returns the presentation width. - */ -function getPresentationWidth() { - var availableWidth = UIUtil.getAvailableVideoWidth(); - var availableHeight = getPresentationHeihgt(); - - var aspectRatio = 16.0 / 9.0; - if (availableHeight < availableWidth / aspectRatio) { - availableWidth = Math.floor(availableHeight * aspectRatio); - } - return availableWidth; -} - -/** - * Returns the presentation height. - */ -function getPresentationHeihgt() { - var remoteVideos = $('#remoteVideos'); - return window.innerHeight - remoteVideos.outerHeight(); -} - -/** - * Resizes the presentation iframe. - */ -function resize() { - if ($('#presentation>iframe')) { - $('#presentation>iframe').width(getPresentationWidth()); - $('#presentation>iframe').height(getPresentationHeihgt()); - } -} - -/** - * Presentation has been removed. - */ -$(document).bind('presentationremoved.muc', presentationRemoved); - -/** - * Presentation has been added. - */ -$(document).bind('presentationadded.muc', presentationAdded); - -/* - * Indicates presentation slide change. - */ -$(document).bind('gotoslide.muc', function (event, jid, presUrl, current) { - if (preziPlayer && preziPlayer.getCurrentStep() != current) { - preziPlayer.flyToStep(current); - - var animationStepsArray = preziPlayer.getAnimationCountOnSteps(); - for (var i = 0; i < parseInt(animationStepsArray[current]); i++) { - preziPlayer.flyToStep(current, i); - } - } -}); - -$(window).resize(function () { - resize(); -}); - -module.exports = Prezi; - -},{"../toolbars/ToolbarToggler":29,"../util/MessageHandler":31,"../util/UIUtil":33,"../videolayout/VideoLayout":39,"./PreziPlayer":19}],19:[function(require,module,exports){ -(function() { - "use strict"; - var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - window.PreziPlayer = (function() { - - PreziPlayer.API_VERSION = 1; - PreziPlayer.CURRENT_STEP = 'currentStep'; - PreziPlayer.CURRENT_ANIMATION_STEP = 'currentAnimationStep'; - PreziPlayer.CURRENT_OBJECT = 'currentObject'; - PreziPlayer.STATUS_LOADING = 'loading'; - PreziPlayer.STATUS_READY = 'ready'; - PreziPlayer.STATUS_CONTENT_READY = 'contentready'; - PreziPlayer.EVENT_CURRENT_STEP = "currentStepChange"; - PreziPlayer.EVENT_CURRENT_ANIMATION_STEP = "currentAnimationStepChange"; - PreziPlayer.EVENT_CURRENT_OBJECT = "currentObjectChange"; - PreziPlayer.EVENT_STATUS = "statusChange"; - PreziPlayer.EVENT_PLAYING = "isAutoPlayingChange"; - PreziPlayer.EVENT_IS_MOVING = "isMovingChange"; - PreziPlayer.domain = "https://prezi.com"; - PreziPlayer.path = "/player/"; - PreziPlayer.players = {}; - PreziPlayer.binded_methods = ['changesHandler']; - - PreziPlayer.createMultiplePlayers = function(optionArray){ - for(var i=0; i 0 && - obj.values.animationCountOnSteps && - obj.values.animationCountOnSteps[step] <= animation_step) { - animation_step = obj.values.animationCountOnSteps[step]; - } - // jump to animation steps by calling flyToNextStep() - function doAnimationSteps() { - if (obj.values.isMoving == true) { - setTimeout(doAnimationSteps, 100); // wait until the flight ends - return; - } - while (animation_step-- > 0) { - obj.flyToNextStep(); // do the animation steps - } - } - setTimeout(doAnimationSteps, 200); // 200ms is the internal "reporting" time - // jump to the step - return this.sendMessage({ - 'action': 'present', - 'data': ['moveToStep', step] - }); - }; - - PreziPlayer.prototype.toObject = /* toObject is DEPRECATED */ - PreziPlayer.prototype.flyToObject = function(objectId) { - return this.sendMessage({ - 'action': 'present', - 'data': ['moveToObject', objectId] - }); - }; - - PreziPlayer.prototype.play = function(defaultDelay) { - return this.sendMessage({ - 'action': 'present', - 'data': ['startAutoPlay', defaultDelay] - }); - }; - - PreziPlayer.prototype.stop = function() { - return this.sendMessage({ - 'action': 'present', - 'data': ['stopAutoPlay'] - }); - }; - - PreziPlayer.prototype.pause = function(defaultDelay) { - return this.sendMessage({ - 'action': 'present', - 'data': ['pauseAutoPlay', defaultDelay] - }); - }; - - PreziPlayer.prototype.getCurrentStep = function() { - return this.values.currentStep; - }; - - PreziPlayer.prototype.getCurrentAnimationStep = function() { - return this.values.currentAnimationStep; - }; - - PreziPlayer.prototype.getCurrentObject = function() { - return this.values.currentObject; - }; - - PreziPlayer.prototype.getStatus = function() { - return this.values.status; - }; - - PreziPlayer.prototype.isPlaying = function() { - return this.values.isAutoPlaying; - }; - - PreziPlayer.prototype.getStepCount = function() { - return this.values.stepCount; - }; - - PreziPlayer.prototype.getAnimationCountOnSteps = function() { - return this.values.animationCountOnSteps; - }; - - PreziPlayer.prototype.getTitle = function() { - return this.values.title; - }; - - PreziPlayer.prototype.setDimensions = function(dims) { - for (var parameter in dims) { - this.iframe[parameter] = dims[parameter]; - } - } - - PreziPlayer.prototype.getDimensions = function() { - return { - width: parseInt(this.iframe.width, 10), - height: parseInt(this.iframe.height, 10) - } - } - - PreziPlayer.prototype.on = function(event, callback) { - this.callbacks.push({ - event: event, - callback: callback - }); - }; - - PreziPlayer.prototype.off = function(event, callback) { - var j, item; - if (event === undefined) { - this.callbacks = []; - } - j = this.callbacks.length; - while (j--) { - item = this.callbacks[j]; - if (item && item.event === event && (callback === undefined || item.callback === callback)){ - this.callbacks.splice(j, 1); - } - } - }; - - if (window.addEventListener) { - window.addEventListener('message', PreziPlayer.messageReceived, false); - } else { - window.attachEvent('onmessage', PreziPlayer.messageReceived); - } - - return PreziPlayer; - - })(); - -})(); - -module.exports = PreziPlayer; - -},{}],20:[function(require,module,exports){ -/* global require, $ */ -var Chat = require("./chat/Chat"); -var ContactList = require("./contactlist/ContactList"); -var Settings = require("./../../settings/Settings"); -var SettingsMenu = require("./settings/SettingsMenu"); -var VideoLayout = require("../videolayout/VideoLayout"); -var ToolbarToggler = require("../toolbars/ToolbarToggler"); -var UIUtil = require("../util/UIUtil"); -var LargeVideo = require("../videolayout/LargeVideo"); - -/** - * Toggler for the chat, contact list, settings menu, etc.. - */ -var PanelToggler = (function(my) { - - var currentlyOpen = null; - var buttons = { - '#chatspace': '#chatBottomButton', - '#contactlist': '#contactListButton', - '#settingsmenu': '#toolbar_button_settings' - }; - - /** - * Toggles the windows in the side panel - * @param object the window that should be shown - * @param selector the selector for the element containing the panel - * @param onOpenComplete function to be called when the panel is opened - * @param onOpen function to be called if the window is going to be opened - * @param onClose function to be called if the window is going to be closed - */ - var toggle = function(object, selector, onOpenComplete, onOpen, onClose) { - UIUtil.buttonClick(buttons[selector], "active"); - - if (object.isVisible()) { - $("#toast-container").animate({ - right: '5px' - }, - { - queue: false, - duration: 500 - }); - $(selector).hide("slide", { - direction: "right", - queue: false, - duration: 500 - }); - if(typeof onClose === "function") { - onClose(); - } - - currentlyOpen = null; - } - else { - // Undock the toolbar when the chat is shown and if we're in a - // video mode. - if (LargeVideo.isLargeVideoVisible()) { - ToolbarToggler.dockToolbar(false); - } - - if(currentlyOpen) { - var current = $(currentlyOpen); - UIUtil.buttonClick(buttons[currentlyOpen], "active"); - current.css('z-index', 4); - setTimeout(function () { - current.css('display', 'none'); - current.css('z-index', 5); - }, 500); - } - - $("#toast-container").animate({ - right: (PanelToggler.getPanelSize()[0] + 5) + 'px' - }, - { - queue: false, - duration: 500 - }); - $(selector).show("slide", { - direction: "right", - queue: false, - duration: 500, - complete: onOpenComplete - }); - if(typeof onOpen === "function") { - onOpen(); - } - - currentlyOpen = selector; - } - }; - - /** - * Opens / closes the chat area. - */ - my.toggleChat = function() { - var chatCompleteFunction = Chat.isVisible() ? - function() {} : function () { - Chat.scrollChatToBottom(); - $('#chatspace').trigger('shown'); - }; - - VideoLayout.resizeVideoArea(!Chat.isVisible(), chatCompleteFunction); - - toggle(Chat, - '#chatspace', - function () { - // Request the focus in the nickname field or the chat input field. - if ($('#nickname').css('visibility') === 'visible') { - $('#nickinput').focus(); - } else { - $('#usermsg').focus(); - } - }, - null, - Chat.resizeChat, - null); - }; - - /** - * Opens / closes the contact list area. - */ - my.toggleContactList = function () { - var completeFunction = ContactList.isVisible() ? - function() {} : function () { $('#contactlist').trigger('shown');}; - VideoLayout.resizeVideoArea(!ContactList.isVisible(), completeFunction); - - toggle(ContactList, - '#contactlist', - null, - function() { - ContactList.setVisualNotification(false); - }, - null); - }; - - /** - * Opens / closes the settings menu - */ - my.toggleSettingsMenu = function() { - VideoLayout.resizeVideoArea(!SettingsMenu.isVisible(), function (){}); - toggle(SettingsMenu, - '#settingsmenu', - null, - function() { - var settings = Settings.getSettings(); - $('#setDisplayName').get(0).value = settings.displayName; - $('#setEmail').get(0).value = settings.email; - }, - null); - }; - - /** - * Returns the size of the side panel. - */ - my.getPanelSize = function () { - var availableHeight = window.innerHeight; - var availableWidth = window.innerWidth; - - var panelWidth = 200; - if (availableWidth * 0.2 < 200) { - panelWidth = availableWidth * 0.2; - } - - return [panelWidth, availableHeight]; - }; - - my.isVisible = function() { - return (Chat.isVisible() || ContactList.isVisible() || SettingsMenu.isVisible()); - }; - - return my; - -}(PanelToggler || {})); - +},{"../../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 (!$('#toolbar_button_etherpad').is(":visible")) + $('#toolbar_button_etherpad').css({display: 'inline-block'}); +} + +/** + * Creates the IFrame for the etherpad. + */ +function createIFrame() { + etherpadIFrame = VideoLayout.createEtherpadIframe( + domain + etherpadName + options, function() { + + document.domain = document.domain; + bubbleIframeMouseMove(etherpadIFrame); + setTimeout(function() { + // the iframes inside of the etherpad are + // not yet loaded when the etherpad iframe is loaded + var outer = etherpadIFrame. + contentDocument.getElementsByName("ace_outer")[0]; + bubbleIframeMouseMove(outer); + var inner = outer. + contentDocument.getElementsByName("ace_inner")[0]; + bubbleIframeMouseMove(inner); + }, 2000); + }); +} + +function bubbleIframeMouseMove(iframe){ + var existingOnMouseMove = iframe.contentWindow.onmousemove; + iframe.contentWindow.onmousemove = function(e){ + if(existingOnMouseMove) existingOnMouseMove(e); + var evt = document.createEvent("MouseEvents"); + var boundingClientRect = iframe.getBoundingClientRect(); + evt.initMouseEvent( + "mousemove", + true, // bubbles + false, // not cancelable + window, + e.detail, + e.screenX, + e.screenY, + e.clientX + boundingClientRect.left, + e.clientY + boundingClientRect.top, + e.ctrlKey, + e.altKey, + e.shiftKey, + e.metaKey, + e.button, + null // no related element + ); + iframe.dispatchEvent(evt); + }; +} + + +var Etherpad = { + /** + * Initializes the etherpad. + */ + init: function (name) { + + if (config.etherpad_base && !etherpadName && name) { + + domain = config.etherpad_base; + + etherpadName = name; + + enableEtherpadButton(); + + /** + * Resizes the etherpad, when the window is resized. + */ + $(window).resize(function () { + resize(); + }); + } + }, + + /** + * Opens/hides the Etherpad. + */ + toggleEtherpad: function (isPresentation) { + if (!etherpadIFrame) + createIFrame(); + + + if(VideoLayout.getLargeVideoState() === "etherpad") + { + VideoLayout.setLargeVideoState("video"); + } + else + { + VideoLayout.setLargeVideoState("etherpad"); + } + resize(); + } +}; + +module.exports = Etherpad; + +},{"../prezi/Prezi":20,"../util/UIUtil":35,"../videolayout/VideoLayout":41}],20:[function(require,module,exports){ +var ToolbarToggler = require("../toolbars/ToolbarToggler"); +var UIUtil = require("../util/UIUtil"); +var VideoLayout = require("../videolayout/VideoLayout"); +var messageHandler = require("../util/MessageHandler"); +var PreziPlayer = require("./PreziPlayer"); + +var preziPlayer = null; + + +/** + * Shows/hides a presentation. + */ +function setPresentationVisible(visible) { + + if (visible) { + VideoLayout.setLargeVideoState("prezi"); + } + else { + VideoLayout.setLargeVideoState("video"); + } +} + +var Prezi = { + + + /** + * Reloads the current presentation. + */ + reloadPresentation: function() { + var iframe = document.getElementById(preziPlayer.options.preziId); + iframe.src = iframe.src; + }, + + /** + * Returns true if the presentation is visible, false - + * otherwise. + */ + isPresentationVisible: function () { + return ($('#presentation>iframe') != null + && $('#presentation>iframe').css('opacity') == 1); + }, + + /** + * Opens the Prezi dialog, from which the user could choose a presentation + * to load. + */ + openPreziDialog: function() { + var myprezi = APP.xmpp.getPrezi(); + if (myprezi) { + messageHandler.openTwoButtonDialog("dialog.removePreziTitle", + null, + "dialog.removePreziMsg", + null, + false, + "dialog.Remove", + function(e,v,m,f) { + if(v) { + APP.xmpp.removePreziFromPresence(); + } + } + ); + } + else if (preziPlayer != null) { + messageHandler.openTwoButtonDialog("dialog.sharePreziTitle", + null, "dialog.sharePreziMsg", + null, + false, + "dialog.Ok", + function(e,v,m,f) { + $.prompt.close(); + } + ); + } + else { + var html = APP.translation.generateTranslationHTML( + "dialog.sharePreziTitle"); + var cancelButton = APP.translation.generateTranslationHTML( + "dialog.Cancel"); + var shareButton = APP.translation.generateTranslationHTML( + "dialog.Share"); + var backButton = APP.translation.generateTranslationHTML( + "dialog.Back"); + var buttons = []; + var buttons1 = []; + // Cancel button to both states + buttons.push({title: cancelButton, value: false}); + buttons1.push({title: cancelButton, value: false}); + // Share button + buttons.push({title: shareButton, value: true}); + // Back button + buttons1.push({title: backButton, value: true}); + var linkError = APP.translation.generateTranslationHTML( + "dialog.preziLinkError"); + var defaultUrl = APP.translation.translateString("defaultPreziLink", + {url: "http://prezi.com/wz7vhjycl7e6/my-prezi"}); + var openPreziState = { + state0: { + html: '

' + html + '

' + + '', + persistent: false, + buttons: buttons, + focus: ':input:first', + defaultButton: 0, + submit: function (e, v, m, f) { + e.preventDefault(); + if(v) + { + var preziUrl = f.preziUrl; + + if (preziUrl) + { + var urlValue + = encodeURI(UIUtil.escapeHtml(preziUrl)); + + if (urlValue.indexOf('http://prezi.com/') != 0 + && urlValue.indexOf('https://prezi.com/') != 0) + { + $.prompt.goToState('state1'); + return false; + } + else { + var presIdTmp = urlValue.substring( + urlValue.indexOf("prezi.com/") + 10); + if (!isAlphanumeric(presIdTmp) + || presIdTmp.indexOf('/') < 2) { + $.prompt.goToState('state1'); + return false; + } + else { + APP.xmpp.addToPresence("prezi", urlValue); + $.prompt.close(); + } + } + } + } + else + $.prompt.close(); + } + }, + state1: { + html: '

' + html + '

' + + linkError, + persistent: false, + buttons: buttons1, + focus: ':input:first', + defaultButton: 1, + submit: function (e, v, m, f) { + e.preventDefault(); + if (v === 0) + $.prompt.close(); + else + $.prompt.goToState('state0'); + } + } + }; + messageHandler.openDialogWithStates(openPreziState); + } + } + +}; + +/** + * A new presentation has been added. + * + * @param event the event indicating the add of a presentation + * @param jid the jid from which the presentation was added + * @param presUrl url of the presentation + * @param currentSlide the current slide to which we should move + */ +function presentationAdded(event, jid, presUrl, currentSlide) { + console.log("presentation added", presUrl); + + var presId = getPresentationId(presUrl); + + var elementId = 'participant_' + + Strophe.getResourceFromJid(jid) + + '_' + presId; + + VideoLayout.addPreziContainer(elementId); + + var controlsEnabled = false; + if (jid === APP.xmpp.myJid()) + controlsEnabled = true; + + setPresentationVisible(true); + VideoLayout.setLargeVideoHover( + function (event) { + if (Prezi.isPresentationVisible()) { + var reloadButtonRight = window.innerWidth + - $('#presentation>iframe').offset().left + - $('#presentation>iframe').width(); + + $('#reloadPresentation').css({ right: reloadButtonRight, + display:'inline-block'}); + } + }, + function (event) { + if (!Prezi.isPresentationVisible()) + $('#reloadPresentation').css({display:'none'}); + else { + var e = event.toElement || event.relatedTarget; + + if (e && e.id != 'reloadPresentation' && e.id != 'header') + $('#reloadPresentation').css({display:'none'}); + } + }); + + preziPlayer = new PreziPlayer( + 'presentation', + {preziId: presId, + width: getPresentationWidth(), + height: getPresentationHeihgt(), + controls: controlsEnabled, + debug: true + }); + + $('#presentation>iframe').attr('id', preziPlayer.options.preziId); + + preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) { + console.log("prezi status", event.value); + if (event.value == PreziPlayer.STATUS_CONTENT_READY) { + if (jid != APP.xmpp.myJid()) + preziPlayer.flyToStep(currentSlide); + } + }); + + preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) { + console.log("event value", event.value); + APP.xmpp.addToPresence("preziSlide", event.value); + }); + + $("#" + elementId).css( 'background-image', + 'url(../images/avatarprezi.png)'); + $("#" + elementId).click( + function () { + setPresentationVisible(true); + } + ); +}; + +/** + * A presentation has been removed. + * + * @param event the event indicating the remove of a presentation + * @param jid the jid for which the presentation was removed + * @param the url of the presentation + */ +function presentationRemoved(event, jid, presUrl) { + console.log('presentation removed', presUrl); + var presId = getPresentationId(presUrl); + setPresentationVisible(false); + $('#participant_' + + Strophe.getResourceFromJid(jid) + + '_' + presId).remove(); + $('#presentation>iframe').remove(); + if (preziPlayer != null) { + preziPlayer.destroy(); + preziPlayer = null; + } +}; + +/** + * Indicates if the given string is an alphanumeric string. + * Note that some special characters are also allowed (-, _ , /, &, ?, =, ;) for the + * purpose of checking URIs. + */ +function isAlphanumeric(unsafeText) { + var regex = /^[a-z0-9-_\/&\?=;]+$/i; + return regex.test(unsafeText); +} + +/** + * Returns the presentation id from the given url. + */ +function getPresentationId (presUrl) { + var presIdTmp = presUrl.substring(presUrl.indexOf("prezi.com/") + 10); + return presIdTmp.substring(0, presIdTmp.indexOf('/')); +} + +/** + * Returns the presentation width. + */ +function getPresentationWidth() { + var availableWidth = UIUtil.getAvailableVideoWidth(); + var availableHeight = getPresentationHeihgt(); + + var aspectRatio = 16.0 / 9.0; + if (availableHeight < availableWidth / aspectRatio) { + availableWidth = Math.floor(availableHeight * aspectRatio); + } + return availableWidth; +} + +/** + * Returns the presentation height. + */ +function getPresentationHeihgt() { + var remoteVideos = $('#remoteVideos'); + return window.innerHeight - remoteVideos.outerHeight(); +} + +/** + * Resizes the presentation iframe. + */ +function resize() { + if ($('#presentation>iframe')) { + $('#presentation>iframe').width(getPresentationWidth()); + $('#presentation>iframe').height(getPresentationHeihgt()); + } +} + +/** + * Presentation has been removed. + */ +$(document).bind('presentationremoved.muc', presentationRemoved); + +/** + * Presentation has been added. + */ +$(document).bind('presentationadded.muc', presentationAdded); + +/* + * Indicates presentation slide change. + */ +$(document).bind('gotoslide.muc', function (event, jid, presUrl, current) { + if (preziPlayer && preziPlayer.getCurrentStep() != current) { + preziPlayer.flyToStep(current); + + var animationStepsArray = preziPlayer.getAnimationCountOnSteps(); + for (var i = 0; i < parseInt(animationStepsArray[current]); i++) { + preziPlayer.flyToStep(current, i); + } + } +}); + +$(window).resize(function () { + resize(); +}); + +module.exports = Prezi; + +},{"../toolbars/ToolbarToggler":31,"../util/MessageHandler":33,"../util/UIUtil":35,"../videolayout/VideoLayout":41,"./PreziPlayer":21}],21:[function(require,module,exports){ +(function() { + "use strict"; + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + window.PreziPlayer = (function() { + + PreziPlayer.API_VERSION = 1; + PreziPlayer.CURRENT_STEP = 'currentStep'; + PreziPlayer.CURRENT_ANIMATION_STEP = 'currentAnimationStep'; + PreziPlayer.CURRENT_OBJECT = 'currentObject'; + PreziPlayer.STATUS_LOADING = 'loading'; + PreziPlayer.STATUS_READY = 'ready'; + PreziPlayer.STATUS_CONTENT_READY = 'contentready'; + PreziPlayer.EVENT_CURRENT_STEP = "currentStepChange"; + PreziPlayer.EVENT_CURRENT_ANIMATION_STEP = "currentAnimationStepChange"; + PreziPlayer.EVENT_CURRENT_OBJECT = "currentObjectChange"; + PreziPlayer.EVENT_STATUS = "statusChange"; + PreziPlayer.EVENT_PLAYING = "isAutoPlayingChange"; + PreziPlayer.EVENT_IS_MOVING = "isMovingChange"; + PreziPlayer.domain = "https://prezi.com"; + PreziPlayer.path = "/player/"; + PreziPlayer.players = {}; + PreziPlayer.binded_methods = ['changesHandler']; + + PreziPlayer.createMultiplePlayers = function(optionArray){ + for(var i=0; i 0 && + obj.values.animationCountOnSteps && + obj.values.animationCountOnSteps[step] <= animation_step) { + animation_step = obj.values.animationCountOnSteps[step]; + } + // jump to animation steps by calling flyToNextStep() + function doAnimationSteps() { + if (obj.values.isMoving == true) { + setTimeout(doAnimationSteps, 100); // wait until the flight ends + return; + } + while (animation_step-- > 0) { + obj.flyToNextStep(); // do the animation steps + } + } + setTimeout(doAnimationSteps, 200); // 200ms is the internal "reporting" time + // jump to the step + return this.sendMessage({ + 'action': 'present', + 'data': ['moveToStep', step] + }); + }; + + PreziPlayer.prototype.toObject = /* toObject is DEPRECATED */ + PreziPlayer.prototype.flyToObject = function(objectId) { + return this.sendMessage({ + 'action': 'present', + 'data': ['moveToObject', objectId] + }); + }; + + PreziPlayer.prototype.play = function(defaultDelay) { + return this.sendMessage({ + 'action': 'present', + 'data': ['startAutoPlay', defaultDelay] + }); + }; + + PreziPlayer.prototype.stop = function() { + return this.sendMessage({ + 'action': 'present', + 'data': ['stopAutoPlay'] + }); + }; + + PreziPlayer.prototype.pause = function(defaultDelay) { + return this.sendMessage({ + 'action': 'present', + 'data': ['pauseAutoPlay', defaultDelay] + }); + }; + + PreziPlayer.prototype.getCurrentStep = function() { + return this.values.currentStep; + }; + + PreziPlayer.prototype.getCurrentAnimationStep = function() { + return this.values.currentAnimationStep; + }; + + PreziPlayer.prototype.getCurrentObject = function() { + return this.values.currentObject; + }; + + PreziPlayer.prototype.getStatus = function() { + return this.values.status; + }; + + PreziPlayer.prototype.isPlaying = function() { + return this.values.isAutoPlaying; + }; + + PreziPlayer.prototype.getStepCount = function() { + return this.values.stepCount; + }; + + PreziPlayer.prototype.getAnimationCountOnSteps = function() { + return this.values.animationCountOnSteps; + }; + + PreziPlayer.prototype.getTitle = function() { + return this.values.title; + }; + + PreziPlayer.prototype.setDimensions = function(dims) { + for (var parameter in dims) { + this.iframe[parameter] = dims[parameter]; + } + } + + PreziPlayer.prototype.getDimensions = function() { + return { + width: parseInt(this.iframe.width, 10), + height: parseInt(this.iframe.height, 10) + } + } + + PreziPlayer.prototype.on = function(event, callback) { + this.callbacks.push({ + event: event, + callback: callback + }); + }; + + PreziPlayer.prototype.off = function(event, callback) { + var j, item; + if (event === undefined) { + this.callbacks = []; + } + j = this.callbacks.length; + while (j--) { + item = this.callbacks[j]; + if (item && item.event === event && (callback === undefined || item.callback === callback)){ + this.callbacks.splice(j, 1); + } + } + }; + + if (window.addEventListener) { + window.addEventListener('message', PreziPlayer.messageReceived, false); + } else { + window.attachEvent('onmessage', PreziPlayer.messageReceived); + } + + return PreziPlayer; + + })(); + +})(); + +module.exports = PreziPlayer; + +},{}],22:[function(require,module,exports){ +/* global require, $ */ +var Chat = require("./chat/Chat"); +var ContactList = require("./contactlist/ContactList"); +var Settings = require("./../../settings/Settings"); +var SettingsMenu = require("./settings/SettingsMenu"); +var VideoLayout = require("../videolayout/VideoLayout"); +var ToolbarToggler = require("../toolbars/ToolbarToggler"); +var UIUtil = require("../util/UIUtil"); +var LargeVideo = require("../videolayout/LargeVideo"); + +/** + * Toggler for the chat, contact list, settings menu, etc.. + */ +var PanelToggler = (function(my) { + + var currentlyOpen = null; + var buttons = { + '#chatspace': '#chatBottomButton', + '#contactlist': '#contactListButton', + '#settingsmenu': '#toolbar_button_settings' + }; + + /** + * Toggles the windows in the side panel + * @param object the window that should be shown + * @param selector the selector for the element containing the panel + * @param onOpenComplete function to be called when the panel is opened + * @param onOpen function to be called if the window is going to be opened + * @param onClose function to be called if the window is going to be closed + */ + var toggle = function(object, selector, onOpenComplete, onOpen, onClose) { + UIUtil.buttonClick(buttons[selector], "active"); + + if (object.isVisible()) { + $("#toast-container").animate({ + right: '5px' + }, + { + queue: false, + duration: 500 + }); + $(selector).hide("slide", { + direction: "right", + queue: false, + duration: 500 + }); + if(typeof onClose === "function") { + onClose(); + } + + currentlyOpen = null; + } + else { + // Undock the toolbar when the chat is shown and if we're in a + // video mode. + if (LargeVideo.isLargeVideoVisible()) { + ToolbarToggler.dockToolbar(false); + } + + if(currentlyOpen) { + var current = $(currentlyOpen); + UIUtil.buttonClick(buttons[currentlyOpen], "active"); + current.css('z-index', 4); + setTimeout(function () { + current.css('display', 'none'); + current.css('z-index', 5); + }, 500); + } + + $("#toast-container").animate({ + right: (PanelToggler.getPanelSize()[0] + 5) + 'px' + }, + { + queue: false, + duration: 500 + }); + $(selector).show("slide", { + direction: "right", + queue: false, + duration: 500, + complete: onOpenComplete + }); + if(typeof onOpen === "function") { + onOpen(); + } + + currentlyOpen = selector; + } + }; + + /** + * Opens / closes the chat area. + */ + my.toggleChat = function() { + var chatCompleteFunction = Chat.isVisible() ? + function() {} : function () { + Chat.scrollChatToBottom(); + $('#chatspace').trigger('shown'); + }; + + VideoLayout.resizeVideoArea(!Chat.isVisible(), chatCompleteFunction); + + toggle(Chat, + '#chatspace', + function () { + // Request the focus in the nickname field or the chat input field. + if ($('#nickname').css('visibility') === 'visible') { + $('#nickinput').focus(); + } else { + $('#usermsg').focus(); + } + }, + null, + Chat.resizeChat, + null); + }; + + /** + * Opens / closes the contact list area. + */ + my.toggleContactList = function () { + var completeFunction = ContactList.isVisible() ? + function() {} : function () { $('#contactlist').trigger('shown');}; + VideoLayout.resizeVideoArea(!ContactList.isVisible(), completeFunction); + + toggle(ContactList, + '#contactlist', + null, + function() { + ContactList.setVisualNotification(false); + }, + null); + }; + + /** + * Opens / closes the settings menu + */ + my.toggleSettingsMenu = function() { + VideoLayout.resizeVideoArea(!SettingsMenu.isVisible(), function (){}); + toggle(SettingsMenu, + '#settingsmenu', + null, + function() { + var settings = Settings.getSettings(); + $('#setDisplayName').get(0).value = settings.displayName; + $('#setEmail').get(0).value = settings.email; + }, + null); + }; + + /** + * Returns the size of the side panel. + */ + my.getPanelSize = function () { + var availableHeight = window.innerHeight; + var availableWidth = window.innerWidth; + + var panelWidth = 200; + if (availableWidth * 0.2 < 200) { + panelWidth = availableWidth * 0.2; + } + + return [panelWidth, availableHeight]; + }; + + my.isVisible = function() { + return (Chat.isVisible() || ContactList.isVisible() || SettingsMenu.isVisible()); + }; + + return my; + +}(PanelToggler || {})); + module.exports = PanelToggler; -},{"../toolbars/ToolbarToggler":29,"../util/UIUtil":33,"../videolayout/LargeVideo":35,"../videolayout/VideoLayout":39,"./../../settings/Settings":47,"./chat/Chat":21,"./contactlist/ContactList":25,"./settings/SettingsMenu":26}],21:[function(require,module,exports){ -/* global APP, $, Util, nickname:true */ -var Replacement = require("./Replacement"); -var CommandsProcessor = require("./Commands"); -var ToolbarToggler = require("../../toolbars/ToolbarToggler"); -var smileys = require("./smileys.json").smileys; -var NicknameHandler = require("../../util/NicknameHandler"); -var UIUtil = require("../../util/UIUtil"); -var UIEvents = require("../../../../service/UI/UIEvents"); - -var notificationInterval = false; -var unreadMessages = 0; - - -/** - * Shows/hides a visual notification, indicating that a message has arrived. - */ -function setVisualNotification(show) { - var unreadMsgElement = document.getElementById('unreadMessages'); - var unreadMsgBottomElement - = document.getElementById('bottomUnreadMessages'); - - var glower = $('#toolbar_button_chat'); - var bottomGlower = $('#chatBottomButton'); - - if (unreadMessages) { - unreadMsgElement.innerHTML = unreadMessages.toString(); - unreadMsgBottomElement.innerHTML = unreadMessages.toString(); - - ToolbarToggler.dockToolbar(true); - - var chatButtonElement - = document.getElementById('toolbar_button_chat'); - var leftIndent = (UIUtil.getTextWidth(chatButtonElement) - - UIUtil.getTextWidth(unreadMsgElement)) / 2; - var topIndent = (UIUtil.getTextHeight(chatButtonElement) - - UIUtil.getTextHeight(unreadMsgElement)) / 2 - 3; - - unreadMsgElement.setAttribute( - 'style', - 'top:' + topIndent + - '; left:' + leftIndent + ';'); - - var chatBottomButtonElement - = document.getElementById('chatBottomButton').parentNode; - var bottomLeftIndent = (UIUtil.getTextWidth(chatBottomButtonElement) - - UIUtil.getTextWidth(unreadMsgBottomElement)) / 2; - var bottomTopIndent = (UIUtil.getTextHeight(chatBottomButtonElement) - - UIUtil.getTextHeight(unreadMsgBottomElement)) / 2 - 2; - - unreadMsgBottomElement.setAttribute( - 'style', - 'top:' + bottomTopIndent + - '; left:' + bottomLeftIndent + ';'); - - - if (!glower.hasClass('icon-chat-simple')) { - glower.removeClass('icon-chat'); - glower.addClass('icon-chat-simple'); - } - } - else { - unreadMsgElement.innerHTML = ''; - unreadMsgBottomElement.innerHTML = ''; - glower.removeClass('icon-chat-simple'); - glower.addClass('icon-chat'); - } - - if (show && !notificationInterval) { - notificationInterval = window.setInterval(function () { - glower.toggleClass('active'); - bottomGlower.toggleClass('active glowing'); - }, 800); - } - else if (!show && notificationInterval) { - window.clearInterval(notificationInterval); - notificationInterval = false; - glower.removeClass('active'); - bottomGlower.removeClass('glowing'); - bottomGlower.addClass('active'); - } -} - - -/** - * Returns the current time in the format it is shown to the user - * @returns {string} - */ -function getCurrentTime(stamp) { - var now = (stamp? new Date(stamp): new Date()); - var hour = now.getHours(); - var minute = now.getMinutes(); - var second = now.getSeconds(); - if(hour.toString().length === 1) { - hour = '0'+hour; - } - if(minute.toString().length === 1) { - minute = '0'+minute; - } - if(second.toString().length === 1) { - second = '0'+second; - } - return hour+':'+minute+':'+second; -} - -function toggleSmileys() { - var smileys = $('#smileysContainer'); - if(!smileys.is(':visible')) { - smileys.show("slide", { direction: "down", duration: 300}); - } else { - smileys.hide("slide", { direction: "down", duration: 300}); - } - $('#usermsg').focus(); -} - -function addClickFunction(smiley, number) { - smiley.onclick = function addSmileyToMessage() { - var usermsg = $('#usermsg'); - var message = usermsg.val(); - message += smileys['smiley' + number]; - usermsg.val(message); - usermsg.get(0).setSelectionRange(message.length, message.length); - toggleSmileys(); - usermsg.focus(); - }; -} - -/** - * Adds the smileys container to the chat - */ -function addSmileys() { - var smileysContainer = document.createElement('div'); - smileysContainer.id = 'smileysContainer'; - for(var i = 1; i <= 21; i++) { - var smileyContainer = document.createElement('div'); - smileyContainer.id = 'smiley' + i; - smileyContainer.className = 'smileyContainer'; - var smiley = document.createElement('img'); - smiley.src = 'images/smileys/smiley' + i + '.svg'; - smiley.className = 'smiley'; - addClickFunction(smiley, i); - smileyContainer.appendChild(smiley); - smileysContainer.appendChild(smileyContainer); - } - - $("#chatspace").append(smileysContainer); -} - -/** - * Resizes the chat conversation. - */ -function resizeChatConversation() { - var msgareaHeight = $('#usermsg').outerHeight(); - var chatspace = $('#chatspace'); - var width = chatspace.width(); - var chat = $('#chatconversation'); - var smileys = $('#smileysarea'); - - smileys.height(msgareaHeight); - $("#smileys").css('bottom', (msgareaHeight - 26) / 2); - $('#smileysContainer').css('bottom', msgareaHeight); - chat.width(width - 10); - chat.height(window.innerHeight - 15 - msgareaHeight); -} - -/** - * Chat related user interface. - */ -var Chat = (function (my) { - /** - * Initializes chat related interface. - */ - my.init = function () { - if(NicknameHandler.getNickname()) - Chat.setChatConversationMode(true); - NicknameHandler.addListener(UIEvents.NICKNAME_CHANGED, - function (nickname) { - Chat.setChatConversationMode(true); - }); - - $('#nickinput').keydown(function (event) { - if (event.keyCode === 13) { - event.preventDefault(); - var val = UIUtil.escapeHtml(this.value); - this.value = ''; - if (!NicknameHandler.getNickname()) { - NicknameHandler.setNickname(val); - - return; - } - } - }); - - var usermsg = $('#usermsg'); - usermsg.keydown(function (event) { - if (event.keyCode === 13) { - event.preventDefault(); - var value = this.value; - usermsg.val('').trigger('autosize.resize'); - this.focus(); - var command = new CommandsProcessor(value); - if(command.isCommand()) { - command.processCommand(); - } - else { - var message = UIUtil.escapeHtml(value); - APP.xmpp.sendChatMessage(message, NicknameHandler.getNickname()); - } - } - }); - - var onTextAreaResize = function () { - resizeChatConversation(); - Chat.scrollChatToBottom(); - }; - usermsg.autosize({callback: onTextAreaResize}); - - $("#chatspace").bind("shown", - function () { - unreadMessages = 0; - setVisualNotification(false); - }); - - addSmileys(); - }; - - /** - * Appends the given message to the chat conversation. - */ - my.updateChatConversation = - function (from, displayName, message, myjid, stamp) { - var divClassName = ''; - - if (APP.xmpp.myJid() === from) { - divClassName = "localuser"; - } - else { - divClassName = "remoteuser"; - - if (!Chat.isVisible()) { - unreadMessages++; - UIUtil.playSoundNotification('chatNotification'); - setVisualNotification(true); - } - } - - // replace links and smileys - // Strophe already escapes special symbols on sending, - // so we escape here only tags to avoid double & - var escMessage = message.replace(//g, '>').replace(/\n/g, '
'); - var escDisplayName = UIUtil.escapeHtml(displayName); - message = Replacement.processReplacements(escMessage); - - var messageContainer = - '
'+ - '' + - '
' + escDisplayName + - '
' + '
' + getCurrentTime(stamp) + - '
' + '
' + message + '
' + - '
'; - - $('#chatconversation').append(messageContainer); - $('#chatconversation').animate( - { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000); - }; - - /** - * Appends error message to the conversation - * @param errorMessage the received error message. - * @param originalText the original message. - */ - my.chatAddError = function(errorMessage, originalText) { - errorMessage = UIUtil.escapeHtml(errorMessage); - originalText = UIUtil.escapeHtml(originalText); - - $('#chatconversation').append( - '
Error: ' + 'Your message' + - (originalText? (' \"'+ originalText + '\"') : "") + - ' was not sent.' + - (errorMessage? (' Reason: ' + errorMessage) : '') + '
'); - $('#chatconversation').animate( - { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000); - }; - - /** - * Sets the subject to the UI - * @param subject the subject - */ - my.chatSetSubject = function(subject) { - if (subject) - subject = subject.trim(); - $('#subject').html(Replacement.linkify(UIUtil.escapeHtml(subject))); - if(subject === "") { - $("#subject").css({display: "none"}); - } - else { - $("#subject").css({display: "block"}); - } - }; - - /** - * Sets the chat conversation mode. - */ - my.setChatConversationMode = function (isConversationMode) { - if (isConversationMode) { - $('#nickname').css({visibility: 'hidden'}); - $('#chatconversation').css({visibility: 'visible'}); - $('#usermsg').css({visibility: 'visible'}); - $('#smileysarea').css({visibility: 'visible'}); - $('#usermsg').focus(); - } - }; - - /** - * Resizes the chat area. - */ - my.resizeChat = function () { - var chatSize = require("../SidePanelToggler").getPanelSize(); - - $('#chatspace').width(chatSize[0]); - $('#chatspace').height(chatSize[1]); - - resizeChatConversation(); - }; - - /** - * Indicates if the chat is currently visible. - */ - my.isVisible = function () { - return $('#chatspace').is(":visible"); - }; - /** - * Shows and hides the window with the smileys - */ - my.toggleSmileys = toggleSmileys; - - /** - * Scrolls chat to the bottom. - */ - my.scrollChatToBottom = function() { - setTimeout(function () { - $('#chatconversation').scrollTop( - $('#chatconversation')[0].scrollHeight); - }, 5); - }; - - - return my; -}(Chat || {})); +},{"../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 APP, $, Util, nickname:true */ +var Replacement = require("./Replacement"); +var CommandsProcessor = require("./Commands"); +var ToolbarToggler = require("../../toolbars/ToolbarToggler"); +var smileys = require("./smileys.json").smileys; +var NicknameHandler = require("../../util/NicknameHandler"); +var UIUtil = require("../../util/UIUtil"); +var UIEvents = require("../../../../service/UI/UIEvents"); + +var notificationInterval = false; +var unreadMessages = 0; + + +/** + * Shows/hides a visual notification, indicating that a message has arrived. + */ +function setVisualNotification(show) { + var unreadMsgElement = document.getElementById('unreadMessages'); + var unreadMsgBottomElement + = document.getElementById('bottomUnreadMessages'); + + var glower = $('#toolbar_button_chat'); + var bottomGlower = $('#chatBottomButton'); + + if (unreadMessages) { + unreadMsgElement.innerHTML = unreadMessages.toString(); + unreadMsgBottomElement.innerHTML = unreadMessages.toString(); + + ToolbarToggler.dockToolbar(true); + + var chatButtonElement + = document.getElementById('toolbar_button_chat'); + var leftIndent = (UIUtil.getTextWidth(chatButtonElement) - + UIUtil.getTextWidth(unreadMsgElement)) / 2; + var topIndent = (UIUtil.getTextHeight(chatButtonElement) - + UIUtil.getTextHeight(unreadMsgElement)) / 2 - 3; + + unreadMsgElement.setAttribute( + 'style', + 'top:' + topIndent + + '; left:' + leftIndent + ';'); + + var chatBottomButtonElement + = document.getElementById('chatBottomButton').parentNode; + var bottomLeftIndent = (UIUtil.getTextWidth(chatBottomButtonElement) - + UIUtil.getTextWidth(unreadMsgBottomElement)) / 2; + var bottomTopIndent = (UIUtil.getTextHeight(chatBottomButtonElement) - + UIUtil.getTextHeight(unreadMsgBottomElement)) / 2 - 2; + + unreadMsgBottomElement.setAttribute( + 'style', + 'top:' + bottomTopIndent + + '; left:' + bottomLeftIndent + ';'); + + + if (!glower.hasClass('icon-chat-simple')) { + glower.removeClass('icon-chat'); + glower.addClass('icon-chat-simple'); + } + } + else { + unreadMsgElement.innerHTML = ''; + unreadMsgBottomElement.innerHTML = ''; + glower.removeClass('icon-chat-simple'); + glower.addClass('icon-chat'); + } + + if (show && !notificationInterval) { + notificationInterval = window.setInterval(function () { + glower.toggleClass('active'); + bottomGlower.toggleClass('active glowing'); + }, 800); + } + else if (!show && notificationInterval) { + window.clearInterval(notificationInterval); + notificationInterval = false; + glower.removeClass('active'); + bottomGlower.removeClass('glowing'); + bottomGlower.addClass('active'); + } +} + + +/** + * Returns the current time in the format it is shown to the user + * @returns {string} + */ +function getCurrentTime(stamp) { + var now = (stamp? new Date(stamp): new Date()); + var hour = now.getHours(); + var minute = now.getMinutes(); + var second = now.getSeconds(); + if(hour.toString().length === 1) { + hour = '0'+hour; + } + if(minute.toString().length === 1) { + minute = '0'+minute; + } + if(second.toString().length === 1) { + second = '0'+second; + } + return hour+':'+minute+':'+second; +} + +function toggleSmileys() { + var smileys = $('#smileysContainer'); + if(!smileys.is(':visible')) { + smileys.show("slide", { direction: "down", duration: 300}); + } else { + smileys.hide("slide", { direction: "down", duration: 300}); + } + $('#usermsg').focus(); +} + +function addClickFunction(smiley, number) { + smiley.onclick = function addSmileyToMessage() { + var usermsg = $('#usermsg'); + var message = usermsg.val(); + message += smileys['smiley' + number]; + usermsg.val(message); + usermsg.get(0).setSelectionRange(message.length, message.length); + toggleSmileys(); + usermsg.focus(); + }; +} + +/** + * Adds the smileys container to the chat + */ +function addSmileys() { + var smileysContainer = document.createElement('div'); + smileysContainer.id = 'smileysContainer'; + for(var i = 1; i <= 21; i++) { + var smileyContainer = document.createElement('div'); + smileyContainer.id = 'smiley' + i; + smileyContainer.className = 'smileyContainer'; + var smiley = document.createElement('img'); + smiley.src = 'images/smileys/smiley' + i + '.svg'; + smiley.className = 'smiley'; + addClickFunction(smiley, i); + smileyContainer.appendChild(smiley); + smileysContainer.appendChild(smileyContainer); + } + + $("#chatspace").append(smileysContainer); +} + +/** + * Resizes the chat conversation. + */ +function resizeChatConversation() { + var msgareaHeight = $('#usermsg').outerHeight(); + var chatspace = $('#chatspace'); + var width = chatspace.width(); + var chat = $('#chatconversation'); + var smileys = $('#smileysarea'); + + smileys.height(msgareaHeight); + $("#smileys").css('bottom', (msgareaHeight - 26) / 2); + $('#smileysContainer').css('bottom', msgareaHeight); + chat.width(width - 10); + chat.height(window.innerHeight - 15 - msgareaHeight); +} + +/** + * Chat related user interface. + */ +var Chat = (function (my) { + /** + * Initializes chat related interface. + */ + my.init = function () { + if(NicknameHandler.getNickname()) + Chat.setChatConversationMode(true); + NicknameHandler.addListener(UIEvents.NICKNAME_CHANGED, + function (nickname) { + Chat.setChatConversationMode(true); + }); + + $('#nickinput').keydown(function (event) { + if (event.keyCode === 13) { + event.preventDefault(); + var val = UIUtil.escapeHtml(this.value); + this.value = ''; + if (!NicknameHandler.getNickname()) { + NicknameHandler.setNickname(val); + + return; + } + } + }); + + var usermsg = $('#usermsg'); + usermsg.keydown(function (event) { + if (event.keyCode === 13) { + event.preventDefault(); + var value = this.value; + usermsg.val('').trigger('autosize.resize'); + this.focus(); + var command = new CommandsProcessor(value); + if(command.isCommand()) { + command.processCommand(); + } + else { + var message = UIUtil.escapeHtml(value); + APP.xmpp.sendChatMessage(message, NicknameHandler.getNickname()); + } + } + }); + + var onTextAreaResize = function () { + resizeChatConversation(); + Chat.scrollChatToBottom(); + }; + usermsg.autosize({callback: onTextAreaResize}); + + $("#chatspace").bind("shown", + function () { + unreadMessages = 0; + setVisualNotification(false); + }); + + addSmileys(); + }; + + /** + * Appends the given message to the chat conversation. + */ + my.updateChatConversation = + function (from, displayName, message, myjid, stamp) { + var divClassName = ''; + + if (APP.xmpp.myJid() === from) { + divClassName = "localuser"; + } + else { + divClassName = "remoteuser"; + + if (!Chat.isVisible()) { + unreadMessages++; + UIUtil.playSoundNotification('chatNotification'); + setVisualNotification(true); + } + } + + // replace links and smileys + // Strophe already escapes special symbols on sending, + // so we escape here only tags to avoid double & + var escMessage = message.replace(//g, '>').replace(/\n/g, '
'); + var escDisplayName = UIUtil.escapeHtml(displayName); + message = Replacement.processReplacements(escMessage); + + var messageContainer = + '
'+ + '' + + '
' + escDisplayName + + '
' + '
' + getCurrentTime(stamp) + + '
' + '
' + message + '
' + + '
'; + + $('#chatconversation').append(messageContainer); + $('#chatconversation').animate( + { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000); + }; + + /** + * Appends error message to the conversation + * @param errorMessage the received error message. + * @param originalText the original message. + */ + my.chatAddError = function(errorMessage, originalText) { + errorMessage = UIUtil.escapeHtml(errorMessage); + originalText = UIUtil.escapeHtml(originalText); + + $('#chatconversation').append( + '
Error: ' + 'Your message' + + (originalText? (' \"'+ originalText + '\"') : "") + + ' was not sent.' + + (errorMessage? (' Reason: ' + errorMessage) : '') + '
'); + $('#chatconversation').animate( + { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000); + }; + + /** + * Sets the subject to the UI + * @param subject the subject + */ + my.chatSetSubject = function(subject) { + if (subject) + subject = subject.trim(); + $('#subject').html(Replacement.linkify(UIUtil.escapeHtml(subject))); + if(subject === "") { + $("#subject").css({display: "none"}); + } + else { + $("#subject").css({display: "block"}); + } + }; + + /** + * Sets the chat conversation mode. + */ + my.setChatConversationMode = function (isConversationMode) { + if (isConversationMode) { + $('#nickname').css({visibility: 'hidden'}); + $('#chatconversation').css({visibility: 'visible'}); + $('#usermsg').css({visibility: 'visible'}); + $('#smileysarea').css({visibility: 'visible'}); + $('#usermsg').focus(); + } + }; + + /** + * Resizes the chat area. + */ + my.resizeChat = function () { + var chatSize = require("../SidePanelToggler").getPanelSize(); + + $('#chatspace').width(chatSize[0]); + $('#chatspace').height(chatSize[1]); + + resizeChatConversation(); + }; + + /** + * Indicates if the chat is currently visible. + */ + my.isVisible = function () { + return $('#chatspace').is(":visible"); + }; + /** + * Shows and hides the window with the smileys + */ + my.toggleSmileys = toggleSmileys; + + /** + * Scrolls chat to the bottom. + */ + my.scrollChatToBottom = function() { + setTimeout(function () { + $('#chatconversation').scrollTop( + $('#chatconversation')[0].scrollHeight); + }, 5); + }; + + + return my; +}(Chat || {})); module.exports = Chat; -},{"../../../../service/UI/UIEvents":155,"../../toolbars/ToolbarToggler":29,"../../util/NicknameHandler":32,"../../util/UIUtil":33,"../SidePanelToggler":20,"./Commands":22,"./Replacement":23,"./smileys.json":24}],22:[function(require,module,exports){ -/* global APP, require */ -var UIUtil = require("../../util/UIUtil"); - -/** - * List with supported commands. The keys are the names of the commands and - * the value is the function that processes the message. - * @type {{String: function}} - */ -var commands = { - "topic" : processTopic -}; - -/** - * Extracts the command from the message. - * @param message the received message - * @returns {string} the command - */ -function getCommand(message) { - if(message) { - for(var command in commands) { - if(message.indexOf("/" + command) == 0) - return command; - } - } - return ""; -} - -/** - * Processes the data for topic command. - * @param commandArguments the arguments of the topic command. - */ -function processTopic(commandArguments) { - var topic = UIUtil.escapeHtml(commandArguments); - APP.xmpp.setSubject(topic); -} - -/** - * Constructs a new CommandProccessor instance from a message that - * handles commands received via chat messages. - * @param message the message - * @constructor - */ -function CommandsProcessor(message) { - var command = getCommand(message); - - /** - * Returns the name of the command. - * @returns {String} the command - */ - this.getCommand = function() { - return command; - }; - - - var messageArgument = message.substr(command.length + 2); - - /** - * Returns the arguments of the command. - * @returns {string} - */ - this.getArgument = function() { - return messageArgument; - }; -} - -/** - * Checks whether this instance is valid command or not. - * @returns {boolean} - */ -CommandsProcessor.prototype.isCommand = function() { - if (this.getCommand()) - return true; - return false; -}; - -/** - * Processes the command. - */ -CommandsProcessor.prototype.processCommand = function() { - if(!this.isCommand()) - return; - - commands[this.getCommand()](this.getArgument()); -}; - +},{"../../../../service/UI/UIEvents":160,"../../toolbars/ToolbarToggler":31,"../../util/NicknameHandler":34,"../../util/UIUtil":35,"../SidePanelToggler":22,"./Commands":24,"./Replacement":25,"./smileys.json":26}],24:[function(require,module,exports){ +/* global APP, require */ +var UIUtil = require("../../util/UIUtil"); + +/** + * List with supported commands. The keys are the names of the commands and + * the value is the function that processes the message. + * @type {{String: function}} + */ +var commands = { + "topic" : processTopic +}; + +/** + * Extracts the command from the message. + * @param message the received message + * @returns {string} the command + */ +function getCommand(message) { + if(message) { + for(var command in commands) { + if(message.indexOf("/" + command) == 0) + return command; + } + } + return ""; +} + +/** + * Processes the data for topic command. + * @param commandArguments the arguments of the topic command. + */ +function processTopic(commandArguments) { + var topic = UIUtil.escapeHtml(commandArguments); + APP.xmpp.setSubject(topic); +} + +/** + * Constructs a new CommandProccessor instance from a message that + * handles commands received via chat messages. + * @param message the message + * @constructor + */ +function CommandsProcessor(message) { + var command = getCommand(message); + + /** + * Returns the name of the command. + * @returns {String} the command + */ + this.getCommand = function() { + return command; + }; + + + var messageArgument = message.substr(command.length + 2); + + /** + * Returns the arguments of the command. + * @returns {string} + */ + this.getArgument = function() { + return messageArgument; + }; +} + +/** + * Checks whether this instance is valid command or not. + * @returns {boolean} + */ +CommandsProcessor.prototype.isCommand = function() { + if (this.getCommand()) + return true; + return false; +}; + +/** + * Processes the command. + */ +CommandsProcessor.prototype.processCommand = function() { + if(!this.isCommand()) + return; + + commands[this.getCommand()](this.getArgument()); +}; + module.exports = CommandsProcessor; -},{"../../util/UIUtil":33}],23:[function(require,module,exports){ -var Smileys = require("./smileys.json"); -/** - * Processes links and smileys in "body" - */ -function processReplacements(body) -{ - //make links clickable - body = linkify(body); +},{"../../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 +}; - //add smileys - body = smilify(body); - - return body; -} - -/** - * Finds and replaces all links in the links in "body" - * with their - */ -function linkify(inputText) -{ - var replacedText, replacePattern1, replacePattern2, replacePattern3; - - //URLs starting with http://, https://, or ftp:// - replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; - replacedText = inputText.replace(replacePattern1, '$1'); - - //URLs starting with "www." (without // before it, or it'd re-link the ones done above). - replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; - replacedText = replacedText.replace(replacePattern2, '$1$2'); - - //Change email addresses to mailto:: links. - replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim; - replacedText = replacedText.replace(replacePattern3, '$1'); - - return replacedText; -} - -/** - * Replaces common smiley strings with images - */ -function smilify(body) -{ - if(!body) { - return body; - } - - var regexs = Smileys["regexs"]; - for(var smiley in regexs) { - if(regexs.hasOwnProperty(smiley)) { - body = body.replace(regexs[smiley], - ''); - } - } - - return body; -} - -module.exports = { - processReplacements: processReplacements, - linkify: linkify -}; - -},{"./smileys.json":24}],24:[function(require,module,exports){ -module.exports={ - "smileys": { - "smiley1": ":)", - "smiley2": ":(", - "smiley3": ":D", - "smiley4": "(y)", - "smiley5": " :P", - "smiley6": "(wave)", - "smiley7": "(blush)", - "smiley8": "(chuckle)", - "smiley9": "(shocked)", - "smiley10": ":*", - "smiley11": "(n)", - "smiley12": "(search)", - "smiley13": " <3", - "smiley14": "(oops)", - "smiley15": "(angry)", - "smiley16": "(angel)", - "smiley17": "(sick)", - "smiley18": ";(", - "smiley19": "(bomb)", - "smiley20": "(clap)", - "smiley21": " ;)" - }, - "regexs": { - "smiley2": /(:-\(\(|:-\(|:\(\(|:\(|\(sad\))/gi, - "smiley3": /(:-\)\)|:\)\)|\(lol\)|:-D|:D)/gi, - "smiley1": /(:-\)|:\))/gi, - "smiley4": /(\(y\)|\(Y\)|\(ok\))/gi, - "smiley5": /(:-P|:P|:-p|:p)/gi, - "smiley6": /(\(wave\))/gi, - "smiley7": /(\(blush\))/gi, - "smiley8": /(\(chuckle\))/gi, - "smiley9": /(:-0|\(shocked\))/gi, - "smiley10": /(:-\*|:\*|\(kiss\))/gi, - "smiley11": /(\(n\))/gi, - "smiley12": /(\(search\))/g, - "smiley13": /(<3|<3|&lt;3|\(L\)|\(l\)|\(H\)|\(h\))/gi, - "smiley14": /(\(oops\))/gi, - "smiley15": /(\(angry\))/gi, - "smiley16": /(\(angel\))/gi, - "smiley17": /(\(sick\))/gi, - "smiley18": /(;-\(\(|;\(\(|;-\(|;\(|:"\(|:"-\(|:~-\(|:~\(|\(upset\))/gi, - "smiley19": /(\(bomb\))/gi, - "smiley20": /(\(clap\))/gi, - "smiley21": /(;-\)|;\)|;-\)\)|;\)\)|;-D|;D|\(wink\))/gi - } -} - -},{}],25:[function(require,module,exports){ -/* global $, APP, Strophe */ -var Avatar = require('../../avatar/Avatar'); - -var numberOfContacts = 0; -var notificationInterval; - -/** - * Updates the number of participants in the contact list button and sets - * the glow - * @param delta indicates whether a new user has joined (1) or someone has - * left(-1) - */ -function updateNumberOfParticipants(delta) { - numberOfContacts += delta; - if (numberOfContacts === 1) { - // when the user is alone we don't show the number of participants - $("#numberOfParticipants").text(''); - ContactList.setVisualNotification(false); - } else if (numberOfContacts > 1) { - ContactList.setVisualNotification(!ContactList.isVisible()); - $("#numberOfParticipants").text(numberOfContacts); - } else { - console.error("Invalid number of participants: " + numberOfContacts); - } -} - -/** - * Creates the avatar element. - * - * @return {object} the newly created avatar element - */ -function createAvatar(jid) { - var avatar = document.createElement('img'); - avatar.className = "icon-avatar avatar"; - avatar.src = Avatar.getContactListUrl(jid); - - return avatar; -} - -/** - * Creates the display name paragraph. - * - * @param displayName the display name to set - */ -function createDisplayNameParagraph(key, displayName) { - var p = document.createElement('p'); - if(displayName) - p.innerText = displayName; - else if(key) { - p.setAttribute("data-i18n",key); - p.innerText = APP.translation.translateString(key); - } - - return p; -} - - -function stopGlowing(glower) { - window.clearInterval(notificationInterval); - notificationInterval = false; - glower.removeClass('glowing'); - if (!ContactList.isVisible()) { - glower.removeClass('active'); - } -} - -/** - * Contact list. - */ -var ContactList = { - /** - * Indicates if the chat is currently visible. - * - * @return true if the chat is currently visible, false - - * otherwise - */ - isVisible: function () { - return $('#contactlist').is(":visible"); - }, - - /** - * Adds a contact for the given peerJid if such doesn't yet exist. - * - * @param peerJid the peerJid corresponding to the contact - */ - ensureAddContact: function (peerJid) { - var resourceJid = Strophe.getResourceFromJid(peerJid); - - var contact = $('#contacts>li[id="' + resourceJid + '"]'); - - if (!contact || contact.length <= 0) - ContactList.addContact(peerJid); - }, - - /** - * Adds a contact for the given peer jid. - * - * @param peerJid the jid of the contact to add - */ - addContact: function (peerJid) { - var resourceJid = Strophe.getResourceFromJid(peerJid); - - var contactlist = $('#contacts'); - - var newContact = document.createElement('li'); - newContact.id = resourceJid; - newContact.className = "clickable"; - newContact.onclick = function (event) { - if (event.currentTarget.className === "clickable") { - $(ContactList).trigger('contactclicked', [peerJid]); - } - }; - - newContact.appendChild(createAvatar(peerJid)); - newContact.appendChild(createDisplayNameParagraph("participant")); - - if (resourceJid === APP.xmpp.myResource()) { - contactlist.prepend(newContact); - } - else { - contactlist.append(newContact); - } - updateNumberOfParticipants(1); - }, - - /** - * Removes a contact for the given peer jid. - * - * @param peerJid the peerJid corresponding to the contact to remove - */ - removeContact: function (peerJid) { - var resourceJid = Strophe.getResourceFromJid(peerJid); - - var contact = $('#contacts>li[id="' + resourceJid + '"]'); - - if (contact && contact.length > 0) { - var contactlist = $('#contactlist>ul'); - - contactlist.get(0).removeChild(contact.get(0)); - - updateNumberOfParticipants(-1); - } - }, - - setVisualNotification: function (show, stopGlowingIn) { - var glower = $('#contactListButton'); - - if (show && !notificationInterval) { - notificationInterval = window.setInterval(function () { - glower.toggleClass('active glowing'); - }, 800); - } - else if (!show && notificationInterval) { - stopGlowing(glower); - } - if (stopGlowingIn) { - setTimeout(function () { - stopGlowing(glower); - }, stopGlowingIn); - } - }, - - setClickable: function (resourceJid, isClickable) { - var contact = $('#contacts>li[id="' + resourceJid + '"]'); - if (isClickable) { - contact.addClass('clickable'); - } else { - contact.removeClass('clickable'); - } - }, - - onDisplayNameChange: function (peerJid, displayName) { - if (peerJid === 'localVideoContainer') - peerJid = APP.xmpp.myJid(); - - var resourceJid = Strophe.getResourceFromJid(peerJid); - - var contactName = $('#contacts #' + resourceJid + '>p'); - - if (contactName && displayName && displayName.length > 0) - contactName.html(displayName); - }, - - userAvatarChanged: function (resourceJid, contactListUrl) { - // set the avatar in the contact list - var contact = $('#' + resourceJid + '>img'); - if (contact && contact.length > 0) { - contact.get(0).src = contactListUrl; - } - - } -}; +},{"./smileys.json":26}],26:[function(require,module,exports){ +module.exports={ + "smileys": { + "smiley1": ":)", + "smiley2": ":(", + "smiley3": ":D", + "smiley4": "(y)", + "smiley5": " :P", + "smiley6": "(wave)", + "smiley7": "(blush)", + "smiley8": "(chuckle)", + "smiley9": "(shocked)", + "smiley10": ":*", + "smiley11": "(n)", + "smiley12": "(search)", + "smiley13": " <3", + "smiley14": "(oops)", + "smiley15": "(angry)", + "smiley16": "(angel)", + "smiley17": "(sick)", + "smiley18": ";(", + "smiley19": "(bomb)", + "smiley20": "(clap)", + "smiley21": " ;)" + }, + "regexs": { + "smiley2": /(:-\(\(|:-\(|:\(\(|:\(|\(sad\))/gi, + "smiley3": /(:-\)\)|:\)\)|\(lol\)|:-D|:D)/gi, + "smiley1": /(:-\)|:\))/gi, + "smiley4": /(\(y\)|\(Y\)|\(ok\))/gi, + "smiley5": /(:-P|:P|:-p|:p)/gi, + "smiley6": /(\(wave\))/gi, + "smiley7": /(\(blush\))/gi, + "smiley8": /(\(chuckle\))/gi, + "smiley9": /(:-0|\(shocked\))/gi, + "smiley10": /(:-\*|:\*|\(kiss\))/gi, + "smiley11": /(\(n\))/gi, + "smiley12": /(\(search\))/g, + "smiley13": /(<3|<3|&lt;3|\(L\)|\(l\)|\(H\)|\(h\))/gi, + "smiley14": /(\(oops\))/gi, + "smiley15": /(\(angry\))/gi, + "smiley16": /(\(angel\))/gi, + "smiley17": /(\(sick\))/gi, + "smiley18": /(;-\(\(|;\(\(|;-\(|;\(|:"\(|:"-\(|:~-\(|:~\(|\(upset\))/gi, + "smiley19": /(\(bomb\))/gi, + "smiley20": /(\(clap\))/gi, + "smiley21": /(;-\)|;\)|;-\)\)|;\)\)|;-D|;D|\(wink\))/gi + } +} +},{}],27:[function(require,module,exports){ +/* global $, APP, Strophe */ +var Avatar = require('../../avatar/Avatar'); + +var numberOfContacts = 0; +var notificationInterval; + +/** + * Updates the number of participants in the contact list button and sets + * the glow + * @param delta indicates whether a new user has joined (1) or someone has + * left(-1) + */ +function updateNumberOfParticipants(delta) { + numberOfContacts += delta; + if (numberOfContacts === 1) { + // when the user is alone we don't show the number of participants + $("#numberOfParticipants").text(''); + ContactList.setVisualNotification(false); + } else if (numberOfContacts > 1) { + ContactList.setVisualNotification(!ContactList.isVisible()); + $("#numberOfParticipants").text(numberOfContacts); + } else { + console.error("Invalid number of participants: " + numberOfContacts); + } +} + +/** + * Creates the avatar element. + * + * @return {object} the newly created avatar element + */ +function createAvatar(jid) { + var avatar = document.createElement('img'); + avatar.className = "icon-avatar avatar"; + avatar.src = Avatar.getContactListUrl(jid); + + return avatar; +} + +/** + * Creates the display name paragraph. + * + * @param displayName the display name to set + */ +function createDisplayNameParagraph(key, displayName) { + var p = document.createElement('p'); + if(displayName) + p.innerText = displayName; + else if(key) { + p.setAttribute("data-i18n",key); + p.innerText = APP.translation.translateString(key); + } + + return p; +} + + +function stopGlowing(glower) { + window.clearInterval(notificationInterval); + notificationInterval = false; + glower.removeClass('glowing'); + if (!ContactList.isVisible()) { + glower.removeClass('active'); + } +} + +/** + * Contact list. + */ +var ContactList = { + /** + * Indicates if the chat is currently visible. + * + * @return true if the chat is currently visible, false - + * otherwise + */ + isVisible: function () { + return $('#contactlist').is(":visible"); + }, + + /** + * Adds a contact for the given peerJid if such doesn't yet exist. + * + * @param peerJid the peerJid corresponding to the contact + */ + ensureAddContact: function (peerJid) { + var resourceJid = Strophe.getResourceFromJid(peerJid); + + var contact = $('#contacts>li[id="' + resourceJid + '"]'); + + if (!contact || contact.length <= 0) + ContactList.addContact(peerJid); + }, + + /** + * Adds a contact for the given peer jid. + * + * @param peerJid the jid of the contact to add + */ + addContact: function (peerJid) { + var resourceJid = Strophe.getResourceFromJid(peerJid); + + var contactlist = $('#contacts'); + + var newContact = document.createElement('li'); + newContact.id = resourceJid; + newContact.className = "clickable"; + newContact.onclick = function (event) { + if (event.currentTarget.className === "clickable") { + $(ContactList).trigger('contactclicked', [peerJid]); + } + }; + + newContact.appendChild(createAvatar(peerJid)); + newContact.appendChild(createDisplayNameParagraph("participant")); + + if (resourceJid === APP.xmpp.myResource()) { + contactlist.prepend(newContact); + } + else { + contactlist.append(newContact); + } + updateNumberOfParticipants(1); + }, + + /** + * Removes a contact for the given peer jid. + * + * @param peerJid the peerJid corresponding to the contact to remove + */ + removeContact: function (peerJid) { + var resourceJid = Strophe.getResourceFromJid(peerJid); + + var contact = $('#contacts>li[id="' + resourceJid + '"]'); + + if (contact && contact.length > 0) { + var contactlist = $('#contactlist>ul'); + + contactlist.get(0).removeChild(contact.get(0)); + + updateNumberOfParticipants(-1); + } + }, + + setVisualNotification: function (show, stopGlowingIn) { + var glower = $('#contactListButton'); + + if (show && !notificationInterval) { + notificationInterval = window.setInterval(function () { + glower.toggleClass('active glowing'); + }, 800); + } + else if (!show && notificationInterval) { + stopGlowing(glower); + } + if (stopGlowingIn) { + setTimeout(function () { + stopGlowing(glower); + }, stopGlowingIn); + } + }, + + setClickable: function (resourceJid, isClickable) { + var contact = $('#contacts>li[id="' + resourceJid + '"]'); + if (isClickable) { + contact.addClass('clickable'); + } else { + contact.removeClass('clickable'); + } + }, + + onDisplayNameChange: function (peerJid, displayName) { + if (peerJid === 'localVideoContainer') + peerJid = APP.xmpp.myJid(); + + var resourceJid = Strophe.getResourceFromJid(peerJid); + + var contactName = $('#contacts #' + resourceJid + '>p'); + + if (contactName && displayName && displayName.length > 0) + contactName.html(displayName); + }, + + userAvatarChanged: function (resourceJid, contactListUrl) { + // set the avatar in the contact list + var contact = $('#' + resourceJid + '>img'); + if (contact && contact.length > 0) { + contact.get(0).src = contactListUrl; + } + + } +}; + module.exports = ContactList; -},{"../../avatar/Avatar":16}],26:[function(require,module,exports){ -/* global APP, $ */ -var Avatar = require("../../avatar/Avatar"); -var Settings = require("./../../../settings/Settings"); -var UIUtil = require("../../util/UIUtil"); -var languages = require("../../../../service/translation/languages"); - -function generateLanguagesSelectBox() { - var currentLang = APP.translation.getCurrentLanguage(); - var html = ""; -} - - -var SettingsMenu = { - - init: function () { - var startMutedSelector = $("#startMutedOptions"); - startMutedSelector.before(generateLanguagesSelectBox()); - APP.translation.translateElement($("#languages_selectbox")); - $('#settingsmenu>input').keyup(function(event){ - if(event.keyCode === 13) {//enter - SettingsMenu.update(); - } - }); - - if (APP.xmpp.isModerator()) { - startMutedSelector.css("display", "block"); - } - else { - startMutedSelector.css("display", "none"); - } - - $("#updateSettings").click(function () { - SettingsMenu.update(); - }); - }, - - onRoleChanged: function () { - if(APP.xmpp.isModerator()) { - $("#startMutedOptions").css("display", "block"); - } - else { - $("#startMutedOptions").css("display", "none"); - } - }, - - setStartMuted: function (audio, video) { - $("#startAudioMuted").attr("checked", audio); - $("#startVideoMuted").attr("checked", video); - }, - - update: function() { - var newDisplayName = UIUtil.escapeHtml($('#setDisplayName').get(0).value); - var newEmail = UIUtil.escapeHtml($('#setEmail').get(0).value); - - if(newDisplayName) { - var displayName = Settings.setDisplayName(newDisplayName); - APP.xmpp.addToPresence("displayName", displayName, true); - } - - var language = $("#languages_selectbox").val(); - APP.translation.setLanguage(language); - Settings.setLanguage(language); - - APP.xmpp.addToPresence("email", newEmail); - var email = Settings.setEmail(newEmail); - - var startAudioMuted = ($("#startAudioMuted").is(":checked")); - var startVideoMuted = ($("#startVideoMuted").is(":checked")); - APP.xmpp.addToPresence("startMuted", - [startAudioMuted, startVideoMuted]); - - Avatar.setUserAvatar(APP.xmpp.myJid(), email); - }, - - isVisible: function() { - return $('#settingsmenu').is(':visible'); - }, - - setDisplayName: function(newDisplayName) { - var displayName = Settings.setDisplayName(newDisplayName); - $('#setDisplayName').get(0).value = displayName; - }, - - onDisplayNameChange: function(peerJid, newDisplayName) { - if(peerJid === 'localVideoContainer' || - peerJid === APP.xmpp.myJid()) { - this.setDisplayName(newDisplayName); - } - }, - changeAvatar: function (thumbUrl) { - $('#avatar').get(0).src = thumbUrl; - } -}; - - +},{"../../avatar/Avatar":18}],28:[function(require,module,exports){ +/* global APP, $ */ +var Avatar = require("../../avatar/Avatar"); +var Settings = require("./../../../settings/Settings"); +var UIUtil = require("../../util/UIUtil"); +var languages = require("../../../../service/translation/languages"); + +function generateLanguagesSelectBox() { + var currentLang = APP.translation.getCurrentLanguage(); + var html = ""; +} + + +var SettingsMenu = { + + init: function () { + var startMutedSelector = $("#startMutedOptions"); + startMutedSelector.before(generateLanguagesSelectBox()); + APP.translation.translateElement($("#languages_selectbox")); + $('#settingsmenu>input').keyup(function(event){ + if(event.keyCode === 13) {//enter + SettingsMenu.update(); + } + }); + + if (APP.xmpp.isModerator()) { + startMutedSelector.css("display", "block"); + } + else { + startMutedSelector.css("display", "none"); + } + + $("#updateSettings").click(function () { + SettingsMenu.update(); + }); + }, + + onRoleChanged: function () { + if(APP.xmpp.isModerator()) { + $("#startMutedOptions").css("display", "block"); + } + else { + $("#startMutedOptions").css("display", "none"); + } + }, + + setStartMuted: function (audio, video) { + $("#startAudioMuted").attr("checked", audio); + $("#startVideoMuted").attr("checked", video); + }, + + update: function() { + var newDisplayName = UIUtil.escapeHtml($('#setDisplayName').get(0).value); + var newEmail = UIUtil.escapeHtml($('#setEmail').get(0).value); + + if(newDisplayName) { + var displayName = Settings.setDisplayName(newDisplayName); + APP.xmpp.addToPresence("displayName", displayName, true); + } + + var language = $("#languages_selectbox").val(); + APP.translation.setLanguage(language); + Settings.setLanguage(language); + + APP.xmpp.addToPresence("email", newEmail); + var email = Settings.setEmail(newEmail); + + var startAudioMuted = ($("#startAudioMuted").is(":checked")); + var startVideoMuted = ($("#startVideoMuted").is(":checked")); + APP.xmpp.addToPresence("startMuted", + [startAudioMuted, startVideoMuted]); + + Avatar.setUserAvatar(APP.xmpp.myJid(), email); + }, + + isVisible: function() { + return $('#settingsmenu').is(':visible'); + }, + + setDisplayName: function(newDisplayName) { + var displayName = Settings.setDisplayName(newDisplayName); + $('#setDisplayName').get(0).value = displayName; + }, + + onDisplayNameChange: function(peerJid, newDisplayName) { + if(peerJid === 'localVideoContainer' || + peerJid === APP.xmpp.myJid()) { + this.setDisplayName(newDisplayName); + } + }, + changeAvatar: function (thumbUrl) { + $('#avatar').get(0).src = thumbUrl; + } +}; + + module.exports = SettingsMenu; -},{"../../../../service/translation/languages":160,"../../avatar/Avatar":16,"../../util/UIUtil":33,"./../../../settings/Settings":47}],27:[function(require,module,exports){ -/* global $ */ -var PanelToggler = require("../side_pannels/SidePanelToggler"); - -var buttonHandlers = { - "bottom_toolbar_contact_list": function () { - BottomToolbar.toggleContactList(); - }, - "bottom_toolbar_film_strip": function () { - BottomToolbar.toggleFilmStrip(); - }, - "bottom_toolbar_chat": function () { - BottomToolbar.toggleChat(); - } -}; - -var BottomToolbar = (function (my) { - my.init = function () { - for(var k in buttonHandlers) - $("#" + k).click(buttonHandlers[k]); - }; - - my.toggleChat = function() { - PanelToggler.toggleChat(); - }; - - my.toggleContactList = function() { - PanelToggler.toggleContactList(); - }; - - my.toggleFilmStrip = function() { - var filmstrip = $("#remoteVideos"); - filmstrip.toggleClass("hidden"); - }; - - $(document).bind("remotevideo.resized", function (event, width, height) { - var bottom = (height - $('#bottomToolbar').outerHeight())/2 + 18; - - $('#bottomToolbar').css({bottom: bottom + 'px'}); - }); - - return my; -}(BottomToolbar || {})); - -module.exports = BottomToolbar; - -},{"../side_pannels/SidePanelToggler":20}],28:[function(require,module,exports){ -/* global APP, $, buttonClick, config, lockRoom, interfaceConfig, setSharedKey, - Util */ -var messageHandler = require("../util/MessageHandler"); -var BottomToolbar = require("./BottomToolbar"); -var Prezi = require("../prezi/Prezi"); -var Etherpad = require("../etherpad/Etherpad"); -var PanelToggler = require("../side_pannels/SidePanelToggler"); -var Authentication = require("../authentication/Authentication"); -var UIUtil = require("../util/UIUtil"); -var AuthenticationEvents - = require("../../../service/authentication/AuthenticationEvents"); - -var roomUrl = null; -var sharedKey = ''; -var UI = null; -var recordingToaster = null; - -var buttonHandlers = { - "toolbar_button_mute": function () { - return APP.UI.toggleAudio(); - }, - "toolbar_button_camera": function () { - return APP.UI.toggleVideo(); - }, - /*"toolbar_button_authentication": function () { - return Toolbar.authenticateClicked(); - },*/ - "toolbar_button_record": function () { - return toggleRecording(); - }, - "toolbar_button_security": function () { - return Toolbar.openLockDialog(); - }, - "toolbar_button_link": function () { - return Toolbar.openLinkDialog(); - }, - "toolbar_button_chat": function () { - return BottomToolbar.toggleChat(); - }, - "toolbar_button_prezi": function () { - return Prezi.openPreziDialog(); - }, - "toolbar_button_etherpad": function () { - return Etherpad.toggleEtherpad(0); - }, - "toolbar_button_desktopsharing": function () { - return APP.desktopsharing.toggleScreenSharing(); - }, - "toolbar_button_fullScreen": function() { - UIUtil.buttonClick("#toolbar_button_fullScreen", "icon-full-screen icon-exit-full-screen"); - return Toolbar.toggleFullScreen(); - }, - "toolbar_button_sip": function () { - return callSipButtonClicked(); - }, - "toolbar_button_dialpad": function () { - return dialpadButtonClicked(); - }, - "toolbar_button_settings": function () { - PanelToggler.toggleSettingsMenu(); - }, - "toolbar_button_hangup": function () { - return hangup(); - }, - "toolbar_button_login": function () { - Toolbar.authenticateClicked(); - }, - "toolbar_button_logout": function () { - // Ask for confirmation - messageHandler.openTwoButtonDialog( - "dialog.logoutTitle", - null, - "dialog.logoutQuestion", - null, - false, - "dialog.Yes", - function (evt, yes) { - if (yes) { - APP.xmpp.logout(function (url) { - if (url) { - window.location.href = url; - } else { - hangup(); - } - }); - } - }); - } -}; - -function hangup() { - APP.xmpp.disposeConference(); - if(config.enableWelcomePage) { - setTimeout(function() { - window.localStorage.welcomePageDisabled = false; - window.location.pathname = "/"; - }, 10000); - - } - - var title = APP.translation.generateTranslationHTML( - "dialog.sessTerminated"); - var msg = APP.translation.generateTranslationHTML( - "dialog.hungUp"); - var button = APP.translation.generateTranslationHTML( - "dialog.joinAgain"); - var buttons = []; - buttons.push({title: button, value: true}); - - UI.messageHandler.openDialog( - title, - msg, - true, - buttons, - function(event, value, message, formVals) { - window.location.reload(); - return false; - } - ); -} - -/** - * Starts or stops the recording for the conference. - */ - -function toggleRecording(predefinedToken) { - APP.xmpp.toggleRecording(function (callback) { - if (predefinedToken) { - callback(UIUtil.escapeHtml(predefinedToken)); - return; - } - - var msg = APP.translation.generateTranslationHTML( - "dialog.recordingToken"); - var token = APP.translation.translateString("dialog.token"); - APP.UI.messageHandler.openTwoButtonDialog(null, null, null, - '

' + msg + '

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

' + sipMsg + '

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

' + msg + '

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

' + settings1 + '

' + - '' + - settings2 + '
' + - '' + - settings3 + - '', - null, - null, - false, - "dialog.Save", - function () { - document.getElementById('lockKey').focus(); - }, - function (e, v) { - if (v) { - if ($('#initMuted').is(":checked")) { - // it is checked - } - - if ($('#requireNicknames').is(":checked")) { - // it is checked - } - /* - var lockKey = document.getElementById('lockKey'); - - if (lockKey.value) { - setSharedKey(lockKey.value); - lockRoom(true); - } - */ - } - } - ); - }; - - /** - * Toggles the application in and out of full screen mode - * (a.k.a. presentation mode in Chrome). - */ - my.toggleFullScreen = function () { - var fsElement = document.documentElement; - - if (!document.mozFullScreen && !document.webkitIsFullScreen) { - //Enter Full Screen - if (fsElement.mozRequestFullScreen) { - fsElement.mozRequestFullScreen(); - } - else { - fsElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT); - } - } else { - //Exit Full Screen - if (document.mozCancelFullScreen) { - document.mozCancelFullScreen(); - } else { - document.webkitCancelFullScreen(); - } - } - }; - /** - * Unlocks the lock button state. - */ - my.unlockLockButton = function () { - if ($("#toolbar_button_security").hasClass("icon-security-locked")) - UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked"); - }; - /** - * Updates the lock button state to locked. - */ - my.lockLockButton = function () { - if ($("#toolbar_button_security").hasClass("icon-security")) - UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked"); - }; - - /** - * Shows or hides authentication button - * @param show true to show or false to hide - */ - my.showAuthenticateButton = function (show) { - if (show) { - $('#authentication').css({display: "inline"}); - } - else { - $('#authentication').css({display: "none"}); - } - }; - - // Shows or hides the 'recording' button. - my.showRecordingButton = function (show) { - if (!config.enableRecording) { - return; - } - - if (show) { - $('#toolbar_button_record').css({display: "inline-block"}); - } - else { - $('#toolbar_button_record').css({display: "none"}); - } - }; - - // Sets the state of the recording button - my.setRecordingButtonState = function (recordingState) { - var selector = $('#toolbar_button_record'); - - if (recordingState === 'on') { - selector.removeClass("icon-recEnable"); - selector.addClass("icon-recEnable active"); - - $("#largeVideo").toggleClass("videoMessageFilter", true); - var recordOnKey = "recording.on"; - $('#videoConnectionMessage').attr("data-i18n", recordOnKey); - $('#videoConnectionMessage').text(APP.translation.translateString(recordOnKey)); - - setTimeout(function(){ - $("#largeVideo").toggleClass("videoMessageFilter", false); - $('#videoConnectionMessage').css({display: "none"}); - }, 1500); - - recordingToaster = messageHandler.notify(null, "recording.toaster", null, - null, null, {timeOut: 0, closeButton: null, tapToDismiss: false}); - } else if (recordingState === 'off') { - selector.removeClass("icon-recEnable active"); - selector.addClass("icon-recEnable"); - - $("#largeVideo").toggleClass("videoMessageFilter", false); - $('#videoConnectionMessage').css({display: "none"}); - - if (recordingToaster) - messageHandler.remove(recordingToaster); - - } else if (recordingState === 'pending') { - selector.removeClass("icon-recEnable active"); - selector.addClass("icon-recEnable"); - - $("#largeVideo").toggleClass("videoMessageFilter", true); - var recordPendingKey = "recording.pending"; - $('#videoConnectionMessage').attr("data-i18n", recordPendingKey); - $('#videoConnectionMessage').text(APP.translation.translateString(recordPendingKey)); - $('#videoConnectionMessage').css({display: "block"}); - } - }; - - // checks whether recording is enabled and whether we have params to start automatically recording - my.checkAutoRecord = function () { - if (config.enableRecording && config.autoRecord) { - toggleRecording(config.autoRecordToken); - } - } - - // Shows or hides SIP calls button - my.showSipCallButton = function (show) { - if (APP.xmpp.isSipGatewayEnabled() && show) { - $('#toolbar_button_sip').css({display: "inline-block"}); - } else { - $('#toolbar_button_sip').css({display: "none"}); - } - }; - - // Shows or hides the dialpad button - my.showDialPadButton = function (show) { - if (show) { - $('#toolbar_button_dialpad').css({display: "inline-block"}); - } else { - $('#toolbar_button_dialpad').css({display: "none"}); - } - }; - - /** - * Displays user authenticated identity name(login). - * @param authIdentity identity name to be displayed. - */ - my.setAuthenticatedIdentity = function (authIdentity) { - if (authIdentity) { - var selector = $('#toolbar_auth_identity'); - selector.css({display: "list-item"}); - selector.text(authIdentity); - } else { - $('#toolbar_auth_identity').css({display: "none"}); - } - }; - - /** - * Shows/hides login button. - * @param show true to show - */ - my.showLoginButton = function (show) { - if (show) { - $('#toolbar_button_login').css({display: "list-item"}); - } else { - $('#toolbar_button_login').css({display: "none"}); - } - }; - - /** - * Shows/hides logout button. - * @param show true to show - */ - my.showLogoutButton = function (show) { - if (show) { - $('#toolbar_button_logout').css({display: "list-item"}); - } else { - $('#toolbar_button_logout').css({display: "none"}); - } - }; - - /** - * Sets the state of the button. The button has blue glow if desktop - * streaming is active. - * @param active the state of the desktop streaming. - */ - my.changeDesktopSharingButtonState = function (active) { - var button = $("#toolbar_button_desktopsharing"); - if (active) { - button.addClass("glow"); - } else { - button.removeClass("glow"); - } - }; - - return my; -}(Toolbar || {})); - +},{"../../../../service/translation/languages":165,"../../avatar/Avatar":18,"../../util/UIUtil":35,"./../../../settings/Settings":49}],29:[function(require,module,exports){ +/* global $ */ +var PanelToggler = require("../side_pannels/SidePanelToggler"); + +var buttonHandlers = { + "bottom_toolbar_contact_list": function () { + BottomToolbar.toggleContactList(); + }, + "bottom_toolbar_film_strip": function () { + BottomToolbar.toggleFilmStrip(); + }, + "bottom_toolbar_chat": function () { + BottomToolbar.toggleChat(); + } +}; + +var BottomToolbar = (function (my) { + my.init = function () { + for(var k in buttonHandlers) + $("#" + k).click(buttonHandlers[k]); + }; + + my.toggleChat = function() { + PanelToggler.toggleChat(); + }; + + my.toggleContactList = function() { + PanelToggler.toggleContactList(); + }; + + my.toggleFilmStrip = function() { + var filmstrip = $("#remoteVideos"); + filmstrip.toggleClass("hidden"); + }; + + $(document).bind("remotevideo.resized", function (event, width, height) { + var bottom = (height - $('#bottomToolbar').outerHeight())/2 + 18; + + $('#bottomToolbar').css({bottom: bottom + 'px'}); + }); + + return my; +}(BottomToolbar || {})); + +module.exports = BottomToolbar; + +},{"../side_pannels/SidePanelToggler":22}],30:[function(require,module,exports){ +/* global APP, $, buttonClick, config, lockRoom, interfaceConfig, setSharedKey, + Util */ +var messageHandler = require("../util/MessageHandler"); +var BottomToolbar = require("./BottomToolbar"); +var Prezi = require("../prezi/Prezi"); +var Etherpad = require("../etherpad/Etherpad"); +var PanelToggler = require("../side_pannels/SidePanelToggler"); +var Authentication = require("../authentication/Authentication"); +var UIUtil = require("../util/UIUtil"); +var AuthenticationEvents + = require("../../../service/authentication/AuthenticationEvents"); + +var roomUrl = null; +var sharedKey = ''; +var UI = null; +var recordingToaster = null; + +var buttonHandlers = { + "toolbar_button_mute": function () { + return APP.UI.toggleAudio(); + }, + "toolbar_button_camera": function () { + return APP.UI.toggleVideo(); + }, + /*"toolbar_button_authentication": function () { + return Toolbar.authenticateClicked(); + },*/ + "toolbar_button_record": function () { + return toggleRecording(); + }, + "toolbar_button_security": function () { + return Toolbar.openLockDialog(); + }, + "toolbar_button_link": function () { + return Toolbar.openLinkDialog(); + }, + "toolbar_button_chat": function () { + return BottomToolbar.toggleChat(); + }, + "toolbar_button_prezi": function () { + return Prezi.openPreziDialog(); + }, + "toolbar_button_etherpad": function () { + return Etherpad.toggleEtherpad(0); + }, + "toolbar_button_desktopsharing": function () { + return APP.desktopsharing.toggleScreenSharing(); + }, + "toolbar_button_fullScreen": function() { + UIUtil.buttonClick("#toolbar_button_fullScreen", "icon-full-screen icon-exit-full-screen"); + return Toolbar.toggleFullScreen(); + }, + "toolbar_button_sip": function () { + return callSipButtonClicked(); + }, + "toolbar_button_dialpad": function () { + return dialpadButtonClicked(); + }, + "toolbar_button_settings": function () { + PanelToggler.toggleSettingsMenu(); + }, + "toolbar_button_hangup": function () { + return hangup(); + }, + "toolbar_button_login": function () { + Toolbar.authenticateClicked(); + }, + "toolbar_button_logout": function () { + // Ask for confirmation + messageHandler.openTwoButtonDialog( + "dialog.logoutTitle", + null, + "dialog.logoutQuestion", + null, + false, + "dialog.Yes", + function (evt, yes) { + if (yes) { + APP.xmpp.logout(function (url) { + if (url) { + window.location.href = url; + } else { + hangup(); + } + }); + } + }); + } +}; + +function hangup() { + APP.xmpp.disposeConference(); + if(config.enableWelcomePage) { + setTimeout(function() { + window.localStorage.welcomePageDisabled = false; + window.location.pathname = "/"; + }, 10000); + + } + + var title = APP.translation.generateTranslationHTML( + "dialog.sessTerminated"); + var msg = APP.translation.generateTranslationHTML( + "dialog.hungUp"); + var button = APP.translation.generateTranslationHTML( + "dialog.joinAgain"); + var buttons = []; + buttons.push({title: button, value: true}); + + UI.messageHandler.openDialog( + title, + msg, + true, + buttons, + function(event, value, message, formVals) { + window.location.reload(); + return false; + } + ); +} + +/** + * Starts or stops the recording for the conference. + */ + +function toggleRecording(predefinedToken) { + APP.xmpp.toggleRecording(function (callback) { + if (predefinedToken) { + callback(UIUtil.escapeHtml(predefinedToken)); + return; + } + + var msg = APP.translation.generateTranslationHTML( + "dialog.recordingToken"); + var token = APP.translation.translateString("dialog.token"); + APP.UI.messageHandler.openTwoButtonDialog(null, null, null, + '

' + msg + '

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

' + sipMsg + '

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

' + msg + '

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

' + settings1 + '

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

"; - } - - if (this.showMoreValue) { - var downloadBandwidth, uploadBandwidth, transport; - if (this.bandwidth === null) { - downloadBandwidth = "N/A"; - uploadBandwidth = "N/A"; - } else { - downloadBandwidth = this.bandwidth.download? - this.bandwidth.download + " Kbps" : - "N/A"; - uploadBandwidth = this.bandwidth.upload? - this.bandwidth.upload + " Kbps" : - "N/A"; - } - - if (!this.transport || this.transport.length === 0) { - transport = "" + - "" + - translate("connectionindicator.address") + "" + - " N/A"; - } else { - var data = {remoteIP: [], localIP:[], remotePort:[], localPort:[]}; - for(i = 0; i < this.transport.length; i++) { - var ip = ConnectionIndicator.getIP(this.transport[i].ip); - var port = ConnectionIndicator.getPort(this.transport[i].ip); - var localIP = - ConnectionIndicator.getIP(this.transport[i].localip); - var localPort = - ConnectionIndicator.getPort(this.transport[i].localip); - if(data.remoteIP.indexOf(ip) == -1) { - data.remoteIP.push(ip); - } - - if(data.remotePort.indexOf(port) == -1) { - data.remotePort.push(port); - } - - if(data.localIP.indexOf(localIP) == -1) { - data.localIP.push(localIP); - } - - if(data.localPort.indexOf(localPort) == -1) { - data.localPort.push(localPort); - } - } - - var local_address_key = "connectionindicator.localaddress"; - var remote_address_key = "connectionindicator.remoteaddress"; - var localTransport = - "" + - translate(local_address_key, {count: data.localIP.length}) + - " " + - ConnectionIndicator.getStringFromArray(data.localIP) + - ""; - transport = - "" + - translate(remote_address_key, - {count: data.remoteIP.length}) + - " " + - ConnectionIndicator.getStringFromArray(data.remoteIP) + - ""; - - var key_remote = "connectionindicator.remoteport", - key_local = "connectionindicator.localport"; - - transport += "" + - "" + - "" + - translate(key_remote, {count: this.transport.length}) + - ""; - localTransport += "" + - "" + - "" + - translate(key_local, {count: this.transport.length}) + - ""; - - transport += - ConnectionIndicator.getStringFromArray(data.remotePort); - localTransport += - ConnectionIndicator.getStringFromArray(data.localPort); - transport += ""; - transport += localTransport + ""; - transport +="" + - "" + - translate("connectionindicator.transport") + "" + - "" + this.transport[0].type + ""; - - } - - result += "" + - "" + - ""; - - result += transport + "
" + - "" + - translate("connectionindicator.bandwidth") + "" + - "" + - "" + - downloadBandwidth + - " " + - uploadBandwidth + "
"; - } - - return result; -}; - -/** - * Shows or hide the additional information. - */ -ConnectionIndicator.prototype.showMore = function () { - this.showMoreValue = !this.showMoreValue; - this.updatePopoverData(); -}; - - -function createIcon(classes) { - var icon = document.createElement("span"); - for(var i in classes) { - icon.classList.add(classes[i]); - } - icon.appendChild( - document.createElement("i")).classList.add("icon-connection"); - return icon; -} - -/** - * Creates the indicator - */ -ConnectionIndicator.prototype.create = function () { - this.connectionIndicatorContainer = document.createElement("div"); - this.connectionIndicatorContainer.className = "connectionindicator"; - this.connectionIndicatorContainer.style.display = "none"; - this.videoContainer.container.appendChild( - this.connectionIndicatorContainer); - this.popover = new JitsiPopover( - $("#" + this.videoContainer.videoSpanId + " > .connectionindicator"), - {content: "
" + - APP.translation.translateString("connectionindicator.na") + "
", - skin: "black"}); - - this.emptyIcon = this.connectionIndicatorContainer.appendChild( - createIcon(["connection", "connection_empty"])); - this.fullIcon = this.connectionIndicatorContainer.appendChild( - createIcon(["connection", "connection_full"])); -}; - -/** - * Removes the indicator - */ -ConnectionIndicator.prototype.remove = function() { - if (this.connectionIndicatorContainer.parentNode) { - this.connectionIndicatorContainer.parentNode.removeChild( - this.connectionIndicatorContainer); - } - this.popover.forceHide(); -}; - -/** - * Updates the data of the indicator - * @param percent the percent of connection quality - * @param object the statistics data. - */ -ConnectionIndicator.prototype.updateConnectionQuality = - function (percent, object) { - - if (percent === null) { - this.connectionIndicatorContainer.style.display = "none"; - this.popover.forceHide(); - return; - } else { - if(this.connectionIndicatorContainer.style.display == "none") { - this.connectionIndicatorContainer.style.display = "block"; - this.videoContainer.updateIconPositions(); - } - } - this.bandwidth = object.bandwidth; - this.bitrate = object.bitrate; - this.packetLoss = object.packetLoss; - this.transport = object.transport; - if (object.resolution) { - this.resolution = object.resolution; - } - for (var quality in ConnectionIndicator.connectionQualityValues) { - if (percent >= quality) { - this.fullIcon.style.width = - ConnectionIndicator.connectionQualityValues[quality]; - } - } - this.updatePopoverData(); -}; - -/** - * Updates the resolution - * @param resolution the new resolution - */ -ConnectionIndicator.prototype.updateResolution = function (resolution) { - this.resolution = resolution; - this.updatePopoverData(); -}; - -/** - * Updates the content of the popover - */ -ConnectionIndicator.prototype.updatePopoverData = function () { - this.popover.updateContent( - "
" + this.generateText() + "
"); - APP.translation.translateElement($(".connection_info")); -}; - -/** - * Hides the popover - */ -ConnectionIndicator.prototype.hide = function () { - this.popover.forceHide(); -}; - -/** - * Hides the indicator - */ -ConnectionIndicator.prototype.hideIndicator = function () { - this.connectionIndicatorContainer.style.display = "none"; - if(this.popover) - this.popover.forceHide(); -}; - +},{"../side_pannels/SidePanelToggler":22}],36:[function(require,module,exports){ +/* global APP, $ */ +var JitsiPopover = require("../util/JitsiPopover"); + +/** + * Constructs new connection indicator. + * @param videoContainer the video container associated with the indicator. + * @constructor + */ +function ConnectionIndicator(videoContainer, jid) { + this.videoContainer = videoContainer; + this.bandwidth = null; + this.packetLoss = null; + this.bitrate = null; + this.showMoreValue = false; + this.resolution = null; + this.transport = []; + this.popover = null; + this.jid = jid; + this.create(); +} + +/** + * Values for the connection quality + * @type {{98: string, + * 81: string, + * 64: string, + * 47: string, + * 30: string, + * 0: string}} + */ +ConnectionIndicator.connectionQualityValues = { + 98: "18px", //full + 81: "15px",//4 bars + 64: "11px",//3 bars + 47: "7px",//2 bars + 30: "3px",//1 bar + 0: "0px"//empty +}; + +ConnectionIndicator.getIP = function(value) { + return value.substring(0, value.lastIndexOf(":")); +}; + +ConnectionIndicator.getPort = function(value) { + return value.substring(value.lastIndexOf(":") + 1, value.length); +}; + +ConnectionIndicator.getStringFromArray = function (array) { + var res = ""; + for(var i = 0; i < array.length; i++) { + res += (i === 0? "" : ", ") + array[i]; + } + return res; +}; + +/** + * Generates the html content. + * @returns {string} the html content. + */ +ConnectionIndicator.prototype.generateText = function () { + var downloadBitrate, uploadBitrate, packetLoss, resolution, i; + + var translate = APP.translation.translateString; + + if(this.bitrate === null) { + downloadBitrate = "N/A"; + uploadBitrate = "N/A"; + } + else { + downloadBitrate = + this.bitrate.download? this.bitrate.download + " Kbps" : "N/A"; + uploadBitrate = + this.bitrate.upload? this.bitrate.upload + " Kbps" : "N/A"; + } + + if(this.packetLoss === null) { + packetLoss = "N/A"; + } else { + + packetLoss = "" + + (this.packetLoss.download !== null ? + this.packetLoss.download : "N/A") + + "% " + + (this.packetLoss.upload !== null? this.packetLoss.upload : "N/A") + + "%"; + } + + var resolutionValue = null; + if(this.resolution && this.jid != null) { + var keys = Object.keys(this.resolution); + for(var ssrc in this.resolution) { + resolutionValue = this.resolution[ssrc]; + } + } + + if(this.jid === null) { + resolution = ""; + if(this.resolution === null || !Object.keys(this.resolution) || + Object.keys(this.resolution).length === 0) { + resolution = "N/A"; + } else { + for (i in this.resolution) { + resolutionValue = this.resolution[i]; + if (resolutionValue) { + if (resolutionValue.height && + resolutionValue.width) { + resolution += (resolution === "" ? "" : ", ") + + resolutionValue.width + "x" + + resolutionValue.height; + } + } + } + } + } else if(!resolutionValue || + !resolutionValue.height || + !resolutionValue.width) { + resolution = "N/A"; + } else { + resolution = resolutionValue.width + "x" + resolutionValue.height; + } + + var result = "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "
" + + translate("connectionindicator.bitrate") + "" + + downloadBitrate + " " + + uploadBitrate + "
" + + translate("connectionindicator.packetloss") + "" + packetLoss + "
" + + translate("connectionindicator.resolution") + "" + resolution + "
"; + + if(this.videoContainer.videoSpanId == "localVideoContainer") { + result += "
" + + translate("connectionindicator." + (this.showMoreValue ? "less" : "more")) + + "

"; + } + + if (this.showMoreValue) { + var downloadBandwidth, uploadBandwidth, transport; + if (this.bandwidth === null) { + downloadBandwidth = "N/A"; + uploadBandwidth = "N/A"; + } else { + downloadBandwidth = this.bandwidth.download? + this.bandwidth.download + " Kbps" : + "N/A"; + uploadBandwidth = this.bandwidth.upload? + this.bandwidth.upload + " Kbps" : + "N/A"; + } + + if (!this.transport || this.transport.length === 0) { + transport = "" + + "" + + translate("connectionindicator.address") + "" + + " N/A"; + } else { + var data = {remoteIP: [], localIP:[], remotePort:[], localPort:[]}; + for(i = 0; i < this.transport.length; i++) { + var ip = ConnectionIndicator.getIP(this.transport[i].ip); + var port = ConnectionIndicator.getPort(this.transport[i].ip); + var localIP = + ConnectionIndicator.getIP(this.transport[i].localip); + var localPort = + ConnectionIndicator.getPort(this.transport[i].localip); + if(data.remoteIP.indexOf(ip) == -1) { + data.remoteIP.push(ip); + } + + if(data.remotePort.indexOf(port) == -1) { + data.remotePort.push(port); + } + + if(data.localIP.indexOf(localIP) == -1) { + data.localIP.push(localIP); + } + + if(data.localPort.indexOf(localPort) == -1) { + data.localPort.push(localPort); + } + } + + var local_address_key = "connectionindicator.localaddress"; + var remote_address_key = "connectionindicator.remoteaddress"; + var localTransport = + "" + + translate(local_address_key, {count: data.localIP.length}) + + " " + + ConnectionIndicator.getStringFromArray(data.localIP) + + ""; + transport = + "" + + translate(remote_address_key, + {count: data.remoteIP.length}) + + " " + + ConnectionIndicator.getStringFromArray(data.remoteIP) + + ""; + + var key_remote = "connectionindicator.remoteport", + key_local = "connectionindicator.localport"; + + transport += "" + + "" + + "" + + translate(key_remote, {count: this.transport.length}) + + ""; + localTransport += "" + + "" + + "" + + translate(key_local, {count: this.transport.length}) + + ""; + + transport += + ConnectionIndicator.getStringFromArray(data.remotePort); + localTransport += + ConnectionIndicator.getStringFromArray(data.localPort); + transport += ""; + transport += localTransport + ""; + transport +="" + + "" + + translate("connectionindicator.transport") + "" + + "" + this.transport[0].type + ""; + + } + + result += "" + + "" + + ""; + + result += transport + "
" + + "" + + translate("connectionindicator.bandwidth") + "" + + "" + + "" + + downloadBandwidth + + " " + + uploadBandwidth + "
"; + } + + return result; +}; + +/** + * Shows or hide the additional information. + */ +ConnectionIndicator.prototype.showMore = function () { + this.showMoreValue = !this.showMoreValue; + this.updatePopoverData(); +}; + + +function createIcon(classes) { + var icon = document.createElement("span"); + for(var i in classes) { + icon.classList.add(classes[i]); + } + icon.appendChild( + document.createElement("i")).classList.add("icon-connection"); + return icon; +} + +/** + * Creates the indicator + */ +ConnectionIndicator.prototype.create = function () { + this.connectionIndicatorContainer = document.createElement("div"); + this.connectionIndicatorContainer.className = "connectionindicator"; + this.connectionIndicatorContainer.style.display = "none"; + this.videoContainer.container.appendChild( + this.connectionIndicatorContainer); + this.popover = new JitsiPopover( + $("#" + this.videoContainer.videoSpanId + " > .connectionindicator"), + {content: "
" + + APP.translation.translateString("connectionindicator.na") + "
", + skin: "black"}); + + this.emptyIcon = this.connectionIndicatorContainer.appendChild( + createIcon(["connection", "connection_empty"])); + this.fullIcon = this.connectionIndicatorContainer.appendChild( + createIcon(["connection", "connection_full"])); +}; + +/** + * Removes the indicator + */ +ConnectionIndicator.prototype.remove = function() { + if (this.connectionIndicatorContainer.parentNode) { + this.connectionIndicatorContainer.parentNode.removeChild( + this.connectionIndicatorContainer); + } + this.popover.forceHide(); +}; + +/** + * Updates the data of the indicator + * @param percent the percent of connection quality + * @param object the statistics data. + */ +ConnectionIndicator.prototype.updateConnectionQuality = + function (percent, object) { + + if (percent === null) { + this.connectionIndicatorContainer.style.display = "none"; + this.popover.forceHide(); + return; + } else { + if(this.connectionIndicatorContainer.style.display == "none") { + this.connectionIndicatorContainer.style.display = "block"; + this.videoContainer.updateIconPositions(); + } + } + this.bandwidth = object.bandwidth; + this.bitrate = object.bitrate; + this.packetLoss = object.packetLoss; + this.transport = object.transport; + if (object.resolution) { + this.resolution = object.resolution; + } + for (var quality in ConnectionIndicator.connectionQualityValues) { + if (percent >= quality) { + this.fullIcon.style.width = + ConnectionIndicator.connectionQualityValues[quality]; + } + } + this.updatePopoverData(); +}; + +/** + * Updates the resolution + * @param resolution the new resolution + */ +ConnectionIndicator.prototype.updateResolution = function (resolution) { + this.resolution = resolution; + this.updatePopoverData(); +}; + +/** + * Updates the content of the popover + */ +ConnectionIndicator.prototype.updatePopoverData = function () { + this.popover.updateContent( + "
" + this.generateText() + "
"); + APP.translation.translateElement($(".connection_info")); +}; + +/** + * Hides the popover + */ +ConnectionIndicator.prototype.hide = function () { + this.popover.forceHide(); +}; + +/** + * Hides the indicator + */ +ConnectionIndicator.prototype.hideIndicator = function () { + this.connectionIndicatorContainer.style.display = "none"; + if(this.popover) + this.popover.forceHide(); +}; + module.exports = ConnectionIndicator; -},{"../util/JitsiPopover":30}],35:[function(require,module,exports){ -/* global $, APP, Strophe, interfaceConfig */ -var Avatar = require("../avatar/Avatar"); -var RTCBrowserType = require("../../RTC/RTCBrowserType"); -var UIUtil = require("../util/UIUtil"); -var UIEvents = require("../../../service/UI/UIEvents"); -var xmpp = require("../../xmpp/xmpp"); -var ToolbarToggler = require("../toolbars/ToolbarToggler"); - -// FIXME: With Temasys we have to re-select everytime -//var video = $('#largeVideo'); - -var currentVideoWidth = null; -var currentVideoHeight = null; -// By default we use camera -var getVideoSize = getCameraVideoSize; -var getVideoPosition = getCameraVideoPosition; -/** - * The small video instance that is displayed in the large video - * @type {SmallVideo} - */ -var currentSmallVideo = null; -/** - * Indicates whether the large video is enabled. - * @type {boolean} - */ -var isEnabled = true; -/** - * Current large video state. - * Possible values - video, prezi or etherpad. - * @type {string} - */ -var state = "video"; - -/** - * Returns the html element associated with the passed state of large video - * @param state the state. - * @returns {JQuery|*|jQuery|HTMLElement} the container. - */ -function getContainerByState(state) -{ - var selector = null; - switch (state) - { - case "video": - selector = "#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 = currentSmallVideo.getVideoType() === 'screen'; - // Change the way we'll be measuring and positioning large video - - getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize; - getVideoPosition = isDesktop ? getDesktopVideoPosition : - getCameraVideoPosition; - - - // Only if the large video is currently visible. - if (isVisible) { - LargeVideo.VideoLayout.largeVideoUpdated(currentSmallVideo); - - $('#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 (resourceJid, newVideoType) { - if (!isEnabled) - return; - if (LargeVideo.isCurrentlyOnLarge(resourceJid)) - { - var isDesktop = newVideoType === 'screen'; - 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); - }, - /** - * Resizes the large html elements. - * @param animate boolean property that indicates whether the resize should be animated or not. - * @param isChatVisible boolean property that indicates whether the chat area is displayed or not. - * If that parameter is null the method will check the chat pannel visibility. - * @param completeFunction a function to be called when the video space is resized - * @returns {*[]} array with the current width and height values of the largeVideo html element. - */ - 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); - - this.VideoLayout.resizeVideoSpace(animate, isVisible, completeFunction); - if(animate) { - $('#largeVideoContainer').animate({ - width: availableWidth, - height: availableHeight - }, - { - queue: false, - duration: 500 - }); - } else { - $('#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); - }, - - /** - * Enables/disables the filter indicating a video problem to the user. - * - * @param enable true to enable, false to disable - */ - enableVideoProblemFilter: function (enable) { - $("#largeVideo").toggleClass("videoProblemFilter", enable); - } -}; - +},{"../util/JitsiPopover":32}],37:[function(require,module,exports){ +/* global $, APP, Strophe, interfaceConfig */ +var Avatar = require("../avatar/Avatar"); +var RTCBrowserType = require("../../RTC/RTCBrowserType"); +var UIUtil = require("../util/UIUtil"); +var UIEvents = require("../../../service/UI/UIEvents"); +var xmpp = require("../../xmpp/xmpp"); +var ToolbarToggler = require("../toolbars/ToolbarToggler"); + +// FIXME: With Temasys we have to re-select everytime +//var video = $('#largeVideo'); + +var currentVideoWidth = null; +var currentVideoHeight = null; +// By default we use camera +var getVideoSize = getCameraVideoSize; +var getVideoPosition = getCameraVideoPosition; +/** + * The small video instance that is displayed in the large video + * @type {SmallVideo} + */ +var currentSmallVideo = null; +/** + * Indicates whether the large video is enabled. + * @type {boolean} + */ +var isEnabled = true; +/** + * Current large video state. + * Possible values - video, prezi or etherpad. + * @type {string} + */ +var state = "video"; + +/** + * Returns the html element associated with the passed state of large video + * @param state the state. + * @returns {JQuery|*|jQuery|HTMLElement} the container. + */ +function getContainerByState(state) +{ + var selector = null; + switch (state) + { + case "video": + selector = "#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 = currentSmallVideo.getVideoType() === 'screen'; + // Change the way we'll be measuring and positioning large video + + getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize; + getVideoPosition = isDesktop ? getDesktopVideoPosition : + getCameraVideoPosition; + + + // Only if the large video is currently visible. + if (isVisible) { + LargeVideo.VideoLayout.largeVideoUpdated(currentSmallVideo); + + $('#largeVideoWrapper').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); + } + // We are doing fadeOut/fadeIn animations on parent div which wraps + // largeVideo, because when Temasys plugin is in use it replaces + //