@webex/plugin-meetings 3.9.0-next.19 → 3.9.0-next.20

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.
@@ -39,6 +39,7 @@ import {
39
39
  ConnectionState,
40
40
  MediaConnectionEventNames,
41
41
  StatsAnalyzerEventNames,
42
+ StatsMonitorEventNames,
42
43
  Errors,
43
44
  ErrorType,
44
45
  RemoteTrackType,
@@ -2288,13 +2289,24 @@ describe('plugin-meetings', () => {
2288
2289
  close: sinon.stub(),
2289
2290
  forceRtcMetricsSend,
2290
2291
  });
2291
- // set a statsAnalyzer on the meeting so that we can check that it gets reset to null
2292
+
2293
+ const mockStatsMonitor = {removeAllListeners: sinon.stub()};
2294
+ const mockNetworkQualityMonitor = {removeAllListeners: sinon.stub()};
2295
+
2296
+ // set a statsAnalyzer and statsMonitor on the meeting so that we can check that they get reset to null
2292
2297
  meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
2298
+ meeting.statsMonitor = mockStatsMonitor;
2299
+ meeting.networkQualityMonitor = mockNetworkQualityMonitor;
2293
2300
  const error = await assert.isRejected(meeting.addMedia());
2294
2301
 
2295
2302
  assert.calledOnce(forceRtcMetricsSend);
2303
+ assert.calledOnce(mockStatsMonitor.removeAllListeners);
2304
+ assert.calledOnce(mockNetworkQualityMonitor.removeAllListeners);
2296
2305
 
2297
2306
  assert.isNull(meeting.statsAnalyzer);
2307
+ assert.isNull(meeting.statsMonitor);
2308
+ assert.isNull(meeting.networkQualityMonitor);
2309
+
2298
2310
  assert(webex.internal.newMetrics.submitInternalEvent.calledTwice);
2299
2311
  assert.calledWith(webex.internal.newMetrics.submitInternalEvent.firstCall, {
2300
2312
  name: 'internal.client.add-media.turn-discovery.start',
@@ -2406,12 +2418,23 @@ describe('plugin-meetings', () => {
2406
2418
 
2407
2419
  meeting.waitForRemoteSDPAnswer = sinon.stub().rejects();
2408
2420
 
2409
- // set a statsAnalyzer on the meeting so that we can check that it gets reset to null
2421
+ const mockStatsMonitor = {removeAllListeners: sinon.stub()};
2422
+ const mockNetworkQualityMonitor = {removeAllListeners: sinon.stub()};
2423
+
2424
+ // set a statsAnalyzer and statsMonitor on the meeting so that we can check that they get reset to null
2410
2425
  meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
2426
+ meeting.statsMonitor = mockStatsMonitor;
2427
+ meeting.networkQualityMonitor = mockNetworkQualityMonitor;
2411
2428
 
2412
2429
  const error = await assert.isRejected(meeting.addMedia());
2413
2430
 
2414
2431
  assert.isNull(meeting.statsAnalyzer);
2432
+ assert.isNull(meeting.statsMonitor);
2433
+ assert.isNull(meeting.networkQualityMonitor);
2434
+
2435
+ assert.calledOnce(mockStatsMonitor.removeAllListeners);
2436
+ assert.calledOnce(mockNetworkQualityMonitor.removeAllListeners);
2437
+
2415
2438
  assert(webex.internal.newMetrics.submitInternalEvent.calledTwice);
2416
2439
  assert.calledWith(webex.internal.newMetrics.submitInternalEvent.firstCall, {
2417
2440
  name: 'internal.client.add-media.turn-discovery.start',
@@ -2476,8 +2499,9 @@ describe('plugin-meetings', () => {
2476
2499
  },
2477
2500
  },
2478
2501
  });
2479
- // set a statsAnalyzer on the meeting so that we can check that it gets reset to null
2502
+ // set a statsAnalyzer and statsMonitor on the meeting so that we can check that they get reset to null
2480
2503
  meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
2504
+ meeting.statsMonitor = {removeAllListeners: sinon.stub()};
2481
2505
  const error = await assert.isRejected(meeting.addMedia());
2482
2506
 
2483
2507
  assert(webex.internal.newMetrics.submitInternalEvent.calledTwice);
@@ -2521,6 +2545,7 @@ describe('plugin-meetings', () => {
2521
2545
  );
2522
2546
 
2523
2547
  assert.isNull(meeting.statsAnalyzer);
2548
+ assert.isNull(meeting.statsMonitor);
2524
2549
  });
2525
2550
 
2526
2551
  it('should include the peer connection properties correctly for transcoded', async () => {
@@ -2537,8 +2562,14 @@ describe('plugin-meetings', () => {
2537
2562
  },
2538
2563
  },
2539
2564
  });
2540
- // set a statsAnalyzer on the meeting so that we can check that it gets reset to null
2565
+
2566
+ const mockStatsMonitor = {removeAllListeners: sinon.stub()};
2567
+ const mockNetworkQualityMonitor = {removeAllListeners: sinon.stub()};
2568
+
2569
+ // set a statsAnalyzer and statsMonitor on the meeting so that we can check that they get reset to null
2541
2570
  meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
2571
+ meeting.statsMonitor = mockStatsMonitor;
2572
+ meeting.networkQualityMonitor = mockNetworkQualityMonitor;
2542
2573
  const error = await assert.isRejected(meeting.addMedia());
2543
2574
 
2544
2575
  assert(webex.internal.newMetrics.submitInternalEvent.calledTwice);
@@ -2582,6 +2613,10 @@ describe('plugin-meetings', () => {
2582
2613
  );
2583
2614
 
2584
2615
  assert.isNull(meeting.statsAnalyzer);
2616
+ assert.isNull(meeting.statsMonitor);
2617
+ assert.isNull(meeting.networkQualityMonitor);
2618
+ assert.calledOnce(mockStatsMonitor.removeAllListeners);
2619
+ assert.calledOnce(mockNetworkQualityMonitor.removeAllListeners);
2585
2620
  });
2586
2621
 
2587
2622
  it('should work the second time addMedia is called in case the first time fails', async () => {
@@ -4117,6 +4152,132 @@ describe('plugin-meetings', () => {
4117
4152
  });
4118
4153
  });
4119
4154
 
4155
+ describe('handles StatsMonitor events', () => {
4156
+ let statsMonitorStub;
4157
+ let prevConfigValue;
4158
+ let listeners;
4159
+
4160
+ beforeEach(async () => {
4161
+ meeting.meetingState = 'ACTIVE';
4162
+ prevConfigValue = meeting.config.stats.enableStatsAnalyzer;
4163
+
4164
+ meeting.config.stats.enableStatsAnalyzer = true;
4165
+
4166
+ listeners = {};
4167
+
4168
+ statsMonitorStub = {
4169
+ on: sinon.stub().callsFake((event, callback) => {
4170
+ listeners[event] = callback;
4171
+ }),
4172
+ removeAllListeners: sinon.stub(),
4173
+ };
4174
+
4175
+ sinon.stub(meeting.mediaProperties, 'sendMediaIssueMetric');
4176
+
4177
+ // mock the StatsMonitor constructor
4178
+ sinon.stub(InternalMediaCoreModule, 'StatsMonitor').returns(statsMonitorStub);
4179
+
4180
+ await meeting.addMedia({
4181
+ mediaSettings: {},
4182
+ });
4183
+ });
4184
+
4185
+ afterEach(() => {
4186
+ meeting.config.stats.enableStatsAnalyzer = prevConfigValue;
4187
+ sinon.restore();
4188
+ });
4189
+
4190
+ describe('INBOUND_AUDIO_ISSUE event', () => {
4191
+ it('should not trigger event when no unmuted members exist', () => {
4192
+ const fakeEventData = {issueSubType: 'DECODE_RESULTS_IN_ZERO_AUDIO_LEVEL'};
4193
+
4194
+ // Setup members that are either self or muted
4195
+ const mutedMember = {
4196
+ isSelf: false,
4197
+ isPairedWithSelf: false,
4198
+ isAudioMuted: true,
4199
+ };
4200
+ const selfMember = {
4201
+ isSelf: true,
4202
+ isPairedWithSelf: false,
4203
+ isAudioMuted: false,
4204
+ };
4205
+ const pairedMember = {
4206
+ isSelf: false,
4207
+ isPairedWithSelf: true,
4208
+ isAudioMuted: false,
4209
+ };
4210
+ meeting.members.membersCollection.getAll = sinon.stub().returns({
4211
+ member1: mutedMember,
4212
+ member2: selfMember,
4213
+ member3: pairedMember,
4214
+ });
4215
+
4216
+ // Reset the stub to clear any previous calls
4217
+ TriggerProxy.trigger.resetHistory();
4218
+
4219
+ // Emit the event from statsMonitor
4220
+ listeners[StatsMonitorEventNames.INBOUND_AUDIO_ISSUE](fakeEventData);
4221
+
4222
+ assert.neverCalledWith(
4223
+ TriggerProxy.trigger,
4224
+ meeting,
4225
+ sinon.match.object,
4226
+ EVENT_TRIGGERS.MEDIA_INBOUND_AUDIO_ISSUE_DETECTED,
4227
+ fakeEventData
4228
+ );
4229
+ assert.notCalled(meeting.mediaProperties.sendMediaIssueMetric);
4230
+ });
4231
+
4232
+ it('should trigger event and metric when there are multiple members and at least one is unmuted', () => {
4233
+ const fakeEventData = {issueSubType: 'DECODE_RESULTS_IN_ZERO_AUDIO_LEVEL'};
4234
+
4235
+ // Setup mixed members - some muted, one unmuted
4236
+ const mutedMember = {
4237
+ isSelf: false,
4238
+ isPairedWithSelf: false,
4239
+ isAudioMuted: true,
4240
+ };
4241
+ const unmutedMember = {
4242
+ isSelf: false,
4243
+ isPairedWithSelf: false,
4244
+ isAudioMuted: false,
4245
+ };
4246
+ const selfMember = {
4247
+ isSelf: true,
4248
+ isPairedWithSelf: false,
4249
+ isAudioMuted: false,
4250
+ };
4251
+ meeting.members.membersCollection.getAll = sinon.stub().returns({
4252
+ member1: mutedMember,
4253
+ member2: unmutedMember,
4254
+ member3: selfMember,
4255
+ });
4256
+
4257
+ // Reset the stub to clear any previous calls
4258
+ TriggerProxy.trigger.resetHistory();
4259
+
4260
+ // Emit the event from statsMonitor
4261
+ listeners[StatsMonitorEventNames.INBOUND_AUDIO_ISSUE](fakeEventData);
4262
+
4263
+ assert.calledWith(
4264
+ TriggerProxy.trigger,
4265
+ meeting,
4266
+ sinon.match.object,
4267
+ EVENT_TRIGGERS.MEDIA_INBOUND_AUDIO_ISSUE_DETECTED,
4268
+ fakeEventData
4269
+ );
4270
+
4271
+ assert.calledOnceWithExactly(
4272
+ meeting.mediaProperties.sendMediaIssueMetric,
4273
+ 'inbound_audio',
4274
+ fakeEventData.issueSubType,
4275
+ meeting.correlationId
4276
+ );
4277
+ });
4278
+ });
4279
+ });
4280
+
4120
4281
  describe('bundlePolicy', () => {
4121
4282
  const FAKE_TURN_URL = 'turns:webex.com:3478';
4122
4283
  const FAKE_TURN_USER = 'some-turn-username';
@@ -5583,6 +5744,7 @@ describe('plugin-meetings', () => {
5583
5744
  let multistreamEventListeners;
5584
5745
  let transcodedEventListeners;
5585
5746
  let mockStatsAnalyzerCtor;
5747
+ let statsMonitorStub;
5586
5748
 
5587
5749
  const setupFakeRoapMediaConnection = (fakeRoapMediaConnection, eventListeners) => {
5588
5750
  fakeRoapMediaConnection.on.callsFake((eventName, cb) => {
@@ -5614,6 +5776,14 @@ describe('plugin-meetings', () => {
5614
5776
  return {on: sinon.stub(), stopAnalyzer: sinon.stub()};
5615
5777
  });
5616
5778
 
5779
+ statsMonitorStub = {
5780
+ on: sinon.stub(),
5781
+ removeAllListeners: sinon.stub(),
5782
+ };
5783
+
5784
+ // mock the StatsMonitor constructor
5785
+ sinon.stub(InternalMediaCoreModule, 'StatsMonitor').returns(statsMonitorStub);
5786
+
5617
5787
  webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
5618
5788
  sinon.stub();
5619
5789
 
@@ -5676,6 +5846,7 @@ describe('plugin-meetings', () => {
5676
5846
  mockStatsAnalyzerCtor,
5677
5847
  sinon.match({
5678
5848
  isMultistream: true,
5849
+ statsMonitor: statsMonitorStub,
5679
5850
  })
5680
5851
  );
5681
5852
  const initialStatsAnalyzer = mockStatsAnalyzerCtor.returnValues[0];