@webex/plugin-meetings 3.11.0 → 3.12.0-next.10

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 (176) hide show
  1. package/dist/aiEnableRequest/index.js +184 -0
  2. package/dist/aiEnableRequest/index.js.map +1 -0
  3. package/dist/aiEnableRequest/utils.js +36 -0
  4. package/dist/aiEnableRequest/utils.js.map +1 -0
  5. package/dist/annotation/index.js +14 -5
  6. package/dist/annotation/index.js.map +1 -1
  7. package/dist/breakouts/breakout.js +1 -1
  8. package/dist/breakouts/index.js +1 -1
  9. package/dist/config.js +7 -2
  10. package/dist/config.js.map +1 -1
  11. package/dist/constants.js +28 -6
  12. package/dist/constants.js.map +1 -1
  13. package/dist/hashTree/constants.js +13 -2
  14. package/dist/hashTree/constants.js.map +1 -1
  15. package/dist/hashTree/hashTree.js +18 -0
  16. package/dist/hashTree/hashTree.js.map +1 -1
  17. package/dist/hashTree/hashTreeParser.js +869 -420
  18. package/dist/hashTree/hashTreeParser.js.map +1 -1
  19. package/dist/hashTree/types.js +4 -2
  20. package/dist/hashTree/types.js.map +1 -1
  21. package/dist/hashTree/utils.js +32 -0
  22. package/dist/hashTree/utils.js.map +1 -1
  23. package/dist/index.js +11 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/interceptors/constant.js +12 -0
  26. package/dist/interceptors/constant.js.map +1 -0
  27. package/dist/interceptors/dataChannelAuthToken.js +290 -0
  28. package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
  29. package/dist/interceptors/index.js +7 -0
  30. package/dist/interceptors/index.js.map +1 -1
  31. package/dist/interceptors/utils.js +27 -0
  32. package/dist/interceptors/utils.js.map +1 -0
  33. package/dist/interpretation/index.js +2 -2
  34. package/dist/interpretation/index.js.map +1 -1
  35. package/dist/interpretation/siLanguage.js +1 -1
  36. package/dist/locus-info/controlsUtils.js +5 -3
  37. package/dist/locus-info/controlsUtils.js.map +1 -1
  38. package/dist/locus-info/index.js +522 -131
  39. package/dist/locus-info/index.js.map +1 -1
  40. package/dist/locus-info/selfUtils.js +1 -0
  41. package/dist/locus-info/selfUtils.js.map +1 -1
  42. package/dist/locus-info/types.js.map +1 -1
  43. package/dist/media/MediaConnectionAwaiter.js +57 -1
  44. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  45. package/dist/media/properties.js +4 -2
  46. package/dist/media/properties.js.map +1 -1
  47. package/dist/meeting/in-meeting-actions.js +7 -1
  48. package/dist/meeting/in-meeting-actions.js.map +1 -1
  49. package/dist/meeting/index.js +1304 -928
  50. package/dist/meeting/index.js.map +1 -1
  51. package/dist/meeting/request.js +50 -0
  52. package/dist/meeting/request.js.map +1 -1
  53. package/dist/meeting/request.type.js.map +1 -1
  54. package/dist/meeting/util.js +133 -3
  55. package/dist/meeting/util.js.map +1 -1
  56. package/dist/meetings/index.js +117 -48
  57. package/dist/meetings/index.js.map +1 -1
  58. package/dist/member/index.js +10 -0
  59. package/dist/member/index.js.map +1 -1
  60. package/dist/member/util.js +10 -0
  61. package/dist/member/util.js.map +1 -1
  62. package/dist/metrics/constants.js +6 -1
  63. package/dist/metrics/constants.js.map +1 -1
  64. package/dist/multistream/mediaRequestManager.js +9 -60
  65. package/dist/multistream/mediaRequestManager.js.map +1 -1
  66. package/dist/multistream/remoteMediaManager.js +11 -0
  67. package/dist/multistream/remoteMediaManager.js.map +1 -1
  68. package/dist/multistream/sendSlotManager.js +116 -2
  69. package/dist/multistream/sendSlotManager.js.map +1 -1
  70. package/dist/reachability/index.js +18 -10
  71. package/dist/reachability/index.js.map +1 -1
  72. package/dist/reactions/reactions.type.js.map +1 -1
  73. package/dist/reconnection-manager/index.js +0 -1
  74. package/dist/reconnection-manager/index.js.map +1 -1
  75. package/dist/types/aiEnableRequest/index.d.ts +5 -0
  76. package/dist/types/aiEnableRequest/utils.d.ts +2 -0
  77. package/dist/types/config.d.ts +4 -0
  78. package/dist/types/constants.d.ts +23 -1
  79. package/dist/types/hashTree/constants.d.ts +2 -0
  80. package/dist/types/hashTree/hashTree.d.ts +7 -0
  81. package/dist/types/hashTree/hashTreeParser.d.ts +122 -14
  82. package/dist/types/hashTree/types.d.ts +3 -0
  83. package/dist/types/hashTree/utils.d.ts +17 -0
  84. package/dist/types/index.d.ts +1 -0
  85. package/dist/types/interceptors/constant.d.ts +5 -0
  86. package/dist/types/interceptors/dataChannelAuthToken.d.ts +43 -0
  87. package/dist/types/interceptors/index.d.ts +2 -1
  88. package/dist/types/interceptors/utils.d.ts +1 -0
  89. package/dist/types/locus-info/index.d.ts +60 -8
  90. package/dist/types/locus-info/types.d.ts +7 -0
  91. package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
  92. package/dist/types/media/properties.d.ts +2 -1
  93. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  94. package/dist/types/meeting/index.d.ts +72 -7
  95. package/dist/types/meeting/request.d.ts +16 -1
  96. package/dist/types/meeting/request.type.d.ts +5 -0
  97. package/dist/types/meeting/util.d.ts +31 -0
  98. package/dist/types/meetings/index.d.ts +4 -2
  99. package/dist/types/member/index.d.ts +1 -0
  100. package/dist/types/member/util.d.ts +5 -0
  101. package/dist/types/metrics/constants.d.ts +5 -0
  102. package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
  103. package/dist/types/multistream/sendSlotManager.d.ts +23 -1
  104. package/dist/types/reactions/reactions.type.d.ts +1 -0
  105. package/dist/types/webinar/utils.d.ts +6 -0
  106. package/dist/webinar/index.js +438 -163
  107. package/dist/webinar/index.js.map +1 -1
  108. package/dist/webinar/utils.js +25 -0
  109. package/dist/webinar/utils.js.map +1 -0
  110. package/package.json +24 -23
  111. package/src/aiEnableRequest/README.md +84 -0
  112. package/src/aiEnableRequest/index.ts +170 -0
  113. package/src/aiEnableRequest/utils.ts +25 -0
  114. package/src/annotation/index.ts +27 -7
  115. package/src/config.ts +4 -0
  116. package/src/constants.ts +29 -1
  117. package/src/hashTree/constants.ts +10 -0
  118. package/src/hashTree/hashTree.ts +17 -0
  119. package/src/hashTree/hashTreeParser.ts +764 -264
  120. package/src/hashTree/types.ts +4 -0
  121. package/src/hashTree/utils.ts +26 -0
  122. package/src/index.ts +8 -1
  123. package/src/interceptors/constant.ts +6 -0
  124. package/src/interceptors/dataChannelAuthToken.ts +170 -0
  125. package/src/interceptors/index.ts +2 -1
  126. package/src/interceptors/utils.ts +16 -0
  127. package/src/interpretation/index.ts +2 -2
  128. package/src/locus-info/controlsUtils.ts +11 -0
  129. package/src/locus-info/index.ts +579 -113
  130. package/src/locus-info/selfUtils.ts +1 -0
  131. package/src/locus-info/types.ts +8 -0
  132. package/src/media/MediaConnectionAwaiter.ts +41 -1
  133. package/src/media/properties.ts +3 -1
  134. package/src/meeting/in-meeting-actions.ts +12 -0
  135. package/src/meeting/index.ts +389 -87
  136. package/src/meeting/request.ts +42 -0
  137. package/src/meeting/request.type.ts +6 -0
  138. package/src/meeting/util.ts +160 -2
  139. package/src/meetings/index.ts +157 -44
  140. package/src/member/index.ts +10 -0
  141. package/src/member/util.ts +12 -0
  142. package/src/metrics/constants.ts +6 -0
  143. package/src/multistream/mediaRequestManager.ts +4 -54
  144. package/src/multistream/remoteMediaManager.ts +13 -0
  145. package/src/multistream/sendSlotManager.ts +97 -3
  146. package/src/reachability/index.ts +9 -0
  147. package/src/reactions/reactions.type.ts +1 -0
  148. package/src/reconnection-manager/index.ts +0 -1
  149. package/src/webinar/index.ts +265 -6
  150. package/src/webinar/utils.ts +16 -0
  151. package/test/unit/spec/aiEnableRequest/index.ts +981 -0
  152. package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
  153. package/test/unit/spec/annotation/index.ts +69 -7
  154. package/test/unit/spec/hashTree/hashTree.ts +66 -0
  155. package/test/unit/spec/hashTree/hashTreeParser.ts +2469 -195
  156. package/test/unit/spec/hashTree/utils.ts +88 -1
  157. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +210 -0
  158. package/test/unit/spec/interceptors/utils.ts +75 -0
  159. package/test/unit/spec/locus-info/controlsUtils.js +29 -0
  160. package/test/unit/spec/locus-info/index.js +1134 -55
  161. package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
  162. package/test/unit/spec/media/properties.ts +12 -3
  163. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
  164. package/test/unit/spec/meeting/index.js +884 -152
  165. package/test/unit/spec/meeting/request.js +70 -0
  166. package/test/unit/spec/meeting/utils.js +438 -26
  167. package/test/unit/spec/meetings/index.js +653 -32
  168. package/test/unit/spec/member/index.js +28 -4
  169. package/test/unit/spec/member/util.js +65 -27
  170. package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
  171. package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
  172. package/test/unit/spec/multistream/sendSlotManager.ts +135 -36
  173. package/test/unit/spec/reachability/index.ts +23 -0
  174. package/test/unit/spec/reconnection-manager/index.js +4 -8
  175. package/test/unit/spec/webinar/index.ts +534 -37
  176. package/test/unit/spec/webinar/utils.ts +39 -0
@@ -13,7 +13,7 @@ import {
13
13
  CALL_DIAGNOSTIC_CONFIG,
14
14
  RtcMetrics,
15
15
  } from '@webex/internal-plugin-metrics';
16
- import {ClientEvent as RawClientEvent} from '@webex/event-dictionary-ts';
16
+ import type {ClientEvent as RawClientEvent} from '@webex/event-dictionary-ts';
17
17
 
18
18
  import {
19
19
  ConnectionState,
@@ -22,6 +22,7 @@ import {
22
22
  MediaConnectionEventNames,
23
23
  MediaContent,
24
24
  MediaType,
25
+ MediaCodecMimeType,
25
26
  RemoteTrackType,
26
27
  RoapMessage,
27
28
  StatsAnalyzer,
@@ -33,6 +34,8 @@ import {
33
34
  InboundAudioIssueSubTypes,
34
35
  } from '@webex/internal-media-core';
35
36
 
37
+ import {DataChannelTokenType} from '@webex/internal-plugin-llm';
38
+
36
39
  import {
37
40
  LocalStream,
38
41
  LocalCameraStream,
@@ -55,6 +58,7 @@ import {
55
58
  isBrowserMediaError,
56
59
  isBrowserMediaErrorName,
57
60
  } from '@webex/internal-plugin-metrics/src/call-diagnostic/call-diagnostic-metrics.util';
61
+ import {CapabilityState, WebCapabilities} from '@webex/web-capabilities';
58
62
  import {processNewCaptions} from './voicea-meeting';
59
63
 
60
64
  import {
@@ -178,8 +182,9 @@ import JoinForbiddenError from '../common/errors/join-forbidden-error';
178
182
  import {ReachabilityMetrics} from '../reachability/reachability.types';
179
183
  import {SetStageOptions, SetStageVideoLayout, UnsetStageVideoLayout} from './request.type';
180
184
  import {Invitee} from './type';
181
- import {DataSet} from '../hashTree/hashTreeParser';
185
+ import {DataSet, HashTreeMessage, Metadata} from '../hashTree/hashTreeParser';
182
186
  import {LocusDTO} from '../locus-info/types';
187
+ import AIEnableRequest from '../aiEnableRequest';
183
188
 
184
189
  // default callback so we don't call an undefined function, but in practice it should never be used
185
190
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -249,6 +254,7 @@ export type AddMediaOptions = {
249
254
  remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
250
255
  bundlePolicy?: BundlePolicy; // applies only to multistream meetings
251
256
  allowMediaInLobby?: boolean; // allows adding media when in the lobby
257
+ allowPublishMediaInLobby?: boolean; // allows publishing media when in the lobby, if not specified, default value false is used
252
258
  additionalMediaOptions?: AdditionalMediaOptions; // allows adding additional options like send/receive audio/video
253
259
  };
254
260
 
@@ -563,6 +569,34 @@ type MediaReachabilityMetrics = ReachabilityMetrics & {
563
569
  * @memberof Meeting
564
570
  */
565
571
 
572
+ /**
573
+ * Stores an event so all events can be later retrieved via a console command for debugging.
574
+ * @param {string} type
575
+ * @param {Object} data
576
+ * @returns {void}
577
+ */
578
+ export function storeEventForDebugging(
579
+ type: string,
580
+ data: {
581
+ eventType: any;
582
+ stateElementsMessage?: HashTreeMessage;
583
+ }
584
+ ) {
585
+ if ((window as any)?.locusEvents) {
586
+ // only store non-heartbeat hash tree messages
587
+ if (
588
+ data.eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED &&
589
+ data.stateElementsMessage?.locusStateElements
590
+ ) {
591
+ (window as any).locusEvents.push({
592
+ ...data,
593
+ timestamp: new Date().toLocaleString(),
594
+ type,
595
+ });
596
+ }
597
+ }
598
+ }
599
+
566
600
  /**
567
601
  * @description Meeting is the crux of the plugin
568
602
  * @export
@@ -574,6 +608,7 @@ export default class Meeting extends StatelessWebexPlugin {
574
608
  breakouts: any;
575
609
  simultaneousInterpretation: any;
576
610
  annotation: any;
611
+ aiEnableRequest: any;
577
612
  webinar: any;
578
613
  conversationUrl: string;
579
614
  callStateForMetrics: CallStateForMetrics;
@@ -622,6 +657,13 @@ export default class Meeting extends StatelessWebexPlugin {
622
657
  keepAliveTimerId: NodeJS.Timeout;
623
658
  lastVideoLayoutInfo: any;
624
659
  locusInfo: any;
660
+ // this group of properties is populated via updateMeetingObject() that's registered as a callback with LocusInfo
661
+ isUserUnadmitted?: boolean;
662
+ joinedWith?: any;
663
+ selfId?: string;
664
+ roles: any[];
665
+ // ... there is more ... see SelfUtils.parse()
666
+ // end of the group
625
667
  locusMediaRequest?: LocusMediaRequest;
626
668
  mediaProperties: MediaProperties;
627
669
  mediaRequestManagers: {
@@ -656,7 +698,6 @@ export default class Meeting extends StatelessWebexPlugin {
656
698
  endCallInitJoinReq: any;
657
699
  endJoinReqResp: any;
658
700
  endLocalSDPGenRemoteSDPRecvDelay: any;
659
- joinedWith: any;
660
701
  locusId: any;
661
702
  startCallInitJoinReq: any;
662
703
  startJoinReqResp: any;
@@ -671,12 +712,11 @@ export default class Meeting extends StatelessWebexPlugin {
671
712
  permissionTokenReceivedLocalTime: number;
672
713
  resourceId: any;
673
714
  resourceUrl: string;
674
- selfId: string;
675
715
  state: any;
676
716
  localAudioStreamMuteStateHandler: () => void;
677
717
  localVideoStreamMuteStateHandler: () => void;
678
718
  localOutputTrackChangeHandler: () => void;
679
- roles: any[];
719
+ localConstraintsChangeHandler: () => void;
680
720
  environment: string;
681
721
  namespace = MEETINGS;
682
722
  allowMediaInLobby: boolean;
@@ -893,6 +933,10 @@ export default class Meeting extends StatelessWebexPlugin {
893
933
  */
894
934
  // @ts-ignore
895
935
  this.simultaneousInterpretation = new SimultaneousInterpretation({}, {parent: this.webex});
936
+
937
+ // @ts-ignore
938
+ this.aiEnableRequest = new AIEnableRequest({}, {parent: this.webex});
939
+
896
940
  /**
897
941
  * @instance
898
942
  * @type {Annotation}
@@ -1542,6 +1586,12 @@ export default class Meeting extends StatelessWebexPlugin {
1542
1586
  }
1543
1587
  };
1544
1588
 
1589
+ this.localConstraintsChangeHandler = () => {
1590
+ if (!this.isMultistream) {
1591
+ this.mediaProperties.webrtcMediaConnection?.updatePreferredBitrateKbps();
1592
+ }
1593
+ };
1594
+
1545
1595
  /**
1546
1596
  * Promise that exists if SDP offer has been generated, and resolves once sdp answer is received.
1547
1597
  * @instance
@@ -2951,6 +3001,18 @@ export default class Meeting extends StatelessWebexPlugin {
2951
3001
  );
2952
3002
  });
2953
3003
 
3004
+ this.locusInfo.on(
3005
+ LOCUSINFO.EVENTS.CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
3006
+ ({aiSummaryNotification}) => {
3007
+ Trigger.trigger(
3008
+ this,
3009
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
3010
+ EVENT_TRIGGERS.MEETING_CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
3011
+ {aiSummaryNotification}
3012
+ );
3013
+ }
3014
+ );
3015
+
2954
3016
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_WEBCAST_CHANGED, ({state}) => {
2955
3017
  Trigger.trigger(
2956
3018
  this,
@@ -3402,6 +3464,8 @@ export default class Meeting extends StatelessWebexPlugin {
3402
3464
  this.recordingController.setLocusUrl(this.locusUrl);
3403
3465
  this.controlsOptionsManager.setLocusUrl(this.locusUrl, !!isMainLocus);
3404
3466
  this.webinar.locusUrlUpdate(url);
3467
+ // @ts-ignore
3468
+ this.webex.internal.llm.setRefreshHandler(() => this.refreshDataChannelToken());
3405
3469
 
3406
3470
  Trigger.trigger(
3407
3471
  this,
@@ -3432,6 +3496,7 @@ export default class Meeting extends StatelessWebexPlugin {
3432
3496
  this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
3433
3497
  this.annotation.approvalUrlUpdate(payload?.services?.approval?.url);
3434
3498
  this.simultaneousInterpretation.approvalUrlUpdate(payload?.services?.approval?.url);
3499
+ this.aiEnableRequest.approvalUrlUpdate(payload?.services?.approval?.url);
3435
3500
  });
3436
3501
  }
3437
3502
 
@@ -3529,6 +3594,9 @@ export default class Meeting extends StatelessWebexPlugin {
3529
3594
  // @ts-ignore - config coming from registerPlugin
3530
3595
  if (datachannelUrl && this.config.enableAutomaticLLM) {
3531
3596
  this.updateLLMConnection();
3597
+ if (this.webinar.isJoinPracticeSessionDataChannel()) {
3598
+ this.webinar.updatePSDataChannel();
3599
+ }
3532
3600
  }
3533
3601
  }
3534
3602
 
@@ -3666,7 +3734,7 @@ export default class Meeting extends StatelessWebexPlugin {
3666
3734
  });
3667
3735
  this.updateLLMConnection();
3668
3736
  });
3669
- this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, async (payload) => {
3737
+ this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, (payload) => {
3670
3738
  this.stopKeepAlive();
3671
3739
 
3672
3740
  if (payload) {
@@ -3692,6 +3760,15 @@ export default class Meeting extends StatelessWebexPlugin {
3692
3760
  });
3693
3761
  }
3694
3762
  this.rtcMetrics?.sendNextMetrics();
3763
+
3764
+ this.ensureDefaultDatachannelTokenAfterAdmit().catch((error) => {
3765
+ LoggerProxy.logger.warn(
3766
+ `Meeting:index#setUpLocusInfoSelfListener --> failed post-admit token prefetch flow: ${
3767
+ error?.message || String(error)
3768
+ }`
3769
+ );
3770
+ });
3771
+
3695
3772
  this.updateLLMConnection();
3696
3773
  });
3697
3774
 
@@ -3761,6 +3838,10 @@ export default class Meeting extends StatelessWebexPlugin {
3761
3838
  );
3762
3839
  });
3763
3840
 
3841
+ this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ID_CHANGED, (payload) => {
3842
+ this.aiEnableRequest.selfParticipantIdUpdate(payload.selfId);
3843
+ });
3844
+
3764
3845
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED, (payload) => {
3765
3846
  const targetChanged = this.simultaneousInterpretation.updateSelfInterpretation(payload);
3766
3847
  Trigger.trigger(
@@ -4263,6 +4344,9 @@ export default class Meeting extends StatelessWebexPlugin {
4263
4344
  bothLeaveAndEndMeetingAvailable: MeetingUtil.bothLeaveAndEndMeetingAvailable(
4264
4345
  this.userDisplayHints
4265
4346
  ),
4347
+ requireHostEndMeetingBeforeLeave: MeetingUtil.requireHostEndMeetingBeforeLeave(
4348
+ this.userDisplayHints
4349
+ ),
4266
4350
  canEnableClosedCaption: MeetingUtil.canEnableClosedCaption(this.userDisplayHints),
4267
4351
  canStartTranscribing: MeetingUtil.canStartTranscribing(this.userDisplayHints),
4268
4352
  canStopTranscribing: MeetingUtil.canStopTranscribing(this.userDisplayHints),
@@ -4521,6 +4605,12 @@ export default class Meeting extends StatelessWebexPlugin {
4521
4605
  requiredHints: [DISPLAY_HINTS.DISABLE_ATTENDEE_START_POLLING_QA],
4522
4606
  displayHints: this.userDisplayHints,
4523
4607
  }),
4608
+ canAttendeeRequestAiAssistantEnabled: MeetingUtil.canAttendeeRequestAiAssistantEnabled(
4609
+ this.userDisplayHints,
4610
+ this.roles
4611
+ ),
4612
+ isAttendeeRequestAiAssistantDeclinedAll:
4613
+ MeetingUtil.attendeeRequestAiAssistantDeclinedAll(this.userDisplayHints),
4524
4614
  }) || changed;
4525
4615
  }
4526
4616
  if (changed) {
@@ -4592,7 +4682,8 @@ export default class Meeting extends StatelessWebexPlugin {
4592
4682
  mediaId: string;
4593
4683
  host: object;
4594
4684
  selfId: string;
4595
- dataSets: DataSet[];
4685
+ dataSets: DataSet[]; // only sent by Locus when hash trees are used
4686
+ metadata: Metadata; // only sent by Locus when hash trees are used
4596
4687
  }) {
4597
4688
  const mtgLocus: any = data.locus;
4598
4689
 
@@ -4608,6 +4699,7 @@ export default class Meeting extends StatelessWebexPlugin {
4608
4699
  trigger: 'join-response',
4609
4700
  locus: mtgLocus,
4610
4701
  dataSets: data.dataSets,
4702
+ metadata: data.metadata,
4611
4703
  });
4612
4704
  }
4613
4705
 
@@ -4817,6 +4909,7 @@ export default class Meeting extends StatelessWebexPlugin {
4817
4909
  this.localVideoStreamMuteStateHandler
4818
4910
  );
4819
4911
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4912
+ oldStream?.off(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4820
4913
 
4821
4914
  // we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
4822
4915
  this.mediaProperties.setLocalVideoStream(localStream);
@@ -4832,6 +4925,7 @@ export default class Meeting extends StatelessWebexPlugin {
4832
4925
  this.localVideoStreamMuteStateHandler
4833
4926
  );
4834
4927
  localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4928
+ localStream?.on(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4835
4929
 
4836
4930
  if (!this.isMultistream || !localStream) {
4837
4931
  // for multistream WCME automatically un-publishes the old stream when we publish a new one
@@ -4966,6 +5060,7 @@ export default class Meeting extends StatelessWebexPlugin {
4966
5060
  this.localVideoStreamMuteStateHandler
4967
5061
  );
4968
5062
  videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
5063
+ videoStream?.off(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4969
5064
 
4970
5065
  shareAudioStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
4971
5066
  shareAudioStream?.off(
@@ -5379,6 +5474,18 @@ export default class Meeting extends StatelessWebexPlugin {
5379
5474
  let joined = false;
5380
5475
  let joinResponse = prevJoinResponse;
5381
5476
 
5477
+ /* Before we do anything, check if RTCPeerConnection is available. Normally this is checked
5478
+ by addMediaInternal() itself when creating the media connection, but since joinWithMedia()
5479
+ is a convenience method that does both join() and addMedia(), we want to fail fast here
5480
+ in case WebRTC is not available at all.
5481
+ */
5482
+ if (WebCapabilities.supportsRTCPeerConnection() === CapabilityState.NOT_CAPABLE) {
5483
+ // throw the same error that would be thrown by addMediaInternal()
5484
+ throw new Errors.WebrtcApiNotAvailableError(
5485
+ 'RTCPeerConnection API is not available in this environment'
5486
+ );
5487
+ }
5488
+
5382
5489
  try {
5383
5490
  let turnServerInfo;
5384
5491
  let turnDiscoverySkippedReason;
@@ -5449,7 +5556,10 @@ export default class Meeting extends StatelessWebexPlugin {
5449
5556
  // if this was the first attempt, let's do a retry
5450
5557
  let shouldRetry = !isRetry;
5451
5558
 
5452
- if (CallDiagnosticUtils.isSdpOfferCreationError(error)) {
5559
+ if (
5560
+ CallDiagnosticUtils.isSdpOfferCreationError(error) ||
5561
+ CallDiagnosticUtils.isWebrtcApiNotAvailableError(error)
5562
+ ) {
5453
5563
  // errors related to offer creation (for example missing H264 codec) will happen again no matter how many times we try,
5454
5564
  // so there is no point doing a retry
5455
5565
  shouldRetry = false;
@@ -5742,6 +5852,11 @@ export default class Meeting extends StatelessWebexPlugin {
5742
5852
  */
5743
5853
  private processLocusLLMEvent = (event: LocusLLMEvent): void => {
5744
5854
  if (event.data.eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
5855
+ // @ts-ignore
5856
+ if (this.config.experimental.storeLocusHashTreeEventsForDebugging) {
5857
+ storeEventForDebugging('llm', event.data);
5858
+ }
5859
+
5745
5860
  this.locusInfo.parse(this, event.data);
5746
5861
  } else {
5747
5862
  LoggerProxy.logger.warn(
@@ -5765,7 +5880,7 @@ export default class Meeting extends StatelessWebexPlugin {
5765
5880
  this.isReactionsSupported()
5766
5881
  ) {
5767
5882
  const member = this.members.membersCollection.get(e.data.sender.participantId);
5768
- if (!member) {
5883
+ if (!member && !this.locusInfo?.info?.isWebinar) {
5769
5884
  // @ts-ignore -- fix type
5770
5885
  LoggerProxy.logger.warn(
5771
5886
  `Meeting:index#processRelayEvent --> Skipping handling of ${REACTION_RELAY_TYPES.REACTION} for ${this.id}. participantId ${e.data.sender.participantId} does not exist in membersCollection.`
@@ -5773,7 +5888,7 @@ export default class Meeting extends StatelessWebexPlugin {
5773
5888
  break;
5774
5889
  }
5775
5890
 
5776
- const {name} = member;
5891
+ const name = (member && member.name) || e.data.sender.displayName;
5777
5892
  const processedReaction: ProcessedReaction = {
5778
5893
  reaction: e.data.reaction,
5779
5894
  sender: {
@@ -5802,37 +5917,35 @@ export default class Meeting extends StatelessWebexPlugin {
5802
5917
  * @returns {void}
5803
5918
  */
5804
5919
  stopTranscription() {
5805
- if (this.transcription) {
5806
- // @ts-ignore
5807
- this.webex.internal.voicea.off(
5808
- VOICEAEVENTS.VOICEA_ANNOUNCEMENT,
5809
- this.voiceaListenerCallbacks[VOICEAEVENTS.VOICEA_ANNOUNCEMENT]
5810
- );
5920
+ // @ts-ignore
5921
+ this.webex.internal.voicea.off(
5922
+ VOICEAEVENTS.VOICEA_ANNOUNCEMENT,
5923
+ this.voiceaListenerCallbacks[VOICEAEVENTS.VOICEA_ANNOUNCEMENT]
5924
+ );
5811
5925
 
5812
- // @ts-ignore
5813
- this.webex.internal.voicea.off(
5814
- VOICEAEVENTS.CAPTIONS_TURNED_ON,
5815
- this.voiceaListenerCallbacks[VOICEAEVENTS.CAPTIONS_TURNED_ON]
5816
- );
5926
+ // @ts-ignore
5927
+ this.webex.internal.voicea.off(
5928
+ VOICEAEVENTS.CAPTIONS_TURNED_ON,
5929
+ this.voiceaListenerCallbacks[VOICEAEVENTS.CAPTIONS_TURNED_ON]
5930
+ );
5817
5931
 
5818
- // @ts-ignore
5819
- this.webex.internal.voicea.off(
5820
- VOICEAEVENTS.EVA_COMMAND,
5821
- this.voiceaListenerCallbacks[VOICEAEVENTS.EVA_COMMAND]
5822
- );
5932
+ // @ts-ignore
5933
+ this.webex.internal.voicea.off(
5934
+ VOICEAEVENTS.EVA_COMMAND,
5935
+ this.voiceaListenerCallbacks[VOICEAEVENTS.EVA_COMMAND]
5936
+ );
5823
5937
 
5824
- // @ts-ignore
5825
- this.webex.internal.voicea.off(
5826
- VOICEAEVENTS.NEW_CAPTION,
5827
- this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
5828
- );
5938
+ // @ts-ignore
5939
+ this.webex.internal.voicea.off(
5940
+ VOICEAEVENTS.NEW_CAPTION,
5941
+ this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
5942
+ );
5829
5943
 
5830
- // @ts-ignore
5831
- this.webex.internal.voicea.deregisterEvents();
5944
+ // @ts-ignore
5945
+ this.webex.internal.voicea.deregisterEvents();
5832
5946
 
5833
- this.areVoiceaEventsSetup = false;
5834
- this.triggerStopReceivingTranscriptionEvent();
5835
- }
5947
+ this.areVoiceaEventsSetup = false;
5948
+ this.triggerStopReceivingTranscriptionEvent();
5836
5949
  }
5837
5950
 
5838
5951
  /**
@@ -5856,6 +5969,30 @@ export default class Meeting extends StatelessWebexPlugin {
5856
5969
  );
5857
5970
  }
5858
5971
 
5972
+ /**
5973
+ * Restores LLM subchannel subscriptions after reconnect when captions are active.
5974
+ * @returns {void}
5975
+ */
5976
+ private restoreLLMSubscriptionsIfNeeded(): void {
5977
+ try {
5978
+ // @ts-ignore
5979
+ const isCaptionBoxOn = this.webex.internal.voicea?.getIsCaptionBoxOn?.();
5980
+
5981
+ if (!isCaptionBoxOn) {
5982
+ return;
5983
+ }
5984
+
5985
+ // @ts-ignore
5986
+ this.webex.internal.voicea.updateSubchannelSubscriptions({subscribe: ['transcription']});
5987
+ } catch (error) {
5988
+ const msg = error?.message || String(error);
5989
+
5990
+ LoggerProxy.logger.warn(
5991
+ `Meeting:index#restoreLLMSubscriptionsIfNeeded --> failed to restore subscriptions after LLM online: ${msg}`
5992
+ );
5993
+ }
5994
+ }
5995
+
5859
5996
  /**
5860
5997
  * This is a callback for the LLM event that is triggered when it comes online
5861
5998
  * This method in turn will trigger an event to the developers that the LLM is connected
@@ -5864,8 +6001,8 @@ export default class Meeting extends StatelessWebexPlugin {
5864
6001
  * @returns {null}
5865
6002
  */
5866
6003
  private handleLLMOnline = (): void => {
5867
- // @ts-ignore
5868
- this.webex.internal.llm.off('online', this.handleLLMOnline);
6004
+ this.restoreLLMSubscriptionsIfNeeded();
6005
+
5869
6006
  Trigger.trigger(
5870
6007
  this,
5871
6008
  {
@@ -6093,8 +6230,11 @@ export default class Meeting extends StatelessWebexPlugin {
6093
6230
  return Promise.reject(error);
6094
6231
  })
6095
6232
  .then((join) => {
6233
+ this.saveDataChannelToken(join);
6096
6234
  // @ts-ignore - config coming from registerPlugin
6097
6235
  if (this.config.enableAutomaticLLM) {
6236
+ // @ts-ignore
6237
+ this.webex.internal.llm.off('online', this.handleLLMOnline);
6098
6238
  // @ts-ignore
6099
6239
  this.webex.internal.llm.on('online', this.handleLLMOnline);
6100
6240
  this.updateLLMConnection()
@@ -6161,23 +6301,146 @@ export default class Meeting extends StatelessWebexPlugin {
6161
6301
  }
6162
6302
  }
6163
6303
 
6304
+ /**
6305
+ * Disconnects and cleans up the default LLM session listeners/timers.
6306
+ * @param {Object} options
6307
+ * @param {boolean} [options.removeOnlineListener=true] removes the one-time online listener
6308
+ * @param {boolean} [options.throwOnError=true] rethrows disconnect errors when true
6309
+ * @returns {Promise<void>}
6310
+ */
6311
+ private cleanupLLMConneciton = async ({
6312
+ removeOnlineListener = true,
6313
+ throwOnError = true,
6314
+ }: {
6315
+ removeOnlineListener?: boolean;
6316
+ throwOnError?: boolean;
6317
+ } = {}): Promise<void> => {
6318
+ try {
6319
+ // @ts-ignore - Fix type
6320
+ await this.webex.internal.llm.disconnectLLM({
6321
+ code: 3050,
6322
+ reason: 'done (permanent)',
6323
+ });
6324
+ } catch (error) {
6325
+ LoggerProxy.logger.error(
6326
+ 'Meeting:index#cleanupLLMConneciton --> Failed to disconnect default LLM session',
6327
+ error
6328
+ );
6329
+
6330
+ if (throwOnError) {
6331
+ throw error;
6332
+ }
6333
+ } finally {
6334
+ if (removeOnlineListener) {
6335
+ // @ts-ignore - Fix type
6336
+ this.webex.internal.llm.off('online', this.handleLLMOnline);
6337
+ }
6338
+ // @ts-ignore - fix types
6339
+ this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
6340
+ // @ts-ignore - Fix type
6341
+ this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
6342
+
6343
+ this.clearLLMHealthCheckTimer();
6344
+ }
6345
+ };
6346
+
6347
+ /**
6348
+ * Clears all data channel tokens stored in LLM.
6349
+ * Called during meeting cleanup to ensure stale tokens are not reused.
6350
+ * @returns {void}
6351
+ */
6352
+ clearDataChannelToken(): void {
6353
+ // @ts-ignore
6354
+ this.webex.internal.llm.resetDatachannelTokens();
6355
+ }
6356
+
6357
+ /**
6358
+ * Saves the data channel tokens from the join response into LLM so that
6359
+ * updateLLMConnection / updatePSDataChannel don't need to fetch them from locusInfo.
6360
+ * @param {Object} join - The parsed join response (from MeetingUtil.parseLocusJoin)
6361
+ * @returns {void}
6362
+ */
6363
+ saveDataChannelToken(join: any): void {
6364
+ const datachannelToken = join?.locus?.self?.datachannelToken;
6365
+ const practiceSessionDatachannelToken = join?.locus?.self?.practiceSessionDatachannelToken;
6366
+
6367
+ if (datachannelToken) {
6368
+ // @ts-ignore
6369
+ this.webex.internal.llm.setDatachannelToken(datachannelToken, DataChannelTokenType.Default);
6370
+ }
6371
+
6372
+ if (practiceSessionDatachannelToken) {
6373
+ // @ts-ignore
6374
+ this.webex.internal.llm.setDatachannelToken(
6375
+ practiceSessionDatachannelToken,
6376
+ DataChannelTokenType.PracticeSession
6377
+ );
6378
+ }
6379
+ }
6380
+
6381
+ /**
6382
+ * Ensures default-session data channel token exists after lobby admission.
6383
+ * Some lobby users do not receive a token until they are admitted.
6384
+ * @returns {Promise<boolean>} true when a new token is fetched and cached
6385
+ */
6386
+ private async ensureDefaultDatachannelTokenAfterAdmit(): Promise<boolean> {
6387
+ try {
6388
+ // @ts-ignore
6389
+ const datachannelToken = this.webex.internal.llm.getDatachannelToken();
6390
+ // @ts-ignore
6391
+ const isDataChannelTokenEnabled = await this.webex.internal.llm.isDataChannelTokenEnabled();
6392
+
6393
+ if (!isDataChannelTokenEnabled || datachannelToken) {
6394
+ return false;
6395
+ }
6396
+
6397
+ const response = await this.meetingRequest.fetchDatachannelToken({
6398
+ locusUrl: this.locusUrl,
6399
+ requestingParticipantId: this.members.selfId,
6400
+ isPracticeSession: false,
6401
+ });
6402
+ const fetchedDatachannelToken = response?.body?.datachannelToken;
6403
+
6404
+ if (!fetchedDatachannelToken) {
6405
+ return false;
6406
+ }
6407
+
6408
+ // @ts-ignore
6409
+ this.webex.internal.llm.setDatachannelToken(
6410
+ fetchedDatachannelToken,
6411
+ DataChannelTokenType.Default
6412
+ );
6413
+
6414
+ return true;
6415
+ } catch (error) {
6416
+ const msg = error?.message || String(error);
6417
+
6418
+ LoggerProxy.logger.warn(
6419
+ `Meeting:index#ensureDefaultDatachannelTokenAfterAdmit --> failed to proactively fetch default data channel token after admit: ${msg}`,
6420
+ {statusCode: error?.statusCode}
6421
+ );
6422
+
6423
+ return false;
6424
+ }
6425
+ }
6426
+
6164
6427
  /**
6165
6428
  * Connects to low latency mercury and reconnects if the address has changed
6166
6429
  * It will also disconnect if called when the meeting has ended
6167
- * @param {String} datachannelUrl
6168
6430
  * @returns {Promise}
6169
6431
  */
6170
6432
  async updateLLMConnection() {
6171
6433
  // @ts-ignore - Fix type
6172
- const {url, info: {datachannelUrl, practiceSessionDatachannelUrl} = {}} = this.locusInfo;
6434
+ const {url = undefined, info: {datachannelUrl = undefined} = {}} = this.locusInfo || {};
6173
6435
 
6174
6436
  const isJoined = this.isJoined();
6175
6437
 
6176
- // webinar panelist should use new data channel in practice session
6177
- const dataChannelUrl =
6178
- this.webinar.isJoinPracticeSessionDataChannel() && practiceSessionDatachannelUrl
6179
- ? practiceSessionDatachannelUrl
6180
- : datachannelUrl;
6438
+ // @ts-ignore
6439
+ const datachannelToken = this.webex.internal.llm.getDatachannelToken(
6440
+ DataChannelTokenType.Default
6441
+ );
6442
+
6443
+ const dataChannelUrl = datachannelUrl;
6181
6444
 
6182
6445
  // @ts-ignore - Fix type
6183
6446
  if (this.webex.internal.llm.isConnected()) {
@@ -6190,21 +6453,7 @@ export default class Meeting extends StatelessWebexPlugin {
6190
6453
  ) {
6191
6454
  return undefined;
6192
6455
  }
6193
- // @ts-ignore - Fix type
6194
- await this.webex.internal.llm.disconnectLLM(
6195
- isJoined
6196
- ? {
6197
- code: 3050,
6198
- reason: 'done (permanent)',
6199
- }
6200
- : undefined
6201
- );
6202
- // @ts-ignore - Fix type
6203
- this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
6204
- // @ts-ignore - Fix type
6205
- this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
6206
-
6207
- this.clearLLMHealthCheckTimer();
6456
+ await this.cleanupLLMConneciton({removeOnlineListener: false});
6208
6457
  }
6209
6458
 
6210
6459
  if (!isJoined) {
@@ -6213,7 +6462,7 @@ export default class Meeting extends StatelessWebexPlugin {
6213
6462
 
6214
6463
  // @ts-ignore - Fix type
6215
6464
  return this.webex.internal.llm
6216
- .registerAndConnect(url, dataChannelUrl)
6465
+ .registerAndConnect(url, dataChannelUrl, datachannelToken)
6217
6466
  .then((registerAndConnectResult) => {
6218
6467
  // @ts-ignore - Fix type
6219
6468
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
@@ -7417,7 +7666,7 @@ export default class Meeting extends StatelessWebexPlugin {
7417
7666
  */
7418
7667
  private async waitForMediaConnectionConnected(): Promise<void> {
7419
7668
  try {
7420
- await this.mediaProperties.waitForMediaConnectionConnected();
7669
+ await this.mediaProperties.waitForMediaConnectionConnected(this.correlationId);
7421
7670
  } catch (error) {
7422
7671
  const {iceConnected} = error;
7423
7672
 
@@ -8010,6 +8259,7 @@ export default class Meeting extends StatelessWebexPlugin {
8010
8259
  remoteMediaManagerConfig,
8011
8260
  bundlePolicy = 'max-bundle',
8012
8261
  additionalMediaOptions = {},
8262
+ allowPublishMediaInLobby = false,
8013
8263
  } = options;
8014
8264
 
8015
8265
  const {
@@ -8030,7 +8280,6 @@ export default class Meeting extends StatelessWebexPlugin {
8030
8280
  const ipver = MeetingUtil.getIpVersion(this.webex); // used just for metrics
8031
8281
 
8032
8282
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
8033
- // @ts-ignore - isUserUnadmitted coming from SelfUtil
8034
8283
  if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
8035
8284
  throw new UserInLobbyError();
8036
8285
  }
@@ -8075,7 +8324,13 @@ export default class Meeting extends StatelessWebexPlugin {
8075
8324
  this.brbState = createBrbState(this, false);
8076
8325
 
8077
8326
  try {
8078
- await this.setUpLocalStreamReferences(localStreams);
8327
+ // if we're in a lobby and allowPublishMediaInLobby==false, we don't want to
8328
+ // setup local streams for publishing, because if we ever end up admitted to the meeting
8329
+ // but Locus event about it for us is delayed or missed, others could see/hear our user's video/audio
8330
+ // while the user would still think they're in the lobby
8331
+ if (allowPublishMediaInLobby || !this.isUserUnadmitted) {
8332
+ await this.setUpLocalStreamReferences(localStreams);
8333
+ }
8079
8334
 
8080
8335
  this.setMercuryListener();
8081
8336
 
@@ -8543,12 +8798,12 @@ export default class Meeting extends StatelessWebexPlugin {
8543
8798
  LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
8544
8799
 
8545
8800
  return MeetingUtil.leaveMeeting(this, options)
8546
- .then((leave) => {
8801
+ .then(async (leave) => {
8547
8802
  // CA team recommends submitting this *after* locus /leave
8548
8803
  submitLeaveMetric();
8549
8804
 
8550
8805
  this.meetingFiniteStateMachine.leave();
8551
- this.clearMeetingData();
8806
+ await this.clearMeetingData();
8552
8807
 
8553
8808
  // upload logs on leave irrespective of meeting delete
8554
8809
  Trigger.trigger(
@@ -9407,10 +9662,10 @@ export default class Meeting extends StatelessWebexPlugin {
9407
9662
  });
9408
9663
 
9409
9664
  return MeetingUtil.endMeetingForAll(this)
9410
- .then((end) => {
9665
+ .then(async (end) => {
9411
9666
  this.meetingFiniteStateMachine.end();
9412
9667
 
9413
- this.clearMeetingData();
9668
+ await this.clearMeetingData();
9414
9669
  // upload logs on leave irrespective of meeting delete
9415
9670
  Trigger.trigger(
9416
9671
  this,
@@ -9458,7 +9713,7 @@ export default class Meeting extends StatelessWebexPlugin {
9458
9713
  * @public
9459
9714
  * @memberof Meeting
9460
9715
  */
9461
- clearMeetingData = () => {
9716
+ clearMeetingData = async () => {
9462
9717
  this.audio = null;
9463
9718
  this.video = null;
9464
9719
  this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
@@ -9467,19 +9722,13 @@ export default class Meeting extends StatelessWebexPlugin {
9467
9722
  }
9468
9723
  this.queuedMediaUpdates = [];
9469
9724
 
9470
- if (this.transcription) {
9471
- this.stopTranscription();
9472
- this.transcription = undefined;
9473
- }
9725
+ this.stopTranscription();
9726
+ this.transcription = undefined;
9474
9727
 
9475
9728
  this.annotation.deregisterEvents();
9476
9729
 
9477
- // @ts-ignore - fix types
9478
- this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
9479
- // @ts-ignore - Fix type
9480
- this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
9481
-
9482
- this.clearLLMHealthCheckTimer();
9730
+ this.clearDataChannelToken();
9731
+ await this.cleanupLLMConneciton({throwOnError: false});
9483
9732
  };
9484
9733
 
9485
9734
  /**
@@ -9672,15 +9921,20 @@ export default class Meeting extends StatelessWebexPlugin {
9672
9921
  }
9673
9922
 
9674
9923
  if (shouldEnableMusicMode) {
9675
- await this.sendSlotManager.setCodecParameters(MediaType.AudioMain, {
9676
- maxaveragebitrate: '64000',
9677
- maxplaybackrate: '48000',
9678
- });
9924
+ await this.sendSlotManager.setCustomCodecParameters(
9925
+ MediaType.AudioMain,
9926
+ MediaCodecMimeType.OPUS,
9927
+ {
9928
+ maxaveragebitrate: '64000',
9929
+ maxplaybackrate: '48000',
9930
+ }
9931
+ );
9679
9932
  } else {
9680
- await this.sendSlotManager.deleteCodecParameters(MediaType.AudioMain, [
9681
- 'maxaveragebitrate',
9682
- 'maxplaybackrate',
9683
- ]);
9933
+ await this.sendSlotManager.markCustomCodecParametersForDeletion(
9934
+ MediaType.AudioMain,
9935
+ MediaCodecMimeType.OPUS,
9936
+ ['maxaveragebitrate', 'maxplaybackrate']
9937
+ );
9684
9938
  }
9685
9939
  }
9686
9940
 
@@ -10179,4 +10433,52 @@ export default class Meeting extends StatelessWebexPlugin {
10179
10433
  cancelSipCallOut(participantId: string) {
10180
10434
  return this.meetingRequest.cancelSipCallOut(participantId);
10181
10435
  }
10436
+
10437
+ /**
10438
+ * Method to get new data
10439
+ * @returns {Promise}
10440
+ */
10441
+ public async refreshDataChannelToken() {
10442
+ const isPracticeSession = this.webinar.isJoinPracticeSessionDataChannel();
10443
+ const dataChannelTokenType = this.getDataChannelTokenType();
10444
+
10445
+ try {
10446
+ const res = await this.meetingRequest.fetchDatachannelToken({
10447
+ locusUrl: this.locusUrl,
10448
+ requestingParticipantId: this.members.selfId,
10449
+ isPracticeSession,
10450
+ });
10451
+
10452
+ return {
10453
+ body: {
10454
+ datachannelToken: res.body.datachannelToken,
10455
+ dataChannelTokenType,
10456
+ },
10457
+ };
10458
+ } catch (e) {
10459
+ const msg = e?.message || String(e);
10460
+
10461
+ LoggerProxy.logger.warn(
10462
+ `Meeting:index#refreshDataChannelToken --> DataChannel token refresh failed (likely locus changed or participant left): ${msg}`,
10463
+ {statusCode: e?.statusCode}
10464
+ );
10465
+
10466
+ return null;
10467
+ }
10468
+ }
10469
+
10470
+ /**
10471
+ * Determines the current data channel token type based on the meeting state.
10472
+ *
10473
+ * variant should be used when connecting to the LLM data channel.
10474
+ *
10475
+ * @returns {DataChannelTokenType} The token type representing the current session mode.
10476
+ */
10477
+ public getDataChannelTokenType(): DataChannelTokenType {
10478
+ if (this.webinar.isJoinPracticeSessionDataChannel()) {
10479
+ return DataChannelTokenType.PracticeSession;
10480
+ }
10481
+
10482
+ return DataChannelTokenType.Default;
10483
+ }
10182
10484
  }