@webex/plugin-meetings 3.7.0-next.8 → 3.7.0-web-workers-keepalive.1
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/annotation/index.js +17 -0
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
- package/dist/common/errors/join-webinar-error.js.map +1 -0
- package/dist/common/errors/multistream-not-supported-error.js +53 -0
- package/dist/common/errors/multistream-not-supported-error.js.map +1 -0
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +26 -5
- package/dist/constants.js.map +1 -1
- package/dist/index.js +16 -11
- package/dist/index.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +13 -2
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +30 -17
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/meeting/index.js +903 -800
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +9 -0
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/request.js +30 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +16 -16
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +29 -17
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meetings/index.js +6 -3
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +1 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +9 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +39 -28
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +1 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/remoteMedia.js +30 -15
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +24 -0
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/roap/index.js +10 -8
- package/dist/roap/index.js.map +1 -1
- package/dist/types/annotation/index.d.ts +5 -0
- package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
- package/dist/types/common/errors/multistream-not-supported-error.d.ts +17 -0
- package/dist/types/constants.d.ts +20 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/locus-info/index.d.ts +2 -1
- package/dist/types/meeting/index.d.ts +19 -12
- package/dist/types/meeting/locusMediaRequest.d.ts +4 -0
- package/dist/types/meeting/request.d.ts +12 -1
- package/dist/types/meeting/request.type.d.ts +6 -0
- package/dist/types/meeting/util.d.ts +1 -1
- package/dist/types/meeting-info/meeting-info-v2.d.ts +4 -4
- package/dist/types/meetings/index.d.ts +3 -0
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +7 -0
- package/dist/types/metrics/constants.d.ts +1 -1
- package/dist/types/multistream/sendSlotManager.d.ts +8 -1
- package/dist/webinar/index.js +354 -3
- package/dist/webinar/index.js.map +1 -1
- package/package.json +23 -22
- package/src/annotation/index.ts +16 -0
- package/src/common/errors/join-webinar-error.ts +24 -0
- package/src/common/errors/multistream-not-supported-error.ts +30 -0
- package/src/config.ts +1 -1
- package/src/constants.ts +23 -3
- package/src/index.ts +5 -3
- package/src/locus-info/index.ts +17 -2
- package/src/locus-info/selfUtils.ts +19 -6
- package/src/meeting/index.ts +234 -80
- package/src/meeting/locusMediaRequest.ts +7 -0
- package/src/meeting/request.ts +26 -1
- package/src/meeting/request.type.ts +7 -0
- package/src/meeting/util.ts +8 -10
- package/src/meeting-info/meeting-info-v2.ts +23 -11
- package/src/meetings/index.ts +8 -2
- package/src/meetings/util.ts +2 -1
- package/src/member/index.ts +9 -0
- package/src/member/types.ts +8 -0
- package/src/member/util.ts +34 -24
- package/src/metrics/constants.ts +1 -1
- package/src/multistream/remoteMedia.ts +28 -15
- package/src/multistream/sendSlotManager.ts +31 -0
- package/src/roap/index.ts +10 -8
- package/src/webinar/index.ts +197 -3
- package/test/unit/spec/annotation/index.ts +46 -1
- package/test/unit/spec/locus-info/index.js +222 -0
- package/test/unit/spec/locus-info/selfConstant.js +7 -0
- package/test/unit/spec/locus-info/selfUtils.js +91 -1
- package/test/unit/spec/meeting/index.js +683 -103
- package/test/unit/spec/meeting/utils.js +22 -19
- package/test/unit/spec/meeting-info/meetinginfov2.js +9 -4
- package/test/unit/spec/meetings/index.js +9 -5
- package/test/unit/spec/meetings/utils.js +10 -0
- package/test/unit/spec/member/util.js +52 -11
- package/test/unit/spec/multistream/remoteMedia.ts +11 -7
- package/test/unit/spec/roap/index.ts +47 -0
- package/test/unit/spec/webinar/index.ts +457 -0
- package/dist/common/errors/webinar-registration-error.js.map +0 -1
- package/src/common/errors/webinar-registration-error.ts +0 -27
package/src/meeting/index.ts
CHANGED
|
@@ -31,7 +31,6 @@ import {
|
|
|
31
31
|
} from '@webex/internal-media-core';
|
|
32
32
|
|
|
33
33
|
import {
|
|
34
|
-
getDevices,
|
|
35
34
|
LocalStream,
|
|
36
35
|
LocalCameraStream,
|
|
37
36
|
LocalDisplayStream,
|
|
@@ -122,6 +121,8 @@ import {
|
|
|
122
121
|
MEETING_PERMISSION_TOKEN_REFRESH_REASON,
|
|
123
122
|
ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT,
|
|
124
123
|
NAMED_MEDIA_GROUP_TYPE_AUDIO,
|
|
124
|
+
WEBINAR_ERROR_WEBCAST,
|
|
125
|
+
WEBINAR_ERROR_REGISTRATIONID,
|
|
125
126
|
} from '../constants';
|
|
126
127
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
127
128
|
import ParameterError from '../common/errors/parameter';
|
|
@@ -129,7 +130,7 @@ import {
|
|
|
129
130
|
MeetingInfoV2PasswordError,
|
|
130
131
|
MeetingInfoV2CaptchaError,
|
|
131
132
|
MeetingInfoV2PolicyError,
|
|
132
|
-
|
|
133
|
+
MeetingInfoV2JoinWebinarError,
|
|
133
134
|
} from '../meeting-info/meeting-info-v2';
|
|
134
135
|
import {CSI, ReceiveSlotManager} from '../multistream/receiveSlotManager';
|
|
135
136
|
import SendSlotManager from '../multistream/sendSlotManager';
|
|
@@ -158,7 +159,9 @@ import ControlsOptionsManager from '../controls-options-manager';
|
|
|
158
159
|
import PermissionError from '../common/errors/permission';
|
|
159
160
|
import {LocusMediaRequest} from './locusMediaRequest';
|
|
160
161
|
import {ConnectionStateHandler, ConnectionStateEvent} from './connectionStateHandler';
|
|
161
|
-
import
|
|
162
|
+
import JoinWebinarError from '../common/errors/join-webinar-error';
|
|
163
|
+
import Member from '../member';
|
|
164
|
+
import MultistreamNotSupportedError from '../common/errors/multistream-not-supported-error';
|
|
162
165
|
|
|
163
166
|
// default callback so we don't call an undefined function, but in practice it should never be used
|
|
164
167
|
const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
|
|
@@ -848,7 +851,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
848
851
|
* @memberof Meeting
|
|
849
852
|
*/
|
|
850
853
|
// @ts-ignore
|
|
851
|
-
this.webinar = new Webinar({}, {parent: this.webex});
|
|
854
|
+
this.webinar = new Webinar({meetingId: this.id}, {parent: this.webex});
|
|
852
855
|
/**
|
|
853
856
|
* helper class for managing receive slots (for multistream media connections)
|
|
854
857
|
*/
|
|
@@ -1767,15 +1770,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1767
1770
|
this.meetingInfo = err.meetingInfo;
|
|
1768
1771
|
}
|
|
1769
1772
|
throw new PermissionError();
|
|
1770
|
-
} else if (err instanceof
|
|
1773
|
+
} else if (err instanceof MeetingInfoV2JoinWebinarError) {
|
|
1771
1774
|
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION;
|
|
1775
|
+
if (WEBINAR_ERROR_WEBCAST.includes(err.wbxAppApiCode)) {
|
|
1776
|
+
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NEED_JOIN_WITH_WEBCAST;
|
|
1777
|
+
} else if (WEBINAR_ERROR_REGISTRATIONID.includes(err.wbxAppApiCode)) {
|
|
1778
|
+
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATIONID;
|
|
1779
|
+
}
|
|
1772
1780
|
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1773
1781
|
|
|
1774
1782
|
if (err.meetingInfo) {
|
|
1775
1783
|
this.meetingInfo = err.meetingInfo;
|
|
1776
1784
|
}
|
|
1777
1785
|
|
|
1778
|
-
throw new
|
|
1786
|
+
throw new JoinWebinarError();
|
|
1779
1787
|
} else if (err instanceof MeetingInfoV2PasswordError) {
|
|
1780
1788
|
LoggerProxy.logger.info(
|
|
1781
1789
|
// @ts-ignore
|
|
@@ -2734,6 +2742,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2734
2742
|
this.triggerAnnotationInfoEvent(contentShare, previousContentShare);
|
|
2735
2743
|
|
|
2736
2744
|
if (
|
|
2745
|
+
!payload.forceUpdate &&
|
|
2737
2746
|
contentShare.beneficiaryId === previousContentShare?.beneficiaryId &&
|
|
2738
2747
|
contentShare.disposition === previousContentShare?.disposition &&
|
|
2739
2748
|
contentShare.deviceUrlSharing === previousContentShare.deviceUrlSharing &&
|
|
@@ -2780,7 +2789,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2780
2789
|
// It does not matter who requested to share the whiteboard, everyone gets the same view
|
|
2781
2790
|
else if (whiteboardShare.disposition === FLOOR_ACTION.GRANTED) {
|
|
2782
2791
|
// WHITEBOARD - sharing whiteboard
|
|
2783
|
-
|
|
2792
|
+
// Webinar attendee should receive whiteboard as remote share
|
|
2793
|
+
newShareStatus =
|
|
2794
|
+
this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee
|
|
2795
|
+
? SHARE_STATUS.REMOTE_SHARE_ACTIVE
|
|
2796
|
+
: SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
2784
2797
|
}
|
|
2785
2798
|
// or if content share is either released or null and whiteboard share is either released or null, no one is sharing
|
|
2786
2799
|
else if (
|
|
@@ -2795,6 +2808,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2795
2808
|
LoggerProxy.logger.info(
|
|
2796
2809
|
`Meeting:index#setUpLocusInfoMediaInactiveListener --> this.shareStatus=${this.shareStatus} newShareStatus=${newShareStatus}`
|
|
2797
2810
|
);
|
|
2811
|
+
|
|
2798
2812
|
if (newShareStatus !== this.shareStatus) {
|
|
2799
2813
|
const oldShareStatus = this.shareStatus;
|
|
2800
2814
|
|
|
@@ -3052,7 +3066,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3052
3066
|
*/
|
|
3053
3067
|
private setUpLocusResourcesListener() {
|
|
3054
3068
|
this.locusInfo.on(LOCUSINFO.EVENTS.LINKS_RESOURCES, (payload) => {
|
|
3055
|
-
|
|
3069
|
+
if (payload) {
|
|
3070
|
+
this.webinar.updateWebcastUrl(payload);
|
|
3071
|
+
Trigger.trigger(
|
|
3072
|
+
this,
|
|
3073
|
+
{
|
|
3074
|
+
file: 'meeting/index',
|
|
3075
|
+
function: 'setUpLocusInfoMeetingInfoListener',
|
|
3076
|
+
},
|
|
3077
|
+
EVENT_TRIGGERS.MEETING_RESOURCE_LINKS_UPDATE,
|
|
3078
|
+
{
|
|
3079
|
+
payload,
|
|
3080
|
+
}
|
|
3081
|
+
);
|
|
3082
|
+
}
|
|
3056
3083
|
});
|
|
3057
3084
|
}
|
|
3058
3085
|
|
|
@@ -3362,6 +3389,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3362
3389
|
}
|
|
3363
3390
|
});
|
|
3364
3391
|
|
|
3392
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED, (payload) => {
|
|
3393
|
+
Trigger.trigger(
|
|
3394
|
+
this,
|
|
3395
|
+
{
|
|
3396
|
+
file: 'meeting/index',
|
|
3397
|
+
function: 'setUpLocusInfoSelfListener',
|
|
3398
|
+
},
|
|
3399
|
+
EVENT_TRIGGERS.MEETING_SELF_BRB_UPDATE,
|
|
3400
|
+
{
|
|
3401
|
+
payload,
|
|
3402
|
+
}
|
|
3403
|
+
);
|
|
3404
|
+
});
|
|
3405
|
+
|
|
3365
3406
|
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ROLES_CHANGED, (payload) => {
|
|
3366
3407
|
const isModeratorOrCohost =
|
|
3367
3408
|
payload.newRoles?.includes(SELF_ROLES.MODERATOR) ||
|
|
@@ -3371,6 +3412,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3371
3412
|
payload.newRoles?.includes(SELF_ROLES.MODERATOR)
|
|
3372
3413
|
);
|
|
3373
3414
|
this.webinar.updateRoleChanged(payload);
|
|
3415
|
+
|
|
3374
3416
|
Trigger.trigger(
|
|
3375
3417
|
this,
|
|
3376
3418
|
{
|
|
@@ -3565,6 +3607,50 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3565
3607
|
return this.members.admitMembers(memberIds, locusUrls);
|
|
3566
3608
|
}
|
|
3567
3609
|
|
|
3610
|
+
/**
|
|
3611
|
+
* Manages be right back status updates for the current participant.
|
|
3612
|
+
*
|
|
3613
|
+
* @param {boolean} enabled - Indicates whether the user enabled brb or not.
|
|
3614
|
+
* @returns {Promise<void>} resolves when the brb status is updated or does nothing if not in a multistream meeting.
|
|
3615
|
+
* @throws {Error} - Throws an error if the request fails.
|
|
3616
|
+
*/
|
|
3617
|
+
public async beRightBack(enabled: boolean): Promise<void> {
|
|
3618
|
+
if (!this.isMultistream) {
|
|
3619
|
+
const errorMessage = 'Meeting:index#beRightBack --> Not a multistream meeting';
|
|
3620
|
+
const error = new Error(errorMessage);
|
|
3621
|
+
|
|
3622
|
+
LoggerProxy.logger.error(error);
|
|
3623
|
+
|
|
3624
|
+
return Promise.reject(error);
|
|
3625
|
+
}
|
|
3626
|
+
|
|
3627
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
3628
|
+
const errorMessage = 'Meeting:index#beRightBack --> WebRTC media connection is not defined';
|
|
3629
|
+
const error = new Error(errorMessage);
|
|
3630
|
+
|
|
3631
|
+
LoggerProxy.logger.error(error);
|
|
3632
|
+
|
|
3633
|
+
return Promise.reject(error);
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
// this logic should be applied only to multistream meetings
|
|
3637
|
+
return this.meetingRequest
|
|
3638
|
+
.setBrb({
|
|
3639
|
+
enabled,
|
|
3640
|
+
locusUrl: this.locusUrl,
|
|
3641
|
+
deviceUrl: this.deviceUrl,
|
|
3642
|
+
selfId: this.selfId,
|
|
3643
|
+
})
|
|
3644
|
+
.then(() => {
|
|
3645
|
+
this.sendSlotManager.setSourceStateOverride(MediaType.VideoMain, enabled ? 'away' : null);
|
|
3646
|
+
})
|
|
3647
|
+
.catch((error) => {
|
|
3648
|
+
LoggerProxy.logger.error('Meeting:index#beRightBack --> Error ', error);
|
|
3649
|
+
|
|
3650
|
+
return Promise.reject(error);
|
|
3651
|
+
});
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3568
3654
|
/**
|
|
3569
3655
|
* Remove the member from the meeting, boot them
|
|
3570
3656
|
* @param {String} memberId
|
|
@@ -4099,10 +4185,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4099
4185
|
*/
|
|
4100
4186
|
private setLogUploadTimer() {
|
|
4101
4187
|
// start with short timeouts and increase them later on so in case users have very long multi-hour meetings we don't get too fragmented logs
|
|
4102
|
-
const LOG_UPLOAD_INTERVALS = [0.1,
|
|
4188
|
+
const LOG_UPLOAD_INTERVALS = [0.1, 15, 30, 60]; // in minutes
|
|
4103
4189
|
|
|
4104
4190
|
const delay =
|
|
4105
4191
|
1000 *
|
|
4192
|
+
60 *
|
|
4106
4193
|
// @ts-ignore - config coming from registerPlugin
|
|
4107
4194
|
this.config.logUploadIntervalMultiplicationFactor *
|
|
4108
4195
|
LOG_UPLOAD_INTERVALS[this.logUploadIntervalIndex];
|
|
@@ -4541,11 +4628,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4541
4628
|
* Close the peer connections and remove them from the class.
|
|
4542
4629
|
* Cleanup any media connection related things.
|
|
4543
4630
|
*
|
|
4631
|
+
* @param {boolean} resetMuteStates whether to also reset the audio/video mute state information
|
|
4544
4632
|
* @returns {Promise}
|
|
4545
4633
|
* @public
|
|
4546
4634
|
* @memberof Meeting
|
|
4547
4635
|
*/
|
|
4548
|
-
public closePeerConnections() {
|
|
4636
|
+
public closePeerConnections(resetMuteStates = true) {
|
|
4549
4637
|
if (this.mediaProperties.webrtcMediaConnection) {
|
|
4550
4638
|
if (this.remoteMediaManager) {
|
|
4551
4639
|
this.remoteMediaManager.stop();
|
|
@@ -4562,8 +4650,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4562
4650
|
this.setNetworkStatus(undefined);
|
|
4563
4651
|
}
|
|
4564
4652
|
|
|
4565
|
-
|
|
4566
|
-
|
|
4653
|
+
if (resetMuteStates) {
|
|
4654
|
+
this.audio = null;
|
|
4655
|
+
this.video = null;
|
|
4656
|
+
}
|
|
4567
4657
|
|
|
4568
4658
|
return Promise.resolve();
|
|
4569
4659
|
}
|
|
@@ -4823,7 +4913,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4823
4913
|
* @param {Object} options - options to join with media
|
|
4824
4914
|
* @param {JoinOptions} [options.joinOptions] - see #join()
|
|
4825
4915
|
* @param {AddMediaOptions} [options.mediaOptions] - see #addMedia()
|
|
4826
|
-
* @returns {Promise} -- {join: see join(), media: see addMedia()}
|
|
4916
|
+
* @returns {Promise} -- {join: see join(), media: see addMedia(), multistreamEnabled: flag to indicate if we managed to join in multistream mode}
|
|
4827
4917
|
* @public
|
|
4828
4918
|
* @memberof Meeting
|
|
4829
4919
|
* @example
|
|
@@ -4913,6 +5003,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4913
5003
|
return {
|
|
4914
5004
|
join: joinResponse,
|
|
4915
5005
|
media: mediaResponse,
|
|
5006
|
+
multistreamEnabled: this.isMultistream,
|
|
4916
5007
|
};
|
|
4917
5008
|
} catch (error) {
|
|
4918
5009
|
LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);
|
|
@@ -4921,7 +5012,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4921
5012
|
|
|
4922
5013
|
this.roap.abortTurnDiscovery();
|
|
4923
5014
|
|
|
4924
|
-
if
|
|
5015
|
+
// if this was the first attempt, let's do a retry
|
|
5016
|
+
let shouldRetry = !isRetry;
|
|
5017
|
+
|
|
5018
|
+
if (CallDiagnosticUtils.isSdpOfferCreationError(error)) {
|
|
5019
|
+
// errors related to offer creation (for example missing H264 codec) will happen again no matter how many times we try,
|
|
5020
|
+
// so there is no point doing a retry
|
|
5021
|
+
shouldRetry = false;
|
|
5022
|
+
}
|
|
5023
|
+
|
|
5024
|
+
// we only want to call leave if join was successful and this was a retry or we won't be doing any more retries
|
|
5025
|
+
if (joined && (isRetry || !shouldRetry)) {
|
|
4925
5026
|
try {
|
|
4926
5027
|
await this.leave({resourceId: joinOptions?.resourceId, reason: 'joinWithMedia failure'});
|
|
4927
5028
|
} catch (e) {
|
|
@@ -4945,15 +5046,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4945
5046
|
}
|
|
4946
5047
|
);
|
|
4947
5048
|
|
|
4948
|
-
// if this was the first attempt, let's do a retry
|
|
4949
|
-
let shouldRetry = !isRetry;
|
|
4950
|
-
|
|
4951
|
-
if (CallDiagnosticUtils.isSdpOfferCreationError(error)) {
|
|
4952
|
-
// errors related to offer creation (for example missing H264 codec) will happen again no matter how many times we try,
|
|
4953
|
-
// so there is no point doing a retry
|
|
4954
|
-
shouldRetry = false;
|
|
4955
|
-
}
|
|
4956
|
-
|
|
4957
5049
|
if (shouldRetry) {
|
|
4958
5050
|
LoggerProxy.logger.warn('Meeting:index#joinWithMedia --> retrying call to joinWithMedia');
|
|
4959
5051
|
this.joinWithMediaRetryInfo.isRetry = true;
|
|
@@ -5209,7 +5301,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5209
5301
|
(this.config.receiveReactions || options.receiveReactions) &&
|
|
5210
5302
|
this.isReactionsSupported()
|
|
5211
5303
|
) {
|
|
5212
|
-
const
|
|
5304
|
+
const member = this.members.membersCollection.get(e.data.sender.participantId);
|
|
5305
|
+
if (!member) {
|
|
5306
|
+
// @ts-ignore -- fix type
|
|
5307
|
+
LoggerProxy.logger.warn(
|
|
5308
|
+
`Meeting:index#processRelayEvent --> Skipping handling of ${REACTION_RELAY_TYPES.REACTION} for ${this.id}. participantId ${e.data.sender.participantId} does not exist in membersCollection.`
|
|
5309
|
+
);
|
|
5310
|
+
break;
|
|
5311
|
+
}
|
|
5312
|
+
|
|
5313
|
+
const {name} = member;
|
|
5213
5314
|
const processedReaction: ProcessedReaction = {
|
|
5214
5315
|
reaction: e.data.reaction,
|
|
5215
5316
|
sender: {
|
|
@@ -5263,6 +5364,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5263
5364
|
this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
|
|
5264
5365
|
);
|
|
5265
5366
|
|
|
5367
|
+
// @ts-ignore
|
|
5368
|
+
this.webex.internal.voicea.deregisterEvents();
|
|
5369
|
+
|
|
5266
5370
|
this.areVoiceaEventsSetup = false;
|
|
5267
5371
|
this.triggerStopReceivingTranscriptionEvent();
|
|
5268
5372
|
}
|
|
@@ -5373,16 +5477,19 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5373
5477
|
this.meetingFiniteStateMachine.reset();
|
|
5374
5478
|
}
|
|
5375
5479
|
|
|
5376
|
-
//
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5480
|
+
// send client.call.initiated unless told not to
|
|
5481
|
+
if (options.sendCallInitiated !== false) {
|
|
5482
|
+
// @ts-ignore
|
|
5483
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
5484
|
+
name: 'client.call.initiated',
|
|
5485
|
+
payload: {
|
|
5486
|
+
trigger: this.callStateForMetrics.joinTrigger || 'user-interaction',
|
|
5487
|
+
isRoapCallEnabled: true,
|
|
5488
|
+
pstnAudioType: options?.pstnAudioType,
|
|
5489
|
+
},
|
|
5490
|
+
options: {meetingId: this.id},
|
|
5491
|
+
});
|
|
5492
|
+
}
|
|
5386
5493
|
|
|
5387
5494
|
LoggerProxy.logger.log('Meeting:index#join --> Joining a meeting');
|
|
5388
5495
|
|
|
@@ -5570,17 +5677,23 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5570
5677
|
*/
|
|
5571
5678
|
async updateLLMConnection() {
|
|
5572
5679
|
// @ts-ignore - Fix type
|
|
5573
|
-
const {url, info: {datachannelUrl} = {}} = this.locusInfo;
|
|
5680
|
+
const {url, info: {datachannelUrl, practiceSessionDatachannelUrl} = {}} = this.locusInfo;
|
|
5574
5681
|
|
|
5575
5682
|
const isJoined = this.isJoined();
|
|
5576
5683
|
|
|
5684
|
+
// webinar panelist should use new data channel in practice session
|
|
5685
|
+
const dataChannelUrl =
|
|
5686
|
+
this.webinar.isJoinPracticeSessionDataChannel() && practiceSessionDatachannelUrl
|
|
5687
|
+
? practiceSessionDatachannelUrl
|
|
5688
|
+
: datachannelUrl;
|
|
5689
|
+
|
|
5577
5690
|
// @ts-ignore - Fix type
|
|
5578
5691
|
if (this.webex.internal.llm.isConnected()) {
|
|
5579
5692
|
if (
|
|
5580
5693
|
// @ts-ignore - Fix type
|
|
5581
5694
|
url === this.webex.internal.llm.getLocusUrl() &&
|
|
5582
5695
|
// @ts-ignore - Fix type
|
|
5583
|
-
|
|
5696
|
+
dataChannelUrl === this.webex.internal.llm.getDatachannelUrl() &&
|
|
5584
5697
|
isJoined
|
|
5585
5698
|
) {
|
|
5586
5699
|
return undefined;
|
|
@@ -5597,7 +5710,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5597
5710
|
|
|
5598
5711
|
// @ts-ignore - Fix type
|
|
5599
5712
|
return this.webex.internal.llm
|
|
5600
|
-
.registerAndConnect(url,
|
|
5713
|
+
.registerAndConnect(url, dataChannelUrl)
|
|
5601
5714
|
.then((registerAndConnectResult) => {
|
|
5602
5715
|
// @ts-ignore - Fix type
|
|
5603
5716
|
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
@@ -5969,6 +6082,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5969
6082
|
public roapMessageReceived = (roapMessage: RoapMessage) => {
|
|
5970
6083
|
const mediaServer = MeetingsUtil.getMediaServer(roapMessage.sdp);
|
|
5971
6084
|
|
|
6085
|
+
if (this.isMultistream && mediaServer !== 'homer') {
|
|
6086
|
+
throw new MultistreamNotSupportedError(
|
|
6087
|
+
`Client asked for multistream backend (Homer), but got ${mediaServer} instead`
|
|
6088
|
+
);
|
|
6089
|
+
}
|
|
5972
6090
|
this.mediaProperties.webrtcMediaConnection.roapMessageReceived(roapMessage);
|
|
5973
6091
|
|
|
5974
6092
|
if (mediaServer) {
|
|
@@ -6091,16 +6209,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6091
6209
|
logText: `${LOG_HEADER} Roap Offer`,
|
|
6092
6210
|
}
|
|
6093
6211
|
).catch((error) => {
|
|
6212
|
+
const multistreamNotSupported = error instanceof MultistreamNotSupportedError;
|
|
6213
|
+
|
|
6094
6214
|
// @ts-ignore
|
|
6095
6215
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
6096
6216
|
name: 'client.media-engine.remote-sdp-received',
|
|
6097
6217
|
payload: {
|
|
6098
|
-
canProceed:
|
|
6218
|
+
canProceed: multistreamNotSupported,
|
|
6099
6219
|
errors: [
|
|
6100
6220
|
// @ts-ignore
|
|
6101
6221
|
this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
|
|
6102
6222
|
{
|
|
6103
|
-
clientErrorCode:
|
|
6223
|
+
clientErrorCode: multistreamNotSupported
|
|
6224
|
+
? CALL_DIAGNOSTIC_CONFIG.MULTISTREAM_NOT_AVAILABLE_CLIENT_CODE
|
|
6225
|
+
: CALL_DIAGNOSTIC_CONFIG.MISSING_ROAP_ANSWER_CLIENT_CODE,
|
|
6104
6226
|
}
|
|
6105
6227
|
),
|
|
6106
6228
|
],
|
|
@@ -6108,7 +6230,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6108
6230
|
options: {meetingId: this.id, rawError: error},
|
|
6109
6231
|
});
|
|
6110
6232
|
|
|
6111
|
-
this.deferSDPAnswer.reject(
|
|
6233
|
+
this.deferSDPAnswer.reject(error);
|
|
6112
6234
|
clearTimeout(this.sdpResponseTimer);
|
|
6113
6235
|
this.sdpResponseTimer = undefined;
|
|
6114
6236
|
});
|
|
@@ -6436,6 +6558,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6436
6558
|
this.webex.meetings.geoHintInfo?.clientAddress ||
|
|
6437
6559
|
options.data.intervalMetadata.peerReflexiveIP ||
|
|
6438
6560
|
MQA_STATS.DEFAULT_IP;
|
|
6561
|
+
|
|
6562
|
+
const {members} = this.getMembers().membersCollection;
|
|
6563
|
+
|
|
6564
|
+
// Count members that are in the meeting
|
|
6565
|
+
options.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
|
|
6566
|
+
(member: Member) => member.isInMeeting
|
|
6567
|
+
).length;
|
|
6568
|
+
|
|
6439
6569
|
// @ts-ignore
|
|
6440
6570
|
this.webex.internal.newMetrics.submitMQE({
|
|
6441
6571
|
name: 'client.mediaquality.event',
|
|
@@ -6767,32 +6897,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6767
6897
|
}
|
|
6768
6898
|
}
|
|
6769
6899
|
|
|
6770
|
-
/**
|
|
6771
|
-
* Handles device logging
|
|
6772
|
-
*
|
|
6773
|
-
* @private
|
|
6774
|
-
* @static
|
|
6775
|
-
* @param {boolean} isAudioEnabled
|
|
6776
|
-
* @param {boolean} isVideoEnabled
|
|
6777
|
-
* @returns {Promise<void>}
|
|
6778
|
-
*/
|
|
6779
|
-
|
|
6780
|
-
private static async handleDeviceLogging(isAudioEnabled, isVideoEnabled): Promise<void> {
|
|
6781
|
-
try {
|
|
6782
|
-
let devices = [];
|
|
6783
|
-
if (isVideoEnabled && isAudioEnabled) {
|
|
6784
|
-
devices = await getDevices();
|
|
6785
|
-
} else if (isVideoEnabled) {
|
|
6786
|
-
devices = await getDevices(Media.DeviceKind.VIDEO_INPUT);
|
|
6787
|
-
} else if (isAudioEnabled) {
|
|
6788
|
-
devices = await getDevices(Media.DeviceKind.AUDIO_INPUT);
|
|
6789
|
-
}
|
|
6790
|
-
MeetingUtil.handleDeviceLogging(devices);
|
|
6791
|
-
} catch {
|
|
6792
|
-
// getDevices may fail if we don't have browser permissions, that's ok, we still can have a media connection
|
|
6793
|
-
}
|
|
6794
|
-
}
|
|
6795
|
-
|
|
6796
6900
|
/**
|
|
6797
6901
|
* Returns a promise. This promise is created once the local sdp offer has been successfully created and is resolved
|
|
6798
6902
|
* once the remote sdp answer has been received.
|
|
@@ -7016,7 +7120,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7016
7120
|
|
|
7017
7121
|
const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
|
|
7018
7122
|
|
|
7019
|
-
LoggerProxy.logger.info(
|
|
7123
|
+
LoggerProxy.logger.info(
|
|
7124
|
+
`${LOG_HEADER} media connection created this.isMultistream=${this.isMultistream}`
|
|
7125
|
+
);
|
|
7020
7126
|
|
|
7021
7127
|
if (this.isMultistream) {
|
|
7022
7128
|
this.remoteMediaManager = new RemoteMediaManager(
|
|
@@ -7094,6 +7200,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7094
7200
|
}
|
|
7095
7201
|
}
|
|
7096
7202
|
|
|
7203
|
+
/**
|
|
7204
|
+
* Cleans up stats analyzer, peer connection and other things before
|
|
7205
|
+
* we can create a new transcoded media connection
|
|
7206
|
+
*
|
|
7207
|
+
* @private
|
|
7208
|
+
* @returns {Promise<void>}
|
|
7209
|
+
*/
|
|
7210
|
+
private async downgradeFromMultistreamToTranscoded(): Promise<void> {
|
|
7211
|
+
if (this.statsAnalyzer) {
|
|
7212
|
+
await this.statsAnalyzer.stopAnalyzer();
|
|
7213
|
+
}
|
|
7214
|
+
this.statsAnalyzer = null;
|
|
7215
|
+
|
|
7216
|
+
this.isMultistream = false;
|
|
7217
|
+
|
|
7218
|
+
if (this.mediaProperties.webrtcMediaConnection) {
|
|
7219
|
+
// close peer connection, but don't reset mute state information, because we will want to use it on the retry
|
|
7220
|
+
this.closePeerConnections(false);
|
|
7221
|
+
|
|
7222
|
+
this.mediaProperties.unsetPeerConnection();
|
|
7223
|
+
}
|
|
7224
|
+
|
|
7225
|
+
this.locusMediaRequest?.downgradeFromMultistreamToTranscoded();
|
|
7226
|
+
|
|
7227
|
+
this.createStatsAnalyzer();
|
|
7228
|
+
}
|
|
7229
|
+
|
|
7097
7230
|
/**
|
|
7098
7231
|
* Sends stats report, closes peer connection and cleans up any media connection
|
|
7099
7232
|
* related things before trying to establish media connection again with turn server
|
|
@@ -7288,19 +7421,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7288
7421
|
|
|
7289
7422
|
this.createStatsAnalyzer();
|
|
7290
7423
|
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7424
|
+
try {
|
|
7425
|
+
await this.establishMediaConnection(
|
|
7426
|
+
remoteMediaManagerConfig,
|
|
7427
|
+
bundlePolicy,
|
|
7428
|
+
forceTurnDiscovery,
|
|
7429
|
+
turnServerInfo
|
|
7430
|
+
);
|
|
7431
|
+
} catch (error) {
|
|
7432
|
+
if (error instanceof MultistreamNotSupportedError) {
|
|
7433
|
+
LoggerProxy.logger.warn(
|
|
7434
|
+
`${LOG_HEADER} we asked for multistream backend (Homer), but got transcoded backend, recreating media connection...`
|
|
7435
|
+
);
|
|
7297
7436
|
|
|
7298
|
-
|
|
7299
|
-
await Meeting.handleDeviceLogging(audioEnabled, videoEnabled);
|
|
7300
|
-
} else {
|
|
7301
|
-
LoggerProxy.logger.info(`${LOG_HEADER} device logging not required`);
|
|
7302
|
-
}
|
|
7437
|
+
await this.downgradeFromMultistreamToTranscoded();
|
|
7303
7438
|
|
|
7439
|
+
// Establish new media connection with forced TURN discovery
|
|
7440
|
+
// We need to do TURN discovery again, because backend will be creating a new confluence, so it might land on a different node or cluster
|
|
7441
|
+
await this.establishMediaConnection(
|
|
7442
|
+
remoteMediaManagerConfig,
|
|
7443
|
+
bundlePolicy,
|
|
7444
|
+
true,
|
|
7445
|
+
undefined
|
|
7446
|
+
);
|
|
7447
|
+
} else {
|
|
7448
|
+
throw error;
|
|
7449
|
+
}
|
|
7450
|
+
}
|
|
7304
7451
|
if (this.mediaProperties.hasLocalShareStream()) {
|
|
7305
7452
|
await this.enqueueScreenShareFloorRequest();
|
|
7306
7453
|
}
|
|
@@ -8270,7 +8417,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8270
8417
|
if (layoutType) {
|
|
8271
8418
|
if (!LAYOUT_TYPES.includes(layoutType)) {
|
|
8272
8419
|
return this.rejectWithErrorLog(
|
|
8273
|
-
|
|
8420
|
+
`Meeting:index#changeVideoLayout --> cannot change video layout, invalid layoutType "${layoutType}" received.`
|
|
8274
8421
|
);
|
|
8275
8422
|
}
|
|
8276
8423
|
|
|
@@ -8628,6 +8775,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8628
8775
|
this.stopTranscription();
|
|
8629
8776
|
this.transcription = undefined;
|
|
8630
8777
|
}
|
|
8778
|
+
|
|
8779
|
+
this.annotation.deregisterEvents();
|
|
8780
|
+
|
|
8781
|
+
// @ts-ignore - fix types
|
|
8782
|
+
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
8631
8783
|
};
|
|
8632
8784
|
|
|
8633
8785
|
/**
|
|
@@ -8665,10 +8817,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8665
8817
|
|
|
8666
8818
|
return;
|
|
8667
8819
|
}
|
|
8668
|
-
|
|
8820
|
+
|
|
8669
8821
|
const keepAliveInterval = (this.joinedWith.keepAliveSecs - 1) * 750; // taken from UCF
|
|
8670
8822
|
|
|
8671
8823
|
this.keepAliveTimerId = setInterval(() => {
|
|
8824
|
+
const {keepAliveUrl} = this.joinedWith;
|
|
8825
|
+
|
|
8672
8826
|
this.meetingRequest.keepAlive({keepAliveUrl}).catch((error) => {
|
|
8673
8827
|
LoggerProxy.logger.warn(
|
|
8674
8828
|
`Meeting:index#startKeepAlive --> Stopping sending keepAlives to ${keepAliveUrl} after error ${error}`
|
|
@@ -342,4 +342,11 @@ export class LocusMediaRequest extends WebexPlugin {
|
|
|
342
342
|
public isConfluenceCreated() {
|
|
343
343
|
return this.confluenceState === 'created';
|
|
344
344
|
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* This method needs to be called when we downgrade from multistream to transcoded connection.
|
|
348
|
+
*/
|
|
349
|
+
public downgradeFromMultistreamToTranscoded() {
|
|
350
|
+
this.config.preferTranscoding = true;
|
|
351
|
+
}
|
|
345
352
|
}
|
package/src/meeting/request.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
_SLIDES_,
|
|
28
28
|
ANNOTATION,
|
|
29
29
|
} from '../constants';
|
|
30
|
-
import {SendReactionOptions, ToggleReactionsOptions} from './request.type';
|
|
30
|
+
import {SendReactionOptions, BrbOptions, ToggleReactionsOptions} from './request.type';
|
|
31
31
|
import MeetingUtil from './util';
|
|
32
32
|
import {AnnotationInfo} from '../annotation/annotation.types';
|
|
33
33
|
import {ClientMediaPreferences} from '../reachability/reachability.types';
|
|
@@ -909,4 +909,29 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
909
909
|
uri: locusUrl,
|
|
910
910
|
});
|
|
911
911
|
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Sends a request to set be right back status.
|
|
915
|
+
*
|
|
916
|
+
* @param {Object} options - The options for brb request.
|
|
917
|
+
* @param {boolean} options.enabled - Whether brb status is enabled.
|
|
918
|
+
* @param {string} options.locusUrl - The URL of the locus.
|
|
919
|
+
* @param {string} options.deviceUrl - The URL of the device.
|
|
920
|
+
* @param {string} options.selfId - The ID of the participant.
|
|
921
|
+
* @returns {Promise}
|
|
922
|
+
*/
|
|
923
|
+
setBrb({enabled, locusUrl, deviceUrl, selfId}: BrbOptions) {
|
|
924
|
+
const uri = `${locusUrl}/${PARTICIPANT}/${selfId}/${CONTROLS}`;
|
|
925
|
+
|
|
926
|
+
return this.locusDeltaRequest({
|
|
927
|
+
method: HTTP_VERBS.PATCH,
|
|
928
|
+
uri,
|
|
929
|
+
body: {
|
|
930
|
+
brb: {
|
|
931
|
+
enabled,
|
|
932
|
+
deviceUrl,
|
|
933
|
+
},
|
|
934
|
+
},
|
|
935
|
+
});
|
|
936
|
+
}
|
|
912
937
|
}
|