@webex/plugin-meetings 3.12.0-next.6 → 3.12.0-next.61
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 +6 -3
- 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 +38 -24
- 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 +10 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +716 -370
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/utils.js +22 -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/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/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 +289 -87
- package/dist/locus-info/index.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 +3 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +907 -535
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +19 -2
- package/dist/meeting/util.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 +2 -0
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/controls-options-manager/index.d.ts +10 -0
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +92 -16
- package/dist/types/hashTree/utils.d.ts +11 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/interceptors/locusRetry.d.ts +4 -4
- package/dist/types/locus-info/index.d.ts +46 -6
- 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 +2 -0
- package/dist/types/meeting/index.d.ts +87 -3
- package/dist/types/meeting/util.d.ts +8 -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 +361 -235
- 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 +5 -1
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +47 -24
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTreeParser.ts +429 -183
- package/src/hashTree/utils.ts +17 -0
- package/src/index.ts +5 -0
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/interpretation/index.ts +25 -8
- package/src/locus-info/controlsUtils.ts +3 -1
- package/src/locus-info/index.ts +291 -97
- 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 +4 -0
- package/src/meeting/index.ts +388 -33
- package/src/meeting/util.ts +20 -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 +162 -21
- 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 +140 -29
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +1508 -149
- package/test/unit/spec/hashTree/utils.ts +88 -1
- 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 +475 -81
- package/test/unit/spec/media/index.ts +31 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
- package/test/unit/spec/meeting/index.js +1131 -49
- package/test/unit/spec/meeting/muteState.js +3 -0
- package/test/unit/spec/meeting/utils.js +33 -0
- package/test/unit/spec/meetings/index.js +360 -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 +141 -16
|
@@ -6,8 +6,15 @@ import {assert} from '@webex/test-helper-chai';
|
|
|
6
6
|
import {getMaxFs, MAX_FS_VALUES} from '@webex/plugin-meetings/src/multistream/remoteMedia';
|
|
7
7
|
import FakeTimers from '@sinonjs/fake-timers';
|
|
8
8
|
import * as InternalMediaCoreModule from '@webex/internal-media-core';
|
|
9
|
-
import {
|
|
9
|
+
import {MediaType, MediaCodecMimeType} from '@webex/internal-media-core';
|
|
10
10
|
|
|
11
|
+
|
|
12
|
+
type ExpectedAv1 = {
|
|
13
|
+
payloadType: number;
|
|
14
|
+
levelIdx: number;
|
|
15
|
+
maxWidth: number;
|
|
16
|
+
maxHeight: number;
|
|
17
|
+
};
|
|
11
18
|
type ExpectedActiveSpeaker = {
|
|
12
19
|
policy: 'active-speaker';
|
|
13
20
|
maxPayloadBitsPerSecond?: number;
|
|
@@ -16,6 +23,7 @@ type ExpectedActiveSpeaker = {
|
|
|
16
23
|
maxFs?: number;
|
|
17
24
|
maxMbps?: number;
|
|
18
25
|
namedMediaGroups?:[{type: number, value: number}];
|
|
26
|
+
av1?: ExpectedAv1;
|
|
19
27
|
};
|
|
20
28
|
type ExpectedReceiverSelected = {
|
|
21
29
|
policy: 'receiver-selected';
|
|
@@ -24,6 +32,7 @@ type ExpectedReceiverSelected = {
|
|
|
24
32
|
receiveSlot: ReceiveSlot;
|
|
25
33
|
maxFs?: number;
|
|
26
34
|
maxMbps?: number;
|
|
35
|
+
av1?: ExpectedAv1;
|
|
27
36
|
};
|
|
28
37
|
type ExpectedRequest = ExpectedActiveSpeaker | ExpectedReceiverSelected;
|
|
29
38
|
|
|
@@ -49,15 +58,18 @@ describe('MediaRequestManager', () => {
|
|
|
49
58
|
const MAX_PAYLOADBITSPS_1080p = 4000000;
|
|
50
59
|
|
|
51
60
|
const NUM_SLOTS = 15;
|
|
61
|
+
const FAKE_H264_PAYLOAD_TYPE = 0x80;
|
|
52
62
|
|
|
53
63
|
let mediaRequestManager: MediaRequestManager;
|
|
54
64
|
let sendMediaRequestsCallback;
|
|
65
|
+
let getIngressPayloadTypeCallback;
|
|
55
66
|
let fakeWcmeSlots;
|
|
56
67
|
let fakeReceiveSlots;
|
|
57
68
|
|
|
58
69
|
beforeEach(() => {
|
|
59
70
|
sendMediaRequestsCallback = sinon.stub();
|
|
60
|
-
|
|
71
|
+
getIngressPayloadTypeCallback = sinon.stub().returns(FAKE_H264_PAYLOAD_TYPE);
|
|
72
|
+
mediaRequestManager = new MediaRequestManager(sendMediaRequestsCallback, getIngressPayloadTypeCallback, {
|
|
61
73
|
degradationPreferences,
|
|
62
74
|
kind: 'video',
|
|
63
75
|
trimRequestsToNumOfSources: false,
|
|
@@ -79,6 +91,7 @@ describe('MediaRequestManager', () => {
|
|
|
79
91
|
on: sinon.stub(),
|
|
80
92
|
off: sinon.stub(),
|
|
81
93
|
sourceState: 'live',
|
|
94
|
+
mediaType: MediaType.VideoMain,
|
|
82
95
|
wcmeReceiveSlot: fakeWcmeSlots[index],
|
|
83
96
|
} as unknown as ReceiveSlot)
|
|
84
97
|
);
|
|
@@ -140,6 +153,36 @@ describe('MediaRequestManager', () => {
|
|
|
140
153
|
preferLiveVideo = true,
|
|
141
154
|
} = {}
|
|
142
155
|
) => {
|
|
156
|
+
const buildCodecInfos = (expectedRequest: ExpectedRequest) => {
|
|
157
|
+
if (!isCodecInfoDefined) return [];
|
|
158
|
+
|
|
159
|
+
const h264Fields: Record<string, unknown> = {};
|
|
160
|
+
if (expectedRequest.maxMbps !== undefined) h264Fields.maxMbps = expectedRequest.maxMbps;
|
|
161
|
+
if (expectedRequest.maxFs !== undefined) h264Fields.maxFs = expectedRequest.maxFs;
|
|
162
|
+
|
|
163
|
+
const infos = [
|
|
164
|
+
sinon.match({
|
|
165
|
+
payloadType: FAKE_H264_PAYLOAD_TYPE,
|
|
166
|
+
...(Object.keys(h264Fields).length > 0 ? {h264: sinon.match(h264Fields)} : {}),
|
|
167
|
+
}),
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
if (expectedRequest.av1) {
|
|
171
|
+
infos.push(
|
|
172
|
+
sinon.match({
|
|
173
|
+
payloadType: expectedRequest.av1.payloadType,
|
|
174
|
+
av1: sinon.match({
|
|
175
|
+
levelIdx: expectedRequest.av1.levelIdx,
|
|
176
|
+
maxWidth: expectedRequest.av1.maxWidth,
|
|
177
|
+
maxHeight: expectedRequest.av1.maxHeight,
|
|
178
|
+
}),
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return infos;
|
|
184
|
+
};
|
|
185
|
+
|
|
143
186
|
assert.calledOnce(sendMediaRequestsCallback);
|
|
144
187
|
assert.calledWith(
|
|
145
188
|
sendMediaRequestsCallback,
|
|
@@ -154,18 +197,10 @@ describe('MediaRequestManager', () => {
|
|
|
154
197
|
preferLiveVideo,
|
|
155
198
|
}),
|
|
156
199
|
receiveSlots: expectedRequest.receiveSlots,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
payloadType: 0x80,
|
|
162
|
-
h264: sinon.match({
|
|
163
|
-
maxMbps: expectedRequest.maxMbps,
|
|
164
|
-
maxFs: expectedRequest.maxFs,
|
|
165
|
-
}),
|
|
166
|
-
}),
|
|
167
|
-
]
|
|
168
|
-
: [],
|
|
200
|
+
...(expectedRequest.maxPayloadBitsPerSecond !== undefined
|
|
201
|
+
? {maxPayloadBitsPerSecond: expectedRequest.maxPayloadBitsPerSecond}
|
|
202
|
+
: {}),
|
|
203
|
+
codecInfos: buildCodecInfos(expectedRequest),
|
|
169
204
|
});
|
|
170
205
|
}
|
|
171
206
|
if (expectedRequest.policy === 'receiver-selected') {
|
|
@@ -175,18 +210,10 @@ describe('MediaRequestManager', () => {
|
|
|
175
210
|
csi: expectedRequest.csi,
|
|
176
211
|
}),
|
|
177
212
|
receiveSlots: [expectedRequest.receiveSlot],
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
payloadType: 0x80,
|
|
183
|
-
h264: sinon.match({
|
|
184
|
-
maxMbps: expectedRequest.maxMbps,
|
|
185
|
-
maxFs: expectedRequest.maxFs,
|
|
186
|
-
}),
|
|
187
|
-
}),
|
|
188
|
-
]
|
|
189
|
-
: [],
|
|
213
|
+
...(expectedRequest.maxPayloadBitsPerSecond !== undefined
|
|
214
|
+
? {maxPayloadBitsPerSecond: expectedRequest.maxPayloadBitsPerSecond}
|
|
215
|
+
: {}),
|
|
216
|
+
codecInfos: buildCodecInfos(expectedRequest),
|
|
190
217
|
});
|
|
191
218
|
}
|
|
192
219
|
|
|
@@ -279,7 +306,7 @@ describe('MediaRequestManager', () => {
|
|
|
279
306
|
maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_360p,
|
|
280
307
|
codecInfos: [
|
|
281
308
|
sinon.match({
|
|
282
|
-
payloadType:
|
|
309
|
+
payloadType: FAKE_H264_PAYLOAD_TYPE,
|
|
283
310
|
h264: sinon.match({
|
|
284
311
|
maxFs: MAX_FS_360p,
|
|
285
312
|
maxFps: MAX_FPS,
|
|
@@ -297,7 +324,7 @@ describe('MediaRequestManager', () => {
|
|
|
297
324
|
maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_720p,
|
|
298
325
|
codecInfos: [
|
|
299
326
|
sinon.match({
|
|
300
|
-
payloadType:
|
|
327
|
+
payloadType: FAKE_H264_PAYLOAD_TYPE,
|
|
301
328
|
h264: sinon.match({
|
|
302
329
|
maxFs: MAX_FS_720p,
|
|
303
330
|
maxFps: MAX_FPS,
|
|
@@ -315,7 +342,7 @@ describe('MediaRequestManager', () => {
|
|
|
315
342
|
maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_1080p,
|
|
316
343
|
codecInfos: [
|
|
317
344
|
sinon.match({
|
|
318
|
-
payloadType:
|
|
345
|
+
payloadType: FAKE_H264_PAYLOAD_TYPE,
|
|
319
346
|
h264: sinon.match({
|
|
320
347
|
maxFs: MAX_FS_1080p,
|
|
321
348
|
maxFps: MAX_FPS,
|
|
@@ -413,8 +440,8 @@ describe('MediaRequestManager', () => {
|
|
|
413
440
|
const maxFsEventName = maxFsHandlerCall.args[0];
|
|
414
441
|
const sourceUpdateEventName = sourceUpdateHandler.args[0];
|
|
415
442
|
|
|
416
|
-
|
|
417
|
-
|
|
443
|
+
assert.isFunction(sourceUpdateHandler.args[1]);
|
|
444
|
+
assert.isFunction(maxFsHandlerCall.args[1]);
|
|
418
445
|
|
|
419
446
|
assert.equal(maxFsEventName, 'maxFsUpdate')
|
|
420
447
|
assert.equal(sourceUpdateEventName, 'sourceUpdate')
|
|
@@ -919,7 +946,7 @@ describe('MediaRequestManager', () => {
|
|
|
919
946
|
});
|
|
920
947
|
|
|
921
948
|
it('returns the default maxPayloadBitsPerSecond if kind is "audio"', () => {
|
|
922
|
-
const mediaRequestManagerAudio = new MediaRequestManager(sendMediaRequestsCallback, {
|
|
949
|
+
const mediaRequestManagerAudio = new MediaRequestManager(sendMediaRequestsCallback, getIngressPayloadTypeCallback, {
|
|
923
950
|
degradationPreferences,
|
|
924
951
|
kind: 'audio',
|
|
925
952
|
trimRequestsToNumOfSources: false,
|
|
@@ -1037,7 +1064,7 @@ describe('MediaRequestManager', () => {
|
|
|
1037
1064
|
|
|
1038
1065
|
describe('trimming of requested receive slots', () => {
|
|
1039
1066
|
beforeEach(() => {
|
|
1040
|
-
mediaRequestManager = new MediaRequestManager(sendMediaRequestsCallback, {
|
|
1067
|
+
mediaRequestManager = new MediaRequestManager(sendMediaRequestsCallback, getIngressPayloadTypeCallback, {
|
|
1041
1068
|
degradationPreferences,
|
|
1042
1069
|
kind: 'video',
|
|
1043
1070
|
trimRequestsToNumOfSources: true,
|
|
@@ -1361,8 +1388,445 @@ describe('MediaRequestManager', () => {
|
|
|
1361
1388
|
assert.throws(() => mediaRequestManager.commit(), 'a mix of active-speaker groups with different values for preferLiveVideo is not supported');
|
|
1362
1389
|
})
|
|
1363
1390
|
})
|
|
1364
|
-
});
|
|
1365
|
-
function assertEqual(arg0: any, arg1: string) {
|
|
1366
|
-
throw new Error('Function not implemented.');
|
|
1367
|
-
}
|
|
1368
1391
|
|
|
1392
|
+
describe('getIngressPayloadTypeCallback', () => {
|
|
1393
|
+
beforeEach(() => {
|
|
1394
|
+
sendMediaRequestsCallback.resetHistory();
|
|
1395
|
+
getIngressPayloadTypeCallback.resetHistory();
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1398
|
+
it('is called with the correct mediaType and codec, and uses the returned payload type', () => {
|
|
1399
|
+
const customPayloadType = 0x63;
|
|
1400
|
+
getIngressPayloadTypeCallback.returns(customPayloadType);
|
|
1401
|
+
|
|
1402
|
+
addReceiverSelectedRequest(100, fakeReceiveSlots[0], MAX_FS_720p, true);
|
|
1403
|
+
|
|
1404
|
+
assert.calledWith(getIngressPayloadTypeCallback, MediaType.VideoMain, MediaCodecMimeType.H264);
|
|
1405
|
+
assert.calledWith(sendMediaRequestsCallback, [
|
|
1406
|
+
sinon.match({
|
|
1407
|
+
codecInfos: [
|
|
1408
|
+
sinon.match({
|
|
1409
|
+
payloadType: customPayloadType,
|
|
1410
|
+
h264: sinon.match({ maxFs: MAX_FS_720p }),
|
|
1411
|
+
}),
|
|
1412
|
+
],
|
|
1413
|
+
}),
|
|
1414
|
+
]);
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1417
|
+
it('sends empty codecInfos when the callback returns undefined', () => {
|
|
1418
|
+
getIngressPayloadTypeCallback.returns(undefined);
|
|
1419
|
+
|
|
1420
|
+
addReceiverSelectedRequest(100, fakeReceiveSlots[0], MAX_FS_720p, true);
|
|
1421
|
+
|
|
1422
|
+
assert.calledWith(sendMediaRequestsCallback, [
|
|
1423
|
+
sinon.match({ codecInfos: [] }),
|
|
1424
|
+
]);
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
it('does not invoke the callback for AV1 when enableAv1 is false (default)', () => {
|
|
1428
|
+
addReceiverSelectedRequest(100, fakeReceiveSlots[0], MAX_FS_720p, true);
|
|
1429
|
+
|
|
1430
|
+
const av1Calls = getIngressPayloadTypeCallback.getCalls().filter(
|
|
1431
|
+
(call) => call.args[1] === MediaCodecMimeType.AV1
|
|
1432
|
+
);
|
|
1433
|
+
assert.lengthOf(av1Calls, 0);
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
it('includes both H264 and AV1 codec infos when enableAv1 is true and both payload types are available', () => {
|
|
1437
|
+
const FAKE_AV1_PAYLOAD_TYPE = 0x90;
|
|
1438
|
+
getIngressPayloadTypeCallback.callsFake((mediaType, codecMimeType) => {
|
|
1439
|
+
if (codecMimeType === MediaCodecMimeType.H264) return FAKE_H264_PAYLOAD_TYPE;
|
|
1440
|
+
if (codecMimeType === MediaCodecMimeType.AV1) return FAKE_AV1_PAYLOAD_TYPE;
|
|
1441
|
+
return undefined;
|
|
1442
|
+
});
|
|
1443
|
+
|
|
1444
|
+
mediaRequestManager = new MediaRequestManager(sendMediaRequestsCallback, getIngressPayloadTypeCallback, {
|
|
1445
|
+
degradationPreferences,
|
|
1446
|
+
kind: 'video',
|
|
1447
|
+
trimRequestsToNumOfSources: false,
|
|
1448
|
+
enableAv1: true,
|
|
1449
|
+
});
|
|
1450
|
+
sendMediaRequestsCallback.resetHistory();
|
|
1451
|
+
|
|
1452
|
+
addReceiverSelectedRequest(100, fakeReceiveSlots[0], MAX_FS_720p, true);
|
|
1453
|
+
|
|
1454
|
+
assert.calledWith(getIngressPayloadTypeCallback, MediaType.VideoMain, MediaCodecMimeType.H264);
|
|
1455
|
+
assert.calledWith(getIngressPayloadTypeCallback, MediaType.VideoMain, MediaCodecMimeType.AV1);
|
|
1456
|
+
assert.calledWith(sendMediaRequestsCallback, [
|
|
1457
|
+
sinon.match({
|
|
1458
|
+
codecInfos: [
|
|
1459
|
+
sinon.match({ payloadType: FAKE_H264_PAYLOAD_TYPE }),
|
|
1460
|
+
sinon.match({ payloadType: FAKE_AV1_PAYLOAD_TYPE }),
|
|
1461
|
+
],
|
|
1462
|
+
}),
|
|
1463
|
+
]);
|
|
1464
|
+
});
|
|
1465
|
+
|
|
1466
|
+
it('sends only H264 codec info when enableAv1 is true but AV1 payload type is undefined', () => {
|
|
1467
|
+
getIngressPayloadTypeCallback.callsFake((mediaType, codecMimeType) => {
|
|
1468
|
+
if (codecMimeType === MediaCodecMimeType.H264) return FAKE_H264_PAYLOAD_TYPE;
|
|
1469
|
+
return undefined;
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
mediaRequestManager = new MediaRequestManager(sendMediaRequestsCallback, getIngressPayloadTypeCallback, {
|
|
1473
|
+
degradationPreferences,
|
|
1474
|
+
kind: 'video',
|
|
1475
|
+
trimRequestsToNumOfSources: false,
|
|
1476
|
+
enableAv1: true,
|
|
1477
|
+
});
|
|
1478
|
+
sendMediaRequestsCallback.resetHistory();
|
|
1479
|
+
|
|
1480
|
+
addReceiverSelectedRequest(100, fakeReceiveSlots[0], MAX_FS_720p, true);
|
|
1481
|
+
|
|
1482
|
+
assert.calledWith(sendMediaRequestsCallback, [
|
|
1483
|
+
sinon.match({
|
|
1484
|
+
codecInfos: sinon.match((codecInfos) =>
|
|
1485
|
+
codecInfos.length === 1 &&
|
|
1486
|
+
codecInfos[0].payloadType === FAKE_H264_PAYLOAD_TYPE
|
|
1487
|
+
),
|
|
1488
|
+
}),
|
|
1489
|
+
]);
|
|
1490
|
+
});
|
|
1491
|
+
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
describe('AV1 resolution mapping', () => {
|
|
1495
|
+
const FAKE_AV1_PAYLOAD_TYPE = 0x90;
|
|
1496
|
+
|
|
1497
|
+
beforeEach(() => {
|
|
1498
|
+
getIngressPayloadTypeCallback.callsFake((mediaType, codecMimeType) => {
|
|
1499
|
+
if (codecMimeType === MediaCodecMimeType.H264) return FAKE_H264_PAYLOAD_TYPE;
|
|
1500
|
+
if (codecMimeType === MediaCodecMimeType.AV1) return FAKE_AV1_PAYLOAD_TYPE;
|
|
1501
|
+
return undefined;
|
|
1502
|
+
});
|
|
1503
|
+
|
|
1504
|
+
mediaRequestManager = new MediaRequestManager(sendMediaRequestsCallback, getIngressPayloadTypeCallback, {
|
|
1505
|
+
degradationPreferences,
|
|
1506
|
+
kind: 'video',
|
|
1507
|
+
trimRequestsToNumOfSources: false,
|
|
1508
|
+
enableAv1: true,
|
|
1509
|
+
});
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
const resolutionCases = [
|
|
1513
|
+
{maxFs: MAX_FS_VALUES['90p'], expectedRes: '90p', levelIdx: 0, maxWidth: 160, maxHeight: 90},
|
|
1514
|
+
{maxFs: MAX_FS_VALUES['180p'], expectedRes: '180p', levelIdx: 0, maxWidth: 320, maxHeight: 180},
|
|
1515
|
+
{maxFs: MAX_FS_VALUES['360p'], expectedRes: '360p', levelIdx: 1, maxWidth: 640, maxHeight: 360},
|
|
1516
|
+
{maxFs: MAX_FS_VALUES['540p'], expectedRes: '540p', levelIdx: 4, maxWidth: 960, maxHeight: 540},
|
|
1517
|
+
{maxFs: MAX_FS_VALUES['720p'], expectedRes: '720p', levelIdx: 5, maxWidth: 1280, maxHeight: 720},
|
|
1518
|
+
{maxFs: MAX_FS_VALUES['1080p'], expectedRes: '1080p', levelIdx: 8, maxWidth: 1920, maxHeight: 1080},
|
|
1519
|
+
];
|
|
1520
|
+
|
|
1521
|
+
resolutionCases.forEach(({maxFs, expectedRes, levelIdx, maxWidth, maxHeight}) => {
|
|
1522
|
+
it(`maps maxFs=${maxFs} to ${expectedRes} AV1 parameters (levelIdx=${levelIdx}, ${maxWidth}x${maxHeight})`, () => {
|
|
1523
|
+
addReceiverSelectedRequest(100, fakeReceiveSlots[0], maxFs, true);
|
|
1524
|
+
|
|
1525
|
+
checkMediaRequestsSent([{
|
|
1526
|
+
policy: 'receiver-selected',
|
|
1527
|
+
csi: 100,
|
|
1528
|
+
receiveSlot: fakeWcmeSlots[0],
|
|
1529
|
+
maxFs,
|
|
1530
|
+
av1: {payloadType: FAKE_AV1_PAYLOAD_TYPE, levelIdx, maxWidth, maxHeight},
|
|
1531
|
+
}]);
|
|
1532
|
+
});
|
|
1533
|
+
});
|
|
1534
|
+
|
|
1535
|
+
it('maps maxFs values between breakpoints to the correct resolution bucket', () => {
|
|
1536
|
+
const betweenFs = MAX_FS_VALUES['360p'] + 1;
|
|
1537
|
+
|
|
1538
|
+
addReceiverSelectedRequest(100, fakeReceiveSlots[0], betweenFs, true);
|
|
1539
|
+
|
|
1540
|
+
checkMediaRequestsSent([{
|
|
1541
|
+
policy: 'receiver-selected',
|
|
1542
|
+
csi: 100,
|
|
1543
|
+
receiveSlot: fakeWcmeSlots[0],
|
|
1544
|
+
maxFs: betweenFs,
|
|
1545
|
+
av1: {payloadType: FAKE_AV1_PAYLOAD_TYPE, levelIdx: 4, maxWidth: 960, maxHeight: 540},
|
|
1546
|
+
}]);
|
|
1547
|
+
});
|
|
1548
|
+
|
|
1549
|
+
it('maps maxFs exceeding 1080p to 1080p AV1 parameters', () => {
|
|
1550
|
+
const largeFs = MAX_FS_VALUES['1080p'] + 1000;
|
|
1551
|
+
|
|
1552
|
+
addReceiverSelectedRequest(100, fakeReceiveSlots[0], largeFs, true);
|
|
1553
|
+
|
|
1554
|
+
checkMediaRequestsSent([{
|
|
1555
|
+
policy: 'receiver-selected',
|
|
1556
|
+
csi: 100,
|
|
1557
|
+
receiveSlot: fakeWcmeSlots[0],
|
|
1558
|
+
av1: {payloadType: FAKE_AV1_PAYLOAD_TYPE, levelIdx: 8, maxWidth: 1920, maxHeight: 1080},
|
|
1559
|
+
}]);
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
it('includes AV1 codec info for active-speaker requests', () => {
|
|
1563
|
+
addActiveSpeakerRequest(
|
|
1564
|
+
255,
|
|
1565
|
+
[fakeReceiveSlots[0], fakeReceiveSlots[1], fakeReceiveSlots[2]],
|
|
1566
|
+
MAX_FS_720p,
|
|
1567
|
+
true
|
|
1568
|
+
);
|
|
1569
|
+
|
|
1570
|
+
checkMediaRequestsSent([{
|
|
1571
|
+
policy: 'active-speaker',
|
|
1572
|
+
priority: 255,
|
|
1573
|
+
receiveSlots: [fakeWcmeSlots[0], fakeWcmeSlots[1], fakeWcmeSlots[2]],
|
|
1574
|
+
maxFs: MAX_FS_720p,
|
|
1575
|
+
av1: {payloadType: FAKE_AV1_PAYLOAD_TYPE, levelIdx: 5, maxWidth: 1280, maxHeight: 720},
|
|
1576
|
+
}]);
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
it('uses codecInfo maxWidth and maxHeight for AV1 when provided', () => {
|
|
1580
|
+
const customWidth = 1000;
|
|
1581
|
+
const customHeight = 700;
|
|
1582
|
+
|
|
1583
|
+
mediaRequestManager.addRequest(
|
|
1584
|
+
{
|
|
1585
|
+
policyInfo: {
|
|
1586
|
+
policy: 'receiver-selected',
|
|
1587
|
+
csi: 77,
|
|
1588
|
+
},
|
|
1589
|
+
receiveSlots: [fakeReceiveSlots[0]],
|
|
1590
|
+
codecInfo: {
|
|
1591
|
+
codec: 'h264',
|
|
1592
|
+
maxFs: MAX_FS_720p,
|
|
1593
|
+
maxWidth: customWidth,
|
|
1594
|
+
maxHeight: customHeight,
|
|
1595
|
+
},
|
|
1596
|
+
},
|
|
1597
|
+
true
|
|
1598
|
+
);
|
|
1599
|
+
|
|
1600
|
+
assert.calledWith(
|
|
1601
|
+
sendMediaRequestsCallback,
|
|
1602
|
+
[
|
|
1603
|
+
sinon.match({
|
|
1604
|
+
codecInfos: [
|
|
1605
|
+
sinon.match({payloadType: FAKE_H264_PAYLOAD_TYPE}),
|
|
1606
|
+
sinon.match({
|
|
1607
|
+
payloadType: FAKE_AV1_PAYLOAD_TYPE,
|
|
1608
|
+
av1: sinon.match({
|
|
1609
|
+
maxWidth: customWidth,
|
|
1610
|
+
maxHeight: customHeight,
|
|
1611
|
+
}),
|
|
1612
|
+
}),
|
|
1613
|
+
],
|
|
1614
|
+
}),
|
|
1615
|
+
]
|
|
1616
|
+
);
|
|
1617
|
+
});
|
|
1618
|
+
|
|
1619
|
+
it('falls back to AV1 bucket-default maxWidth and maxHeight when codecInfo does not set them', () => {
|
|
1620
|
+
addReceiverSelectedRequest(100, fakeReceiveSlots[0], MAX_FS_720p, true);
|
|
1621
|
+
|
|
1622
|
+
assert.calledWith(
|
|
1623
|
+
sendMediaRequestsCallback,
|
|
1624
|
+
[
|
|
1625
|
+
sinon.match({
|
|
1626
|
+
codecInfos: [
|
|
1627
|
+
sinon.match({payloadType: FAKE_H264_PAYLOAD_TYPE}),
|
|
1628
|
+
sinon.match({
|
|
1629
|
+
payloadType: FAKE_AV1_PAYLOAD_TYPE,
|
|
1630
|
+
av1: sinon.match({
|
|
1631
|
+
maxWidth: 1280,
|
|
1632
|
+
maxHeight: 720,
|
|
1633
|
+
}),
|
|
1634
|
+
}),
|
|
1635
|
+
],
|
|
1636
|
+
}),
|
|
1637
|
+
]
|
|
1638
|
+
);
|
|
1639
|
+
});
|
|
1640
|
+
});
|
|
1641
|
+
|
|
1642
|
+
describe('degradation with AV1 enabled', () => {
|
|
1643
|
+
const FAKE_AV1_PAYLOAD_TYPE = 0x90;
|
|
1644
|
+
|
|
1645
|
+
beforeEach(() => {
|
|
1646
|
+
getIngressPayloadTypeCallback.callsFake((mediaType, codecMimeType) => {
|
|
1647
|
+
if (codecMimeType === MediaCodecMimeType.H264) return FAKE_H264_PAYLOAD_TYPE;
|
|
1648
|
+
if (codecMimeType === MediaCodecMimeType.AV1) return FAKE_AV1_PAYLOAD_TYPE;
|
|
1649
|
+
return undefined;
|
|
1650
|
+
});
|
|
1651
|
+
|
|
1652
|
+
mediaRequestManager = new MediaRequestManager(sendMediaRequestsCallback, getIngressPayloadTypeCallback, {
|
|
1653
|
+
degradationPreferences,
|
|
1654
|
+
kind: 'video',
|
|
1655
|
+
trimRequestsToNumOfSources: false,
|
|
1656
|
+
enableAv1: true,
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
sendMediaRequestsCallback.resetHistory();
|
|
1660
|
+
});
|
|
1661
|
+
|
|
1662
|
+
it('degrades AV1 codec info along with H264 when request exceeds max macroblocks limit', () => {
|
|
1663
|
+
mediaRequestManager.setDegradationPreferences({maxMacroblocksLimit: 32400});
|
|
1664
|
+
sendMediaRequestsCallback.resetHistory();
|
|
1665
|
+
|
|
1666
|
+
addActiveSpeakerRequest(255, fakeReceiveSlots.slice(0, 3), getMaxFs('large'), false);
|
|
1667
|
+
addReceiverSelectedRequest(123, fakeReceiveSlots[3], getMaxFs('large'), true);
|
|
1668
|
+
|
|
1669
|
+
assert.calledOnce(sendMediaRequestsCallback);
|
|
1670
|
+
assert.calledWith(sendMediaRequestsCallback, [
|
|
1671
|
+
sinon.match({
|
|
1672
|
+
policy: 'active-speaker',
|
|
1673
|
+
policySpecificInfo: sinon.match({ priority: 255 }),
|
|
1674
|
+
receiveSlots: fakeWcmeSlots.slice(0, 3),
|
|
1675
|
+
maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_720p,
|
|
1676
|
+
codecInfos: [
|
|
1677
|
+
sinon.match({
|
|
1678
|
+
payloadType: FAKE_H264_PAYLOAD_TYPE,
|
|
1679
|
+
h264: sinon.match({ maxFs: getMaxFs('medium'), maxMbps: MAX_MBPS_720p }),
|
|
1680
|
+
}),
|
|
1681
|
+
sinon.match({
|
|
1682
|
+
payloadType: FAKE_AV1_PAYLOAD_TYPE,
|
|
1683
|
+
av1: sinon.match({
|
|
1684
|
+
levelIdx: 5,
|
|
1685
|
+
maxWidth: 1280,
|
|
1686
|
+
maxHeight: 720,
|
|
1687
|
+
}),
|
|
1688
|
+
}),
|
|
1689
|
+
],
|
|
1690
|
+
}),
|
|
1691
|
+
sinon.match({
|
|
1692
|
+
policy: 'receiver-selected',
|
|
1693
|
+
policySpecificInfo: sinon.match({ csi: 123 }),
|
|
1694
|
+
receiveSlots: [fakeWcmeSlots[3]],
|
|
1695
|
+
maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_720p,
|
|
1696
|
+
codecInfos: [
|
|
1697
|
+
sinon.match({
|
|
1698
|
+
payloadType: FAKE_H264_PAYLOAD_TYPE,
|
|
1699
|
+
h264: sinon.match({ maxFs: getMaxFs('medium'), maxMbps: MAX_MBPS_720p }),
|
|
1700
|
+
}),
|
|
1701
|
+
sinon.match({
|
|
1702
|
+
payloadType: FAKE_AV1_PAYLOAD_TYPE,
|
|
1703
|
+
av1: sinon.match({
|
|
1704
|
+
levelIdx: 5,
|
|
1705
|
+
maxWidth: 1280,
|
|
1706
|
+
maxHeight: 720,
|
|
1707
|
+
}),
|
|
1708
|
+
}),
|
|
1709
|
+
],
|
|
1710
|
+
}),
|
|
1711
|
+
]);
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1714
|
+
it('degrades AV1 codec info through multiple steps when many streams are requested', () => {
|
|
1715
|
+
mediaRequestManager.setDegradationPreferences({maxMacroblocksLimit: 32400});
|
|
1716
|
+
sendMediaRequestsCallback.resetHistory();
|
|
1717
|
+
|
|
1718
|
+
addActiveSpeakerRequest(255, fakeReceiveSlots.slice(0, 10), getMaxFs('large'), true);
|
|
1719
|
+
|
|
1720
|
+
assert.calledOnce(sendMediaRequestsCallback);
|
|
1721
|
+
assert.calledWith(sendMediaRequestsCallback, [
|
|
1722
|
+
sinon.match({
|
|
1723
|
+
policy: 'active-speaker',
|
|
1724
|
+
policySpecificInfo: sinon.match({ priority: 255 }),
|
|
1725
|
+
receiveSlots: fakeWcmeSlots.slice(0, 10),
|
|
1726
|
+
maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_540p,
|
|
1727
|
+
codecInfos: [
|
|
1728
|
+
sinon.match({
|
|
1729
|
+
payloadType: FAKE_H264_PAYLOAD_TYPE,
|
|
1730
|
+
h264: sinon.match({ maxFs: MAX_FS_VALUES['540p'], maxMbps: MAX_MBPS_540p }),
|
|
1731
|
+
}),
|
|
1732
|
+
sinon.match({
|
|
1733
|
+
payloadType: FAKE_AV1_PAYLOAD_TYPE,
|
|
1734
|
+
av1: sinon.match({
|
|
1735
|
+
levelIdx: 4,
|
|
1736
|
+
maxWidth: 960,
|
|
1737
|
+
maxHeight: 540,
|
|
1738
|
+
}),
|
|
1739
|
+
}),
|
|
1740
|
+
],
|
|
1741
|
+
}),
|
|
1742
|
+
]);
|
|
1743
|
+
});
|
|
1744
|
+
|
|
1745
|
+
it('degrades only the largest streams AV1 info when mixed resolutions exceed the limit', () => {
|
|
1746
|
+
mediaRequestManager.setDegradationPreferences({maxMacroblocksLimit: 32400});
|
|
1747
|
+
sendMediaRequestsCallback.resetHistory();
|
|
1748
|
+
|
|
1749
|
+
addActiveSpeakerRequest(255, fakeReceiveSlots.slice(0, 5), getMaxFs('large'), false);
|
|
1750
|
+
addActiveSpeakerRequest(254, fakeReceiveSlots.slice(5, 10), getMaxFs('small'), true);
|
|
1751
|
+
|
|
1752
|
+
assert.calledOnce(sendMediaRequestsCallback);
|
|
1753
|
+
assert.calledWith(sendMediaRequestsCallback, [
|
|
1754
|
+
sinon.match({
|
|
1755
|
+
policy: 'active-speaker',
|
|
1756
|
+
policySpecificInfo: sinon.match({ priority: 255 }),
|
|
1757
|
+
receiveSlots: fakeWcmeSlots.slice(0, 5),
|
|
1758
|
+
maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_720p,
|
|
1759
|
+
codecInfos: [
|
|
1760
|
+
sinon.match({
|
|
1761
|
+
payloadType: FAKE_H264_PAYLOAD_TYPE,
|
|
1762
|
+
h264: sinon.match({ maxFs: getMaxFs('medium'), maxMbps: MAX_MBPS_720p }),
|
|
1763
|
+
}),
|
|
1764
|
+
sinon.match({
|
|
1765
|
+
payloadType: FAKE_AV1_PAYLOAD_TYPE,
|
|
1766
|
+
av1: sinon.match({
|
|
1767
|
+
levelIdx: 5,
|
|
1768
|
+
maxWidth: 1280,
|
|
1769
|
+
maxHeight: 720,
|
|
1770
|
+
}),
|
|
1771
|
+
}),
|
|
1772
|
+
],
|
|
1773
|
+
}),
|
|
1774
|
+
sinon.match({
|
|
1775
|
+
policy: 'active-speaker',
|
|
1776
|
+
policySpecificInfo: sinon.match({ priority: 254 }),
|
|
1777
|
+
receiveSlots: fakeWcmeSlots.slice(5, 10),
|
|
1778
|
+
maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_360p,
|
|
1779
|
+
codecInfos: [
|
|
1780
|
+
sinon.match({
|
|
1781
|
+
payloadType: FAKE_H264_PAYLOAD_TYPE,
|
|
1782
|
+
h264: sinon.match({ maxFs: getMaxFs('small'), maxMbps: MAX_MBPS_360p }),
|
|
1783
|
+
}),
|
|
1784
|
+
sinon.match({
|
|
1785
|
+
payloadType: FAKE_AV1_PAYLOAD_TYPE,
|
|
1786
|
+
av1: sinon.match({
|
|
1787
|
+
levelIdx: 1,
|
|
1788
|
+
maxWidth: 640,
|
|
1789
|
+
maxHeight: 360,
|
|
1790
|
+
}),
|
|
1791
|
+
}),
|
|
1792
|
+
],
|
|
1793
|
+
}),
|
|
1794
|
+
]);
|
|
1795
|
+
});
|
|
1796
|
+
|
|
1797
|
+
it('does not degrade AV1 codec info if receive slot sources are not live', () => {
|
|
1798
|
+
fakeReceiveSlots.forEach((slot) => {
|
|
1799
|
+
slot.sourceState = 'no source';
|
|
1800
|
+
});
|
|
1801
|
+
|
|
1802
|
+
mediaRequestManager.setDegradationPreferences({maxMacroblocksLimit: 32400});
|
|
1803
|
+
sendMediaRequestsCallback.resetHistory();
|
|
1804
|
+
|
|
1805
|
+
addActiveSpeakerRequest(255, fakeReceiveSlots.slice(0, 4), getMaxFs('large'), true);
|
|
1806
|
+
|
|
1807
|
+
assert.calledOnce(sendMediaRequestsCallback);
|
|
1808
|
+
assert.calledWith(sendMediaRequestsCallback, [
|
|
1809
|
+
sinon.match({
|
|
1810
|
+
policy: 'active-speaker',
|
|
1811
|
+
policySpecificInfo: sinon.match({ priority: 255 }),
|
|
1812
|
+
receiveSlots: fakeWcmeSlots.slice(0, 4),
|
|
1813
|
+
maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_1080p,
|
|
1814
|
+
codecInfos: [
|
|
1815
|
+
sinon.match({
|
|
1816
|
+
payloadType: FAKE_H264_PAYLOAD_TYPE,
|
|
1817
|
+
h264: sinon.match({ maxFs: getMaxFs('large'), maxMbps: MAX_MBPS_1080p }),
|
|
1818
|
+
}),
|
|
1819
|
+
sinon.match({
|
|
1820
|
+
payloadType: FAKE_AV1_PAYLOAD_TYPE,
|
|
1821
|
+
av1: sinon.match({
|
|
1822
|
+
levelIdx: 8,
|
|
1823
|
+
maxWidth: 1920,
|
|
1824
|
+
maxHeight: 1080,
|
|
1825
|
+
}),
|
|
1826
|
+
}),
|
|
1827
|
+
],
|
|
1828
|
+
}),
|
|
1829
|
+
]);
|
|
1830
|
+
});
|
|
1831
|
+
});
|
|
1832
|
+
});
|