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