@webex/plugin-meetings 3.0.0-next.10 → 3.0.0-next.12
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.
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.d.ts +1 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/interpretation/index.js +3 -3
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/mediaSharesUtils.js +15 -1
- package/dist/locus-info/mediaSharesUtils.js.map +1 -1
- package/dist/media/index.js +4 -1
- package/dist/media/index.js.map +1 -1
- package/dist/meeting/index.d.ts +15 -5
- package/dist/meeting/index.js +702 -561
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.d.ts +2 -8
- package/dist/meeting/muteState.js +37 -25
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.d.ts +3 -0
- package/dist/meeting/request.js +32 -23
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/multistream/mediaRequestManager.d.ts +1 -2
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.d.ts +1 -1
- package/dist/multistream/remoteMediaGroup.js.map +1 -1
- package/dist/multistream/remoteMediaManager.d.ts +1 -2
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/multistream/sendSlotManager.d.ts +1 -2
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/reconnection-manager/index.js +2 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.d.ts +10 -2
- package/dist/roap/index.js +15 -0
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/turnDiscovery.d.ts +64 -17
- package/dist/roap/turnDiscovery.js +307 -126
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +22 -22
- package/src/constants.ts +1 -1
- package/src/index.ts +1 -0
- package/src/interpretation/index.ts +2 -2
- package/src/locus-info/mediaSharesUtils.ts +16 -0
- package/src/media/index.ts +3 -1
- package/src/meeting/index.ts +220 -82
- package/src/meeting/muteState.ts +34 -20
- package/src/meeting/request.ts +18 -2
- package/src/meeting/util.ts +1 -0
- package/src/multistream/mediaRequestManager.ts +1 -1
- package/src/multistream/remoteMediaGroup.ts +1 -1
- package/src/multistream/remoteMediaManager.ts +1 -2
- package/src/multistream/sendSlotManager.ts +1 -2
- package/src/reconnection-manager/index.ts +1 -1
- package/src/roap/index.ts +25 -3
- package/src/roap/turnDiscovery.ts +244 -78
- package/test/integration/spec/journey.js +13 -13
- package/test/unit/spec/interpretation/index.ts +4 -1
- package/test/unit/spec/locus-info/mediaSharesUtils.ts +9 -0
- package/test/unit/spec/media/index.ts +89 -78
- package/test/unit/spec/meeting/index.js +460 -75
- package/test/unit/spec/meeting/muteState.js +219 -67
- package/test/unit/spec/meeting/request.js +21 -0
- package/test/unit/spec/meeting/utils.js +6 -1
- package/test/unit/spec/multistream/remoteMediaGroup.ts +0 -1
- package/test/unit/spec/multistream/remoteMediaManager.ts +0 -1
- package/test/unit/spec/reconnection-manager/index.js +28 -0
- package/test/unit/spec/roap/index.ts +61 -6
- package/test/unit/spec/roap/turnDiscovery.ts +298 -16
package/src/meeting/index.ts
CHANGED
|
@@ -51,7 +51,11 @@ import {StatsAnalyzer, EVENTS as StatsAnalyzerEvents} from '../statsAnalyzer';
|
|
|
51
51
|
import NetworkQualityMonitor from '../networkQualityMonitor';
|
|
52
52
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
53
53
|
import Trigger from '../common/events/trigger-proxy';
|
|
54
|
-
import Roap
|
|
54
|
+
import Roap, {
|
|
55
|
+
type TurnDiscoveryResult,
|
|
56
|
+
type TurnServerInfo,
|
|
57
|
+
type TurnDiscoverySkipReason,
|
|
58
|
+
} from '../roap/index';
|
|
55
59
|
import Media, {type BundlePolicy} from '../media';
|
|
56
60
|
import MediaProperties from '../media/properties';
|
|
57
61
|
import MeetingStateMachine from './state';
|
|
@@ -120,7 +124,6 @@ import {
|
|
|
120
124
|
MeetingInfoV2CaptchaError,
|
|
121
125
|
MeetingInfoV2PolicyError,
|
|
122
126
|
} from '../meeting-info/meeting-info-v2';
|
|
123
|
-
import BrowserDetection from '../common/browser-detection';
|
|
124
127
|
import {CSI, ReceiveSlotManager} from '../multistream/receiveSlotManager';
|
|
125
128
|
import SendSlotManager from '../multistream/sendSlotManager';
|
|
126
129
|
import {MediaRequestManager} from '../multistream/mediaRequestManager';
|
|
@@ -148,8 +151,6 @@ import ControlsOptionsManager from '../controls-options-manager';
|
|
|
148
151
|
import PermissionError from '../common/errors/permission';
|
|
149
152
|
import {LocusMediaRequest} from './locusMediaRequest';
|
|
150
153
|
|
|
151
|
-
const {isBrowser} = BrowserDetection();
|
|
152
|
-
|
|
153
154
|
const logRequest = (request: any, {logText = ''}) => {
|
|
154
155
|
LoggerProxy.logger.info(`${logText} - sending request`);
|
|
155
156
|
|
|
@@ -615,8 +616,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
615
616
|
resourceUrl: string;
|
|
616
617
|
selfId: string;
|
|
617
618
|
state: any;
|
|
618
|
-
localAudioStreamMuteStateHandler: (
|
|
619
|
-
localVideoStreamMuteStateHandler: (
|
|
619
|
+
localAudioStreamMuteStateHandler: () => void;
|
|
620
|
+
localVideoStreamMuteStateHandler: () => void;
|
|
620
621
|
localOutputTrackChangeHandler: () => void;
|
|
621
622
|
roles: any[];
|
|
622
623
|
environment: string;
|
|
@@ -624,7 +625,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
624
625
|
allowMediaInLobby: boolean;
|
|
625
626
|
localShareInstanceId: string;
|
|
626
627
|
remoteShareInstanceId: string;
|
|
627
|
-
turnDiscoverySkippedReason:
|
|
628
|
+
turnDiscoverySkippedReason: TurnDiscoverySkipReason;
|
|
628
629
|
turnServerUsed: boolean;
|
|
629
630
|
areVoiceaEventsSetup = false;
|
|
630
631
|
voiceaListenerCallbacks: object = {
|
|
@@ -1381,12 +1382,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1381
1382
|
*/
|
|
1382
1383
|
this.remoteMediaManager = null;
|
|
1383
1384
|
|
|
1384
|
-
this.localAudioStreamMuteStateHandler = (
|
|
1385
|
-
this.audio.handleLocalStreamMuteStateChange(this
|
|
1385
|
+
this.localAudioStreamMuteStateHandler = () => {
|
|
1386
|
+
this.audio.handleLocalStreamMuteStateChange(this);
|
|
1386
1387
|
};
|
|
1387
1388
|
|
|
1388
|
-
this.localVideoStreamMuteStateHandler = (
|
|
1389
|
-
this.video.handleLocalStreamMuteStateChange(this
|
|
1389
|
+
this.localVideoStreamMuteStateHandler = () => {
|
|
1390
|
+
this.video.handleLocalStreamMuteStateChange(this);
|
|
1390
1391
|
};
|
|
1391
1392
|
|
|
1392
1393
|
// The handling of output track changes should be done inside
|
|
@@ -2519,6 +2520,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2519
2520
|
{
|
|
2520
2521
|
annotationInfo: contentShare?.annotation,
|
|
2521
2522
|
meetingId: this.id,
|
|
2523
|
+
resourceType: contentShare?.resourceType,
|
|
2522
2524
|
}
|
|
2523
2525
|
);
|
|
2524
2526
|
}
|
|
@@ -2547,7 +2549,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2547
2549
|
contentShare.deviceUrlSharing === previousContentShare.deviceUrlSharing &&
|
|
2548
2550
|
whiteboardShare.beneficiaryId === previousWhiteboardShare?.beneficiaryId &&
|
|
2549
2551
|
whiteboardShare.disposition === previousWhiteboardShare?.disposition &&
|
|
2550
|
-
whiteboardShare.resourceUrl === previousWhiteboardShare?.resourceUrl
|
|
2552
|
+
whiteboardShare.resourceUrl === previousWhiteboardShare?.resourceUrl &&
|
|
2553
|
+
contentShare.resourceType === previousContentShare?.resourceType
|
|
2551
2554
|
) {
|
|
2552
2555
|
// nothing changed, so ignore
|
|
2553
2556
|
// (this happens when we steal presentation from remote)
|
|
@@ -2669,6 +2672,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2669
2672
|
url: contentShare.url,
|
|
2670
2673
|
shareInstanceId: this.remoteShareInstanceId,
|
|
2671
2674
|
annotationInfo: contentShare.annotation,
|
|
2675
|
+
resourceType: contentShare.resourceType,
|
|
2672
2676
|
}
|
|
2673
2677
|
);
|
|
2674
2678
|
};
|
|
@@ -2761,6 +2765,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2761
2765
|
url: contentShare.url,
|
|
2762
2766
|
shareInstanceId: this.remoteShareInstanceId,
|
|
2763
2767
|
annotationInfo: contentShare.annotation,
|
|
2768
|
+
resourceType: contentShare.resourceType,
|
|
2764
2769
|
}
|
|
2765
2770
|
);
|
|
2766
2771
|
this.members.locusMediaSharesUpdate(payload);
|
|
@@ -3070,6 +3075,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3070
3075
|
options: {meetingId: this.id},
|
|
3071
3076
|
});
|
|
3072
3077
|
}
|
|
3078
|
+
this.updateLLMConnection();
|
|
3073
3079
|
});
|
|
3074
3080
|
|
|
3075
3081
|
// @ts-ignore - check if MEDIA_INACTIVITY exists
|
|
@@ -3891,7 +3897,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3891
3897
|
private async setLocalAudioStream(localStream?: LocalMicrophoneStream) {
|
|
3892
3898
|
const oldStream = this.mediaProperties.audioStream;
|
|
3893
3899
|
|
|
3894
|
-
oldStream?.off(
|
|
3900
|
+
oldStream?.off(
|
|
3901
|
+
LocalStreamEventNames.UserMuteStateChange,
|
|
3902
|
+
this.localAudioStreamMuteStateHandler
|
|
3903
|
+
);
|
|
3904
|
+
oldStream?.off(
|
|
3905
|
+
LocalStreamEventNames.SystemMuteStateChange,
|
|
3906
|
+
this.localAudioStreamMuteStateHandler
|
|
3907
|
+
);
|
|
3895
3908
|
oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3896
3909
|
|
|
3897
3910
|
// we don't update this.mediaProperties.mediaDirection.sendAudio, because we always keep it as true to avoid extra SDP exchanges
|
|
@@ -3899,7 +3912,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3899
3912
|
|
|
3900
3913
|
this.audio.handleLocalStreamChange(this);
|
|
3901
3914
|
|
|
3902
|
-
localStream?.on(
|
|
3915
|
+
localStream?.on(
|
|
3916
|
+
LocalStreamEventNames.UserMuteStateChange,
|
|
3917
|
+
this.localAudioStreamMuteStateHandler
|
|
3918
|
+
);
|
|
3919
|
+
localStream?.on(
|
|
3920
|
+
LocalStreamEventNames.SystemMuteStateChange,
|
|
3921
|
+
this.localAudioStreamMuteStateHandler
|
|
3922
|
+
);
|
|
3903
3923
|
localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3904
3924
|
|
|
3905
3925
|
if (!this.isMultistream || !localStream) {
|
|
@@ -3919,7 +3939,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3919
3939
|
private async setLocalVideoStream(localStream?: LocalCameraStream) {
|
|
3920
3940
|
const oldStream = this.mediaProperties.videoStream;
|
|
3921
3941
|
|
|
3922
|
-
oldStream?.off(
|
|
3942
|
+
oldStream?.off(
|
|
3943
|
+
LocalStreamEventNames.UserMuteStateChange,
|
|
3944
|
+
this.localVideoStreamMuteStateHandler
|
|
3945
|
+
);
|
|
3946
|
+
oldStream?.off(
|
|
3947
|
+
LocalStreamEventNames.SystemMuteStateChange,
|
|
3948
|
+
this.localVideoStreamMuteStateHandler
|
|
3949
|
+
);
|
|
3923
3950
|
oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3924
3951
|
|
|
3925
3952
|
// we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
|
|
@@ -3927,7 +3954,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3927
3954
|
|
|
3928
3955
|
this.video.handleLocalStreamChange(this);
|
|
3929
3956
|
|
|
3930
|
-
localStream?.on(
|
|
3957
|
+
localStream?.on(
|
|
3958
|
+
LocalStreamEventNames.UserMuteStateChange,
|
|
3959
|
+
this.localVideoStreamMuteStateHandler
|
|
3960
|
+
);
|
|
3961
|
+
localStream?.on(
|
|
3962
|
+
LocalStreamEventNames.SystemMuteStateChange,
|
|
3963
|
+
this.localVideoStreamMuteStateHandler
|
|
3964
|
+
);
|
|
3931
3965
|
localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3932
3966
|
|
|
3933
3967
|
if (!this.isMultistream || !localStream) {
|
|
@@ -3948,14 +3982,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3948
3982
|
private async setLocalShareVideoStream(localDisplayStream?: LocalDisplayStream) {
|
|
3949
3983
|
const oldStream = this.mediaProperties.shareVideoStream;
|
|
3950
3984
|
|
|
3951
|
-
oldStream?.off(
|
|
3985
|
+
oldStream?.off(
|
|
3986
|
+
LocalStreamEventNames.SystemMuteStateChange,
|
|
3987
|
+
this.handleShareVideoStreamMuteStateChange
|
|
3988
|
+
);
|
|
3952
3989
|
oldStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
3953
3990
|
oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3954
3991
|
|
|
3955
3992
|
this.mediaProperties.setLocalShareVideoStream(localDisplayStream);
|
|
3956
3993
|
|
|
3957
3994
|
localDisplayStream?.on(
|
|
3958
|
-
|
|
3995
|
+
LocalStreamEventNames.SystemMuteStateChange,
|
|
3959
3996
|
this.handleShareVideoStreamMuteStateChange
|
|
3960
3997
|
);
|
|
3961
3998
|
localDisplayStream?.on(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
@@ -4041,10 +4078,24 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4041
4078
|
public cleanupLocalStreams() {
|
|
4042
4079
|
const {audioStream, videoStream, shareAudioStream, shareVideoStream} = this.mediaProperties;
|
|
4043
4080
|
|
|
4044
|
-
audioStream?.off(
|
|
4081
|
+
audioStream?.off(
|
|
4082
|
+
LocalStreamEventNames.UserMuteStateChange,
|
|
4083
|
+
this.localAudioStreamMuteStateHandler
|
|
4084
|
+
);
|
|
4085
|
+
audioStream?.off(
|
|
4086
|
+
LocalStreamEventNames.SystemMuteStateChange,
|
|
4087
|
+
this.localAudioStreamMuteStateHandler
|
|
4088
|
+
);
|
|
4045
4089
|
audioStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
4046
4090
|
|
|
4047
|
-
videoStream?.off(
|
|
4091
|
+
videoStream?.off(
|
|
4092
|
+
LocalStreamEventNames.UserMuteStateChange,
|
|
4093
|
+
this.localVideoStreamMuteStateHandler
|
|
4094
|
+
);
|
|
4095
|
+
videoStream?.off(
|
|
4096
|
+
LocalStreamEventNames.SystemMuteStateChange,
|
|
4097
|
+
this.localVideoStreamMuteStateHandler
|
|
4098
|
+
);
|
|
4048
4099
|
videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
4049
4100
|
|
|
4050
4101
|
shareAudioStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
|
|
@@ -4052,8 +4103,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4052
4103
|
LocalStreamEventNames.OutputTrackChange,
|
|
4053
4104
|
this.localOutputTrackChangeHandler
|
|
4054
4105
|
);
|
|
4106
|
+
|
|
4055
4107
|
shareVideoStream?.off(
|
|
4056
|
-
|
|
4108
|
+
LocalStreamEventNames.SystemMuteStateChange,
|
|
4057
4109
|
this.handleShareVideoStreamMuteStateChange
|
|
4058
4110
|
);
|
|
4059
4111
|
shareVideoStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
@@ -4445,47 +4497,90 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4445
4497
|
* }
|
|
4446
4498
|
* })
|
|
4447
4499
|
*/
|
|
4448
|
-
public joinWithMedia(
|
|
4500
|
+
public async joinWithMedia(
|
|
4449
4501
|
options: {
|
|
4450
4502
|
joinOptions?: any;
|
|
4451
4503
|
mediaOptions?: AddMediaOptions;
|
|
4452
4504
|
} = {}
|
|
4453
4505
|
) {
|
|
4454
|
-
const {mediaOptions, joinOptions} = options;
|
|
4506
|
+
const {mediaOptions, joinOptions = {}} = options;
|
|
4455
4507
|
|
|
4456
4508
|
if (!mediaOptions?.allowMediaInLobby) {
|
|
4457
4509
|
return Promise.reject(
|
|
4458
4510
|
new ParameterError('joinWithMedia() can only be used with allowMediaInLobby set to true')
|
|
4459
4511
|
);
|
|
4460
4512
|
}
|
|
4513
|
+
this.allowMediaInLobby = true;
|
|
4461
4514
|
|
|
4462
4515
|
LoggerProxy.logger.info('Meeting:index#joinWithMedia called');
|
|
4463
4516
|
|
|
4464
|
-
|
|
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);
|
|
4517
|
+
let joined = false;
|
|
4473
4518
|
|
|
4474
|
-
|
|
4475
|
-
|
|
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
|
-
);
|
|
4519
|
+
try {
|
|
4520
|
+
let turnServerInfo;
|
|
4521
|
+
let turnDiscoverySkippedReason;
|
|
4486
4522
|
|
|
4487
|
-
|
|
4488
|
-
|
|
4523
|
+
// @ts-ignore
|
|
4524
|
+
joinOptions.reachability = await this.webex.meetings.reachability.getReachabilityResults();
|
|
4525
|
+
const turnDiscoveryRequest = await this.roap.generateTurnDiscoveryRequestMessage(this, true);
|
|
4526
|
+
|
|
4527
|
+
({turnDiscoverySkippedReason} = turnDiscoveryRequest);
|
|
4528
|
+
joinOptions.roapMessage = turnDiscoveryRequest.roapMessage;
|
|
4529
|
+
|
|
4530
|
+
const joinResponse = await this.join(joinOptions);
|
|
4531
|
+
|
|
4532
|
+
joined = true;
|
|
4533
|
+
|
|
4534
|
+
if (joinOptions.roapMessage) {
|
|
4535
|
+
({turnServerInfo, turnDiscoverySkippedReason} =
|
|
4536
|
+
await this.roap.handleTurnDiscoveryHttpResponse(this, joinResponse));
|
|
4537
|
+
|
|
4538
|
+
this.turnDiscoverySkippedReason = turnDiscoverySkippedReason;
|
|
4539
|
+
this.turnServerUsed = !!turnServerInfo;
|
|
4540
|
+
|
|
4541
|
+
if (turnServerInfo === undefined) {
|
|
4542
|
+
this.roap.abortTurnDiscovery();
|
|
4543
|
+
}
|
|
4544
|
+
}
|
|
4545
|
+
|
|
4546
|
+
const mediaResponse = await this.addMedia(mediaOptions, turnServerInfo);
|
|
4547
|
+
|
|
4548
|
+
return {
|
|
4549
|
+
join: joinResponse,
|
|
4550
|
+
media: mediaResponse,
|
|
4551
|
+
};
|
|
4552
|
+
} catch (error) {
|
|
4553
|
+
LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);
|
|
4554
|
+
|
|
4555
|
+
let leaveError;
|
|
4556
|
+
|
|
4557
|
+
this.roap.abortTurnDiscovery();
|
|
4558
|
+
|
|
4559
|
+
if (joined) {
|
|
4560
|
+
try {
|
|
4561
|
+
await this.leave({resourceId: joinOptions?.resourceId, reason: 'joinWithMedia failure'});
|
|
4562
|
+
} catch (e) {
|
|
4563
|
+
LoggerProxy.logger.error('Meeting:index#joinWithMedia --> leave error', e);
|
|
4564
|
+
leaveError = e;
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
|
|
4568
|
+
Metrics.sendBehavioralMetric(
|
|
4569
|
+
BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
|
|
4570
|
+
{
|
|
4571
|
+
correlation_id: this.correlationId,
|
|
4572
|
+
locus_id: this.locusUrl?.split('/').pop(), // if join fails, we may end up with no locusUrl
|
|
4573
|
+
reason: error.message,
|
|
4574
|
+
stack: error.stack,
|
|
4575
|
+
leaveErrorReason: leaveError?.message,
|
|
4576
|
+
},
|
|
4577
|
+
{
|
|
4578
|
+
type: error.name,
|
|
4579
|
+
}
|
|
4580
|
+
);
|
|
4581
|
+
|
|
4582
|
+
throw error;
|
|
4583
|
+
}
|
|
4489
4584
|
}
|
|
4490
4585
|
|
|
4491
4586
|
/**
|
|
@@ -6074,16 +6169,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6074
6169
|
private async setUpLocalStreamReferences(localStreams: LocalStreams) {
|
|
6075
6170
|
const setUpStreamPromises = [];
|
|
6076
6171
|
|
|
6077
|
-
if (localStreams?.microphone) {
|
|
6172
|
+
if (localStreams?.microphone && localStreams?.microphone?.readyState !== 'ended') {
|
|
6078
6173
|
setUpStreamPromises.push(this.setLocalAudioStream(localStreams.microphone));
|
|
6079
6174
|
}
|
|
6080
|
-
if (localStreams?.camera) {
|
|
6175
|
+
if (localStreams?.camera && localStreams?.camera?.readyState !== 'ended') {
|
|
6081
6176
|
setUpStreamPromises.push(this.setLocalVideoStream(localStreams.camera));
|
|
6082
6177
|
}
|
|
6083
|
-
if (
|
|
6178
|
+
if (
|
|
6179
|
+
localStreams?.screenShare?.video &&
|
|
6180
|
+
localStreams?.screenShare?.video?.readyState !== 'ended'
|
|
6181
|
+
) {
|
|
6084
6182
|
setUpStreamPromises.push(this.setLocalShareVideoStream(localStreams.screenShare.video));
|
|
6085
6183
|
}
|
|
6086
|
-
if (
|
|
6184
|
+
if (
|
|
6185
|
+
localStreams?.screenShare?.audio &&
|
|
6186
|
+
localStreams?.screenShare?.audio?.readyState !== 'ended'
|
|
6187
|
+
) {
|
|
6087
6188
|
setUpStreamPromises.push(this.setLocalShareAudioStream(localStreams.screenShare.audio));
|
|
6088
6189
|
}
|
|
6089
6190
|
|
|
@@ -6328,6 +6429,44 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6328
6429
|
}
|
|
6329
6430
|
}
|
|
6330
6431
|
|
|
6432
|
+
/**
|
|
6433
|
+
* Performs TURN discovery as a separate call to the Locus /media API
|
|
6434
|
+
*
|
|
6435
|
+
* @param {boolean} isRetry
|
|
6436
|
+
* @param {boolean} isForced
|
|
6437
|
+
* @returns {Promise}
|
|
6438
|
+
*/
|
|
6439
|
+
private async doTurnDiscovery(isRetry: boolean, isForced: boolean): Promise<TurnDiscoveryResult> {
|
|
6440
|
+
// @ts-ignore
|
|
6441
|
+
const cdl = this.webex.internal.newMetrics.callDiagnosticLatencies;
|
|
6442
|
+
|
|
6443
|
+
// @ts-ignore
|
|
6444
|
+
this.webex.internal.newMetrics.submitInternalEvent({
|
|
6445
|
+
name: 'internal.client.add-media.turn-discovery.start',
|
|
6446
|
+
});
|
|
6447
|
+
|
|
6448
|
+
const turnDiscoveryResult = await this.roap.doTurnDiscovery(this, isRetry, isForced);
|
|
6449
|
+
|
|
6450
|
+
this.turnDiscoverySkippedReason = turnDiscoveryResult?.turnDiscoverySkippedReason;
|
|
6451
|
+
this.turnServerUsed = !this.turnDiscoverySkippedReason;
|
|
6452
|
+
|
|
6453
|
+
// @ts-ignore
|
|
6454
|
+
this.webex.internal.newMetrics.submitInternalEvent({
|
|
6455
|
+
name: 'internal.client.add-media.turn-discovery.end',
|
|
6456
|
+
});
|
|
6457
|
+
|
|
6458
|
+
if (this.turnServerUsed && turnDiscoveryResult.turnServerInfo) {
|
|
6459
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_LATENCY, {
|
|
6460
|
+
correlation_id: this.correlationId,
|
|
6461
|
+
latency: cdl.getTurnDiscoveryTime(),
|
|
6462
|
+
turnServerUsed: this.turnServerUsed,
|
|
6463
|
+
retriedWithTurnServer: this.retriedWithTurnServer,
|
|
6464
|
+
});
|
|
6465
|
+
}
|
|
6466
|
+
|
|
6467
|
+
return turnDiscoveryResult;
|
|
6468
|
+
}
|
|
6469
|
+
|
|
6331
6470
|
/**
|
|
6332
6471
|
* Does TURN discovery, SDP offer/answer exhange, establishes ICE connection and DTLS handshake.
|
|
6333
6472
|
*
|
|
@@ -6335,43 +6474,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6335
6474
|
* @param {RemoteMediaManagerConfiguration} [remoteMediaManagerConfig]
|
|
6336
6475
|
* @param {BundlePolicy} [bundlePolicy]
|
|
6337
6476
|
* @param {boolean} [isForced] - let isForced be true to do turn discovery regardless of reachability results
|
|
6477
|
+
* @param {TurnServerInfo} [turnServerInfo]
|
|
6338
6478
|
* @returns {Promise<void>}
|
|
6339
6479
|
*/
|
|
6340
6480
|
private async establishMediaConnection(
|
|
6341
6481
|
remoteMediaManagerConfig?: RemoteMediaManagerConfiguration,
|
|
6342
6482
|
bundlePolicy?: BundlePolicy,
|
|
6343
|
-
isForced?: boolean
|
|
6483
|
+
isForced?: boolean,
|
|
6484
|
+
turnServerInfo?: TurnServerInfo
|
|
6344
6485
|
): Promise<void> {
|
|
6345
6486
|
const LOG_HEADER = 'Meeting:index#addMedia():establishMediaConnection -->';
|
|
6346
|
-
// @ts-ignore
|
|
6347
|
-
const cdl = this.webex.internal.newMetrics.callDiagnosticLatencies;
|
|
6348
6487
|
const isRetry = this.retriedWithTurnServer;
|
|
6349
6488
|
|
|
6350
6489
|
try {
|
|
6351
|
-
|
|
6352
|
-
|
|
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
|
-
});
|
|
6490
|
+
if (!turnServerInfo) {
|
|
6491
|
+
({turnServerInfo} = await this.doTurnDiscovery(isRetry, isForced));
|
|
6375
6492
|
}
|
|
6376
6493
|
|
|
6377
6494
|
const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
|
|
@@ -6488,15 +6605,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6488
6605
|
* Creates a media connection to the server. Media connection is required for sending or receiving any audio/video.
|
|
6489
6606
|
*
|
|
6490
6607
|
* @param {AddMediaOptions} options
|
|
6608
|
+
* @param {TurnServerInfo} turnServerInfo - TURN server information (used only internally by the SDK)
|
|
6491
6609
|
* @returns {Promise<void>}
|
|
6492
6610
|
* @public
|
|
6493
6611
|
* @memberof Meeting
|
|
6494
6612
|
*/
|
|
6495
|
-
async addMedia(
|
|
6613
|
+
async addMedia(
|
|
6614
|
+
options: AddMediaOptions = {},
|
|
6615
|
+
turnServerInfo: TurnServerInfo = undefined
|
|
6616
|
+
): Promise<void> {
|
|
6496
6617
|
this.retriedWithTurnServer = false;
|
|
6497
6618
|
this.hasMediaConnectionConnectedAtLeastOnce = false;
|
|
6498
6619
|
const LOG_HEADER = 'Meeting:index#addMedia -->';
|
|
6499
|
-
LoggerProxy.logger.info(
|
|
6620
|
+
LoggerProxy.logger.info(
|
|
6621
|
+
`${LOG_HEADER} called with: ${JSON.stringify(options)}, ${JSON.stringify(turnServerInfo)}`
|
|
6622
|
+
);
|
|
6500
6623
|
|
|
6501
6624
|
if (options.allowMediaInLobby !== true && this.meetingState !== FULL_STATE.ACTIVE) {
|
|
6502
6625
|
throw new MeetingNotActiveError();
|
|
@@ -6514,14 +6637,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6514
6637
|
shareVideoEnabled = true,
|
|
6515
6638
|
remoteMediaManagerConfig,
|
|
6516
6639
|
bundlePolicy,
|
|
6517
|
-
allowMediaInLobby,
|
|
6518
6640
|
} = options;
|
|
6519
6641
|
|
|
6520
6642
|
this.allowMediaInLobby = options?.allowMediaInLobby;
|
|
6521
6643
|
|
|
6522
6644
|
// If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
|
|
6523
6645
|
// @ts-ignore - isUserUnadmitted coming from SelfUtil
|
|
6524
|
-
if (this.isUserUnadmitted && !this.wirelessShare && !allowMediaInLobby) {
|
|
6646
|
+
if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
|
|
6525
6647
|
throw new UserInLobbyError();
|
|
6526
6648
|
}
|
|
6527
6649
|
|
|
@@ -6590,7 +6712,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6590
6712
|
|
|
6591
6713
|
this.createStatsAnalyzer();
|
|
6592
6714
|
|
|
6593
|
-
await this.establishMediaConnection(
|
|
6715
|
+
await this.establishMediaConnection(
|
|
6716
|
+
remoteMediaManagerConfig,
|
|
6717
|
+
bundlePolicy,
|
|
6718
|
+
false,
|
|
6719
|
+
turnServerInfo
|
|
6720
|
+
);
|
|
6594
6721
|
|
|
6595
6722
|
await Meeting.handleDeviceLogging();
|
|
6596
6723
|
|
|
@@ -8201,6 +8328,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8201
8328
|
return;
|
|
8202
8329
|
}
|
|
8203
8330
|
|
|
8331
|
+
if (
|
|
8332
|
+
streams?.microphone?.readyState === 'ended' ||
|
|
8333
|
+
streams?.camera?.readyState === 'ended' ||
|
|
8334
|
+
streams?.screenShare?.audio?.readyState === 'ended' ||
|
|
8335
|
+
streams?.screenShare?.video?.readyState === 'ended'
|
|
8336
|
+
) {
|
|
8337
|
+
throw new Error(
|
|
8338
|
+
`Attempted to publish stream with ended readyState, correlationId=${this.correlationId}`
|
|
8339
|
+
);
|
|
8340
|
+
}
|
|
8341
|
+
|
|
8204
8342
|
let floorRequestNeeded = false;
|
|
8205
8343
|
|
|
8206
8344
|
// Screenshare Audio is supported only in multi stream. So we check for screenshare audio presence only if it's a multi stream meeting
|
package/src/meeting/muteState.ts
CHANGED
|
@@ -150,15 +150,30 @@ export class MuteState {
|
|
|
150
150
|
* @param {Boolean} [mute] true for muting, false for unmuting request
|
|
151
151
|
* @returns {void}
|
|
152
152
|
*/
|
|
153
|
-
public handleLocalStreamMuteStateChange(meeting?:
|
|
153
|
+
public handleLocalStreamMuteStateChange(meeting?: any) {
|
|
154
154
|
if (this.ignoreMuteStateChange) {
|
|
155
155
|
return;
|
|
156
156
|
}
|
|
157
|
+
|
|
158
|
+
// either user or system may have triggered a mute state change, but localMute should reflect both
|
|
159
|
+
let newMuteState: boolean;
|
|
160
|
+
let userMuteState: boolean;
|
|
161
|
+
let systemMuteState: boolean;
|
|
162
|
+
if (this.type === AUDIO) {
|
|
163
|
+
newMuteState = meeting.mediaProperties.audioStream?.muted;
|
|
164
|
+
userMuteState = meeting.mediaProperties.audioStream?.userMuted;
|
|
165
|
+
systemMuteState = meeting.mediaProperties.audioStream?.systemMuted;
|
|
166
|
+
} else {
|
|
167
|
+
newMuteState = meeting.mediaProperties.videoStream?.muted;
|
|
168
|
+
userMuteState = meeting.mediaProperties.videoStream?.userMuted;
|
|
169
|
+
systemMuteState = meeting.mediaProperties.videoStream?.systemMuted;
|
|
170
|
+
}
|
|
171
|
+
|
|
157
172
|
LoggerProxy.logger.info(
|
|
158
|
-
`Meeting:muteState#handleLocalStreamMuteStateChange --> ${this.type}: local stream new mute state: ${mute}`
|
|
173
|
+
`Meeting:muteState#handleLocalStreamMuteStateChange --> ${this.type}: local stream new mute state: ${newMuteState} (user mute: ${userMuteState}, system mute: ${systemMuteState})`
|
|
159
174
|
);
|
|
160
175
|
|
|
161
|
-
this.state.client.localMute =
|
|
176
|
+
this.state.client.localMute = newMuteState;
|
|
162
177
|
|
|
163
178
|
this.applyClientStateToServer(meeting);
|
|
164
179
|
}
|
|
@@ -249,7 +264,12 @@ export class MuteState {
|
|
|
249
264
|
`Meeting:muteState#applyClientStateToServer --> ${this.type}: error: ${e}`
|
|
250
265
|
);
|
|
251
266
|
|
|
252
|
-
|
|
267
|
+
// failed to apply client state to server, so revert stream mute state to server state
|
|
268
|
+
this.muteLocalStream(
|
|
269
|
+
meeting,
|
|
270
|
+
this.state.server.localMute || this.state.server.remoteMute,
|
|
271
|
+
'clientRequestFailed'
|
|
272
|
+
);
|
|
253
273
|
});
|
|
254
274
|
}
|
|
255
275
|
|
|
@@ -325,18 +345,6 @@ export class MuteState {
|
|
|
325
345
|
});
|
|
326
346
|
}
|
|
327
347
|
|
|
328
|
-
/** Sets the mute state of the local stream according to what server thinks is our state
|
|
329
|
-
* @param {Object} meeting - the meeting object
|
|
330
|
-
* @param {ServerMuteReason} serverMuteReason - reason why we're applying server mute to the local stream
|
|
331
|
-
* @returns {void}
|
|
332
|
-
*/
|
|
333
|
-
private applyServerMuteToLocalStream(meeting: any, serverMuteReason: ServerMuteReason) {
|
|
334
|
-
const muted = this.state.server.localMute || this.state.server.remoteMute;
|
|
335
|
-
|
|
336
|
-
// update the local stream mute state, but not this.state.client.localMute
|
|
337
|
-
this.muteLocalStream(meeting, muted, serverMuteReason);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
348
|
/** Applies the current value for unmute allowed to the underlying stream
|
|
341
349
|
*
|
|
342
350
|
* @param {Meeting} meeting
|
|
@@ -371,7 +379,7 @@ export class MuteState {
|
|
|
371
379
|
}
|
|
372
380
|
if (muted !== undefined) {
|
|
373
381
|
this.state.server.remoteMute = muted;
|
|
374
|
-
this.
|
|
382
|
+
this.muteLocalStream(meeting, muted, 'remotelyMuted');
|
|
375
383
|
}
|
|
376
384
|
}
|
|
377
385
|
|
|
@@ -383,7 +391,7 @@ export class MuteState {
|
|
|
383
391
|
* @param {Object} [meeting] the meeting object
|
|
384
392
|
* @returns {undefined}
|
|
385
393
|
*/
|
|
386
|
-
public handleServerLocalUnmuteRequired(meeting?:
|
|
394
|
+
public handleServerLocalUnmuteRequired(meeting?: any) {
|
|
387
395
|
if (!this.state.client.enabled) {
|
|
388
396
|
LoggerProxy.logger.warn(
|
|
389
397
|
`Meeting:muteState#handleServerLocalUnmuteRequired --> ${this.type}: localAudioUnmuteRequired received while ${this.type} is disabled -> local unmute will not result in ${this.type} being sent`
|
|
@@ -396,9 +404,15 @@ export class MuteState {
|
|
|
396
404
|
|
|
397
405
|
// todo: I'm seeing "you can now unmute yourself " popup when this happens - but same thing happens on web.w.c so we can ignore for now
|
|
398
406
|
this.state.server.remoteMute = false;
|
|
399
|
-
this.state.client.localMute = false;
|
|
400
407
|
|
|
401
|
-
|
|
408
|
+
// change user mute state to false, but keep localMute true if overall mute state is still true
|
|
409
|
+
this.muteLocalStream(meeting, false, 'localUnmuteRequired');
|
|
410
|
+
if (this.type === AUDIO) {
|
|
411
|
+
this.state.client.localMute = meeting.mediaProperties.audioStream?.muted;
|
|
412
|
+
} else {
|
|
413
|
+
this.state.client.localMute = meeting.mediaProperties.videoStream?.muted;
|
|
414
|
+
}
|
|
415
|
+
|
|
402
416
|
this.applyClientStateToServer(meeting);
|
|
403
417
|
}
|
|
404
418
|
|