livekit-client 2.13.5 → 2.13.6
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 +11 -3
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +86 -76
- 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/api/SignalClient.d.ts +5 -5
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/publishVideo.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +1 -0
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +2 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +3 -2
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts +3 -3
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +4 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/ts4.2/src/api/SignalClient.d.ts +5 -5
- package/dist/ts4.2/src/e2ee/types.d.ts +1 -0
- package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +2 -1
- package/dist/ts4.2/src/room/PCTransport.d.ts +3 -2
- package/dist/ts4.2/src/room/PCTransportManager.d.ts +3 -3
- package/dist/ts4.2/src/room/RTCEngine.d.ts +4 -0
- package/package.json +8 -8
- package/src/api/SignalClient.ts +13 -14
- package/src/connectionHelper/checks/publishVideo.ts +1 -0
- package/src/e2ee/E2eeManager.ts +3 -0
- package/src/e2ee/types.ts +1 -0
- package/src/e2ee/worker/FrameCryptor.ts +15 -0
- package/src/e2ee/worker/e2ee.worker.ts +2 -0
- package/src/room/PCTransport.ts +30 -4
- package/src/room/PCTransportManager.ts +10 -7
- package/src/room/RTCEngine.ts +15 -7
- package/src/room/Room.ts +9 -9
- package/src/room/track/LocalVideoTrack.ts +14 -15
@@ -65,6 +65,7 @@ onmessage = (ev) => {
|
|
65
65
|
data.readableStream,
|
66
66
|
data.writableStream,
|
67
67
|
data.trackId,
|
68
|
+
data.isReuse,
|
68
69
|
data.codec,
|
69
70
|
);
|
70
71
|
break;
|
@@ -75,6 +76,7 @@ onmessage = (ev) => {
|
|
75
76
|
data.readableStream,
|
76
77
|
data.writableStream,
|
77
78
|
data.trackId,
|
79
|
+
data.isReuse,
|
78
80
|
data.codec,
|
79
81
|
);
|
80
82
|
break;
|
package/src/room/PCTransport.ts
CHANGED
@@ -50,6 +50,8 @@ export default class PCTransport extends EventEmitter {
|
|
50
50
|
|
51
51
|
private ddExtID = 0;
|
52
52
|
|
53
|
+
private latestOfferId: number = 0;
|
54
|
+
|
53
55
|
pendingCandidates: RTCIceCandidateInit[] = [];
|
54
56
|
|
55
57
|
restartingIce: boolean = false;
|
@@ -62,7 +64,7 @@ export default class PCTransport extends EventEmitter {
|
|
62
64
|
|
63
65
|
remoteNackMids: string[] = [];
|
64
66
|
|
65
|
-
onOffer?: (offer: RTCSessionDescriptionInit) => void;
|
67
|
+
onOffer?: (offer: RTCSessionDescriptionInit, offerId: number) => void;
|
66
68
|
|
67
69
|
onIceCandidate?: (candidate: RTCIceCandidate) => void;
|
68
70
|
|
@@ -137,7 +139,20 @@ export default class PCTransport extends EventEmitter {
|
|
137
139
|
this.pendingCandidates.push(candidate);
|
138
140
|
}
|
139
141
|
|
140
|
-
async setRemoteDescription(sd: RTCSessionDescriptionInit): Promise<
|
142
|
+
async setRemoteDescription(sd: RTCSessionDescriptionInit, offerId: number): Promise<boolean> {
|
143
|
+
if (
|
144
|
+
sd.type === 'answer' &&
|
145
|
+
this.latestOfferId > 0 &&
|
146
|
+
offerId > 0 &&
|
147
|
+
offerId !== this.latestOfferId
|
148
|
+
) {
|
149
|
+
this.log.warn('ignoring answer for old offer', {
|
150
|
+
...this.logContext,
|
151
|
+
offerId,
|
152
|
+
latestOfferId: this.latestOfferId,
|
153
|
+
});
|
154
|
+
return false;
|
155
|
+
}
|
141
156
|
let mungedSDP: string | undefined = undefined;
|
142
157
|
if (sd.type === 'offer') {
|
143
158
|
let { stereoMids, nackMids } = extractStereoAndNackAudioFromOffer(sd);
|
@@ -218,6 +233,7 @@ export default class PCTransport extends EventEmitter {
|
|
218
233
|
});
|
219
234
|
}
|
220
235
|
}
|
236
|
+
return true;
|
221
237
|
}
|
222
238
|
|
223
239
|
// debounced negotiate interface
|
@@ -235,6 +251,9 @@ export default class PCTransport extends EventEmitter {
|
|
235
251
|
}, debounceInterval);
|
236
252
|
|
237
253
|
async createAndSendOffer(options?: RTCOfferOptions) {
|
254
|
+
// increase the offer id at the start to ensure the offer is always > 0 so that we can use 0 as a default value for legacy behavior
|
255
|
+
const offerId = this.latestOfferId + 1;
|
256
|
+
this.latestOfferId = offerId;
|
238
257
|
if (this.onOffer === undefined) {
|
239
258
|
return;
|
240
259
|
}
|
@@ -317,9 +336,16 @@ export default class PCTransport extends EventEmitter {
|
|
317
336
|
});
|
318
337
|
}
|
319
338
|
});
|
320
|
-
|
339
|
+
if (this.latestOfferId > offerId) {
|
340
|
+
this.log.warn('latestOfferId mismatch', {
|
341
|
+
...this.logContext,
|
342
|
+
latestOfferId: this.latestOfferId,
|
343
|
+
offerId,
|
344
|
+
});
|
345
|
+
return;
|
346
|
+
}
|
321
347
|
await this.setMungedSDP(offer, write(sdpParsed));
|
322
|
-
this.onOffer(offer);
|
348
|
+
this.onOffer(offer, this.latestOfferId);
|
323
349
|
}
|
324
350
|
|
325
351
|
async createAndSetAnswer(): Promise<RTCSessionDescriptionInit> {
|
@@ -48,7 +48,7 @@ export class PCTransportManager {
|
|
48
48
|
|
49
49
|
public onTrack?: (ev: RTCTrackEvent) => void;
|
50
50
|
|
51
|
-
public onPublisherOffer?: (offer: RTCSessionDescriptionInit) => void;
|
51
|
+
public onPublisherOffer?: (offer: RTCSessionDescriptionInit, offerId: number) => void;
|
52
52
|
|
53
53
|
private isPublisherConnectionRequired: boolean;
|
54
54
|
|
@@ -96,8 +96,8 @@ export class PCTransportManager {
|
|
96
96
|
this.subscriber.onTrack = (ev) => {
|
97
97
|
this.onTrack?.(ev);
|
98
98
|
};
|
99
|
-
this.publisher.onOffer = (offer) => {
|
100
|
-
this.onPublisherOffer?.(offer);
|
99
|
+
this.publisher.onOffer = (offer, offerId) => {
|
100
|
+
this.onPublisherOffer?.(offer, offerId);
|
101
101
|
};
|
102
102
|
|
103
103
|
this.state = PCTransportState.NEW;
|
@@ -126,8 +126,8 @@ export class PCTransportManager {
|
|
126
126
|
return this.publisher.createAndSendOffer(options);
|
127
127
|
}
|
128
128
|
|
129
|
-
setPublisherAnswer(sd: RTCSessionDescriptionInit) {
|
130
|
-
return this.publisher.setRemoteDescription(sd);
|
129
|
+
setPublisherAnswer(sd: RTCSessionDescriptionInit, offerId: number) {
|
130
|
+
return this.publisher.setRemoteDescription(sd, offerId);
|
131
131
|
}
|
132
132
|
|
133
133
|
removeTrack(sender: RTCRtpSender) {
|
@@ -168,7 +168,7 @@ export class PCTransportManager {
|
|
168
168
|
}
|
169
169
|
}
|
170
170
|
|
171
|
-
async createSubscriberAnswerFromOffer(sd: RTCSessionDescriptionInit) {
|
171
|
+
async createSubscriberAnswerFromOffer(sd: RTCSessionDescriptionInit, offerId: number) {
|
172
172
|
this.log.debug('received server offer', {
|
173
173
|
...this.logContext,
|
174
174
|
RTCSdpType: sd.type,
|
@@ -177,7 +177,10 @@ export class PCTransportManager {
|
|
177
177
|
});
|
178
178
|
const unlock = await this.remoteOfferLock.lock();
|
179
179
|
try {
|
180
|
-
await this.subscriber.setRemoteDescription(sd);
|
180
|
+
const success = await this.subscriber.setRemoteDescription(sd, offerId);
|
181
|
+
if (!success) {
|
182
|
+
return undefined;
|
183
|
+
}
|
181
184
|
|
182
185
|
// answer the offer
|
183
186
|
const answer = await this.subscriber.createAndSetAnswer();
|
package/src/room/RTCEngine.ts
CHANGED
@@ -111,6 +111,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
111
111
|
*/
|
112
112
|
latestJoinResponse?: JoinResponse;
|
113
113
|
|
114
|
+
/**
|
115
|
+
* @internal
|
116
|
+
*/
|
117
|
+
latestRemoteOfferId: number = 0;
|
118
|
+
|
114
119
|
get isClosed() {
|
115
120
|
return this._isClosed;
|
116
121
|
}
|
@@ -420,8 +425,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
420
425
|
this.client.sendIceCandidate(candidate, target);
|
421
426
|
};
|
422
427
|
|
423
|
-
this.pcManager.onPublisherOffer = (offer) => {
|
424
|
-
this.client.sendOffer(offer);
|
428
|
+
this.pcManager.onPublisherOffer = (offer, offerId) => {
|
429
|
+
this.client.sendOffer(offer, offerId);
|
425
430
|
};
|
426
431
|
|
427
432
|
this.pcManager.onDataChannel = this.handleDataChannel;
|
@@ -476,12 +481,12 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
476
481
|
|
477
482
|
private setupSignalClientCallbacks() {
|
478
483
|
// configure signaling client
|
479
|
-
this.client.onAnswer = async (sd) => {
|
484
|
+
this.client.onAnswer = async (sd, offerId) => {
|
480
485
|
if (!this.pcManager) {
|
481
486
|
return;
|
482
487
|
}
|
483
488
|
this.log.debug('received server answer', { ...this.logContext, RTCSdpType: sd.type });
|
484
|
-
await this.pcManager.setPublisherAnswer(sd);
|
489
|
+
await this.pcManager.setPublisherAnswer(sd, offerId);
|
485
490
|
};
|
486
491
|
|
487
492
|
// add candidate on trickle
|
@@ -494,12 +499,15 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
494
499
|
};
|
495
500
|
|
496
501
|
// when server creates an offer for the client
|
497
|
-
this.client.onOffer = async (sd) => {
|
502
|
+
this.client.onOffer = async (sd, offerId) => {
|
503
|
+
this.latestRemoteOfferId = offerId;
|
498
504
|
if (!this.pcManager) {
|
499
505
|
return;
|
500
506
|
}
|
501
|
-
const answer = await this.pcManager.createSubscriberAnswerFromOffer(sd);
|
502
|
-
|
507
|
+
const answer = await this.pcManager.createSubscriberAnswerFromOffer(sd, offerId);
|
508
|
+
if (answer) {
|
509
|
+
this.client.sendAnswer(answer, offerId);
|
510
|
+
}
|
503
511
|
};
|
504
512
|
|
505
513
|
this.client.onLocalTrackPublished = (res: TrackPublishedResponse) => {
|
package/src/room/Room.ts
CHANGED
@@ -1301,10 +1301,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1301
1301
|
*/
|
1302
1302
|
async switchActiveDevice(kind: MediaDeviceKind, deviceId: string, exact: boolean = true) {
|
1303
1303
|
let success = true;
|
1304
|
-
let
|
1304
|
+
let shouldTriggerImmediateDeviceChange = false;
|
1305
1305
|
const deviceConstraint = exact ? { exact: deviceId } : deviceId;
|
1306
1306
|
if (kind === 'audioinput') {
|
1307
|
-
|
1307
|
+
shouldTriggerImmediateDeviceChange = this.localParticipant.audioTrackPublications.size === 0;
|
1308
1308
|
const prevDeviceId =
|
1309
1309
|
this.getActiveDevice(kind) ?? this.options.audioCaptureDefaults!.deviceId;
|
1310
1310
|
this.options.audioCaptureDefaults!.deviceId = deviceConstraint;
|
@@ -1319,8 +1319,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1319
1319
|
this.options.audioCaptureDefaults!.deviceId = prevDeviceId;
|
1320
1320
|
throw e;
|
1321
1321
|
}
|
1322
|
+
const isMuted = tracks.some((t) => t.track?.isMuted ?? false);
|
1323
|
+
if (success && isMuted) shouldTriggerImmediateDeviceChange = true;
|
1322
1324
|
} else if (kind === 'videoinput') {
|
1323
|
-
|
1325
|
+
shouldTriggerImmediateDeviceChange = this.localParticipant.videoTrackPublications.size === 0;
|
1324
1326
|
const prevDeviceId =
|
1325
1327
|
this.getActiveDevice(kind) ?? this.options.videoCaptureDefaults!.deviceId;
|
1326
1328
|
this.options.videoCaptureDefaults!.deviceId = deviceConstraint;
|
@@ -1336,6 +1338,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1336
1338
|
throw e;
|
1337
1339
|
}
|
1338
1340
|
} else if (kind === 'audiooutput') {
|
1341
|
+
shouldTriggerImmediateDeviceChange = true;
|
1339
1342
|
if (
|
1340
1343
|
(!supportsSetSinkId() && !this.options.webAudioMix) ||
|
1341
1344
|
(this.options.webAudioMix && this.audioContext && !('setSinkId' in this.audioContext))
|
@@ -1367,12 +1370,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1367
1370
|
throw e;
|
1368
1371
|
}
|
1369
1372
|
}
|
1370
|
-
|
1371
|
-
|
1372
|
-
this.localParticipant.activeDeviceMap.set(
|
1373
|
-
kind,
|
1374
|
-
(kind === 'audiooutput' && this.options.audioOutput?.deviceId) || deviceId,
|
1375
|
-
);
|
1373
|
+
|
1374
|
+
if (shouldTriggerImmediateDeviceChange) {
|
1375
|
+
this.localParticipant.activeDeviceMap.set(kind, deviceId);
|
1376
1376
|
this.emit(RoomEvent.ActiveDeviceChanged, kind, deviceId);
|
1377
1377
|
}
|
1378
1378
|
|
@@ -7,12 +7,11 @@ import {
|
|
7
7
|
} from '@livekit/protocol';
|
8
8
|
import type { SignalClient } from '../../api/SignalClient';
|
9
9
|
import type { StructuredLogger } from '../../logger';
|
10
|
-
import { getBrowser } from '../../utils/browserParser';
|
11
10
|
import { ScalabilityMode } from '../participant/publishUtils';
|
12
11
|
import type { VideoSenderStats } from '../stats';
|
13
12
|
import { computeBitrate, monitorFrequency } from '../stats';
|
14
13
|
import type { LoggerOptions } from '../types';
|
15
|
-
import {
|
14
|
+
import { isFireFox, isMobile, isSVCCodec, isWeb } from '../utils';
|
16
15
|
import LocalTrack from './LocalTrack';
|
17
16
|
import { Track, VideoQuality } from './Track';
|
18
17
|
import type { VideoCaptureOptions, VideoCodec } from './options';
|
@@ -459,15 +458,18 @@ async function setPublishingLayersForSender(
|
|
459
458
|
}
|
460
459
|
|
461
460
|
let hasChanged = false;
|
462
|
-
|
463
|
-
|
464
|
-
|
461
|
+
|
462
|
+
/* disable closable spatial layer as it has video blur / frozen issue with current server / client
|
463
|
+
1. chrome 113: when switching to up layer with scalability Mode change, it will generate a
|
464
|
+
low resolution frame and recover very quickly, but noticable
|
465
|
+
2. livekit sfu: additional pli request cause video frozen for a few frames, also noticable */
|
466
|
+
const closableSpatial = false;
|
465
467
|
/* @ts-ignore */
|
466
468
|
if (closableSpatial && encodings[0].scalabilityMode) {
|
467
469
|
// svc dynacast encodings
|
468
470
|
const encoding = encodings[0];
|
469
471
|
/* @ts-ignore */
|
470
|
-
const mode = new ScalabilityMode(encoding.scalabilityMode);
|
472
|
+
// const mode = new ScalabilityMode(encoding.scalabilityMode);
|
471
473
|
let maxQuality = ProtoVideoQuality.OFF;
|
472
474
|
qualities.forEach((q) => {
|
473
475
|
if (q.enabled && (maxQuality === ProtoVideoQuality.OFF || q.quality > maxQuality)) {
|
@@ -480,25 +482,22 @@ async function setPublishingLayersForSender(
|
|
480
482
|
encoding.active = false;
|
481
483
|
hasChanged = true;
|
482
484
|
}
|
483
|
-
} else if (!encoding.active || mode.spatial !== maxQuality + 1) {
|
485
|
+
} else if (!encoding.active /* || mode.spatial !== maxQuality + 1*/) {
|
484
486
|
hasChanged = true;
|
485
487
|
encoding.active = true;
|
486
|
-
/*
|
487
|
-
|
488
|
+
/*
|
489
|
+
@ts-ignore
|
490
|
+
const originalMode = new ScalabilityMode(senderEncodings[0].scalabilityMode)
|
488
491
|
mode.spatial = maxQuality + 1;
|
489
492
|
mode.suffix = originalMode.suffix;
|
490
493
|
if (mode.spatial === 1) {
|
491
494
|
// no suffix for L1Tx
|
492
495
|
mode.suffix = undefined;
|
493
496
|
}
|
494
|
-
|
497
|
+
@ts-ignore
|
495
498
|
encoding.scalabilityMode = mode.toString();
|
496
499
|
encoding.scaleResolutionDownBy = 2 ** (2 - maxQuality);
|
497
|
-
|
498
|
-
encoding.maxBitrate =
|
499
|
-
senderEncodings[0].maxBitrate /
|
500
|
-
(encoding.scaleResolutionDownBy * encoding.scaleResolutionDownBy);
|
501
|
-
}
|
500
|
+
*/
|
502
501
|
}
|
503
502
|
} else {
|
504
503
|
if (isSVC) {
|