|
@@ -13,368 +13,457 @@ import RTPStats from './RTPStatsCollector';
|
13
|
13
|
const logger = require('@jitsi/logger').getLogger('modules/statistics/statistics');
|
14
|
14
|
|
15
|
15
|
/**
|
16
|
|
- * Stores all active {@link Statistics} instances.
|
17
|
|
- * @type {Set<Statistics>}
|
|
16
|
+ * Statistics class provides various functionality related to collecting and reporting statistics.
|
18
|
17
|
*/
|
19
|
|
-let _instances;
|
|
18
|
+export default class Statistics {
|
20
|
19
|
|
21
|
|
-/**
|
22
|
|
- * Init statistic options
|
23
|
|
- * @param options
|
24
|
|
- */
|
25
|
|
-Statistics.init = function(options) {
|
26
|
|
- Statistics.audioLevelsEnabled = !options.disableAudioLevels;
|
27
|
|
- if (typeof options.pcStatsInterval === 'number') {
|
28
|
|
- Statistics.pcStatsInterval = options.pcStatsInterval;
|
29
|
|
- }
|
|
20
|
+ /**
|
|
21
|
+ * Stores all active Statistics instances.
|
|
22
|
+ * @type {Set<Statistics>}
|
|
23
|
+ */
|
|
24
|
+ static _instances;
|
30
|
25
|
|
31
|
|
- if (typeof options.audioLevelsInterval === 'number') {
|
32
|
|
- Statistics.audioLevelsInterval = options.audioLevelsInterval;
|
|
26
|
+ /**
|
|
27
|
+ * Static getter for instances property
|
|
28
|
+ * Returns the Set holding all active Statistics instances. Lazily
|
|
29
|
+ * initializes the Set to allow any Set polyfills to be applied.
|
|
30
|
+ * @type {Set<Statistics>}
|
|
31
|
+ */
|
|
32
|
+ static get instances() {
|
|
33
|
+ if (!Statistics._instances) {
|
|
34
|
+ Statistics._instances = new Set();
|
|
35
|
+ }
|
|
36
|
+
|
|
37
|
+ return Statistics._instances;
|
33
|
38
|
}
|
34
|
39
|
|
35
|
|
- Statistics.disableThirdPartyRequests = options.disableThirdPartyRequests;
|
|
40
|
+ /**
|
|
41
|
+ * Flag indicating whether audio levels are enabled or not.
|
|
42
|
+ * @static
|
|
43
|
+ * @type {boolean}
|
|
44
|
+ */
|
|
45
|
+ static audioLevelsEnabled = false;
|
36
|
46
|
|
37
|
|
- LocalStats.init();
|
38
|
|
- WatchRTC.init(options);
|
39
|
|
-};
|
|
47
|
+ /**
|
|
48
|
+ * The interval for audio levels stats collection.
|
|
49
|
+ * @static
|
|
50
|
+ * @type {number}
|
|
51
|
+ */
|
|
52
|
+ static audioLevelsInterval = 200;
|
40
|
53
|
|
41
|
|
-/**
|
42
|
|
- * The options to configure Statistics.
|
43
|
|
- * @typedef {Object} StatisticsOptions
|
44
|
|
- * @property {string} userName - The user name to use
|
45
|
|
- * @property {string} roomName - The room name we are currently in.
|
46
|
|
- *
|
47
|
|
- * @param {JitsiConference} conference - The conference instance from which the statistics were initialized.
|
48
|
|
- * @param {StatisticsOptions} options - The options to use creating the
|
49
|
|
- * Statistics.
|
50
|
|
- */
|
51
|
|
-export default function Statistics(conference, options) {
|
52
|
54
|
/**
|
53
|
|
- * {@link RTPStats} mapped by {@link TraceablePeerConnection.id} which
|
54
|
|
- * collect RTP statistics for each peerconnection.
|
55
|
|
- * @type {Map<string, RTPStats}
|
|
55
|
+ * The interval for peer connection stats collection.
|
|
56
|
+ * @static
|
|
57
|
+ * @type {number}
|
56
|
58
|
*/
|
57
|
|
- this.rtpStatsMap = new Map();
|
58
|
|
- this.eventEmitter = new EventEmitter();
|
59
|
|
- this.conference = conference;
|
60
|
|
- this.xmpp = conference?.xmpp;
|
61
|
|
- this.options = options || {};
|
|
59
|
+ static pcStatsInterval = 10000;
|
62
|
60
|
|
63
|
|
- Statistics.instances.add(this);
|
|
61
|
+ /**
|
|
62
|
+ * Flag indicating whether third party requests are disabled.
|
|
63
|
+ * @static
|
|
64
|
+ * @type {boolean}
|
|
65
|
+ */
|
|
66
|
+ static disableThirdPartyRequests = false;
|
64
|
67
|
|
65
|
|
- RTCStats.attachToConference(this.conference);
|
|
68
|
+ /**
|
|
69
|
+ * Analytics adapter for sending events.
|
|
70
|
+ * @static
|
|
71
|
+ * @type {Object}
|
|
72
|
+ */
|
|
73
|
+ static analytics = analytics;
|
66
|
74
|
|
67
|
|
- // WatchRTC is not required to work for react native
|
68
|
|
- if (!browser.isReactNative()) {
|
69
|
|
- WatchRTC.start(this.options.roomName, this.options.userName);
|
70
|
|
- }
|
|
75
|
+ /**
|
|
76
|
+ * Array holding local statistics collectors.
|
|
77
|
+ * @static
|
|
78
|
+ * @type {Array}
|
|
79
|
+ */
|
|
80
|
+ static localStats = [];
|
71
|
81
|
|
72
|
|
-}
|
73
|
|
-Statistics.audioLevelsEnabled = false;
|
74
|
|
-Statistics.audioLevelsInterval = 200;
|
75
|
|
-Statistics.pcStatsInterval = 10000;
|
76
|
|
-Statistics.disableThirdPartyRequests = false;
|
77
|
|
-Statistics.analytics = analytics;
|
|
82
|
+ /**
|
|
83
|
+ * Local JID constant.
|
|
84
|
+ * @static
|
|
85
|
+ * @type {string}
|
|
86
|
+ */
|
|
87
|
+ static LOCAL_JID = require('../../service/statistics/constants').LOCAL_JID;
|
78
|
88
|
|
79
|
|
-Object.defineProperty(Statistics, 'instances', {
|
80
|
89
|
/**
|
81
|
|
- * Returns the Set holding all active {@link Statistics} instances. Lazily
|
82
|
|
- * initializes the Set to allow any Set polyfills to be applied.
|
83
|
|
- * @type {Set<Statistics>}
|
|
90
|
+ * Init statistic options
|
|
91
|
+ * @static
|
|
92
|
+ * @param {Object} options - The options to initialize statistics with
|
84
|
93
|
*/
|
85
|
|
- get() {
|
86
|
|
- if (!_instances) {
|
87
|
|
- _instances = new Set();
|
|
94
|
+ static init(options) {
|
|
95
|
+ Statistics.audioLevelsEnabled = !options.disableAudioLevels;
|
|
96
|
+ if (typeof options.pcStatsInterval === 'number') {
|
|
97
|
+ Statistics.pcStatsInterval = options.pcStatsInterval;
|
88
|
98
|
}
|
89
|
99
|
|
90
|
|
- return _instances;
|
91
|
|
- }
|
92
|
|
-});
|
|
100
|
+ if (typeof options.audioLevelsInterval === 'number') {
|
|
101
|
+ Statistics.audioLevelsInterval = options.audioLevelsInterval;
|
|
102
|
+ }
|
93
|
103
|
|
94
|
|
-/**
|
95
|
|
- * Starts collecting RTP stats for given peerconnection.
|
96
|
|
- * @param {TraceablePeerConnection} peerconnection
|
97
|
|
- */
|
98
|
|
-Statistics.prototype.startRemoteStats = function(peerconnection) {
|
99
|
|
- this.stopRemoteStats(peerconnection);
|
100
|
|
-
|
101
|
|
- try {
|
102
|
|
- const rtpStats
|
103
|
|
- = new RTPStats(
|
104
|
|
- peerconnection,
|
105
|
|
- Statistics.audioLevelsInterval,
|
106
|
|
- Statistics.pcStatsInterval,
|
107
|
|
- this.eventEmitter);
|
108
|
|
-
|
109
|
|
- rtpStats.start(Statistics.audioLevelsEnabled);
|
110
|
|
- this.rtpStatsMap.set(peerconnection.id, rtpStats);
|
111
|
|
- } catch (e) {
|
112
|
|
- logger.error(`Failed to start collecting remote statistics: ${e}`);
|
113
|
|
- }
|
114
|
|
-};
|
|
104
|
+ if (typeof options.longTasksStatsInterval === 'number') {
|
|
105
|
+ Statistics.longTasksStatsInterval = options.longTasksStatsInterval;
|
|
106
|
+ }
|
115
|
107
|
|
116
|
|
-Statistics.localStats = [];
|
|
108
|
+ Statistics.disableThirdPartyRequests = options.disableThirdPartyRequests;
|
117
|
109
|
|
118
|
|
-Statistics.startLocalStats = function(track, callback) {
|
119
|
|
- if (browser.isIosBrowser()) {
|
120
|
|
- // On iOS browsers audio is lost if the audio input device is in use by another app
|
121
|
|
- // https://bugs.webkit.org/show_bug.cgi?id=233473
|
122
|
|
- // The culprit was using the AudioContext, so now we close the AudioContext during
|
123
|
|
- // the track being muted, and re-instantiate it afterwards.
|
124
|
|
- track.addEventListener(
|
125
|
|
- JitsiTrackEvents.NO_DATA_FROM_SOURCE,
|
|
110
|
+ LocalStats.init();
|
|
111
|
+ WatchRTC.init(options);
|
|
112
|
+ }
|
126
|
113
|
|
127
|
|
- /**
|
128
|
|
- * Closes AudioContext on no audio data, and enables it on data received again.
|
129
|
|
- *
|
130
|
|
- * @param {boolean} value - Whether we receive audio data or not.
|
131
|
|
- */
|
132
|
|
- async value => {
|
133
|
|
- if (value) {
|
134
|
|
- for (const localStat of Statistics.localStats) {
|
135
|
|
- localStat.stop();
|
|
114
|
+ /**
|
|
115
|
+ * Starts collecting local statistics for a track.
|
|
116
|
+ * @static
|
|
117
|
+ * @param {JitsiTrack} track - The track to collect statistics for
|
|
118
|
+ * @param {Function} callback - The callback to invoke with audio levels
|
|
119
|
+ */
|
|
120
|
+ static startLocalStats(track, callback) {
|
|
121
|
+ if (browser.isIosBrowser()) {
|
|
122
|
+ // On iOS browsers audio is lost if the audio input device is in use by another app
|
|
123
|
+ // https://bugs.webkit.org/show_bug.cgi?id=233473
|
|
124
|
+ // The culprit was using the AudioContext, so now we close the AudioContext during
|
|
125
|
+ // the track being muted, and re-instantiate it afterwards.
|
|
126
|
+ track.addEventListener(
|
|
127
|
+ JitsiTrackEvents.NO_DATA_FROM_SOURCE,
|
|
128
|
+
|
|
129
|
+ /**
|
|
130
|
+ * Closes AudioContext on no audio data, and enables it on data received again.
|
|
131
|
+ *
|
|
132
|
+ * @param {boolean} value - Whether we receive audio data or not.
|
|
133
|
+ */
|
|
134
|
+ async value => {
|
|
135
|
+ if (value) {
|
|
136
|
+ for (const localStat of Statistics.localStats) {
|
|
137
|
+ localStat.stop();
|
|
138
|
+ }
|
|
139
|
+
|
|
140
|
+ await LocalStats.disconnectAudioContext();
|
|
141
|
+ } else {
|
|
142
|
+ LocalStats.connectAudioContext();
|
|
143
|
+ for (const localStat of Statistics.localStats) {
|
|
144
|
+ localStat.start();
|
|
145
|
+ }
|
136
|
146
|
}
|
|
147
|
+ });
|
|
148
|
+ }
|
137
|
149
|
|
138
|
|
- await LocalStats.disconnectAudioContext();
|
139
|
|
- } else {
|
140
|
|
- LocalStats.connectAudioContext();
|
141
|
|
- for (const localStat of Statistics.localStats) {
|
142
|
|
- localStat.start();
|
143
|
|
- }
|
144
|
|
- }
|
145
|
|
- });
|
146
|
|
- }
|
|
150
|
+ if (!Statistics.audioLevelsEnabled) {
|
|
151
|
+ return;
|
|
152
|
+ }
|
147
|
153
|
|
148
|
|
- if (!Statistics.audioLevelsEnabled) {
|
149
|
|
- return;
|
150
|
|
- }
|
|
154
|
+ track.addEventListener(
|
|
155
|
+ JitsiTrackEvents.LOCAL_TRACK_STOPPED,
|
|
156
|
+ () => {
|
|
157
|
+ Statistics.stopLocalStats(track);
|
|
158
|
+ });
|
151
|
159
|
|
152
|
|
- track.addEventListener(
|
153
|
|
- JitsiTrackEvents.LOCAL_TRACK_STOPPED,
|
154
|
|
- () => {
|
155
|
|
- Statistics.stopLocalStats(track);
|
156
|
|
- });
|
|
160
|
+ const stream = track.getOriginalStream();
|
|
161
|
+ const localStats = new LocalStats(stream, Statistics.audioLevelsInterval,
|
|
162
|
+ callback);
|
157
|
163
|
|
158
|
|
- const stream = track.getOriginalStream();
|
159
|
|
- const localStats = new LocalStats(stream, Statistics.audioLevelsInterval,
|
160
|
|
- callback);
|
|
164
|
+ this.localStats.push(localStats);
|
|
165
|
+ localStats.start();
|
|
166
|
+ }
|
161
|
167
|
|
162
|
|
- this.localStats.push(localStats);
|
163
|
|
- localStats.start();
|
164
|
|
-};
|
|
168
|
+ /**
|
|
169
|
+ * Stops collecting local statistics for a track.
|
|
170
|
+ * @static
|
|
171
|
+ * @param {JitsiTrack} track - The track to stop collecting statistics for
|
|
172
|
+ */
|
|
173
|
+ static stopLocalStats(track) {
|
|
174
|
+ if (!Statistics.audioLevelsEnabled) {
|
|
175
|
+ return;
|
|
176
|
+ }
|
165
|
177
|
|
166
|
|
-Statistics.prototype.addAudioLevelListener = function(listener) {
|
167
|
|
- if (!Statistics.audioLevelsEnabled) {
|
168
|
|
- return;
|
169
|
|
- }
|
170
|
|
- this.eventEmitter.on(StatisticsEvents.AUDIO_LEVEL, listener);
|
171
|
|
-};
|
|
178
|
+ const stream = track.getOriginalStream();
|
172
|
179
|
|
173
|
|
-Statistics.prototype.removeAudioLevelListener = function(listener) {
|
174
|
|
- if (!Statistics.audioLevelsEnabled) {
|
175
|
|
- return;
|
|
180
|
+ for (let i = 0; i < Statistics.localStats.length; i++) {
|
|
181
|
+ if (Statistics.localStats[i].stream === stream) {
|
|
182
|
+ const localStats = Statistics.localStats.splice(i, 1);
|
|
183
|
+
|
|
184
|
+ localStats[0].stop();
|
|
185
|
+ break;
|
|
186
|
+ }
|
|
187
|
+ }
|
176
|
188
|
}
|
177
|
|
- this.eventEmitter.removeListener(StatisticsEvents.AUDIO_LEVEL, listener);
|
178
|
|
-};
|
179
|
189
|
|
180
|
|
-Statistics.prototype.addBeforeDisposedListener = function(listener) {
|
181
|
|
- this.eventEmitter.on(StatisticsEvents.BEFORE_DISPOSED, listener);
|
182
|
|
-};
|
|
190
|
+ /**
|
|
191
|
+ * Sends event to analytics and logs a message to the logger/console.
|
|
192
|
+ * @static
|
|
193
|
+ * @param {string | Object} event - The event name, or an object which represents the entire event
|
|
194
|
+ * @param {Object} properties - Properties to attach to the event
|
|
195
|
+ */
|
|
196
|
+ static sendAnalyticsAndLog(event, properties = {}) {
|
|
197
|
+ if (!event) {
|
|
198
|
+ logger.warn('No event or event name given.');
|
183
|
199
|
|
184
|
|
-Statistics.prototype.removeBeforeDisposedListener = function(listener) {
|
185
|
|
- this.eventEmitter.removeListener(
|
186
|
|
- StatisticsEvents.BEFORE_DISPOSED, listener);
|
187
|
|
-};
|
|
200
|
+ return;
|
|
201
|
+ }
|
188
|
202
|
|
189
|
|
-Statistics.prototype.addConnectionStatsListener = function(listener) {
|
190
|
|
- this.eventEmitter.on(StatisticsEvents.CONNECTION_STATS, listener);
|
191
|
|
-};
|
|
203
|
+ let eventToLog;
|
192
|
204
|
|
193
|
|
-Statistics.prototype.removeConnectionStatsListener = function(listener) {
|
194
|
|
- this.eventEmitter.removeListener(
|
195
|
|
- StatisticsEvents.CONNECTION_STATS,
|
196
|
|
- listener);
|
197
|
|
-};
|
|
205
|
+ // Also support an API with a single object as an event.
|
|
206
|
+ if (typeof event === 'object') {
|
|
207
|
+ eventToLog = event;
|
|
208
|
+ } else {
|
|
209
|
+ eventToLog = {
|
|
210
|
+ name: event,
|
|
211
|
+ properties
|
|
212
|
+ };
|
|
213
|
+ }
|
198
|
214
|
|
199
|
|
-Statistics.prototype.addEncodeTimeStatsListener = function(listener) {
|
200
|
|
- this.eventEmitter.on(StatisticsEvents.ENCODE_TIME_STATS, listener);
|
201
|
|
-};
|
|
215
|
+ logger.debug(JSON.stringify(eventToLog));
|
202
|
216
|
|
203
|
|
-Statistics.prototype.removeEncodeTimeStatsListener = function(listener) {
|
204
|
|
- this.eventEmitter.removeListener(StatisticsEvents.ENCODE_TIME_STATS, listener);
|
205
|
|
-};
|
|
217
|
+ // We do this last, because it may modify the object which is passed.
|
|
218
|
+ this.analytics.sendEvent(event, properties);
|
|
219
|
+ }
|
206
|
220
|
|
207
|
|
-Statistics.prototype.addByteSentStatsListener = function(listener) {
|
208
|
|
- this.eventEmitter.on(StatisticsEvents.BYTE_SENT_STATS, listener);
|
209
|
|
-};
|
|
221
|
+ /**
|
|
222
|
+ * Sends event to analytics.
|
|
223
|
+ * @static
|
|
224
|
+ * @param {string | Object} eventName - The event name, or an object which represents the entire event
|
|
225
|
+ * @param {Object} properties - Properties to attach to the event
|
|
226
|
+ */
|
|
227
|
+ static sendAnalytics(eventName, properties = {}) {
|
|
228
|
+ this.analytics.sendEvent(eventName, properties);
|
|
229
|
+ }
|
210
|
230
|
|
211
|
|
-Statistics.prototype.removeByteSentStatsListener = function(listener) {
|
212
|
|
- this.eventEmitter.removeListener(StatisticsEvents.BYTE_SENT_STATS,
|
213
|
|
- listener);
|
214
|
|
-};
|
|
231
|
+ /**
|
|
232
|
+ * The options to configure Statistics.
|
|
233
|
+ * @typedef {Object} StatisticsOptions
|
|
234
|
+ * @property {string} userName - The user name to use
|
|
235
|
+ * @property {string} roomName - The room name we are currently in.
|
|
236
|
+ *
|
|
237
|
+ * @param {JitsiConference} conference - The conference instance from which the statistics were initialized.
|
|
238
|
+ * @param {StatisticsOptions} options - The options to use creating the
|
|
239
|
+ * Statistics.
|
|
240
|
+ */
|
|
241
|
+ constructor(conference, options) {
|
|
242
|
+ /**
|
|
243
|
+ * {@link RTPStats} mapped by {@link TraceablePeerConnection.id} which
|
|
244
|
+ * collect RTP statistics for each peerconnection.
|
|
245
|
+ * @type {Map<string, RTPStats}
|
|
246
|
+ */
|
|
247
|
+ this.rtpStatsMap = new Map();
|
|
248
|
+ this.eventEmitter = new EventEmitter();
|
|
249
|
+ this.conference = conference;
|
|
250
|
+ this.xmpp = conference?.xmpp;
|
|
251
|
+ this.options = options || {};
|
215
|
252
|
|
216
|
|
-/**
|
217
|
|
- * Add a listener that would be notified on a LONG_TASKS_STATS event.
|
218
|
|
- *
|
219
|
|
- * @param {Function} listener a function that would be called when notified.
|
220
|
|
- * @returns {void}
|
221
|
|
- */
|
222
|
|
-Statistics.prototype.addLongTasksStatsListener = function(listener) {
|
223
|
|
- this.eventEmitter.on(StatisticsEvents.LONG_TASKS_STATS, listener);
|
224
|
|
-};
|
|
253
|
+ Statistics.instances.add(this);
|
225
|
254
|
|
226
|
|
-/**
|
227
|
|
- * Obtains the current value of the LongTasks event statistics.
|
228
|
|
- *
|
229
|
|
- * @returns {Object|null} stats object if the observer has been
|
230
|
|
- * created, null otherwise.
|
231
|
|
- */
|
232
|
|
-Statistics.prototype.getLongTasksStats = function() {
|
233
|
|
- return this.performanceObserverStats
|
234
|
|
- ? this.performanceObserverStats.getLongTasksStats()
|
235
|
|
- : null;
|
236
|
|
-};
|
|
255
|
+ RTCStats.attachToConference(this.conference);
|
237
|
256
|
|
238
|
|
-/**
|
239
|
|
- * Removes the given listener for the LONG_TASKS_STATS event.
|
240
|
|
- *
|
241
|
|
- * @param {Function} listener the listener we want to remove.
|
242
|
|
- * @returns {void}
|
243
|
|
- */
|
244
|
|
-Statistics.prototype.removeLongTasksStatsListener = function(listener) {
|
245
|
|
- this.eventEmitter.removeListener(StatisticsEvents.LONG_TASKS_STATS, listener);
|
246
|
|
-};
|
247
|
|
-
|
248
|
|
-/**
|
249
|
|
- * Updates the list of speakers for which the audio levels are to be calculated. This is needed for the jvb pc only.
|
250
|
|
- *
|
251
|
|
- * @param {Array<string>} speakerList The list of remote endpoint ids.
|
252
|
|
- * @returns {void}
|
253
|
|
- */
|
254
|
|
-Statistics.prototype.setSpeakerList = function(speakerList) {
|
255
|
|
- for (const rtpStats of Array.from(this.rtpStatsMap.values())) {
|
256
|
|
- if (!rtpStats.peerconnection.isP2P) {
|
257
|
|
- rtpStats.setSpeakerList(speakerList);
|
|
257
|
+ // WatchRTC is not required to work for react native
|
|
258
|
+ if (!browser.isReactNative()) {
|
|
259
|
+ WatchRTC.start(this.options.roomName, this.options.userName);
|
258
|
260
|
}
|
259
|
261
|
}
|
260
|
|
-};
|
261
|
262
|
|
262
|
|
-Statistics.prototype.dispose = function() {
|
263
|
|
- try {
|
264
|
|
- this.eventEmitter.emit(StatisticsEvents.BEFORE_DISPOSED);
|
|
263
|
+ /**
|
|
264
|
+ * Starts collecting RTP stats for given peerconnection.
|
|
265
|
+ * @param {TraceablePeerConnection} peerconnection
|
|
266
|
+ */
|
|
267
|
+ startRemoteStats(peerconnection) {
|
|
268
|
+ this.stopRemoteStats(peerconnection);
|
|
269
|
+
|
|
270
|
+ try {
|
|
271
|
+ const rtpStats
|
|
272
|
+ = new RTPStats(
|
|
273
|
+ peerconnection,
|
|
274
|
+ Statistics.audioLevelsInterval,
|
|
275
|
+ Statistics.pcStatsInterval,
|
|
276
|
+ this.eventEmitter);
|
|
277
|
+
|
|
278
|
+ rtpStats.start(Statistics.audioLevelsEnabled);
|
|
279
|
+ this.rtpStatsMap.set(peerconnection.id, rtpStats);
|
|
280
|
+ } catch (e) {
|
|
281
|
+ logger.error(`Failed to start collecting remote statistics: ${e}`);
|
|
282
|
+ }
|
|
283
|
+ }
|
265
|
284
|
|
266
|
|
- for (const tpcId of this.rtpStatsMap.keys()) {
|
267
|
|
- this._stopRemoteStats(tpcId);
|
|
285
|
+ /**
|
|
286
|
+ * Adds a listener for audio level events.
|
|
287
|
+ * @param {Function} listener - The listener to add
|
|
288
|
+ */
|
|
289
|
+ addAudioLevelListener(listener) {
|
|
290
|
+ if (!Statistics.audioLevelsEnabled) {
|
|
291
|
+ return;
|
268
|
292
|
}
|
269
|
|
- if (this.eventEmitter) {
|
270
|
|
- this.eventEmitter.removeAllListeners();
|
|
293
|
+ this.eventEmitter.on(StatisticsEvents.AUDIO_LEVEL, listener);
|
|
294
|
+ }
|
|
295
|
+
|
|
296
|
+ /**
|
|
297
|
+ * Removes an audio level listener.
|
|
298
|
+ * @param {Function} listener - The listener to remove
|
|
299
|
+ */
|
|
300
|
+ removeAudioLevelListener(listener) {
|
|
301
|
+ if (!Statistics.audioLevelsEnabled) {
|
|
302
|
+ return;
|
271
|
303
|
}
|
272
|
|
- } finally {
|
273
|
|
- Statistics.instances.delete(this);
|
|
304
|
+ this.eventEmitter.removeListener(StatisticsEvents.AUDIO_LEVEL, listener);
|
274
|
305
|
}
|
275
|
|
-};
|
276
|
306
|
|
277
|
|
-Statistics.stopLocalStats = function(track) {
|
278
|
|
- if (!Statistics.audioLevelsEnabled) {
|
279
|
|
- return;
|
|
307
|
+ /**
|
|
308
|
+ * Adds a listener for before disposed events.
|
|
309
|
+ * @param {Function} listener - The listener to add
|
|
310
|
+ */
|
|
311
|
+ addBeforeDisposedListener(listener) {
|
|
312
|
+ this.eventEmitter.on(StatisticsEvents.BEFORE_DISPOSED, listener);
|
280
|
313
|
}
|
281
|
314
|
|
282
|
|
- const stream = track.getOriginalStream();
|
|
315
|
+ /**
|
|
316
|
+ * Removes a before disposed listener.
|
|
317
|
+ * @param {Function} listener - The listener to remove
|
|
318
|
+ */
|
|
319
|
+ removeBeforeDisposedListener(listener) {
|
|
320
|
+ this.eventEmitter.removeListener(
|
|
321
|
+ StatisticsEvents.BEFORE_DISPOSED, listener);
|
|
322
|
+ }
|
283
|
323
|
|
284
|
|
- for (let i = 0; i < Statistics.localStats.length; i++) {
|
285
|
|
- if (Statistics.localStats[i].stream === stream) {
|
286
|
|
- const localStats = Statistics.localStats.splice(i, 1);
|
|
324
|
+ /**
|
|
325
|
+ * Adds a listener for connection stats events.
|
|
326
|
+ * @param {Function} listener - The listener to add
|
|
327
|
+ */
|
|
328
|
+ addConnectionStatsListener(listener) {
|
|
329
|
+ this.eventEmitter.on(StatisticsEvents.CONNECTION_STATS, listener);
|
|
330
|
+ }
|
287
|
331
|
|
288
|
|
- localStats[0].stop();
|
289
|
|
- break;
|
290
|
|
- }
|
|
332
|
+ /**
|
|
333
|
+ * Removes a connection stats listener.
|
|
334
|
+ * @param {Function} listener - The listener to remove
|
|
335
|
+ */
|
|
336
|
+ removeConnectionStatsListener(listener) {
|
|
337
|
+ this.eventEmitter.removeListener(
|
|
338
|
+ StatisticsEvents.CONNECTION_STATS,
|
|
339
|
+ listener);
|
291
|
340
|
}
|
292
|
|
-};
|
293
|
341
|
|
294
|
|
-/**
|
295
|
|
- * Stops remote RTP stats for given peerconnection ID.
|
296
|
|
- * @param {string} tpcId {@link TraceablePeerConnection.id}
|
297
|
|
- * @private
|
298
|
|
- */
|
299
|
|
-Statistics.prototype._stopRemoteStats = function(tpcId) {
|
300
|
|
- const rtpStats = this.rtpStatsMap.get(tpcId);
|
|
342
|
+ /**
|
|
343
|
+ * Adds a listener for encode time stats events.
|
|
344
|
+ * @param {Function} listener - The listener to add
|
|
345
|
+ */
|
|
346
|
+ addEncodeTimeStatsListener(listener) {
|
|
347
|
+ this.eventEmitter.on(StatisticsEvents.ENCODE_TIME_STATS, listener);
|
|
348
|
+ }
|
301
|
349
|
|
302
|
|
- if (rtpStats) {
|
303
|
|
- rtpStats.stop();
|
304
|
|
- this.rtpStatsMap.delete(tpcId);
|
|
350
|
+ /**
|
|
351
|
+ * Removes an encode time stats listener.
|
|
352
|
+ * @param {Function} listener - The listener to remove
|
|
353
|
+ */
|
|
354
|
+ removeEncodeTimeStatsListener(listener) {
|
|
355
|
+ this.eventEmitter.removeListener(StatisticsEvents.ENCODE_TIME_STATS, listener);
|
305
|
356
|
}
|
306
|
|
-};
|
307
|
357
|
|
308
|
|
-/**
|
309
|
|
- * Stops collecting RTP stats for given peerconnection
|
310
|
|
- * @param {TraceablePeerConnection} tpc
|
311
|
|
- */
|
312
|
|
-Statistics.prototype.stopRemoteStats = function(tpc) {
|
313
|
|
- this._stopRemoteStats(tpc.id);
|
314
|
|
-};
|
|
358
|
+ /**
|
|
359
|
+ * Adds a listener for byte sent stats events.
|
|
360
|
+ * @param {Function} listener - The listener to add
|
|
361
|
+ */
|
|
362
|
+ addByteSentStatsListener(listener) {
|
|
363
|
+ this.eventEmitter.on(StatisticsEvents.BYTE_SENT_STATS, listener);
|
|
364
|
+ }
|
315
|
365
|
|
316
|
|
-/**
|
317
|
|
- * Sends the given feedback
|
318
|
|
- *
|
319
|
|
- * @param overall an integer between 1 and 5 indicating the user's rating.
|
320
|
|
- * @param comment the comment from the user.
|
321
|
|
- * @returns {Promise} Resolves immediately.
|
322
|
|
- */
|
323
|
|
-Statistics.prototype.sendFeedback = function(overall, comment) {
|
324
|
|
- // Statistics.analytics.sendEvent is currently fire and forget, without
|
325
|
|
- // confirmation of successful send.
|
326
|
|
- Statistics.analytics.sendEvent(
|
327
|
|
- FEEDBACK,
|
328
|
|
- {
|
329
|
|
- rating: overall,
|
330
|
|
- comment
|
331
|
|
- });
|
|
366
|
+ /**
|
|
367
|
+ * Removes a byte sent stats listener.
|
|
368
|
+ * @param {Function} listener - The listener to remove
|
|
369
|
+ */
|
|
370
|
+ removeByteSentStatsListener(listener) {
|
|
371
|
+ this.eventEmitter.removeListener(StatisticsEvents.BYTE_SENT_STATS,
|
|
372
|
+ listener);
|
|
373
|
+ }
|
332
|
374
|
|
333
|
|
- return Promise.resolve();
|
334
|
|
-};
|
|
375
|
+ /**
|
|
376
|
+ * Add a listener that would be notified on a LONG_TASKS_STATS event.
|
|
377
|
+ *
|
|
378
|
+ * @param {Function} listener a function that would be called when notified.
|
|
379
|
+ * @returns {void}
|
|
380
|
+ */
|
|
381
|
+ addLongTasksStatsListener(listener) {
|
|
382
|
+ this.eventEmitter.on(StatisticsEvents.LONG_TASKS_STATS, listener);
|
|
383
|
+ }
|
335
|
384
|
|
336
|
|
-Statistics.LOCAL_JID = require('../../service/statistics/constants').LOCAL_JID;
|
|
385
|
+ /**
|
|
386
|
+ * Removes the given listener for the LONG_TASKS_STATS event.
|
|
387
|
+ *
|
|
388
|
+ * @param {Function} listener the listener we want to remove.
|
|
389
|
+ * @returns {void}
|
|
390
|
+ */
|
|
391
|
+ removeLongTasksStatsListener(listener) {
|
|
392
|
+ this.eventEmitter.removeListener(StatisticsEvents.LONG_TASKS_STATS, listener);
|
|
393
|
+ }
|
337
|
394
|
|
338
|
|
-/**
|
339
|
|
- * Sends event to analytics and logs a message to the logger/console.
|
340
|
|
- *
|
341
|
|
- * @param {string | Object} event the event name, or an object which
|
342
|
|
- * represents the entire event.
|
343
|
|
- * @param {Object} properties properties to attach to the event (if an event
|
344
|
|
- * name as opposed to an event object is provided).
|
345
|
|
- */
|
346
|
|
-Statistics.sendAnalyticsAndLog = function(event, properties = {}) {
|
347
|
|
- if (!event) {
|
348
|
|
- logger.warn('No event or event name given.');
|
|
395
|
+ /**
|
|
396
|
+ * Updates the list of speakers for which the audio levels are to be calculated. This is needed for the jvb pc only.
|
|
397
|
+ *
|
|
398
|
+ * @param {Array<string>} speakerList The list of remote endpoint ids.
|
|
399
|
+ * @returns {void}
|
|
400
|
+ */
|
|
401
|
+ setSpeakerList(speakerList) {
|
|
402
|
+ for (const rtpStats of Array.from(this.rtpStatsMap.values())) {
|
|
403
|
+ if (!rtpStats.peerconnection.isP2P) {
|
|
404
|
+ rtpStats.setSpeakerList(speakerList);
|
|
405
|
+ }
|
|
406
|
+ }
|
|
407
|
+ }
|
349
|
408
|
|
350
|
|
- return;
|
|
409
|
+ /**
|
|
410
|
+ * Disposes of this instance, stopping any ongoing stats collection.
|
|
411
|
+ */
|
|
412
|
+ dispose() {
|
|
413
|
+ try {
|
|
414
|
+ this.eventEmitter.emit(StatisticsEvents.BEFORE_DISPOSED);
|
|
415
|
+
|
|
416
|
+ for (const tpcId of this.rtpStatsMap.keys()) {
|
|
417
|
+ this._stopRemoteStats(tpcId);
|
|
418
|
+ }
|
|
419
|
+ if (this.eventEmitter) {
|
|
420
|
+ this.eventEmitter.removeAllListeners();
|
|
421
|
+ }
|
|
422
|
+ } finally {
|
|
423
|
+ Statistics.instances.delete(this);
|
|
424
|
+ }
|
351
|
425
|
}
|
352
|
426
|
|
353
|
|
- let eventToLog;
|
|
427
|
+ /**
|
|
428
|
+ * Stops remote RTP stats for given peerconnection ID.
|
|
429
|
+ * @param {string} tpcId {@link TraceablePeerConnection.id}
|
|
430
|
+ * @private
|
|
431
|
+ */
|
|
432
|
+ _stopRemoteStats(tpcId) {
|
|
433
|
+ const rtpStats = this.rtpStatsMap.get(tpcId);
|
354
|
434
|
|
355
|
|
- // Also support an API with a single object as an event.
|
356
|
|
- if (typeof event === 'object') {
|
357
|
|
- eventToLog = event;
|
358
|
|
- } else {
|
359
|
|
- eventToLog = {
|
360
|
|
- name: event,
|
361
|
|
- properties
|
362
|
|
- };
|
|
435
|
+ if (rtpStats) {
|
|
436
|
+ rtpStats.stop();
|
|
437
|
+ this.rtpStatsMap.delete(tpcId);
|
|
438
|
+ }
|
363
|
439
|
}
|
364
|
440
|
|
365
|
|
- logger.debug(JSON.stringify(eventToLog));
|
|
441
|
+ /**
|
|
442
|
+ * Stops collecting RTP stats for given peerconnection
|
|
443
|
+ * @param {TraceablePeerConnection} tpc
|
|
444
|
+ */
|
|
445
|
+ stopRemoteStats(tpc) {
|
|
446
|
+ this._stopRemoteStats(tpc.id);
|
|
447
|
+ }
|
366
|
448
|
|
367
|
|
- // We do this last, because it may modify the object which is passed.
|
368
|
|
- this.analytics.sendEvent(event, properties);
|
369
|
|
-};
|
|
449
|
+ /**
|
|
450
|
+ * Sends the given feedback
|
|
451
|
+ *
|
|
452
|
+ * @param {number} overall an integer between 1 and 5 indicating the user's rating.
|
|
453
|
+ * @param {string} comment the comment from the user.
|
|
454
|
+ * @returns {Promise} Resolves immediately.
|
|
455
|
+ */
|
|
456
|
+ sendFeedback(overall, comment) {
|
|
457
|
+ // Statistics.analytics.sendEvent is currently fire and forget, without
|
|
458
|
+ // confirmation of successful send.
|
|
459
|
+ Statistics.analytics.sendEvent(
|
|
460
|
+ FEEDBACK,
|
|
461
|
+ {
|
|
462
|
+ rating: overall,
|
|
463
|
+ comment
|
|
464
|
+ });
|
|
465
|
+
|
|
466
|
+ return Promise.resolve();
|
|
467
|
+ }
|
|
468
|
+}
|
370
|
469
|
|
371
|
|
-/**
|
372
|
|
- * Sends event to analytics.
|
373
|
|
- *
|
374
|
|
- * @param {string | Object} eventName the event name, or an object which
|
375
|
|
- * represents the entire event.
|
376
|
|
- * @param {Object} properties properties to attach to the event
|
377
|
|
- */
|
378
|
|
-Statistics.sendAnalytics = function(eventName, properties = {}) {
|
379
|
|
- this.analytics.sendEvent(eventName, properties);
|
380
|
|
-};
|