@webex/plugin-meetings 3.3.1-next.1 → 3.3.1-next.11

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.
@@ -35,6 +35,7 @@ import {
35
35
  getAudioReceiverStreamMqa,
36
36
  getVideoSenderStreamMqa,
37
37
  getVideoReceiverStreamMqa,
38
+ isStreamRequested,
38
39
  } from './mqaUtil';
39
40
  import {ReceiveSlot} from '../multistream/receiveSlot';
40
41
 
@@ -91,22 +92,31 @@ export class StatsAnalyzer extends EventsScope {
91
92
  successfulCandidatePair: any;
92
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
93
94
  receiveSlotCallback: ReceiveSlotCallback;
95
+ isMultistream: boolean;
94
96
 
95
97
  /**
96
98
  * Creates a new instance of StatsAnalyzer
97
99
  * @constructor
98
100
  * @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
101
+ * @param {Object} config - SDK Configuration Object
102
+ * @param {Function} receiveSlotCallback - Callback used to access receive slots.
103
+ * @param {Object} networkQualityMonitor - Class for assessing network characteristics (jitter, packetLoss, latency)
104
+ * @param {Object} statsResults - Default properties for stats
105
+ * @param {boolean | undefined} isMultistream - Param indicating if the media connection is multistream or not
103
106
  */
104
- constructor(
105
- config: any,
106
- receiveSlotCallback: ReceiveSlotCallback = () => undefined,
107
- networkQualityMonitor: object = {},
108
- statsResults: object = defaultStats
109
- ) {
107
+ constructor({
108
+ config,
109
+ receiveSlotCallback = () => undefined,
110
+ networkQualityMonitor = {},
111
+ statsResults = defaultStats,
112
+ isMultistream = false,
113
+ }: {
114
+ config: any;
115
+ receiveSlotCallback: ReceiveSlotCallback;
116
+ networkQualityMonitor: any;
117
+ statsResults?: any;
118
+ isMultistream?: boolean;
119
+ }) {
110
120
  super();
111
121
  this.statsStarted = false;
112
122
  this.statsResults = statsResults;
@@ -120,6 +130,7 @@ export class StatsAnalyzer extends EventsScope {
120
130
  this.receiveSlotCallback = receiveSlotCallback;
121
131
  this.successfulCandidatePair = {};
122
132
  this.localIpAddress = '';
133
+ this.isMultistream = isMultistream;
123
134
  }
124
135
 
125
136
  /**
@@ -203,6 +214,7 @@ export class StatsAnalyzer extends EventsScope {
203
214
  statsResults: this.statsResults,
204
215
  lastMqaDataSent: this.lastMqaDataSent,
205
216
  baseMediaType: 'audio-send',
217
+ isMultistream: this.isMultistream,
206
218
  });
207
219
  newMqa.audioTransmit.push(audioSender);
208
220
 
@@ -211,6 +223,7 @@ export class StatsAnalyzer extends EventsScope {
211
223
  statsResults: this.statsResults,
212
224
  lastMqaDataSent: this.lastMqaDataSent,
213
225
  baseMediaType: 'audio-share-send',
226
+ isMultistream: this.isMultistream,
214
227
  });
215
228
  newMqa.audioTransmit.push(audioShareSender);
216
229
 
@@ -219,6 +232,7 @@ export class StatsAnalyzer extends EventsScope {
219
232
  statsResults: this.statsResults,
220
233
  lastMqaDataSent: this.lastMqaDataSent,
221
234
  baseMediaType: 'audio-recv',
235
+ isMultistream: this.isMultistream,
222
236
  });
223
237
  newMqa.audioReceive.push(audioReceiver);
224
238
 
@@ -227,6 +241,7 @@ export class StatsAnalyzer extends EventsScope {
227
241
  statsResults: this.statsResults,
228
242
  lastMqaDataSent: this.lastMqaDataSent,
229
243
  baseMediaType: 'audio-share-recv',
244
+ isMultistream: this.isMultistream,
230
245
  });
231
246
  newMqa.audioReceive.push(audioShareReceiver);
232
247
 
@@ -235,6 +250,7 @@ export class StatsAnalyzer extends EventsScope {
235
250
  statsResults: this.statsResults,
236
251
  lastMqaDataSent: this.lastMqaDataSent,
237
252
  baseMediaType: 'video-send',
253
+ isMultistream: this.isMultistream,
238
254
  });
239
255
  newMqa.videoTransmit.push(videoSender);
240
256
 
@@ -243,6 +259,7 @@ export class StatsAnalyzer extends EventsScope {
243
259
  statsResults: this.statsResults,
244
260
  lastMqaDataSent: this.lastMqaDataSent,
245
261
  baseMediaType: 'video-share-send',
262
+ isMultistream: this.isMultistream,
246
263
  });
247
264
  newMqa.videoTransmit.push(videoShareSender);
248
265
 
@@ -251,6 +268,7 @@ export class StatsAnalyzer extends EventsScope {
251
268
  statsResults: this.statsResults,
252
269
  lastMqaDataSent: this.lastMqaDataSent,
253
270
  baseMediaType: 'video-recv',
271
+ isMultistream: this.isMultistream,
254
272
  });
255
273
  newMqa.videoReceive.push(videoReceiver);
256
274
 
@@ -259,6 +277,7 @@ export class StatsAnalyzer extends EventsScope {
259
277
  statsResults: this.statsResults,
260
278
  lastMqaDataSent: this.lastMqaDataSent,
261
279
  baseMediaType: 'video-share-recv',
280
+ isMultistream: this.isMultistream,
262
281
  });
263
282
  newMqa.videoReceive.push(videoShareReceiver);
264
283
 
@@ -273,7 +292,9 @@ export class StatsAnalyzer extends EventsScope {
273
292
  lastMqaDataSent: this.lastMqaDataSent,
274
293
  mediaType,
275
294
  });
276
- newMqa.audioTransmit[0].streams.push(audioSenderStream);
295
+ if (isStreamRequested(this.statsResults, mediaType, STATS.SEND_DIRECTION)) {
296
+ newMqa.audioTransmit[0].streams.push(audioSenderStream);
297
+ }
277
298
 
278
299
  this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
279
300
  } else if (mediaType.startsWith('audio-share-send')) {
@@ -285,7 +306,9 @@ export class StatsAnalyzer extends EventsScope {
285
306
  lastMqaDataSent: this.lastMqaDataSent,
286
307
  mediaType,
287
308
  });
288
- newMqa.audioTransmit[1].streams.push(audioSenderStream);
309
+ if (isStreamRequested(this.statsResults, mediaType, STATS.SEND_DIRECTION)) {
310
+ newMqa.audioTransmit[1].streams.push(audioSenderStream);
311
+ }
289
312
 
290
313
  this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
291
314
  } else if (mediaType.startsWith('audio-recv')) {
@@ -297,7 +320,9 @@ export class StatsAnalyzer extends EventsScope {
297
320
  lastMqaDataSent: this.lastMqaDataSent,
298
321
  mediaType,
299
322
  });
300
- newMqa.audioReceive[0].streams.push(audioReceiverStream);
323
+ if (isStreamRequested(this.statsResults, mediaType, STATS.RECEIVE_DIRECTION)) {
324
+ newMqa.audioReceive[0].streams.push(audioReceiverStream);
325
+ }
301
326
 
302
327
  this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
303
328
  } else if (mediaType.startsWith('audio-share-recv')) {
@@ -309,8 +334,9 @@ export class StatsAnalyzer extends EventsScope {
309
334
  lastMqaDataSent: this.lastMqaDataSent,
310
335
  mediaType,
311
336
  });
312
- newMqa.audioReceive[1].streams.push(audioReceiverStream);
313
-
337
+ if (isStreamRequested(this.statsResults, mediaType, STATS.RECEIVE_DIRECTION)) {
338
+ newMqa.audioReceive[1].streams.push(audioReceiverStream);
339
+ }
314
340
  this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
315
341
  } else if (mediaType.startsWith('video-send-layer')) {
316
342
  // We only want the stream-specific stats we get with video-send-layer-0, video-send-layer-1, etc.
@@ -322,8 +348,9 @@ export class StatsAnalyzer extends EventsScope {
322
348
  lastMqaDataSent: this.lastMqaDataSent,
323
349
  mediaType,
324
350
  });
325
- newMqa.videoTransmit[0].streams.push(videoSenderStream);
326
-
351
+ if (isStreamRequested(this.statsResults, mediaType, STATS.SEND_DIRECTION)) {
352
+ newMqa.videoTransmit[0].streams.push(videoSenderStream);
353
+ }
327
354
  this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
328
355
  } else if (mediaType.startsWith('video-share-send')) {
329
356
  const videoSenderStream = cloneDeep(emptyVideoTransmitStream);
@@ -334,7 +361,9 @@ export class StatsAnalyzer extends EventsScope {
334
361
  lastMqaDataSent: this.lastMqaDataSent,
335
362
  mediaType,
336
363
  });
337
- newMqa.videoTransmit[1].streams.push(videoSenderStream);
364
+ if (isStreamRequested(this.statsResults, mediaType, STATS.SEND_DIRECTION)) {
365
+ newMqa.videoTransmit[1].streams.push(videoSenderStream);
366
+ }
338
367
 
339
368
  this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
340
369
  } else if (mediaType.startsWith('video-recv')) {
@@ -346,7 +375,9 @@ export class StatsAnalyzer extends EventsScope {
346
375
  lastMqaDataSent: this.lastMqaDataSent,
347
376
  mediaType,
348
377
  });
349
- newMqa.videoReceive[0].streams.push(videoReceiverStream);
378
+ if (isStreamRequested(this.statsResults, mediaType, STATS.RECEIVE_DIRECTION)) {
379
+ newMqa.videoReceive[0].streams.push(videoReceiverStream);
380
+ }
350
381
 
351
382
  this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
352
383
  } else if (mediaType.startsWith('video-share-recv')) {
@@ -358,8 +389,9 @@ export class StatsAnalyzer extends EventsScope {
358
389
  lastMqaDataSent: this.lastMqaDataSent,
359
390
  mediaType,
360
391
  });
361
- newMqa.videoReceive[1].streams.push(videoReceiverStream);
362
-
392
+ if (isStreamRequested(this.statsResults, mediaType, STATS.RECEIVE_DIRECTION)) {
393
+ newMqa.videoReceive[1].streams.push(videoReceiverStream);
394
+ }
363
395
  this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
364
396
  }
365
397
  });
@@ -388,6 +420,17 @@ export class StatsAnalyzer extends EventsScope {
388
420
 
389
421
  newMqa.networkType = this.statsResults.connectionType.local.networkType;
390
422
 
423
+ newMqa.intervalMetadata.screenWidth = window.screen.width;
424
+ newMqa.intervalMetadata.screenHeight = window.screen.height;
425
+ newMqa.intervalMetadata.screenResolution = Math.round(
426
+ (window.screen.width * window.screen.height) / 256
427
+ );
428
+ newMqa.intervalMetadata.appWindowWidth = window.innerWidth;
429
+ newMqa.intervalMetadata.appWindowHeight = window.innerHeight;
430
+ newMqa.intervalMetadata.appWindowSize = Math.round(
431
+ (window.innerWidth * window.innerHeight) / 256
432
+ );
433
+
391
434
  this.mqaSentCount += 1;
392
435
 
393
436
  newMqa.intervalNumber = this.mqaSentCount;
@@ -1003,6 +1046,11 @@ export class StatsAnalyzer extends EventsScope {
1003
1046
  this.statsResults[mediaType][sendrecvType].totalRtxBytesSent = result.retransmittedBytesSent;
1004
1047
  this.statsResults[mediaType][sendrecvType].totalBytesSent = result.bytesSent;
1005
1048
  this.statsResults[mediaType][sendrecvType].headerBytesSent = result.headerBytesSent;
1049
+ this.statsResults[mediaType][sendrecvType].retransmittedBytesSent =
1050
+ result.retransmittedBytesSent;
1051
+ this.statsResults[mediaType][sendrecvType].isRequested = result.isRequested;
1052
+ this.statsResults[mediaType][sendrecvType].lastRequestedUpdateTimestamp =
1053
+ result.lastRequestedUpdateTimestamp;
1006
1054
  this.statsResults[mediaType][sendrecvType].requestedBitrate = result.requestedBitrate;
1007
1055
  this.statsResults[mediaType][sendrecvType].requestedFrameSize = result.requestedFrameSize;
1008
1056
  }
@@ -1088,6 +1136,12 @@ export class StatsAnalyzer extends EventsScope {
1088
1136
  }
1089
1137
  }
1090
1138
 
1139
+ if (mediaType.startsWith('video-recv')) {
1140
+ this.statsResults[mediaType][sendrecvType].isActiveSpeaker = result.isActiveSpeaker;
1141
+ this.statsResults[mediaType][sendrecvType].lastActiveSpeakerTimestamp =
1142
+ result.lastActiveSpeakerUpdateTimestamp;
1143
+ }
1144
+
1091
1145
  // Check the over all packet Lost ratio
1092
1146
  this.statsResults[mediaType][sendrecvType].currentPacketLossRatio =
1093
1147
  currentPacketsLost > 0
@@ -1151,6 +1205,9 @@ export class StatsAnalyzer extends EventsScope {
1151
1205
  this.statsResults[mediaType][sendrecvType].totalSamplesDecoded =
1152
1206
  result.totalSamplesDecoded || 0;
1153
1207
  this.statsResults[mediaType][sendrecvType].concealedSamples = result.concealedSamples || 0;
1208
+ this.statsResults[mediaType][sendrecvType].isRequested = result.isRequested;
1209
+ this.statsResults[mediaType][sendrecvType].lastRequestedUpdateTimestamp =
1210
+ result.lastRequestedUpdateTimestamp;
1154
1211
  }
1155
1212
  }
1156
1213
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  import {mean, max} from 'lodash';
4
4
 
5
- import {STATS} from '../constants';
5
+ import {MQA_INTERVAL, STATS} from '../constants';
6
6
 
7
7
  /**
8
8
  * Get the totals of a certain value from a certain media type.
@@ -28,6 +28,7 @@ export const getAudioReceiverMqa = ({
28
28
  statsResults,
29
29
  lastMqaDataSent,
30
30
  baseMediaType,
31
+ isMultistream,
31
32
  }) => {
32
33
  const sendrecvType = STATS.RECEIVE_DIRECTION;
33
34
 
@@ -52,6 +53,7 @@ export const getAudioReceiverMqa = ({
52
53
  statsResults[Object.keys(statsResults).find((mediaType) => mediaType.includes(baseMediaType))]
53
54
  ?.direction || 'inactive';
54
55
  audioReceiver.common.common.isMain = !baseMediaType.includes('-share');
56
+ audioReceiver.common.common.multistreamEnabled = isMultistream;
55
57
  audioReceiver.common.transportType = statsResults.connectionType.local.transport;
56
58
 
57
59
  // add rtpPacket info inside common as also for call analyzer
@@ -67,7 +69,9 @@ export const getAudioReceiverMqa = ({
67
69
  totalFecPacketsReceived -
68
70
  lastFecPacketsReceived -
69
71
  (totalFecPacketsDiscarded - lastFecPacketsDiscarded);
70
- audioReceiver.common.fecPackets = fecRecovered;
72
+ audioReceiver.common.fecPackets = totalFecPacketsReceived - lastFecPacketsReceived;
73
+
74
+ audioReceiver.common.rtpRecovered = fecRecovered;
71
75
 
72
76
  audioReceiver.common.rtpBitrate = ((totalBytesReceived - lastBytesReceived) * 8) / 60 || 0;
73
77
  };
@@ -127,7 +131,13 @@ export const getAudioReceiverStreamMqa = ({
127
131
  ((statsResults[mediaType][sendrecvType].totalBytesReceived - lastBytesReceived) * 8) / 60 || 0;
128
132
  };
129
133
 
130
- export const getAudioSenderMqa = ({audioSender, statsResults, lastMqaDataSent, baseMediaType}) => {
134
+ export const getAudioSenderMqa = ({
135
+ audioSender,
136
+ statsResults,
137
+ lastMqaDataSent,
138
+ baseMediaType,
139
+ isMultistream,
140
+ }) => {
131
141
  const sendrecvType = STATS.SEND_DIRECTION;
132
142
 
133
143
  const getLastTotalValue = (value: string) =>
@@ -152,6 +162,7 @@ export const getAudioSenderMqa = ({audioSender, statsResults, lastMqaDataSent, b
152
162
  statsResults[Object.keys(statsResults).find((mediaType) => mediaType.includes(baseMediaType))]
153
163
  ?.direction || 'inactive';
154
164
  audioSender.common.common.isMain = !baseMediaType.includes('-share');
165
+ audioSender.common.common.multistreamEnabled = isMultistream;
155
166
  audioSender.common.transportType = statsResults.connectionType.local.transport;
156
167
 
157
168
  audioSender.common.maxRemoteJitter = max(meanRemoteJitter) * 1000 || 0;
@@ -166,8 +177,8 @@ export const getAudioSenderMqa = ({audioSender, statsResults, lastMqaDataSent, b
166
177
  baseMediaType,
167
178
  'availableOutgoingBitrate'
168
179
  );
169
- // Calculate based on how much packets lost of received compated to how to the client sent
170
180
 
181
+ // Calculate based on how much packets lost of received compated to how to the client sent
171
182
  const totalPacketsLostForaMin = totalPacketsLostOnReceiver - lastPacketsLostTotal;
172
183
  audioSender.common.remoteLossRate =
173
184
  totalPacketsSent - lastPacketsSent > 0
@@ -225,6 +236,7 @@ export const getVideoReceiverMqa = ({
225
236
  statsResults,
226
237
  lastMqaDataSent,
227
238
  baseMediaType,
239
+ isMultistream,
228
240
  }) => {
229
241
  const sendrecvType = STATS.RECEIVE_DIRECTION;
230
242
 
@@ -254,6 +266,7 @@ export const getVideoReceiverMqa = ({
254
266
  videoReceiver.common.common.direction =
255
267
  statsResults[Object.keys(statsResults).find((mediaType) => mediaType.includes(baseMediaType))]
256
268
  ?.direction || 'inactive';
269
+ videoReceiver.common.common.multistreamEnabled = isMultistream;
257
270
  videoReceiver.common.common.isMain = !baseMediaType.includes('-share');
258
271
  videoReceiver.common.transportType = statsResults.connectionType.local.transport;
259
272
 
@@ -347,9 +360,23 @@ export const getVideoReceiverStreamMqa = ({
347
360
  statsResults[mediaType][sendrecvType].keyFramesDecoded - lastKeyFramesDecoded || 0;
348
361
  videoReceiverStream.requestedKeyFrames =
349
362
  statsResults[mediaType][sendrecvType].totalPliCount - lastPliCount || 0;
363
+
364
+ videoReceiverStream.isActiveSpeaker =
365
+ statsResults[mediaType][sendrecvType].isActiveSpeaker ||
366
+ ((statsResults[mediaType][sendrecvType].lastActiveSpeakerTimestamp ?? 0) > 0 &&
367
+ performance.now() +
368
+ performance.timeOrigin -
369
+ (statsResults[mediaType][sendrecvType].lastActiveSpeakerTimestamp ?? 0) <
370
+ MQA_INTERVAL);
350
371
  };
351
372
 
352
- export const getVideoSenderMqa = ({videoSender, statsResults, lastMqaDataSent, baseMediaType}) => {
373
+ export const getVideoSenderMqa = ({
374
+ videoSender,
375
+ statsResults,
376
+ lastMqaDataSent,
377
+ baseMediaType,
378
+ isMultistream,
379
+ }) => {
353
380
  const sendrecvType = STATS.SEND_DIRECTION;
354
381
 
355
382
  const getLastTotalValue = (value: string) =>
@@ -373,6 +400,7 @@ export const getVideoSenderMqa = ({videoSender, statsResults, lastMqaDataSent, b
373
400
  videoSender.common.common.direction =
374
401
  statsResults[Object.keys(statsResults).find((mediaType) => mediaType.includes(baseMediaType))]
375
402
  ?.direction || 'inactive';
403
+ videoSender.common.common.multistreamEnabled = isMultistream;
376
404
  videoSender.common.common.isMain = !baseMediaType.includes('-share');
377
405
  videoSender.common.transportType = statsResults.connectionType.local.transport;
378
406
 
@@ -461,3 +489,23 @@ export const getVideoSenderStreamMqa = ({
461
489
  videoSenderStream.requestedFrameSize =
462
490
  statsResults[mediaType][sendrecvType].requestedFrameSize || 0;
463
491
  };
492
+
493
+ /**
494
+ * Checks if stream stats should be updated based on request status and elapsed time.
495
+ *
496
+ * @param {Object} statsResults - Stats results object.
497
+ * @param {string} mediaType - Media type (e.g., 'audio', 'video').
498
+ * @param {string} direction - Stats direction (e.g., 'send', 'receive').
499
+ * @returns {boolean} Whether stats should be updated.
500
+ */
501
+ export const isStreamRequested = (
502
+ statsResults: any,
503
+ mediaType: string,
504
+ direction: string
505
+ ): boolean => {
506
+ const now = performance.timeOrigin + performance.now();
507
+ const lastUpdateTimestamp = statsResults[mediaType][direction]?.lastRequestedUpdateTimestamp;
508
+ const isRequested = statsResults[mediaType][direction]?.isRequested;
509
+
510
+ return isRequested || (lastUpdateTimestamp && now - lastUpdateTimestamp < MQA_INTERVAL);
511
+ };