@webex/plugin-meetings 3.3.1 → 3.4.0

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.
Files changed (126) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +7 -2
  3. package/dist/breakouts/index.js.map +1 -1
  4. package/dist/constants.js +11 -4
  5. package/dist/constants.js.map +1 -1
  6. package/dist/interpretation/index.js +1 -1
  7. package/dist/interpretation/siLanguage.js +1 -1
  8. package/dist/locus-info/selfUtils.js +0 -5
  9. package/dist/locus-info/selfUtils.js.map +1 -1
  10. package/dist/media/MediaConnectionAwaiter.js +70 -15
  11. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  12. package/dist/media/index.js +12 -0
  13. package/dist/media/index.js.map +1 -1
  14. package/dist/meeting/connectionStateHandler.js +67 -0
  15. package/dist/meeting/connectionStateHandler.js.map +1 -0
  16. package/dist/meeting/index.js +552 -357
  17. package/dist/meeting/index.js.map +1 -1
  18. package/dist/meeting/locusMediaRequest.js +7 -0
  19. package/dist/meeting/locusMediaRequest.js.map +1 -1
  20. package/dist/meeting/muteState.js +6 -1
  21. package/dist/meeting/muteState.js.map +1 -1
  22. package/dist/meeting/util.js +1 -0
  23. package/dist/meeting/util.js.map +1 -1
  24. package/dist/meeting-info/index.js +4 -4
  25. package/dist/meeting-info/index.js.map +1 -1
  26. package/dist/meeting-info/meeting-info-v2.js +2 -2
  27. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  28. package/dist/meeting-info/util.js +17 -17
  29. package/dist/meeting-info/util.js.map +1 -1
  30. package/dist/meeting-info/utilv2.js +16 -16
  31. package/dist/meeting-info/utilv2.js.map +1 -1
  32. package/dist/meetings/collection.js +1 -1
  33. package/dist/meetings/collection.js.map +1 -1
  34. package/dist/meetings/index.js +37 -33
  35. package/dist/meetings/index.js.map +1 -1
  36. package/dist/meetings/meetings.types.js +8 -0
  37. package/dist/meetings/meetings.types.js.map +1 -1
  38. package/dist/meetings/util.js +3 -2
  39. package/dist/meetings/util.js.map +1 -1
  40. package/dist/metrics/constants.js +2 -1
  41. package/dist/metrics/constants.js.map +1 -1
  42. package/dist/metrics/index.js +57 -0
  43. package/dist/metrics/index.js.map +1 -1
  44. package/dist/personal-meeting-room/index.js +1 -1
  45. package/dist/personal-meeting-room/index.js.map +1 -1
  46. package/dist/reachability/clusterReachability.js +108 -53
  47. package/dist/reachability/clusterReachability.js.map +1 -1
  48. package/dist/reachability/index.js +415 -56
  49. package/dist/reachability/index.js.map +1 -1
  50. package/dist/types/constants.d.ts +11 -3
  51. package/dist/types/media/MediaConnectionAwaiter.d.ts +24 -4
  52. package/dist/types/meeting/connectionStateHandler.d.ts +30 -0
  53. package/dist/types/meeting/index.d.ts +27 -7
  54. package/dist/types/meeting/locusMediaRequest.d.ts +2 -0
  55. package/dist/types/meeting-info/index.d.ts +3 -2
  56. package/dist/types/meeting-info/meeting-info-v2.d.ts +3 -2
  57. package/dist/types/meeting-info/util.d.ts +5 -4
  58. package/dist/types/meeting-info/utilv2.d.ts +3 -2
  59. package/dist/types/meetings/collection.d.ts +3 -2
  60. package/dist/types/meetings/index.d.ts +4 -3
  61. package/dist/types/meetings/meetings.types.d.ts +9 -0
  62. package/dist/types/metrics/constants.d.ts +1 -0
  63. package/dist/types/metrics/index.d.ts +15 -0
  64. package/dist/types/reachability/clusterReachability.d.ts +31 -3
  65. package/dist/types/reachability/index.d.ts +93 -2
  66. package/dist/webinar/index.js +1 -1
  67. package/package.json +23 -23
  68. package/src/breakouts/index.ts +7 -1
  69. package/src/constants.ts +13 -17
  70. package/src/locus-info/selfUtils.ts +0 -5
  71. package/src/media/MediaConnectionAwaiter.ts +89 -14
  72. package/src/media/index.ts +13 -0
  73. package/src/meeting/connectionStateHandler.ts +65 -0
  74. package/src/meeting/index.ts +526 -292
  75. package/src/meeting/locusMediaRequest.ts +5 -0
  76. package/src/meeting/muteState.ts +6 -1
  77. package/src/meeting/util.ts +1 -0
  78. package/src/meeting-info/index.ts +9 -6
  79. package/src/meeting-info/meeting-info-v2.ts +4 -4
  80. package/src/meeting-info/util.ts +23 -28
  81. package/src/meeting-info/utilv2.ts +18 -24
  82. package/src/meetings/collection.ts +3 -3
  83. package/src/meetings/index.ts +39 -40
  84. package/src/meetings/meetings.types.ts +11 -0
  85. package/src/meetings/util.ts +5 -4
  86. package/src/metrics/constants.ts +1 -0
  87. package/src/metrics/index.ts +44 -0
  88. package/src/personal-meeting-room/index.ts +2 -2
  89. package/src/reachability/clusterReachability.ts +86 -25
  90. package/src/reachability/index.ts +316 -27
  91. package/test/unit/spec/breakouts/index.ts +51 -32
  92. package/test/unit/spec/locus-info/selfUtils.js +25 -23
  93. package/test/unit/spec/media/MediaConnectionAwaiter.ts +131 -32
  94. package/test/unit/spec/media/index.ts +42 -27
  95. package/test/unit/spec/meeting/connectionStateHandler.ts +102 -0
  96. package/test/unit/spec/meeting/index.js +758 -179
  97. package/test/unit/spec/meeting/locusMediaRequest.ts +7 -0
  98. package/test/unit/spec/meeting/muteState.js +24 -0
  99. package/test/unit/spec/meeting-info/index.js +4 -4
  100. package/test/unit/spec/meeting-info/meetinginfov2.js +24 -28
  101. package/test/unit/spec/meeting-info/request.js +2 -2
  102. package/test/unit/spec/meeting-info/utilv2.js +41 -49
  103. package/test/unit/spec/meetings/index.js +14 -0
  104. package/test/unit/spec/metrics/index.js +126 -0
  105. package/test/unit/spec/multistream/mediaRequestManager.ts +2 -2
  106. package/test/unit/spec/personal-meeting-room/personal-meeting-room.js +2 -2
  107. package/test/unit/spec/reachability/clusterReachability.ts +116 -22
  108. package/test/unit/spec/reachability/index.ts +1153 -84
  109. package/test/unit/spec/rtcMetrics/index.ts +1 -0
  110. package/dist/mediaQualityMetrics/config.js +0 -321
  111. package/dist/mediaQualityMetrics/config.js.map +0 -1
  112. package/dist/statsAnalyzer/global.js +0 -44
  113. package/dist/statsAnalyzer/global.js.map +0 -1
  114. package/dist/statsAnalyzer/index.js +0 -1072
  115. package/dist/statsAnalyzer/index.js.map +0 -1
  116. package/dist/statsAnalyzer/mqaUtil.js +0 -368
  117. package/dist/statsAnalyzer/mqaUtil.js.map +0 -1
  118. package/dist/types/mediaQualityMetrics/config.d.ts +0 -247
  119. package/dist/types/statsAnalyzer/global.d.ts +0 -36
  120. package/dist/types/statsAnalyzer/index.d.ts +0 -217
  121. package/dist/types/statsAnalyzer/mqaUtil.d.ts +0 -48
  122. package/src/mediaQualityMetrics/config.ts +0 -255
  123. package/src/statsAnalyzer/global.ts +0 -37
  124. package/src/statsAnalyzer/index.ts +0 -1318
  125. package/src/statsAnalyzer/mqaUtil.ts +0 -463
  126. package/test/unit/spec/stats-analyzer/index.js +0 -1819
@@ -1,1318 +0,0 @@
1
- /* eslint-disable prefer-destructuring */
2
-
3
- import {cloneDeep, isEmpty} from 'lodash';
4
- import {ConnectionState} from '@webex/internal-media-core';
5
-
6
- import EventsScope from '../common/events/events-scope';
7
- import {
8
- DEFAULT_GET_STATS_FILTER,
9
- STATS,
10
- MQA_INTERVAL,
11
- NETWORK_TYPE,
12
- MEDIA_DEVICES,
13
- _UNKNOWN_,
14
- } from '../constants';
15
- import {
16
- emptyAudioReceive,
17
- emptyAudioTransmit,
18
- emptyMqaInterval,
19
- emptyVideoReceive,
20
- emptyVideoTransmit,
21
- emptyAudioReceiveStream,
22
- emptyAudioTransmitStream,
23
- emptyVideoReceiveStream,
24
- emptyVideoTransmitStream,
25
- } from '../mediaQualityMetrics/config';
26
- import LoggerProxy from '../common/logs/logger-proxy';
27
-
28
- import defaultStats from './global';
29
- import {
30
- getAudioSenderMqa,
31
- getAudioReceiverMqa,
32
- getVideoSenderMqa,
33
- getVideoReceiverMqa,
34
- getAudioSenderStreamMqa,
35
- getAudioReceiverStreamMqa,
36
- getVideoSenderStreamMqa,
37
- getVideoReceiverStreamMqa,
38
- } from './mqaUtil';
39
- import {ReceiveSlot} from '../multistream/receiveSlot';
40
-
41
- export const EVENTS = {
42
- MEDIA_QUALITY: 'MEDIA_QUALITY',
43
- LOCAL_MEDIA_STARTED: 'LOCAL_MEDIA_STARTED',
44
- LOCAL_MEDIA_STOPPED: 'LOCAL_MEDIA_STOPPED',
45
- REMOTE_MEDIA_STARTED: 'REMOTE_MEDIA_STARTED',
46
- REMOTE_MEDIA_STOPPED: 'REMOTE_MEDIA_STOPPED',
47
- };
48
-
49
- const emptySender = {
50
- trackLabel: '',
51
- maxPacketLossRatio: 0,
52
- availableBandwidth: 0,
53
- bytesSent: 0,
54
- meanRemoteJitter: [],
55
- meanRoundTripTime: [],
56
- };
57
-
58
- const emptyReceiver = {
59
- availableBandwidth: 0,
60
- bytesReceived: 0,
61
- meanRtpJitter: [],
62
- meanRoundTripTime: [],
63
- };
64
-
65
- type ReceiveSlotCallback = (csi: number) => ReceiveSlot | undefined;
66
- type MediaStatus = {
67
- actual?: any;
68
- expected?: any;
69
- };
70
- /**
71
- * Stats Analyzer class that will emit events based on detected quality
72
- *
73
- * @export
74
- * @class StatsAnalyzer
75
- * @extends {EventsScope}
76
- */
77
- export class StatsAnalyzer extends EventsScope {
78
- config: any;
79
- correlationId: any;
80
- lastEmittedStartStopEvent: any;
81
- lastMqaDataSent: any;
82
- lastStatsResults: any;
83
- meetingMediaStatus: any;
84
- mqaInterval: NodeJS.Timeout;
85
- mqaSentCount: any;
86
- networkQualityMonitor: any;
87
- mediaConnection: any;
88
- statsInterval: NodeJS.Timeout;
89
- statsResults: any;
90
- statsStarted: any;
91
- successfulCandidatePair: any;
92
- localIpAddress: string; // Returns the local IP address for diagnostics. this is the local IP of the interface used for the current media connection a host can have many local Ip Addresses
93
- receiveSlotCallback: ReceiveSlotCallback;
94
-
95
- /**
96
- * Creates a new instance of StatsAnalyzer
97
- * @constructor
98
- * @public
99
- * @param {Object} config SDK Configuration Object
100
- * @param {Function} receiveSlotCallback Callback used to access receive slots.
101
- * @param {Object} networkQualityMonitor class for assessing network characteristics (jitter, packetLoss, latency)
102
- * @param {Object} statsResults Default properties for stats
103
- */
104
- constructor(
105
- config: any,
106
- receiveSlotCallback: ReceiveSlotCallback = () => undefined,
107
- networkQualityMonitor: object = {},
108
- statsResults: object = defaultStats
109
- ) {
110
- super();
111
- this.statsStarted = false;
112
- this.statsResults = statsResults;
113
- this.lastStatsResults = null;
114
- this.config = config;
115
- this.networkQualityMonitor = networkQualityMonitor;
116
- this.correlationId = config.correlationId;
117
- this.mqaSentCount = -1;
118
- this.lastMqaDataSent = {};
119
- this.lastEmittedStartStopEvent = {};
120
- this.receiveSlotCallback = receiveSlotCallback;
121
- this.successfulCandidatePair = {};
122
- this.localIpAddress = '';
123
- }
124
-
125
- /**
126
- * Resets cumulative stats arrays.
127
- *
128
- * @public
129
- * @memberof StatsAnalyzer
130
- * @returns {void}
131
- */
132
- resetStatsResults() {
133
- Object.keys(this.statsResults).forEach((mediaType) => {
134
- if (mediaType.includes('recv')) {
135
- this.statsResults[mediaType].recv.meanRtpJitter = [];
136
- }
137
-
138
- if (mediaType.includes('send')) {
139
- this.statsResults[mediaType].send.meanRemoteJitter = [];
140
- this.statsResults[mediaType].send.meanRoundTripTime = [];
141
- }
142
- });
143
- }
144
-
145
- /**
146
- * sets mediaStatus status for analyzing metrics
147
- *
148
- * @public
149
- * @param {Object} status for the audio and video
150
- * @memberof StatsAnalyzer
151
- * @returns {void}
152
- */
153
- public updateMediaStatus(status: MediaStatus) {
154
- this.meetingMediaStatus = {
155
- actual: {
156
- ...this.meetingMediaStatus?.actual,
157
- ...status?.actual,
158
- },
159
- expected: {
160
- ...this.meetingMediaStatus?.expected,
161
- ...status?.expected,
162
- },
163
- };
164
- }
165
-
166
- /**
167
- * captures MQA data from media connection
168
- *
169
- * @public
170
- * @memberof StatsAnalyzer
171
- * @returns {void}
172
- */
173
- sendMqaData() {
174
- const newMqa = cloneDeep(emptyMqaInterval);
175
-
176
- // Fill in empty stats items for lastMqaDataSent
177
- Object.keys(this.statsResults).forEach((mediaType) => {
178
- if (!this.lastMqaDataSent[mediaType]) {
179
- this.lastMqaDataSent[mediaType] = {};
180
- }
181
-
182
- if (!this.lastMqaDataSent[mediaType].send && mediaType.includes('-send')) {
183
- this.lastMqaDataSent[mediaType].send = {};
184
- }
185
-
186
- if (!this.lastMqaDataSent[mediaType].recv && mediaType.includes('-recv')) {
187
- this.lastMqaDataSent[mediaType].recv = {};
188
- }
189
- });
190
-
191
- // Create stats the first level, totals for senders and receivers
192
- const audioSender = cloneDeep(emptyAudioTransmit);
193
- const audioShareSender = cloneDeep(emptyAudioTransmit);
194
- const audioReceiver = cloneDeep(emptyAudioReceive);
195
- const audioShareReceiver = cloneDeep(emptyAudioReceive);
196
- const videoSender = cloneDeep(emptyVideoTransmit);
197
- const videoShareSender = cloneDeep(emptyVideoTransmit);
198
- const videoReceiver = cloneDeep(emptyVideoReceive);
199
- const videoShareReceiver = cloneDeep(emptyVideoReceive);
200
-
201
- getAudioSenderMqa({
202
- audioSender,
203
- statsResults: this.statsResults,
204
- lastMqaDataSent: this.lastMqaDataSent,
205
- baseMediaType: 'audio-send',
206
- });
207
- newMqa.audioTransmit.push(audioSender);
208
-
209
- getAudioSenderMqa({
210
- audioSender: audioShareSender,
211
- statsResults: this.statsResults,
212
- lastMqaDataSent: this.lastMqaDataSent,
213
- baseMediaType: 'audio-share-send',
214
- });
215
- newMqa.audioTransmit.push(audioShareSender);
216
-
217
- getAudioReceiverMqa({
218
- audioReceiver,
219
- statsResults: this.statsResults,
220
- lastMqaDataSent: this.lastMqaDataSent,
221
- baseMediaType: 'audio-recv',
222
- });
223
- newMqa.audioReceive.push(audioReceiver);
224
-
225
- getAudioReceiverMqa({
226
- audioReceiver: audioShareReceiver,
227
- statsResults: this.statsResults,
228
- lastMqaDataSent: this.lastMqaDataSent,
229
- baseMediaType: 'audio-share-recv',
230
- });
231
- newMqa.audioReceive.push(audioShareReceiver);
232
-
233
- getVideoSenderMqa({
234
- videoSender,
235
- statsResults: this.statsResults,
236
- lastMqaDataSent: this.lastMqaDataSent,
237
- baseMediaType: 'video-send',
238
- });
239
- newMqa.videoTransmit.push(videoSender);
240
-
241
- getVideoSenderMqa({
242
- videoSender: videoShareSender,
243
- statsResults: this.statsResults,
244
- lastMqaDataSent: this.lastMqaDataSent,
245
- baseMediaType: 'video-share-send',
246
- });
247
- newMqa.videoTransmit.push(videoShareSender);
248
-
249
- getVideoReceiverMqa({
250
- videoReceiver,
251
- statsResults: this.statsResults,
252
- lastMqaDataSent: this.lastMqaDataSent,
253
- baseMediaType: 'video-recv',
254
- });
255
- newMqa.videoReceive.push(videoReceiver);
256
-
257
- getVideoReceiverMqa({
258
- videoReceiver: videoShareReceiver,
259
- statsResults: this.statsResults,
260
- lastMqaDataSent: this.lastMqaDataSent,
261
- baseMediaType: 'video-share-recv',
262
- });
263
- newMqa.videoReceive.push(videoShareReceiver);
264
-
265
- // Add stats for individual streams
266
- Object.keys(this.statsResults).forEach((mediaType) => {
267
- if (mediaType.startsWith('audio-send')) {
268
- const audioSenderStream = cloneDeep(emptyAudioTransmitStream);
269
-
270
- getAudioSenderStreamMqa({
271
- audioSenderStream,
272
- statsResults: this.statsResults,
273
- lastMqaDataSent: this.lastMqaDataSent,
274
- mediaType,
275
- });
276
- newMqa.audioTransmit[0].streams.push(audioSenderStream);
277
-
278
- this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
279
- } else if (mediaType.startsWith('audio-share-send')) {
280
- const audioSenderStream = cloneDeep(emptyAudioTransmitStream);
281
-
282
- getAudioSenderStreamMqa({
283
- audioSenderStream,
284
- statsResults: this.statsResults,
285
- lastMqaDataSent: this.lastMqaDataSent,
286
- mediaType,
287
- });
288
- newMqa.audioTransmit[1].streams.push(audioSenderStream);
289
-
290
- this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
291
- } else if (mediaType.startsWith('audio-recv')) {
292
- const audioReceiverStream = cloneDeep(emptyAudioReceiveStream);
293
-
294
- getAudioReceiverStreamMqa({
295
- audioReceiverStream,
296
- statsResults: this.statsResults,
297
- lastMqaDataSent: this.lastMqaDataSent,
298
- mediaType,
299
- });
300
- newMqa.audioReceive[0].streams.push(audioReceiverStream);
301
-
302
- this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
303
- } else if (mediaType.startsWith('audio-share-recv')) {
304
- const audioReceiverStream = cloneDeep(emptyAudioReceiveStream);
305
-
306
- getAudioReceiverStreamMqa({
307
- audioReceiverStream,
308
- statsResults: this.statsResults,
309
- lastMqaDataSent: this.lastMqaDataSent,
310
- mediaType,
311
- });
312
- newMqa.audioReceive[1].streams.push(audioReceiverStream);
313
-
314
- this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
315
- } else if (mediaType.startsWith('video-send-layer')) {
316
- // We only want the stream-specific stats we get with video-send-layer-0, video-send-layer-1, etc.
317
- const videoSenderStream = cloneDeep(emptyVideoTransmitStream);
318
-
319
- getVideoSenderStreamMqa({
320
- videoSenderStream,
321
- statsResults: this.statsResults,
322
- lastMqaDataSent: this.lastMqaDataSent,
323
- mediaType,
324
- });
325
- newMqa.videoTransmit[0].streams.push(videoSenderStream);
326
-
327
- this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
328
- } else if (mediaType.startsWith('video-share-send')) {
329
- const videoSenderStream = cloneDeep(emptyVideoTransmitStream);
330
-
331
- getVideoSenderStreamMqa({
332
- videoSenderStream,
333
- statsResults: this.statsResults,
334
- lastMqaDataSent: this.lastMqaDataSent,
335
- mediaType,
336
- });
337
- newMqa.videoTransmit[1].streams.push(videoSenderStream);
338
-
339
- this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
340
- } else if (mediaType.startsWith('video-recv')) {
341
- const videoReceiverStream = cloneDeep(emptyVideoReceiveStream);
342
-
343
- getVideoReceiverStreamMqa({
344
- videoReceiverStream,
345
- statsResults: this.statsResults,
346
- lastMqaDataSent: this.lastMqaDataSent,
347
- mediaType,
348
- });
349
- newMqa.videoReceive[0].streams.push(videoReceiverStream);
350
-
351
- this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
352
- } else if (mediaType.startsWith('video-share-recv')) {
353
- const videoReceiverStream = cloneDeep(emptyVideoReceiveStream);
354
-
355
- getVideoReceiverStreamMqa({
356
- videoReceiverStream,
357
- statsResults: this.statsResults,
358
- lastMqaDataSent: this.lastMqaDataSent,
359
- mediaType,
360
- });
361
- newMqa.videoReceive[1].streams.push(videoReceiverStream);
362
-
363
- this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
364
- }
365
- });
366
-
367
- newMqa.intervalMetadata.peerReflexiveIP = this.statsResults.connectionType.local.ipAddress;
368
-
369
- // Adding peripheral information
370
- newMqa.intervalMetadata.peripherals.push({information: _UNKNOWN_, name: MEDIA_DEVICES.SPEAKER});
371
- if (this.statsResults['audio-send']) {
372
- newMqa.intervalMetadata.peripherals.push({
373
- information: this.statsResults['audio-send'].trackLabel || _UNKNOWN_,
374
- name: MEDIA_DEVICES.MICROPHONE,
375
- });
376
- }
377
-
378
- const existingVideoSender = Object.keys(this.statsResults).find((item) =>
379
- item.includes('video-send')
380
- );
381
-
382
- if (existingVideoSender) {
383
- newMqa.intervalMetadata.peripherals.push({
384
- information: this.statsResults[existingVideoSender].trackLabel || _UNKNOWN_,
385
- name: MEDIA_DEVICES.CAMERA,
386
- });
387
- }
388
-
389
- newMqa.networkType = this.statsResults.connectionType.local.networkType;
390
-
391
- this.mqaSentCount += 1;
392
-
393
- newMqa.intervalNumber = this.mqaSentCount;
394
-
395
- this.resetStatsResults();
396
-
397
- this.emit(
398
- {
399
- file: 'statsAnalyzer',
400
- function: 'sendMqaData',
401
- },
402
- EVENTS.MEDIA_QUALITY,
403
- {
404
- data: newMqa,
405
- // @ts-ignore
406
- networkType: newMqa.networkType,
407
- }
408
- );
409
- }
410
-
411
- /**
412
- * updated the media connection when changed
413
- *
414
- * @private
415
- * @memberof StatsAnalyzer
416
- * @param {RoapMediaConnection} mediaConnection
417
- * @returns {void}
418
- */
419
- updateMediaConnection(mediaConnection: any) {
420
- this.mediaConnection = mediaConnection;
421
- }
422
-
423
- /**
424
- * Returns the local IP address for diagnostics.
425
- * this is the local IP of the interface used for the current media connection
426
- * a host can have many local Ip Addresses
427
- * @returns {string | undefined} The local IP address.
428
- */
429
- getLocalIpAddress(): string {
430
- return this.localIpAddress;
431
- }
432
-
433
- /**
434
- * Starts the stats analyzer on interval
435
- *
436
- * @public
437
- * @memberof StatsAnalyzer
438
- * @param {RoapMediaConnection} mediaConnection
439
- * @returns {Promise}
440
- */
441
- public startAnalyzer(mediaConnection: any) {
442
- if (!this.statsStarted) {
443
- this.statsStarted = true;
444
- this.mediaConnection = mediaConnection;
445
-
446
- return this.getStatsAndParse().then(() => {
447
- this.statsInterval = setInterval(() => {
448
- this.getStatsAndParse();
449
- }, this.config.analyzerInterval);
450
- // Trigger initial fetch
451
- this.sendMqaData();
452
- this.mqaInterval = setInterval(() => {
453
- this.sendMqaData();
454
- }, MQA_INTERVAL);
455
- });
456
- }
457
-
458
- return Promise.resolve();
459
- }
460
-
461
- /**
462
- * Cleans up the analyzer when done
463
- *
464
- * @public
465
- * @memberof StatsAnalyzer
466
- * @returns {void}
467
- */
468
- public stopAnalyzer() {
469
- const sendOneLastMqa = this.mqaInterval && this.statsInterval;
470
-
471
- if (this.statsInterval) {
472
- clearInterval(this.statsInterval);
473
- this.statsInterval = undefined;
474
- }
475
-
476
- if (this.mqaInterval) {
477
- clearInterval(this.mqaInterval);
478
- this.mqaInterval = undefined;
479
- }
480
-
481
- if (sendOneLastMqa) {
482
- return this.getStatsAndParse().then(() => {
483
- this.sendMqaData();
484
- this.mediaConnection = null;
485
- });
486
- }
487
-
488
- return Promise.resolve();
489
- }
490
-
491
- /**
492
- * Parse a single result of get stats
493
- *
494
- * @private
495
- * @param {*} getStatsResult
496
- * @param {String} type
497
- * @param {boolean} isSender
498
- * @returns {void}
499
- * @memberof StatsAnalyzer
500
- */
501
- private parseGetStatsResult(getStatsResult: any, type: string, isSender: boolean) {
502
- if (!getStatsResult) {
503
- return;
504
- }
505
-
506
- // Generate empty stats results
507
- if (!this.statsResults[type]) {
508
- this.statsResults[type] = {};
509
- }
510
-
511
- if (isSender && !this.statsResults[type].send) {
512
- this.statsResults[type].send = cloneDeep(emptySender);
513
- } else if (!isSender && !this.statsResults[type].recv) {
514
- this.statsResults[type].recv = cloneDeep(emptyReceiver);
515
- }
516
-
517
- switch (getStatsResult.type) {
518
- case 'outbound-rtp':
519
- this.processOutboundRTPResult(getStatsResult, type);
520
- break;
521
- case 'inbound-rtp':
522
- this.processInboundRTPResult(getStatsResult, type);
523
- break;
524
- case 'remote-inbound-rtp':
525
- case 'remote-outbound-rtp':
526
- this.compareSentAndReceived(getStatsResult, type);
527
- break;
528
- case 'remotecandidate':
529
- case 'remote-candidate':
530
- this.parseCandidate(getStatsResult, type, isSender, true);
531
- break;
532
- case 'local-candidate':
533
- this.parseCandidate(getStatsResult, type, isSender, false);
534
- break;
535
- case 'media-source':
536
- this.parseAudioSource(getStatsResult, type);
537
- break;
538
- default:
539
- break;
540
- }
541
- }
542
-
543
- /**
544
- * Filters the get stats results for types
545
- * @private
546
- * @param {Array} statsItem
547
- * @param {String} type
548
- * @param {boolean} isSender
549
- * @returns {void}
550
- */
551
- filterAndParseGetStatsResults(statsItem: any, type: string, isSender: boolean) {
552
- const {types} = DEFAULT_GET_STATS_FILTER;
553
-
554
- // get the successful candidate pair before parsing stats.
555
- statsItem.report.forEach((report) => {
556
- if (report.type === 'candidate-pair' && report.state === 'succeeded') {
557
- this.successfulCandidatePair = report;
558
- }
559
- });
560
-
561
- let videoSenderIndex = 0;
562
- statsItem.report.forEach((result) => {
563
- if (types.includes(result.type)) {
564
- // if the video sender has multiple streams in the report, it is a new stream object.
565
- if (type === 'video-send' && result.type === 'outbound-rtp') {
566
- const newType = `video-send-layer-${videoSenderIndex}`;
567
- this.parseGetStatsResult(result, newType, isSender);
568
- videoSenderIndex += 1;
569
-
570
- this.statsResults[newType].direction = statsItem.currentDirection;
571
- this.statsResults[newType].trackLabel = statsItem.localTrackLabel;
572
- this.statsResults[newType].csi = statsItem.csi;
573
- } else {
574
- this.parseGetStatsResult(result, type, isSender);
575
- }
576
- }
577
- });
578
-
579
- if (this.statsResults[type]) {
580
- this.statsResults[type].direction = statsItem.currentDirection;
581
- this.statsResults[type].trackLabel = statsItem.localTrackLabel;
582
- this.statsResults[type].csi = statsItem.csi;
583
- this.extractAndSetLocalIpAddressInfoForDiagnostics(
584
- this.successfulCandidatePair?.localCandidateId,
585
- this.statsResults?.candidates
586
- );
587
- // reset the successful candidate pair.
588
- this.successfulCandidatePair = {};
589
- }
590
- }
591
-
592
- /**
593
- * parse the audio
594
- * @param {String} result
595
- * @param {boolean} type
596
- * @returns {void}
597
- */
598
- parseAudioSource(result: any, type: any) {
599
- if (!result) {
600
- return;
601
- }
602
-
603
- if (type.includes('audio-send')) {
604
- this.statsResults[type].send.audioLevel = result.audioLevel;
605
- this.statsResults[type].send.totalAudioEnergy = result.totalAudioEnergy;
606
- }
607
- }
608
-
609
- /**
610
- * emits started/stopped events for local/remote media by checking
611
- * if given values are increasing or not. The previousValue, currentValue
612
- * params can be any numerical value like number of receive packets or
613
- * decoded frames, etc.
614
- *
615
- * @private
616
- * @param {string} mediaType
617
- * @param {number} previousValue - value to compare
618
- * @param {number} currentValue - value to compare (must be same type of value as previousValue)
619
- * @param {boolean} isLocal - true if stats are for local media being sent out, false for remote media being received
620
- * @memberof StatsAnalyzer
621
- * @returns {void}
622
- */
623
- emitStartStopEvents = (
624
- mediaType: string,
625
- previousValue: number,
626
- currentValue: number,
627
- isLocal: boolean
628
- ) => {
629
- if (mediaType !== 'audio' && mediaType !== 'video' && mediaType !== 'share') {
630
- throw new Error(`Unsupported mediaType: ${mediaType}`);
631
- }
632
-
633
- // eslint-disable-next-line no-param-reassign
634
- if (previousValue === undefined) previousValue = 0;
635
- // eslint-disable-next-line no-param-reassign
636
- if (currentValue === undefined) currentValue = 0;
637
-
638
- if (!this.lastEmittedStartStopEvent[mediaType]) {
639
- this.lastEmittedStartStopEvent[mediaType] = {};
640
- }
641
-
642
- const lastEmittedEvent = isLocal
643
- ? this.lastEmittedStartStopEvent[mediaType].local
644
- : this.lastEmittedStartStopEvent[mediaType].remote;
645
-
646
- let newEvent;
647
-
648
- if (currentValue - previousValue > 0) {
649
- newEvent = isLocal ? EVENTS.LOCAL_MEDIA_STARTED : EVENTS.REMOTE_MEDIA_STARTED;
650
- } else if (currentValue === previousValue && currentValue > 0) {
651
- newEvent = isLocal ? EVENTS.LOCAL_MEDIA_STOPPED : EVENTS.REMOTE_MEDIA_STOPPED;
652
- }
653
-
654
- if (newEvent && lastEmittedEvent !== newEvent) {
655
- if (isLocal) {
656
- this.lastEmittedStartStopEvent[mediaType].local = newEvent;
657
- } else {
658
- this.lastEmittedStartStopEvent[mediaType].remote = newEvent;
659
- }
660
- this.emit(
661
- {
662
- file: 'statsAnalyzer/index',
663
- function: 'compareLastStatsResult',
664
- },
665
- newEvent,
666
- {
667
- type: mediaType,
668
- }
669
- );
670
- }
671
- };
672
-
673
- /**
674
- * compares current and previous stats to check if packets are not sent
675
- *
676
- * @private
677
- * @memberof StatsAnalyzer
678
- * @returns {void}
679
- */
680
- private compareLastStatsResult() {
681
- if (this.lastStatsResults !== null && this.meetingMediaStatus) {
682
- const getCurrentStatsTotals = (keyPrefix: string, value: string): number =>
683
- Object.keys(this.statsResults)
684
- .filter((key) => key.startsWith(keyPrefix))
685
- .reduce(
686
- (prev, cur) =>
687
- prev +
688
- (this.statsResults[cur]?.[keyPrefix.includes('send') ? 'send' : 'recv'][value] || 0),
689
- 0
690
- );
691
-
692
- const getPreviousStatsTotals = (keyPrefix: string, value: string): number =>
693
- Object.keys(this.statsResults)
694
- .filter((key) => key.startsWith(keyPrefix))
695
- .reduce(
696
- (prev, cur) =>
697
- prev +
698
- (this.lastStatsResults[cur]?.[keyPrefix.includes('send') ? 'send' : 'recv'][value] ||
699
- 0),
700
- 0
701
- );
702
-
703
- // Audio Transmit
704
- if (this.lastStatsResults['audio-send']) {
705
- // compare audio stats sent
706
- // NOTE: relies on there being only one sender.
707
- const currentStats = this.statsResults['audio-send'].send;
708
- const previousStats = this.lastStatsResults['audio-send'].send;
709
-
710
- if (
711
- (this.meetingMediaStatus.expected.sendAudio &&
712
- currentStats.totalPacketsSent === previousStats.totalPacketsSent) ||
713
- currentStats.totalPacketsSent === 0
714
- ) {
715
- LoggerProxy.logger.info(
716
- `StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets sent`,
717
- currentStats.totalPacketsSent
718
- );
719
- } else {
720
- if (
721
- (this.meetingMediaStatus.expected.sendAudio &&
722
- currentStats.totalAudioEnergy === previousStats.totalAudioEnergy) ||
723
- currentStats.totalAudioEnergy === 0
724
- ) {
725
- LoggerProxy.logger.info(
726
- `StatsAnalyzer:index#compareLastStatsResult --> No audio Energy present`,
727
- currentStats.totalAudioEnergy
728
- );
729
- }
730
-
731
- if (this.meetingMediaStatus.expected.sendAudio && currentStats.audioLevel === 0) {
732
- LoggerProxy.logger.info(
733
- `StatsAnalyzer:index#compareLastStatsResult --> audio level is 0 for the user`
734
- );
735
- }
736
- }
737
-
738
- this.emitStartStopEvents(
739
- 'audio',
740
- previousStats.totalPacketsSent,
741
- currentStats.totalPacketsSent,
742
- true
743
- );
744
- }
745
-
746
- // Audio Receive
747
- const currentAudioPacketsReceived = getCurrentStatsTotals(
748
- 'audio-recv',
749
- 'totalPacketsReceived'
750
- );
751
- const previousAudioPacketsReceived = getPreviousStatsTotals(
752
- 'audio-recv',
753
- 'totalPacketsReceived'
754
- );
755
-
756
- this.emitStartStopEvents(
757
- 'audio',
758
- previousAudioPacketsReceived,
759
- currentAudioPacketsReceived,
760
- false
761
- );
762
-
763
- const currentTotalPacketsSent = getCurrentStatsTotals('video-send', 'totalPacketsSent');
764
- const previousTotalPacketsSent = getPreviousStatsTotals('video-send', 'totalPacketsSent');
765
-
766
- const currentFramesEncoded = getCurrentStatsTotals('video-send', 'framesEncoded');
767
- const previousFramesEncoded = getPreviousStatsTotals('video-send', 'framesEncoded');
768
-
769
- const currentFramesSent = getCurrentStatsTotals('video-send', 'framesSent');
770
- const previousFramesSent = getPreviousStatsTotals('video-send', 'framesSent');
771
-
772
- const doesVideoSendExist = Object.keys(this.lastStatsResults).some((item) =>
773
- item.includes('video-send')
774
- );
775
-
776
- // Video Transmit
777
- if (doesVideoSendExist) {
778
- // compare video stats sent
779
-
780
- if (
781
- this.meetingMediaStatus.expected.sendVideo &&
782
- (currentTotalPacketsSent === previousTotalPacketsSent || currentTotalPacketsSent === 0)
783
- ) {
784
- LoggerProxy.logger.info(
785
- `StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent`,
786
- currentTotalPacketsSent
787
- );
788
- } else {
789
- if (
790
- this.meetingMediaStatus.expected.sendVideo &&
791
- (currentFramesEncoded === previousFramesEncoded || currentFramesEncoded === 0)
792
- ) {
793
- LoggerProxy.logger.info(
794
- `StatsAnalyzer:index#compareLastStatsResult --> No video Frames Encoded`,
795
- currentFramesEncoded
796
- );
797
- }
798
-
799
- if (
800
- this.meetingMediaStatus.expected.sendVideo &&
801
- (currentFramesSent === previousFramesSent || currentFramesSent === 0)
802
- ) {
803
- LoggerProxy.logger.info(
804
- `StatsAnalyzer:index#compareLastStatsResult --> No video Frames sent`,
805
- currentFramesSent
806
- );
807
- }
808
- }
809
-
810
- this.emitStartStopEvents('video', previousFramesSent, currentFramesSent, true);
811
- }
812
-
813
- // Video Receive
814
- const currentVideoFramesDecoded = getCurrentStatsTotals('video-recv', 'framesDecoded');
815
- const previousVideoFramesDecoded = getPreviousStatsTotals('video-recv', 'framesDecoded');
816
-
817
- this.emitStartStopEvents(
818
- 'video',
819
- previousVideoFramesDecoded,
820
- currentVideoFramesDecoded,
821
- false
822
- );
823
-
824
- // Share Transmit
825
- if (this.lastStatsResults['video-share-send']) {
826
- // compare share stats sent
827
-
828
- const currentStats = this.statsResults['video-share-send'].send;
829
- const previousStats = this.lastStatsResults['video-share-send'].send;
830
-
831
- if (
832
- this.meetingMediaStatus.expected.sendShare &&
833
- (currentStats.totalPacketsSent === previousStats.totalPacketsSent ||
834
- currentStats.totalPacketsSent === 0)
835
- ) {
836
- LoggerProxy.logger.info(
837
- `StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets sent`,
838
- currentStats.totalPacketsSent
839
- );
840
- } else {
841
- if (
842
- this.meetingMediaStatus.expected.sendShare &&
843
- (currentStats.framesEncoded === previousStats.framesEncoded ||
844
- currentStats.framesEncoded === 0)
845
- ) {
846
- LoggerProxy.logger.info(
847
- `StatsAnalyzer:index#compareLastStatsResult --> No share frames getting encoded`,
848
- currentStats.framesEncoded
849
- );
850
- }
851
-
852
- if (
853
- this.meetingMediaStatus.expected.sendShare &&
854
- (this.statsResults['video-share-send'].send.framesSent ===
855
- this.lastStatsResults['video-share-send'].send.framesSent ||
856
- this.statsResults['video-share-send'].send.framesSent === 0)
857
- ) {
858
- LoggerProxy.logger.info(
859
- `StatsAnalyzer:index#compareLastStatsResult --> No share frames sent`,
860
- this.statsResults['video-share-send'].send.framesSent
861
- );
862
- }
863
- }
864
-
865
- this.emitStartStopEvents('share', previousStats.framesSent, currentStats.framesSent, true);
866
- }
867
-
868
- // Share receive
869
- const currentShareFramesDecoded = getCurrentStatsTotals('video-share-recv', 'framesDecoded');
870
- const previousShareFramesDecoded = getPreviousStatsTotals(
871
- 'video-share-recv',
872
- 'framesDecoded'
873
- );
874
-
875
- this.emitStartStopEvents(
876
- 'share',
877
- previousShareFramesDecoded,
878
- currentShareFramesDecoded,
879
- false
880
- );
881
- }
882
- }
883
-
884
- /**
885
- * Does a `getStats` on all the transceivers and parses the results
886
- *
887
- * @private
888
- * @memberof StatsAnalyzer
889
- * @returns {Promise}
890
- */
891
- private getStatsAndParse() {
892
- if (!this.mediaConnection) {
893
- return Promise.resolve();
894
- }
895
-
896
- if (
897
- this.mediaConnection &&
898
- this.mediaConnection.getConnectionState() === ConnectionState.Failed
899
- ) {
900
- LoggerProxy.logger.trace(
901
- 'StatsAnalyzer:index#getStatsAndParse --> media connection is in failed state'
902
- );
903
-
904
- return Promise.resolve();
905
- }
906
-
907
- LoggerProxy.logger.trace('StatsAnalyzer:index#getStatsAndParse --> Collecting Stats');
908
-
909
- return this.mediaConnection.getTransceiverStats().then((transceiverStats) => {
910
- transceiverStats.video.receivers.forEach((receiver, i) =>
911
- this.filterAndParseGetStatsResults(receiver, `video-recv-${i}`, false)
912
- );
913
- transceiverStats.audio.receivers.forEach((receiver, i) =>
914
- this.filterAndParseGetStatsResults(receiver, `audio-recv-${i}`, false)
915
- );
916
- transceiverStats.screenShareVideo.receivers.forEach((receiver, i) =>
917
- this.filterAndParseGetStatsResults(receiver, `video-share-recv-${i}`, false)
918
- );
919
- transceiverStats.screenShareAudio.receivers.forEach((receiver, i) =>
920
- this.filterAndParseGetStatsResults(receiver, `audio-share-recv-${i}`, false)
921
- );
922
-
923
- transceiverStats.video.senders.forEach((sender, i) => {
924
- if (i > 0) {
925
- throw new Error('Stats Analyzer does not support multiple senders.');
926
- }
927
- this.filterAndParseGetStatsResults(sender, 'video-send', true);
928
- });
929
- transceiverStats.audio.senders.forEach((sender, i) => {
930
- if (i > 0) {
931
- throw new Error('Stats Analyzer does not support multiple senders.');
932
- }
933
- this.filterAndParseGetStatsResults(sender, 'audio-send', true);
934
- });
935
- transceiverStats.screenShareVideo.senders.forEach((sender, i) => {
936
- if (i > 0) {
937
- throw new Error('Stats Analyzer does not support multiple senders.');
938
- }
939
- this.filterAndParseGetStatsResults(sender, 'video-share-send', true);
940
- });
941
- transceiverStats.screenShareAudio.senders.forEach((sender, i) => {
942
- if (i > 0) {
943
- throw new Error('Stats Analyzer does not support multiple senders.');
944
- }
945
- this.filterAndParseGetStatsResults(sender, 'audio-share-send', true);
946
- });
947
-
948
- this.compareLastStatsResult();
949
-
950
- // Save the last results to compare with the current
951
- // DO Deep copy, for some reason it takes the reference all the time rather then old value set
952
- this.lastStatsResults = JSON.parse(JSON.stringify(this.statsResults));
953
-
954
- LoggerProxy.logger.trace(
955
- 'StatsAnalyzer:index#getStatsAndParse --> Finished Collecting Stats'
956
- );
957
- });
958
- }
959
-
960
- /**
961
- * Processes OutboundRTP stats result and stores
962
- * @private
963
- * @param {*} result
964
- * @param {*} mediaType
965
- * @returns {void}
966
- */
967
- private processOutboundRTPResult(result: any, mediaType: any) {
968
- const sendrecvType = STATS.SEND_DIRECTION;
969
-
970
- if (result.bytesSent) {
971
- const kilobytes = 0;
972
-
973
- if (result.frameWidth && result.frameHeight) {
974
- this.statsResults[mediaType][sendrecvType].width = result.frameWidth;
975
- this.statsResults[mediaType][sendrecvType].height = result.frameHeight;
976
- this.statsResults[mediaType][sendrecvType].framesSent = result.framesSent;
977
- this.statsResults[mediaType][sendrecvType].hugeFramesSent = result.hugeFramesSent;
978
- }
979
-
980
- this.statsResults[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1);
981
-
982
- this.statsResults[mediaType][sendrecvType].framesEncoded = result.framesEncoded;
983
- this.statsResults[mediaType][sendrecvType].keyFramesEncoded = result.keyFramesEncoded;
984
- this.statsResults[mediaType][sendrecvType].packetsSent = result.packetsSent;
985
-
986
- // Data saved to send MQA metrics
987
-
988
- this.statsResults[mediaType][sendrecvType].totalKeyFramesEncoded = result.keyFramesEncoded;
989
- this.statsResults[mediaType][sendrecvType].totalNackCount = result.nackCount;
990
- this.statsResults[mediaType][sendrecvType].totalPliCount = result.pliCount;
991
- this.statsResults[mediaType][sendrecvType].totalPacketsSent = result.packetsSent;
992
- this.statsResults[mediaType][sendrecvType].totalFirCount = result.firCount;
993
- this.statsResults[mediaType][sendrecvType].framesSent = result.framesSent;
994
- this.statsResults[mediaType][sendrecvType].framesEncoded = result.framesEncoded;
995
- this.statsResults[mediaType][sendrecvType].encoderImplementation =
996
- result.encoderImplementation;
997
- this.statsResults[mediaType][sendrecvType].qualityLimitationReason =
998
- result.qualityLimitationReason;
999
- this.statsResults[mediaType][sendrecvType].qualityLimitationResolutionChanges =
1000
- result.qualityLimitationResolutionChanges;
1001
- this.statsResults[mediaType][sendrecvType].totalRtxPacketsSent =
1002
- result.retransmittedPacketsSent;
1003
- this.statsResults[mediaType][sendrecvType].totalRtxBytesSent = result.retransmittedBytesSent;
1004
- this.statsResults[mediaType][sendrecvType].totalBytesSent = result.bytesSent;
1005
- this.statsResults[mediaType][sendrecvType].headerBytesSent = result.headerBytesSent;
1006
- this.statsResults[mediaType][sendrecvType].requestedBitrate = result.requestedBitrate;
1007
- this.statsResults[mediaType][sendrecvType].requestedFrameSize = result.requestedFrameSize;
1008
- }
1009
- }
1010
-
1011
- /**
1012
- * Processes InboundRTP stats result and stores
1013
- * @private
1014
- * @param {*} result
1015
- * @param {*} mediaType
1016
- * @returns {void}
1017
- */
1018
- private processInboundRTPResult(result: any, mediaType: any) {
1019
- const sendrecvType = STATS.RECEIVE_DIRECTION;
1020
-
1021
- if (result.bytesReceived) {
1022
- let kilobytes = 0;
1023
- const receiveSlot = this.receiveSlotCallback(result.ssrc);
1024
- const sourceState = receiveSlot?.sourceState;
1025
- const idAndCsi = receiveSlot
1026
- ? `id: "${receiveSlot.id || ''}"${receiveSlot.csi ? ` and csi: ${receiveSlot.csi}` : ''}`
1027
- : '';
1028
-
1029
- const bytes =
1030
- result.bytesReceived - this.statsResults[mediaType][sendrecvType].totalBytesReceived;
1031
-
1032
- kilobytes = bytes / 1024;
1033
- this.statsResults[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1);
1034
-
1035
- let currentPacketsLost =
1036
- result.packetsLost - this.statsResults[mediaType][sendrecvType].totalPacketsLost;
1037
- if (currentPacketsLost < 0) {
1038
- currentPacketsLost = 0;
1039
- }
1040
- const packetsReceivedDiff =
1041
- result.packetsReceived - this.statsResults[mediaType][sendrecvType].totalPacketsReceived;
1042
- this.statsResults[mediaType][sendrecvType].totalPacketsReceived = result.packetsReceived;
1043
-
1044
- if (packetsReceivedDiff === 0) {
1045
- if (receiveSlot && sourceState === 'live') {
1046
- LoggerProxy.logger.info(
1047
- `StatsAnalyzer:index#processInboundRTPResult --> No packets received for mediaType: ${mediaType}, receive slot ${idAndCsi}. Total packets received on slot: `,
1048
- result.packetsReceived
1049
- );
1050
- }
1051
- }
1052
-
1053
- if (mediaType.startsWith('video') || mediaType.startsWith('share')) {
1054
- const videoFramesReceivedDiff =
1055
- result.framesReceived - this.statsResults[mediaType][sendrecvType].framesReceived;
1056
-
1057
- if (videoFramesReceivedDiff === 0) {
1058
- if (receiveSlot && sourceState === 'live') {
1059
- LoggerProxy.logger.info(
1060
- `StatsAnalyzer:index#processInboundRTPResult --> No frames received for mediaType: ${mediaType}, receive slot ${idAndCsi}. Total frames received on slot: `,
1061
- result.framesReceived
1062
- );
1063
- }
1064
- }
1065
-
1066
- const videoFramesDecodedDiff =
1067
- result.framesDecoded - this.statsResults[mediaType][sendrecvType].framesDecoded;
1068
-
1069
- if (videoFramesDecodedDiff === 0) {
1070
- if (receiveSlot && sourceState === 'live') {
1071
- LoggerProxy.logger.info(
1072
- `StatsAnalyzer:index#processInboundRTPResult --> No frames decoded for mediaType: ${mediaType}, receive slot ${idAndCsi}. Total frames decoded on slot: `,
1073
- result.framesDecoded
1074
- );
1075
- }
1076
- }
1077
-
1078
- const videoFramesDroppedDiff =
1079
- result.framesDropped - this.statsResults[mediaType][sendrecvType].framesDropped;
1080
-
1081
- if (videoFramesDroppedDiff > 10) {
1082
- if (receiveSlot && sourceState === 'live') {
1083
- LoggerProxy.logger.info(
1084
- `StatsAnalyzer:index#processInboundRTPResult --> Frames dropped for mediaType: ${mediaType}, receive slot ${idAndCsi}. Total frames dropped on slot: `,
1085
- result.framesDropped
1086
- );
1087
- }
1088
- }
1089
- }
1090
-
1091
- // Check the over all packet Lost ratio
1092
- this.statsResults[mediaType][sendrecvType].currentPacketLossRatio =
1093
- currentPacketsLost > 0
1094
- ? currentPacketsLost / (packetsReceivedDiff + currentPacketsLost)
1095
- : 0;
1096
- if (this.statsResults[mediaType][sendrecvType].currentPacketLossRatio > 3) {
1097
- LoggerProxy.logger.info(
1098
- `StatsAnalyzer:index#processInboundRTPResult --> Packets getting lost from the receiver with slot ${idAndCsi}`,
1099
- this.statsResults[mediaType][sendrecvType].currentPacketLossRatio
1100
- );
1101
- }
1102
-
1103
- if (result.frameWidth && result.frameHeight) {
1104
- this.statsResults[mediaType][sendrecvType].width = result.frameWidth;
1105
- this.statsResults[mediaType][sendrecvType].height = result.frameHeight;
1106
- this.statsResults[mediaType][sendrecvType].framesReceived = result.framesReceived;
1107
- }
1108
-
1109
- // TODO: check the packet loss value is negative values here
1110
-
1111
- if (result.packetsLost) {
1112
- this.statsResults[mediaType][sendrecvType].totalPacketsLost =
1113
- result.packetsLost > 0 ? result.packetsLost : -result.packetsLost;
1114
- } else {
1115
- this.statsResults[mediaType][sendrecvType].totalPacketsLost = 0;
1116
- }
1117
-
1118
- this.statsResults[mediaType][sendrecvType].lastPacketReceivedTimestamp =
1119
- result.lastPacketReceivedTimestamp;
1120
- this.statsResults[mediaType][sendrecvType].requestedBitrate = result.requestedBitrate;
1121
- this.statsResults[mediaType][sendrecvType].requestedFrameSize = result.requestedFrameSize;
1122
-
1123
- // From Thin
1124
- this.statsResults[mediaType][sendrecvType].totalNackCount = result.nackCount;
1125
- this.statsResults[mediaType][sendrecvType].totalPliCount = result.pliCount;
1126
- this.statsResults[mediaType][sendrecvType].framesDecoded = result.framesDecoded;
1127
- this.statsResults[mediaType][sendrecvType].keyFramesDecoded = result.keyFramesDecoded;
1128
- this.statsResults[mediaType][sendrecvType].framesDropped = result.framesDropped;
1129
-
1130
- this.statsResults[mediaType][sendrecvType].decoderImplementation =
1131
- result.decoderImplementation;
1132
- this.statsResults[mediaType][sendrecvType].totalPacketsReceived = result.packetsReceived;
1133
-
1134
- this.statsResults[mediaType][sendrecvType].fecPacketsDiscarded = result.fecPacketsDiscarded;
1135
- this.statsResults[mediaType][sendrecvType].fecPacketsReceived = result.fecPacketsReceived;
1136
- this.statsResults[mediaType][sendrecvType].totalBytesReceived = result.bytesReceived;
1137
- this.statsResults[mediaType][sendrecvType].headerBytesReceived = result.headerBytesReceived;
1138
- this.statsResults[mediaType][sendrecvType].totalRtxPacketsReceived =
1139
- result.retransmittedPacketsReceived;
1140
- this.statsResults[mediaType][sendrecvType].totalRtxBytesReceived =
1141
- result.retransmittedBytesReceived;
1142
-
1143
- this.statsResults[mediaType][sendrecvType].meanRtpJitter.push(result.jitter);
1144
-
1145
- // Audio stats
1146
-
1147
- this.statsResults[mediaType][sendrecvType].audioLevel = result.audioLevel;
1148
- this.statsResults[mediaType][sendrecvType].totalAudioEnergy = result.totalAudioEnergy;
1149
- this.statsResults[mediaType][sendrecvType].totalSamplesReceived =
1150
- result.totalSamplesReceived || 0;
1151
- this.statsResults[mediaType][sendrecvType].totalSamplesDecoded =
1152
- result.totalSamplesDecoded || 0;
1153
- this.statsResults[mediaType][sendrecvType].concealedSamples = result.concealedSamples || 0;
1154
- }
1155
- }
1156
-
1157
- /**
1158
- * extracts the local Ip address from the statsResult object by looking at stats results candidates
1159
- * and matches that ID with the successful candidate pair. It looks at the type of local candidate it is
1160
- * and then extracts the IP address from the relatedAddress or address property based on conditions known in webrtc
1161
- * note, there are known incompatibilities and it is possible for this to set undefined, or for the IP address to be the public IP address
1162
- * for example, firefox does not set the relayProtocol, and if the user is behind a NAT it might be the public IP
1163
- * @private
1164
- * @param {string} successfulCandidatePairId - The ID of the successful candidate pair.
1165
- * @param {Object} candidates - the stats result candidates
1166
- * @returns {void}
1167
- */
1168
- extractAndSetLocalIpAddressInfoForDiagnostics = (
1169
- successfulCandidatePairId: string,
1170
- candidates: {[key: string]: Record<string, unknown>}
1171
- ) => {
1172
- let newIpAddress = '';
1173
- if (successfulCandidatePairId && !isEmpty(candidates)) {
1174
- const localCandidate = candidates[successfulCandidatePairId];
1175
- if (localCandidate) {
1176
- if (localCandidate.candidateType === 'host') {
1177
- // if it's a host candidate, use the address property - it will be the local IP
1178
- newIpAddress = `${localCandidate.address}`;
1179
- } else if (localCandidate.candidateType === 'prflx') {
1180
- // if it's a peer reflexive candidate and we're not using a relay (there is no relayProtocol set)
1181
- // then look at the relatedAddress - it will be the local
1182
- //
1183
- // Firefox doesn't populate the relayProtocol property
1184
- if (!localCandidate.relayProtocol) {
1185
- newIpAddress = `${localCandidate.relatedAddress}`;
1186
- } else {
1187
- // if it's a peer reflexive candidate and we are using a relay -
1188
- // in that case the relatedAddress will be the IP of the TURN server (Linus),
1189
- // so we can only look at the address, but it might be local IP or public IP,
1190
- // depending on if the user is behind a NAT or not
1191
- newIpAddress = `${localCandidate.address}`;
1192
- }
1193
- }
1194
- }
1195
- }
1196
- this.localIpAddress = newIpAddress;
1197
- };
1198
-
1199
- /**
1200
- * Processes remote and local candidate result and stores
1201
- * @private
1202
- * @param {*} result
1203
- * @param {*} type
1204
- * @param {boolean} isSender
1205
- * @param {boolean} isRemote
1206
- *
1207
- * @returns {void}
1208
- */
1209
- parseCandidate = (result: any, type: any, isSender: boolean, isRemote: boolean) => {
1210
- if (!result || !result.id) {
1211
- return;
1212
- }
1213
-
1214
- // We only care about the successful local candidate
1215
- if (this.successfulCandidatePair?.localCandidateId !== result.id) {
1216
- return;
1217
- }
1218
-
1219
- let transport;
1220
- if (result.relayProtocol) {
1221
- transport = result.relayProtocol.toUpperCase();
1222
- } else if (result.protocol) {
1223
- transport = result.protocol.toUpperCase();
1224
- }
1225
-
1226
- const sendRecvType = isSender ? STATS.SEND_DIRECTION : STATS.RECEIVE_DIRECTION;
1227
- const ipType = isRemote ? STATS.REMOTE : STATS.LOCAL;
1228
-
1229
- if (!this.statsResults.candidates) {
1230
- this.statsResults.candidates = {};
1231
- }
1232
-
1233
- this.statsResults.candidates[result.id] = {
1234
- candidateType: result.candidateType,
1235
- ipAddress: result.ip, // TODO: add ports
1236
- relatedAddress: result.relatedAddress,
1237
- relatedPort: result.relatedPort,
1238
- relayProtocol: result.relayProtocol,
1239
- protocol: result.protocol,
1240
- address: result.address,
1241
- portNumber: result.port,
1242
- networkType: result.networkType,
1243
- priority: result.priority,
1244
- transport,
1245
- timestamp: result.time,
1246
- id: result.id,
1247
- type: result.type,
1248
- };
1249
-
1250
- this.statsResults.connectionType[ipType].candidateType = result.candidateType;
1251
- this.statsResults.connectionType[ipType].ipAddress = result.ipAddress;
1252
-
1253
- this.statsResults.connectionType[ipType].networkType =
1254
- result.networkType === NETWORK_TYPE.VPN ? NETWORK_TYPE.UNKNOWN : result.networkType;
1255
- this.statsResults.connectionType[ipType].transport = transport;
1256
-
1257
- this.statsResults[type][sendRecvType].totalRoundTripTime = result.totalRoundTripTime;
1258
- };
1259
-
1260
- /**
1261
- *
1262
- * @private
1263
- * @param {*} result
1264
- * @param {*} type
1265
- * @returns {void}
1266
- * @memberof StatsAnalyzer
1267
- */
1268
- compareSentAndReceived(result, type) {
1269
- // Don't compare on transceivers without a sender.
1270
- if (!type || !this.statsResults[type].send) {
1271
- return;
1272
- }
1273
-
1274
- const mediaType = type;
1275
-
1276
- const currentPacketLoss =
1277
- result.packetsLost - this.statsResults[mediaType].send.totalPacketsLostOnReceiver;
1278
-
1279
- this.statsResults[mediaType].send.packetsLostOnReceiver = currentPacketLoss;
1280
- this.statsResults[mediaType].send.totalPacketsLostOnReceiver = result.packetsLost;
1281
-
1282
- this.statsResults[mediaType].send.meanRemoteJitter.push(result.jitter);
1283
- this.statsResults[mediaType].send.meanRoundTripTime.push(result.roundTripTime);
1284
-
1285
- this.statsResults[mediaType].send.timestamp = result.timestamp;
1286
- this.statsResults[mediaType].send.ssrc = result.ssrc;
1287
- this.statsResults[mediaType].send.reportsReceived = result.reportsReceived;
1288
-
1289
- // Total packloss ratio on this video section of the call
1290
- this.statsResults[mediaType].send.overAllPacketLossRatio =
1291
- this.statsResults[mediaType].send.totalPacketsLostOnReceiver > 0
1292
- ? this.statsResults[mediaType].send.totalPacketsLostOnReceiver /
1293
- this.statsResults[mediaType].send.totalPacketsSent
1294
- : 0;
1295
- this.statsResults[mediaType].send.currentPacketLossRatio =
1296
- this.statsResults[mediaType].send.packetsLostOnReceiver > 0
1297
- ? (this.statsResults[mediaType].send.packetsLostOnReceiver * 100) /
1298
- (this.statsResults[mediaType].send.packetsSent +
1299
- this.statsResults[mediaType].send.packetsLostOnReceiver)
1300
- : 0;
1301
-
1302
- if (
1303
- this.statsResults[mediaType].send.maxPacketLossRatio <
1304
- this.statsResults[mediaType].send.currentPacketLossRatio
1305
- ) {
1306
- this.statsResults[mediaType].send.maxPacketLossRatio =
1307
- this.statsResults[mediaType].send.currentPacketLossRatio;
1308
- }
1309
-
1310
- if (result.type === 'remote-inbound-rtp') {
1311
- this.networkQualityMonitor.determineUplinkNetworkQuality({
1312
- mediaType,
1313
- remoteRtpResults: result,
1314
- statsAnalyzerCurrentStats: this.statsResults,
1315
- });
1316
- }
1317
- }
1318
- }