diff --git a/app.js b/app.js index 716b3ae5b..6abba9548 100644 --- a/app.js +++ b/app.js @@ -18,6 +18,10 @@ import RoomnameGenerator from './modules/util/RoomnameGenerator'; import CQEvents from './service/connectionquality/CQEvents'; import UIEvents from './service/UI/UIEvents'; +import UI from "./modules/UI/UI"; +import statistics from "./modules/statistics/statistics"; +import settings from "./modules/settings/Settings"; + import {openConnection} from './modules/connection'; import AuthHandler from './modules/AuthHandler'; @@ -80,6 +84,10 @@ function buildRoomName () { const APP = { + UI, + statistics, + settings, + init () { let roomName = buildRoomName(); this.conference = { @@ -94,29 +102,28 @@ const APP = { }, muteAudio (mute) { APP.UI.eventEmitter.emit(UIEvents.AUDIO_MUTED, mute); + APP.statistics.onAudioMute(mute); }, toggleAudioMuted () { this.muteAudio(!this.audioMuted); }, muteVideo (mute) { APP.UI.eventEmitter.emit(UIEvents.VIDEO_MUTED, mute); + APP.statistics.onVideoMute(mute); }, toggleVideoMuted () { this.muteVideo(!this.videoMuted); } }; - 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.desktopsharing = require("./modules/desktopsharing/desktopsharing"); this.keyboardshortcut = require("./modules/keyboardshortcut/keyboardshortcut"); this.translation = require("./modules/translation/translation"); - this.settings = require("./modules/settings/Settings"); this.configFetch = require("./modules/config/HttpConfigFetch"); } }; @@ -126,6 +133,7 @@ function initConference(localTracks, connection) { openSctp: config.openSctp, disableAudioLevels: config.disableAudioLevels }); + APP.conference._room = room; // FIXME do not use this const addTrack = (track) => { room.addTrack(track); @@ -464,6 +472,8 @@ function initConference(localTracks, connection) { window.location.pathname = "/"; }, 3000); } + }, function (err) { + console.error(err); }); }); @@ -598,6 +608,7 @@ function createLocalTracks () { devices: ['audio', 'video'] }).catch(function (err) { console.error('failed to create local tracks', err); + APP.statistics.onGetUserMediaFailed(err); return []; }); } @@ -688,7 +699,7 @@ $(document).ready(function () { URLProcessor.setConfigParametersFromUrl(); APP.init(); - APP.translation.init(); + APP.translation.init(settings.getLanguage()); if (APP.API.isEnabled()) { APP.API.init(); diff --git a/modules/UI/Feedback.js b/modules/UI/Feedback.js index c9bbea0d6..419d3715d 100644 --- a/modules/UI/Feedback.js +++ b/modules/UI/Feedback.js @@ -1,11 +1,10 @@ -/* global $, config, interfaceConfig */ +/* global $, APP, config, interfaceConfig */ /* * Created by Yana Stamcheva on 2/10/15. */ var messageHandler = require("./util/MessageHandler"); var callStats = require("../statistics/CallStats"); -var APP = require("../../app"); /** * Constructs the html for the overall feedback window. diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 49a457439..b0c884102 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -16,16 +16,15 @@ import EtherpadManager from './etherpad/Etherpad'; import VideoLayout from "./videolayout/VideoLayout"; import SettingsMenu from "./side_pannels/settings/SettingsMenu"; +import Settings from "./../settings/Settings"; var EventEmitter = require("events"); -var Settings = require("./../settings/Settings"); UI.messageHandler = require("./util/MessageHandler"); var messageHandler = UI.messageHandler; var JitsiPopover = require("./util/JitsiPopover"); var CQEvents = require("../../service/connectionquality/CQEvents"); var DesktopSharingEventTypes = require("../../service/desktopsharing/DesktopSharingEventTypes"); -var StatisticsEvents = require("../../service/statistics/Events"); var Feedback = require("./Feedback"); var eventEmitter = new EventEmitter(); @@ -611,8 +610,8 @@ UI.updateLocalStats = function (percent, stats) { VideoLayout.updateLocalConnectionStats(percent, stats); }; -UI.updateRemoteStats = function (jid, percent, stats) { - VideoLayout.updateConnectionStats(jid, percent, stats); +UI.updateRemoteStats = function (id, percent, stats) { + VideoLayout.updateConnectionStats(id, percent, stats); }; UI.markVideoInterrupted = function (interrupted) { diff --git a/modules/UI/videolayout/RemoteVideo.js b/modules/UI/videolayout/RemoteVideo.js index 4fe7dd463..66a35c132 100644 --- a/modules/UI/videolayout/RemoteVideo.js +++ b/modules/UI/videolayout/RemoteVideo.js @@ -296,6 +296,12 @@ RemoteVideo.prototype.showPeerContainer = function (state) { }; +RemoteVideo.prototype.updateResolution = function (resolution) { + if (this.connectionIndicator) { + this.connectionIndicator.updateResolution(resolution); + } +}; + RemoteVideo.prototype.removeConnectionIndicator = function () { if (this.connectionIndicator) this.connectionIndicator.remove(); diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js index 2a471ee0f..683693fb5 100644 --- a/modules/UI/videolayout/VideoLayout.js +++ b/modules/UI/videolayout/VideoLayout.js @@ -733,23 +733,25 @@ var VideoLayout = { * @param object */ updateLocalConnectionStats (percent, object) { - var resolution = null; + let resolutions = {}; if (object.resolution !== null) { - resolution = object.resolution; - object.resolution = resolution[APP.xmpp.myJid()]; - delete resolution[APP.xmpp.myJid()]; + resolutions = object.resolution; + object.resolution = resolutions[APP.conference.localId]; } localVideoThumbnail.updateStatsIndicator(percent, object); - for (var jid in resolution) { - if (resolution[jid] === null) - continue; - var resourceJid = Strophe.getResourceFromJid(jid); - if (remoteVideos[resourceJid] && - remoteVideos[resourceJid].connectionIndicator) { - remoteVideos[resourceJid].connectionIndicator. - updateResolution(resolution[jid]); + + Object.keys(resolutions).forEach(function (id) { + if (APP.conference.isLocalId(id)) { + return; } - } + + let resolution = resolutions[id]; + let remoteVideo = remoteVideos[id]; + + if (resolution && remoteVideo) { + remoteVideo.updateResolution(resolution); + } + }); }, /** diff --git a/modules/settings/Settings.js b/modules/settings/Settings.js index f07b4c282..299057dfa 100644 --- a/modules/settings/Settings.js +++ b/modules/settings/Settings.js @@ -1,4 +1,4 @@ -var UsernameGenerator = require('../util/UsernameGenerator'); +import {generateUsername} from '../util/UsernameGenerator'; var email = ''; var displayName = ''; @@ -32,7 +32,7 @@ if (supportsLocalStorage()) { if (!window.localStorage.callStatsUserName) { window.localStorage.callStatsUserName - = UsernameGenerator.generateUsername(); + = generateUsername(); console.log('generated callstats uid', window.localStorage.callStatsUserName); @@ -45,10 +45,10 @@ if (supportsLocalStorage()) { } else { console.log("local storage is not supported"); userId = generateUniqueId(); - callStatsUserName = UsernameGenerator.generateUsername(); + callStatsUserName = generateUsername(); } -var Settings = { +export default { /** * Sets the local user display name and saves it to local storage @@ -99,10 +99,11 @@ var Settings = { language: language }; }, + getLanguage () { + return language; + }, setLanguage: function (lang) { language = lang; window.localStorage.language = lang; } }; - -module.exports = Settings; diff --git a/modules/statistics/AnalyticsAdapter.js b/modules/statistics/AnalyticsAdapter.js index 8492c1123..e66089ad4 100644 --- a/modules/statistics/AnalyticsAdapter.js +++ b/modules/statistics/AnalyticsAdapter.js @@ -1,15 +1,19 @@ -function NoopAnalytics() {} -NoopAnalytics.prototype.sendEvent = function () {}; - -function AnalyticsAdapter() { - var AnalyticsImpl = window.Analytics || NoopAnalytics; - this.analytics = new AnalyticsImpl(); +class NoopAnalytics { + sendEvent () {} } -AnalyticsAdapter.prototype.sendEvent = function (action, data) { - try { - this.analytics.sendEvent.apply(this.analytics, arguments); - } catch (ignored) {} -}; +const AnalyticsImpl = window.Analytics || NoopAnalytics; -module.exports = new AnalyticsAdapter(); \ No newline at end of file +class AnalyticsAdapter { + constructor () { + this.analytics = new AnalyticsImpl(); + } + + sendEvent (...args) { + try { + this.analytics.sendEvent(...args); + } catch (ignored) {} + } +} + +export default new AnalyticsAdapter(); diff --git a/modules/statistics/LocalStatsCollector.js b/modules/statistics/LocalStatsCollector.js deleted file mode 100644 index a3df9ed26..000000000 --- a/modules/statistics/LocalStatsCollector.js +++ /dev/null @@ -1,128 +0,0 @@ -/* global config, AudioContext */ -/** - * Provides statistics for the local stream. - */ - -var RTCBrowserType = require('../RTC/RTCBrowserType'); -var StatisticsEvents = require('../../service/statistics/Events'); - -/** - * Size of the webaudio analyzer buffer. - * @type {number} - */ -var WEBAUDIO_ANALYZER_FFT_SIZE = 2048; - -/** - * Value of the webaudio analyzer smoothing time parameter. - * @type {number} - */ -var WEBAUDIO_ANALYZER_SMOOTING_TIME = 0.8; - -/** - * Converts time domain data array to audio level. - * @param samples the time domain data array. - * @returns {number} the audio level - */ -function timeDomainDataToAudioLevel(samples) { - - var maxVolume = 0; - - var length = samples.length; - - for (var i = 0; i < length; i++) { - if (maxVolume < samples[i]) - maxVolume = samples[i]; - } - - return parseFloat(((maxVolume - 127) / 128).toFixed(3)); -} - -/** - * Animates audio level change - * @param newLevel the new audio level - * @param lastLevel the last audio level - * @returns {Number} the audio level to be set - */ -function animateLevel(newLevel, lastLevel) { - var value = 0; - var diff = lastLevel - newLevel; - if(diff > 0.2) { - value = lastLevel - 0.2; - } - else if(diff < -0.4) { - value = lastLevel + 0.4; - } - else { - value = newLevel; - } - - return parseFloat(value.toFixed(3)); -} - - -/** - * LocalStatsCollector calculates statistics for the local stream. - * - * @param stream the local stream - * @param interval stats refresh interval given in ms. - * @constructor - */ -function LocalStatsCollector(stream, interval, - statisticsService, eventEmitter) { - window.AudioContext = window.AudioContext || window.webkitAudioContext; - this.stream = stream; - this.intervalId = null; - this.intervalMilis = interval; - this.eventEmitter = eventEmitter; - this.audioLevel = 0; - this.statisticsService = statisticsService; -} - -/** - * Starts the collecting the statistics. - */ -LocalStatsCollector.prototype.start = function () { - if (config.disableAudioLevels || !window.AudioContext || - RTCBrowserType.isTemasysPluginUsed()) - return; - - var context = new AudioContext(); - var analyser = context.createAnalyser(); - analyser.smoothingTimeConstant = WEBAUDIO_ANALYZER_SMOOTING_TIME; - analyser.fftSize = WEBAUDIO_ANALYZER_FFT_SIZE; - - - var source = context.createMediaStreamSource(this.stream); - source.connect(analyser); - - - var self = this; - - this.intervalId = setInterval( - function () { - var array = new Uint8Array(analyser.frequencyBinCount); - analyser.getByteTimeDomainData(array); - var audioLevel = timeDomainDataToAudioLevel(array); - if (audioLevel != self.audioLevel) { - self.audioLevel = animateLevel(audioLevel, self.audioLevel); - self.eventEmitter.emit( - StatisticsEvents.AUDIO_LEVEL, - self.statisticsService.LOCAL_JID, - self.audioLevel); - } - }, - this.intervalMilis - ); -}; - -/** - * Stops collecting the statistics. - */ -LocalStatsCollector.prototype.stop = function () { - if (this.intervalId) { - clearInterval(this.intervalId); - this.intervalId = null; - } -}; - -module.exports = LocalStatsCollector; \ No newline at end of file diff --git a/modules/statistics/RTPStatsCollector.js b/modules/statistics/RTPStatsCollector.js index 0dbb6a737..9bbcad6f3 100644 --- a/modules/statistics/RTPStatsCollector.js +++ b/modules/statistics/RTPStatsCollector.js @@ -385,7 +385,7 @@ StatsCollector.prototype.addStatsToBeLogged = function (reports) { StatsCollector.prototype.logStats = function () { - if(!APP.xmpp.sendLogs(this.statsToBeLogged)) + if(!APP.conference._room.xmpp.sendLogs(this.statsToBeLogged)) return; // Reset the stats this.statsToBeLogged.stats = {}; @@ -501,7 +501,7 @@ StatsCollector.prototype.processStatsReport = function () { var ssrc = getStatValue(now, 'ssrc'); if(!ssrc) continue; - var jid = APP.xmpp.getJidFromSSRC(ssrc); + var jid = APP.conference._room.room.getJidBySSRC(ssrc); if (!jid && (Date.now() - now.timestamp) < 3000) { console.warn("No jid for ssrc: " + ssrc); continue; @@ -647,12 +647,20 @@ StatsCollector.prototype.processStatsReport = function () { upload: calculatePacketLoss(lostPackets.upload, totalPackets.upload) }; + + let idResolution = {}; + if (resolutions) { // use id instead of jid + Object.keys(resolutions).forEach(function (jid) { + let id = Strophe.getResourceFromJid(jid); + resolution[id] = resolutions[id]; + }); + } this.eventEmitter.emit(StatisticsEvents.CONNECTION_STATS, { "bitrate": PeerStats.bitrate, "packetLoss": PeerStats.packetLoss, "bandwidth": PeerStats.bandwidth, - "resolution": resolutions, + "resolution": idResolution, "transport": PeerStats.transport }); PeerStats.transport = []; @@ -681,7 +689,7 @@ StatsCollector.prototype.processAudioLevelReport = function () { } var ssrc = getStatValue(now, 'ssrc'); - var jid = APP.xmpp.getJidFromSSRC(ssrc); + var jid = APP.conference._room.room.getJidBySSRC(ssrc); if (!jid) { if((Date.now() - now.timestamp) < 3000) console.warn("No jid for ssrc: " + ssrc); @@ -713,7 +721,7 @@ StatsCollector.prototype.processAudioLevelReport = function () { // but it seems to vary between 0 and around 32k. audioLevel = audioLevel / 32767; jidStats.setSsrcAudioLevel(ssrc, audioLevel); - if (jid != APP.xmpp.myJid()) { + if (jid != APP.conference._room.room.myroomjid) { this.eventEmitter.emit( StatisticsEvents.AUDIO_LEVEL, jid, audioLevel); } diff --git a/modules/statistics/statistics.js b/modules/statistics/statistics.js index 578970f5e..b84fd0d30 100644 --- a/modules/statistics/statistics.js +++ b/modules/statistics/statistics.js @@ -2,7 +2,6 @@ /** * Created by hristo on 8/4/14. */ -var LocalStats = require("./LocalStatsCollector.js"); var RTPStats = require("./RTPStatsCollector.js"); var EventEmitter = require("events"); var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); @@ -13,17 +12,8 @@ var StatisticsEvents = require("../../service/statistics/Events"); var eventEmitter = new EventEmitter(); -var localStats = null; - var rtpStats = null; -function stopLocal() { - if (localStats) { - localStats.stop(); - localStats = null; - } -} - function stopRemote() { if (rtpStats) { rtpStats.stop(); @@ -41,26 +31,15 @@ function startRemoteStats (peerconnection) { rtpStats.start(); } -function onStreamCreated(stream) { - if(stream.getOriginalStream().getAudioTracks().length === 0) { - return; - } - - localStats = new LocalStats(stream.getOriginalStream(), 200, statistics, - eventEmitter); - localStats.start(); -} - function onDisposeConference(onUnload) { CallStats.sendTerminateEvent(); stopRemote(); - if(onUnload) { - stopLocal(); + if (onUnload) { eventEmitter.removeAllListeners(); } } -var statistics = { +export default { /** * Indicates that this audio level is for local jid. * @type {string} @@ -74,65 +53,61 @@ var statistics = { eventEmitter.removeListener(type, listener); }, stop: function () { - stopLocal(); stopRemote(); - if(eventEmitter) - { + if (eventEmitter) { eventEmitter.removeAllListeners(); } }, - stopRemoteStatistics: function() - { - stopRemote(); + onAudioMute (mute) { + CallStats.sendMuteEvent(mute, "audio"); + }, + onVideoMute (mute) { + CallStats.sendMuteEvent(mute, "video"); + }, + onGetUserMediaFailed (e) { + CallStats.sendGetUserMediaFailed(e); }, start: function () { - return; - APP.RTC.addStreamListener(onStreamCreated, - StreamEventTypes.EVENT_TYPE_LOCAL_CREATED); - APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, - onDisposeConference); + const xmpp = APP.conference._room.xmpp; + xmpp.addListener( + XMPPEvents.DISPOSE_CONFERENCE, + onDisposeConference + ); //FIXME: we may want to change CALL INCOMING event to // onnegotiationneeded - APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) { + xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) { startRemoteStats(event.peerconnection); -// CallStats.init(event); + // CallStats.init(event); }); - APP.xmpp.addListener(XMPPEvents.PEERCONNECTION_READY, + xmpp.addListener( + XMPPEvents.PEERCONNECTION_READY, function (session) { - CallStats.init(session); - }); - APP.RTC.addListener(RTCEvents.AUDIO_MUTE, function (mute) { - CallStats.sendMuteEvent(mute, "audio"); - }); - APP.xmpp.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () { + CallStats.init(session); + } + ); + xmpp.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () { CallStats.sendSetupFailedEvent(); }); - APP.RTC.addListener(RTCEvents.VIDEO_MUTE, function (mute) { - CallStats.sendMuteEvent(mute, "video"); - }); - APP.RTC.addListener(RTCEvents.GET_USER_MEDIA_FAILED, function (e) { - CallStats.sendGetUserMediaFailed(e); - }); - APP.xmpp.addListener(RTCEvents.CREATE_OFFER_FAILED, function (e, pc) { + xmpp.addListener(RTCEvents.CREATE_OFFER_FAILED, function (e, pc) { CallStats.sendCreateOfferFailed(e, pc); }); - APP.xmpp.addListener(RTCEvents.CREATE_ANSWER_FAILED, function (e, pc) { + xmpp.addListener(RTCEvents.CREATE_ANSWER_FAILED, function (e, pc) { CallStats.sendCreateAnswerFailed(e, pc); }); - APP.xmpp.addListener( + xmpp.addListener( RTCEvents.SET_LOCAL_DESCRIPTION_FAILED, function (e, pc) { CallStats.sendSetLocalDescFailed(e, pc); } ); - APP.xmpp.addListener( + xmpp.addListener( RTCEvents.SET_REMOTE_DESCRIPTION_FAILED, function (e, pc) { CallStats.sendSetRemoteDescFailed(e, pc); } ); - APP.xmpp.addListener( + xmpp.addListener( RTCEvents.ADD_ICE_CANDIDATE_FAILED, function (e, pc) { CallStats.sendAddIceCandidateFailed(e, pc); @@ -140,8 +115,3 @@ var statistics = { ); } }; - - - - -module.exports = statistics; \ No newline at end of file diff --git a/modules/translation/translation.js b/modules/translation/translation.js index de3d38fa0..99331d5f3 100644 --- a/modules/translation/translation.js +++ b/modules/translation/translation.js @@ -1,7 +1,6 @@ /* global $, require, config, interfaceConfig */ var i18n = require("i18next-client"); var languages = require("../../service/translation/languages"); -var Settings = require("../settings/Settings"); var DEFAULT_LANG = languages.EN; i18n.addPostProcessor("resolveAppName", function(value, key, options) { @@ -68,7 +67,7 @@ function initCompleted(t) { $("[data-i18n]").i18n(); } -function checkForParameter() { +function getLangFromQuery() { var query = window.location.search.substring(1); var vars = query.split("&"); for (var i=0;i