@webex/plugin-meetings 3.0.0-next.3 → 3.0.0-next.30

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 (130) 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 +3 -4
  7. package/dist/constants.js +2 -2
  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 +3 -3
  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 +18 -7
  27. package/dist/meeting/index.js +859 -674
  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 +1 -2
  43. package/dist/multistream/mediaRequestManager.js.map +1 -1
  44. package/dist/multistream/remoteMediaGroup.d.ts +1 -1
  45. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  46. package/dist/multistream/remoteMediaManager.d.ts +1 -2
  47. package/dist/multistream/remoteMediaManager.js.map +1 -1
  48. package/dist/multistream/sendSlotManager.d.ts +1 -2
  49. package/dist/multistream/sendSlotManager.js.map +1 -1
  50. package/dist/reachability/clusterReachability.d.ts +1 -0
  51. package/dist/reachability/clusterReachability.js +29 -15
  52. package/dist/reachability/clusterReachability.js.map +1 -1
  53. package/dist/reachability/index.d.ts +4 -0
  54. package/dist/reachability/index.js +18 -2
  55. package/dist/reachability/index.js.map +1 -1
  56. package/dist/reachability/request.js +12 -10
  57. package/dist/reachability/request.js.map +1 -1
  58. package/dist/reachability/util.d.ts +7 -0
  59. package/dist/reachability/util.js +19 -0
  60. package/dist/reachability/util.js.map +1 -1
  61. package/dist/reconnection-manager/index.js +2 -1
  62. package/dist/reconnection-manager/index.js.map +1 -1
  63. package/dist/roap/index.d.ts +10 -2
  64. package/dist/roap/index.js +15 -0
  65. package/dist/roap/index.js.map +1 -1
  66. package/dist/roap/request.js +2 -2
  67. package/dist/roap/request.js.map +1 -1
  68. package/dist/roap/turnDiscovery.d.ts +64 -17
  69. package/dist/roap/turnDiscovery.js +307 -126
  70. package/dist/roap/turnDiscovery.js.map +1 -1
  71. package/dist/statsAnalyzer/index.js +53 -30
  72. package/dist/statsAnalyzer/index.js.map +1 -1
  73. package/dist/webinar/index.js +1 -1
  74. package/package.json +22 -22
  75. package/src/config.ts +1 -0
  76. package/src/constants.ts +3 -3
  77. package/src/index.ts +1 -0
  78. package/src/interpretation/index.ts +2 -2
  79. package/src/locus-info/mediaSharesUtils.ts +16 -0
  80. package/src/locus-info/selfUtils.ts +5 -0
  81. package/src/media/MediaConnectionAwaiter.ts +174 -0
  82. package/src/media/index.ts +3 -1
  83. package/src/media/properties.ts +6 -31
  84. package/src/meeting/index.ts +283 -105
  85. package/src/meeting/muteState.ts +34 -20
  86. package/src/meeting/request.ts +18 -2
  87. package/src/meeting/util.ts +1 -0
  88. package/src/meeting-info/utilv2.ts +2 -1
  89. package/src/meetings/index.ts +18 -0
  90. package/src/multistream/mediaRequestManager.ts +1 -1
  91. package/src/multistream/remoteMediaGroup.ts +1 -1
  92. package/src/multistream/remoteMediaManager.ts +1 -2
  93. package/src/multistream/sendSlotManager.ts +1 -2
  94. package/src/reachability/clusterReachability.ts +20 -5
  95. package/src/reachability/index.ts +24 -1
  96. package/src/reachability/request.ts +15 -11
  97. package/src/reachability/util.ts +21 -0
  98. package/src/reconnection-manager/index.ts +1 -1
  99. package/src/roap/index.ts +25 -3
  100. package/src/roap/request.ts +2 -2
  101. package/src/roap/turnDiscovery.ts +244 -78
  102. package/src/statsAnalyzer/index.ts +63 -27
  103. package/test/integration/spec/journey.js +14 -14
  104. package/test/integration/spec/space-meeting.js +1 -1
  105. package/test/unit/spec/interpretation/index.ts +4 -1
  106. package/test/unit/spec/locus-info/mediaSharesUtils.ts +9 -0
  107. package/test/unit/spec/locus-info/selfUtils.js +35 -0
  108. package/test/unit/spec/media/MediaConnectionAwaiter.ts +344 -0
  109. package/test/unit/spec/media/index.ts +89 -78
  110. package/test/unit/spec/media/properties.ts +16 -70
  111. package/test/unit/spec/meeting/index.js +618 -143
  112. package/test/unit/spec/meeting/muteState.js +219 -67
  113. package/test/unit/spec/meeting/request.js +21 -0
  114. package/test/unit/spec/meeting/utils.js +6 -1
  115. package/test/unit/spec/meeting-info/utilv2.js +6 -0
  116. package/test/unit/spec/meetings/index.js +40 -20
  117. package/test/unit/spec/multistream/remoteMediaGroup.ts +0 -1
  118. package/test/unit/spec/multistream/remoteMediaManager.ts +0 -1
  119. package/test/unit/spec/reachability/clusterReachability.ts +86 -22
  120. package/test/unit/spec/reachability/index.ts +197 -60
  121. package/test/unit/spec/reachability/request.js +15 -7
  122. package/test/unit/spec/reachability/util.ts +32 -2
  123. package/test/unit/spec/reconnection-manager/index.js +28 -0
  124. package/test/unit/spec/roap/index.ts +61 -6
  125. package/test/unit/spec/roap/turnDiscovery.ts +298 -16
  126. package/test/unit/spec/stats-analyzer/index.js +179 -0
  127. package/dist/member/member.types.d.ts +0 -11
  128. package/dist/member/member.types.js +0 -17
  129. package/dist/member/member.types.js.map +0 -1
  130. package/src/member/member.types.ts +0 -13
@@ -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';
@@ -120,7 +127,6 @@ import {
120
127
  MeetingInfoV2CaptchaError,
121
128
  MeetingInfoV2PolicyError,
122
129
  } from '../meeting-info/meeting-info-v2';
123
- import BrowserDetection from '../common/browser-detection';
124
130
  import {CSI, ReceiveSlotManager} from '../multistream/receiveSlotManager';
125
131
  import SendSlotManager from '../multistream/sendSlotManager';
126
132
  import {MediaRequestManager} from '../multistream/mediaRequestManager';
@@ -148,8 +154,6 @@ import ControlsOptionsManager from '../controls-options-manager';
148
154
  import PermissionError from '../common/errors/permission';
149
155
  import {LocusMediaRequest} from './locusMediaRequest';
150
156
 
151
- const {isBrowser} = BrowserDetection();
152
-
153
157
  const logRequest = (request: any, {logText = ''}) => {
154
158
  LoggerProxy.logger.info(`${logText} - sending request`);
155
159
 
@@ -615,8 +619,8 @@ export default class Meeting extends StatelessWebexPlugin {
615
619
  resourceUrl: string;
616
620
  selfId: string;
617
621
  state: any;
618
- localAudioStreamMuteStateHandler: (muted: boolean) => void;
619
- localVideoStreamMuteStateHandler: (muted: boolean) => void;
622
+ localAudioStreamMuteStateHandler: () => void;
623
+ localVideoStreamMuteStateHandler: () => void;
620
624
  localOutputTrackChangeHandler: () => void;
621
625
  roles: any[];
622
626
  environment: string;
@@ -624,21 +628,23 @@ export default class Meeting extends StatelessWebexPlugin {
624
628
  allowMediaInLobby: boolean;
625
629
  localShareInstanceId: string;
626
630
  remoteShareInstanceId: string;
627
- turnDiscoverySkippedReason: string;
631
+ turnDiscoverySkippedReason: TurnDiscoverySkipReason;
628
632
  turnServerUsed: boolean;
629
633
  areVoiceaEventsSetup = false;
634
+
630
635
  voiceaListenerCallbacks: object = {
631
636
  [VOICEAEVENTS.VOICEA_ANNOUNCEMENT]: (payload: Transcription['languageOptions']) => {
632
637
  this.transcription.languageOptions = payload;
633
- Trigger.trigger(
634
- this,
635
- {
638
+
639
+ LoggerProxy.logger.debug(
640
+ `${EventsUtil.getScopeLog({
636
641
  file: 'meeting/index',
637
642
  function: 'setUpVoiceaListeners',
638
- },
639
- EVENT_TRIGGERS.MEETING_STARTED_RECEIVING_TRANSCRIPTION,
640
- payload
643
+ })}event#${EVENT_TRIGGERS.MEETING_STARTED_RECEIVING_TRANSCRIPTION}`
641
644
  );
645
+
646
+ // @ts-ignore
647
+ this.trigger(EVENT_TRIGGERS.MEETING_STARTED_RECEIVING_TRANSCRIPTION, payload);
642
648
  },
643
649
  [VOICEAEVENTS.CAPTIONS_TURNED_ON]: () => {
644
650
  this.transcription.status = TURN_ON_CAPTION_STATUS.ENABLED;
@@ -651,18 +657,19 @@ export default class Meeting extends StatelessWebexPlugin {
651
657
  },
652
658
  [VOICEAEVENTS.NEW_CAPTION]: (data) => {
653
659
  processNewCaptions({data, meeting: this});
654
- Trigger.trigger(
655
- this,
656
- {
660
+
661
+ LoggerProxy.logger.debug(
662
+ `${EventsUtil.getScopeLog({
657
663
  file: 'meeting/index',
658
664
  function: 'setUpVoiceaListeners',
659
- },
660
- EVENT_TRIGGERS.MEETING_CAPTION_RECEIVED,
661
- {
662
- captions: this.transcription.captions,
663
- interimCaptions: this.transcription.interimCaptions,
664
- }
665
+ })}event#${EVENT_TRIGGERS.MEETING_CAPTION_RECEIVED}`
665
666
  );
667
+
668
+ // @ts-ignore
669
+ this.trigger(EVENT_TRIGGERS.MEETING_CAPTION_RECEIVED, {
670
+ captions: this.transcription.captions,
671
+ interimCaptions: this.transcription.interimCaptions,
672
+ });
666
673
  },
667
674
  };
668
675
 
@@ -1381,12 +1388,12 @@ export default class Meeting extends StatelessWebexPlugin {
1381
1388
  */
1382
1389
  this.remoteMediaManager = null;
1383
1390
 
1384
- this.localAudioStreamMuteStateHandler = (muted: boolean) => {
1385
- this.audio.handleLocalStreamMuteStateChange(this, muted);
1391
+ this.localAudioStreamMuteStateHandler = () => {
1392
+ this.audio.handleLocalStreamMuteStateChange(this);
1386
1393
  };
1387
1394
 
1388
- this.localVideoStreamMuteStateHandler = (muted: boolean) => {
1389
- this.video.handleLocalStreamMuteStateChange(this, muted);
1395
+ this.localVideoStreamMuteStateHandler = () => {
1396
+ this.video.handleLocalStreamMuteStateChange(this);
1390
1397
  };
1391
1398
 
1392
1399
  // The handling of output track changes should be done inside
@@ -2519,6 +2526,7 @@ export default class Meeting extends StatelessWebexPlugin {
2519
2526
  {
2520
2527
  annotationInfo: contentShare?.annotation,
2521
2528
  meetingId: this.id,
2529
+ resourceType: contentShare?.resourceType,
2522
2530
  }
2523
2531
  );
2524
2532
  }
@@ -2547,7 +2555,8 @@ export default class Meeting extends StatelessWebexPlugin {
2547
2555
  contentShare.deviceUrlSharing === previousContentShare.deviceUrlSharing &&
2548
2556
  whiteboardShare.beneficiaryId === previousWhiteboardShare?.beneficiaryId &&
2549
2557
  whiteboardShare.disposition === previousWhiteboardShare?.disposition &&
2550
- whiteboardShare.resourceUrl === previousWhiteboardShare?.resourceUrl
2558
+ whiteboardShare.resourceUrl === previousWhiteboardShare?.resourceUrl &&
2559
+ contentShare.resourceType === previousContentShare?.resourceType
2551
2560
  ) {
2552
2561
  // nothing changed, so ignore
2553
2562
  // (this happens when we steal presentation from remote)
@@ -2669,6 +2678,7 @@ export default class Meeting extends StatelessWebexPlugin {
2669
2678
  url: contentShare.url,
2670
2679
  shareInstanceId: this.remoteShareInstanceId,
2671
2680
  annotationInfo: contentShare.annotation,
2681
+ resourceType: contentShare.resourceType,
2672
2682
  }
2673
2683
  );
2674
2684
  };
@@ -2761,6 +2771,7 @@ export default class Meeting extends StatelessWebexPlugin {
2761
2771
  url: contentShare.url,
2762
2772
  shareInstanceId: this.remoteShareInstanceId,
2763
2773
  annotationInfo: contentShare.annotation,
2774
+ resourceType: contentShare.resourceType,
2764
2775
  }
2765
2776
  );
2766
2777
  this.members.locusMediaSharesUpdate(payload);
@@ -3070,6 +3081,7 @@ export default class Meeting extends StatelessWebexPlugin {
3070
3081
  options: {meetingId: this.id},
3071
3082
  });
3072
3083
  }
3084
+ this.updateLLMConnection();
3073
3085
  });
3074
3086
 
3075
3087
  // @ts-ignore - check if MEDIA_INACTIVITY exists
@@ -3454,8 +3466,10 @@ export default class Meeting extends StatelessWebexPlugin {
3454
3466
  this.owner =
3455
3467
  locusMeetingObject?.info.owner || meetingInfo?.owner || meetingInfo?.hostId || this.owner;
3456
3468
  this.permissionToken = meetingInfo?.permissionToken;
3457
- this.setPermissionTokenPayload(meetingInfo?.permissionToken);
3458
- this.setSelfUserPolicies();
3469
+ if (this.permissionToken) {
3470
+ this.setPermissionTokenPayload(meetingInfo?.permissionToken);
3471
+ this.setSelfUserPolicies();
3472
+ }
3459
3473
  // Need to populate environment when sending CA event
3460
3474
  this.environment = locusMeetingObject?.info.channel || meetingInfo?.channel;
3461
3475
  }
@@ -3891,7 +3905,14 @@ export default class Meeting extends StatelessWebexPlugin {
3891
3905
  private async setLocalAudioStream(localStream?: LocalMicrophoneStream) {
3892
3906
  const oldStream = this.mediaProperties.audioStream;
3893
3907
 
3894
- 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
+ );
3895
3916
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3896
3917
 
3897
3918
  // we don't update this.mediaProperties.mediaDirection.sendAudio, because we always keep it as true to avoid extra SDP exchanges
@@ -3899,7 +3920,14 @@ export default class Meeting extends StatelessWebexPlugin {
3899
3920
 
3900
3921
  this.audio.handleLocalStreamChange(this);
3901
3922
 
3902
- 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
+ );
3903
3931
  localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3904
3932
 
3905
3933
  if (!this.isMultistream || !localStream) {
@@ -3919,7 +3947,14 @@ export default class Meeting extends StatelessWebexPlugin {
3919
3947
  private async setLocalVideoStream(localStream?: LocalCameraStream) {
3920
3948
  const oldStream = this.mediaProperties.videoStream;
3921
3949
 
3922
- 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
+ );
3923
3958
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3924
3959
 
3925
3960
  // we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
@@ -3927,7 +3962,14 @@ export default class Meeting extends StatelessWebexPlugin {
3927
3962
 
3928
3963
  this.video.handleLocalStreamChange(this);
3929
3964
 
3930
- 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
+ );
3931
3973
  localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3932
3974
 
3933
3975
  if (!this.isMultistream || !localStream) {
@@ -3948,14 +3990,17 @@ export default class Meeting extends StatelessWebexPlugin {
3948
3990
  private async setLocalShareVideoStream(localDisplayStream?: LocalDisplayStream) {
3949
3991
  const oldStream = this.mediaProperties.shareVideoStream;
3950
3992
 
3951
- oldStream?.off(StreamEventNames.MuteStateChange, this.handleShareVideoStreamMuteStateChange);
3993
+ oldStream?.off(
3994
+ LocalStreamEventNames.SystemMuteStateChange,
3995
+ this.handleShareVideoStreamMuteStateChange
3996
+ );
3952
3997
  oldStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
3953
3998
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3954
3999
 
3955
4000
  this.mediaProperties.setLocalShareVideoStream(localDisplayStream);
3956
4001
 
3957
4002
  localDisplayStream?.on(
3958
- StreamEventNames.MuteStateChange,
4003
+ LocalStreamEventNames.SystemMuteStateChange,
3959
4004
  this.handleShareVideoStreamMuteStateChange
3960
4005
  );
3961
4006
  localDisplayStream?.on(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
@@ -4041,10 +4086,24 @@ export default class Meeting extends StatelessWebexPlugin {
4041
4086
  public cleanupLocalStreams() {
4042
4087
  const {audioStream, videoStream, shareAudioStream, shareVideoStream} = this.mediaProperties;
4043
4088
 
4044
- 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
+ );
4045
4097
  audioStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4046
4098
 
4047
- 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
+ );
4048
4107
  videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4049
4108
 
4050
4109
  shareAudioStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
@@ -4052,8 +4111,9 @@ export default class Meeting extends StatelessWebexPlugin {
4052
4111
  LocalStreamEventNames.OutputTrackChange,
4053
4112
  this.localOutputTrackChangeHandler
4054
4113
  );
4114
+
4055
4115
  shareVideoStream?.off(
4056
- StreamEventNames.MuteStateChange,
4116
+ LocalStreamEventNames.SystemMuteStateChange,
4057
4117
  this.handleShareVideoStreamMuteStateChange
4058
4118
  );
4059
4119
  shareVideoStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
@@ -4445,47 +4505,90 @@ export default class Meeting extends StatelessWebexPlugin {
4445
4505
  * }
4446
4506
  * })
4447
4507
  */
4448
- public joinWithMedia(
4508
+ public async joinWithMedia(
4449
4509
  options: {
4450
4510
  joinOptions?: any;
4451
4511
  mediaOptions?: AddMediaOptions;
4452
4512
  } = {}
4453
4513
  ) {
4454
- const {mediaOptions, joinOptions} = options;
4514
+ const {mediaOptions, joinOptions = {}} = options;
4455
4515
 
4456
4516
  if (!mediaOptions?.allowMediaInLobby) {
4457
4517
  return Promise.reject(
4458
4518
  new ParameterError('joinWithMedia() can only be used with allowMediaInLobby set to true')
4459
4519
  );
4460
4520
  }
4521
+ this.allowMediaInLobby = true;
4461
4522
 
4462
4523
  LoggerProxy.logger.info('Meeting:index#joinWithMedia called');
4463
4524
 
4464
- return this.join(joinOptions)
4465
- .then((joinResponse) =>
4466
- this.addMedia(mediaOptions).then((mediaResponse) => ({
4467
- join: joinResponse,
4468
- media: mediaResponse,
4469
- }))
4470
- )
4471
- .catch((error) => {
4472
- LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);
4525
+ let joined = false;
4473
4526
 
4474
- Metrics.sendBehavioralMetric(
4475
- BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
4476
- {
4477
- correlation_id: this.correlationId,
4478
- locus_id: this.locusUrl.split('/').pop(),
4479
- reason: error.message,
4480
- stack: error.stack,
4481
- },
4482
- {
4483
- type: error.name,
4484
- }
4485
- );
4527
+ try {
4528
+ let turnServerInfo;
4529
+ let turnDiscoverySkippedReason;
4486
4530
 
4487
- return Promise.reject(error);
4488
- });
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
+ }
4489
4592
  }
4490
4593
 
4491
4594
  /**
@@ -5608,7 +5711,7 @@ export default class Meeting extends StatelessWebexPlugin {
5608
5711
  logText: `${LOG_HEADER} Roap Offer`,
5609
5712
  }
5610
5713
  ).catch(() => {
5611
- this.deferSDPAnswer.reject();
5714
+ this.deferSDPAnswer.reject(new Error('failed to send ROAP SDP offer'));
5612
5715
  clearTimeout(this.sdpResponseTimer);
5613
5716
  this.sdpResponseTimer = undefined;
5614
5717
  });
@@ -5955,6 +6058,20 @@ export default class Meeting extends StatelessWebexPlugin {
5955
6058
  meetingId: this.id,
5956
6059
  },
5957
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
+ }
5958
6075
  });
5959
6076
  this.statsAnalyzer.on(StatsAnalyzerEvents.REMOTE_MEDIA_STOPPED, (data) => {
5960
6077
  // @ts-ignore
@@ -5968,6 +6085,20 @@ export default class Meeting extends StatelessWebexPlugin {
5968
6085
  meetingId: this.id,
5969
6086
  },
5970
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
+ }
5971
6102
  });
5972
6103
  };
5973
6104
 
@@ -6074,16 +6205,22 @@ export default class Meeting extends StatelessWebexPlugin {
6074
6205
  private async setUpLocalStreamReferences(localStreams: LocalStreams) {
6075
6206
  const setUpStreamPromises = [];
6076
6207
 
6077
- if (localStreams?.microphone) {
6208
+ if (localStreams?.microphone && localStreams?.microphone?.readyState !== 'ended') {
6078
6209
  setUpStreamPromises.push(this.setLocalAudioStream(localStreams.microphone));
6079
6210
  }
6080
- if (localStreams?.camera) {
6211
+ if (localStreams?.camera && localStreams?.camera?.readyState !== 'ended') {
6081
6212
  setUpStreamPromises.push(this.setLocalVideoStream(localStreams.camera));
6082
6213
  }
6083
- if (localStreams?.screenShare?.video) {
6214
+ if (
6215
+ localStreams?.screenShare?.video &&
6216
+ localStreams?.screenShare?.video?.readyState !== 'ended'
6217
+ ) {
6084
6218
  setUpStreamPromises.push(this.setLocalShareVideoStream(localStreams.screenShare.video));
6085
6219
  }
6086
- if (localStreams?.screenShare?.audio) {
6220
+ if (
6221
+ localStreams?.screenShare?.audio &&
6222
+ localStreams?.screenShare?.audio?.readyState !== 'ended'
6223
+ ) {
6087
6224
  setUpStreamPromises.push(this.setLocalShareAudioStream(localStreams.screenShare.audio));
6088
6225
  }
6089
6226
 
@@ -6328,6 +6465,44 @@ export default class Meeting extends StatelessWebexPlugin {
6328
6465
  }
6329
6466
  }
6330
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
+
6331
6506
  /**
6332
6507
  * Does TURN discovery, SDP offer/answer exhange, establishes ICE connection and DTLS handshake.
6333
6508
  *
@@ -6335,43 +6510,21 @@ export default class Meeting extends StatelessWebexPlugin {
6335
6510
  * @param {RemoteMediaManagerConfiguration} [remoteMediaManagerConfig]
6336
6511
  * @param {BundlePolicy} [bundlePolicy]
6337
6512
  * @param {boolean} [isForced] - let isForced be true to do turn discovery regardless of reachability results
6513
+ * @param {TurnServerInfo} [turnServerInfo]
6338
6514
  * @returns {Promise<void>}
6339
6515
  */
6340
6516
  private async establishMediaConnection(
6341
6517
  remoteMediaManagerConfig?: RemoteMediaManagerConfiguration,
6342
6518
  bundlePolicy?: BundlePolicy,
6343
- isForced?: boolean
6519
+ isForced?: boolean,
6520
+ turnServerInfo?: TurnServerInfo
6344
6521
  ): Promise<void> {
6345
6522
  const LOG_HEADER = 'Meeting:index#addMedia():establishMediaConnection -->';
6346
- // @ts-ignore
6347
- const cdl = this.webex.internal.newMetrics.callDiagnosticLatencies;
6348
6523
  const isRetry = this.retriedWithTurnServer;
6349
6524
 
6350
6525
  try {
6351
- // @ts-ignore
6352
- this.webex.internal.newMetrics.submitInternalEvent({
6353
- name: 'internal.client.add-media.turn-discovery.start',
6354
- });
6355
-
6356
- const turnDiscoveryObject = await this.roap.doTurnDiscovery(this, isRetry, isForced);
6357
-
6358
- this.turnDiscoverySkippedReason = turnDiscoveryObject?.turnDiscoverySkippedReason;
6359
- this.turnServerUsed = !this.turnDiscoverySkippedReason;
6360
-
6361
- // @ts-ignore
6362
- this.webex.internal.newMetrics.submitInternalEvent({
6363
- name: 'internal.client.add-media.turn-discovery.end',
6364
- });
6365
-
6366
- const {turnServerInfo} = turnDiscoveryObject;
6367
-
6368
- if (this.turnServerUsed && turnServerInfo) {
6369
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_LATENCY, {
6370
- correlation_id: this.correlationId,
6371
- latency: cdl.getTurnDiscoveryTime(),
6372
- turnServerUsed: this.turnServerUsed,
6373
- retriedWithTurnServer: this.retriedWithTurnServer,
6374
- });
6526
+ if (!turnServerInfo) {
6527
+ ({turnServerInfo} = await this.doTurnDiscovery(isRetry, isForced));
6375
6528
  }
6376
6529
 
6377
6530
  const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
@@ -6488,15 +6641,21 @@ export default class Meeting extends StatelessWebexPlugin {
6488
6641
  * Creates a media connection to the server. Media connection is required for sending or receiving any audio/video.
6489
6642
  *
6490
6643
  * @param {AddMediaOptions} options
6644
+ * @param {TurnServerInfo} turnServerInfo - TURN server information (used only internally by the SDK)
6491
6645
  * @returns {Promise<void>}
6492
6646
  * @public
6493
6647
  * @memberof Meeting
6494
6648
  */
6495
- async addMedia(options: AddMediaOptions = {}): Promise<void> {
6649
+ async addMedia(
6650
+ options: AddMediaOptions = {},
6651
+ turnServerInfo: TurnServerInfo = undefined
6652
+ ): Promise<void> {
6496
6653
  this.retriedWithTurnServer = false;
6497
6654
  this.hasMediaConnectionConnectedAtLeastOnce = false;
6498
6655
  const LOG_HEADER = 'Meeting:index#addMedia -->';
6499
- 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
+ );
6500
6659
 
6501
6660
  if (options.allowMediaInLobby !== true && this.meetingState !== FULL_STATE.ACTIVE) {
6502
6661
  throw new MeetingNotActiveError();
@@ -6514,14 +6673,13 @@ export default class Meeting extends StatelessWebexPlugin {
6514
6673
  shareVideoEnabled = true,
6515
6674
  remoteMediaManagerConfig,
6516
6675
  bundlePolicy,
6517
- allowMediaInLobby,
6518
6676
  } = options;
6519
6677
 
6520
6678
  this.allowMediaInLobby = options?.allowMediaInLobby;
6521
6679
 
6522
6680
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
6523
6681
  // @ts-ignore - isUserUnadmitted coming from SelfUtil
6524
- if (this.isUserUnadmitted && !this.wirelessShare && !allowMediaInLobby) {
6682
+ if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
6525
6683
  throw new UserInLobbyError();
6526
6684
  }
6527
6685
 
@@ -6590,9 +6748,18 @@ export default class Meeting extends StatelessWebexPlugin {
6590
6748
 
6591
6749
  this.createStatsAnalyzer();
6592
6750
 
6593
- await this.establishMediaConnection(remoteMediaManagerConfig, bundlePolicy, false);
6751
+ await this.establishMediaConnection(
6752
+ remoteMediaManagerConfig,
6753
+ bundlePolicy,
6754
+ false,
6755
+ turnServerInfo
6756
+ );
6594
6757
 
6595
- await Meeting.handleDeviceLogging();
6758
+ if (audioEnabled || videoEnabled) {
6759
+ await Meeting.handleDeviceLogging();
6760
+ } else {
6761
+ LoggerProxy.logger.info(`${LOG_HEADER} device logging not required`);
6762
+ }
6596
6763
 
6597
6764
  if (this.mediaProperties.hasLocalShareStream()) {
6598
6765
  await this.enqueueScreenShareFloorRequest();
@@ -7784,9 +7951,9 @@ export default class Meeting extends StatelessWebexPlugin {
7784
7951
 
7785
7952
  /**
7786
7953
  *
7787
- * @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
7788
7955
  */
7789
- getCurUserType() {
7956
+ getCurUserType(): RawClientEvent['userType'] | null {
7790
7957
  const {roles} = this;
7791
7958
  if (roles) {
7792
7959
  if (roles.includes(SELF_ROLES.MODERATOR)) {
@@ -7795,8 +7962,8 @@ export default class Meeting extends StatelessWebexPlugin {
7795
7962
  if (roles.includes(SELF_ROLES.COHOST)) {
7796
7963
  return 'cohost';
7797
7964
  }
7798
- if (roles.includes(SELF_ROLES.PRESENTER)) {
7799
- return 'presenter';
7965
+ if (roles.includes(SELF_ROLES.PANELIST)) {
7966
+ return 'panelist';
7800
7967
  }
7801
7968
  if (roles.includes(SELF_ROLES.ATTENDEE)) {
7802
7969
  return 'attendee';
@@ -8201,6 +8368,17 @@ export default class Meeting extends StatelessWebexPlugin {
8201
8368
  return;
8202
8369
  }
8203
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
+
8204
8382
  let floorRequestNeeded = false;
8205
8383
 
8206
8384
  // Screenshare Audio is supported only in multi stream. So we check for screenshare audio presence only if it's a multi stream meeting