livekit-client 2.13.8 → 2.15.0
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.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +276 -117
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +1 -0
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +18 -0
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +1 -0
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +3 -0
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrackPublication.d.ts +1 -0
- package/dist/src/room/track/LocalTrackPublication.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/RemoteTrackPublication.d.ts +12 -3
- package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +1 -0
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/TrackPublication.d.ts +1 -0
- package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +5 -1
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts +3 -1
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +2 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/ts4.2/src/room/PCTransport.d.ts +1 -0
- package/dist/ts4.2/src/room/events.d.ts +18 -0
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +1 -0
- package/dist/ts4.2/src/room/participant/Participant.d.ts +3 -0
- package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +1 -0
- package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +7 -0
- package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +12 -3
- package/dist/ts4.2/src/room/track/Track.d.ts +1 -0
- package/dist/ts4.2/src/room/track/TrackPublication.d.ts +1 -0
- package/dist/ts4.2/src/room/track/options.d.ts +6 -1
- package/dist/ts4.2/src/room/track/utils.d.ts +3 -1
- package/dist/ts4.2/src/room/utils.d.ts +2 -1
- package/package.json +11 -11
- package/src/e2ee/E2eeManager.ts +3 -2
- package/src/room/PCTransport.ts +88 -77
- package/src/room/Room.ts +2 -0
- package/src/room/events.ts +21 -0
- package/src/room/participant/LocalParticipant.ts +14 -2
- package/src/room/participant/Participant.ts +3 -0
- package/src/room/participant/RemoteParticipant.ts +1 -0
- package/src/room/participant/publishUtils.ts +3 -2
- package/src/room/track/LocalTrackPublication.ts +9 -1
- package/src/room/track/LocalVideoTrack.ts +68 -1
- package/src/room/track/RemoteTrackPublication.ts +91 -32
- package/src/room/track/Track.ts +1 -0
- package/src/room/track/TrackPublication.ts +1 -0
- package/src/room/track/create.ts +2 -2
- package/src/room/track/options.ts +6 -1
- package/src/room/track/utils.ts +12 -1
- package/src/room/utils.ts +37 -3
@@ -7,6 +7,7 @@ import {
|
|
7
7
|
} from '@livekit/protocol';
|
8
8
|
import type { SignalClient } from '../../api/SignalClient';
|
9
9
|
import type { StructuredLogger } from '../../logger';
|
10
|
+
import { TrackEvent } from '../events';
|
10
11
|
import { ScalabilityMode } from '../participant/publishUtils';
|
11
12
|
import type { VideoSenderStats } from '../stats';
|
12
13
|
import { computeBitrate, monitorFrequency } from '../stats';
|
@@ -56,6 +57,10 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
56
57
|
|
57
58
|
private degradationPreference: RTCDegradationPreference = 'balanced';
|
58
59
|
|
60
|
+
private isCpuConstrained: boolean = false;
|
61
|
+
|
62
|
+
private optimizeForPerformance: boolean = false;
|
63
|
+
|
59
64
|
get sender(): RTCRtpSender | undefined {
|
60
65
|
return this._sender;
|
61
66
|
}
|
@@ -251,6 +256,9 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
251
256
|
}
|
252
257
|
await this.restart(constraints);
|
253
258
|
|
259
|
+
// reset cpu constrained state after track is restarted
|
260
|
+
this.isCpuConstrained = false;
|
261
|
+
|
254
262
|
for await (const sc of this.simulcastCodecs.values()) {
|
255
263
|
if (sc.sender && sc.sender.transport?.state !== 'closed') {
|
256
264
|
sc.mediaStreamTrack = this.mediaStreamTrack.clone();
|
@@ -334,6 +342,7 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
334
342
|
// only enable simulcast codec for preference codec setted
|
335
343
|
if (!this.codec && codecs.length > 0) {
|
336
344
|
await this.setPublishingLayers(isSVCCodec(codecs[0].codec), codecs[0].qualities);
|
345
|
+
|
337
346
|
return [];
|
338
347
|
}
|
339
348
|
|
@@ -378,6 +387,13 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
378
387
|
* Sets layers that should be publishing
|
379
388
|
*/
|
380
389
|
async setPublishingLayers(isSvc: boolean, qualities: SubscribedQuality[]) {
|
390
|
+
if (this.optimizeForPerformance) {
|
391
|
+
this.log.info('skipping setPublishingLayers due to optimized publishing performance', {
|
392
|
+
...this.logContext,
|
393
|
+
qualities,
|
394
|
+
});
|
395
|
+
return;
|
396
|
+
}
|
381
397
|
this.log.debug('setting publishing layers', { ...this.logContext, qualities });
|
382
398
|
if (!this.sender || !this.encodings) {
|
383
399
|
return;
|
@@ -394,6 +410,49 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
394
410
|
);
|
395
411
|
}
|
396
412
|
|
413
|
+
/**
|
414
|
+
* Designed for lower powered devices, reduces video publishing quality and disables simulcast.
|
415
|
+
* @experimental
|
416
|
+
*/
|
417
|
+
async prioritizePerformance() {
|
418
|
+
if (!this.sender) {
|
419
|
+
throw new Error('sender not found');
|
420
|
+
}
|
421
|
+
|
422
|
+
const unlock = await this.senderLock.lock();
|
423
|
+
|
424
|
+
try {
|
425
|
+
this.optimizeForPerformance = true;
|
426
|
+
const params = this.sender.getParameters();
|
427
|
+
|
428
|
+
params.encodings = params.encodings.map((e, idx) => ({
|
429
|
+
...e,
|
430
|
+
active: idx === 0,
|
431
|
+
scaleResolutionDownBy: Math.max(
|
432
|
+
1,
|
433
|
+
Math.ceil((this.mediaStreamTrack.getSettings().height ?? 360) / 360),
|
434
|
+
),
|
435
|
+
scalabilityMode: idx === 0 && isSVCCodec(this.codec) ? 'L1T3' : undefined,
|
436
|
+
maxFramerate: idx === 0 ? 15 : 0,
|
437
|
+
maxBitrate: idx === 0 ? e.maxBitrate : 0,
|
438
|
+
}));
|
439
|
+
this.log.debug('setting performance optimised encodings', {
|
440
|
+
...this.logContext,
|
441
|
+
encodings: params.encodings,
|
442
|
+
});
|
443
|
+
this.encodings = params.encodings;
|
444
|
+
await this.sender.setParameters(params);
|
445
|
+
} catch (e) {
|
446
|
+
this.log.error('failed to set performance optimised encodings', {
|
447
|
+
...this.logContext,
|
448
|
+
error: e,
|
449
|
+
});
|
450
|
+
this.optimizeForPerformance = false;
|
451
|
+
} finally {
|
452
|
+
unlock();
|
453
|
+
}
|
454
|
+
}
|
455
|
+
|
397
456
|
protected monitorSender = async () => {
|
398
457
|
if (!this.sender) {
|
399
458
|
this._currentBitrate = 0;
|
@@ -404,11 +463,19 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
404
463
|
try {
|
405
464
|
stats = await this.getSenderStats();
|
406
465
|
} catch (e) {
|
407
|
-
this.log.error('could not get
|
466
|
+
this.log.error('could not get video sender stats', { ...this.logContext, error: e });
|
408
467
|
return;
|
409
468
|
}
|
410
469
|
const statsMap = new Map<string, VideoSenderStats>(stats.map((s) => [s.rid, s]));
|
411
470
|
|
471
|
+
const isCpuConstrained = stats.some((s) => s.qualityLimitationReason === 'cpu');
|
472
|
+
if (isCpuConstrained !== this.isCpuConstrained) {
|
473
|
+
this.isCpuConstrained = isCpuConstrained;
|
474
|
+
if (this.isCpuConstrained) {
|
475
|
+
this.emit(TrackEvent.CpuConstrained);
|
476
|
+
}
|
477
|
+
}
|
478
|
+
|
412
479
|
if (this.prevStats) {
|
413
480
|
let totalBitrate = 0;
|
414
481
|
statsMap.forEach((s, key) => {
|
@@ -11,6 +11,7 @@ import { isRemoteVideoTrack } from '../utils';
|
|
11
11
|
import type RemoteTrack from './RemoteTrack';
|
12
12
|
import { Track, VideoQuality } from './Track';
|
13
13
|
import { TrackPublication } from './TrackPublication';
|
14
|
+
import { areDimensionsSmaller, layerDimensionsFor } from './utils';
|
14
15
|
|
15
16
|
export default class RemoteTrackPublication extends TrackPublication {
|
16
17
|
track?: RemoteTrack = undefined;
|
@@ -21,11 +22,15 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
21
22
|
// keeps track of client's desire to subscribe to a track, also true if autoSubscribe is active
|
22
23
|
protected subscribed?: boolean;
|
23
24
|
|
24
|
-
protected
|
25
|
+
protected requestedDisabled: boolean | undefined = undefined;
|
25
26
|
|
26
|
-
protected
|
27
|
+
protected visible: boolean = true;
|
27
28
|
|
28
|
-
protected
|
29
|
+
protected videoDimensionsAdaptiveStream?: Track.Dimensions;
|
30
|
+
|
31
|
+
protected requestedVideoDimensions?: Track.Dimensions;
|
32
|
+
|
33
|
+
protected requestedMaxQuality?: VideoQuality;
|
29
34
|
|
30
35
|
protected fps?: number;
|
31
36
|
|
@@ -105,7 +110,11 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
105
110
|
}
|
106
111
|
|
107
112
|
get isEnabled(): boolean {
|
108
|
-
return
|
113
|
+
return this.requestedDisabled !== undefined
|
114
|
+
? !this.requestedDisabled
|
115
|
+
: this.isAdaptiveStream
|
116
|
+
? this.visible
|
117
|
+
: true;
|
109
118
|
}
|
110
119
|
|
111
120
|
get isLocal() {
|
@@ -119,10 +128,10 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
119
128
|
* @param enabled
|
120
129
|
*/
|
121
130
|
setEnabled(enabled: boolean) {
|
122
|
-
if (!this.isManualOperationAllowed() || this.
|
131
|
+
if (!this.isManualOperationAllowed() || this.requestedDisabled === !enabled) {
|
123
132
|
return;
|
124
133
|
}
|
125
|
-
this.
|
134
|
+
this.requestedDisabled = !enabled;
|
126
135
|
|
127
136
|
this.emitTrackUpdate();
|
128
137
|
}
|
@@ -135,29 +144,36 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
135
144
|
* optimize for uninterrupted video
|
136
145
|
*/
|
137
146
|
setVideoQuality(quality: VideoQuality) {
|
138
|
-
if (!this.isManualOperationAllowed() || this.
|
147
|
+
if (!this.isManualOperationAllowed() || this.requestedMaxQuality === quality) {
|
139
148
|
return;
|
140
149
|
}
|
141
|
-
this.
|
142
|
-
this.
|
150
|
+
this.requestedMaxQuality = quality;
|
151
|
+
this.requestedVideoDimensions = undefined;
|
143
152
|
|
144
153
|
this.emitTrackUpdate();
|
145
154
|
}
|
146
155
|
|
156
|
+
/**
|
157
|
+
* Explicitly set the video dimensions for this track.
|
158
|
+
*
|
159
|
+
* This will take precedence over adaptive stream dimensions.
|
160
|
+
*
|
161
|
+
* @param dimensions The video dimensions to set.
|
162
|
+
*/
|
147
163
|
setVideoDimensions(dimensions: Track.Dimensions) {
|
148
164
|
if (!this.isManualOperationAllowed()) {
|
149
165
|
return;
|
150
166
|
}
|
151
167
|
if (
|
152
|
-
this.
|
153
|
-
this.
|
168
|
+
this.requestedVideoDimensions?.width === dimensions.width &&
|
169
|
+
this.requestedVideoDimensions?.height === dimensions.height
|
154
170
|
) {
|
155
171
|
return;
|
156
172
|
}
|
157
173
|
if (isRemoteVideoTrack(this.track)) {
|
158
|
-
this.
|
174
|
+
this.requestedVideoDimensions = dimensions;
|
159
175
|
}
|
160
|
-
this.
|
176
|
+
this.requestedMaxQuality = undefined;
|
161
177
|
|
162
178
|
this.emitTrackUpdate();
|
163
179
|
}
|
@@ -180,7 +196,7 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
180
196
|
}
|
181
197
|
|
182
198
|
get videoQuality(): VideoQuality | undefined {
|
183
|
-
return this.
|
199
|
+
return this.requestedMaxQuality ?? VideoQuality.HIGH;
|
184
200
|
}
|
185
201
|
|
186
202
|
/** @internal */
|
@@ -260,13 +276,6 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
260
276
|
}
|
261
277
|
|
262
278
|
private isManualOperationAllowed(): boolean {
|
263
|
-
if (this.kind === Track.Kind.Video && this.isAdaptiveStream) {
|
264
|
-
this.log.warn(
|
265
|
-
'adaptive stream is enabled, cannot change video track settings',
|
266
|
-
this.logContext,
|
267
|
-
);
|
268
|
-
return false;
|
269
|
-
}
|
270
279
|
if (!this.isDesired) {
|
271
280
|
this.log.warn('cannot update track settings when not subscribed', this.logContext);
|
272
281
|
return false;
|
@@ -288,7 +297,7 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
288
297
|
`adaptivestream video visibility ${this.trackSid}, visible=${visible}`,
|
289
298
|
this.logContext,
|
290
299
|
);
|
291
|
-
this.
|
300
|
+
this.visible = visible;
|
292
301
|
this.emitTrackUpdate();
|
293
302
|
};
|
294
303
|
|
@@ -297,7 +306,7 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
297
306
|
`adaptivestream video dimensions ${dimensions.width}x${dimensions.height}`,
|
298
307
|
this.logContext,
|
299
308
|
);
|
300
|
-
this.
|
309
|
+
this.videoDimensionsAdaptiveStream = dimensions;
|
301
310
|
this.emitTrackUpdate();
|
302
311
|
};
|
303
312
|
|
@@ -305,17 +314,67 @@ export default class RemoteTrackPublication extends TrackPublication {
|
|
305
314
|
emitTrackUpdate() {
|
306
315
|
const settings: UpdateTrackSettings = new UpdateTrackSettings({
|
307
316
|
trackSids: [this.trackSid],
|
308
|
-
disabled: this.
|
317
|
+
disabled: !this.isEnabled,
|
309
318
|
fps: this.fps,
|
310
319
|
});
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
320
|
+
|
321
|
+
if (this.kind === Track.Kind.Video) {
|
322
|
+
let minDimensions = this.requestedVideoDimensions;
|
323
|
+
|
324
|
+
if (this.videoDimensionsAdaptiveStream !== undefined) {
|
325
|
+
if (minDimensions) {
|
326
|
+
// check whether the adaptive stream dimensions are smaller than the requested dimensions and use smaller one
|
327
|
+
const smallerAdaptive = areDimensionsSmaller(
|
328
|
+
this.videoDimensionsAdaptiveStream,
|
329
|
+
minDimensions,
|
330
|
+
);
|
331
|
+
if (smallerAdaptive) {
|
332
|
+
this.log.debug('using adaptive stream dimensions instead of requested', {
|
333
|
+
...this.logContext,
|
334
|
+
...this.videoDimensionsAdaptiveStream,
|
335
|
+
});
|
336
|
+
minDimensions = this.videoDimensionsAdaptiveStream;
|
337
|
+
}
|
338
|
+
} else if (this.requestedMaxQuality !== undefined && this.trackInfo) {
|
339
|
+
// check whether adaptive stream dimensions are smaller than the max quality layer and use smaller one
|
340
|
+
const maxQualityLayer = layerDimensionsFor(this.trackInfo, this.requestedMaxQuality);
|
341
|
+
|
342
|
+
if (
|
343
|
+
maxQualityLayer &&
|
344
|
+
areDimensionsSmaller(this.videoDimensionsAdaptiveStream, maxQualityLayer)
|
345
|
+
) {
|
346
|
+
this.log.debug('using adaptive stream dimensions instead of max quality layer', {
|
347
|
+
...this.logContext,
|
348
|
+
...this.videoDimensionsAdaptiveStream,
|
349
|
+
});
|
350
|
+
minDimensions = this.videoDimensionsAdaptiveStream;
|
351
|
+
}
|
352
|
+
} else {
|
353
|
+
this.log.debug('using adaptive stream dimensions', {
|
354
|
+
...this.logContext,
|
355
|
+
...this.videoDimensionsAdaptiveStream,
|
356
|
+
});
|
357
|
+
minDimensions = this.videoDimensionsAdaptiveStream;
|
358
|
+
}
|
359
|
+
}
|
360
|
+
|
361
|
+
if (minDimensions) {
|
362
|
+
settings.width = Math.ceil(minDimensions.width);
|
363
|
+
settings.height = Math.ceil(minDimensions.height);
|
364
|
+
} else if (this.requestedMaxQuality !== undefined) {
|
365
|
+
this.log.debug('using requested max quality', {
|
366
|
+
...this.logContext,
|
367
|
+
quality: this.requestedMaxQuality,
|
368
|
+
});
|
369
|
+
settings.quality = this.requestedMaxQuality;
|
370
|
+
} else {
|
371
|
+
this.log.debug('using default quality', {
|
372
|
+
...this.logContext,
|
373
|
+
quality: VideoQuality.HIGH,
|
374
|
+
});
|
375
|
+
// defaults to high quality
|
376
|
+
settings.quality = VideoQuality.HIGH;
|
377
|
+
}
|
319
378
|
}
|
320
379
|
|
321
380
|
this.emit(TrackEvent.UpdateSettings, settings);
|
package/src/room/track/Track.ts
CHANGED
@@ -529,4 +529,5 @@ export type TrackEventCallbacks = {
|
|
529
529
|
audioTrackFeatureUpdate: (track: any, feature: AudioTrackFeature, enabled: boolean) => void;
|
530
530
|
timeSyncUpdate: (update: { timestamp: number; rtpTimestamp: number }) => void;
|
531
531
|
preConnectBufferFlushed: (buffer: Uint8Array[]) => void;
|
532
|
+
cpuConstrained: () => void;
|
532
533
|
};
|
@@ -179,4 +179,5 @@ export type PublicationEventCallbacks = {
|
|
179
179
|
subscriptionFailed: (error: SubscriptionError) => void;
|
180
180
|
transcriptionReceived: (transcription: TranscriptionSegment[]) => void;
|
181
181
|
timeSyncUpdate: (timestamp: number) => void;
|
182
|
+
cpuConstrained: (track: LocalVideoTrack) => void;
|
182
183
|
};
|
package/src/room/track/create.ts
CHANGED
@@ -3,7 +3,7 @@ import { audioDefaults, videoDefaults } from '../defaults';
|
|
3
3
|
import { DeviceUnsupportedError, TrackInvalidError } from '../errors';
|
4
4
|
import { mediaTrackToLocalTrack } from '../participant/publishUtils';
|
5
5
|
import type { LoggerOptions } from '../types';
|
6
|
-
import { isAudioTrack,
|
6
|
+
import { isAudioTrack, isSafari17Based, isVideoTrack, unwrapConstraint } from '../utils';
|
7
7
|
import LocalAudioTrack from './LocalAudioTrack';
|
8
8
|
import type LocalTrack from './LocalTrack';
|
9
9
|
import LocalVideoTrack from './LocalVideoTrack';
|
@@ -198,7 +198,7 @@ export async function createLocalScreenTracks(
|
|
198
198
|
if (options === undefined) {
|
199
199
|
options = {};
|
200
200
|
}
|
201
|
-
if (options.resolution === undefined && !
|
201
|
+
if (options.resolution === undefined && !isSafari17Based()) {
|
202
202
|
options.resolution = ScreenSharePresets.h1080fps30.resolution;
|
203
203
|
}
|
204
204
|
|
@@ -173,6 +173,11 @@ export interface VideoCaptureOptions {
|
|
173
173
|
*/
|
174
174
|
deviceId?: ConstrainDOMString;
|
175
175
|
|
176
|
+
/**
|
177
|
+
* A ConstrainDouble specifying the frame rate or range of frame rates which are acceptable and/or required.
|
178
|
+
*/
|
179
|
+
frameRate?: ConstrainDouble;
|
180
|
+
|
176
181
|
/**
|
177
182
|
* a facing or an array of facings which are acceptable and/or required.
|
178
183
|
*/
|
@@ -387,7 +392,7 @@ export interface AudioPreset {
|
|
387
392
|
|
388
393
|
const backupCodecs = ['vp8', 'h264'] as const;
|
389
394
|
|
390
|
-
export const videoCodecs = ['vp8', 'h264', 'vp9', 'av1'] as const;
|
395
|
+
export const videoCodecs = ['vp8', 'h264', 'vp9', 'av1', 'h265'] as const;
|
391
396
|
|
392
397
|
export type VideoCodec = (typeof videoCodecs)[number];
|
393
398
|
|
package/src/room/track/utils.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { TrackPublishedResponse, TrackSource } from '@livekit/protocol';
|
1
|
+
import { TrackInfo, TrackPublishedResponse, TrackSource, VideoQuality } from '@livekit/protocol';
|
2
2
|
import type { AudioProcessorOptions, TrackProcessor, VideoProcessorOptions } from '../..';
|
3
3
|
import { cloneDeep } from '../../utils/cloneDeep';
|
4
4
|
import { isSafari, sleep } from '../utils';
|
@@ -329,3 +329,14 @@ export function getTrackSourceFromProto(source: TrackSource): Track.Source {
|
|
329
329
|
return Track.Source.Unknown;
|
330
330
|
}
|
331
331
|
}
|
332
|
+
|
333
|
+
export function areDimensionsSmaller(a: Track.Dimensions, b: Track.Dimensions): boolean {
|
334
|
+
return a.width * a.height < b.width * b.height;
|
335
|
+
}
|
336
|
+
|
337
|
+
export function layerDimensionsFor(
|
338
|
+
trackInfo: TrackInfo,
|
339
|
+
quality: VideoQuality,
|
340
|
+
): Track.Dimensions | undefined {
|
341
|
+
return trackInfo.layers?.find((l) => l.quality === quality);
|
342
|
+
}
|
package/src/room/utils.ts
CHANGED
@@ -96,6 +96,14 @@ export function supportsVP9(): boolean {
|
|
96
96
|
// Safari 16 and below does not support VP9
|
97
97
|
return false;
|
98
98
|
}
|
99
|
+
if (
|
100
|
+
browser?.os === 'iOS' &&
|
101
|
+
browser?.osVersion &&
|
102
|
+
compareVersions(browser.osVersion, '16') < 0
|
103
|
+
) {
|
104
|
+
// Safari 16 and below on iOS does not support VP9 we need the iOS check to account for other browsers running webkit under the hood
|
105
|
+
return false;
|
106
|
+
}
|
99
107
|
}
|
100
108
|
const capabilities = RTCRtpSender.getCapabilities('video');
|
101
109
|
let hasVP9 = false;
|
@@ -110,6 +118,24 @@ export function supportsVP9(): boolean {
|
|
110
118
|
return hasVP9;
|
111
119
|
}
|
112
120
|
|
121
|
+
export function supportsH265(): boolean {
|
122
|
+
if (!('getCapabilities' in RTCRtpSender)) {
|
123
|
+
return false;
|
124
|
+
}
|
125
|
+
|
126
|
+
const capabilities = RTCRtpSender.getCapabilities('video');
|
127
|
+
let hasH265 = false;
|
128
|
+
if (capabilities) {
|
129
|
+
for (const codec of capabilities.codecs) {
|
130
|
+
if (codec.mimeType === 'video/H265') {
|
131
|
+
hasH265 = true;
|
132
|
+
break;
|
133
|
+
}
|
134
|
+
}
|
135
|
+
}
|
136
|
+
return hasH265;
|
137
|
+
}
|
138
|
+
|
113
139
|
export function isSVCCodec(codec?: string): boolean {
|
114
140
|
return codec === 'av1' || codec === 'vp9';
|
115
141
|
}
|
@@ -148,9 +174,12 @@ export function isSafariBased(): boolean {
|
|
148
174
|
return b?.name === 'Safari' || b?.os === 'iOS';
|
149
175
|
}
|
150
176
|
|
151
|
-
export function
|
177
|
+
export function isSafari17Based(): boolean {
|
152
178
|
const b = getBrowser();
|
153
|
-
return
|
179
|
+
return (
|
180
|
+
(b?.name === 'Safari' && b.version.startsWith('17.')) ||
|
181
|
+
(b?.os === 'iOS' && !!b?.osVersion && compareVersions(b.osVersion, '17') >= 0)
|
182
|
+
);
|
154
183
|
}
|
155
184
|
|
156
185
|
export function isSafariSvcApi(browser?: BrowserDetails): boolean {
|
@@ -158,7 +187,12 @@ export function isSafariSvcApi(browser?: BrowserDetails): boolean {
|
|
158
187
|
browser = getBrowser();
|
159
188
|
}
|
160
189
|
// Safari 18.4 requires legacy svc api and scaleResolutionDown to be set
|
161
|
-
return
|
190
|
+
return (
|
191
|
+
(browser?.name === 'Safari' && compareVersions(browser.version, '18.3') > 0) ||
|
192
|
+
(browser?.os === 'iOS' &&
|
193
|
+
!!browser?.osVersion &&
|
194
|
+
compareVersions(browser.osVersion, '18.3') > 0)
|
195
|
+
);
|
162
196
|
}
|
163
197
|
|
164
198
|
export function isMobile(): boolean {
|