@webex/plugin-meetings 3.12.0-next.57 → 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 (37) hide show
  1. package/dist/aiEnableRequest/index.js +1 -1
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +24 -1
  4. package/dist/breakouts/index.js.map +1 -1
  5. package/dist/config.js +1 -0
  6. package/dist/config.js.map +1 -1
  7. package/dist/interpretation/index.js +1 -1
  8. package/dist/interpretation/siLanguage.js +1 -1
  9. package/dist/media/index.js +3 -1
  10. package/dist/media/index.js.map +1 -1
  11. package/dist/meeting/index.js +37 -10
  12. package/dist/meeting/index.js.map +1 -1
  13. package/dist/meetings/index.js +23 -0
  14. package/dist/meetings/index.js.map +1 -1
  15. package/dist/multistream/codec/constants.js +63 -0
  16. package/dist/multistream/codec/constants.js.map +1 -0
  17. package/dist/multistream/mediaRequestManager.js +62 -15
  18. package/dist/multistream/mediaRequestManager.js.map +1 -1
  19. package/dist/types/config.d.ts +1 -0
  20. package/dist/types/meeting/index.d.ts +9 -0
  21. package/dist/types/meetings/index.d.ts +10 -0
  22. package/dist/types/multistream/codec/constants.d.ts +7 -0
  23. package/dist/types/multistream/mediaRequestManager.d.ts +22 -5
  24. package/dist/webinar/index.js +1 -1
  25. package/package.json +1 -1
  26. package/src/breakouts/index.ts +30 -0
  27. package/src/config.ts +1 -0
  28. package/src/media/index.ts +3 -0
  29. package/src/meeting/index.ts +41 -2
  30. package/src/meetings/index.ts +21 -0
  31. package/src/multistream/codec/constants.ts +58 -0
  32. package/src/multistream/mediaRequestManager.ts +119 -28
  33. package/test/unit/spec/breakouts/index.ts +47 -0
  34. package/test/unit/spec/media/index.ts +31 -0
  35. package/test/unit/spec/meeting/index.js +154 -0
  36. package/test/unit/spec/meetings/index.js +27 -0
  37. package/test/unit/spec/multistream/mediaRequestManager.ts +501 -37
@@ -22,7 +22,6 @@ import {
22
22
  MediaConnectionEventNames,
23
23
  MediaContent,
24
24
  MediaType,
25
- MediaCodecMimeType,
26
25
  RemoteTrackType,
27
26
  RoapMessage,
28
27
  StatsAnalyzer,
@@ -31,7 +30,7 @@ import {
31
30
  NetworkQualityMonitor,
32
31
  StatsMonitor,
33
32
  StatsMonitorEventNames,
34
- InboundAudioIssueSubTypes,
33
+ MediaCodecMimeType,
35
34
  } from '@webex/internal-media-core';
36
35
 
37
36
  import {DataChannelTokenType} from '@webex/internal-plugin-llm';
@@ -969,6 +968,7 @@ export default class Meeting extends StatelessWebexPlugin {
969
968
  },
970
969
  (csi: CSI) => (this.members.findMemberByCsi(csi) as any)?.id
971
970
  );
971
+
972
972
  /**
973
973
  * Object containing helper classes for managing media requests for audio/video/screenshare (for multistream media connections)
974
974
  * All multistream media requests sent out for this meeting have to go through them.
@@ -988,6 +988,7 @@ export default class Meeting extends StatelessWebexPlugin {
988
988
  mediaRequests
989
989
  );
990
990
  },
991
+ this.getIngressPayloadTypeCallback.bind(this),
991
992
  {
992
993
  // @ts-ignore - config coming from registerPlugin
993
994
  degradationPreferences: this.config.degradationPreferences,
@@ -1009,6 +1010,7 @@ export default class Meeting extends StatelessWebexPlugin {
1009
1010
  mediaRequests
1010
1011
  );
1011
1012
  },
1013
+ this.getIngressPayloadTypeCallback.bind(this),
1012
1014
  {
1013
1015
  // @ts-ignore - config coming from registerPlugin
1014
1016
  degradationPreferences: this.config.degradationPreferences,
@@ -1030,6 +1032,7 @@ export default class Meeting extends StatelessWebexPlugin {
1030
1032
  mediaRequests
1031
1033
  );
1032
1034
  },
1035
+ this.getIngressPayloadTypeCallback.bind(this),
1033
1036
  {
1034
1037
  // @ts-ignore - config coming from registerPlugin
1035
1038
  degradationPreferences: this.config.degradationPreferences,
@@ -1051,11 +1054,14 @@ export default class Meeting extends StatelessWebexPlugin {
1051
1054
  mediaRequests
1052
1055
  );
1053
1056
  },
1057
+ this.getIngressPayloadTypeCallback.bind(this),
1054
1058
  {
1055
1059
  // @ts-ignore - config coming from registerPlugin
1056
1060
  degradationPreferences: this.config.degradationPreferences,
1057
1061
  kind: 'video',
1058
1062
  trimRequestsToNumOfSources: false,
1063
+ // @ts-ignore - config coming from registerPlugin
1064
+ enableAv1: this.config.enableAv1SlidesSupport,
1059
1065
  }
1060
1066
  ),
1061
1067
  };
@@ -1716,6 +1722,37 @@ export default class Meeting extends StatelessWebexPlugin {
1716
1722
  this.mediaServerIp = undefined;
1717
1723
  }
1718
1724
 
1725
+ /**
1726
+ * Get the ingress payload type for a given media type and codec mime type
1727
+ * @param {MediaType} mediaType - The media type
1728
+ * @param {MediaCodecMimeType} codecMimeType - The codec mime type
1729
+ * @returns {number | undefined} - The ingress payload type
1730
+ * @private
1731
+ * @memberof Meeting
1732
+ */
1733
+ private getIngressPayloadTypeCallback(
1734
+ mediaType: MediaType,
1735
+ codecMimeType: MediaCodecMimeType
1736
+ ): number | undefined {
1737
+ if (this.isMultistream) {
1738
+ try {
1739
+ return this.mediaProperties.webrtcMediaConnection.getIngressPayloadType(
1740
+ mediaType,
1741
+ codecMimeType
1742
+ );
1743
+ } catch (error) {
1744
+ LoggerProxy.logger.info(
1745
+ `Meeting:index#mediaRequestManager --> failed to get ingress payload type for mediaType=${mediaType}, codecMimeType=${codecMimeType}`,
1746
+ error
1747
+ );
1748
+
1749
+ return undefined;
1750
+ }
1751
+ }
1752
+
1753
+ return undefined;
1754
+ }
1755
+
1719
1756
  /**
1720
1757
  * Temporary func to return webex object,
1721
1758
  * in order to access internal plugin metrics
@@ -7768,6 +7805,8 @@ export default class Meeting extends StatelessWebexPlugin {
7768
7805
  disableAudioMainDtx: this.config.experimental.disableAudioMainDtx,
7769
7806
  // @ts-ignore - config coming from registerPlugin
7770
7807
  enableAudioTwcc: this.config.enableAudioTwccForMultistream,
7808
+ // @ts-ignore - config coming from registerPlugin
7809
+ enableAv1SlidesSupport: this.config.enableAv1SlidesSupport,
7771
7810
  stopIceGatheringAfterFirstRelayCandidate:
7772
7811
  // @ts-ignore - config coming from registerPlugin
7773
7812
  this.config.stopIceGatheringAfterFirstRelayCandidate,
@@ -935,6 +935,27 @@ export default class Meetings extends WebexPlugin {
935
935
  }
936
936
  }
937
937
 
938
+ /**
939
+ * API to toggle AV1 codec support for video slides in multistream,
940
+ * needs to be called before webex.meetings.joinWithMedia()
941
+ *
942
+ * @param {Boolean} newValue
943
+ * @private
944
+ * @memberof Meetings
945
+ * @returns {undefined}
946
+ */
947
+ private _toggleEnableAv1SlidesSupport(newValue: boolean) {
948
+ if (typeof newValue !== 'boolean') {
949
+ return;
950
+ }
951
+
952
+ // @ts-ignore
953
+ if (this.config.enableAv1SlidesSupport !== newValue) {
954
+ // @ts-ignore
955
+ this.config.enableAv1SlidesSupport = newValue;
956
+ }
957
+ }
958
+
938
959
  /**
939
960
  * API to toggle stopping ICE Candidates Gathering after first relay candidate,
940
961
  * needs to be called before webex.meetings.joinWithMedia()
@@ -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: 160 * 90,
10
+ maxDecodeRate: 5_529_600,
11
+ },
12
+ '180p': {
13
+ levelIdx: 0,
14
+ tier: 0,
15
+ maxWidth: 320,
16
+ maxHeight: 180,
17
+ maxPicSize: 320 * 180,
18
+ maxDecodeRate: 5_529_600,
19
+ },
20
+ '360p': {
21
+ levelIdx: 1,
22
+ tier: 0,
23
+ maxWidth: 640,
24
+ maxHeight: 360,
25
+ maxPicSize: 640 * 360,
26
+ maxDecodeRate: 10_454_400,
27
+ },
28
+ '540p': {
29
+ levelIdx: 4,
30
+ tier: 0,
31
+ maxWidth: 960,
32
+ maxHeight: 540,
33
+ maxPicSize: 960 * 540,
34
+ maxDecodeRate: 24_969_600,
35
+ },
36
+ '720p': {
37
+ levelIdx: 5,
38
+ tier: 0,
39
+ maxWidth: 1280,
40
+ maxHeight: 720,
41
+ maxPicSize: 1280 * 720,
42
+ maxDecodeRate: 39_938_400,
43
+ },
44
+ '1080p': {
45
+ levelIdx: 8,
46
+ tier: 0,
47
+ maxWidth: 1920,
48
+ maxHeight: 1080,
49
+ maxPicSize: 1920 * 1080,
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 Options = {
84
+ type AudioMediaRequestManagerOptions = {
75
85
  degradationPreferences: DegradationPreferences;
76
- kind: 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(sendMediaRequestsCallback: SendMediaRequestsCallback, options: Options) {
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 || CODEC_DEFAULTS.h264.maxFs,
139
- mr.codecInfo.maxFs || CODEC_DEFAULTS.h264.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 || CODEC_DEFAULTS.h264.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 || CODEC_DEFAULTS.h264.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
- mr.codecInfo && [
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 {
@@ -1849,6 +1849,53 @@ describe('plugin-meetings', () => {
1849
1849
  });
1850
1850
  });
1851
1851
 
1852
+ describe('#removeFromBreakout', () => {
1853
+ it('should make a POST request with correct body and return the result', async () => {
1854
+ breakouts.request = sinon.stub().returns(Promise.resolve('REQUEST_RETURN_VALUE'));
1855
+ breakouts.set('url', 'url');
1856
+ breakouts.set('mainGroupId', 'mainGroupId');
1857
+ breakouts.set('mainSessionId', 'mainSessionId');
1858
+
1859
+ const participants = ['participant1', 'participant2'];
1860
+ const result = await breakouts.removeFromBreakout(participants);
1861
+
1862
+ assert.calledOnceWithExactly(breakouts.request, {
1863
+ method: 'POST',
1864
+ uri: 'url/move',
1865
+ body: {
1866
+ groups: [
1867
+ {
1868
+ id: 'mainGroupId',
1869
+ sessions: [
1870
+ {
1871
+ id: 'mainSessionId',
1872
+ participants,
1873
+ },
1874
+ ],
1875
+ },
1876
+ ],
1877
+ },
1878
+ });
1879
+ assert.equal(result, 'REQUEST_RETURN_VALUE');
1880
+ });
1881
+
1882
+ it('should throw an error if mainGroupId is missing', () => {
1883
+ breakouts.set('mainSessionId', 'mainSessionId');
1884
+ assert.throws(
1885
+ () => breakouts.removeFromBreakout(['participant1']),
1886
+ 'Main group ID and session ID must be available to remove participants from breakout'
1887
+ );
1888
+ });
1889
+
1890
+ it('should throw an error if mainSessionId is missing', () => {
1891
+ breakouts.set('mainGroupId', 'mainGroupId');
1892
+ assert.throws(
1893
+ () => breakouts.removeFromBreakout(['participant1']),
1894
+ 'Main group ID and session ID must be available to remove participants from breakout'
1895
+ );
1896
+ });
1897
+ });
1898
+
1852
1899
  describe('#triggerReturnToMainEvent', () => {
1853
1900
  const checkTrigger = ({breakout, shouldTrigger}) => {
1854
1901
  breakouts.trigger = sinon.stub();
@@ -295,6 +295,7 @@ describe('createMediaConnection', () => {
295
295
  bundlePolicy: 'max-bundle',
296
296
  disableAudioMainDtx: false,
297
297
  disableAudioTwcc: false,
298
+ enableAV1SlidesSupport: false,
298
299
  },
299
300
  'meeting id'
300
301
  );
@@ -322,6 +323,26 @@ describe('createMediaConnection', () => {
322
323
  assert.calledOnce(rtcMetrics.sendMetricsInQueue);
323
324
  });
324
325
 
326
+ it('passes enableAV1SlidesSupport: true to MultistreamRoapMediaConnection when enableAv1SlidesSupport is set', () => {
327
+ const multistreamRoapMediaConnectionConstructorStub = sinon
328
+ .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
329
+ .returns(fakeRoapMediaConnection);
330
+
331
+ Media.createMediaConnection(true, 'some debug id', 'meeting id', {
332
+ enableAv1SlidesSupport: true,
333
+ });
334
+ assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
335
+ assert.calledWith(
336
+ multistreamRoapMediaConnectionConstructorStub,
337
+ sinon.match({
338
+ iceServers: [],
339
+ disableAudioTwcc: true,
340
+ enableAV1SlidesSupport: true,
341
+ }),
342
+ 'meeting id'
343
+ );
344
+ });
345
+
325
346
  it('multistream non-firefox does not care about stopIceGatheringAfterFirstRelayCandidate', () => {
326
347
  const multistreamRoapMediaConnectionConstructorStub = sinon
327
348
  .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
@@ -336,6 +357,7 @@ describe('createMediaConnection', () => {
336
357
  {
337
358
  iceServers: [],
338
359
  disableAudioTwcc: true,
360
+ enableAV1SlidesSupport: false,
339
361
  },
340
362
  'meeting id'
341
363
  );
@@ -359,6 +381,7 @@ describe('createMediaConnection', () => {
359
381
  doFullIce: true,
360
382
  stopIceGatheringAfterFirstRelayCandidate: true,
361
383
  disableAudioTwcc: true,
384
+ enableAV1SlidesSupport: false,
362
385
  },
363
386
  'meeting id'
364
387
  );
@@ -382,6 +405,7 @@ describe('createMediaConnection', () => {
382
405
  doFullIce: true,
383
406
  stopIceGatheringAfterFirstRelayCandidate: false,
384
407
  disableAudioTwcc: true,
408
+ enableAV1SlidesSupport: false,
385
409
  },
386
410
  'meeting id'
387
411
  );
@@ -418,6 +442,7 @@ describe('createMediaConnection', () => {
418
442
  {
419
443
  iceServers: [],
420
444
  disableAudioTwcc: true,
445
+ enableAV1SlidesSupport: false,
421
446
  },
422
447
  'meeting id'
423
448
  );
@@ -448,6 +473,7 @@ describe('createMediaConnection', () => {
448
473
  {
449
474
  iceServers: [],
450
475
  disableAudioTwcc: true,
476
+ enableAV1SlidesSupport: false,
451
477
  },
452
478
  'meeting id'
453
479
  );
@@ -477,6 +503,7 @@ describe('createMediaConnection', () => {
477
503
  {
478
504
  iceServers: [],
479
505
  disableAudioTwcc: true,
506
+ enableAV1SlidesSupport: false,
480
507
  },
481
508
  'meeting id'
482
509
  );
@@ -505,6 +532,7 @@ describe('createMediaConnection', () => {
505
532
  {
506
533
  iceServers: [],
507
534
  disableAudioTwcc: true,
535
+ enableAV1SlidesSupport: false,
508
536
  },
509
537
  'meeting id'
510
538
  );
@@ -591,6 +619,7 @@ describe('createMediaConnection', () => {
591
619
  {
592
620
  iceServers: [],
593
621
  disableAudioTwcc: true,
622
+ enableAV1SlidesSupport: false,
594
623
  enableInboundAudioLevelMonitoring: true,
595
624
  }
596
625
  );
@@ -602,6 +631,7 @@ describe('createMediaConnection', () => {
602
631
  {
603
632
  iceServers: [],
604
633
  disableAudioTwcc: true,
634
+ enableAV1SlidesSupport: false,
605
635
  enableInboundAudioLevelMonitoring: true,
606
636
  }
607
637
  );
@@ -613,6 +643,7 @@ describe('createMediaConnection', () => {
613
643
  {
614
644
  iceServers: [],
615
645
  disableAudioTwcc: true,
646
+ enableAV1SlidesSupport: false,
616
647
  doFullIce: true,
617
648
  stopIceGatheringAfterFirstRelayCandidate: undefined,
618
649
  }