Integration of statistics module

This commit is contained in:
isymchych 2015-12-29 14:41:43 +02:00
parent 0fd0f5b633
commit a10f9437f1
12 changed files with 113 additions and 263 deletions

19
app.js
View File

@ -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();

View File

@ -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.

View File

@ -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) {

View File

@ -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();

View File

@ -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);
}
});
},
/**

View File

@ -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;

View File

@ -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();
class AnalyticsAdapter {
constructor () {
this.analytics = new AnalyticsImpl();
}
sendEvent (...args) {
try {
this.analytics.sendEvent(...args);
} catch (ignored) {}
}
}
export default new AnalyticsAdapter();

View File

@ -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));
}
/**
* <tt>LocalStatsCollector</tt> 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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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<vars.length;i++) {
@ -82,27 +81,11 @@ function checkForParameter() {
}
module.exports = {
init: function (lang) {
var options = defaultOptions;
init: function (settingsLang) {
let options = defaultOptions;
if(!lang)
{
lang = checkForParameter();
if(!lang)
{
var settings = Settings.getSettings();
if(settings)
lang = settings.language;
if(!lang && config.defaultLanguage)
{
lang = config.defaultLanguage;
}
}
}
if(lang) {
let lang = getLangFromQuery() || settingsLang || config.defaultLanguage;
if (lang) {
options.lng = lang;
}
@ -124,8 +107,7 @@ module.exports = {
},
generateTranslationHTML: function (key, options) {
var str = "<span data-i18n=\"" + key + "\"";
if(options)
{
if (options) {
str += " data-i18n-options=\"" + JSON.stringify(options) + "\"";
}
str += ">";

View File

@ -1,4 +1,4 @@
var RandomUtil = require('./RandomUtil');
import RandomUtil from './RandomUtil';
/**
* from faker.js - Copyright (c) 2014-2015 Matthew Bergman & Marak Squires
@ -417,13 +417,9 @@ var names = [
* Generate random username.
* @returns {string} random username
*/
function generateUsername () {
export function generateUsername () {
var name = RandomUtil.randomElement(names);
var suffix = RandomUtil.randomAlphanumStr(3);
return name + '-' + suffix;
}
module.exports = {
generateUsername: generateUsername
};