@webex/plugin-meetings 3.0.0-next.2 → 3.0.0-next.21

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 (94) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.d.ts +3 -9
  4. package/dist/constants.js +4 -9
  5. package/dist/constants.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +6 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/interpretation/index.js +3 -3
  10. package/dist/interpretation/index.js.map +1 -1
  11. package/dist/interpretation/siLanguage.js +1 -1
  12. package/dist/locus-info/mediaSharesUtils.js +15 -1
  13. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  14. package/dist/media/index.js +4 -1
  15. package/dist/media/index.js.map +1 -1
  16. package/dist/mediaQualityMetrics/config.d.ts +9 -1
  17. package/dist/mediaQualityMetrics/config.js +10 -1
  18. package/dist/mediaQualityMetrics/config.js.map +1 -1
  19. package/dist/meeting/index.d.ts +18 -7
  20. package/dist/meeting/index.js +745 -567
  21. package/dist/meeting/index.js.map +1 -1
  22. package/dist/meeting/muteState.d.ts +2 -8
  23. package/dist/meeting/muteState.js +37 -25
  24. package/dist/meeting/muteState.js.map +1 -1
  25. package/dist/meeting/request.d.ts +3 -0
  26. package/dist/meeting/request.js +32 -23
  27. package/dist/meeting/request.js.map +1 -1
  28. package/dist/meeting/util.js +1 -0
  29. package/dist/meeting/util.js.map +1 -1
  30. package/dist/multistream/mediaRequestManager.d.ts +1 -2
  31. package/dist/multistream/mediaRequestManager.js.map +1 -1
  32. package/dist/multistream/remoteMediaGroup.d.ts +1 -1
  33. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  34. package/dist/multistream/remoteMediaManager.d.ts +1 -2
  35. package/dist/multistream/remoteMediaManager.js.map +1 -1
  36. package/dist/multistream/sendSlotManager.d.ts +1 -2
  37. package/dist/multistream/sendSlotManager.js.map +1 -1
  38. package/dist/reachability/request.js +12 -10
  39. package/dist/reachability/request.js.map +1 -1
  40. package/dist/reconnection-manager/index.js +2 -1
  41. package/dist/reconnection-manager/index.js.map +1 -1
  42. package/dist/roap/index.d.ts +10 -2
  43. package/dist/roap/index.js +15 -0
  44. package/dist/roap/index.js.map +1 -1
  45. package/dist/roap/request.js +3 -3
  46. package/dist/roap/request.js.map +1 -1
  47. package/dist/roap/turnDiscovery.d.ts +64 -17
  48. package/dist/roap/turnDiscovery.js +307 -126
  49. package/dist/roap/turnDiscovery.js.map +1 -1
  50. package/dist/statsAnalyzer/index.js +61 -41
  51. package/dist/statsAnalyzer/index.js.map +1 -1
  52. package/dist/webinar/index.js +1 -1
  53. package/package.json +22 -22
  54. package/src/constants.ts +3 -9
  55. package/src/index.ts +1 -0
  56. package/src/interpretation/index.ts +2 -2
  57. package/src/locus-info/mediaSharesUtils.ts +16 -0
  58. package/src/media/index.ts +3 -1
  59. package/src/mediaQualityMetrics/config.ts +11 -1
  60. package/src/meeting/index.ts +264 -90
  61. package/src/meeting/muteState.ts +34 -20
  62. package/src/meeting/request.ts +18 -2
  63. package/src/meeting/util.ts +1 -0
  64. package/src/multistream/mediaRequestManager.ts +1 -1
  65. package/src/multistream/remoteMediaGroup.ts +1 -1
  66. package/src/multistream/remoteMediaManager.ts +1 -2
  67. package/src/multistream/sendSlotManager.ts +1 -2
  68. package/src/reachability/request.ts +15 -11
  69. package/src/reconnection-manager/index.ts +1 -1
  70. package/src/roap/index.ts +25 -3
  71. package/src/roap/request.ts +3 -3
  72. package/src/roap/turnDiscovery.ts +244 -78
  73. package/src/statsAnalyzer/index.ts +72 -43
  74. package/test/integration/spec/journey.js +14 -14
  75. package/test/integration/spec/space-meeting.js +1 -1
  76. package/test/unit/spec/interpretation/index.ts +4 -1
  77. package/test/unit/spec/locus-info/mediaSharesUtils.ts +9 -0
  78. package/test/unit/spec/media/index.ts +89 -78
  79. package/test/unit/spec/meeting/index.js +611 -125
  80. package/test/unit/spec/meeting/muteState.js +219 -67
  81. package/test/unit/spec/meeting/request.js +21 -0
  82. package/test/unit/spec/meeting/utils.js +6 -1
  83. package/test/unit/spec/meetings/index.js +27 -20
  84. package/test/unit/spec/multistream/remoteMediaGroup.ts +0 -1
  85. package/test/unit/spec/multistream/remoteMediaManager.ts +0 -1
  86. package/test/unit/spec/reachability/request.js +15 -7
  87. package/test/unit/spec/reconnection-manager/index.js +28 -0
  88. package/test/unit/spec/roap/index.ts +61 -6
  89. package/test/unit/spec/roap/turnDiscovery.ts +298 -16
  90. package/test/unit/spec/stats-analyzer/index.js +183 -8
  91. package/dist/member/member.types.d.ts +0 -11
  92. package/dist/member/member.types.js +0 -17
  93. package/dist/member/member.types.js.map +0 -1
  94. 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,
@@ -51,7 +53,11 @@ import {StatsAnalyzer, EVENTS as StatsAnalyzerEvents} from '../statsAnalyzer';
51
53
  import NetworkQualityMonitor from '../networkQualityMonitor';
52
54
  import LoggerProxy from '../common/logs/logger-proxy';
53
55
  import Trigger from '../common/events/trigger-proxy';
54
- import Roap from '../roap/index';
56
+ import Roap, {
57
+ type TurnDiscoveryResult,
58
+ type TurnServerInfo,
59
+ type TurnDiscoverySkipReason,
60
+ } from '../roap/index';
55
61
  import Media, {type BundlePolicy} from '../media';
56
62
  import MediaProperties from '../media/properties';
57
63
  import MeetingStateMachine from './state';
@@ -120,7 +126,6 @@ import {
120
126
  MeetingInfoV2CaptchaError,
121
127
  MeetingInfoV2PolicyError,
122
128
  } from '../meeting-info/meeting-info-v2';
123
- import BrowserDetection from '../common/browser-detection';
124
129
  import {CSI, ReceiveSlotManager} from '../multistream/receiveSlotManager';
125
130
  import SendSlotManager from '../multistream/sendSlotManager';
126
131
  import {MediaRequestManager} from '../multistream/mediaRequestManager';
@@ -148,8 +153,6 @@ import ControlsOptionsManager from '../controls-options-manager';
148
153
  import PermissionError from '../common/errors/permission';
149
154
  import {LocusMediaRequest} from './locusMediaRequest';
150
155
 
151
- const {isBrowser} = BrowserDetection();
152
-
153
156
  const logRequest = (request: any, {logText = ''}) => {
154
157
  LoggerProxy.logger.info(`${logText} - sending request`);
155
158
 
@@ -615,8 +618,8 @@ export default class Meeting extends StatelessWebexPlugin {
615
618
  resourceUrl: string;
616
619
  selfId: string;
617
620
  state: any;
618
- localAudioStreamMuteStateHandler: (muted: boolean) => void;
619
- localVideoStreamMuteStateHandler: (muted: boolean) => void;
621
+ localAudioStreamMuteStateHandler: () => void;
622
+ localVideoStreamMuteStateHandler: () => void;
620
623
  localOutputTrackChangeHandler: () => void;
621
624
  roles: any[];
622
625
  environment: string;
@@ -624,7 +627,7 @@ export default class Meeting extends StatelessWebexPlugin {
624
627
  allowMediaInLobby: boolean;
625
628
  localShareInstanceId: string;
626
629
  remoteShareInstanceId: string;
627
- turnDiscoverySkippedReason: string;
630
+ turnDiscoverySkippedReason: TurnDiscoverySkipReason;
628
631
  turnServerUsed: boolean;
629
632
  areVoiceaEventsSetup = false;
630
633
  voiceaListenerCallbacks: object = {
@@ -1381,12 +1384,12 @@ export default class Meeting extends StatelessWebexPlugin {
1381
1384
  */
1382
1385
  this.remoteMediaManager = null;
1383
1386
 
1384
- this.localAudioStreamMuteStateHandler = (muted: boolean) => {
1385
- this.audio.handleLocalStreamMuteStateChange(this, muted);
1387
+ this.localAudioStreamMuteStateHandler = () => {
1388
+ this.audio.handleLocalStreamMuteStateChange(this);
1386
1389
  };
1387
1390
 
1388
- this.localVideoStreamMuteStateHandler = (muted: boolean) => {
1389
- this.video.handleLocalStreamMuteStateChange(this, muted);
1391
+ this.localVideoStreamMuteStateHandler = () => {
1392
+ this.video.handleLocalStreamMuteStateChange(this);
1390
1393
  };
1391
1394
 
1392
1395
  // The handling of output track changes should be done inside
@@ -2519,6 +2522,7 @@ export default class Meeting extends StatelessWebexPlugin {
2519
2522
  {
2520
2523
  annotationInfo: contentShare?.annotation,
2521
2524
  meetingId: this.id,
2525
+ resourceType: contentShare?.resourceType,
2522
2526
  }
2523
2527
  );
2524
2528
  }
@@ -2547,7 +2551,8 @@ export default class Meeting extends StatelessWebexPlugin {
2547
2551
  contentShare.deviceUrlSharing === previousContentShare.deviceUrlSharing &&
2548
2552
  whiteboardShare.beneficiaryId === previousWhiteboardShare?.beneficiaryId &&
2549
2553
  whiteboardShare.disposition === previousWhiteboardShare?.disposition &&
2550
- whiteboardShare.resourceUrl === previousWhiteboardShare?.resourceUrl
2554
+ whiteboardShare.resourceUrl === previousWhiteboardShare?.resourceUrl &&
2555
+ contentShare.resourceType === previousContentShare?.resourceType
2551
2556
  ) {
2552
2557
  // nothing changed, so ignore
2553
2558
  // (this happens when we steal presentation from remote)
@@ -2669,6 +2674,7 @@ export default class Meeting extends StatelessWebexPlugin {
2669
2674
  url: contentShare.url,
2670
2675
  shareInstanceId: this.remoteShareInstanceId,
2671
2676
  annotationInfo: contentShare.annotation,
2677
+ resourceType: contentShare.resourceType,
2672
2678
  }
2673
2679
  );
2674
2680
  };
@@ -2761,6 +2767,7 @@ export default class Meeting extends StatelessWebexPlugin {
2761
2767
  url: contentShare.url,
2762
2768
  shareInstanceId: this.remoteShareInstanceId,
2763
2769
  annotationInfo: contentShare.annotation,
2770
+ resourceType: contentShare.resourceType,
2764
2771
  }
2765
2772
  );
2766
2773
  this.members.locusMediaSharesUpdate(payload);
@@ -3070,6 +3077,7 @@ export default class Meeting extends StatelessWebexPlugin {
3070
3077
  options: {meetingId: this.id},
3071
3078
  });
3072
3079
  }
3080
+ this.updateLLMConnection();
3073
3081
  });
3074
3082
 
3075
3083
  // @ts-ignore - check if MEDIA_INACTIVITY exists
@@ -3454,8 +3462,10 @@ export default class Meeting extends StatelessWebexPlugin {
3454
3462
  this.owner =
3455
3463
  locusMeetingObject?.info.owner || meetingInfo?.owner || meetingInfo?.hostId || this.owner;
3456
3464
  this.permissionToken = meetingInfo?.permissionToken;
3457
- this.setPermissionTokenPayload(meetingInfo?.permissionToken);
3458
- this.setSelfUserPolicies();
3465
+ if (this.permissionToken) {
3466
+ this.setPermissionTokenPayload(meetingInfo?.permissionToken);
3467
+ this.setSelfUserPolicies();
3468
+ }
3459
3469
  // Need to populate environment when sending CA event
3460
3470
  this.environment = locusMeetingObject?.info.channel || meetingInfo?.channel;
3461
3471
  }
@@ -3891,7 +3901,14 @@ export default class Meeting extends StatelessWebexPlugin {
3891
3901
  private async setLocalAudioStream(localStream?: LocalMicrophoneStream) {
3892
3902
  const oldStream = this.mediaProperties.audioStream;
3893
3903
 
3894
- oldStream?.off(StreamEventNames.MuteStateChange, this.localAudioStreamMuteStateHandler);
3904
+ oldStream?.off(
3905
+ LocalStreamEventNames.UserMuteStateChange,
3906
+ this.localAudioStreamMuteStateHandler
3907
+ );
3908
+ oldStream?.off(
3909
+ LocalStreamEventNames.SystemMuteStateChange,
3910
+ this.localAudioStreamMuteStateHandler
3911
+ );
3895
3912
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3896
3913
 
3897
3914
  // we don't update this.mediaProperties.mediaDirection.sendAudio, because we always keep it as true to avoid extra SDP exchanges
@@ -3899,7 +3916,14 @@ export default class Meeting extends StatelessWebexPlugin {
3899
3916
 
3900
3917
  this.audio.handleLocalStreamChange(this);
3901
3918
 
3902
- localStream?.on(StreamEventNames.MuteStateChange, this.localAudioStreamMuteStateHandler);
3919
+ localStream?.on(
3920
+ LocalStreamEventNames.UserMuteStateChange,
3921
+ this.localAudioStreamMuteStateHandler
3922
+ );
3923
+ localStream?.on(
3924
+ LocalStreamEventNames.SystemMuteStateChange,
3925
+ this.localAudioStreamMuteStateHandler
3926
+ );
3903
3927
  localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3904
3928
 
3905
3929
  if (!this.isMultistream || !localStream) {
@@ -3919,7 +3943,14 @@ export default class Meeting extends StatelessWebexPlugin {
3919
3943
  private async setLocalVideoStream(localStream?: LocalCameraStream) {
3920
3944
  const oldStream = this.mediaProperties.videoStream;
3921
3945
 
3922
- oldStream?.off(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
3946
+ oldStream?.off(
3947
+ LocalStreamEventNames.UserMuteStateChange,
3948
+ this.localVideoStreamMuteStateHandler
3949
+ );
3950
+ oldStream?.off(
3951
+ LocalStreamEventNames.SystemMuteStateChange,
3952
+ this.localVideoStreamMuteStateHandler
3953
+ );
3923
3954
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3924
3955
 
3925
3956
  // we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
@@ -3927,7 +3958,14 @@ export default class Meeting extends StatelessWebexPlugin {
3927
3958
 
3928
3959
  this.video.handleLocalStreamChange(this);
3929
3960
 
3930
- localStream?.on(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
3961
+ localStream?.on(
3962
+ LocalStreamEventNames.UserMuteStateChange,
3963
+ this.localVideoStreamMuteStateHandler
3964
+ );
3965
+ localStream?.on(
3966
+ LocalStreamEventNames.SystemMuteStateChange,
3967
+ this.localVideoStreamMuteStateHandler
3968
+ );
3931
3969
  localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3932
3970
 
3933
3971
  if (!this.isMultistream || !localStream) {
@@ -3948,14 +3986,17 @@ export default class Meeting extends StatelessWebexPlugin {
3948
3986
  private async setLocalShareVideoStream(localDisplayStream?: LocalDisplayStream) {
3949
3987
  const oldStream = this.mediaProperties.shareVideoStream;
3950
3988
 
3951
- oldStream?.off(StreamEventNames.MuteStateChange, this.handleShareVideoStreamMuteStateChange);
3989
+ oldStream?.off(
3990
+ LocalStreamEventNames.SystemMuteStateChange,
3991
+ this.handleShareVideoStreamMuteStateChange
3992
+ );
3952
3993
  oldStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
3953
3994
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
3954
3995
 
3955
3996
  this.mediaProperties.setLocalShareVideoStream(localDisplayStream);
3956
3997
 
3957
3998
  localDisplayStream?.on(
3958
- StreamEventNames.MuteStateChange,
3999
+ LocalStreamEventNames.SystemMuteStateChange,
3959
4000
  this.handleShareVideoStreamMuteStateChange
3960
4001
  );
3961
4002
  localDisplayStream?.on(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
@@ -4041,10 +4082,24 @@ export default class Meeting extends StatelessWebexPlugin {
4041
4082
  public cleanupLocalStreams() {
4042
4083
  const {audioStream, videoStream, shareAudioStream, shareVideoStream} = this.mediaProperties;
4043
4084
 
4044
- audioStream?.off(StreamEventNames.MuteStateChange, this.localAudioStreamMuteStateHandler);
4085
+ audioStream?.off(
4086
+ LocalStreamEventNames.UserMuteStateChange,
4087
+ this.localAudioStreamMuteStateHandler
4088
+ );
4089
+ audioStream?.off(
4090
+ LocalStreamEventNames.SystemMuteStateChange,
4091
+ this.localAudioStreamMuteStateHandler
4092
+ );
4045
4093
  audioStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4046
4094
 
4047
- videoStream?.off(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
4095
+ videoStream?.off(
4096
+ LocalStreamEventNames.UserMuteStateChange,
4097
+ this.localVideoStreamMuteStateHandler
4098
+ );
4099
+ videoStream?.off(
4100
+ LocalStreamEventNames.SystemMuteStateChange,
4101
+ this.localVideoStreamMuteStateHandler
4102
+ );
4048
4103
  videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4049
4104
 
4050
4105
  shareAudioStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
@@ -4052,8 +4107,9 @@ export default class Meeting extends StatelessWebexPlugin {
4052
4107
  LocalStreamEventNames.OutputTrackChange,
4053
4108
  this.localOutputTrackChangeHandler
4054
4109
  );
4110
+
4055
4111
  shareVideoStream?.off(
4056
- StreamEventNames.MuteStateChange,
4112
+ LocalStreamEventNames.SystemMuteStateChange,
4057
4113
  this.handleShareVideoStreamMuteStateChange
4058
4114
  );
4059
4115
  shareVideoStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
@@ -4445,47 +4501,90 @@ export default class Meeting extends StatelessWebexPlugin {
4445
4501
  * }
4446
4502
  * })
4447
4503
  */
4448
- public joinWithMedia(
4504
+ public async joinWithMedia(
4449
4505
  options: {
4450
4506
  joinOptions?: any;
4451
4507
  mediaOptions?: AddMediaOptions;
4452
4508
  } = {}
4453
4509
  ) {
4454
- const {mediaOptions, joinOptions} = options;
4510
+ const {mediaOptions, joinOptions = {}} = options;
4455
4511
 
4456
4512
  if (!mediaOptions?.allowMediaInLobby) {
4457
4513
  return Promise.reject(
4458
4514
  new ParameterError('joinWithMedia() can only be used with allowMediaInLobby set to true')
4459
4515
  );
4460
4516
  }
4517
+ this.allowMediaInLobby = true;
4461
4518
 
4462
4519
  LoggerProxy.logger.info('Meeting:index#joinWithMedia called');
4463
4520
 
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);
4521
+ let joined = false;
4473
4522
 
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
- );
4523
+ try {
4524
+ let turnServerInfo;
4525
+ let turnDiscoverySkippedReason;
4486
4526
 
4487
- return Promise.reject(error);
4488
- });
4527
+ // @ts-ignore
4528
+ joinOptions.reachability = await this.webex.meetings.reachability.getReachabilityResults();
4529
+ const turnDiscoveryRequest = await this.roap.generateTurnDiscoveryRequestMessage(this, true);
4530
+
4531
+ ({turnDiscoverySkippedReason} = turnDiscoveryRequest);
4532
+ joinOptions.roapMessage = turnDiscoveryRequest.roapMessage;
4533
+
4534
+ const joinResponse = await this.join(joinOptions);
4535
+
4536
+ joined = true;
4537
+
4538
+ if (joinOptions.roapMessage) {
4539
+ ({turnServerInfo, turnDiscoverySkippedReason} =
4540
+ await this.roap.handleTurnDiscoveryHttpResponse(this, joinResponse));
4541
+
4542
+ this.turnDiscoverySkippedReason = turnDiscoverySkippedReason;
4543
+ this.turnServerUsed = !!turnServerInfo;
4544
+
4545
+ if (turnServerInfo === undefined) {
4546
+ this.roap.abortTurnDiscovery();
4547
+ }
4548
+ }
4549
+
4550
+ const mediaResponse = await this.addMedia(mediaOptions, turnServerInfo);
4551
+
4552
+ return {
4553
+ join: joinResponse,
4554
+ media: mediaResponse,
4555
+ };
4556
+ } catch (error) {
4557
+ LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);
4558
+
4559
+ let leaveError;
4560
+
4561
+ this.roap.abortTurnDiscovery();
4562
+
4563
+ if (joined) {
4564
+ try {
4565
+ await this.leave({resourceId: joinOptions?.resourceId, reason: 'joinWithMedia failure'});
4566
+ } catch (e) {
4567
+ LoggerProxy.logger.error('Meeting:index#joinWithMedia --> leave error', e);
4568
+ leaveError = e;
4569
+ }
4570
+ }
4571
+
4572
+ Metrics.sendBehavioralMetric(
4573
+ BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
4574
+ {
4575
+ correlation_id: this.correlationId,
4576
+ locus_id: this.locusUrl?.split('/').pop(), // if join fails, we may end up with no locusUrl
4577
+ reason: error.message,
4578
+ stack: error.stack,
4579
+ leaveErrorReason: leaveError?.message,
4580
+ },
4581
+ {
4582
+ type: error.name,
4583
+ }
4584
+ );
4585
+
4586
+ throw error;
4587
+ }
4489
4588
  }
4490
4589
 
4491
4590
  /**
@@ -5608,7 +5707,7 @@ export default class Meeting extends StatelessWebexPlugin {
5608
5707
  logText: `${LOG_HEADER} Roap Offer`,
5609
5708
  }
5610
5709
  ).catch(() => {
5611
- this.deferSDPAnswer.reject();
5710
+ this.deferSDPAnswer.reject(new Error('failed to send ROAP SDP offer'));
5612
5711
  clearTimeout(this.sdpResponseTimer);
5613
5712
  this.sdpResponseTimer = undefined;
5614
5713
  });
@@ -5955,6 +6054,20 @@ export default class Meeting extends StatelessWebexPlugin {
5955
6054
  meetingId: this.id,
5956
6055
  },
5957
6056
  });
6057
+
6058
+ if (data.type === 'share') {
6059
+ // @ts-ignore
6060
+ this.webex.internal.newMetrics.submitClientEvent({
6061
+ name: 'client.media.render.start',
6062
+ payload: {
6063
+ mediaType: 'share',
6064
+ shareInstanceId: this.remoteShareInstanceId,
6065
+ },
6066
+ options: {
6067
+ meetingId: this.id,
6068
+ },
6069
+ });
6070
+ }
5958
6071
  });
5959
6072
  this.statsAnalyzer.on(StatsAnalyzerEvents.REMOTE_MEDIA_STOPPED, (data) => {
5960
6073
  // @ts-ignore
@@ -5968,6 +6081,20 @@ export default class Meeting extends StatelessWebexPlugin {
5968
6081
  meetingId: this.id,
5969
6082
  },
5970
6083
  });
6084
+
6085
+ if (data.type === 'share') {
6086
+ // @ts-ignore
6087
+ this.webex.internal.newMetrics.submitClientEvent({
6088
+ name: 'client.media.render.stop',
6089
+ payload: {
6090
+ mediaType: 'share',
6091
+ shareInstanceId: this.remoteShareInstanceId,
6092
+ },
6093
+ options: {
6094
+ meetingId: this.id,
6095
+ },
6096
+ });
6097
+ }
5971
6098
  });
5972
6099
  };
5973
6100
 
@@ -6074,16 +6201,22 @@ export default class Meeting extends StatelessWebexPlugin {
6074
6201
  private async setUpLocalStreamReferences(localStreams: LocalStreams) {
6075
6202
  const setUpStreamPromises = [];
6076
6203
 
6077
- if (localStreams?.microphone) {
6204
+ if (localStreams?.microphone && localStreams?.microphone?.readyState !== 'ended') {
6078
6205
  setUpStreamPromises.push(this.setLocalAudioStream(localStreams.microphone));
6079
6206
  }
6080
- if (localStreams?.camera) {
6207
+ if (localStreams?.camera && localStreams?.camera?.readyState !== 'ended') {
6081
6208
  setUpStreamPromises.push(this.setLocalVideoStream(localStreams.camera));
6082
6209
  }
6083
- if (localStreams?.screenShare?.video) {
6210
+ if (
6211
+ localStreams?.screenShare?.video &&
6212
+ localStreams?.screenShare?.video?.readyState !== 'ended'
6213
+ ) {
6084
6214
  setUpStreamPromises.push(this.setLocalShareVideoStream(localStreams.screenShare.video));
6085
6215
  }
6086
- if (localStreams?.screenShare?.audio) {
6216
+ if (
6217
+ localStreams?.screenShare?.audio &&
6218
+ localStreams?.screenShare?.audio?.readyState !== 'ended'
6219
+ ) {
6087
6220
  setUpStreamPromises.push(this.setLocalShareAudioStream(localStreams.screenShare.audio));
6088
6221
  }
6089
6222
 
@@ -6328,6 +6461,44 @@ export default class Meeting extends StatelessWebexPlugin {
6328
6461
  }
6329
6462
  }
6330
6463
 
6464
+ /**
6465
+ * Performs TURN discovery as a separate call to the Locus /media API
6466
+ *
6467
+ * @param {boolean} isRetry
6468
+ * @param {boolean} isForced
6469
+ * @returns {Promise}
6470
+ */
6471
+ private async doTurnDiscovery(isRetry: boolean, isForced: boolean): Promise<TurnDiscoveryResult> {
6472
+ // @ts-ignore
6473
+ const cdl = this.webex.internal.newMetrics.callDiagnosticLatencies;
6474
+
6475
+ // @ts-ignore
6476
+ this.webex.internal.newMetrics.submitInternalEvent({
6477
+ name: 'internal.client.add-media.turn-discovery.start',
6478
+ });
6479
+
6480
+ const turnDiscoveryResult = await this.roap.doTurnDiscovery(this, isRetry, isForced);
6481
+
6482
+ this.turnDiscoverySkippedReason = turnDiscoveryResult?.turnDiscoverySkippedReason;
6483
+ this.turnServerUsed = !this.turnDiscoverySkippedReason;
6484
+
6485
+ // @ts-ignore
6486
+ this.webex.internal.newMetrics.submitInternalEvent({
6487
+ name: 'internal.client.add-media.turn-discovery.end',
6488
+ });
6489
+
6490
+ if (this.turnServerUsed && turnDiscoveryResult.turnServerInfo) {
6491
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_LATENCY, {
6492
+ correlation_id: this.correlationId,
6493
+ latency: cdl.getTurnDiscoveryTime(),
6494
+ turnServerUsed: this.turnServerUsed,
6495
+ retriedWithTurnServer: this.retriedWithTurnServer,
6496
+ });
6497
+ }
6498
+
6499
+ return turnDiscoveryResult;
6500
+ }
6501
+
6331
6502
  /**
6332
6503
  * Does TURN discovery, SDP offer/answer exhange, establishes ICE connection and DTLS handshake.
6333
6504
  *
@@ -6335,43 +6506,21 @@ export default class Meeting extends StatelessWebexPlugin {
6335
6506
  * @param {RemoteMediaManagerConfiguration} [remoteMediaManagerConfig]
6336
6507
  * @param {BundlePolicy} [bundlePolicy]
6337
6508
  * @param {boolean} [isForced] - let isForced be true to do turn discovery regardless of reachability results
6509
+ * @param {TurnServerInfo} [turnServerInfo]
6338
6510
  * @returns {Promise<void>}
6339
6511
  */
6340
6512
  private async establishMediaConnection(
6341
6513
  remoteMediaManagerConfig?: RemoteMediaManagerConfiguration,
6342
6514
  bundlePolicy?: BundlePolicy,
6343
- isForced?: boolean
6515
+ isForced?: boolean,
6516
+ turnServerInfo?: TurnServerInfo
6344
6517
  ): Promise<void> {
6345
6518
  const LOG_HEADER = 'Meeting:index#addMedia():establishMediaConnection -->';
6346
- // @ts-ignore
6347
- const cdl = this.webex.internal.newMetrics.callDiagnosticLatencies;
6348
6519
  const isRetry = this.retriedWithTurnServer;
6349
6520
 
6350
6521
  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
- });
6522
+ if (!turnServerInfo) {
6523
+ ({turnServerInfo} = await this.doTurnDiscovery(isRetry, isForced));
6375
6524
  }
6376
6525
 
6377
6526
  const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
@@ -6488,15 +6637,21 @@ export default class Meeting extends StatelessWebexPlugin {
6488
6637
  * Creates a media connection to the server. Media connection is required for sending or receiving any audio/video.
6489
6638
  *
6490
6639
  * @param {AddMediaOptions} options
6640
+ * @param {TurnServerInfo} turnServerInfo - TURN server information (used only internally by the SDK)
6491
6641
  * @returns {Promise<void>}
6492
6642
  * @public
6493
6643
  * @memberof Meeting
6494
6644
  */
6495
- async addMedia(options: AddMediaOptions = {}): Promise<void> {
6645
+ async addMedia(
6646
+ options: AddMediaOptions = {},
6647
+ turnServerInfo: TurnServerInfo = undefined
6648
+ ): Promise<void> {
6496
6649
  this.retriedWithTurnServer = false;
6497
6650
  this.hasMediaConnectionConnectedAtLeastOnce = false;
6498
6651
  const LOG_HEADER = 'Meeting:index#addMedia -->';
6499
- LoggerProxy.logger.info(`${LOG_HEADER} called with: ${JSON.stringify(options)}`);
6652
+ LoggerProxy.logger.info(
6653
+ `${LOG_HEADER} called with: ${JSON.stringify(options)}, ${JSON.stringify(turnServerInfo)}`
6654
+ );
6500
6655
 
6501
6656
  if (options.allowMediaInLobby !== true && this.meetingState !== FULL_STATE.ACTIVE) {
6502
6657
  throw new MeetingNotActiveError();
@@ -6514,14 +6669,13 @@ export default class Meeting extends StatelessWebexPlugin {
6514
6669
  shareVideoEnabled = true,
6515
6670
  remoteMediaManagerConfig,
6516
6671
  bundlePolicy,
6517
- allowMediaInLobby,
6518
6672
  } = options;
6519
6673
 
6520
6674
  this.allowMediaInLobby = options?.allowMediaInLobby;
6521
6675
 
6522
6676
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
6523
6677
  // @ts-ignore - isUserUnadmitted coming from SelfUtil
6524
- if (this.isUserUnadmitted && !this.wirelessShare && !allowMediaInLobby) {
6678
+ if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
6525
6679
  throw new UserInLobbyError();
6526
6680
  }
6527
6681
 
@@ -6590,9 +6744,18 @@ export default class Meeting extends StatelessWebexPlugin {
6590
6744
 
6591
6745
  this.createStatsAnalyzer();
6592
6746
 
6593
- await this.establishMediaConnection(remoteMediaManagerConfig, bundlePolicy, false);
6747
+ await this.establishMediaConnection(
6748
+ remoteMediaManagerConfig,
6749
+ bundlePolicy,
6750
+ false,
6751
+ turnServerInfo
6752
+ );
6594
6753
 
6595
- await Meeting.handleDeviceLogging();
6754
+ if (audioEnabled || videoEnabled) {
6755
+ await Meeting.handleDeviceLogging();
6756
+ } else {
6757
+ LoggerProxy.logger.info(`${LOG_HEADER} device logging not required`);
6758
+ }
6596
6759
 
6597
6760
  if (this.mediaProperties.hasLocalShareStream()) {
6598
6761
  await this.enqueueScreenShareFloorRequest();
@@ -7784,9 +7947,9 @@ export default class Meeting extends StatelessWebexPlugin {
7784
7947
 
7785
7948
  /**
7786
7949
  *
7787
- * @returns {string} one of 'attendee','host','cohost', returns the user type of the current user
7950
+ * @returns {string} one of 'panelist', 'attendee', 'host', 'cohost', returns the user type of the current user
7788
7951
  */
7789
- getCurUserType() {
7952
+ getCurUserType(): RawClientEvent['userType'] | null {
7790
7953
  const {roles} = this;
7791
7954
  if (roles) {
7792
7955
  if (roles.includes(SELF_ROLES.MODERATOR)) {
@@ -7795,8 +7958,8 @@ export default class Meeting extends StatelessWebexPlugin {
7795
7958
  if (roles.includes(SELF_ROLES.COHOST)) {
7796
7959
  return 'cohost';
7797
7960
  }
7798
- if (roles.includes(SELF_ROLES.PRESENTER)) {
7799
- return 'presenter';
7961
+ if (roles.includes(SELF_ROLES.PANELIST)) {
7962
+ return 'panelist';
7800
7963
  }
7801
7964
  if (roles.includes(SELF_ROLES.ATTENDEE)) {
7802
7965
  return 'attendee';
@@ -8201,6 +8364,17 @@ export default class Meeting extends StatelessWebexPlugin {
8201
8364
  return;
8202
8365
  }
8203
8366
 
8367
+ if (
8368
+ streams?.microphone?.readyState === 'ended' ||
8369
+ streams?.camera?.readyState === 'ended' ||
8370
+ streams?.screenShare?.audio?.readyState === 'ended' ||
8371
+ streams?.screenShare?.video?.readyState === 'ended'
8372
+ ) {
8373
+ throw new Error(
8374
+ `Attempted to publish stream with ended readyState, correlationId=${this.correlationId}`
8375
+ );
8376
+ }
8377
+
8204
8378
  let floorRequestNeeded = false;
8205
8379
 
8206
8380
  // Screenshare Audio is supported only in multi stream. So we check for screenshare audio presence only if it's a multi stream meeting