@webex/plugin-meetings 3.12.0-next.58 → 3.12.0-next.59

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.
Files changed (34) hide show
  1. package/dist/aiEnableRequest/index.js +1 -1
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/config.js +1 -0
  5. package/dist/config.js.map +1 -1
  6. package/dist/interpretation/index.js +1 -1
  7. package/dist/interpretation/siLanguage.js +1 -1
  8. package/dist/media/index.js +3 -1
  9. package/dist/media/index.js.map +1 -1
  10. package/dist/meeting/index.js +37 -10
  11. package/dist/meeting/index.js.map +1 -1
  12. package/dist/meetings/index.js +23 -0
  13. package/dist/meetings/index.js.map +1 -1
  14. package/dist/multistream/codec/constants.js +63 -0
  15. package/dist/multistream/codec/constants.js.map +1 -0
  16. package/dist/multistream/mediaRequestManager.js +62 -15
  17. package/dist/multistream/mediaRequestManager.js.map +1 -1
  18. package/dist/types/config.d.ts +1 -0
  19. package/dist/types/meeting/index.d.ts +9 -0
  20. package/dist/types/meetings/index.d.ts +10 -0
  21. package/dist/types/multistream/codec/constants.d.ts +7 -0
  22. package/dist/types/multistream/mediaRequestManager.d.ts +22 -5
  23. package/dist/webinar/index.js +1 -1
  24. package/package.json +1 -1
  25. package/src/config.ts +1 -0
  26. package/src/media/index.ts +3 -0
  27. package/src/meeting/index.ts +41 -2
  28. package/src/meetings/index.ts +21 -0
  29. package/src/multistream/codec/constants.ts +58 -0
  30. package/src/multistream/mediaRequestManager.ts +119 -28
  31. package/test/unit/spec/media/index.ts +31 -0
  32. package/test/unit/spec/meeting/index.js +154 -0
  33. package/test/unit/spec/meetings/index.js +27 -0
  34. package/test/unit/spec/multistream/mediaRequestManager.ts +501 -37
@@ -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 { expect } from 'chai';
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
- mediaRequestManager = new MediaRequestManager(sendMediaRequestsCallback, {
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
- maxPayloadBitsPerSecond: expectedRequest.maxPayloadBitsPerSecond,
158
- codecInfos: isCodecInfoDefined
159
- ? [
160
- sinon.match({
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
- maxPayloadBitsPerSecond: expectedRequest.maxPayloadBitsPerSecond,
179
- codecInfos: isCodecInfoDefined
180
- ? [
181
- sinon.match({
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: 0x80,
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: 0x80,
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: 0x80,
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
- expect(sourceUpdateHandler.args[1]).to.be.a('function');
417
- expect(maxFsHandlerCall.args[1]).to.be.a('function');
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
+ });