@webex/plugin-meetings 3.12.0-next.8 → 3.12.0-next.80
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/AGENTS.md +9 -0
- package/dist/aiEnableRequest/index.js +15 -2
- package/dist/aiEnableRequest/index.js.map +1 -1
- package/dist/breakouts/breakout.js +8 -3
- package/dist/breakouts/breakout.js.map +1 -1
- package/dist/breakouts/index.js +26 -2
- package/dist/breakouts/index.js.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +34 -8
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +11 -1
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +67 -29
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/util.js +91 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/hashTree/constants.js +13 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +880 -382
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/utils.js +42 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/interceptors/dataChannelAuthToken.js +75 -15
- package/dist/interceptors/dataChannelAuthToken.js.map +1 -1
- package/dist/interceptors/locusRetry.js +23 -8
- package/dist/interceptors/locusRetry.js.map +1 -1
- package/dist/interpretation/index.js +10 -1
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/interpretation.types.js +7 -0
- package/dist/interpretation/interpretation.types.js.map +1 -0
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +4 -1
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +303 -88
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/infoUtils.js +36 -5
- package/dist/locus-info/infoUtils.js.map +1 -1
- package/dist/locus-info/types.js +19 -0
- package/dist/locus-info/types.js.map +1 -1
- package/dist/media/index.js +3 -1
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +1 -0
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +5 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +1111 -724
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.js +10 -1
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +5 -2
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +29 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +2 -2
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meetings/index.js +231 -78
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js +6 -1
- package/dist/meetings/meetings.types.js.map +1 -1
- package/dist/meetings/request.js +39 -0
- package/dist/meetings/request.js.map +1 -1
- package/dist/meetings/util.js +79 -5
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +3 -0
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +4 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/codec/constants.js +63 -0
- package/dist/multistream/codec/constants.js.map +1 -0
- package/dist/multistream/mediaRequestManager.js +62 -15
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/receiveSlot.js +9 -0
- package/dist/multistream/receiveSlot.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/recording-controller/index.js +1 -3
- package/dist/recording-controller/index.js.map +1 -1
- package/dist/types/config.d.ts +2 -0
- package/dist/types/constants.d.ts +12 -1
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/controls-options-manager/index.d.ts +16 -0
- package/dist/types/hashTree/constants.d.ts +2 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +146 -17
- package/dist/types/hashTree/utils.d.ts +18 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/interceptors/locusRetry.d.ts +4 -4
- package/dist/types/interpretation/interpretation.types.d.ts +10 -0
- package/dist/types/locus-info/index.d.ts +50 -6
- package/dist/types/locus-info/infoUtils.d.ts +8 -0
- package/dist/types/locus-info/types.d.ts +21 -1
- package/dist/types/media/properties.d.ts +1 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
- package/dist/types/meeting/index.d.ts +80 -6
- package/dist/types/meeting/request.d.ts +1 -0
- package/dist/types/meeting/util.d.ts +9 -0
- package/dist/types/meetings/index.d.ts +30 -2
- package/dist/types/meetings/meetings.types.d.ts +15 -0
- package/dist/types/meetings/request.d.ts +14 -0
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +1 -0
- package/dist/types/member/util.d.ts +1 -0
- package/dist/types/metrics/constants.d.ts +3 -0
- package/dist/types/multistream/codec/constants.d.ts +7 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +22 -5
- package/dist/types/reactions/reactions.type.d.ts +3 -0
- package/dist/webinar/index.js +305 -159
- package/dist/webinar/index.js.map +1 -1
- package/package.json +22 -22
- package/src/aiEnableRequest/index.ts +16 -0
- package/src/breakouts/breakout.ts +3 -1
- package/src/breakouts/index.ts +31 -0
- package/src/config.ts +2 -0
- package/src/constants.ts +16 -2
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +87 -28
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +16 -0
- package/src/hashTree/hashTreeParser.ts +580 -196
- package/src/hashTree/utils.ts +36 -0
- package/src/index.ts +6 -0
- package/src/interceptors/dataChannelAuthToken.ts +88 -12
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/interpretation/index.ts +27 -9
- package/src/interpretation/interpretation.types.ts +11 -0
- package/src/locus-info/controlsUtils.ts +3 -1
- package/src/locus-info/index.ts +304 -100
- package/src/locus-info/infoUtils.ts +41 -6
- package/src/locus-info/types.ts +25 -1
- package/src/media/index.ts +3 -0
- package/src/media/properties.ts +1 -0
- package/src/meeting/in-meeting-actions.ts +8 -0
- package/src/meeting/index.ts +441 -65
- package/src/meeting/muteState.ts +10 -1
- package/src/meeting/request.ts +11 -0
- package/src/meeting/util.ts +33 -2
- package/src/meeting-info/meeting-info-v2.ts +4 -2
- package/src/meetings/index.ts +134 -44
- package/src/meetings/meetings.types.ts +19 -0
- package/src/meetings/request.ts +43 -0
- package/src/meetings/util.ts +97 -1
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +3 -0
- package/src/metrics/constants.ts +3 -0
- package/src/multistream/codec/constants.ts +58 -0
- package/src/multistream/mediaRequestManager.ts +119 -28
- package/src/multistream/receiveSlot.ts +18 -0
- package/src/reactions/reactions.type.ts +3 -0
- package/src/recording-controller/index.ts +1 -2
- package/src/webinar/index.ts +214 -36
- package/test/unit/spec/aiEnableRequest/index.ts +86 -0
- package/test/unit/spec/breakouts/breakout.ts +9 -3
- package/test/unit/spec/breakouts/index.ts +49 -0
- package/test/unit/spec/controls-options-manager/index.js +244 -29
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +1838 -180
- package/test/unit/spec/hashTree/utils.ts +125 -1
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +196 -0
- package/test/unit/spec/interceptors/locusRetry.ts +205 -4
- package/test/unit/spec/interpretation/index.ts +26 -4
- package/test/unit/spec/locus-info/controlsUtils.js +172 -57
- package/test/unit/spec/locus-info/index.js +499 -81
- package/test/unit/spec/locus-info/infoUtils.js +87 -0
- package/test/unit/spec/media/index.ts +31 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
- package/test/unit/spec/meeting/index.js +1552 -42
- package/test/unit/spec/meeting/muteState.js +81 -0
- package/test/unit/spec/meeting/request.js +12 -0
- package/test/unit/spec/meeting/utils.js +82 -0
- package/test/unit/spec/meeting-info/meetinginfov2.js +19 -10
- package/test/unit/spec/meetings/index.js +363 -10
- package/test/unit/spec/meetings/request.js +141 -0
- package/test/unit/spec/meetings/utils.js +189 -0
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/multistream/mediaRequestManager.ts +501 -37
- package/test/unit/spec/recording-controller/index.js +9 -8
- package/test/unit/spec/webinar/index.ts +329 -28
package/src/meetings/util.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/* globals window */
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
_CALL_,
|
|
4
5
|
_CREATED_,
|
|
5
6
|
_INCOMING_,
|
|
6
7
|
_JOINED_,
|
|
7
8
|
_LEFT_,
|
|
8
9
|
DESTINATION_TYPE,
|
|
9
10
|
_MOVED_,
|
|
11
|
+
_SIP_BRIDGE_,
|
|
12
|
+
_SPACE_SHARE_,
|
|
10
13
|
BREAKOUTS,
|
|
11
14
|
EVENT_TRIGGERS,
|
|
12
15
|
LOCUS,
|
|
@@ -18,6 +21,7 @@ import Trigger from '../common/events/trigger-proxy';
|
|
|
18
21
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
19
22
|
import Metrics from '../metrics';
|
|
20
23
|
import {MEETING_KEY} from './meetings.types';
|
|
24
|
+
import {EndMeetingReason, LocusFullState} from '../locus-info/types';
|
|
21
25
|
|
|
22
26
|
/**
|
|
23
27
|
* Meetings Media Codec Missing Event
|
|
@@ -152,6 +156,30 @@ MeetingsUtil.parseDefaultSiteFromMeetingPreferences = (userPreferences) => {
|
|
|
152
156
|
return result;
|
|
153
157
|
};
|
|
154
158
|
|
|
159
|
+
MeetingsUtil.getSiteName = (site: string, multipartSitePrefixList: string[] = []) => {
|
|
160
|
+
if (!site) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let siteName: string | undefined;
|
|
165
|
+
|
|
166
|
+
multipartSitePrefixList.forEach((multipartSitePrefix) => {
|
|
167
|
+
if (!siteName && site.includes(multipartSitePrefix)) {
|
|
168
|
+
const secondDot = site.indexOf('.', site.indexOf('.') + 1);
|
|
169
|
+
|
|
170
|
+
siteName = site.substring(0, secondDot);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (siteName) {
|
|
175
|
+
return siteName;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
siteName = site.substring(0, site.indexOf('.'));
|
|
179
|
+
|
|
180
|
+
return siteName;
|
|
181
|
+
};
|
|
182
|
+
|
|
155
183
|
/**
|
|
156
184
|
* Will check to see if the H.264 media codec is supported.
|
|
157
185
|
* @async
|
|
@@ -266,6 +294,49 @@ MeetingsUtil.getThisDevice = (newLocus: any, deviceUrl: string) => {
|
|
|
266
294
|
return null;
|
|
267
295
|
};
|
|
268
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Checks if the fullState indicates the meeting has fully ended (not just a breakout move).
|
|
299
|
+
* @param {Object} fullState locus fullState data
|
|
300
|
+
* @returns {boolean}
|
|
301
|
+
*/
|
|
302
|
+
MeetingsUtil.isWholeMeetingEnded = (fullState: LocusFullState): boolean => {
|
|
303
|
+
return (
|
|
304
|
+
fullState.state === LOCUS.STATE.INACTIVE &&
|
|
305
|
+
fullState.endMeetingReason !== EndMeetingReason.breakoutEnded
|
|
306
|
+
);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Checks if the self state in a locus indicates a breakout move or breakout end.
|
|
311
|
+
* Returns true when:
|
|
312
|
+
* - self state is LEFT with reason MOVED (regular breakout move), OR
|
|
313
|
+
* - fullState is INACTIVE with endMeetingReason BREAKOUT_ENDED (breakout session ended)
|
|
314
|
+
* @param {Object} locus locus data
|
|
315
|
+
* @returns {boolean}
|
|
316
|
+
*/
|
|
317
|
+
MeetingsUtil.isSelfMovedOrBreakoutEnded = (locus: any): boolean => {
|
|
318
|
+
const isSelfLeftMoved = locus?.self?.state === _LEFT_ && locus?.self?.reason === _MOVED_;
|
|
319
|
+
const isBreakoutEnded =
|
|
320
|
+
locus?.fullState?.state === LOCUS.STATE.INACTIVE &&
|
|
321
|
+
locus?.fullState?.endMeetingReason === EndMeetingReason.breakoutEnded;
|
|
322
|
+
|
|
323
|
+
return isSelfLeftMoved || isBreakoutEnded;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Checks if a locus is a 1:1 call using locus.fullState.type.
|
|
328
|
+
* Returns true when fullState.type is CALL, SIP_BRIDGE, or SPACE_SHARE.
|
|
329
|
+
* @param {Object} locus locus data
|
|
330
|
+
* @returns {boolean}
|
|
331
|
+
*/
|
|
332
|
+
MeetingsUtil.isOneOnOneCall = (locus: any): boolean => {
|
|
333
|
+
const fullStateType = locus?.fullState?.type;
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
fullStateType === _CALL_ || fullStateType === _SIP_BRIDGE_ || fullStateType === _SPACE_SHARE_
|
|
337
|
+
);
|
|
338
|
+
};
|
|
339
|
+
|
|
269
340
|
/**
|
|
270
341
|
* get self device joined status from locus data
|
|
271
342
|
* @param {Object} meeting current meeting data
|
|
@@ -294,7 +365,10 @@ MeetingsUtil.joinedOnThisDevice = (meeting: any, newLocus: any, deviceUrl: strin
|
|
|
294
365
|
* @private
|
|
295
366
|
*/
|
|
296
367
|
MeetingsUtil.isBreakoutLocusDTO = (newLocus: any) => {
|
|
297
|
-
return
|
|
368
|
+
return (
|
|
369
|
+
newLocus?.controls?.breakout?.sessionType === BREAKOUTS.SESSION_TYPES.BREAKOUT ||
|
|
370
|
+
!!newLocus?.info?.isBreakout
|
|
371
|
+
);
|
|
298
372
|
};
|
|
299
373
|
|
|
300
374
|
/**
|
|
@@ -310,4 +384,26 @@ MeetingsUtil.isValidBreakoutLocus = (locus: any) => {
|
|
|
310
384
|
|
|
311
385
|
return isLocusAsBreakout && !inActiveStatus && selfJoined;
|
|
312
386
|
};
|
|
387
|
+
/**
|
|
388
|
+
* check if the breakout locus is associated with the main locus by comparing the breakout control url or the replaces info in self device
|
|
389
|
+
* @param {Object} mainLocus main locus data
|
|
390
|
+
* @param {Object} breakoutLocus breakout locus data
|
|
391
|
+
* @returns {boolean}
|
|
392
|
+
* @private
|
|
393
|
+
*/
|
|
394
|
+
MeetingsUtil.isMainAssociatedWithBreakout = (mainLocus: any, breakoutLocus: any) => {
|
|
395
|
+
if (
|
|
396
|
+
mainLocus.controls?.breakout?.url &&
|
|
397
|
+
mainLocus.controls?.breakout?.url === breakoutLocus.controls?.breakout?.url
|
|
398
|
+
) {
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
const deviceUrl = breakoutLocus?.self?.deviceUrl;
|
|
402
|
+
const replaceInfo = MeetingsUtil.getThisDevice(breakoutLocus, deviceUrl)?.replaces?.[0];
|
|
403
|
+
if (replaceInfo?.locusUrl && replaceInfo.locusUrl === mainLocus.url) {
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return false;
|
|
408
|
+
};
|
|
313
409
|
export default MeetingsUtil;
|
package/src/member/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export default class Member {
|
|
|
27
27
|
isModerator: any;
|
|
28
28
|
isModeratorAssignmentProhibited: any;
|
|
29
29
|
isPresenterAssignmentProhibited: any;
|
|
30
|
+
isAttendeeAssignmentProhibited: any;
|
|
30
31
|
isMutable: any;
|
|
31
32
|
isNotAdmitted: any;
|
|
32
33
|
isRecording: any;
|
|
@@ -292,6 +293,14 @@ export default class Member {
|
|
|
292
293
|
*/
|
|
293
294
|
this.isPresenterAssignmentProhibited = null;
|
|
294
295
|
|
|
296
|
+
/**
|
|
297
|
+
* @instance
|
|
298
|
+
* @type {Boolean}
|
|
299
|
+
* @public
|
|
300
|
+
* @memberof Member
|
|
301
|
+
*/
|
|
302
|
+
this.isAttendeeAssignmentProhibited = null;
|
|
303
|
+
|
|
295
304
|
/**
|
|
296
305
|
* @instance
|
|
297
306
|
* @type {Boolean}
|
|
@@ -369,6 +378,7 @@ export default class Member {
|
|
|
369
378
|
MemberUtil.isModeratorAssignmentProhibited(participant);
|
|
370
379
|
this.isPresenterAssignmentProhibited =
|
|
371
380
|
MemberUtil.isPresenterAssignmentProhibited(participant);
|
|
381
|
+
this.isAttendeeAssignmentProhibited = MemberUtil.isAttendeeAssignmentProhibited(participant);
|
|
372
382
|
this.canApproveAIEnablement = MemberUtil.canApproveAIEnablement(participant);
|
|
373
383
|
this.processStatus(participant);
|
|
374
384
|
this.processRoles(participant);
|
package/src/member/types.ts
CHANGED
|
@@ -103,6 +103,7 @@ export interface Participant {
|
|
|
103
103
|
moderator: boolean; // Locus docs say this is deprecated and role control should be used instead
|
|
104
104
|
moderatorAssignmentNotAllowed: boolean;
|
|
105
105
|
presenterAssignmentNotAllowed: boolean;
|
|
106
|
+
attendeeAssignmentNotAllowed?: boolean;
|
|
106
107
|
person: ParticipantPerson;
|
|
107
108
|
resourceGuest: boolean;
|
|
108
109
|
state: string; // probably one of MEETING_STATE.STATES
|
package/src/member/util.ts
CHANGED
|
@@ -140,6 +140,9 @@ const MemberUtil = {
|
|
|
140
140
|
isPresenterAssignmentProhibited: (participant: Participant) =>
|
|
141
141
|
participant && participant.presenterAssignmentNotAllowed,
|
|
142
142
|
|
|
143
|
+
isAttendeeAssignmentProhibited: (participant: Participant) =>
|
|
144
|
+
!!(participant && participant.attendeeAssignmentNotAllowed),
|
|
145
|
+
|
|
143
146
|
/**
|
|
144
147
|
* checks to see if the participant id is the same as the passed id
|
|
145
148
|
* there are multiple ids that can be used
|
package/src/metrics/constants.ts
CHANGED
|
@@ -96,6 +96,9 @@ const BEHAVIORAL_METRICS = {
|
|
|
96
96
|
SET_CUSTOM_CODEC_PARAMETERS_USED: 'js_sdk_set_custom_codec_parameters_used',
|
|
97
97
|
MARK_CUSTOM_CODEC_PARAMETERS_FOR_DELETION_USED:
|
|
98
98
|
'js_sdk_mark_custom_codec_parameters_for_deletion_used',
|
|
99
|
+
HASH_TREE_SYNC_FAILURE: 'js_sdk_hash_tree_sync_failure',
|
|
100
|
+
HASH_TREE_HEARTBEAT_WATCHDOG_EXPIRED: 'js_sdk_hash_tree_heartbeat_watchdog_expired',
|
|
101
|
+
HASH_TREE_EMPTY_LOCUS_STATE_ELEMENTS: 'js_sdk_hash_tree_empty_locus_state_elements',
|
|
99
102
|
};
|
|
100
103
|
|
|
101
104
|
export {BEHAVIORAL_METRICS as default};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {AV1EncodingParams, SupportedResolution} from '@webex/internal-media-core';
|
|
2
|
+
|
|
3
|
+
export const AV1_CODEC_PARAMETERS: Record<SupportedResolution, AV1EncodingParams> = {
|
|
4
|
+
'90p': {
|
|
5
|
+
levelIdx: 0,
|
|
6
|
+
tier: 0,
|
|
7
|
+
maxWidth: 160,
|
|
8
|
+
maxHeight: 90,
|
|
9
|
+
maxPicSize: 147_456,
|
|
10
|
+
maxDecodeRate: 5_529_600,
|
|
11
|
+
},
|
|
12
|
+
'180p': {
|
|
13
|
+
levelIdx: 0,
|
|
14
|
+
tier: 0,
|
|
15
|
+
maxWidth: 320,
|
|
16
|
+
maxHeight: 180,
|
|
17
|
+
maxPicSize: 147_456,
|
|
18
|
+
maxDecodeRate: 5_529_600,
|
|
19
|
+
},
|
|
20
|
+
'360p': {
|
|
21
|
+
levelIdx: 1,
|
|
22
|
+
tier: 0,
|
|
23
|
+
maxWidth: 640,
|
|
24
|
+
maxHeight: 360,
|
|
25
|
+
maxPicSize: 278_784,
|
|
26
|
+
maxDecodeRate: 10_454_400,
|
|
27
|
+
},
|
|
28
|
+
'540p': {
|
|
29
|
+
levelIdx: 4,
|
|
30
|
+
tier: 0,
|
|
31
|
+
maxWidth: 960,
|
|
32
|
+
maxHeight: 540,
|
|
33
|
+
maxPicSize: 665_856,
|
|
34
|
+
maxDecodeRate: 24_969_600,
|
|
35
|
+
},
|
|
36
|
+
'720p': {
|
|
37
|
+
levelIdx: 5,
|
|
38
|
+
tier: 0,
|
|
39
|
+
maxWidth: 1280,
|
|
40
|
+
maxHeight: 720,
|
|
41
|
+
maxPicSize: 1_065_024,
|
|
42
|
+
maxDecodeRate: 39_938_400,
|
|
43
|
+
},
|
|
44
|
+
'1080p': {
|
|
45
|
+
levelIdx: 8,
|
|
46
|
+
tier: 0,
|
|
47
|
+
maxWidth: 1920,
|
|
48
|
+
maxHeight: 1080,
|
|
49
|
+
maxPicSize: 2_359_296,
|
|
50
|
+
maxDecodeRate: 77_856_768,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const H264_CODEC_PARAMETERS = {
|
|
55
|
+
maxFs: 8192,
|
|
56
|
+
maxFps: 3000,
|
|
57
|
+
maxMbps: 245760,
|
|
58
|
+
};
|
|
@@ -9,6 +9,11 @@ import {
|
|
|
9
9
|
getRecommendedMaxBitrateForFrameSize,
|
|
10
10
|
RecommendedOpusBitrates,
|
|
11
11
|
NamedMediaGroup,
|
|
12
|
+
AV1Codec,
|
|
13
|
+
SupportedResolution,
|
|
14
|
+
AV1EncodingParams,
|
|
15
|
+
MediaType,
|
|
16
|
+
MediaCodecMimeType,
|
|
12
17
|
} from '@webex/internal-media-core';
|
|
13
18
|
import {cloneDeepWith, debounce} from 'lodash';
|
|
14
19
|
|
|
@@ -16,6 +21,7 @@ import LoggerProxy from '../common/logs/logger-proxy';
|
|
|
16
21
|
|
|
17
22
|
import {ReceiveSlot, ReceiveSlotEvents} from './receiveSlot';
|
|
18
23
|
import {MAX_FS_VALUES} from './remoteMedia';
|
|
24
|
+
import {AV1_CODEC_PARAMETERS, H264_CODEC_PARAMETERS} from './codec/constants';
|
|
19
25
|
|
|
20
26
|
export interface ActiveSpeakerPolicyInfo {
|
|
21
27
|
policy: 'active-speaker';
|
|
@@ -54,34 +60,49 @@ export interface MediaRequest {
|
|
|
54
60
|
|
|
55
61
|
export type MediaRequestId = string;
|
|
56
62
|
|
|
57
|
-
const CODEC_DEFAULTS = {
|
|
58
|
-
h264: {
|
|
59
|
-
maxFs: 8192,
|
|
60
|
-
maxFps: 3000,
|
|
61
|
-
maxMbps: 245760,
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
|
|
65
63
|
const DEBOUNCED_SOURCE_UPDATE_TIME = 1000;
|
|
66
64
|
|
|
65
|
+
const RESOLUTION_BUCKETS: Array<[SupportedResolution, number]> = [
|
|
66
|
+
['90p', MAX_FS_VALUES['90p']],
|
|
67
|
+
['180p', MAX_FS_VALUES['180p']],
|
|
68
|
+
['360p', MAX_FS_VALUES['360p']],
|
|
69
|
+
['540p', MAX_FS_VALUES['540p']],
|
|
70
|
+
['720p', MAX_FS_VALUES['720p']],
|
|
71
|
+
];
|
|
72
|
+
|
|
67
73
|
type DegradationPreferences = {
|
|
68
74
|
maxMacroblocksLimit: number;
|
|
69
75
|
};
|
|
70
76
|
|
|
71
77
|
type SendMediaRequestsCallback = (streamRequests: StreamRequest[]) => void;
|
|
78
|
+
type GetIngressPayloadTypeCallback = (
|
|
79
|
+
mediaType: MediaType,
|
|
80
|
+
codecMimeType: MediaCodecMimeType
|
|
81
|
+
) => number | undefined;
|
|
72
82
|
type Kind = 'audio' | 'video';
|
|
73
83
|
|
|
74
|
-
type
|
|
84
|
+
type AudioMediaRequestManagerOptions = {
|
|
75
85
|
degradationPreferences: DegradationPreferences;
|
|
76
|
-
kind:
|
|
86
|
+
kind: 'audio';
|
|
77
87
|
trimRequestsToNumOfSources: boolean; // if enabled, AS speaker requests will be trimmed based on the calls to setNumCurrentSources()
|
|
78
88
|
};
|
|
79
89
|
|
|
90
|
+
type VideoMediaRequestManagerOptions = {
|
|
91
|
+
degradationPreferences: DegradationPreferences;
|
|
92
|
+
kind: 'video';
|
|
93
|
+
trimRequestsToNumOfSources: boolean;
|
|
94
|
+
enableAv1?: boolean;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
type Options = AudioMediaRequestManagerOptions | VideoMediaRequestManagerOptions;
|
|
98
|
+
|
|
80
99
|
type ClientRequestsMap = {[key: MediaRequestId]: MediaRequest};
|
|
81
100
|
|
|
82
101
|
export class MediaRequestManager {
|
|
83
102
|
private sendMediaRequestsCallback: SendMediaRequestsCallback;
|
|
84
103
|
|
|
104
|
+
private getIngressPayloadTypeCallback: GetIngressPayloadTypeCallback;
|
|
105
|
+
|
|
85
106
|
private kind: Kind;
|
|
86
107
|
|
|
87
108
|
private counter: number;
|
|
@@ -95,11 +116,17 @@ export class MediaRequestManager {
|
|
|
95
116
|
private debouncedSourceUpdateListener: () => void;
|
|
96
117
|
|
|
97
118
|
private trimRequestsToNumOfSources: boolean;
|
|
119
|
+
private enableAv1: boolean;
|
|
98
120
|
private numTotalSources: number;
|
|
99
121
|
private numLiveSources: number;
|
|
100
122
|
|
|
101
|
-
constructor(
|
|
123
|
+
constructor(
|
|
124
|
+
sendMediaRequestsCallback: SendMediaRequestsCallback,
|
|
125
|
+
getIngressPayloadTypeCallback: GetIngressPayloadTypeCallback,
|
|
126
|
+
options: Options
|
|
127
|
+
) {
|
|
102
128
|
this.sendMediaRequestsCallback = sendMediaRequestsCallback;
|
|
129
|
+
this.getIngressPayloadTypeCallback = getIngressPayloadTypeCallback;
|
|
103
130
|
this.counter = 0;
|
|
104
131
|
this.numLiveSources = 0;
|
|
105
132
|
this.numTotalSources = 0;
|
|
@@ -107,6 +134,7 @@ export class MediaRequestManager {
|
|
|
107
134
|
this.degradationPreferences = options.degradationPreferences;
|
|
108
135
|
this.kind = options.kind;
|
|
109
136
|
this.trimRequestsToNumOfSources = options.trimRequestsToNumOfSources;
|
|
137
|
+
this.enableAv1 = options.kind === 'video' && !!options.enableAv1;
|
|
110
138
|
this.sourceUpdateListener = this.commit.bind(this);
|
|
111
139
|
this.debouncedSourceUpdateListener = debounce(
|
|
112
140
|
this.sourceUpdateListener,
|
|
@@ -135,8 +163,8 @@ export class MediaRequestManager {
|
|
|
135
163
|
Object.values(clientRequests).forEach((mr) => {
|
|
136
164
|
if (mr.codecInfo) {
|
|
137
165
|
mr.codecInfo.maxFs = Math.min(
|
|
138
|
-
mr.preferredMaxFs ||
|
|
139
|
-
mr.codecInfo.maxFs ||
|
|
166
|
+
mr.preferredMaxFs || H264_CODEC_PARAMETERS.maxFs,
|
|
167
|
+
mr.codecInfo.maxFs || H264_CODEC_PARAMETERS.maxFs,
|
|
140
168
|
maxFsLimits[i]
|
|
141
169
|
);
|
|
142
170
|
// we only consider sources with "live" state
|
|
@@ -176,7 +204,7 @@ export class MediaRequestManager {
|
|
|
176
204
|
}
|
|
177
205
|
|
|
178
206
|
return getRecommendedMaxBitrateForFrameSize(
|
|
179
|
-
mediaRequest.codecInfo.maxFs ||
|
|
207
|
+
mediaRequest.codecInfo.maxFs || H264_CODEC_PARAMETERS.maxFs
|
|
180
208
|
);
|
|
181
209
|
}
|
|
182
210
|
|
|
@@ -192,12 +220,80 @@ export class MediaRequestManager {
|
|
|
192
220
|
// eslint-disable-next-line class-methods-use-this
|
|
193
221
|
private getH264MaxMbps(mediaRequest: MediaRequest): number {
|
|
194
222
|
// fallback for maxFps (not needed for maxFs, since there is a fallback already in getDegradedClientRequests)
|
|
195
|
-
const maxFps = mediaRequest.codecInfo.maxFps ||
|
|
223
|
+
const maxFps = mediaRequest.codecInfo.maxFps || H264_CODEC_PARAMETERS.maxFps;
|
|
196
224
|
|
|
197
225
|
// divided by 100 since maxFps is 3000 (for 30 frames per seconds)
|
|
198
226
|
return (mediaRequest.codecInfo.maxFs * maxFps) / 100;
|
|
199
227
|
}
|
|
200
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Returns the AV1 encoding parameters for a media request
|
|
231
|
+
* @param mediaRequest - The media request to get the AV1 encoding parameters for
|
|
232
|
+
* @returns {AV1EncodingParams} The AV1 encoding parameters
|
|
233
|
+
*/
|
|
234
|
+
// eslint-disable-next-line class-methods-use-this
|
|
235
|
+
private getAv1EncodingParams(mediaRequest: MediaRequest): AV1EncodingParams {
|
|
236
|
+
const frameSize = mediaRequest.codecInfo.maxFs || H264_CODEC_PARAMETERS.maxFs;
|
|
237
|
+
const resolution = RESOLUTION_BUCKETS.find(([, maxFs]) => frameSize <= maxFs)?.[0] ?? '1080p';
|
|
238
|
+
|
|
239
|
+
return AV1_CODEC_PARAMETERS[resolution];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private buildH264CodecInfo(mr: MediaRequest): WcmeCodecInfo | undefined {
|
|
243
|
+
if (!mr.codecInfo) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const h264PayloadType = this.getIngressPayloadTypeCallback(
|
|
248
|
+
mr.receiveSlots[0].mediaType,
|
|
249
|
+
MediaCodecMimeType.H264
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
if (h264PayloadType === undefined) {
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return WcmeCodecInfo.fromH264(
|
|
257
|
+
h264PayloadType,
|
|
258
|
+
new H264Codec(
|
|
259
|
+
mr.codecInfo.maxFs,
|
|
260
|
+
mr.codecInfo.maxFps || H264_CODEC_PARAMETERS.maxFps,
|
|
261
|
+
this.getH264MaxMbps(mr),
|
|
262
|
+
mr.codecInfo.maxWidth,
|
|
263
|
+
mr.codecInfo.maxHeight
|
|
264
|
+
)
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private buildAv1CodecInfo(mr: MediaRequest): WcmeCodecInfo | undefined {
|
|
269
|
+
if (!this.enableAv1 || !mr.codecInfo) {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const av1PayloadType = this.getIngressPayloadTypeCallback(
|
|
274
|
+
mr.receiveSlots[0].mediaType,
|
|
275
|
+
MediaCodecMimeType.AV1
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
if (av1PayloadType === undefined) {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const av1EncodingParams = this.getAv1EncodingParams(mr);
|
|
283
|
+
|
|
284
|
+
return WcmeCodecInfo.fromAv1(
|
|
285
|
+
av1PayloadType,
|
|
286
|
+
new AV1Codec(
|
|
287
|
+
av1EncodingParams.levelIdx,
|
|
288
|
+
av1EncodingParams.tier,
|
|
289
|
+
mr.codecInfo.maxWidth || av1EncodingParams.maxWidth,
|
|
290
|
+
mr.codecInfo.maxHeight || av1EncodingParams.maxHeight,
|
|
291
|
+
av1EncodingParams.maxPicSize,
|
|
292
|
+
av1EncodingParams.maxDecodeRate
|
|
293
|
+
)
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
201
297
|
/** Modifies the passed in clientRequests and makes sure that in total they don't ask
|
|
202
298
|
* for more streams than there are available.
|
|
203
299
|
*
|
|
@@ -298,6 +394,12 @@ export class MediaRequestManager {
|
|
|
298
394
|
// map all the client media requests to wcme stream requests
|
|
299
395
|
Object.values(clientRequests).forEach((mr) => {
|
|
300
396
|
if (mr.receiveSlots.length > 0) {
|
|
397
|
+
const codecInfos: WcmeCodecInfo[] = mr.codecInfo
|
|
398
|
+
? [this.buildH264CodecInfo(mr), this.buildAv1CodecInfo(mr)].filter(
|
|
399
|
+
(info): info is WcmeCodecInfo => info !== undefined
|
|
400
|
+
)
|
|
401
|
+
: [];
|
|
402
|
+
|
|
301
403
|
streamRequests.push(
|
|
302
404
|
new StreamRequest(
|
|
303
405
|
mr.policyInfo.policy === 'active-speaker'
|
|
@@ -314,25 +416,14 @@ export class MediaRequestManager {
|
|
|
314
416
|
: new ReceiverSelectedInfo(mr.policyInfo.csi),
|
|
315
417
|
mr.receiveSlots.map((receiveSlot) => receiveSlot.wcmeReceiveSlot),
|
|
316
418
|
this.getMaxPayloadBitsPerSecond(mr),
|
|
317
|
-
|
|
318
|
-
WcmeCodecInfo.fromH264(
|
|
319
|
-
0x80,
|
|
320
|
-
new H264Codec(
|
|
321
|
-
mr.codecInfo.maxFs,
|
|
322
|
-
mr.codecInfo.maxFps || CODEC_DEFAULTS.h264.maxFps,
|
|
323
|
-
this.getH264MaxMbps(mr),
|
|
324
|
-
mr.codecInfo.maxWidth,
|
|
325
|
-
mr.codecInfo.maxHeight
|
|
326
|
-
)
|
|
327
|
-
),
|
|
328
|
-
]
|
|
419
|
+
codecInfos
|
|
329
420
|
)
|
|
330
421
|
);
|
|
331
422
|
}
|
|
332
423
|
});
|
|
333
424
|
|
|
334
425
|
this.sendMediaRequestsCallback(streamRequests);
|
|
335
|
-
LoggerProxy.logger.info(`multistream:sendRequests --> media requests sent
|
|
426
|
+
LoggerProxy.logger.info(`multistream:sendRequests --> media requests sent.`);
|
|
336
427
|
}
|
|
337
428
|
|
|
338
429
|
public addRequest(mediaRequest: MediaRequest, commit = true): MediaRequestId {
|
|
@@ -134,6 +134,24 @@ export class ReceiveSlot extends EventsScope {
|
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
136
|
);
|
|
137
|
+
|
|
138
|
+
this.mcReceiveSlot.on(WcmeReceiveSlotEvents.MediaStarted, () => {
|
|
139
|
+
LoggerProxy.logger.log(
|
|
140
|
+
`ReceiveSlot#setupEventListeners --> media started on receive slot ${this.id}, mediaType=${this.mediaType}`
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
this.mcReceiveSlot.on(WcmeReceiveSlotEvents.MediaStopped, () => {
|
|
145
|
+
LoggerProxy.logger.log(
|
|
146
|
+
`ReceiveSlot#setupEventListeners --> media stopped on receive slot ${this.id}, mediaType=${this.mediaType}`
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
this.mcReceiveSlot.on(WcmeReceiveSlotEvents.MediaEnded, () => {
|
|
151
|
+
LoggerProxy.logger.log(
|
|
152
|
+
`ReceiveSlot#setupEventListeners --> media ended on receive slot ${this.id}, mediaType=${this.mediaType}`
|
|
153
|
+
);
|
|
154
|
+
});
|
|
137
155
|
}
|
|
138
156
|
|
|
139
157
|
/** Tries to find the member id for this receive slot if it hasn't got one */
|
|
@@ -261,8 +261,7 @@ export default class RecordingController {
|
|
|
261
261
|
|
|
262
262
|
LoggerProxy.logger.log(`RecordingController:index#recordingControls --> ${record}`);
|
|
263
263
|
|
|
264
|
-
|
|
265
|
-
return this.request.request({
|
|
264
|
+
return this.request.locusDeltaRequest({
|
|
266
265
|
uri: `${this.locusUrl}/${CONTROLS}`,
|
|
267
266
|
body: {
|
|
268
267
|
record,
|