@webex/plugin-meetings 2.19.1 → 2.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -300
- package/dist/constants.js +3 -206
- package/dist/constants.js.map +1 -1
- package/dist/meeting/index.js +352 -489
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +4 -213
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +0 -28
- package/dist/meetings/index.js.map +1 -1
- package/dist/statsAnalyzer/index.js +145 -86
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/package.json +5 -7
- package/src/constants.ts +1 -214
- package/src/meeting/index.js +110 -208
- package/src/meeting/util.js +4 -252
- package/src/meetings/index.js +0 -22
- package/src/statsAnalyzer/index.js +164 -99
- package/test/integration/spec/journey.js +2 -67
- package/test/unit/spec/meeting/index.js +88 -29
- package/test/unit/spec/meeting/utils.js +0 -2
- package/test/unit/spec/stats-analyzer/index.js +209 -1
- package/dist/analyzer/analyzer.js +0 -113
- package/dist/analyzer/analyzer.js.map +0 -1
- package/dist/analyzer/calculator.js +0 -87
- package/dist/analyzer/calculator.js.map +0 -1
- package/dist/metrics/mqa-processor.js +0 -170
- package/dist/metrics/mqa-processor.js.map +0 -1
- package/dist/stats/data.js +0 -93
- package/dist/stats/data.js.map +0 -1
- package/dist/stats/events.js +0 -222
- package/dist/stats/events.js.map +0 -1
- package/dist/stats/filter.js +0 -84
- package/dist/stats/filter.js.map +0 -1
- package/dist/stats/history.js +0 -147
- package/dist/stats/history.js.map +0 -1
- package/dist/stats/index.js +0 -425
- package/dist/stats/index.js.map +0 -1
- package/dist/stats/metrics.js +0 -112
- package/dist/stats/metrics.js.map +0 -1
- package/dist/stats/stats.js +0 -592
- package/dist/stats/stats.js.map +0 -1
- package/dist/stats/stream.js +0 -156
- package/dist/stats/stream.js.map +0 -1
- package/dist/stats/transformer.js +0 -126
- package/dist/stats/transformer.js.map +0 -1
- package/dist/stats/util.js +0 -64
- package/dist/stats/util.js.map +0 -1
- package/src/analyzer/analyzer.js +0 -78
- package/src/analyzer/calculator.js +0 -77
- package/src/metrics/mqa-processor.js +0 -118
- package/src/stats/data.js +0 -56
- package/src/stats/events.js +0 -185
- package/src/stats/filter.js +0 -40
- package/src/stats/history.js +0 -107
- package/src/stats/index.js +0 -320
- package/src/stats/metrics.js +0 -95
- package/src/stats/stats.js +0 -477
- package/src/stats/stream.js +0 -108
- package/src/stats/transformer.js +0 -109
- package/src/stats/util.js +0 -44
package/src/stats/stats.js
DELETED
|
@@ -1,477 +0,0 @@
|
|
|
1
|
-
import {StatelessWebexPlugin} from '@webex/webex-core';
|
|
2
|
-
import {isFunction} from 'lodash';
|
|
3
|
-
import {uuid} from 'uuid';
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
MEETINGS,
|
|
7
|
-
STATS,
|
|
8
|
-
MQA_STATS
|
|
9
|
-
} from '../constants';
|
|
10
|
-
import StatsHistory from '../stats/history';
|
|
11
|
-
import StatsStream from '../stats/stream';
|
|
12
|
-
import StatsFilter from '../stats/filter';
|
|
13
|
-
import StatsEvents from '../stats/events';
|
|
14
|
-
import StatsError from '../common/errors/stats';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @class MeetingStats
|
|
18
|
-
*/
|
|
19
|
-
export default class MeetingStats extends StatelessWebexPlugin {
|
|
20
|
-
namespace = MEETINGS;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @param {Object} attrs
|
|
24
|
-
* @param {Object} options
|
|
25
|
-
* @param {Object} [optionalCreateOptions]
|
|
26
|
-
* @param {Boolean} optionalCreateOptions.history
|
|
27
|
-
* @param {Boolean} optionalCreateOptions.mqa
|
|
28
|
-
* @param {RTCRtpSender|RTCRtpReceiver} optionalCreateOptions.stream
|
|
29
|
-
* @param {RTCRtpSender|RTCRtpReceiver} optionalCreateOptions.filter
|
|
30
|
-
* @param {RTCPeerConnection} optionalCreateOptions.media
|
|
31
|
-
* @param {String} optionalCreateOptions.id
|
|
32
|
-
* @param {Function} optionalCreateOptions.onClose
|
|
33
|
-
* @param {Function} optionalCreateOptions.onEvent
|
|
34
|
-
* @param {Function} optionalCreateOptions.onData
|
|
35
|
-
* if using filter or stream, media must also exist
|
|
36
|
-
*/
|
|
37
|
-
constructor(attrs, options, optionalCreateOptions) {
|
|
38
|
-
super({}, options);
|
|
39
|
-
this.attrs = attrs;
|
|
40
|
-
this.options = options;
|
|
41
|
-
// what this stats object is configured to work with
|
|
42
|
-
/**
|
|
43
|
-
* @instance
|
|
44
|
-
* @type {RTCPeerConnection}
|
|
45
|
-
* @private
|
|
46
|
-
* @memberof MeetingStats
|
|
47
|
-
*/
|
|
48
|
-
this.peerConnection = null;
|
|
49
|
-
/**
|
|
50
|
-
* @instance
|
|
51
|
-
* @type {RTCRtpSender|RTCRtpReceiver}
|
|
52
|
-
* @private
|
|
53
|
-
* @memberof MeetingStats
|
|
54
|
-
*/
|
|
55
|
-
this.RTCRtpDirection = null;
|
|
56
|
-
// usable values
|
|
57
|
-
/**
|
|
58
|
-
* @instance
|
|
59
|
-
* @type {StatsHistory}
|
|
60
|
-
* @readonly
|
|
61
|
-
* @private
|
|
62
|
-
* @memberof MeetingStats
|
|
63
|
-
*/
|
|
64
|
-
this.history = null;
|
|
65
|
-
/**
|
|
66
|
-
* @instance
|
|
67
|
-
* @type {StatsHistory}
|
|
68
|
-
* @readonly
|
|
69
|
-
* @private
|
|
70
|
-
* @memberof MeetingStats
|
|
71
|
-
*/
|
|
72
|
-
this.mqa = null;
|
|
73
|
-
/**
|
|
74
|
-
* @instance
|
|
75
|
-
* @type {ReadableStream}
|
|
76
|
-
* @readonly
|
|
77
|
-
* @private
|
|
78
|
-
* @memberof MeetingStats
|
|
79
|
-
*/
|
|
80
|
-
this.stream = null;
|
|
81
|
-
/**
|
|
82
|
-
* @instance
|
|
83
|
-
* @type {TransformStream}
|
|
84
|
-
* @readonly
|
|
85
|
-
* @private
|
|
86
|
-
* @memberof MeetingStats
|
|
87
|
-
*/
|
|
88
|
-
this.filter = null;
|
|
89
|
-
/**
|
|
90
|
-
* @instance
|
|
91
|
-
* @type {StatsEvents}
|
|
92
|
-
* @readonly
|
|
93
|
-
* @private
|
|
94
|
-
* @memberof MeetingStats
|
|
95
|
-
*/
|
|
96
|
-
this.events = null;
|
|
97
|
-
/**
|
|
98
|
-
* @instance
|
|
99
|
-
* @type {String}
|
|
100
|
-
* @readonly
|
|
101
|
-
* @private
|
|
102
|
-
* @memberof MeetingStats
|
|
103
|
-
*/
|
|
104
|
-
this.id = null;
|
|
105
|
-
this.populate(optionalCreateOptions);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* @param {Object} [optionalCreateOptions]
|
|
110
|
-
* @returns {undefined}
|
|
111
|
-
* @private
|
|
112
|
-
* @memberof MeetingStats
|
|
113
|
-
*/
|
|
114
|
-
populate(optionalCreateOptions) {
|
|
115
|
-
if (optionalCreateOptions) {
|
|
116
|
-
if (optionalCreateOptions.history) {
|
|
117
|
-
this.withHistory();
|
|
118
|
-
}
|
|
119
|
-
if (optionalCreateOptions.mqa) {
|
|
120
|
-
this.withMQA();
|
|
121
|
-
}
|
|
122
|
-
if (optionalCreateOptions.filter && !optionalCreateOptions.stream && optionalCreateOptions.media) {
|
|
123
|
-
this.withFilter(optionalCreateOptions.filter, optionalCreateOptions.media);
|
|
124
|
-
}
|
|
125
|
-
if (optionalCreateOptions.stream && !optionalCreateOptions.filter && optionalCreateOptions.media) {
|
|
126
|
-
this.withStream(optionalCreateOptions.stream, optionalCreateOptions.media);
|
|
127
|
-
}
|
|
128
|
-
if (optionalCreateOptions.id) {
|
|
129
|
-
this.withId(optionalCreateOptions.id);
|
|
130
|
-
}
|
|
131
|
-
if (optionalCreateOptions.onClose) {
|
|
132
|
-
if (!isFunction(optionalCreateOptions.onClose)) {
|
|
133
|
-
throw new TypeError('stats->populate#onClose must be a callback function for filtered data.');
|
|
134
|
-
}
|
|
135
|
-
this.onClose(optionalCreateOptions.onClose);
|
|
136
|
-
}
|
|
137
|
-
if (optionalCreateOptions.onEvent) {
|
|
138
|
-
if (!isFunction(optionalCreateOptions.onEvent)) {
|
|
139
|
-
throw new TypeError('stats->populate#onEvent must be a callback function for filtered data.');
|
|
140
|
-
}
|
|
141
|
-
if (this.history) {
|
|
142
|
-
this.withEventsHistory(this.history, optionalCreateOptions.onEvent);
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
this.withEvents(optionalCreateOptions.onEvent);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
if (optionalCreateOptions.onData) {
|
|
149
|
-
if (!isFunction(optionalCreateOptions.onData)) {
|
|
150
|
-
throw new TypeError('stats->populate#onData must be a callback function for filtered data.');
|
|
151
|
-
}
|
|
152
|
-
this.onData(optionalCreateOptions.onData);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return this;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* @param {WebRTCData} data
|
|
161
|
-
* @returns {undefined}
|
|
162
|
-
* @public
|
|
163
|
-
* @memberof MeetingStats
|
|
164
|
-
*/
|
|
165
|
-
doHistory(data) {
|
|
166
|
-
if (this.history) {
|
|
167
|
-
this.history.add(data);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* @param {WebRTCData} data
|
|
173
|
-
* @returns {undefined}
|
|
174
|
-
* @public
|
|
175
|
-
* @memberof MeetingStats
|
|
176
|
-
*/
|
|
177
|
-
doMQA(data) {
|
|
178
|
-
if (this.mqa && data.data) {
|
|
179
|
-
if (!data.data.isEmpty()) {
|
|
180
|
-
this.mqa.add(data.data.omit());
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* @param {WebRTCData} data
|
|
187
|
-
* @returns {undefined}
|
|
188
|
-
* @public
|
|
189
|
-
* @memberof MeetingStats
|
|
190
|
-
*/
|
|
191
|
-
doEvents(data) {
|
|
192
|
-
if (this.events) {
|
|
193
|
-
this.events.event(data);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* does all the work for the built properties
|
|
199
|
-
* calls back a function with data from piped stream filter
|
|
200
|
-
* @param {Function} cbFn
|
|
201
|
-
* @returns {undefined}
|
|
202
|
-
* @throws {Error} if the filter stream does not exist
|
|
203
|
-
* @private
|
|
204
|
-
* @memberof MeetingStats
|
|
205
|
-
*/
|
|
206
|
-
onData(cbFn) {
|
|
207
|
-
if (!this.filter) {
|
|
208
|
-
throw new TypeError('The stats sender/receiver filter must be set up before data can be processed.');
|
|
209
|
-
}
|
|
210
|
-
this.filter.on(STATS.DATA, (filtered) => {
|
|
211
|
-
this.doHistory(filtered);
|
|
212
|
-
this.doMQA(filtered);
|
|
213
|
-
this.doEvents(filtered);
|
|
214
|
-
cbFn(filtered);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
return this;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* triggered if the data stream closes
|
|
222
|
-
* calls back a function with error
|
|
223
|
-
* @param {Function} cbFn
|
|
224
|
-
* @returns {undefined}
|
|
225
|
-
* @private
|
|
226
|
-
* @memberof MeetingStats
|
|
227
|
-
*/
|
|
228
|
-
onClose(cbFn) {
|
|
229
|
-
if (!this.filter) {
|
|
230
|
-
throw new TypeError('the stats sender/receiver filter must be set up before data can be closed.');
|
|
231
|
-
}
|
|
232
|
-
this.stream.on(STATS.END, (err) => {
|
|
233
|
-
if (!err) {
|
|
234
|
-
err = new StatsError(`The stats stream for id: ${this.id} ended.`);
|
|
235
|
-
}
|
|
236
|
-
cbFn(err);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
return this;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* constructs an event object on this instance
|
|
244
|
-
* @param {StatsHistory} history
|
|
245
|
-
* @param {Function} cb
|
|
246
|
-
* @returns {MeetingStats}
|
|
247
|
-
* @public
|
|
248
|
-
* @memberof MeetingStats
|
|
249
|
-
*/
|
|
250
|
-
withEventsHistory(history, cb) {
|
|
251
|
-
const events = new StatsEvents(history, cb);
|
|
252
|
-
|
|
253
|
-
this.setEvents(events);
|
|
254
|
-
|
|
255
|
-
return this;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* constructs an event object on this instance
|
|
260
|
-
* @param {Function} cb
|
|
261
|
-
* @returns {MeetingStats}
|
|
262
|
-
* @public
|
|
263
|
-
* @memberof MeetingStats
|
|
264
|
-
*/
|
|
265
|
-
withEvents(cb) {
|
|
266
|
-
const events = new StatsEvents(null, cb);
|
|
267
|
-
|
|
268
|
-
this.setEvents(events);
|
|
269
|
-
|
|
270
|
-
return this;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* constructs a history object on this instance
|
|
275
|
-
* @returns {MeetingStats}
|
|
276
|
-
* @public
|
|
277
|
-
* @memberof MeetingStats
|
|
278
|
-
*/
|
|
279
|
-
withHistory() {
|
|
280
|
-
const history = new StatsHistory(this.config.stats.historyMax);
|
|
281
|
-
|
|
282
|
-
this.setHistory(history);
|
|
283
|
-
|
|
284
|
-
return this;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* constructs a history object on this instance
|
|
289
|
-
* @returns {MeetingStats}
|
|
290
|
-
* @public
|
|
291
|
-
* @memberof MeetingStats
|
|
292
|
-
*/
|
|
293
|
-
withMQA() {
|
|
294
|
-
const mqa = new StatsHistory(MQA_STATS.MQA_SIZE);
|
|
295
|
-
|
|
296
|
-
this.setMQA(mqa);
|
|
297
|
-
|
|
298
|
-
return this;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* constructs a readable stream object on this instance
|
|
303
|
-
* @param {RTCRtpReceiver|RTCRtpSender} transceiverDirection
|
|
304
|
-
* @param {RTCPeerConnection} peerConnection
|
|
305
|
-
* @returns {MeetingStats}
|
|
306
|
-
* @public
|
|
307
|
-
* @memberof MeetingStats
|
|
308
|
-
*/
|
|
309
|
-
withStream(transceiverDirection, peerConnection) {
|
|
310
|
-
const stream = new StatsStream({
|
|
311
|
-
rTCRtpDirection: transceiverDirection,
|
|
312
|
-
peerConnection,
|
|
313
|
-
interval: this.config.stats.interval
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
this.setStream(stream);
|
|
317
|
-
|
|
318
|
-
return this;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* @param {RTCRtpReceiver|RTCRtpSender} transceiverDirection
|
|
323
|
-
* @param {RTCPeerConnection} peerConnection
|
|
324
|
-
* @returns {MeetingStats}
|
|
325
|
-
* @public
|
|
326
|
-
* @memberof MeetingStats
|
|
327
|
-
*/
|
|
328
|
-
withFilter(transceiverDirection, peerConnection) {
|
|
329
|
-
this.withStream(transceiverDirection, peerConnection);
|
|
330
|
-
this.setFilter(new StatsFilter());
|
|
331
|
-
this.getStream().pipe(this.getFilter());
|
|
332
|
-
|
|
333
|
-
return this;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* constructs an id to match this stats object
|
|
338
|
-
* takes params as precedence
|
|
339
|
-
* @param {String} id
|
|
340
|
-
* @returns {MeetingStats}
|
|
341
|
-
* @public
|
|
342
|
-
* @memberof MeetingStats
|
|
343
|
-
*/
|
|
344
|
-
withId(id) {
|
|
345
|
-
if (id) {
|
|
346
|
-
this.setId(id);
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
this.setId(uuid.v4());
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return this;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* @returns {MeetingStats}
|
|
357
|
-
* @public
|
|
358
|
-
* @memberof MeetingStats
|
|
359
|
-
*/
|
|
360
|
-
build() {
|
|
361
|
-
return this;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* @param {String} id
|
|
366
|
-
* @returns {undefined}
|
|
367
|
-
* @public
|
|
368
|
-
* @memberof MeetingStats
|
|
369
|
-
*/
|
|
370
|
-
setId(id) {
|
|
371
|
-
this.id = id;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* @param {StatsHistory} history
|
|
376
|
-
* @returns {undefined}
|
|
377
|
-
* @public
|
|
378
|
-
* @memberof MeetingStats
|
|
379
|
-
*/
|
|
380
|
-
setHistory(history) {
|
|
381
|
-
this.history = history;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* @param {StatsHistory} mqa
|
|
386
|
-
* @returns {undefined}
|
|
387
|
-
* @public
|
|
388
|
-
* @memberof MeetingStats
|
|
389
|
-
*/
|
|
390
|
-
setMQA(mqa) {
|
|
391
|
-
this.mqa = mqa;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* @param {StatsEvent} events
|
|
396
|
-
* @returns {undefined}
|
|
397
|
-
* @public
|
|
398
|
-
* @memberof MeetingStats
|
|
399
|
-
*/
|
|
400
|
-
setEvents(events) {
|
|
401
|
-
this.events = events;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* @param {Readable} stream
|
|
406
|
-
* @returns {undefined}
|
|
407
|
-
* @public
|
|
408
|
-
* @memberof MeetingStats
|
|
409
|
-
*/
|
|
410
|
-
setStream(stream) {
|
|
411
|
-
this.stream = stream;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* @param {Transform} filter
|
|
416
|
-
* @returns {undefined}
|
|
417
|
-
* @public
|
|
418
|
-
* @memberof MeetingStats
|
|
419
|
-
*/
|
|
420
|
-
setFilter(filter) {
|
|
421
|
-
this.filter = filter;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* @returns {String}
|
|
426
|
-
* @public
|
|
427
|
-
* @memberof MeetingStats
|
|
428
|
-
*/
|
|
429
|
-
getId() {
|
|
430
|
-
return this.id;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* @returns {StatsHistory}
|
|
435
|
-
* @public
|
|
436
|
-
* @memberof MeetingStats
|
|
437
|
-
*/
|
|
438
|
-
getHistory() {
|
|
439
|
-
return this.history;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* @returns {StatsHistory}
|
|
444
|
-
* @public
|
|
445
|
-
* @memberof MeetingStats
|
|
446
|
-
*/
|
|
447
|
-
getMQA() {
|
|
448
|
-
return this.mqa;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* @returns {StatsEvents}
|
|
453
|
-
* @public
|
|
454
|
-
* @memberof MeetingStats
|
|
455
|
-
*/
|
|
456
|
-
getEvents() {
|
|
457
|
-
return this.events;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* @returns {Readable}
|
|
462
|
-
* @public
|
|
463
|
-
* @memberof MeetingStats
|
|
464
|
-
*/
|
|
465
|
-
getStream() {
|
|
466
|
-
return this.stream;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* @returns {Transform}
|
|
471
|
-
* @public
|
|
472
|
-
* @memberof MeetingStats
|
|
473
|
-
*/
|
|
474
|
-
getFilter() {
|
|
475
|
-
return this.filter;
|
|
476
|
-
}
|
|
477
|
-
}
|
package/src/stats/stream.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import {EventEmitter} from 'events';
|
|
2
|
-
|
|
3
|
-
import {Readable} from 'readable-stream';
|
|
4
|
-
import {safeSetTimeout} from '@webex/common-timers';
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
ERROR,
|
|
8
|
-
STATS
|
|
9
|
-
} from '../constants';
|
|
10
|
-
|
|
11
|
-
const pcsByRTCRtpDirection = new WeakMap();
|
|
12
|
-
const emittersByRTCRtpDirection = new WeakMap();
|
|
13
|
-
const RTCRtpDirectionByEmitter = new WeakMap();
|
|
14
|
-
const emittersByStream = new WeakMap();
|
|
15
|
-
const timersByEmitter = new WeakMap();
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Helper function that ensures no matter how many stats streams we create, we
|
|
19
|
-
* don't poll the PeerConnection more than once per interval.
|
|
20
|
-
* @param {EventEmitter} emitter
|
|
21
|
-
* @param {Number} interval
|
|
22
|
-
* @private
|
|
23
|
-
* @returns {undefined}
|
|
24
|
-
*/
|
|
25
|
-
const schedule = (emitter, interval) => {
|
|
26
|
-
const timer = safeSetTimeout(() => {
|
|
27
|
-
const direction = RTCRtpDirectionByEmitter.get(emitter);
|
|
28
|
-
const pc = pcsByRTCRtpDirection.get(direction);
|
|
29
|
-
|
|
30
|
-
if (direction) {
|
|
31
|
-
direction.getStats()
|
|
32
|
-
.then((stats) => {
|
|
33
|
-
emitter.emit(STATS.DATA, stats);
|
|
34
|
-
// TODO: Remove on 1.0 spec adoption
|
|
35
|
-
// "closed" is supposed to be part of the {@link RTCPeerConnectionState}
|
|
36
|
-
// enum according to spec, but at time of writing, was still implemented
|
|
37
|
-
// in the {@link RTCSignalingState} enum.
|
|
38
|
-
if (!(pc.signalingState === STATS.CLOSED || pc.connectionState === STATS.CLOSED)) {
|
|
39
|
-
schedule(emitter, interval);
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
.catch((err) => {
|
|
43
|
-
emitter.emit(ERROR, err);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}, interval);
|
|
47
|
-
|
|
48
|
-
timersByEmitter.set(emitter, timer);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Polls an {@link RTCPeerConnection} once per second and emits its {@link RTCStatsReport}
|
|
53
|
-
* {@link RTCStatsReport}
|
|
54
|
-
*/
|
|
55
|
-
export default class StatsStream extends Readable {
|
|
56
|
-
/**
|
|
57
|
-
* @private
|
|
58
|
-
* @param {Object} config
|
|
59
|
-
* @param {RTCRtpSender|RTCRtpReceiver} config.rTCRtpDirection
|
|
60
|
-
* @param {RTCPeerConnection} config.peerConnection
|
|
61
|
-
* @param {Number} config.interval
|
|
62
|
-
*/
|
|
63
|
-
constructor(config = {}) {
|
|
64
|
-
super({objectMode: true});
|
|
65
|
-
|
|
66
|
-
this.interval = config.interval;
|
|
67
|
-
|
|
68
|
-
if (!emittersByRTCRtpDirection.has(config.rTCRtpDirection)) {
|
|
69
|
-
emittersByRTCRtpDirection.set(config.rTCRtpDirection, new EventEmitter());
|
|
70
|
-
}
|
|
71
|
-
const emitter = emittersByRTCRtpDirection.get(config.rTCRtpDirection);
|
|
72
|
-
|
|
73
|
-
if (!emittersByStream.has(this)) {
|
|
74
|
-
emittersByStream.set(this, emitter);
|
|
75
|
-
}
|
|
76
|
-
if (!RTCRtpDirectionByEmitter.has(emitter)) {
|
|
77
|
-
RTCRtpDirectionByEmitter.set(emitter, config.rTCRtpDirection);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (!pcsByRTCRtpDirection.has(config.rTCRtpDirection)) {
|
|
81
|
-
pcsByRTCRtpDirection.set(config.rTCRtpDirection, config.peerConnection);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
emitter.once(ERROR, (err) => {
|
|
85
|
-
this.emit(ERROR, err);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* See NodeJS Docs
|
|
91
|
-
* @private
|
|
92
|
-
* @returns {undefined}
|
|
93
|
-
*/
|
|
94
|
-
_read() {
|
|
95
|
-
const emitter = emittersByStream.get(this);
|
|
96
|
-
|
|
97
|
-
emitter.once(STATS.DATA, (data) => {
|
|
98
|
-
if (!this.isPaused()) {
|
|
99
|
-
this.push(data);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
if (!timersByEmitter.has(emitter)) {
|
|
104
|
-
schedule(emitter, this.interval);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
package/src/stats/transformer.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import {keys, find, forEach} from 'lodash';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
DEFAULT_TRANSFORM_REGEX, DEFAULT_FF_TRANSFORM_REGEX, DEFAULT_GET_STATS_FILTER
|
|
5
|
-
} from '../constants';
|
|
6
|
-
import BrowserDetection from '../common/browser-detection';
|
|
7
|
-
|
|
8
|
-
const {isBrowser} = BrowserDetection();
|
|
9
|
-
|
|
10
|
-
const StatsTransformer = {
|
|
11
|
-
isFF: isBrowser('firefox')
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
// convert the ids from the parsed stats objects into understandable keys
|
|
15
|
-
StatsTransformer.simplify = (stat) => find(keys(StatsTransformer.isFF ? DEFAULT_FF_TRANSFORM_REGEX : DEFAULT_TRANSFORM_REGEX), (key) => {
|
|
16
|
-
let value;
|
|
17
|
-
|
|
18
|
-
if (StatsTransformer.isFF) {
|
|
19
|
-
// FF stats are formatted poorly so we have to take what we can get
|
|
20
|
-
value = (DEFAULT_FF_TRANSFORM_REGEX[key].regex.test(`${stat.type}${stat.kind ? `_${stat.kind}` : ''}_${stat.id}`) ? key : false);
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
// no other parameters necessary besides id
|
|
24
|
-
value = (DEFAULT_TRANSFORM_REGEX[key].regex.test(stat.id) ? key : false);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (value) {
|
|
28
|
-
// others were included but have a value that exclude them from the list because of duplicates that weren't chosen
|
|
29
|
-
if (DEFAULT_TRANSFORM_REGEX[value].decider) {
|
|
30
|
-
if (stat[DEFAULT_TRANSFORM_REGEX[value].decider] !== DEFAULT_TRANSFORM_REGEX[value].selector) {
|
|
31
|
-
value = false;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
// // some types need to check against a type field as well
|
|
37
|
-
if (DEFAULT_TRANSFORM_REGEX[value].profiler) {
|
|
38
|
-
const mapStatToReadableType = stat[DEFAULT_TRANSFORM_REGEX[value].profiler.type];
|
|
39
|
-
|
|
40
|
-
if (!((mapStatToReadableType && mapStatToReadableType.includes(DEFAULT_TRANSFORM_REGEX[value].profiler.value)) &&
|
|
41
|
-
(value.toLowerCase().includes(DEFAULT_TRANSFORM_REGEX[value].profiler.value)))) {
|
|
42
|
-
value = false;
|
|
43
|
-
|
|
44
|
-
return value;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return value;
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// parse the RTCStatsReport, extracting only the data we care about
|
|
53
|
-
StatsTransformer.parse = (report) => {
|
|
54
|
-
const target = {};
|
|
55
|
-
|
|
56
|
-
// from the stats object
|
|
57
|
-
// get the rtc stats report
|
|
58
|
-
if (report && report.forEach && report.size > 0) {
|
|
59
|
-
report.forEach((stat) => {
|
|
60
|
-
// each report has internal data
|
|
61
|
-
if (stat && stat.type) {
|
|
62
|
-
// pull only certain types
|
|
63
|
-
if (DEFAULT_GET_STATS_FILTER.types.includes(stat.type)) {
|
|
64
|
-
// insert into the target the statistic mapped to it's statical id
|
|
65
|
-
target[stat.id] = stat;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return target;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
StatsTransformer.convert = (parsed, options) => {
|
|
75
|
-
// supply your own simplification function (rather than id as the key)
|
|
76
|
-
if (options.simplifyFn) {
|
|
77
|
-
return options.simplifyFn(parsed);
|
|
78
|
-
}
|
|
79
|
-
// else use ours that stores things like rtcOutAudio, rtpInVideo, etc
|
|
80
|
-
const target = {};
|
|
81
|
-
|
|
82
|
-
forEach(parsed, (stat) => {
|
|
83
|
-
const key = StatsTransformer.simplify(stat);
|
|
84
|
-
|
|
85
|
-
if (key) {
|
|
86
|
-
target[key] = stat;
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
return target;
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
// transform the RTCStatsReport into a much more readable, processable format
|
|
94
|
-
StatsTransformer.transform = (report, options = {simplifyFn: undefined}) => {
|
|
95
|
-
let data;
|
|
96
|
-
// first parse it and cut out not necessary data
|
|
97
|
-
const parsed = StatsTransformer.parse(report);
|
|
98
|
-
|
|
99
|
-
// either convert the data or store each stat with it's id mapped as the key to the stat object itself
|
|
100
|
-
if (DEFAULT_TRANSFORM_REGEX) {
|
|
101
|
-
data = StatsTransformer.convert(parsed, options);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
data = parsed;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return data;
|
|
108
|
-
};
|
|
109
|
-
export default StatsTransformer;
|