@webex/plugin-meetings 3.11.0-webex-services-ready.1 → 3.12.0-mobius-socket.1

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 (171) 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 +3 -1
  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 +868 -419
  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 +10 -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 +1293 -929
  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/reactions/reactions.type.js.map +1 -1
  71. package/dist/reconnection-manager/index.js +0 -1
  72. package/dist/reconnection-manager/index.js.map +1 -1
  73. package/dist/types/aiEnableRequest/index.d.ts +5 -0
  74. package/dist/types/aiEnableRequest/utils.d.ts +2 -0
  75. package/dist/types/config.d.ts +4 -0
  76. package/dist/types/constants.d.ts +23 -1
  77. package/dist/types/hashTree/constants.d.ts +1 -0
  78. package/dist/types/hashTree/hashTree.d.ts +7 -0
  79. package/dist/types/hashTree/hashTreeParser.d.ts +122 -14
  80. package/dist/types/hashTree/types.d.ts +3 -0
  81. package/dist/types/hashTree/utils.d.ts +6 -0
  82. package/dist/types/index.d.ts +1 -0
  83. package/dist/types/interceptors/constant.d.ts +5 -0
  84. package/dist/types/interceptors/dataChannelAuthToken.d.ts +43 -0
  85. package/dist/types/interceptors/index.d.ts +2 -1
  86. package/dist/types/interceptors/utils.d.ts +1 -0
  87. package/dist/types/locus-info/index.d.ts +60 -8
  88. package/dist/types/locus-info/types.d.ts +7 -0
  89. package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
  90. package/dist/types/media/properties.d.ts +2 -1
  91. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  92. package/dist/types/meeting/index.d.ts +72 -7
  93. package/dist/types/meeting/request.d.ts +16 -1
  94. package/dist/types/meeting/request.type.d.ts +5 -0
  95. package/dist/types/meeting/util.d.ts +31 -0
  96. package/dist/types/meetings/index.d.ts +4 -2
  97. package/dist/types/member/index.d.ts +1 -0
  98. package/dist/types/member/util.d.ts +5 -0
  99. package/dist/types/metrics/constants.d.ts +5 -0
  100. package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
  101. package/dist/types/multistream/sendSlotManager.d.ts +23 -1
  102. package/dist/types/reactions/reactions.type.d.ts +1 -0
  103. package/dist/types/webinar/utils.d.ts +6 -0
  104. package/dist/webinar/index.js +438 -163
  105. package/dist/webinar/index.js.map +1 -1
  106. package/dist/webinar/utils.js +25 -0
  107. package/dist/webinar/utils.js.map +1 -0
  108. package/package.json +24 -23
  109. package/src/aiEnableRequest/README.md +84 -0
  110. package/src/aiEnableRequest/index.ts +170 -0
  111. package/src/aiEnableRequest/utils.ts +25 -0
  112. package/src/annotation/index.ts +27 -7
  113. package/src/config.ts +4 -0
  114. package/src/constants.ts +29 -1
  115. package/src/hashTree/constants.ts +1 -0
  116. package/src/hashTree/hashTree.ts +17 -0
  117. package/src/hashTree/hashTreeParser.ts +761 -260
  118. package/src/hashTree/types.ts +4 -0
  119. package/src/hashTree/utils.ts +9 -0
  120. package/src/index.ts +8 -1
  121. package/src/interceptors/constant.ts +6 -0
  122. package/src/interceptors/dataChannelAuthToken.ts +170 -0
  123. package/src/interceptors/index.ts +2 -1
  124. package/src/interceptors/utils.ts +16 -0
  125. package/src/interpretation/index.ts +2 -2
  126. package/src/locus-info/controlsUtils.ts +11 -0
  127. package/src/locus-info/index.ts +579 -113
  128. package/src/locus-info/selfUtils.ts +1 -0
  129. package/src/locus-info/types.ts +8 -0
  130. package/src/media/MediaConnectionAwaiter.ts +41 -1
  131. package/src/media/properties.ts +3 -1
  132. package/src/meeting/in-meeting-actions.ts +12 -0
  133. package/src/meeting/index.ts +372 -86
  134. package/src/meeting/request.ts +42 -0
  135. package/src/meeting/request.type.ts +6 -0
  136. package/src/meeting/util.ts +160 -2
  137. package/src/meetings/index.ts +157 -44
  138. package/src/member/index.ts +10 -0
  139. package/src/member/util.ts +12 -0
  140. package/src/metrics/constants.ts +6 -0
  141. package/src/multistream/mediaRequestManager.ts +4 -54
  142. package/src/multistream/remoteMediaManager.ts +13 -0
  143. package/src/multistream/sendSlotManager.ts +97 -3
  144. package/src/reactions/reactions.type.ts +1 -0
  145. package/src/reconnection-manager/index.ts +0 -1
  146. package/src/webinar/index.ts +265 -6
  147. package/src/webinar/utils.ts +16 -0
  148. package/test/unit/spec/aiEnableRequest/index.ts +981 -0
  149. package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
  150. package/test/unit/spec/annotation/index.ts +69 -7
  151. package/test/unit/spec/hashTree/hashTree.ts +66 -0
  152. package/test/unit/spec/hashTree/hashTreeParser.ts +2321 -175
  153. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +210 -0
  154. package/test/unit/spec/interceptors/utils.ts +75 -0
  155. package/test/unit/spec/locus-info/controlsUtils.js +29 -0
  156. package/test/unit/spec/locus-info/index.js +1134 -55
  157. package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
  158. package/test/unit/spec/media/properties.ts +12 -3
  159. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
  160. package/test/unit/spec/meeting/index.js +829 -121
  161. package/test/unit/spec/meeting/request.js +70 -0
  162. package/test/unit/spec/meeting/utils.js +438 -26
  163. package/test/unit/spec/meetings/index.js +653 -32
  164. package/test/unit/spec/member/index.js +28 -4
  165. package/test/unit/spec/member/util.js +65 -27
  166. package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
  167. package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
  168. package/test/unit/spec/multistream/sendSlotManager.ts +135 -36
  169. package/test/unit/spec/reconnection-manager/index.js +4 -8
  170. package/test/unit/spec/webinar/index.ts +534 -37
  171. 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,
@@ -179,8 +182,9 @@ import JoinForbiddenError from '../common/errors/join-forbidden-error';
179
182
  import {ReachabilityMetrics} from '../reachability/reachability.types';
180
183
  import {SetStageOptions, SetStageVideoLayout, UnsetStageVideoLayout} from './request.type';
181
184
  import {Invitee} from './type';
182
- import {DataSet} from '../hashTree/hashTreeParser';
185
+ import {DataSet, HashTreeMessage, Metadata} from '../hashTree/hashTreeParser';
183
186
  import {LocusDTO} from '../locus-info/types';
187
+ import AIEnableRequest from '../aiEnableRequest';
184
188
 
185
189
  // default callback so we don't call an undefined function, but in practice it should never be used
186
190
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -250,6 +254,7 @@ export type AddMediaOptions = {
250
254
  remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
251
255
  bundlePolicy?: BundlePolicy; // applies only to multistream meetings
252
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
253
258
  additionalMediaOptions?: AdditionalMediaOptions; // allows adding additional options like send/receive audio/video
254
259
  };
255
260
 
@@ -564,6 +569,34 @@ type MediaReachabilityMetrics = ReachabilityMetrics & {
564
569
  * @memberof Meeting
565
570
  */
566
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
+
567
600
  /**
568
601
  * @description Meeting is the crux of the plugin
569
602
  * @export
@@ -575,6 +608,7 @@ export default class Meeting extends StatelessWebexPlugin {
575
608
  breakouts: any;
576
609
  simultaneousInterpretation: any;
577
610
  annotation: any;
611
+ aiEnableRequest: any;
578
612
  webinar: any;
579
613
  conversationUrl: string;
580
614
  callStateForMetrics: CallStateForMetrics;
@@ -623,6 +657,13 @@ export default class Meeting extends StatelessWebexPlugin {
623
657
  keepAliveTimerId: NodeJS.Timeout;
624
658
  lastVideoLayoutInfo: any;
625
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
626
667
  locusMediaRequest?: LocusMediaRequest;
627
668
  mediaProperties: MediaProperties;
628
669
  mediaRequestManagers: {
@@ -657,7 +698,6 @@ export default class Meeting extends StatelessWebexPlugin {
657
698
  endCallInitJoinReq: any;
658
699
  endJoinReqResp: any;
659
700
  endLocalSDPGenRemoteSDPRecvDelay: any;
660
- joinedWith: any;
661
701
  locusId: any;
662
702
  startCallInitJoinReq: any;
663
703
  startJoinReqResp: any;
@@ -672,12 +712,11 @@ export default class Meeting extends StatelessWebexPlugin {
672
712
  permissionTokenReceivedLocalTime: number;
673
713
  resourceId: any;
674
714
  resourceUrl: string;
675
- selfId: string;
676
715
  state: any;
677
716
  localAudioStreamMuteStateHandler: () => void;
678
717
  localVideoStreamMuteStateHandler: () => void;
679
718
  localOutputTrackChangeHandler: () => void;
680
- roles: any[];
719
+ localConstraintsChangeHandler: () => void;
681
720
  environment: string;
682
721
  namespace = MEETINGS;
683
722
  allowMediaInLobby: boolean;
@@ -894,6 +933,10 @@ export default class Meeting extends StatelessWebexPlugin {
894
933
  */
895
934
  // @ts-ignore
896
935
  this.simultaneousInterpretation = new SimultaneousInterpretation({}, {parent: this.webex});
936
+
937
+ // @ts-ignore
938
+ this.aiEnableRequest = new AIEnableRequest({}, {parent: this.webex});
939
+
897
940
  /**
898
941
  * @instance
899
942
  * @type {Annotation}
@@ -1543,6 +1586,12 @@ export default class Meeting extends StatelessWebexPlugin {
1543
1586
  }
1544
1587
  };
1545
1588
 
1589
+ this.localConstraintsChangeHandler = () => {
1590
+ if (!this.isMultistream) {
1591
+ this.mediaProperties.webrtcMediaConnection?.updatePreferredBitrateKbps();
1592
+ }
1593
+ };
1594
+
1546
1595
  /**
1547
1596
  * Promise that exists if SDP offer has been generated, and resolves once sdp answer is received.
1548
1597
  * @instance
@@ -2952,6 +3001,18 @@ export default class Meeting extends StatelessWebexPlugin {
2952
3001
  );
2953
3002
  });
2954
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
+
2955
3016
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_WEBCAST_CHANGED, ({state}) => {
2956
3017
  Trigger.trigger(
2957
3018
  this,
@@ -3403,6 +3464,8 @@ export default class Meeting extends StatelessWebexPlugin {
3403
3464
  this.recordingController.setLocusUrl(this.locusUrl);
3404
3465
  this.controlsOptionsManager.setLocusUrl(this.locusUrl, !!isMainLocus);
3405
3466
  this.webinar.locusUrlUpdate(url);
3467
+ // @ts-ignore
3468
+ this.webex.internal.llm.setRefreshHandler(() => this.refreshDataChannelToken());
3406
3469
 
3407
3470
  Trigger.trigger(
3408
3471
  this,
@@ -3433,6 +3496,7 @@ export default class Meeting extends StatelessWebexPlugin {
3433
3496
  this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
3434
3497
  this.annotation.approvalUrlUpdate(payload?.services?.approval?.url);
3435
3498
  this.simultaneousInterpretation.approvalUrlUpdate(payload?.services?.approval?.url);
3499
+ this.aiEnableRequest.approvalUrlUpdate(payload?.services?.approval?.url);
3436
3500
  });
3437
3501
  }
3438
3502
 
@@ -3530,6 +3594,9 @@ export default class Meeting extends StatelessWebexPlugin {
3530
3594
  // @ts-ignore - config coming from registerPlugin
3531
3595
  if (datachannelUrl && this.config.enableAutomaticLLM) {
3532
3596
  this.updateLLMConnection();
3597
+ if (this.webinar.isJoinPracticeSessionDataChannel()) {
3598
+ this.webinar.updatePSDataChannel();
3599
+ }
3533
3600
  }
3534
3601
  }
3535
3602
 
@@ -3667,7 +3734,7 @@ export default class Meeting extends StatelessWebexPlugin {
3667
3734
  });
3668
3735
  this.updateLLMConnection();
3669
3736
  });
3670
- this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, async (payload) => {
3737
+ this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, (payload) => {
3671
3738
  this.stopKeepAlive();
3672
3739
 
3673
3740
  if (payload) {
@@ -3693,6 +3760,15 @@ export default class Meeting extends StatelessWebexPlugin {
3693
3760
  });
3694
3761
  }
3695
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
+
3696
3772
  this.updateLLMConnection();
3697
3773
  });
3698
3774
 
@@ -3762,6 +3838,10 @@ export default class Meeting extends StatelessWebexPlugin {
3762
3838
  );
3763
3839
  });
3764
3840
 
3841
+ this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ID_CHANGED, (payload) => {
3842
+ this.aiEnableRequest.selfParticipantIdUpdate(payload.selfId);
3843
+ });
3844
+
3765
3845
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED, (payload) => {
3766
3846
  const targetChanged = this.simultaneousInterpretation.updateSelfInterpretation(payload);
3767
3847
  Trigger.trigger(
@@ -4264,6 +4344,9 @@ export default class Meeting extends StatelessWebexPlugin {
4264
4344
  bothLeaveAndEndMeetingAvailable: MeetingUtil.bothLeaveAndEndMeetingAvailable(
4265
4345
  this.userDisplayHints
4266
4346
  ),
4347
+ requireHostEndMeetingBeforeLeave: MeetingUtil.requireHostEndMeetingBeforeLeave(
4348
+ this.userDisplayHints
4349
+ ),
4267
4350
  canEnableClosedCaption: MeetingUtil.canEnableClosedCaption(this.userDisplayHints),
4268
4351
  canStartTranscribing: MeetingUtil.canStartTranscribing(this.userDisplayHints),
4269
4352
  canStopTranscribing: MeetingUtil.canStopTranscribing(this.userDisplayHints),
@@ -4522,6 +4605,12 @@ export default class Meeting extends StatelessWebexPlugin {
4522
4605
  requiredHints: [DISPLAY_HINTS.DISABLE_ATTENDEE_START_POLLING_QA],
4523
4606
  displayHints: this.userDisplayHints,
4524
4607
  }),
4608
+ canAttendeeRequestAiAssistantEnabled: MeetingUtil.canAttendeeRequestAiAssistantEnabled(
4609
+ this.userDisplayHints,
4610
+ this.roles
4611
+ ),
4612
+ isAttendeeRequestAiAssistantDeclinedAll:
4613
+ MeetingUtil.attendeeRequestAiAssistantDeclinedAll(this.userDisplayHints),
4525
4614
  }) || changed;
4526
4615
  }
4527
4616
  if (changed) {
@@ -4593,7 +4682,8 @@ export default class Meeting extends StatelessWebexPlugin {
4593
4682
  mediaId: string;
4594
4683
  host: object;
4595
4684
  selfId: string;
4596
- 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
4597
4687
  }) {
4598
4688
  const mtgLocus: any = data.locus;
4599
4689
 
@@ -4609,6 +4699,7 @@ export default class Meeting extends StatelessWebexPlugin {
4609
4699
  trigger: 'join-response',
4610
4700
  locus: mtgLocus,
4611
4701
  dataSets: data.dataSets,
4702
+ metadata: data.metadata,
4612
4703
  });
4613
4704
  }
4614
4705
 
@@ -4818,6 +4909,7 @@ export default class Meeting extends StatelessWebexPlugin {
4818
4909
  this.localVideoStreamMuteStateHandler
4819
4910
  );
4820
4911
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4912
+ oldStream?.off(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4821
4913
 
4822
4914
  // we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
4823
4915
  this.mediaProperties.setLocalVideoStream(localStream);
@@ -4833,6 +4925,7 @@ export default class Meeting extends StatelessWebexPlugin {
4833
4925
  this.localVideoStreamMuteStateHandler
4834
4926
  );
4835
4927
  localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4928
+ localStream?.on(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4836
4929
 
4837
4930
  if (!this.isMultistream || !localStream) {
4838
4931
  // for multistream WCME automatically un-publishes the old stream when we publish a new one
@@ -4967,6 +5060,7 @@ export default class Meeting extends StatelessWebexPlugin {
4967
5060
  this.localVideoStreamMuteStateHandler
4968
5061
  );
4969
5062
  videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
5063
+ videoStream?.off(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4970
5064
 
4971
5065
  shareAudioStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
4972
5066
  shareAudioStream?.off(
@@ -5758,6 +5852,11 @@ export default class Meeting extends StatelessWebexPlugin {
5758
5852
  */
5759
5853
  private processLocusLLMEvent = (event: LocusLLMEvent): void => {
5760
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
+
5761
5860
  this.locusInfo.parse(this, event.data);
5762
5861
  } else {
5763
5862
  LoggerProxy.logger.warn(
@@ -5781,7 +5880,7 @@ export default class Meeting extends StatelessWebexPlugin {
5781
5880
  this.isReactionsSupported()
5782
5881
  ) {
5783
5882
  const member = this.members.membersCollection.get(e.data.sender.participantId);
5784
- if (!member) {
5883
+ if (!member && !this.locusInfo?.info?.isWebinar) {
5785
5884
  // @ts-ignore -- fix type
5786
5885
  LoggerProxy.logger.warn(
5787
5886
  `Meeting:index#processRelayEvent --> Skipping handling of ${REACTION_RELAY_TYPES.REACTION} for ${this.id}. participantId ${e.data.sender.participantId} does not exist in membersCollection.`
@@ -5789,7 +5888,7 @@ export default class Meeting extends StatelessWebexPlugin {
5789
5888
  break;
5790
5889
  }
5791
5890
 
5792
- const {name} = member;
5891
+ const name = (member && member.name) || e.data.sender.displayName;
5793
5892
  const processedReaction: ProcessedReaction = {
5794
5893
  reaction: e.data.reaction,
5795
5894
  sender: {
@@ -5818,37 +5917,35 @@ export default class Meeting extends StatelessWebexPlugin {
5818
5917
  * @returns {void}
5819
5918
  */
5820
5919
  stopTranscription() {
5821
- if (this.transcription) {
5822
- // @ts-ignore
5823
- this.webex.internal.voicea.off(
5824
- VOICEAEVENTS.VOICEA_ANNOUNCEMENT,
5825
- this.voiceaListenerCallbacks[VOICEAEVENTS.VOICEA_ANNOUNCEMENT]
5826
- );
5920
+ // @ts-ignore
5921
+ this.webex.internal.voicea.off(
5922
+ VOICEAEVENTS.VOICEA_ANNOUNCEMENT,
5923
+ this.voiceaListenerCallbacks[VOICEAEVENTS.VOICEA_ANNOUNCEMENT]
5924
+ );
5827
5925
 
5828
- // @ts-ignore
5829
- this.webex.internal.voicea.off(
5830
- VOICEAEVENTS.CAPTIONS_TURNED_ON,
5831
- this.voiceaListenerCallbacks[VOICEAEVENTS.CAPTIONS_TURNED_ON]
5832
- );
5926
+ // @ts-ignore
5927
+ this.webex.internal.voicea.off(
5928
+ VOICEAEVENTS.CAPTIONS_TURNED_ON,
5929
+ this.voiceaListenerCallbacks[VOICEAEVENTS.CAPTIONS_TURNED_ON]
5930
+ );
5833
5931
 
5834
- // @ts-ignore
5835
- this.webex.internal.voicea.off(
5836
- VOICEAEVENTS.EVA_COMMAND,
5837
- this.voiceaListenerCallbacks[VOICEAEVENTS.EVA_COMMAND]
5838
- );
5932
+ // @ts-ignore
5933
+ this.webex.internal.voicea.off(
5934
+ VOICEAEVENTS.EVA_COMMAND,
5935
+ this.voiceaListenerCallbacks[VOICEAEVENTS.EVA_COMMAND]
5936
+ );
5839
5937
 
5840
- // @ts-ignore
5841
- this.webex.internal.voicea.off(
5842
- VOICEAEVENTS.NEW_CAPTION,
5843
- this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
5844
- );
5938
+ // @ts-ignore
5939
+ this.webex.internal.voicea.off(
5940
+ VOICEAEVENTS.NEW_CAPTION,
5941
+ this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
5942
+ );
5845
5943
 
5846
- // @ts-ignore
5847
- this.webex.internal.voicea.deregisterEvents();
5944
+ // @ts-ignore
5945
+ this.webex.internal.voicea.deregisterEvents();
5848
5946
 
5849
- this.areVoiceaEventsSetup = false;
5850
- this.triggerStopReceivingTranscriptionEvent();
5851
- }
5947
+ this.areVoiceaEventsSetup = false;
5948
+ this.triggerStopReceivingTranscriptionEvent();
5852
5949
  }
5853
5950
 
5854
5951
  /**
@@ -5872,6 +5969,30 @@ export default class Meeting extends StatelessWebexPlugin {
5872
5969
  );
5873
5970
  }
5874
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
+
5875
5996
  /**
5876
5997
  * This is a callback for the LLM event that is triggered when it comes online
5877
5998
  * This method in turn will trigger an event to the developers that the LLM is connected
@@ -5880,8 +6001,8 @@ export default class Meeting extends StatelessWebexPlugin {
5880
6001
  * @returns {null}
5881
6002
  */
5882
6003
  private handleLLMOnline = (): void => {
5883
- // @ts-ignore
5884
- this.webex.internal.llm.off('online', this.handleLLMOnline);
6004
+ this.restoreLLMSubscriptionsIfNeeded();
6005
+
5885
6006
  Trigger.trigger(
5886
6007
  this,
5887
6008
  {
@@ -6109,8 +6230,11 @@ export default class Meeting extends StatelessWebexPlugin {
6109
6230
  return Promise.reject(error);
6110
6231
  })
6111
6232
  .then((join) => {
6233
+ this.saveDataChannelToken(join);
6112
6234
  // @ts-ignore - config coming from registerPlugin
6113
6235
  if (this.config.enableAutomaticLLM) {
6236
+ // @ts-ignore
6237
+ this.webex.internal.llm.off('online', this.handleLLMOnline);
6114
6238
  // @ts-ignore
6115
6239
  this.webex.internal.llm.on('online', this.handleLLMOnline);
6116
6240
  this.updateLLMConnection()
@@ -6177,23 +6301,146 @@ export default class Meeting extends StatelessWebexPlugin {
6177
6301
  }
6178
6302
  }
6179
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
+
6180
6427
  /**
6181
6428
  * Connects to low latency mercury and reconnects if the address has changed
6182
6429
  * It will also disconnect if called when the meeting has ended
6183
- * @param {String} datachannelUrl
6184
6430
  * @returns {Promise}
6185
6431
  */
6186
6432
  async updateLLMConnection() {
6187
6433
  // @ts-ignore - Fix type
6188
- const {url, info: {datachannelUrl, practiceSessionDatachannelUrl} = {}} = this.locusInfo;
6434
+ const {url = undefined, info: {datachannelUrl = undefined} = {}} = this.locusInfo || {};
6189
6435
 
6190
6436
  const isJoined = this.isJoined();
6191
6437
 
6192
- // webinar panelist should use new data channel in practice session
6193
- const dataChannelUrl =
6194
- this.webinar.isJoinPracticeSessionDataChannel() && practiceSessionDatachannelUrl
6195
- ? practiceSessionDatachannelUrl
6196
- : datachannelUrl;
6438
+ // @ts-ignore
6439
+ const datachannelToken = this.webex.internal.llm.getDatachannelToken(
6440
+ DataChannelTokenType.Default
6441
+ );
6442
+
6443
+ const dataChannelUrl = datachannelUrl;
6197
6444
 
6198
6445
  // @ts-ignore - Fix type
6199
6446
  if (this.webex.internal.llm.isConnected()) {
@@ -6206,21 +6453,7 @@ export default class Meeting extends StatelessWebexPlugin {
6206
6453
  ) {
6207
6454
  return undefined;
6208
6455
  }
6209
- // @ts-ignore - Fix type
6210
- await this.webex.internal.llm.disconnectLLM(
6211
- isJoined
6212
- ? {
6213
- code: 3050,
6214
- reason: 'done (permanent)',
6215
- }
6216
- : undefined
6217
- );
6218
- // @ts-ignore - Fix type
6219
- this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
6220
- // @ts-ignore - Fix type
6221
- this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
6222
-
6223
- this.clearLLMHealthCheckTimer();
6456
+ await this.cleanupLLMConneciton({removeOnlineListener: false});
6224
6457
  }
6225
6458
 
6226
6459
  if (!isJoined) {
@@ -6229,7 +6462,7 @@ export default class Meeting extends StatelessWebexPlugin {
6229
6462
 
6230
6463
  // @ts-ignore - Fix type
6231
6464
  return this.webex.internal.llm
6232
- .registerAndConnect(url, dataChannelUrl)
6465
+ .registerAndConnect(url, dataChannelUrl, datachannelToken)
6233
6466
  .then((registerAndConnectResult) => {
6234
6467
  // @ts-ignore - Fix type
6235
6468
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
@@ -7433,7 +7666,7 @@ export default class Meeting extends StatelessWebexPlugin {
7433
7666
  */
7434
7667
  private async waitForMediaConnectionConnected(): Promise<void> {
7435
7668
  try {
7436
- await this.mediaProperties.waitForMediaConnectionConnected();
7669
+ await this.mediaProperties.waitForMediaConnectionConnected(this.correlationId);
7437
7670
  } catch (error) {
7438
7671
  const {iceConnected} = error;
7439
7672
 
@@ -8026,6 +8259,7 @@ export default class Meeting extends StatelessWebexPlugin {
8026
8259
  remoteMediaManagerConfig,
8027
8260
  bundlePolicy = 'max-bundle',
8028
8261
  additionalMediaOptions = {},
8262
+ allowPublishMediaInLobby = false,
8029
8263
  } = options;
8030
8264
 
8031
8265
  const {
@@ -8046,7 +8280,6 @@ export default class Meeting extends StatelessWebexPlugin {
8046
8280
  const ipver = MeetingUtil.getIpVersion(this.webex); // used just for metrics
8047
8281
 
8048
8282
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
8049
- // @ts-ignore - isUserUnadmitted coming from SelfUtil
8050
8283
  if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
8051
8284
  throw new UserInLobbyError();
8052
8285
  }
@@ -8091,7 +8324,13 @@ export default class Meeting extends StatelessWebexPlugin {
8091
8324
  this.brbState = createBrbState(this, false);
8092
8325
 
8093
8326
  try {
8094
- 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
+ }
8095
8334
 
8096
8335
  this.setMercuryListener();
8097
8336
 
@@ -8559,12 +8798,12 @@ export default class Meeting extends StatelessWebexPlugin {
8559
8798
  LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
8560
8799
 
8561
8800
  return MeetingUtil.leaveMeeting(this, options)
8562
- .then((leave) => {
8801
+ .then(async (leave) => {
8563
8802
  // CA team recommends submitting this *after* locus /leave
8564
8803
  submitLeaveMetric();
8565
8804
 
8566
8805
  this.meetingFiniteStateMachine.leave();
8567
- this.clearMeetingData();
8806
+ await this.clearMeetingData();
8568
8807
 
8569
8808
  // upload logs on leave irrespective of meeting delete
8570
8809
  Trigger.trigger(
@@ -9423,10 +9662,10 @@ export default class Meeting extends StatelessWebexPlugin {
9423
9662
  });
9424
9663
 
9425
9664
  return MeetingUtil.endMeetingForAll(this)
9426
- .then((end) => {
9665
+ .then(async (end) => {
9427
9666
  this.meetingFiniteStateMachine.end();
9428
9667
 
9429
- this.clearMeetingData();
9668
+ await this.clearMeetingData();
9430
9669
  // upload logs on leave irrespective of meeting delete
9431
9670
  Trigger.trigger(
9432
9671
  this,
@@ -9474,7 +9713,7 @@ export default class Meeting extends StatelessWebexPlugin {
9474
9713
  * @public
9475
9714
  * @memberof Meeting
9476
9715
  */
9477
- clearMeetingData = () => {
9716
+ clearMeetingData = async () => {
9478
9717
  this.audio = null;
9479
9718
  this.video = null;
9480
9719
  this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
@@ -9483,19 +9722,13 @@ export default class Meeting extends StatelessWebexPlugin {
9483
9722
  }
9484
9723
  this.queuedMediaUpdates = [];
9485
9724
 
9486
- if (this.transcription) {
9487
- this.stopTranscription();
9488
- this.transcription = undefined;
9489
- }
9725
+ this.stopTranscription();
9726
+ this.transcription = undefined;
9490
9727
 
9491
9728
  this.annotation.deregisterEvents();
9492
9729
 
9493
- // @ts-ignore - fix types
9494
- this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
9495
- // @ts-ignore - Fix type
9496
- this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
9497
-
9498
- this.clearLLMHealthCheckTimer();
9730
+ this.clearDataChannelToken();
9731
+ await this.cleanupLLMConneciton({throwOnError: false});
9499
9732
  };
9500
9733
 
9501
9734
  /**
@@ -9688,15 +9921,20 @@ export default class Meeting extends StatelessWebexPlugin {
9688
9921
  }
9689
9922
 
9690
9923
  if (shouldEnableMusicMode) {
9691
- await this.sendSlotManager.setCodecParameters(MediaType.AudioMain, {
9692
- maxaveragebitrate: '64000',
9693
- maxplaybackrate: '48000',
9694
- });
9924
+ await this.sendSlotManager.setCustomCodecParameters(
9925
+ MediaType.AudioMain,
9926
+ MediaCodecMimeType.OPUS,
9927
+ {
9928
+ maxaveragebitrate: '64000',
9929
+ maxplaybackrate: '48000',
9930
+ }
9931
+ );
9695
9932
  } else {
9696
- await this.sendSlotManager.deleteCodecParameters(MediaType.AudioMain, [
9697
- 'maxaveragebitrate',
9698
- 'maxplaybackrate',
9699
- ]);
9933
+ await this.sendSlotManager.markCustomCodecParametersForDeletion(
9934
+ MediaType.AudioMain,
9935
+ MediaCodecMimeType.OPUS,
9936
+ ['maxaveragebitrate', 'maxplaybackrate']
9937
+ );
9700
9938
  }
9701
9939
  }
9702
9940
 
@@ -10195,4 +10433,52 @@ export default class Meeting extends StatelessWebexPlugin {
10195
10433
  cancelSipCallOut(participantId: string) {
10196
10434
  return this.meetingRequest.cancelSipCallOut(participantId);
10197
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
+ }
10198
10484
  }