@webex/plugin-meetings 2.60.0-next.1 → 2.60.0-next.3
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.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/enums.js +2 -1
- package/dist/controls-options-manager/enums.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/parser.js +5 -5
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/media/index.js +6 -5
- package/dist/media/index.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +4 -0
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +276 -150
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +3 -0
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/utilv2.js +14 -29
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/collection.js +17 -0
- package/dist/meetings/collection.js.map +1 -1
- package/dist/meetings/index.js +30 -9
- package/dist/meetings/index.js.map +1 -1
- package/dist/metrics/constants.js +3 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/reconnection-manager/index.js +27 -28
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/rtcMetrics/index.js +25 -0
- package/dist/rtcMetrics/index.js.map +1 -1
- package/dist/statsAnalyzer/index.js +21 -1
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.js +16 -16
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +21 -22
- package/src/constants.ts +10 -4
- package/src/controls-options-manager/enums.ts +2 -0
- package/src/locus-info/parser.ts +6 -6
- package/src/media/index.ts +5 -5
- package/src/meeting/in-meeting-actions.ts +8 -0
- package/src/meeting/index.ts +249 -120
- package/src/meeting-info/meeting-info-v2.ts +4 -0
- package/src/meeting-info/utilv2.ts +6 -19
- package/src/meetings/collection.ts +13 -0
- package/src/meetings/index.ts +28 -10
- package/src/metrics/constants.ts +3 -0
- package/src/reconnection-manager/index.ts +63 -68
- package/src/rtcMetrics/index.ts +24 -0
- package/src/statsAnalyzer/index.ts +30 -1
- package/src/statsAnalyzer/mqaUtil.ts +17 -14
- package/test/unit/spec/media/index.ts +20 -4
- package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
- package/test/unit/spec/meeting/index.js +1253 -157
- package/test/unit/spec/meeting/muteState.js +2 -1
- package/test/unit/spec/meeting-info/meetinginfov2.js +28 -0
- package/test/unit/spec/meetings/collection.js +12 -0
- package/test/unit/spec/meetings/index.js +382 -118
- package/test/unit/spec/member/util.js +0 -31
- package/test/unit/spec/reconnection-manager/index.js +42 -12
- package/test/unit/spec/rtcMetrics/index.ts +20 -0
- package/test/unit/spec/stats-analyzer/index.js +12 -2
package/src/meeting/index.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
ClientEvent,
|
|
10
10
|
ClientEventLeaveReason,
|
|
11
11
|
CallDiagnosticUtils,
|
|
12
|
-
CALL_DIAGNOSTIC_CONFIG,
|
|
13
12
|
} from '@webex/internal-plugin-metrics';
|
|
14
13
|
import {
|
|
15
14
|
ConnectionState,
|
|
@@ -105,6 +104,7 @@ import {
|
|
|
105
104
|
MEETING_PERMISSION_TOKEN_REFRESH_THRESHOLD_IN_SEC,
|
|
106
105
|
MEETING_PERMISSION_TOKEN_REFRESH_REASON,
|
|
107
106
|
ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT,
|
|
107
|
+
RECONNECTION,
|
|
108
108
|
} from '../constants';
|
|
109
109
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
110
110
|
import ParameterError from '../common/errors/parameter';
|
|
@@ -178,6 +178,12 @@ export type AddMediaOptions = {
|
|
|
178
178
|
allowMediaInLobby?: boolean; // allows adding media when in the lobby
|
|
179
179
|
};
|
|
180
180
|
|
|
181
|
+
export type CallStateForMetrics = {
|
|
182
|
+
correlationId?: string;
|
|
183
|
+
joinTrigger?: string;
|
|
184
|
+
loginType?: string;
|
|
185
|
+
};
|
|
186
|
+
|
|
181
187
|
export const MEDIA_UPDATE_TYPE = {
|
|
182
188
|
TRANSCODED_MEDIA_CONNECTION: 'TRANSCODED_MEDIA_CONNECTION',
|
|
183
189
|
SHARE_FLOOR_REQUEST: 'SHARE_FLOOR_REQUEST',
|
|
@@ -470,7 +476,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
470
476
|
annotation: any;
|
|
471
477
|
webinar: any;
|
|
472
478
|
conversationUrl: string;
|
|
473
|
-
|
|
479
|
+
callStateForMetrics: CallStateForMetrics;
|
|
474
480
|
destination: string;
|
|
475
481
|
destinationType: string;
|
|
476
482
|
deviceUrl: string;
|
|
@@ -528,7 +534,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
528
534
|
meetingInfoFailureCode?: number;
|
|
529
535
|
meetingInfoExtraParams?: Record<string, any>;
|
|
530
536
|
networkQualityMonitor: NetworkQualityMonitor;
|
|
531
|
-
networkStatus
|
|
537
|
+
networkStatus?: NETWORK_STATUS;
|
|
532
538
|
passwordStatus: string;
|
|
533
539
|
queuedMediaUpdates: any[];
|
|
534
540
|
recording: any;
|
|
@@ -538,6 +544,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
538
544
|
requiredCaptcha: any;
|
|
539
545
|
receiveSlotManager: ReceiveSlotManager;
|
|
540
546
|
selfUserPolicies: any;
|
|
547
|
+
enforceVBGImagesURL: string;
|
|
541
548
|
shareStatus: string;
|
|
542
549
|
screenShareFloorState: ScreenShareFloorStatus;
|
|
543
550
|
statsAnalyzer: StatsAnalyzer;
|
|
@@ -577,6 +584,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
577
584
|
private sendSlotManager: SendSlotManager = new SendSlotManager(LoggerProxy);
|
|
578
585
|
private deferSDPAnswer?: Defer; // used for waiting for a response
|
|
579
586
|
private sdpResponseTimer?: ReturnType<typeof setTimeout>;
|
|
587
|
+
private hasMediaConnectionConnectedAtLeastOnce: boolean;
|
|
580
588
|
|
|
581
589
|
/**
|
|
582
590
|
* @param {Object} attrs
|
|
@@ -611,20 +619,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
611
619
|
*/
|
|
612
620
|
this.id = uuid.v4();
|
|
613
621
|
/**
|
|
614
|
-
*
|
|
622
|
+
* Call state used for metrics
|
|
615
623
|
* @instance
|
|
616
|
-
* @type {
|
|
624
|
+
* @type {CallStateForMetrics}
|
|
617
625
|
* @readonly
|
|
618
626
|
* @public
|
|
619
627
|
* @memberof Meeting
|
|
620
628
|
*/
|
|
621
|
-
|
|
629
|
+
this.callStateForMetrics = attrs.callStateForMetrics || {};
|
|
630
|
+
const correlationId = attrs.correlationId || attrs.callStateForMetrics?.correlationId;
|
|
631
|
+
if (correlationId) {
|
|
622
632
|
LoggerProxy.logger.log(
|
|
623
|
-
`Meetings:index#constructor --> Initializing the meeting object with correlation id from app ${
|
|
633
|
+
`Meetings:index#constructor --> Initializing the meeting object with correlation id from app ${correlationId}`
|
|
624
634
|
);
|
|
625
|
-
this.correlationId =
|
|
635
|
+
this.callStateForMetrics.correlationId = correlationId;
|
|
626
636
|
} else {
|
|
627
|
-
this.correlationId = this.id;
|
|
637
|
+
this.callStateForMetrics.correlationId = this.id;
|
|
628
638
|
}
|
|
629
639
|
/**
|
|
630
640
|
* @instance
|
|
@@ -1096,13 +1106,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1096
1106
|
*/
|
|
1097
1107
|
this.networkQualityMonitor = null;
|
|
1098
1108
|
/**
|
|
1109
|
+
* Indicates network status of the webrtc media connection
|
|
1099
1110
|
* @instance
|
|
1100
1111
|
* @type {String}
|
|
1101
1112
|
* @readonly
|
|
1102
1113
|
* @public
|
|
1103
1114
|
* @memberof Meeting
|
|
1104
1115
|
*/
|
|
1105
|
-
this.networkStatus =
|
|
1116
|
+
this.networkStatus = undefined;
|
|
1106
1117
|
/**
|
|
1107
1118
|
* Passing only info as we send basic info for meeting added event
|
|
1108
1119
|
* @instance
|
|
@@ -1318,6 +1329,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1318
1329
|
* @memberof Meeting
|
|
1319
1330
|
*/
|
|
1320
1331
|
this.retriedWithTurnServer = false;
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* Whether or not the media connection has ever successfully connected.
|
|
1335
|
+
* @instance
|
|
1336
|
+
* @type {boolean}
|
|
1337
|
+
* @private
|
|
1338
|
+
* @memberof Meeting
|
|
1339
|
+
*/
|
|
1340
|
+
this.hasMediaConnectionConnectedAtLeastOnce = false;
|
|
1321
1341
|
}
|
|
1322
1342
|
|
|
1323
1343
|
/**
|
|
@@ -1350,6 +1370,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1350
1370
|
return this.type === 'CALL';
|
|
1351
1371
|
}
|
|
1352
1372
|
|
|
1373
|
+
/**
|
|
1374
|
+
* Getter - Returns callStateForMetrics.correlationId
|
|
1375
|
+
* @returns {string}
|
|
1376
|
+
*/
|
|
1377
|
+
get correlationId() {
|
|
1378
|
+
return this.callStateForMetrics.correlationId;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* Setter - sets callStateForMetrics.correlationId
|
|
1383
|
+
* @param {string} correlationId
|
|
1384
|
+
*/
|
|
1385
|
+
set correlationId(correlationId: string) {
|
|
1386
|
+
this.callStateForMetrics.correlationId = correlationId;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1353
1389
|
/**
|
|
1354
1390
|
* Internal method for fetching meeting info
|
|
1355
1391
|
*
|
|
@@ -1491,15 +1527,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1491
1527
|
: this.destination;
|
|
1492
1528
|
const destinationType = isStartingSpaceInstantV2Meeting ? _MEETING_LINK_ : this.destinationType;
|
|
1493
1529
|
|
|
1494
|
-
const
|
|
1530
|
+
const permissionTokenExpiryInfo = this.getPermissionTokenExpiryInfo();
|
|
1531
|
+
|
|
1532
|
+
const timeLeft = permissionTokenExpiryInfo?.timeLeft;
|
|
1533
|
+
const expiryTime = permissionTokenExpiryInfo?.expiryTime;
|
|
1534
|
+
const currentTime = permissionTokenExpiryInfo?.currentTime;
|
|
1495
1535
|
|
|
1496
1536
|
LoggerProxy.logger.info(
|
|
1497
|
-
`Meeting:index#refreshPermissionToken --> refreshing permission token, destinationType=${destinationType}, timeLeft=${timeLeft}, reason=${reason}`
|
|
1537
|
+
`Meeting:index#refreshPermissionToken --> refreshing permission token, destinationType=${destinationType}, timeLeft=${timeLeft}, permissionTokenExpiry=${expiryTime}, currentTimestamp=${currentTime},reason=${reason}`
|
|
1498
1538
|
);
|
|
1499
1539
|
|
|
1500
1540
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.PERMISSION_TOKEN_REFRESH, {
|
|
1501
1541
|
correlationId: this.correlationId,
|
|
1502
1542
|
timeLeft,
|
|
1543
|
+
expiryTime,
|
|
1544
|
+
currentTime,
|
|
1503
1545
|
reason,
|
|
1504
1546
|
destinationType,
|
|
1505
1547
|
});
|
|
@@ -1928,12 +1970,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1928
1970
|
|
|
1929
1971
|
/**
|
|
1930
1972
|
* sets the network status on meeting object
|
|
1931
|
-
* @param {
|
|
1973
|
+
* @param {NETWORK_STATUS} networkStatus
|
|
1932
1974
|
* @private
|
|
1933
1975
|
* @returns {undefined}
|
|
1934
1976
|
* @memberof Meeting
|
|
1935
1977
|
*/
|
|
1936
|
-
private setNetworkStatus(networkStatus
|
|
1978
|
+
private setNetworkStatus(networkStatus?: NETWORK_STATUS) {
|
|
1937
1979
|
if (networkStatus === NETWORK_STATUS.DISCONNECTED) {
|
|
1938
1980
|
Trigger.trigger(
|
|
1939
1981
|
this,
|
|
@@ -3275,6 +3317,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3275
3317
|
}) &&
|
|
3276
3318
|
this.meetingInfo?.video?.supportHDV) ||
|
|
3277
3319
|
!this.arePolicyRestrictionsSupported(),
|
|
3320
|
+
enforceVirtualBackground:
|
|
3321
|
+
ControlsOptionsUtil.hasPolicies({
|
|
3322
|
+
requiredPolicies: [SELF_POLICY.ENFORCE_VIRTUAL_BACKGROUND],
|
|
3323
|
+
policies: this.selfUserPolicies,
|
|
3324
|
+
}) && this.arePolicyRestrictionsSupported(),
|
|
3278
3325
|
supportHQV:
|
|
3279
3326
|
(ControlsOptionsUtil.hasPolicies({
|
|
3280
3327
|
requiredPolicies: [SELF_POLICY.SUPPORT_HQV],
|
|
@@ -3428,6 +3475,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3428
3475
|
requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
|
|
3429
3476
|
policies: this.selfUserPolicies,
|
|
3430
3477
|
}),
|
|
3478
|
+
canChat: ControlsOptionsUtil.hasPolicies({
|
|
3479
|
+
requiredPolicies: [SELF_POLICY.SUPPORT_CHAT],
|
|
3480
|
+
policies: this.selfUserPolicies,
|
|
3481
|
+
}),
|
|
3431
3482
|
canShareApplication:
|
|
3432
3483
|
(ControlsOptionsUtil.hasHints({
|
|
3433
3484
|
requiredHints: [DISPLAY_HINTS.SHARE_APPLICATION],
|
|
@@ -3487,6 +3538,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3487
3538
|
*/
|
|
3488
3539
|
setSelfUserPolicies() {
|
|
3489
3540
|
this.selfUserPolicies = this.permissionTokenPayload?.permission?.userPolicies;
|
|
3541
|
+
this.enforceVBGImagesURL = this.permissionTokenPayload?.permission?.enforceVBGImagesURL;
|
|
3490
3542
|
}
|
|
3491
3543
|
|
|
3492
3544
|
/**
|
|
@@ -3590,8 +3642,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3590
3642
|
* @memberof Meeting
|
|
3591
3643
|
*/
|
|
3592
3644
|
closeRemoteStreams() {
|
|
3593
|
-
const {remoteAudioStream, remoteVideoStream, remoteShareStream
|
|
3594
|
-
this.mediaProperties;
|
|
3645
|
+
const {remoteAudioStream, remoteVideoStream, remoteShareStream} = this.mediaProperties;
|
|
3595
3646
|
|
|
3596
3647
|
/**
|
|
3597
3648
|
* Triggers an event to the developer
|
|
@@ -3632,7 +3683,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3632
3683
|
stopStream(remoteAudioStream, EVENT_TYPES.REMOTE_AUDIO),
|
|
3633
3684
|
stopStream(remoteVideoStream, EVENT_TYPES.REMOTE_VIDEO),
|
|
3634
3685
|
stopStream(remoteShareStream, EVENT_TYPES.REMOTE_SHARE),
|
|
3635
|
-
stopStream(shareAudioStream, EVENT_TYPES.REMOTE_SHARE_AUDIO),
|
|
3636
3686
|
]);
|
|
3637
3687
|
}
|
|
3638
3688
|
|
|
@@ -3703,11 +3753,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3703
3753
|
private async setLocalShareVideoStream(localDisplayStream?: LocalDisplayStream) {
|
|
3704
3754
|
const oldStream = this.mediaProperties.shareVideoStream;
|
|
3705
3755
|
|
|
3756
|
+
oldStream?.off(StreamEventNames.MuteStateChange, this.handleShareVideoStreamMuteStateChange);
|
|
3706
3757
|
oldStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
3707
3758
|
oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3708
3759
|
|
|
3709
3760
|
this.mediaProperties.setLocalShareVideoStream(localDisplayStream);
|
|
3710
3761
|
|
|
3762
|
+
localDisplayStream?.on(
|
|
3763
|
+
StreamEventNames.MuteStateChange,
|
|
3764
|
+
this.handleShareVideoStreamMuteStateChange
|
|
3765
|
+
);
|
|
3711
3766
|
localDisplayStream?.on(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
3712
3767
|
localDisplayStream?.on(
|
|
3713
3768
|
LocalStreamEventNames.OutputTrackChange,
|
|
@@ -3797,12 +3852,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3797
3852
|
videoStream?.off(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
|
|
3798
3853
|
videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3799
3854
|
|
|
3800
|
-
shareAudioStream?.off(StreamEventNames.
|
|
3855
|
+
shareAudioStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
|
|
3801
3856
|
shareAudioStream?.off(
|
|
3802
3857
|
LocalStreamEventNames.OutputTrackChange,
|
|
3803
3858
|
this.localOutputTrackChangeHandler
|
|
3804
3859
|
);
|
|
3805
|
-
shareVideoStream?.off(
|
|
3860
|
+
shareVideoStream?.off(
|
|
3861
|
+
StreamEventNames.MuteStateChange,
|
|
3862
|
+
this.handleShareVideoStreamMuteStateChange
|
|
3863
|
+
);
|
|
3864
|
+
shareVideoStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
3806
3865
|
shareVideoStream?.off(
|
|
3807
3866
|
LocalStreamEventNames.OutputTrackChange,
|
|
3808
3867
|
this.localOutputTrackChangeHandler
|
|
@@ -3916,6 +3975,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3916
3975
|
this.receiveSlotManager.reset();
|
|
3917
3976
|
this.mediaProperties.webrtcMediaConnection.close();
|
|
3918
3977
|
this.sendSlotManager.reset();
|
|
3978
|
+
this.setNetworkStatus(undefined);
|
|
3919
3979
|
}
|
|
3920
3980
|
|
|
3921
3981
|
this.audio = null;
|
|
@@ -3943,14 +4003,25 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3943
4003
|
}
|
|
3944
4004
|
|
|
3945
4005
|
/**
|
|
3946
|
-
* Convenience method to set the correlation id for the
|
|
3947
|
-
* @param {String} id correlation id to set on the
|
|
4006
|
+
* Convenience method to set the correlation id for the callStateForMetrics
|
|
4007
|
+
* @param {String} id correlation id to set on the callStateForMetrics
|
|
3948
4008
|
* @returns {undefined}
|
|
3949
4009
|
* @public
|
|
3950
4010
|
* @memberof Meeting
|
|
3951
4011
|
*/
|
|
3952
4012
|
public setCorrelationId(id: string) {
|
|
3953
|
-
this.correlationId = id;
|
|
4013
|
+
this.callStateForMetrics.correlationId = id;
|
|
4014
|
+
}
|
|
4015
|
+
|
|
4016
|
+
/**
|
|
4017
|
+
* Update the callStateForMetrics
|
|
4018
|
+
* @param {CallStateForMetrics} callStateForMetrics updated values for callStateForMetrics
|
|
4019
|
+
* @returns {undefined}
|
|
4020
|
+
* @public
|
|
4021
|
+
* @memberof Meeting
|
|
4022
|
+
*/
|
|
4023
|
+
public updateCallStateForMetrics(callStateForMetrics: CallStateForMetrics) {
|
|
4024
|
+
this.callStateForMetrics = {...this.callStateForMetrics, ...callStateForMetrics};
|
|
3954
4025
|
}
|
|
3955
4026
|
|
|
3956
4027
|
/**
|
|
@@ -4268,6 +4339,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4268
4339
|
|
|
4269
4340
|
return this.reconnectionManager
|
|
4270
4341
|
.reconnect(options)
|
|
4342
|
+
.then(() => this.waitForRemoteSDPAnswer())
|
|
4343
|
+
.then(() => this.waitForMediaConnectionConnected())
|
|
4271
4344
|
.then(() => {
|
|
4272
4345
|
Trigger.trigger(
|
|
4273
4346
|
this,
|
|
@@ -4278,6 +4351,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4278
4351
|
EVENT_TRIGGERS.MEETING_RECONNECTION_SUCCESS
|
|
4279
4352
|
);
|
|
4280
4353
|
LoggerProxy.logger.log('Meeting:index#reconnect --> Meeting reconnect success');
|
|
4354
|
+
|
|
4355
|
+
// @ts-ignore
|
|
4356
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
4357
|
+
name: 'client.media.recovered',
|
|
4358
|
+
payload: {
|
|
4359
|
+
recoveredBy: 'new',
|
|
4360
|
+
},
|
|
4361
|
+
options: {
|
|
4362
|
+
meetingId: this.id,
|
|
4363
|
+
},
|
|
4364
|
+
});
|
|
4365
|
+
this.reconnectionManager.setStatus(RECONNECTION.STATE.COMPLETE);
|
|
4281
4366
|
})
|
|
4282
4367
|
.catch((error) => {
|
|
4283
4368
|
Trigger.trigger(
|
|
@@ -4587,7 +4672,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4587
4672
|
// @ts-ignore
|
|
4588
4673
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
4589
4674
|
name: 'client.call.initiated',
|
|
4590
|
-
payload: {
|
|
4675
|
+
payload: {
|
|
4676
|
+
trigger: this.callStateForMetrics.joinTrigger || 'user-interaction',
|
|
4677
|
+
isRoapCallEnabled: true,
|
|
4678
|
+
pstnAudioType: options?.pstnAudioType,
|
|
4679
|
+
},
|
|
4591
4680
|
options: {meetingId: this.id},
|
|
4592
4681
|
});
|
|
4593
4682
|
|
|
@@ -4753,7 +4842,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4753
4842
|
.then((join) => {
|
|
4754
4843
|
if (isBrowser) {
|
|
4755
4844
|
// @ts-ignore - config coming from registerPlugin
|
|
4756
|
-
if (this.config.receiveTranscription ||
|
|
4845
|
+
if (this.config.receiveTranscription || options.receiveTranscription) {
|
|
4757
4846
|
if (this.isTranscriptionSupported()) {
|
|
4758
4847
|
LoggerProxy.logger.info(
|
|
4759
4848
|
'Meeting:index#join --> Attempting to enabled to receive transcription!'
|
|
@@ -5398,40 +5487,25 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5398
5487
|
|
|
5399
5488
|
this.mediaProperties.webrtcMediaConnection.on(Event.CONNECTION_STATE_CHANGED, (event) => {
|
|
5400
5489
|
const connectionFailed = () => {
|
|
5401
|
-
// we know the media connection failed and browser will not attempt to recover it any more
|
|
5402
|
-
// so reset the timer as it's not needed anymore, we want to reconnect immediately
|
|
5403
|
-
this.reconnectionManager.resetReconnectionTimer();
|
|
5404
|
-
|
|
5405
|
-
this.reconnect({networkDisconnect: true});
|
|
5406
|
-
// @ts-ignore
|
|
5407
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
5408
|
-
name: 'client.ice.end',
|
|
5409
|
-
payload: {
|
|
5410
|
-
canProceed: false,
|
|
5411
|
-
icePhase: 'IN_MEETING',
|
|
5412
|
-
errors: [
|
|
5413
|
-
// @ts-ignore
|
|
5414
|
-
this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
|
|
5415
|
-
{
|
|
5416
|
-
clientErrorCode: CALL_DIAGNOSTIC_CONFIG.ICE_FAILURE_CLIENT_CODE,
|
|
5417
|
-
}
|
|
5418
|
-
),
|
|
5419
|
-
],
|
|
5420
|
-
},
|
|
5421
|
-
options: {
|
|
5422
|
-
meetingId: this.id,
|
|
5423
|
-
},
|
|
5424
|
-
});
|
|
5425
|
-
|
|
5426
|
-
this.uploadLogs({
|
|
5427
|
-
file: 'peer-connection-manager/index',
|
|
5428
|
-
function: 'connectionFailed',
|
|
5429
|
-
});
|
|
5430
|
-
|
|
5431
5490
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.CONNECTION_FAILURE, {
|
|
5432
5491
|
correlation_id: this.correlationId,
|
|
5433
5492
|
locus_id: this.locusId,
|
|
5493
|
+
networkStatus: this.networkStatus,
|
|
5494
|
+
hasMediaConnectionConnectedAtLeastOnce: this.hasMediaConnectionConnectedAtLeastOnce,
|
|
5434
5495
|
});
|
|
5496
|
+
|
|
5497
|
+
if (this.hasMediaConnectionConnectedAtLeastOnce) {
|
|
5498
|
+
// we know the media connection failed and browser will not attempt to recover it any more
|
|
5499
|
+
// so reset the timer as it's not needed anymore, we want to reconnect immediately
|
|
5500
|
+
this.reconnectionManager.resetReconnectionTimer();
|
|
5501
|
+
|
|
5502
|
+
this.reconnect({networkDisconnect: true});
|
|
5503
|
+
|
|
5504
|
+
this.uploadLogs({
|
|
5505
|
+
file: 'peer-connection-manager/index',
|
|
5506
|
+
function: 'connectionFailed',
|
|
5507
|
+
});
|
|
5508
|
+
}
|
|
5435
5509
|
};
|
|
5436
5510
|
|
|
5437
5511
|
LoggerProxy.logger.info(
|
|
@@ -5443,22 +5517,32 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5443
5517
|
|
|
5444
5518
|
switch (event.state) {
|
|
5445
5519
|
case ConnectionState.Connecting:
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5520
|
+
if (!this.hasMediaConnectionConnectedAtLeastOnce) {
|
|
5521
|
+
// Only send CA event for join flow if we haven't successfully connected media yet
|
|
5522
|
+
// @ts-ignore
|
|
5523
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
5524
|
+
name: 'client.ice.start',
|
|
5525
|
+
options: {
|
|
5526
|
+
meetingId: this.id,
|
|
5527
|
+
},
|
|
5528
|
+
});
|
|
5529
|
+
}
|
|
5453
5530
|
break;
|
|
5454
5531
|
case ConnectionState.Connected:
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5532
|
+
if (!this.hasMediaConnectionConnectedAtLeastOnce) {
|
|
5533
|
+
// Only send CA event for join flow if we haven't successfully connected media yet
|
|
5534
|
+
// @ts-ignore
|
|
5535
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
5536
|
+
name: 'client.ice.end',
|
|
5537
|
+
payload: {
|
|
5538
|
+
canProceed: true,
|
|
5539
|
+
icePhase: 'JOIN_MEETING_FINAL',
|
|
5540
|
+
},
|
|
5541
|
+
options: {
|
|
5542
|
+
meetingId: this.id,
|
|
5543
|
+
},
|
|
5544
|
+
});
|
|
5545
|
+
}
|
|
5462
5546
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.CONNECTION_SUCCESS, {
|
|
5463
5547
|
correlation_id: this.correlationId,
|
|
5464
5548
|
locus_id: this.locusId,
|
|
@@ -5467,6 +5551,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5467
5551
|
this.setNetworkStatus(NETWORK_STATUS.CONNECTED);
|
|
5468
5552
|
this.reconnectionManager.iceReconnected();
|
|
5469
5553
|
this.statsAnalyzer.startAnalyzer(this.mediaProperties.webrtcMediaConnection);
|
|
5554
|
+
this.hasMediaConnectionConnectedAtLeastOnce = true;
|
|
5470
5555
|
break;
|
|
5471
5556
|
case ConnectionState.Disconnected:
|
|
5472
5557
|
this.setNetworkStatus(NETWORK_STATUS.DISCONNECTED);
|
|
@@ -5771,36 +5856,42 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5771
5856
|
try {
|
|
5772
5857
|
await this.mediaProperties.waitForMediaConnectionConnected();
|
|
5773
5858
|
} catch (error) {
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5859
|
+
if (!this.hasMediaConnectionConnectedAtLeastOnce) {
|
|
5860
|
+
// Only send CA event for join flow if we haven't successfully connected media yet
|
|
5861
|
+
// @ts-ignore
|
|
5862
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
5863
|
+
name: 'client.ice.end',
|
|
5864
|
+
payload: {
|
|
5865
|
+
canProceed: !this.turnServerUsed, // If we haven't done turn tls retry yet we will proceed with join attempt
|
|
5866
|
+
icePhase: this.turnServerUsed ? 'JOIN_MEETING_FINAL' : 'JOIN_MEETING_RETRY',
|
|
5867
|
+
errors: [
|
|
5868
|
+
// @ts-ignore
|
|
5869
|
+
this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
|
|
5870
|
+
{
|
|
5871
|
+
clientErrorCode: CallDiagnosticUtils.generateClientErrorCodeForIceFailure({
|
|
5872
|
+
signalingState:
|
|
5873
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5874
|
+
?.signalingState ||
|
|
5875
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc
|
|
5876
|
+
?.signalingState ||
|
|
5877
|
+
'unknown',
|
|
5878
|
+
iceConnectionState:
|
|
5879
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5880
|
+
?.iceConnectionState ||
|
|
5881
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc
|
|
5882
|
+
?.iceConnectionState ||
|
|
5883
|
+
'unknown',
|
|
5884
|
+
turnServerUsed: this.turnServerUsed,
|
|
5885
|
+
}),
|
|
5886
|
+
}
|
|
5887
|
+
),
|
|
5888
|
+
],
|
|
5889
|
+
},
|
|
5890
|
+
options: {
|
|
5891
|
+
meetingId: this.id,
|
|
5892
|
+
},
|
|
5893
|
+
});
|
|
5894
|
+
}
|
|
5804
5895
|
throw new Error(
|
|
5805
5896
|
`Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
|
|
5806
5897
|
);
|
|
@@ -5924,9 +6015,24 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5924
6015
|
bundlePolicy?: BundlePolicy
|
|
5925
6016
|
): Promise<void> {
|
|
5926
6017
|
this.retriedWithTurnServer = true;
|
|
6018
|
+
const LOG_HEADER = 'Meeting:index#addMedia():retryWithForcedTurnDiscovery -->';
|
|
5927
6019
|
|
|
5928
6020
|
await this.cleanUpBeforeRetryWithTurnServer();
|
|
5929
6021
|
|
|
6022
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_RETRY, {
|
|
6023
|
+
correlation_id: this.correlationId,
|
|
6024
|
+
state: this.state,
|
|
6025
|
+
meetingState: this.meetingState,
|
|
6026
|
+
reason: 'forcingTurnTls',
|
|
6027
|
+
});
|
|
6028
|
+
|
|
6029
|
+
if (this.state === MEETING_STATE.STATES.LEFT) {
|
|
6030
|
+
LoggerProxy.logger.info(
|
|
6031
|
+
`${LOG_HEADER} meeting state was LEFT after first attempt to establish media connection. Attempting to rejoin. `
|
|
6032
|
+
);
|
|
6033
|
+
await this.join({rejoin: true});
|
|
6034
|
+
}
|
|
6035
|
+
|
|
5930
6036
|
await this.retryEstablishMediaConnectionWithForcedTurnDiscovery(
|
|
5931
6037
|
remoteMediaManagerConfig,
|
|
5932
6038
|
bundlePolicy
|
|
@@ -6130,10 +6236,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6130
6236
|
*/
|
|
6131
6237
|
async addMedia(options: AddMediaOptions = {}): Promise<void> {
|
|
6132
6238
|
this.retriedWithTurnServer = false;
|
|
6239
|
+
this.hasMediaConnectionConnectedAtLeastOnce = false;
|
|
6133
6240
|
const LOG_HEADER = 'Meeting:index#addMedia -->';
|
|
6134
6241
|
LoggerProxy.logger.info(`${LOG_HEADER} called with: ${JSON.stringify(options)}`);
|
|
6135
6242
|
|
|
6136
|
-
if (this.meetingState !== FULL_STATE.ACTIVE) {
|
|
6243
|
+
if (options.allowMediaInLobby !== true && this.meetingState !== FULL_STATE.ACTIVE) {
|
|
6137
6244
|
throw new MeetingNotActiveError();
|
|
6138
6245
|
}
|
|
6139
6246
|
|
|
@@ -6807,17 +6914,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6807
6914
|
.catch((error) => {
|
|
6808
6915
|
LoggerProxy.logger.error('Meeting:index#stopWhiteboardShare --> Error ', error);
|
|
6809
6916
|
|
|
6810
|
-
Metrics.sendBehavioralMetric(
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
stack: error.stack,
|
|
6818
|
-
board: {channelUrl},
|
|
6819
|
-
}
|
|
6820
|
-
);
|
|
6917
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_STOP_WHITEBOARD_SHARE_FAILURE, {
|
|
6918
|
+
correlation_id: this.correlationId,
|
|
6919
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
6920
|
+
reason: error.message,
|
|
6921
|
+
stack: error.stack,
|
|
6922
|
+
board: {channelUrl},
|
|
6923
|
+
});
|
|
6821
6924
|
|
|
6822
6925
|
return Promise.reject(error);
|
|
6823
6926
|
})
|
|
@@ -6872,6 +6975,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6872
6975
|
.then(() => {
|
|
6873
6976
|
this.screenShareFloorState = ScreenShareFloorStatus.GRANTED;
|
|
6874
6977
|
|
|
6978
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_SUCCESS, {
|
|
6979
|
+
correlation_id: this.correlationId,
|
|
6980
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
6981
|
+
});
|
|
6982
|
+
|
|
6875
6983
|
return Promise.resolve();
|
|
6876
6984
|
})
|
|
6877
6985
|
.catch((error) => {
|
|
@@ -7293,6 +7401,23 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7293
7401
|
}
|
|
7294
7402
|
};
|
|
7295
7403
|
|
|
7404
|
+
/**
|
|
7405
|
+
* Functionality for when a share video is muted or unmuted.
|
|
7406
|
+
* @private
|
|
7407
|
+
* @memberof Meeting
|
|
7408
|
+
* @param {boolean} muted
|
|
7409
|
+
* @returns {undefined}
|
|
7410
|
+
*/
|
|
7411
|
+
private handleShareVideoStreamMuteStateChange = (muted: boolean) => {
|
|
7412
|
+
LoggerProxy.logger.log(
|
|
7413
|
+
`Meeting:index#handleShareVideoStreamMuteStateChange --> Share video stream mute state changed to muted ${muted}`
|
|
7414
|
+
);
|
|
7415
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE, {
|
|
7416
|
+
correlationId: this.correlationId,
|
|
7417
|
+
muted,
|
|
7418
|
+
});
|
|
7419
|
+
};
|
|
7420
|
+
|
|
7296
7421
|
/**
|
|
7297
7422
|
* Functionality for when a share video is ended.
|
|
7298
7423
|
* @private
|
|
@@ -7659,10 +7784,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7659
7784
|
.update({
|
|
7660
7785
|
// TODO: RoapMediaConnection is not ready to use stream classes yet, so we pass the raw MediaStreamTrack for now
|
|
7661
7786
|
localTracks: {
|
|
7662
|
-
audio: this.mediaProperties.audioStream?.
|
|
7663
|
-
video: this.mediaProperties.videoStream?.
|
|
7664
|
-
screenShareVideo:
|
|
7665
|
-
|
|
7787
|
+
audio: this.mediaProperties.audioStream?.outputStream?.getTracks()[0] || null,
|
|
7788
|
+
video: this.mediaProperties.videoStream?.outputStream?.getTracks()[0] || null,
|
|
7789
|
+
screenShareVideo:
|
|
7790
|
+
this.mediaProperties.shareVideoStream?.outputStream?.getTracks()[0] || null,
|
|
7791
|
+
screenShareAudio:
|
|
7792
|
+
this.mediaProperties.shareAudioStream?.outputStream?.getTracks()[0] || null,
|
|
7666
7793
|
},
|
|
7667
7794
|
direction: {
|
|
7668
7795
|
audio: Media.getDirection(
|
|
@@ -7851,12 +7978,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7851
7978
|
}
|
|
7852
7979
|
|
|
7853
7980
|
/**
|
|
7854
|
-
* Gets
|
|
7981
|
+
* Gets permission token expiry information including timeLeft, expiryTime, currentTime
|
|
7855
7982
|
* (from the time the function has been fired)
|
|
7856
7983
|
*
|
|
7857
|
-
* @returns {
|
|
7984
|
+
* @returns {object} containing timeLeft, expiryTime, currentTime
|
|
7858
7985
|
*/
|
|
7859
|
-
public
|
|
7986
|
+
public getPermissionTokenExpiryInfo() {
|
|
7860
7987
|
if (!this.permissionTokenPayload) {
|
|
7861
7988
|
return undefined;
|
|
7862
7989
|
}
|
|
@@ -7869,7 +7996,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7869
7996
|
|
|
7870
7997
|
// substract current time from the permissionTokenExp
|
|
7871
7998
|
// (permissionTokenExp is a epoch timestamp, not a time to live duration)
|
|
7872
|
-
|
|
7999
|
+
const timeLeft = (permissionTokenExpValue - now) / 1000;
|
|
8000
|
+
|
|
8001
|
+
return {timeLeft, expiryTime: permissionTokenExpValue, currentTime: now};
|
|
7873
8002
|
}
|
|
7874
8003
|
|
|
7875
8004
|
/**
|
|
@@ -7881,9 +8010,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7881
8010
|
* @returns {Promise<void>}
|
|
7882
8011
|
*/
|
|
7883
8012
|
public checkAndRefreshPermissionToken(threshold: number, reason: string): Promise<void> {
|
|
7884
|
-
const
|
|
8013
|
+
const timeLeft = this.getPermissionTokenExpiryInfo()?.timeLeft;
|
|
7885
8014
|
|
|
7886
|
-
if (
|
|
8015
|
+
if (timeLeft !== undefined && timeLeft <= threshold) {
|
|
7887
8016
|
return this.refreshPermissionToken(reason);
|
|
7888
8017
|
}
|
|
7889
8018
|
|