@webex/plugin-meetings 3.9.0 → 3.10.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 (117) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +8 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/controls-options-manager/index.js +22 -5
  6. package/dist/controls-options-manager/index.js.map +1 -1
  7. package/dist/index.js +2 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/interceptors/index.js +7 -0
  10. package/dist/interceptors/index.js.map +1 -1
  11. package/dist/interceptors/locusRouteToken.js +116 -0
  12. package/dist/interceptors/locusRouteToken.js.map +1 -0
  13. package/dist/interpretation/index.js +1 -1
  14. package/dist/interpretation/siLanguage.js +1 -1
  15. package/dist/locus-info/controlsUtils.js +11 -2
  16. package/dist/locus-info/controlsUtils.js.map +1 -1
  17. package/dist/locus-info/index.js +56 -14
  18. package/dist/locus-info/index.js.map +1 -1
  19. package/dist/locus-info/parser.js +4 -1
  20. package/dist/locus-info/parser.js.map +1 -1
  21. package/dist/media/properties.js +53 -5
  22. package/dist/media/properties.js.map +1 -1
  23. package/dist/meeting/in-meeting-actions.js +8 -0
  24. package/dist/meeting/in-meeting-actions.js.map +1 -1
  25. package/dist/meeting/index.js +339 -185
  26. package/dist/meeting/index.js.map +1 -1
  27. package/dist/meeting/muteState.js +2 -5
  28. package/dist/meeting/muteState.js.map +1 -1
  29. package/dist/meeting/request.js +177 -14
  30. package/dist/meeting/request.js.map +1 -1
  31. package/dist/meeting/util.js +39 -11
  32. package/dist/meeting/util.js.map +1 -1
  33. package/dist/meeting-info/meeting-info-v2.js +29 -21
  34. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  35. package/dist/meetings/index.js +31 -25
  36. package/dist/meetings/index.js.map +1 -1
  37. package/dist/member/index.js +9 -0
  38. package/dist/member/index.js.map +1 -1
  39. package/dist/member/types.js.map +1 -1
  40. package/dist/member/util.js +10 -0
  41. package/dist/member/util.js.map +1 -1
  42. package/dist/members/collection.js +13 -0
  43. package/dist/members/collection.js.map +1 -1
  44. package/dist/members/index.js +42 -20
  45. package/dist/members/index.js.map +1 -1
  46. package/dist/members/util.js +7 -2
  47. package/dist/members/util.js.map +1 -1
  48. package/dist/metrics/constants.js +2 -1
  49. package/dist/metrics/constants.js.map +1 -1
  50. package/dist/reachability/index.js +3 -3
  51. package/dist/reachability/index.js.map +1 -1
  52. package/dist/types/constants.d.ts +7 -0
  53. package/dist/types/controls-options-manager/index.d.ts +9 -1
  54. package/dist/types/interceptors/index.d.ts +2 -1
  55. package/dist/types/interceptors/locusRouteToken.d.ts +38 -0
  56. package/dist/types/locus-info/index.d.ts +56 -2
  57. package/dist/types/media/properties.d.ts +21 -0
  58. package/dist/types/meeting/in-meeting-actions.d.ts +8 -0
  59. package/dist/types/meeting/index.d.ts +41 -1
  60. package/dist/types/meeting/request.d.ts +42 -0
  61. package/dist/types/meeting/util.d.ts +13 -3
  62. package/dist/types/meeting-info/meeting-info-v2.d.ts +6 -3
  63. package/dist/types/meetings/index.d.ts +3 -1
  64. package/dist/types/member/index.d.ts +1 -0
  65. package/dist/types/member/types.d.ts +1 -0
  66. package/dist/types/member/util.d.ts +5 -0
  67. package/dist/types/members/collection.d.ts +6 -0
  68. package/dist/types/members/index.d.ts +12 -2
  69. package/dist/types/members/util.d.ts +6 -3
  70. package/dist/types/metrics/constants.d.ts +1 -0
  71. package/dist/webinar/index.js +1 -1
  72. package/package.json +23 -23
  73. package/src/constants.ts +10 -0
  74. package/src/controls-options-manager/index.ts +26 -5
  75. package/src/index.ts +2 -1
  76. package/src/interceptors/index.ts +2 -1
  77. package/src/interceptors/locusRouteToken.ts +80 -0
  78. package/src/locus-info/controlsUtils.ts +18 -0
  79. package/src/locus-info/index.ts +99 -17
  80. package/src/locus-info/parser.ts +5 -1
  81. package/src/media/properties.ts +43 -0
  82. package/src/meeting/in-meeting-actions.ts +16 -0
  83. package/src/meeting/index.ts +204 -24
  84. package/src/meeting/muteState.ts +2 -6
  85. package/src/meeting/request.ts +141 -0
  86. package/src/meeting/util.ts +50 -20
  87. package/src/meeting-info/meeting-info-v2.ts +24 -5
  88. package/src/meetings/index.ts +9 -3
  89. package/src/member/index.ts +10 -0
  90. package/src/member/types.ts +1 -0
  91. package/src/member/util.ts +14 -0
  92. package/src/members/collection.ts +11 -0
  93. package/src/members/index.ts +38 -5
  94. package/src/members/util.ts +18 -2
  95. package/src/metrics/constants.ts +1 -0
  96. package/src/reachability/index.ts +3 -3
  97. package/test/unit/spec/common/browser-detection.js +0 -24
  98. package/test/unit/spec/controls-options-manager/index.js +47 -0
  99. package/test/unit/spec/fixture/locus.js +1 -0
  100. package/test/unit/spec/interceptors/locusRouteToken.ts +87 -0
  101. package/test/unit/spec/locus-info/index.js +91 -15
  102. package/test/unit/spec/locus-info/parser.js +3 -2
  103. package/test/unit/spec/media/properties.ts +137 -0
  104. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -0
  105. package/test/unit/spec/meeting/index.js +398 -30
  106. package/test/unit/spec/meeting/muteState.js +32 -6
  107. package/test/unit/spec/meeting/request.js +21 -0
  108. package/test/unit/spec/meeting/utils.js +49 -17
  109. package/test/unit/spec/meeting-info/meetinginfov2.js +8 -3
  110. package/test/unit/spec/meetings/index.js +10 -5
  111. package/test/unit/spec/member/util.js +24 -0
  112. package/test/unit/spec/members/collection.js +120 -0
  113. package/test/unit/spec/members/index.js +72 -3
  114. package/test/unit/spec/members/request.js +55 -0
  115. package/test/unit/spec/members/utils.js +116 -14
  116. package/test/unit/spec/reachability/index.ts +158 -3
  117. package/test/unit/spec/roap/turnDiscovery.ts +3 -3
@@ -772,7 +772,7 @@ describe('plugin-meetings', () => {
772
772
  },
773
773
  };
774
774
  locusInfo.emitScoped = sinon.stub();
775
- locusInfo.updateParticipants({});
775
+ locusInfo.updateParticipants({}, []);
776
776
 
777
777
  // if this assertion fails, double-check the attributes used in
778
778
  // the updateParticipants function in locus-info/index.js
@@ -790,6 +790,7 @@ describe('plugin-meetings', () => {
790
790
  selfId: '2',
791
791
  hostId: '3',
792
792
  isReplace: undefined,
793
+ removedParticipantIds: [],
793
794
  }
794
795
  );
795
796
  // note: in a real use case, recordingId, selfId, and hostId would all be the same
@@ -814,7 +815,7 @@ describe('plugin-meetings', () => {
814
815
  };
815
816
 
816
817
  locusInfo.emitScoped = sinon.stub();
817
- locusInfo.updateParticipants({}, true);
818
+ locusInfo.updateParticipants({}, [], true);
818
819
 
819
820
  assert.calledWith(
820
821
  locusInfo.emitScoped,
@@ -830,6 +831,7 @@ describe('plugin-meetings', () => {
830
831
  selfId: '2',
831
832
  hostId: '3',
832
833
  isReplace: true,
834
+ removedParticipantIds: [],
833
835
  }
834
836
  );
835
837
  });
@@ -847,7 +849,7 @@ describe('plugin-meetings', () => {
847
849
  ];
848
850
 
849
851
  locusInfo.emitScoped = sinon.stub();
850
- locusInfo.updateParticipants(failureParticipant);
852
+ locusInfo.updateParticipants(failureParticipant, []);
851
853
  assert.calledWith(
852
854
  locusInfo.emitScoped,
853
855
  {
@@ -1641,6 +1643,28 @@ describe('plugin-meetings', () => {
1641
1643
  );
1642
1644
  });
1643
1645
 
1646
+ it('should trigger MEETING_INFO_UPDATED even if the roles array is empty', () => {
1647
+ const initialInfo = cloneDeep(meetingInfo);
1648
+
1649
+ const updateSelf = cloneDeep(self);
1650
+ updateSelf.controls.role.roles = [];
1651
+
1652
+ locusInfo.emitScoped = sinon.stub();
1653
+ locusInfo.updateMeetingInfo(initialInfo, updateSelf);
1654
+
1655
+ assert.calledWith(
1656
+ locusInfo.emitScoped,
1657
+ {
1658
+ file: 'locus-info',
1659
+ function: 'updateMeetingInfo',
1660
+ },
1661
+ LOCUSINFO.EVENTS.MEETING_INFO_UPDATED,
1662
+ {
1663
+ isInitializing: !self,
1664
+ }
1665
+ );
1666
+ });
1667
+
1644
1668
  const checkMeetingInfoUpdatedCalled = (expected, payload) => {
1645
1669
  const expectedArgs = [
1646
1670
  locusInfo.emitScoped,
@@ -2016,6 +2040,18 @@ describe('plugin-meetings', () => {
2016
2040
  });
2017
2041
  });
2018
2042
 
2043
+ describe('#handleLocusAPIResponse', () => {
2044
+ it('calls handleLocusDelta', () => {
2045
+ const fakeLocus = {eventType: LOCUSEVENT.DIFFERENCE};
2046
+
2047
+ sinon.stub(locusInfo, 'handleLocusDelta');
2048
+
2049
+ locusInfo.handleLocusAPIResponse(mockMeeting, {locus: fakeLocus});
2050
+
2051
+ assert.calledWith(locusInfo.handleLocusDelta, fakeLocus, mockMeeting);
2052
+ });
2053
+ });
2054
+
2019
2055
  describe('#LocusDeltaEvents', () => {
2020
2056
  const fakeMeeting = 'fakeMeeting';
2021
2057
  let sandbox = null;
@@ -2028,7 +2064,7 @@ describe('plugin-meetings', () => {
2028
2064
 
2029
2065
  fakeLocus = {
2030
2066
  meeting: true,
2031
- participants: true,
2067
+ participants: [],
2032
2068
  url: 'newLocusUrl',
2033
2069
  syncUrl: 'newSyncUrl',
2034
2070
  };
@@ -2347,23 +2383,23 @@ describe('plugin-meetings', () => {
2347
2383
 
2348
2384
  it('applyLocusDeltaData handles LOCUS_URL_CHANGED action correctly', () => {
2349
2385
  const {LOCUS_URL_CHANGED} = LocusDeltaParser.loci;
2350
- const fakeDeltaLocus = {id: 'fake delta locus'};
2386
+ const fakeFullLocus = {
2387
+ url: 'new full loci url',
2388
+ };
2351
2389
  const meeting = {
2352
2390
  meetingRequest: {
2353
- getLocusDTO: sandbox.stub().resolves({body: fakeDeltaLocus}),
2391
+ getLocusDTO: sandbox.stub().resolves({body: fakeFullLocus}),
2354
2392
  },
2355
2393
  locusInfo: {
2356
2394
  handleLocusDelta: sandbox.stub(),
2357
2395
  },
2358
- locusUrl: 'current locus url',
2396
+ locusUrl: 'current BO session locus url',
2359
2397
  };
2360
2398
 
2361
- locusInfo.locusParser.workingCopy = {
2362
- syncUrl: 'current sync url',
2363
- };
2399
+ locusInfo.locusParser.workingCopy = null;
2364
2400
 
2365
2401
  locusInfo.applyLocusDeltaData(LOCUS_URL_CHANGED, fakeLocus, meeting);
2366
- assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'current sync url'});
2402
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: fakeLocus.url});
2367
2403
  });
2368
2404
 
2369
2405
  describe('edge cases for sync failing', () => {
@@ -2503,7 +2539,7 @@ describe('plugin-meetings', () => {
2503
2539
  });
2504
2540
 
2505
2541
  it('onDeltaLocus handle delta data', () => {
2506
- fakeLocus.participants = {};
2542
+ fakeLocus.participants = [];
2507
2543
  const fakeBreakout = {
2508
2544
  sessionId: 'sessionId',
2509
2545
  groupId: 'groupId',
@@ -2520,11 +2556,11 @@ describe('plugin-meetings', () => {
2520
2556
  };
2521
2557
  locusInfo.updateParticipants = sinon.stub();
2522
2558
  locusInfo.onDeltaLocus(fakeLocus);
2523
- assert.calledWith(locusInfo.updateParticipants, {}, false);
2559
+ assert.calledWith(locusInfo.updateParticipants, [], undefined, false);
2524
2560
 
2525
2561
  fakeLocus.controls.breakout.sessionId = 'sessionId2';
2526
2562
  locusInfo.onDeltaLocus(fakeLocus);
2527
- assert.calledWith(locusInfo.updateParticipants, {}, true);
2563
+ assert.calledWith(locusInfo.updateParticipants, [], undefined, true);
2528
2564
  });
2529
2565
 
2530
2566
  it('onDeltaLocus merges delta participants with existing participants', () => {
@@ -2541,7 +2577,7 @@ describe('plugin-meetings', () => {
2541
2577
  existingParticipants,
2542
2578
  FAKE_DELTA_PARTICIPANTS
2543
2579
  );
2544
- assert.calledWith(locusInfo.updateParticipants, FAKE_DELTA_PARTICIPANTS, false);
2580
+ assert.calledWith(locusInfo.updateParticipants, FAKE_DELTA_PARTICIPANTS, undefined, false);
2545
2581
  });
2546
2582
 
2547
2583
  [true, false].forEach((isDelta) =>
@@ -2984,6 +3020,45 @@ describe('plugin-meetings', () => {
2984
3020
  });
2985
3021
  });
2986
3022
 
3023
+ describe('#updateLocusUrl', () => {
3024
+ it('trigger LOCUS_INFO_UPDATE_URL event with isMainLocus is true as default', () => {
3025
+ const fakeUrl = "https://fake.com/locus";
3026
+ locusInfo.emitScoped = sinon.stub();
3027
+ locusInfo.updateLocusUrl(fakeUrl);
3028
+
3029
+ assert.calledWith(
3030
+ locusInfo.emitScoped,
3031
+ {
3032
+ file: 'locus-info',
3033
+ function: 'updateLocusUrl',
3034
+ },
3035
+ EVENTS.LOCUS_INFO_UPDATE_URL,
3036
+ {
3037
+ url: fakeUrl,
3038
+ isMainLocus: true
3039
+ },
3040
+ );
3041
+ });
3042
+ it('trigger LOCUS_INFO_UPDATE_URL event with isMainLocus is false', () => {
3043
+ const fakeUrl = "https://fake.com/locus";
3044
+ locusInfo.emitScoped = sinon.stub();
3045
+ locusInfo.updateLocusUrl(fakeUrl, false);
3046
+
3047
+ assert.calledWith(
3048
+ locusInfo.emitScoped,
3049
+ {
3050
+ file: 'locus-info',
3051
+ function: 'updateLocusUrl',
3052
+ },
3053
+ EVENTS.LOCUS_INFO_UPDATE_URL,
3054
+ {
3055
+ url: fakeUrl,
3056
+ isMainLocus: false
3057
+ },
3058
+ );
3059
+ });
3060
+ });
3061
+
2987
3062
  // semi-integration tests that use real LocusInfo with real Parser
2988
3063
  // and test various scenarios related to handling out-of-order Locus delta events
2989
3064
  describe('handling of out-of-order Locus delta events', () => {
@@ -3052,6 +3127,7 @@ describe('plugin-meetings', () => {
3052
3127
  id: 'test person id',
3053
3128
  },
3054
3129
  },
3130
+ participants: [],
3055
3131
  });
3056
3132
 
3057
3133
  updateLocusInfoStub.resetHistory();
@@ -253,7 +253,8 @@ describe('locus-info/parser', () => {
253
253
  });
254
254
 
255
255
  it('replaces current loci when the locus URL changes and incoming sequence is later, even when baseSequence doesn\'t match', () => {
256
- const {USE_INCOMING} = LocusDeltaParser.loci;
256
+ const {LOCUS_URL_CHANGED} = LocusDeltaParser.loci;
257
+ sandbox.stub(LocusDeltaParser, 'compare').returns(LOCUS_URL_CHANGED);
257
258
 
258
259
  parser.queue.dequeue = sandbox.stub().returns(NEW_LOCI);
259
260
  parser.onDeltaAction = sandbox.stub();
@@ -262,7 +263,7 @@ describe('locus-info/parser', () => {
262
263
 
263
264
  parser.processDeltaEvent();
264
265
 
265
- assert.equal(parser.workingCopy, NEW_LOCI);
266
+ assert.equal(parser.workingCopy, null);
266
267
  });
267
268
 
268
269
  it('does not replace current loci when the locus URL changes but incoming sequence is not later', () => {
@@ -6,6 +6,8 @@ import * as tsSdpModule from '@webex/ts-sdp';
6
6
  import MediaProperties from '@webex/plugin-meetings/src/media/properties';
7
7
  import {Defer} from '@webex/common';
8
8
  import MediaConnectionAwaiter from '../../../../src/media/MediaConnectionAwaiter';
9
+ import Metrics from '../../../../src/metrics';
10
+ import BEHAVIORAL_METRICS from '../../../../src/metrics/constants';
9
11
 
10
12
  describe('MediaProperties', () => {
11
13
  let mediaProperties;
@@ -389,4 +391,139 @@ describe('MediaProperties', () => {
389
391
  });
390
392
  });
391
393
  });
394
+
395
+ // issue types and subtypes used in these tests are just examples
396
+ // they don't reflect real issue types/subtypes used in production
397
+ describe('sendMediaIssueMetric', () => {
398
+ let sendBehavioralMetricStub;
399
+ let clock;
400
+
401
+ beforeEach(() => {
402
+ clock = sinon.useFakeTimers();
403
+ sendBehavioralMetricStub = sinon.stub(Metrics, 'sendBehavioralMetric');
404
+ });
405
+
406
+ afterEach(() => {
407
+ clock.restore();
408
+ });
409
+
410
+ it('should send a behavioral metric with correct parameters', () => {
411
+ const issueType = 'audio';
412
+ const issueSubType = 'packet-loss';
413
+ const correlationId = 'test-correlation-id-123';
414
+
415
+ mediaProperties.sendMediaIssueMetric(issueType, issueSubType, correlationId);
416
+
417
+ assert.calledOnce(sendBehavioralMetricStub);
418
+ assert.calledWith(sendBehavioralMetricStub, BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED, {
419
+ correlationId,
420
+ 'audio_packet-loss': 1,
421
+ });
422
+ });
423
+
424
+ it('should increment count while being throttled and reset it once metric goes out', () => {
425
+ const issueType = 'video';
426
+ const issueSubType = 'freeze';
427
+ const correlationId = 'test-correlation-id';
428
+
429
+ // Call multiple times with same issue type/subtype
430
+ mediaProperties.sendMediaIssueMetric(issueType, issueSubType, correlationId);
431
+ mediaProperties.sendMediaIssueMetric(issueType, issueSubType, correlationId);
432
+ mediaProperties.sendMediaIssueMetric(issueType, issueSubType, correlationId);
433
+
434
+ // First call should go through immediately, subsequent calls are throttled
435
+ assert.calledOnce(sendBehavioralMetricStub);
436
+ assert.calledWith(sendBehavioralMetricStub, BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED, {
437
+ correlationId,
438
+ video_freeze: 1, // Only the first call goes through due to throttling
439
+ });
440
+ sendBehavioralMetricStub.resetHistory();
441
+
442
+ assert.equal(mediaProperties.mediaIssueCounters['video_freeze'], 2); // counter should be reset after the first metric goes out, hence only 2 not 3 here
443
+
444
+ clock.tick(5 * 60 * 1000); // Advance time by 5 minutes to expire throttle
445
+
446
+ assert.calledOnceWithExactly(
447
+ sendBehavioralMetricStub,
448
+ BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED,
449
+ {
450
+ correlationId,
451
+ video_freeze: 2,
452
+ }
453
+ );
454
+ });
455
+
456
+ it('should track different issue types separately in counters', () => {
457
+ const correlationId = 'test-correlation-id';
458
+
459
+ // Send different issue types
460
+ mediaProperties.sendMediaIssueMetric('audio', 'packet-loss', correlationId);
461
+ mediaProperties.sendMediaIssueMetric('video', 'freeze', correlationId);
462
+ mediaProperties.sendMediaIssueMetric('audio', 'packet-loss', correlationId);
463
+ mediaProperties.sendMediaIssueMetric('audio', 'packet-loss', correlationId);
464
+ mediaProperties.sendMediaIssueMetric('audio', 'packet-loss', correlationId);
465
+ mediaProperties.sendMediaIssueMetric('video', 'freeze', correlationId);
466
+
467
+ // First call should go through immediately, subsequent calls are throttled
468
+ assert.calledOnceWithExactly(
469
+ sendBehavioralMetricStub,
470
+ BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED,
471
+ {
472
+ correlationId,
473
+ 'audio_packet-loss': 1,
474
+ }
475
+ );
476
+
477
+ // But the counters should be tracked separately
478
+ assert.equal(mediaProperties.mediaIssueCounters['audio_packet-loss'], 3);
479
+ assert.equal(mediaProperties.mediaIssueCounters['video_freeze'], 2);
480
+
481
+ sendBehavioralMetricStub.resetHistory();
482
+
483
+ clock.tick(5 * 60 * 1000); // Advance time by 5 minutes to expire throttle
484
+
485
+ assert.calledOnceWithExactly(
486
+ sendBehavioralMetricStub,
487
+ BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED,
488
+ {
489
+ correlationId,
490
+ video_freeze: 2,
491
+ 'audio_packet-loss': 3,
492
+ }
493
+ );
494
+ });
495
+
496
+ it('should flush throttled metrics when unsetPeerConnection is called', () => {
497
+ const issueType = 'share';
498
+ const issueSubType = 'connection-lost';
499
+ const correlationId = 'test-correlation-id';
500
+
501
+ // Send metrics multiple times
502
+ mediaProperties.sendMediaIssueMetric(issueType, issueSubType, correlationId);
503
+ mediaProperties.sendMediaIssueMetric(issueType, issueSubType, correlationId);
504
+
505
+ // First call should go through immediately
506
+ assert.calledOnceWithExactly(
507
+ sendBehavioralMetricStub,
508
+ BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED,
509
+ {
510
+ correlationId,
511
+ 'share_connection-lost': 1,
512
+ }
513
+ );
514
+ sendBehavioralMetricStub.resetHistory();
515
+
516
+ // Call unsetPeerConnection which should flush throttled metrics
517
+ mediaProperties.unsetPeerConnection();
518
+
519
+ assert.calledOnceWithExactly(
520
+ sendBehavioralMetricStub,
521
+ BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED,
522
+ {
523
+ correlationId,
524
+ 'share_connection-lost': 1,
525
+ }
526
+ );
527
+ });
528
+ });
392
529
  });
@@ -35,9 +35,12 @@ describe('plugin-meetings', () => {
35
35
  isLocalRecordingStarted: null,
36
36
  isLocalRecordingStopped: null,
37
37
  isLocalRecordingPaused: null,
38
+ isLocalStreamingStarted:null,
39
+ isLocalStreamingStopped:null,
38
40
  isManualCaptionActive: null,
39
41
  isPremiseRecordingEnabled: null,
40
42
  isSaveTranscriptsEnabled: null,
43
+ isSpokenLanguageAutoDetectionEnabled: null,
41
44
  isWebexAssistantActive: null,
42
45
  canViewCaptionPanel: null,
43
46
  isRealTimeTranslationEnabled: null,
@@ -85,6 +88,7 @@ describe('plugin-meetings', () => {
85
88
  canDoVideo: null,
86
89
  canAnnotate: null,
87
90
  canUseVoip: null,
91
+ showAutoEndMeetingWarning: null,
88
92
  supportHQV: null,
89
93
  supportHDV: null,
90
94
  canShareWhiteBoard: null,
@@ -135,6 +139,8 @@ describe('plugin-meetings', () => {
135
139
  'isLocalRecordingStarted',
136
140
  'isLocalRecordingStopped',
137
141
  'isLocalRecordingPaused',
142
+ 'isLocalStreamingStarted',
143
+ 'isLocalStreamingStopped',
138
144
  'canSetMuteOnEntry',
139
145
  'canUnsetMuteOnEntry',
140
146
  'canSetDisallowUnmute',
@@ -151,6 +157,7 @@ describe('plugin-meetings', () => {
151
157
  'isManualCaptionActive',
152
158
  'isPremiseRecordingEnabled',
153
159
  'isSaveTranscriptsEnabled',
160
+ 'isSpokenLanguageAutoDetectionEnabled',
154
161
  'isWebexAssistantActive',
155
162
  'canViewCaptionPanel',
156
163
  'isRealTimeTranslationEnabled',
@@ -195,6 +202,7 @@ describe('plugin-meetings', () => {
195
202
  'canRealtimeCloseCaption',
196
203
  'canRealtimeCloseCaptionManual',
197
204
  'canChat',
205
+ 'showAutoEndMeetingWarning',
198
206
  'canDoVideo',
199
207
  'canAnnotate',
200
208
  'canUseVoip',