livekit-client 2.22.2 → 2.22.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +486 -437
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +398 -100
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.fm.worker.js +1 -1
- package/dist/livekit-client.fm.worker.js.map +1 -1
- package/dist/livekit-client.fm.worker.mjs +8 -1
- package/dist/livekit-client.fm.worker.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/WebSocketStream.d.ts.map +1 -1
- package/dist/src/api/utils.d.ts +1 -0
- package/dist/src/api/utils.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +26 -0
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +15 -1
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/ErrorRateLimiter.d.ts +21 -0
- package/dist/src/e2ee/worker/ErrorRateLimiter.d.ts.map +1 -0
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +1 -18
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/logger.d.ts +4 -0
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +1 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts +16 -0
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +7 -0
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +1 -1
- package/dist/src/room/utils.d.ts +34 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/ts4.2/api/utils.d.ts +1 -0
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +26 -0
- package/dist/ts4.2/e2ee/types.d.ts +15 -1
- package/dist/ts4.2/e2ee/worker/ErrorRateLimiter.d.ts +21 -0
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +1 -18
- package/dist/ts4.2/logger.d.ts +4 -0
- package/dist/ts4.2/room/RTCEngine.d.ts +1 -0
- package/dist/ts4.2/room/participant/publishUtils.d.ts +16 -0
- package/dist/ts4.2/room/track/LocalVideoTrack.d.ts +7 -0
- package/dist/ts4.2/room/track/options.d.ts +1 -1
- package/dist/ts4.2/room/utils.d.ts +34 -0
- package/package.json +1 -1
- package/src/api/WebSocketStream.ts +3 -8
- package/src/api/utils.ts +10 -0
- package/src/e2ee/E2eeManager.test.ts +196 -0
- package/src/e2ee/E2eeManager.ts +114 -19
- package/src/e2ee/types.ts +19 -1
- package/src/e2ee/worker/DataCryptor.ts +2 -1
- package/src/e2ee/worker/ErrorRateLimiter.test.ts +53 -0
- package/src/e2ee/worker/ErrorRateLimiter.ts +52 -0
- package/src/e2ee/worker/FrameCryptor.ts +20 -70
- package/src/e2ee/worker/e2ee.worker.ts +34 -11
- package/src/logger.ts +22 -0
- package/src/room/RTCEngine.ts +28 -7
- package/src/room/Room.ts +1 -1
- package/src/room/participant/LocalParticipant.ts +30 -14
- package/src/room/participant/publishUtils.test.ts +133 -0
- package/src/room/participant/publishUtils.ts +54 -19
- package/src/room/track/LocalVideoTrack.ts +15 -5
- package/src/room/track/options.ts +1 -1
- package/src/room/utils.test.ts +87 -0
- package/src/room/utils.ts +59 -0
|
@@ -13,12 +13,12 @@ import type {
|
|
|
13
13
|
import { ScreenSharePresets, VideoPreset, VideoPresets, VideoPresets43 } from '../track/options';
|
|
14
14
|
import type { LoggerOptions } from '../types';
|
|
15
15
|
import {
|
|
16
|
-
compareVersions,
|
|
17
16
|
getReactNativeOs,
|
|
18
17
|
isReactNative,
|
|
19
18
|
isSVCCodec,
|
|
20
|
-
|
|
19
|
+
isSVCSimulcast,
|
|
21
20
|
isSafariSvcApi,
|
|
21
|
+
usesLegacySVCEncodings,
|
|
22
22
|
} from '../utils';
|
|
23
23
|
|
|
24
24
|
/** @internal */
|
|
@@ -111,6 +111,8 @@ export function computeVideoEncodings(
|
|
|
111
111
|
const useSimulcast = options?.simulcast;
|
|
112
112
|
const scalabilityMode = options?.scalabilityMode;
|
|
113
113
|
const videoCodec = options?.videoCodec;
|
|
114
|
+
// VP9/AV1 published as rid based simulcast rather than SVC, see isSVCSimulcast
|
|
115
|
+
const useSVCSimulcast = isSVCSimulcast(videoCodec, options);
|
|
114
116
|
|
|
115
117
|
if ((!videoEncoding && !useSimulcast && !scalabilityMode) || !width || !height) {
|
|
116
118
|
// when we aren't simulcasting or svc, will need to return a single encoding without
|
|
@@ -134,7 +136,7 @@ export function computeVideoEncodings(
|
|
|
134
136
|
videoEncoding.priority,
|
|
135
137
|
);
|
|
136
138
|
|
|
137
|
-
if (scalabilityMode && isSVCCodec(videoCodec)) {
|
|
139
|
+
if (scalabilityMode && isSVCCodec(videoCodec) && !useSVCSimulcast) {
|
|
138
140
|
const sm = new ScalabilityMode(scalabilityMode);
|
|
139
141
|
|
|
140
142
|
const encodings: RTCRtpEncodingParameters[] = [];
|
|
@@ -142,20 +144,10 @@ export function computeVideoEncodings(
|
|
|
142
144
|
if (sm.spatial > 3) {
|
|
143
145
|
throw new Error(`unsupported scalabilityMode: ${scalabilityMode}`);
|
|
144
146
|
}
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
// This is a bit confusing but is due to how libwebrtc interpreted the encodings field
|
|
148
|
-
// before M113.
|
|
149
|
-
// Announced here: https://groups.google.com/g/discuss-webrtc/c/-QQ3pxrl-fw?pli=1
|
|
147
|
+
// Browsers that read multiple encodings on an SVC codec as SVC rather than as
|
|
148
|
+
// simulcast, see usesLegacySVCEncodings
|
|
150
149
|
const browser = getBrowser();
|
|
151
|
-
if (
|
|
152
|
-
isSafariBased() ||
|
|
153
|
-
// Even tho RN runs M114, it does not produce SVC layers when a single encoding
|
|
154
|
-
// is provided. So we'll use the legacy SVC specification for now.
|
|
155
|
-
// TODO: when we upstream libwebrtc, this will need additional verification
|
|
156
|
-
isReactNative() ||
|
|
157
|
-
(browser?.name === 'Chrome' && compareVersions(browser?.version, '113') < 0)
|
|
158
|
-
) {
|
|
150
|
+
if (usesLegacySVCEncodings()) {
|
|
159
151
|
const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
|
|
160
152
|
// safari 18.4 uses a different svc API that requires scaleResolutionDownBy to be set.
|
|
161
153
|
const requireScale = isSafariSvcApi(browser);
|
|
@@ -193,6 +185,19 @@ export function computeVideoEncodings(
|
|
|
193
185
|
return [videoEncoding];
|
|
194
186
|
}
|
|
195
187
|
|
|
188
|
+
// Chrome M113+ only treats multiple encodings on an SVC capable codec as real
|
|
189
|
+
// simulcast when every encoding carries an explicit scalabilityMode next to its
|
|
190
|
+
// scaleResolutionDownBy. Without it the encodings are interpreted as legacy SVC.
|
|
191
|
+
const applySVCSimulcastMode = (encodings: RTCRtpEncodingParameters[]) => {
|
|
192
|
+
if (useSVCSimulcast) {
|
|
193
|
+
encodings.forEach((encoding) => {
|
|
194
|
+
/* @ts-ignore */
|
|
195
|
+
encoding.scalabilityMode = scalabilityMode;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return encodings;
|
|
199
|
+
};
|
|
200
|
+
|
|
196
201
|
let presets: Array<VideoPreset>;
|
|
197
202
|
if (isScreenShare) {
|
|
198
203
|
presets =
|
|
@@ -220,13 +225,43 @@ export function computeVideoEncodings(
|
|
|
220
225
|
// based on other conditions.
|
|
221
226
|
const size = Math.max(width, height);
|
|
222
227
|
if (size >= 960 && midPreset) {
|
|
223
|
-
return
|
|
228
|
+
return applySVCSimulcastMode(
|
|
229
|
+
encodingsFromPresets(width, height, [lowPreset, midPreset, original], sourceFramerate),
|
|
230
|
+
);
|
|
224
231
|
}
|
|
225
232
|
if (size >= 480) {
|
|
226
|
-
return
|
|
233
|
+
return applySVCSimulcastMode(
|
|
234
|
+
encodingsFromPresets(width, height, [lowPreset, original], sourceFramerate),
|
|
235
|
+
);
|
|
227
236
|
}
|
|
228
237
|
}
|
|
229
|
-
return encodingsFromPresets(width, height, [original]);
|
|
238
|
+
return applySVCSimulcastMode(encodingsFromPresets(width, height, [original]));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Bitrate to hint to the bandwidth estimator through `x-google-start-bitrate`, so that
|
|
243
|
+
* a publish does not spend its first seconds ramping up from a very low rate.
|
|
244
|
+
*
|
|
245
|
+
* It has to be the total the encoder will put on the wire, which means picking the
|
|
246
|
+
* encoding that carries the inclusive bitrate:
|
|
247
|
+
* - SVC publishes a single stream with the layers built in. `encodings[0]` holds the
|
|
248
|
+
* full bitrate — the legacy SVC shape orders its encodings `f`..`q`, so that holds
|
|
249
|
+
* for both SVC shapes.
|
|
250
|
+
* - Simulcast publishes independent streams ordered `q`..`f`, so the total is the sum.
|
|
251
|
+
* This includes VP9/AV1 published as rid based simulcast, where `encodings[0]` is
|
|
252
|
+
* the *smallest* layer even though the codec is SVC capable.
|
|
253
|
+
*
|
|
254
|
+
* @internal
|
|
255
|
+
*/
|
|
256
|
+
export function computeStartTargetBitrate(
|
|
257
|
+
codec: string,
|
|
258
|
+
options: TrackPublishOptions | undefined,
|
|
259
|
+
encodings: RTCRtpEncodingParameters[],
|
|
260
|
+
): number {
|
|
261
|
+
if (isSVCCodec(codec) && !isSVCSimulcast(codec, options)) {
|
|
262
|
+
return encodings[0]?.maxBitrate ?? 0;
|
|
263
|
+
}
|
|
264
|
+
return encodings.reduce((sum, enc) => sum + (enc.maxBitrate ?? 0), 0);
|
|
230
265
|
}
|
|
231
266
|
|
|
232
267
|
export function computeTrackBackupEncodings(
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import type { VideoSenderStats } from '../stats';
|
|
17
17
|
import { computeBitrate, monitorFrequency } from '../stats';
|
|
18
18
|
import type { LoggerOptions } from '../types';
|
|
19
|
-
import { isFireFox, isMobile, isSVCCodec, isWeb } from '../utils';
|
|
19
|
+
import { isFireFox, isMobile, isSVCCodec, isSVCSimulcast, isWeb } from '../utils';
|
|
20
20
|
import LocalTrack from './LocalTrack';
|
|
21
21
|
import { Track, VideoQuality } from './Track';
|
|
22
22
|
import type { TrackPublishOptions, VideoCaptureOptions, VideoCodec } from './options';
|
|
@@ -243,6 +243,16 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
|
243
243
|
return items;
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Whether `codec` is being published as SVC (a single stream carrying all spatial
|
|
248
|
+
* layers) as opposed to rid based simulcast. VP9/AV1 are SVC unless the publisher
|
|
249
|
+
* opted into simulcast, in which case each rid is an independent stream and the
|
|
250
|
+
* layers can be enabled/disabled individually.
|
|
251
|
+
*/
|
|
252
|
+
private isSvcPublish(codec?: string): boolean {
|
|
253
|
+
return isSVCCodec(codec) && !isSVCSimulcast(codec, this.publishOptions);
|
|
254
|
+
}
|
|
255
|
+
|
|
246
256
|
setPublishingQuality(maxQuality: VideoQuality) {
|
|
247
257
|
const qualities: SubscribedQuality[] = [];
|
|
248
258
|
for (let q = VideoQuality.LOW; q <= VideoQuality.HIGH; q += 1) {
|
|
@@ -254,7 +264,7 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
|
254
264
|
);
|
|
255
265
|
}
|
|
256
266
|
this.log.debug(`setting publishing quality. max quality ${maxQuality}`, this.logContext);
|
|
257
|
-
this.setPublishingLayers(
|
|
267
|
+
this.setPublishingLayers(this.isSvcPublish(this.codec), qualities);
|
|
258
268
|
}
|
|
259
269
|
|
|
260
270
|
async restartTrack(options?: VideoCaptureOptions) {
|
|
@@ -491,7 +501,7 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
|
491
501
|
});
|
|
492
502
|
// only enable simulcast codec for preference codec setted
|
|
493
503
|
if (!this.codec && codecs.length > 0) {
|
|
494
|
-
await this.setPublishingLayers(
|
|
504
|
+
await this.setPublishingLayers(this.isSvcPublish(codecs[0].codec), codecs[0].qualities);
|
|
495
505
|
|
|
496
506
|
return [];
|
|
497
507
|
}
|
|
@@ -501,7 +511,7 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
|
501
511
|
const newCodecs: VideoCodec[] = [];
|
|
502
512
|
for await (const codec of codecs) {
|
|
503
513
|
if (!this.codec || this.codec === codec.codec) {
|
|
504
|
-
await this.setPublishingLayers(
|
|
514
|
+
await this.setPublishingLayers(this.isSvcPublish(codec.codec), codec.qualities);
|
|
505
515
|
} else {
|
|
506
516
|
const simulcastCodecInfo = this.simulcastCodecs.get(codec.codec as VideoCodec);
|
|
507
517
|
this.log.debug(`try setPublishingCodec for ${codec.codec}`, {
|
|
@@ -522,7 +532,7 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
|
522
532
|
simulcastCodecInfo.encodings!,
|
|
523
533
|
codec.qualities,
|
|
524
534
|
this.senderLock,
|
|
525
|
-
|
|
535
|
+
this.isSvcPublish(codec.codec),
|
|
526
536
|
this.log,
|
|
527
537
|
this.logContext,
|
|
528
538
|
);
|
|
@@ -79,7 +79,7 @@ export interface TrackPublishDefaults {
|
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* scalability mode for svc codecs, defaults to 'L3T3_KEY'.
|
|
82
|
-
* for svc codecs, simulcast is disabled.
|
|
82
|
+
* for svc codecs, simulcast is disabled if more than one spatial layer is used ('L2Tx' or 'L3Tx').
|
|
83
83
|
*/
|
|
84
84
|
scalabilityMode?: ScalabilityMode;
|
|
85
85
|
|
package/src/room/utils.test.ts
CHANGED
|
@@ -4,10 +4,13 @@ import {
|
|
|
4
4
|
ddExtensionURI,
|
|
5
5
|
extractMaxAgeFromRequestHeaders,
|
|
6
6
|
getClientInfo,
|
|
7
|
+
isSVCSimulcast,
|
|
8
|
+
isSVCSimulcastSupportedByServer,
|
|
7
9
|
negotiateDependencyDescriptor,
|
|
8
10
|
splitUtf8,
|
|
9
11
|
supportsAdaptiveStream,
|
|
10
12
|
toWebsocketUrl,
|
|
13
|
+
usesLegacySVCEncodings,
|
|
11
14
|
} from './utils';
|
|
12
15
|
|
|
13
16
|
describe('toWebsocketUrl', () => {
|
|
@@ -280,3 +283,87 @@ describe('supportsAdaptiveStream', () => {
|
|
|
280
283
|
expect(supportsAdaptiveStream()).toBe(false);
|
|
281
284
|
});
|
|
282
285
|
});
|
|
286
|
+
|
|
287
|
+
describe('isSVCSimulcast', () => {
|
|
288
|
+
it('requires an svc capable codec, simulcast and a single spatial layer mode', () => {
|
|
289
|
+
expect(isSVCSimulcast('vp9', { simulcast: true, scalabilityMode: 'L1T2' })).toBe(true);
|
|
290
|
+
expect(isSVCSimulcast('av1', { simulcast: true, scalabilityMode: 'L1T3' })).toBe(true);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('stays on svc without the opt in', () => {
|
|
294
|
+
expect(isSVCSimulcast('vp9', { simulcast: false, scalabilityMode: 'L1T2' })).toBe(false);
|
|
295
|
+
expect(isSVCSimulcast('vp9', { simulcast: true })).toBe(false);
|
|
296
|
+
// a multi spatial layer mode is svc by definition
|
|
297
|
+
expect(isSVCSimulcast('vp9', { simulcast: true, scalabilityMode: 'L3T3_KEY' })).toBe(false);
|
|
298
|
+
expect(isSVCSimulcast('vp9', undefined)).toBe(false);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('does not apply to non svc codecs', () => {
|
|
302
|
+
expect(isSVCSimulcast('vp8', { simulcast: true, scalabilityMode: 'L1T2' })).toBe(false);
|
|
303
|
+
expect(isSVCSimulcast('h264', { simulcast: true, scalabilityMode: 'L1T2' })).toBe(false);
|
|
304
|
+
expect(isSVCSimulcast(undefined, { simulcast: true, scalabilityMode: 'L1T2' })).toBe(false);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('isSVCSimulcastSupportedByServer', () => {
|
|
309
|
+
it('requires a server newer than 1.13.6', () => {
|
|
310
|
+
expect(isSVCSimulcastSupportedByServer('1.13.7')).toBe(true);
|
|
311
|
+
expect(isSVCSimulcastSupportedByServer('1.14.0')).toBe(true);
|
|
312
|
+
expect(isSVCSimulcastSupportedByServer('2.0.0')).toBe(true);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('rejects 1.13.6 and older', () => {
|
|
316
|
+
expect(isSVCSimulcastSupportedByServer('1.13.6')).toBe(false);
|
|
317
|
+
expect(isSVCSimulcastSupportedByServer('1.13.5')).toBe(false);
|
|
318
|
+
expect(isSVCSimulcastSupportedByServer('1.9.0')).toBe(false);
|
|
319
|
+
expect(isSVCSimulcastSupportedByServer('0.15.1')).toBe(false);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('treats an unknown version as unsupported', () => {
|
|
323
|
+
expect(isSVCSimulcastSupportedByServer(undefined)).toBe(false);
|
|
324
|
+
expect(isSVCSimulcastSupportedByServer('')).toBe(false);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe('usesLegacySVCEncodings', () => {
|
|
329
|
+
const stubUserAgent = (userAgent: string) =>
|
|
330
|
+
vi.stubGlobal('navigator', { userAgent, product: 'Gecko' });
|
|
331
|
+
|
|
332
|
+
afterEach(() => {
|
|
333
|
+
vi.unstubAllGlobals();
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const chrome = (v: string) =>
|
|
337
|
+
`Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${v} Safari/537.36`;
|
|
338
|
+
|
|
339
|
+
it('is legacy on chrome before M113', () => {
|
|
340
|
+
stubUserAgent(chrome('112.0.0.0'));
|
|
341
|
+
expect(usesLegacySVCEncodings()).toBe(true);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('is not legacy from chrome M113 onwards', () => {
|
|
345
|
+
stubUserAgent(chrome('113.0.0.0'));
|
|
346
|
+
expect(usesLegacySVCEncodings()).toBe(false);
|
|
347
|
+
stubUserAgent(chrome('120.0.0.0'));
|
|
348
|
+
expect(usesLegacySVCEncodings()).toBe(false);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('is legacy on safari, which has no rid based svc simulcast', () => {
|
|
352
|
+
stubUserAgent(
|
|
353
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
|
|
354
|
+
);
|
|
355
|
+
expect(usesLegacySVCEncodings()).toBe(true);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('is legacy on iOS, where every browser is webkit', () => {
|
|
359
|
+
stubUserAgent(
|
|
360
|
+
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/120.0.0.0 Mobile/15E148 Safari/604.1',
|
|
361
|
+
);
|
|
362
|
+
expect(usesLegacySVCEncodings()).toBe(true);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('is legacy on react native regardless of chrome version', () => {
|
|
366
|
+
vi.stubGlobal('navigator', { userAgent: chrome('120.0.0.0'), product: 'ReactNative' });
|
|
367
|
+
expect(usesLegacySVCEncodings()).toBe(true);
|
|
368
|
+
});
|
|
369
|
+
});
|
package/src/room/utils.ts
CHANGED
|
@@ -185,6 +185,65 @@ export function negotiateDependencyDescriptor(transceiver: RTCRtpTransceiver): b
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
/**
|
|
189
|
+
* VP9 and AV1 are published as SVC (a single RTP stream carrying every spatial layer)
|
|
190
|
+
* by default. They can instead be published as real, rid based simulcast — one
|
|
191
|
+
* independent stream per rid, each carrying a single spatial layer — when the caller
|
|
192
|
+
* opts in with `simulcast: true` and a single spatial layer scalability mode (`L1Tx`).
|
|
193
|
+
*
|
|
194
|
+
* The SFU has to be told about this: without an explicit
|
|
195
|
+
* `SimulcastCodec.videoLayerMode` it assumes `MULTIPLE_SPATIAL_LAYERS_PER_STREAM` for
|
|
196
|
+
* any SVC capable codec.
|
|
197
|
+
*/
|
|
198
|
+
export function isSVCSimulcast(
|
|
199
|
+
codec?: string,
|
|
200
|
+
options?: { simulcast?: boolean; scalabilityMode?: string },
|
|
201
|
+
): boolean {
|
|
202
|
+
return isSVCCodec(codec) && !!options?.simulcast && !!options.scalabilityMode?.startsWith('L1T');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Whether the browser reads multiple encodings on an SVC capable codec as *legacy SVC*
|
|
207
|
+
* rather than as real simulcast.
|
|
208
|
+
*
|
|
209
|
+
* Before Chrome M113, supplying more than one encoding for VP9/AV1 selected SVC mode;
|
|
210
|
+
* only from M113 does libwebrtc treat such encodings as simulcast, and only when each
|
|
211
|
+
* one carries its own scalabilityMode. Safari (and anything WebKit based, i. e. every
|
|
212
|
+
* browser on iOS) still uses the old interpretation, as does React Native's libwebrtc.
|
|
213
|
+
* Announced at https://groups.google.com/g/discuss-webrtc/c/-QQ3pxrl-fw
|
|
214
|
+
*
|
|
215
|
+
* Where this is true the rids would not exist on the wire, so VP9/AV1 must be published
|
|
216
|
+
* as SVC no matter what the caller asked for.
|
|
217
|
+
*/
|
|
218
|
+
export function usesLegacySVCEncodings(): boolean {
|
|
219
|
+
const browser = getBrowser();
|
|
220
|
+
return (
|
|
221
|
+
isSafariBased() ||
|
|
222
|
+
// Even tho RN runs M114, it does not produce SVC layers when a single encoding
|
|
223
|
+
// is provided. So we'll use the legacy SVC specification for now.
|
|
224
|
+
// TODO: when we upstream libwebrtc, this will need additional verification
|
|
225
|
+
isReactNative() ||
|
|
226
|
+
(browser?.name === 'Chrome' && compareVersions(browser.version, '113') < 0)
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Last server version that doesn't support vp9/av1 simulcast.
|
|
232
|
+
*/
|
|
233
|
+
const svcSimulcastMinServerVersion = '1.13.6';
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Whether the connected server honours `SimulcastCodec.videoLayerMode`, i. e. whether
|
|
237
|
+
* VP9/AV1 can be published as rid based simulcast. An unknown version is treated as
|
|
238
|
+
* unsupported so the publish falls back to SVC.
|
|
239
|
+
*/
|
|
240
|
+
export function isSVCSimulcastSupportedByServer(serverVersion?: string): boolean {
|
|
241
|
+
if (!serverVersion) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
return compareVersions(serverVersion, svcSimulcastMinServerVersion) > 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
188
247
|
export function supportsSetSinkId(elm?: HTMLMediaElement): boolean {
|
|
189
248
|
if (!document || isSafariBased()) {
|
|
190
249
|
return false;
|