@webex/plugin-meetings 3.0.0 → 3.1.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 (138) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/config.d.ts +1 -0
  4. package/dist/config.js +2 -1
  5. package/dist/config.js.map +1 -1
  6. package/dist/constants.d.ts +5 -4
  7. package/dist/constants.js +8 -4
  8. package/dist/constants.js.map +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.js +6 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/interpretation/index.js +16 -2
  13. package/dist/interpretation/index.js.map +1 -1
  14. package/dist/interpretation/siLanguage.js +1 -1
  15. package/dist/locus-info/mediaSharesUtils.js +15 -1
  16. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  17. package/dist/locus-info/selfUtils.js +5 -0
  18. package/dist/locus-info/selfUtils.js.map +1 -1
  19. package/dist/media/MediaConnectionAwaiter.d.ts +61 -0
  20. package/dist/media/MediaConnectionAwaiter.js +163 -0
  21. package/dist/media/MediaConnectionAwaiter.js.map +1 -0
  22. package/dist/media/index.js +4 -1
  23. package/dist/media/index.js.map +1 -1
  24. package/dist/media/properties.js +4 -24
  25. package/dist/media/properties.js.map +1 -1
  26. package/dist/meeting/index.d.ts +26 -7
  27. package/dist/meeting/index.js +893 -677
  28. package/dist/meeting/index.js.map +1 -1
  29. package/dist/meeting/muteState.d.ts +2 -8
  30. package/dist/meeting/muteState.js +37 -25
  31. package/dist/meeting/muteState.js.map +1 -1
  32. package/dist/meeting/request.d.ts +3 -0
  33. package/dist/meeting/request.js +32 -23
  34. package/dist/meeting/request.js.map +1 -1
  35. package/dist/meeting/util.js +1 -0
  36. package/dist/meeting/util.js.map +1 -1
  37. package/dist/meeting-info/utilv2.js +4 -1
  38. package/dist/meeting-info/utilv2.js.map +1 -1
  39. package/dist/meetings/index.d.ts +8 -0
  40. package/dist/meetings/index.js +20 -0
  41. package/dist/meetings/index.js.map +1 -1
  42. package/dist/multistream/mediaRequestManager.d.ts +2 -1
  43. package/dist/multistream/mediaRequestManager.js +1 -1
  44. package/dist/multistream/mediaRequestManager.js.map +1 -1
  45. package/dist/multistream/remoteMediaGroup.d.ts +2 -0
  46. package/dist/multistream/remoteMediaGroup.js +16 -2
  47. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  48. package/dist/multistream/remoteMediaManager.d.ts +15 -0
  49. package/dist/multistream/remoteMediaManager.js +179 -65
  50. package/dist/multistream/remoteMediaManager.js.map +1 -1
  51. package/dist/multistream/sendSlotManager.d.ts +9 -1
  52. package/dist/multistream/sendSlotManager.js +22 -0
  53. package/dist/multistream/sendSlotManager.js.map +1 -1
  54. package/dist/reachability/clusterReachability.d.ts +1 -0
  55. package/dist/reachability/clusterReachability.js +29 -15
  56. package/dist/reachability/clusterReachability.js.map +1 -1
  57. package/dist/reachability/index.d.ts +4 -0
  58. package/dist/reachability/index.js +18 -2
  59. package/dist/reachability/index.js.map +1 -1
  60. package/dist/reachability/request.js +12 -10
  61. package/dist/reachability/request.js.map +1 -1
  62. package/dist/reachability/util.d.ts +7 -0
  63. package/dist/reachability/util.js +19 -0
  64. package/dist/reachability/util.js.map +1 -1
  65. package/dist/reconnection-manager/index.js +2 -1
  66. package/dist/reconnection-manager/index.js.map +1 -1
  67. package/dist/roap/index.d.ts +10 -2
  68. package/dist/roap/index.js +15 -0
  69. package/dist/roap/index.js.map +1 -1
  70. package/dist/roap/request.js +3 -3
  71. package/dist/roap/request.js.map +1 -1
  72. package/dist/roap/turnDiscovery.d.ts +64 -17
  73. package/dist/roap/turnDiscovery.js +307 -126
  74. package/dist/roap/turnDiscovery.js.map +1 -1
  75. package/dist/statsAnalyzer/index.js +53 -30
  76. package/dist/statsAnalyzer/index.js.map +1 -1
  77. package/dist/webinar/index.js +1 -1
  78. package/package.json +22 -22
  79. package/src/config.ts +1 -0
  80. package/src/constants.ts +7 -3
  81. package/src/index.ts +1 -0
  82. package/src/interpretation/index.ts +18 -1
  83. package/src/locus-info/mediaSharesUtils.ts +16 -0
  84. package/src/locus-info/selfUtils.ts +5 -0
  85. package/src/media/MediaConnectionAwaiter.ts +174 -0
  86. package/src/media/index.ts +3 -1
  87. package/src/media/properties.ts +6 -31
  88. package/src/meeting/index.ts +321 -106
  89. package/src/meeting/muteState.ts +34 -20
  90. package/src/meeting/request.ts +18 -2
  91. package/src/meeting/util.ts +1 -0
  92. package/src/meeting-info/utilv2.ts +2 -1
  93. package/src/meetings/index.ts +18 -0
  94. package/src/multistream/mediaRequestManager.ts +4 -1
  95. package/src/multistream/remoteMediaGroup.ts +19 -0
  96. package/src/multistream/remoteMediaManager.ts +101 -16
  97. package/src/multistream/sendSlotManager.ts +28 -0
  98. package/src/reachability/clusterReachability.ts +20 -5
  99. package/src/reachability/index.ts +24 -1
  100. package/src/reachability/request.ts +15 -11
  101. package/src/reachability/util.ts +21 -0
  102. package/src/reconnection-manager/index.ts +1 -1
  103. package/src/roap/index.ts +25 -3
  104. package/src/roap/request.ts +3 -3
  105. package/src/roap/turnDiscovery.ts +244 -78
  106. package/src/statsAnalyzer/index.ts +63 -27
  107. package/test/integration/spec/journey.js +14 -14
  108. package/test/integration/spec/space-meeting.js +1 -1
  109. package/test/unit/spec/interpretation/index.ts +39 -3
  110. package/test/unit/spec/locus-info/index.js +28 -19
  111. package/test/unit/spec/locus-info/mediaSharesUtils.ts +9 -0
  112. package/test/unit/spec/locus-info/selfUtils.js +42 -12
  113. package/test/unit/spec/media/MediaConnectionAwaiter.ts +344 -0
  114. package/test/unit/spec/media/index.ts +89 -78
  115. package/test/unit/spec/media/properties.ts +16 -70
  116. package/test/unit/spec/meeting/index.js +638 -139
  117. package/test/unit/spec/meeting/muteState.js +219 -67
  118. package/test/unit/spec/meeting/request.js +21 -0
  119. package/test/unit/spec/meeting/utils.js +6 -1
  120. package/test/unit/spec/meeting-info/utilv2.js +6 -0
  121. package/test/unit/spec/meetings/index.js +40 -20
  122. package/test/unit/spec/multistream/mediaRequestManager.ts +20 -2
  123. package/test/unit/spec/multistream/remoteMediaGroup.ts +79 -1
  124. package/test/unit/spec/multistream/remoteMediaManager.ts +199 -1
  125. package/test/unit/spec/multistream/sendSlotManager.ts +50 -18
  126. package/test/unit/spec/reachability/clusterReachability.ts +86 -22
  127. package/test/unit/spec/reachability/index.ts +197 -60
  128. package/test/unit/spec/reachability/request.js +15 -7
  129. package/test/unit/spec/reachability/util.ts +32 -2
  130. package/test/unit/spec/reconnection-manager/index.js +28 -0
  131. package/test/unit/spec/roap/index.ts +61 -6
  132. package/test/unit/spec/roap/turnDiscovery.ts +298 -16
  133. package/test/unit/spec/stats-analyzer/index.js +179 -0
  134. package/dist/member/member.types.d.ts +0 -11
  135. package/dist/member/member.types.js +0 -17
  136. package/dist/member/member.types.js.map +0 -1
  137. package/src/member/member.types.ts +0 -13
  138. /package/test/unit/spec/locus-info/{lib/selfConstant.js → selfConstant.js} +0 -0
@@ -10,6 +10,8 @@ import {
10
10
  ClientEventLeaveReason,
11
11
  CallDiagnosticUtils,
12
12
  } from '@webex/internal-plugin-metrics';
13
+ import {ClientEvent as RawClientEvent} from '@webex/event-dictionary-ts';
14
+
13
15
  import {
14
16
  ConnectionState,
15
17
  Errors,
@@ -50,8 +52,13 @@ import {
50
52
  import {StatsAnalyzer, EVENTS as StatsAnalyzerEvents} from '../statsAnalyzer';
51
53
  import NetworkQualityMonitor from '../networkQualityMonitor';
52
54
  import LoggerProxy from '../common/logs/logger-proxy';
55
+ import EventsUtil from '../common/events/util';
53
56
  import Trigger from '../common/events/trigger-proxy';
54
- import Roap from '../roap/index';
57
+ import Roap, {
58
+ type TurnDiscoveryResult,
59
+ type TurnServerInfo,
60
+ type TurnDiscoverySkipReason,
61
+ } from '../roap/index';
55
62
  import Media, {type BundlePolicy} from '../media';
56
63
  import MediaProperties from '../media/properties';
57
64
  import MeetingStateMachine from './state';
@@ -110,6 +117,7 @@ import {
110
117
  MEETING_PERMISSION_TOKEN_REFRESH_REASON,
111
118
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT,
112
119
  RECONNECTION,
120
+ NAMED_MEDIA_GROUP_TYPE_AUDIO,
113
121
  LANGUAGE_ENGLISH,
114
122
  } from '../constants';
115
123
  import BEHAVIORAL_METRICS from '../metrics/constants';
@@ -119,7 +127,6 @@ import {
119
127
  MeetingInfoV2CaptchaError,
120
128
  MeetingInfoV2PolicyError,
121
129
  } from '../meeting-info/meeting-info-v2';
122
- import BrowserDetection from '../common/browser-detection';
123
130
  import {CSI, ReceiveSlotManager} from '../multistream/receiveSlotManager';
124
131
  import SendSlotManager from '../multistream/sendSlotManager';
125
132
  import {MediaRequestManager} from '../multistream/mediaRequestManager';
@@ -147,8 +154,6 @@ import ControlsOptionsManager from '../controls-options-manager';
147
154
  import PermissionError from '../common/errors/permission';
148
155
  import {LocusMediaRequest} from './locusMediaRequest';
149
156
 
150
- const {isBrowser} = BrowserDetection();
151
-
152
157
  const logRequest = (request: any, {logText = ''}) => {
153
158
  LoggerProxy.logger.info(`${logText} - sending request`);
154
159
 
@@ -614,8 +619,8 @@ export default class Meeting extends StatelessWebexPlugin {
614
619
  resourceUrl: string;
615
620
  selfId: string;
616
621
  state: any;
617
- localAudioStreamMuteStateHandler: (muted: boolean) => void;
618
- localVideoStreamMuteStateHandler: (muted: boolean) => void;
622
+ localAudioStreamMuteStateHandler: () => void;
623
+ localVideoStreamMuteStateHandler: () => void;
619
624
  localOutputTrackChangeHandler: () => void;
620
625
  roles: any[];
621
626
  environment: string;
@@ -623,21 +628,23 @@ export default class Meeting extends StatelessWebexPlugin {
623
628
  allowMediaInLobby: boolean;
624
629
  localShareInstanceId: string;
625
630
  remoteShareInstanceId: string;
626
- turnDiscoverySkippedReason: string;
631
+ turnDiscoverySkippedReason: TurnDiscoverySkipReason;
627
632
  turnServerUsed: boolean;
628
633
  areVoiceaEventsSetup = false;
634
+
629
635
  voiceaListenerCallbacks: object = {
630
636
  [VOICEAEVENTS.VOICEA_ANNOUNCEMENT]: (payload: Transcription['languageOptions']) => {
631
637
  this.transcription.languageOptions = payload;
632
- Trigger.trigger(
633
- this,
634
- {
638
+
639
+ LoggerProxy.logger.debug(
640
+ `${EventsUtil.getScopeLog({
635
641
  file: 'meeting/index',
636
642
  function: 'setUpVoiceaListeners',
637
- },
638
- EVENT_TRIGGERS.MEETING_STARTED_RECEIVING_TRANSCRIPTION,
639
- payload
643
+ })}event#${EVENT_TRIGGERS.MEETING_STARTED_RECEIVING_TRANSCRIPTION}`
640
644
  );
645
+
646
+ // @ts-ignore
647
+ this.trigger(EVENT_TRIGGERS.MEETING_STARTED_RECEIVING_TRANSCRIPTION, payload);
641
648
  },
642
649
  [VOICEAEVENTS.CAPTIONS_TURNED_ON]: () => {
643
650
  this.transcription.status = TURN_ON_CAPTION_STATUS.ENABLED;
@@ -650,18 +657,19 @@ export default class Meeting extends StatelessWebexPlugin {
650
657
  },
651
658
  [VOICEAEVENTS.NEW_CAPTION]: (data) => {
652
659
  processNewCaptions({data, meeting: this});
653
- Trigger.trigger(
654
- this,
655
- {
660
+
661
+ LoggerProxy.logger.debug(
662
+ `${EventsUtil.getScopeLog({
656
663
  file: 'meeting/index',
657
664
  function: 'setUpVoiceaListeners',
658
- },
659
- EVENT_TRIGGERS.MEETING_CAPTION_RECEIVED,
660
- {
661
- captions: this.transcription.captions,
662
- interimCaptions: this.transcription.interimCaptions,
663
- }
665
+ })}event#${EVENT_TRIGGERS.MEETING_CAPTION_RECEIVED}`
664
666
  );
667
+
668
+ // @ts-ignore
669
+ this.trigger(EVENT_TRIGGERS.MEETING_CAPTION_RECEIVED, {
670
+ captions: this.transcription.captions,
671
+ interimCaptions: this.transcription.interimCaptions,
672
+ });
665
673
  },
666
674
  };
667
675
 
@@ -1380,12 +1388,12 @@ export default class Meeting extends StatelessWebexPlugin {
1380
1388
  */
1381
1389
  this.remoteMediaManager = null;
1382
1390
 
1383
- this.localAudioStreamMuteStateHandler = (muted: boolean) => {
1384
- this.audio.handleLocalStreamMuteStateChange(this, muted);
1391
+ this.localAudioStreamMuteStateHandler = () => {
1392
+ this.audio.handleLocalStreamMuteStateChange(this);
1385
1393
  };
1386
1394
 
1387
- this.localVideoStreamMuteStateHandler = (muted: boolean) => {
1388
- this.video.handleLocalStreamMuteStateChange(this, muted);
1395
+ this.localVideoStreamMuteStateHandler = () => {
1396
+ this.video.handleLocalStreamMuteStateChange(this);
1389
1397
  };
1390
1398
 
1391
1399
  // The handling of output track changes should be done inside
@@ -2518,6 +2526,7 @@ export default class Meeting extends StatelessWebexPlugin {
2518
2526
  {
2519
2527
  annotationInfo: contentShare?.annotation,
2520
2528
  meetingId: this.id,
2529
+ resourceType: contentShare?.resourceType,
2521
2530
  }
2522
2531
  );
2523
2532
  }
@@ -2546,7 +2555,8 @@ export default class Meeting extends StatelessWebexPlugin {
2546
2555
  contentShare.deviceUrlSharing === previousContentShare.deviceUrlSharing &&
2547
2556
  whiteboardShare.beneficiaryId === previousWhiteboardShare?.beneficiaryId &&
2548
2557
  whiteboardShare.disposition === previousWhiteboardShare?.disposition &&
2549
- whiteboardShare.resourceUrl === previousWhiteboardShare?.resourceUrl
2558
+ whiteboardShare.resourceUrl === previousWhiteboardShare?.resourceUrl &&
2559
+ contentShare.resourceType === previousContentShare?.resourceType
2550
2560
  ) {
2551
2561
  // nothing changed, so ignore
2552
2562
  // (this happens when we steal presentation from remote)
@@ -2668,6 +2678,7 @@ export default class Meeting extends StatelessWebexPlugin {
2668
2678
  url: contentShare.url,
2669
2679
  shareInstanceId: this.remoteShareInstanceId,
2670
2680
  annotationInfo: contentShare.annotation,
2681
+ resourceType: contentShare.resourceType,
2671
2682
  }
2672
2683
  );
2673
2684
  };
@@ -2760,6 +2771,7 @@ export default class Meeting extends StatelessWebexPlugin {
2760
2771
  url: contentShare.url,
2761
2772
  shareInstanceId: this.remoteShareInstanceId,
2762
2773
  annotationInfo: contentShare.annotation,
2774
+ resourceType: contentShare.resourceType,
2763
2775
  }
2764
2776
  );
2765
2777
  this.members.locusMediaSharesUpdate(payload);
@@ -3069,6 +3081,7 @@ export default class Meeting extends StatelessWebexPlugin {
3069
3081
  options: {meetingId: this.id},
3070
3082
  });
3071
3083
  }
3084
+ this.updateLLMConnection();
3072
3085
  });
3073
3086
 
3074
3087
  // @ts-ignore - check if MEDIA_INACTIVITY exists
@@ -3127,7 +3140,7 @@ export default class Meeting extends StatelessWebexPlugin {
3127
3140
  });
3128
3141
 
3129
3142
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED, (payload) => {
3130
- this.simultaneousInterpretation.updateSelfInterpretation(payload);
3143
+ const targetChanged = this.simultaneousInterpretation.updateSelfInterpretation(payload);
3131
3144
  Trigger.trigger(
3132
3145
  this,
3133
3146
  {
@@ -3136,6 +3149,9 @@ export default class Meeting extends StatelessWebexPlugin {
3136
3149
  },
3137
3150
  EVENT_TRIGGERS.MEETING_INTERPRETATION_UPDATE
3138
3151
  );
3152
+ if (targetChanged && this.mediaProperties.audioStream) {
3153
+ this.setSendNamedMediaGroup(MediaType.AudioMain);
3154
+ }
3139
3155
  });
3140
3156
 
3141
3157
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ROLES_CHANGED, (payload) => {
@@ -3450,8 +3466,10 @@ export default class Meeting extends StatelessWebexPlugin {
3450
3466
  this.owner =
3451
3467
  locusMeetingObject?.info.owner || meetingInfo?.owner || meetingInfo?.hostId || this.owner;
3452
3468
  this.permissionToken = meetingInfo?.permissionToken;
3453
- this.setPermissionTokenPayload(meetingInfo?.permissionToken);
3454
- this.setSelfUserPolicies();
3469
+ if (this.permissionToken) {
3470
+ this.setPermissionTokenPayload(meetingInfo?.permissionToken);
3471
+ this.setSelfUserPolicies();
3472
+ }
3455
3473
  // Need to populate environment when sending CA event
3456
3474
  this.environment = locusMeetingObject?.info.channel || meetingInfo?.channel;
3457
3475
  }
@@ -3887,7 +3905,14 @@ export default class Meeting extends StatelessWebexPlugin {
3887
3905
  private async setLocalAudioStream(localStream?: LocalMicrophoneStream) {
3888
3906
  const oldStream = this.mediaProperties.audioStream;
3889
3907
 
3890
- oldStream?.off(StreamEventNames.MuteStateChange, this.localAudioStreamMuteStateHandler);
3908
+ oldStream?.off(
3909
+ LocalStreamEventNames.UserMuteStateChange,
3910
+ this.localAudioStreamMuteStateHandler
3911
+ );
3912
+ oldStream?.off(
3913
+ LocalStreamEventNames.SystemMuteStateChange,
3914
+ this.localAudioStreamMuteStateHandler
3915
+ );
3891
3916
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3892
3917
 
3893
3918
  // we don't update this.mediaProperties.mediaDirection.sendAudio, because we always keep it as true to avoid extra SDP exchanges
@@ -3895,7 +3920,14 @@ export default class Meeting extends StatelessWebexPlugin {
3895
3920
 
3896
3921
  this.audio.handleLocalStreamChange(this);
3897
3922
 
3898
- localStream?.on(StreamEventNames.MuteStateChange, this.localAudioStreamMuteStateHandler);
3923
+ localStream?.on(
3924
+ LocalStreamEventNames.UserMuteStateChange,
3925
+ this.localAudioStreamMuteStateHandler
3926
+ );
3927
+ localStream?.on(
3928
+ LocalStreamEventNames.SystemMuteStateChange,
3929
+ this.localAudioStreamMuteStateHandler
3930
+ );
3899
3931
  localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3900
3932
 
3901
3933
  if (!this.isMultistream || !localStream) {
@@ -3915,7 +3947,14 @@ export default class Meeting extends StatelessWebexPlugin {
3915
3947
  private async setLocalVideoStream(localStream?: LocalCameraStream) {
3916
3948
  const oldStream = this.mediaProperties.videoStream;
3917
3949
 
3918
- oldStream?.off(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
3950
+ oldStream?.off(
3951
+ LocalStreamEventNames.UserMuteStateChange,
3952
+ this.localVideoStreamMuteStateHandler
3953
+ );
3954
+ oldStream?.off(
3955
+ LocalStreamEventNames.SystemMuteStateChange,
3956
+ this.localVideoStreamMuteStateHandler
3957
+ );
3919
3958
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3920
3959
 
3921
3960
  // we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
@@ -3923,7 +3962,14 @@ export default class Meeting extends StatelessWebexPlugin {
3923
3962
 
3924
3963
  this.video.handleLocalStreamChange(this);
3925
3964
 
3926
- localStream?.on(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
3965
+ localStream?.on(
3966
+ LocalStreamEventNames.UserMuteStateChange,
3967
+ this.localVideoStreamMuteStateHandler
3968
+ );
3969
+ localStream?.on(
3970
+ LocalStreamEventNames.SystemMuteStateChange,
3971
+ this.localVideoStreamMuteStateHandler
3972
+ );
3927
3973
  localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3928
3974
 
3929
3975
  if (!this.isMultistream || !localStream) {
@@ -3944,14 +3990,17 @@ export default class Meeting extends StatelessWebexPlugin {
3944
3990
  private async setLocalShareVideoStream(localDisplayStream?: LocalDisplayStream) {
3945
3991
  const oldStream = this.mediaProperties.shareVideoStream;
3946
3992
 
3947
- oldStream?.off(StreamEventNames.MuteStateChange, this.handleShareVideoStreamMuteStateChange);
3993
+ oldStream?.off(
3994
+ LocalStreamEventNames.SystemMuteStateChange,
3995
+ this.handleShareVideoStreamMuteStateChange
3996
+ );
3948
3997
  oldStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
3949
3998
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3950
3999
 
3951
4000
  this.mediaProperties.setLocalShareVideoStream(localDisplayStream);
3952
4001
 
3953
4002
  localDisplayStream?.on(
3954
- StreamEventNames.MuteStateChange,
4003
+ LocalStreamEventNames.SystemMuteStateChange,
3955
4004
  this.handleShareVideoStreamMuteStateChange
3956
4005
  );
3957
4006
  localDisplayStream?.on(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
@@ -4037,10 +4086,24 @@ export default class Meeting extends StatelessWebexPlugin {
4037
4086
  public cleanupLocalStreams() {
4038
4087
  const {audioStream, videoStream, shareAudioStream, shareVideoStream} = this.mediaProperties;
4039
4088
 
4040
- audioStream?.off(StreamEventNames.MuteStateChange, this.localAudioStreamMuteStateHandler);
4089
+ audioStream?.off(
4090
+ LocalStreamEventNames.UserMuteStateChange,
4091
+ this.localAudioStreamMuteStateHandler
4092
+ );
4093
+ audioStream?.off(
4094
+ LocalStreamEventNames.SystemMuteStateChange,
4095
+ this.localAudioStreamMuteStateHandler
4096
+ );
4041
4097
  audioStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4042
4098
 
4043
- videoStream?.off(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
4099
+ videoStream?.off(
4100
+ LocalStreamEventNames.UserMuteStateChange,
4101
+ this.localVideoStreamMuteStateHandler
4102
+ );
4103
+ videoStream?.off(
4104
+ LocalStreamEventNames.SystemMuteStateChange,
4105
+ this.localVideoStreamMuteStateHandler
4106
+ );
4044
4107
  videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4045
4108
 
4046
4109
  shareAudioStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
@@ -4048,8 +4111,9 @@ export default class Meeting extends StatelessWebexPlugin {
4048
4111
  LocalStreamEventNames.OutputTrackChange,
4049
4112
  this.localOutputTrackChangeHandler
4050
4113
  );
4114
+
4051
4115
  shareVideoStream?.off(
4052
- StreamEventNames.MuteStateChange,
4116
+ LocalStreamEventNames.SystemMuteStateChange,
4053
4117
  this.handleShareVideoStreamMuteStateChange
4054
4118
  );
4055
4119
  shareVideoStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
@@ -4441,47 +4505,90 @@ export default class Meeting extends StatelessWebexPlugin {
4441
4505
  * }
4442
4506
  * })
4443
4507
  */
4444
- public joinWithMedia(
4508
+ public async joinWithMedia(
4445
4509
  options: {
4446
4510
  joinOptions?: any;
4447
4511
  mediaOptions?: AddMediaOptions;
4448
4512
  } = {}
4449
4513
  ) {
4450
- const {mediaOptions, joinOptions} = options;
4514
+ const {mediaOptions, joinOptions = {}} = options;
4451
4515
 
4452
4516
  if (!mediaOptions?.allowMediaInLobby) {
4453
4517
  return Promise.reject(
4454
4518
  new ParameterError('joinWithMedia() can only be used with allowMediaInLobby set to true')
4455
4519
  );
4456
4520
  }
4521
+ this.allowMediaInLobby = true;
4457
4522
 
4458
4523
  LoggerProxy.logger.info('Meeting:index#joinWithMedia called');
4459
4524
 
4460
- return this.join(joinOptions)
4461
- .then((joinResponse) =>
4462
- this.addMedia(mediaOptions).then((mediaResponse) => ({
4463
- join: joinResponse,
4464
- media: mediaResponse,
4465
- }))
4466
- )
4467
- .catch((error) => {
4468
- LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);
4525
+ let joined = false;
4469
4526
 
4470
- Metrics.sendBehavioralMetric(
4471
- BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
4472
- {
4473
- correlation_id: this.correlationId,
4474
- locus_id: this.locusUrl.split('/').pop(),
4475
- reason: error.message,
4476
- stack: error.stack,
4477
- },
4478
- {
4479
- type: error.name,
4480
- }
4481
- );
4527
+ try {
4528
+ let turnServerInfo;
4529
+ let turnDiscoverySkippedReason;
4482
4530
 
4483
- return Promise.reject(error);
4484
- });
4531
+ // @ts-ignore
4532
+ joinOptions.reachability = await this.webex.meetings.reachability.getReachabilityResults();
4533
+ const turnDiscoveryRequest = await this.roap.generateTurnDiscoveryRequestMessage(this, true);
4534
+
4535
+ ({turnDiscoverySkippedReason} = turnDiscoveryRequest);
4536
+ joinOptions.roapMessage = turnDiscoveryRequest.roapMessage;
4537
+
4538
+ const joinResponse = await this.join(joinOptions);
4539
+
4540
+ joined = true;
4541
+
4542
+ if (joinOptions.roapMessage) {
4543
+ ({turnServerInfo, turnDiscoverySkippedReason} =
4544
+ await this.roap.handleTurnDiscoveryHttpResponse(this, joinResponse));
4545
+
4546
+ this.turnDiscoverySkippedReason = turnDiscoverySkippedReason;
4547
+ this.turnServerUsed = !!turnServerInfo;
4548
+
4549
+ if (turnServerInfo === undefined) {
4550
+ this.roap.abortTurnDiscovery();
4551
+ }
4552
+ }
4553
+
4554
+ const mediaResponse = await this.addMedia(mediaOptions, turnServerInfo);
4555
+
4556
+ return {
4557
+ join: joinResponse,
4558
+ media: mediaResponse,
4559
+ };
4560
+ } catch (error) {
4561
+ LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);
4562
+
4563
+ let leaveError;
4564
+
4565
+ this.roap.abortTurnDiscovery();
4566
+
4567
+ if (joined) {
4568
+ try {
4569
+ await this.leave({resourceId: joinOptions?.resourceId, reason: 'joinWithMedia failure'});
4570
+ } catch (e) {
4571
+ LoggerProxy.logger.error('Meeting:index#joinWithMedia --> leave error', e);
4572
+ leaveError = e;
4573
+ }
4574
+ }
4575
+
4576
+ Metrics.sendBehavioralMetric(
4577
+ BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
4578
+ {
4579
+ correlation_id: this.correlationId,
4580
+ locus_id: this.locusUrl?.split('/').pop(), // if join fails, we may end up with no locusUrl
4581
+ reason: error.message,
4582
+ stack: error.stack,
4583
+ leaveErrorReason: leaveError?.message,
4584
+ },
4585
+ {
4586
+ type: error.name,
4587
+ }
4588
+ );
4589
+
4590
+ throw error;
4591
+ }
4485
4592
  }
4486
4593
 
4487
4594
  /**
@@ -5604,7 +5711,7 @@ export default class Meeting extends StatelessWebexPlugin {
5604
5711
  logText: `${LOG_HEADER} Roap Offer`,
5605
5712
  }
5606
5713
  ).catch(() => {
5607
- this.deferSDPAnswer.reject();
5714
+ this.deferSDPAnswer.reject(new Error('failed to send ROAP SDP offer'));
5608
5715
  clearTimeout(this.sdpResponseTimer);
5609
5716
  this.sdpResponseTimer = undefined;
5610
5717
  });
@@ -5951,6 +6058,20 @@ export default class Meeting extends StatelessWebexPlugin {
5951
6058
  meetingId: this.id,
5952
6059
  },
5953
6060
  });
6061
+
6062
+ if (data.type === 'share') {
6063
+ // @ts-ignore
6064
+ this.webex.internal.newMetrics.submitClientEvent({
6065
+ name: 'client.media.render.start',
6066
+ payload: {
6067
+ mediaType: 'share',
6068
+ shareInstanceId: this.remoteShareInstanceId,
6069
+ },
6070
+ options: {
6071
+ meetingId: this.id,
6072
+ },
6073
+ });
6074
+ }
5954
6075
  });
5955
6076
  this.statsAnalyzer.on(StatsAnalyzerEvents.REMOTE_MEDIA_STOPPED, (data) => {
5956
6077
  // @ts-ignore
@@ -5964,6 +6085,20 @@ export default class Meeting extends StatelessWebexPlugin {
5964
6085
  meetingId: this.id,
5965
6086
  },
5966
6087
  });
6088
+
6089
+ if (data.type === 'share') {
6090
+ // @ts-ignore
6091
+ this.webex.internal.newMetrics.submitClientEvent({
6092
+ name: 'client.media.render.stop',
6093
+ payload: {
6094
+ mediaType: 'share',
6095
+ shareInstanceId: this.remoteShareInstanceId,
6096
+ },
6097
+ options: {
6098
+ meetingId: this.id,
6099
+ },
6100
+ });
6101
+ }
5967
6102
  });
5968
6103
  };
5969
6104
 
@@ -6020,6 +6155,7 @@ export default class Meeting extends StatelessWebexPlugin {
6020
6155
 
6021
6156
  // publish the streams
6022
6157
  if (this.mediaProperties.audioStream) {
6158
+ this.setSendNamedMediaGroup(MediaType.AudioMain);
6023
6159
  await this.publishStream(MediaType.AudioMain, this.mediaProperties.audioStream);
6024
6160
  }
6025
6161
  if (this.mediaProperties.videoStream) {
@@ -6069,16 +6205,22 @@ export default class Meeting extends StatelessWebexPlugin {
6069
6205
  private async setUpLocalStreamReferences(localStreams: LocalStreams) {
6070
6206
  const setUpStreamPromises = [];
6071
6207
 
6072
- if (localStreams?.microphone) {
6208
+ if (localStreams?.microphone && localStreams?.microphone?.readyState !== 'ended') {
6073
6209
  setUpStreamPromises.push(this.setLocalAudioStream(localStreams.microphone));
6074
6210
  }
6075
- if (localStreams?.camera) {
6211
+ if (localStreams?.camera && localStreams?.camera?.readyState !== 'ended') {
6076
6212
  setUpStreamPromises.push(this.setLocalVideoStream(localStreams.camera));
6077
6213
  }
6078
- if (localStreams?.screenShare?.video) {
6214
+ if (
6215
+ localStreams?.screenShare?.video &&
6216
+ localStreams?.screenShare?.video?.readyState !== 'ended'
6217
+ ) {
6079
6218
  setUpStreamPromises.push(this.setLocalShareVideoStream(localStreams.screenShare.video));
6080
6219
  }
6081
- if (localStreams?.screenShare?.audio) {
6220
+ if (
6221
+ localStreams?.screenShare?.audio &&
6222
+ localStreams?.screenShare?.audio?.readyState !== 'ended'
6223
+ ) {
6082
6224
  setUpStreamPromises.push(this.setLocalShareAudioStream(localStreams.screenShare.audio));
6083
6225
  }
6084
6226
 
@@ -6323,6 +6465,44 @@ export default class Meeting extends StatelessWebexPlugin {
6323
6465
  }
6324
6466
  }
6325
6467
 
6468
+ /**
6469
+ * Performs TURN discovery as a separate call to the Locus /media API
6470
+ *
6471
+ * @param {boolean} isRetry
6472
+ * @param {boolean} isForced
6473
+ * @returns {Promise}
6474
+ */
6475
+ private async doTurnDiscovery(isRetry: boolean, isForced: boolean): Promise<TurnDiscoveryResult> {
6476
+ // @ts-ignore
6477
+ const cdl = this.webex.internal.newMetrics.callDiagnosticLatencies;
6478
+
6479
+ // @ts-ignore
6480
+ this.webex.internal.newMetrics.submitInternalEvent({
6481
+ name: 'internal.client.add-media.turn-discovery.start',
6482
+ });
6483
+
6484
+ const turnDiscoveryResult = await this.roap.doTurnDiscovery(this, isRetry, isForced);
6485
+
6486
+ this.turnDiscoverySkippedReason = turnDiscoveryResult?.turnDiscoverySkippedReason;
6487
+ this.turnServerUsed = !this.turnDiscoverySkippedReason;
6488
+
6489
+ // @ts-ignore
6490
+ this.webex.internal.newMetrics.submitInternalEvent({
6491
+ name: 'internal.client.add-media.turn-discovery.end',
6492
+ });
6493
+
6494
+ if (this.turnServerUsed && turnDiscoveryResult.turnServerInfo) {
6495
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_LATENCY, {
6496
+ correlation_id: this.correlationId,
6497
+ latency: cdl.getTurnDiscoveryTime(),
6498
+ turnServerUsed: this.turnServerUsed,
6499
+ retriedWithTurnServer: this.retriedWithTurnServer,
6500
+ });
6501
+ }
6502
+
6503
+ return turnDiscoveryResult;
6504
+ }
6505
+
6326
6506
  /**
6327
6507
  * Does TURN discovery, SDP offer/answer exhange, establishes ICE connection and DTLS handshake.
6328
6508
  *
@@ -6330,43 +6510,21 @@ export default class Meeting extends StatelessWebexPlugin {
6330
6510
  * @param {RemoteMediaManagerConfiguration} [remoteMediaManagerConfig]
6331
6511
  * @param {BundlePolicy} [bundlePolicy]
6332
6512
  * @param {boolean} [isForced] - let isForced be true to do turn discovery regardless of reachability results
6513
+ * @param {TurnServerInfo} [turnServerInfo]
6333
6514
  * @returns {Promise<void>}
6334
6515
  */
6335
6516
  private async establishMediaConnection(
6336
6517
  remoteMediaManagerConfig?: RemoteMediaManagerConfiguration,
6337
6518
  bundlePolicy?: BundlePolicy,
6338
- isForced?: boolean
6519
+ isForced?: boolean,
6520
+ turnServerInfo?: TurnServerInfo
6339
6521
  ): Promise<void> {
6340
6522
  const LOG_HEADER = 'Meeting:index#addMedia():establishMediaConnection -->';
6341
- // @ts-ignore
6342
- const cdl = this.webex.internal.newMetrics.callDiagnosticLatencies;
6343
6523
  const isRetry = this.retriedWithTurnServer;
6344
6524
 
6345
6525
  try {
6346
- // @ts-ignore
6347
- this.webex.internal.newMetrics.submitInternalEvent({
6348
- name: 'internal.client.add-media.turn-discovery.start',
6349
- });
6350
-
6351
- const turnDiscoveryObject = await this.roap.doTurnDiscovery(this, isRetry, isForced);
6352
-
6353
- this.turnDiscoverySkippedReason = turnDiscoveryObject?.turnDiscoverySkippedReason;
6354
- this.turnServerUsed = !this.turnDiscoverySkippedReason;
6355
-
6356
- // @ts-ignore
6357
- this.webex.internal.newMetrics.submitInternalEvent({
6358
- name: 'internal.client.add-media.turn-discovery.end',
6359
- });
6360
-
6361
- const {turnServerInfo} = turnDiscoveryObject;
6362
-
6363
- if (this.turnServerUsed && turnServerInfo) {
6364
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_LATENCY, {
6365
- correlation_id: this.correlationId,
6366
- latency: cdl.getTurnDiscoveryTime(),
6367
- turnServerUsed: this.turnServerUsed,
6368
- retriedWithTurnServer: this.retriedWithTurnServer,
6369
- });
6526
+ if (!turnServerInfo) {
6527
+ ({turnServerInfo} = await this.doTurnDiscovery(isRetry, isForced));
6370
6528
  }
6371
6529
 
6372
6530
  const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
@@ -6385,6 +6543,11 @@ export default class Meeting extends StatelessWebexPlugin {
6385
6543
  RemoteMediaManagerEvent.AudioCreated,
6386
6544
  EVENT_TRIGGERS.REMOTE_MEDIA_AUDIO_CREATED
6387
6545
  );
6546
+ this.forwardEvent(
6547
+ this.remoteMediaManager,
6548
+ RemoteMediaManagerEvent.InterpretationAudioCreated,
6549
+ EVENT_TRIGGERS.REMOTE_MEDIA_INTERPRETATION_AUDIO_CREATED
6550
+ );
6388
6551
  this.forwardEvent(
6389
6552
  this.remoteMediaManager,
6390
6553
  RemoteMediaManagerEvent.ScreenShareAudioCreated,
@@ -6478,15 +6641,21 @@ export default class Meeting extends StatelessWebexPlugin {
6478
6641
  * Creates a media connection to the server. Media connection is required for sending or receiving any audio/video.
6479
6642
  *
6480
6643
  * @param {AddMediaOptions} options
6644
+ * @param {TurnServerInfo} turnServerInfo - TURN server information (used only internally by the SDK)
6481
6645
  * @returns {Promise<void>}
6482
6646
  * @public
6483
6647
  * @memberof Meeting
6484
6648
  */
6485
- async addMedia(options: AddMediaOptions = {}): Promise<void> {
6649
+ async addMedia(
6650
+ options: AddMediaOptions = {},
6651
+ turnServerInfo: TurnServerInfo = undefined
6652
+ ): Promise<void> {
6486
6653
  this.retriedWithTurnServer = false;
6487
6654
  this.hasMediaConnectionConnectedAtLeastOnce = false;
6488
6655
  const LOG_HEADER = 'Meeting:index#addMedia -->';
6489
- LoggerProxy.logger.info(`${LOG_HEADER} called with: ${JSON.stringify(options)}`);
6656
+ LoggerProxy.logger.info(
6657
+ `${LOG_HEADER} called with: ${JSON.stringify(options)}, ${JSON.stringify(turnServerInfo)}`
6658
+ );
6490
6659
 
6491
6660
  if (options.allowMediaInLobby !== true && this.meetingState !== FULL_STATE.ACTIVE) {
6492
6661
  throw new MeetingNotActiveError();
@@ -6504,14 +6673,13 @@ export default class Meeting extends StatelessWebexPlugin {
6504
6673
  shareVideoEnabled = true,
6505
6674
  remoteMediaManagerConfig,
6506
6675
  bundlePolicy,
6507
- allowMediaInLobby,
6508
6676
  } = options;
6509
6677
 
6510
6678
  this.allowMediaInLobby = options?.allowMediaInLobby;
6511
6679
 
6512
6680
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
6513
6681
  // @ts-ignore - isUserUnadmitted coming from SelfUtil
6514
- if (this.isUserUnadmitted && !this.wirelessShare && !allowMediaInLobby) {
6682
+ if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
6515
6683
  throw new UserInLobbyError();
6516
6684
  }
6517
6685
 
@@ -6580,9 +6748,18 @@ export default class Meeting extends StatelessWebexPlugin {
6580
6748
 
6581
6749
  this.createStatsAnalyzer();
6582
6750
 
6583
- await this.establishMediaConnection(remoteMediaManagerConfig, bundlePolicy, false);
6751
+ await this.establishMediaConnection(
6752
+ remoteMediaManagerConfig,
6753
+ bundlePolicy,
6754
+ false,
6755
+ turnServerInfo
6756
+ );
6584
6757
 
6585
- await Meeting.handleDeviceLogging();
6758
+ if (audioEnabled || videoEnabled) {
6759
+ await Meeting.handleDeviceLogging();
6760
+ } else {
6761
+ LoggerProxy.logger.info(`${LOG_HEADER} device logging not required`);
6762
+ }
6586
6763
 
6587
6764
  if (this.mediaProperties.hasLocalShareStream()) {
6588
6765
  await this.enqueueScreenShareFloorRequest();
@@ -7774,9 +7951,9 @@ export default class Meeting extends StatelessWebexPlugin {
7774
7951
 
7775
7952
  /**
7776
7953
  *
7777
- * @returns {string} one of 'attendee','host','cohost', returns the user type of the current user
7954
+ * @returns {string} one of 'panelist', 'attendee', 'host', 'cohost', returns the user type of the current user
7778
7955
  */
7779
- getCurUserType() {
7956
+ getCurUserType(): RawClientEvent['userType'] | null {
7780
7957
  const {roles} = this;
7781
7958
  if (roles) {
7782
7959
  if (roles.includes(SELF_ROLES.MODERATOR)) {
@@ -7785,8 +7962,8 @@ export default class Meeting extends StatelessWebexPlugin {
7785
7962
  if (roles.includes(SELF_ROLES.COHOST)) {
7786
7963
  return 'cohost';
7787
7964
  }
7788
- if (roles.includes(SELF_ROLES.PRESENTER)) {
7789
- return 'presenter';
7965
+ if (roles.includes(SELF_ROLES.PANELIST)) {
7966
+ return 'panelist';
7790
7967
  }
7791
7968
  if (roles.includes(SELF_ROLES.ATTENDEE)) {
7792
7969
  return 'attendee';
@@ -8096,6 +8273,33 @@ export default class Meeting extends StatelessWebexPlugin {
8096
8273
  });
8097
8274
  }
8098
8275
 
8276
+ /**
8277
+ * set sending named media group which the audio should send to
8278
+ * @param {MediaType} mediaType of the stream
8279
+ * @param {number} languageCode of the stream
8280
+ * @returns {void}
8281
+ */
8282
+ public setSendNamedMediaGroup(mediaType: MediaType, languageCode = 0): void {
8283
+ if (mediaType !== MediaType.AudioMain) {
8284
+ throw new Error(`cannot set send named media group which media type is ${mediaType}`);
8285
+ }
8286
+
8287
+ const value = languageCode || this.simultaneousInterpretation.getTargetLanguageCode();
8288
+ let groups = [];
8289
+
8290
+ if (value) {
8291
+ groups = [
8292
+ {
8293
+ type: NAMED_MEDIA_GROUP_TYPE_AUDIO,
8294
+ value,
8295
+ },
8296
+ ];
8297
+ }
8298
+ if (this.isMultistream && this.mediaProperties.webrtcMediaConnection) {
8299
+ this.sendSlotManager.setNamedMediaGroups(mediaType, groups);
8300
+ }
8301
+ }
8302
+
8099
8303
  /**
8100
8304
  * Publishes a stream.
8101
8305
  *
@@ -8164,6 +8368,17 @@ export default class Meeting extends StatelessWebexPlugin {
8164
8368
  return;
8165
8369
  }
8166
8370
 
8371
+ if (
8372
+ streams?.microphone?.readyState === 'ended' ||
8373
+ streams?.camera?.readyState === 'ended' ||
8374
+ streams?.screenShare?.audio?.readyState === 'ended' ||
8375
+ streams?.screenShare?.video?.readyState === 'ended'
8376
+ ) {
8377
+ throw new Error(
8378
+ `Attempted to publish stream with ended readyState, correlationId=${this.correlationId}`
8379
+ );
8380
+ }
8381
+
8167
8382
  let floorRequestNeeded = false;
8168
8383
 
8169
8384
  // Screenshare Audio is supported only in multi stream. So we check for screenshare audio presence only if it's a multi stream meeting