livekit-client 2.0.2 → 2.0.4
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 +53 -17
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +158 -65
- 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.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/KeyProvider.d.ts +1 -1
- package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +2 -0
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +1 -0
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +2 -2
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/logger.d.ts +2 -0
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/room/DeviceManager.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/Room.d.ts +1 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +7 -2
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +4 -1
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +2 -0
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +10 -0
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/types.d.ts +4 -0
- package/dist/src/room/track/types.d.ts.map +1 -1
- package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +1 -1
- package/dist/ts4.2/src/e2ee/types.d.ts +2 -0
- package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +1 -0
- package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +2 -2
- package/dist/ts4.2/src/index.d.ts +2 -2
- package/dist/ts4.2/src/logger.d.ts +2 -0
- package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -0
- package/dist/ts4.2/src/room/Room.d.ts +1 -0
- package/dist/ts4.2/src/room/events.d.ts +7 -2
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +4 -1
- package/dist/ts4.2/src/room/track/Track.d.ts +2 -0
- package/dist/ts4.2/src/room/track/options.d.ts +10 -0
- package/dist/ts4.2/src/room/track/types.d.ts +4 -0
- package/package.json +1 -1
- package/src/api/SignalClient.ts +1 -0
- package/src/e2ee/E2eeManager.ts +2 -1
- package/src/e2ee/KeyProvider.ts +6 -1
- package/src/e2ee/types.ts +2 -0
- package/src/e2ee/worker/FrameCryptor.ts +26 -0
- package/src/e2ee/worker/ParticipantKeyHandler.ts +9 -5
- package/src/e2ee/worker/e2ee.worker.ts +17 -17
- package/src/index.ts +2 -1
- package/src/logger.ts +2 -0
- package/src/room/DeviceManager.ts +10 -1
- package/src/room/RTCEngine.ts +14 -0
- package/src/room/Room.ts +29 -4
- package/src/room/events.ts +5 -0
- package/src/room/participant/LocalParticipant.ts +4 -4
- package/src/room/track/LocalAudioTrack.ts +11 -0
- package/src/room/track/LocalTrack.ts +62 -36
- package/src/room/track/LocalVideoTrack.ts +10 -0
- package/src/room/track/Track.ts +2 -0
- package/src/room/track/options.ts +41 -8
- package/src/room/track/types.ts +5 -0
@@ -3,6 +3,7 @@ import { Mutex } from '../utils';
|
|
3
3
|
import { Track } from './Track';
|
4
4
|
import type { VideoCodec } from './options';
|
5
5
|
import type { TrackProcessor } from './processor/types';
|
6
|
+
import type { ReplaceTrackOptions } from './types';
|
6
7
|
export default abstract class LocalTrack<TrackKind extends Track.Kind = Track.Kind> extends Track<TrackKind> {
|
7
8
|
/** @internal */
|
8
9
|
sender?: RTCRtpSender;
|
@@ -18,6 +19,7 @@ export default abstract class LocalTrack<TrackKind extends Track.Kind = Track.Ki
|
|
18
19
|
protected processor?: TrackProcessor<TrackKind, any>;
|
19
20
|
protected processorLock: Mutex;
|
20
21
|
protected audioContext?: AudioContext;
|
22
|
+
private restartLock;
|
21
23
|
/**
|
22
24
|
*
|
23
25
|
* @param mediaTrack
|
@@ -40,7 +42,8 @@ export default abstract class LocalTrack<TrackKind extends Track.Kind = Track.Ki
|
|
40
42
|
getDeviceId(): Promise<string | undefined>;
|
41
43
|
mute(): Promise<this>;
|
42
44
|
unmute(): Promise<this>;
|
43
|
-
replaceTrack(track: MediaStreamTrack,
|
45
|
+
replaceTrack(track: MediaStreamTrack, options?: ReplaceTrackOptions): Promise<typeof this>;
|
46
|
+
replaceTrack(track: MediaStreamTrack, userProvidedTrack?: boolean): Promise<typeof this>;
|
44
47
|
protected restart(constraints?: MediaTrackConstraints): Promise<this>;
|
45
48
|
protected setTrackMuted(muted: boolean): void;
|
46
49
|
protected get needsReAcquisition(): boolean;
|
@@ -4,6 +4,7 @@ import { StructuredLogger } from '../../logger';
|
|
4
4
|
import { TrackSource, TrackType } from '../../proto/livekit_models_pb';
|
5
5
|
import { StreamState as ProtoStreamState } from '../../proto/livekit_rtc_pb';
|
6
6
|
import type { LoggerOptions } from '../types';
|
7
|
+
import type { TrackProcessor } from './processor/types';
|
7
8
|
export declare enum VideoQuality {
|
8
9
|
LOW = 0,
|
9
10
|
MEDIUM = 1,
|
@@ -135,6 +136,7 @@ export type TrackEventCallbacks = {
|
|
135
136
|
elementDetached: (element: HTMLMediaElement) => void;
|
136
137
|
upstreamPaused: (track: any) => void;
|
137
138
|
upstreamResumed: (track: any) => void;
|
139
|
+
trackProcessorUpdate: (processor?: TrackProcessor<Track.Kind, any>) => void;
|
138
140
|
};
|
139
141
|
export {};
|
140
142
|
//# sourceMappingURL=Track.d.ts.map
|
@@ -220,10 +220,20 @@ export interface VideoEncoding {
|
|
220
220
|
maxFramerate?: number;
|
221
221
|
priority?: RTCPriorityType;
|
222
222
|
}
|
223
|
+
export interface VideoPresetOptions {
|
224
|
+
width: number;
|
225
|
+
height: number;
|
226
|
+
aspectRatio?: number;
|
227
|
+
maxBitrate: number;
|
228
|
+
maxFramerate?: number;
|
229
|
+
priority?: RTCPriorityType;
|
230
|
+
}
|
223
231
|
export declare class VideoPreset {
|
224
232
|
encoding: VideoEncoding;
|
225
233
|
width: number;
|
226
234
|
height: number;
|
235
|
+
aspectRatio?: number;
|
236
|
+
constructor(videoPresetOptions: VideoPresetOptions);
|
227
237
|
constructor(width: number, height: number, maxBitrate: number, maxFramerate?: number, priority?: RTCPriorityType);
|
228
238
|
get resolution(): VideoResolution;
|
229
239
|
}
|
package/package.json
CHANGED
package/src/api/SignalClient.ts
CHANGED
@@ -283,6 +283,7 @@ export class SignalClient {
|
|
283
283
|
|
284
284
|
this.ws.onerror = async (ev: Event) => {
|
285
285
|
if (this.state !== SignalConnectionState.CONNECTED) {
|
286
|
+
this.state = SignalConnectionState.DISCONNECTED;
|
286
287
|
clearTimeout(wsTimeout);
|
287
288
|
try {
|
288
289
|
const resp = await fetch(`http${url.substring(2)}/validate${params}`);
|
package/src/e2ee/E2eeManager.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { EventEmitter } from 'events';
|
2
2
|
import type TypedEventEmitter from 'typed-emitter';
|
3
|
-
import log from '../logger';
|
3
|
+
import log, { LogLevel, workerLogger } from '../logger';
|
4
4
|
import { Encryption_Type, TrackInfo } from '../proto/livekit_models_pb';
|
5
5
|
import type RTCEngine from '../room/RTCEngine';
|
6
6
|
import type Room from '../room/Room';
|
@@ -68,6 +68,7 @@ export class E2EEManager extends (EventEmitter as new () => TypedEventEmitter<E2
|
|
68
68
|
kind: 'init',
|
69
69
|
data: {
|
70
70
|
keyProviderOptions: this.keyProvider.getOptions(),
|
71
|
+
loglevel: workerLogger.getLevel() as LogLevel,
|
71
72
|
},
|
72
73
|
};
|
73
74
|
if (this.worker) {
|
package/src/e2ee/KeyProvider.ts
CHANGED
@@ -12,7 +12,7 @@ import { createKeyMaterialFromBuffer, createKeyMaterialFromString } from './util
|
|
12
12
|
export class BaseKeyProvider extends (EventEmitter as new () => TypedEventEmitter<KeyProviderCallbacks>) {
|
13
13
|
private keyInfoMap: Map<string, KeyInfo>;
|
14
14
|
|
15
|
-
private options: KeyProviderOptions;
|
15
|
+
private readonly options: KeyProviderOptions;
|
16
16
|
|
17
17
|
constructor(options: Partial<KeyProviderOptions> = {}) {
|
18
18
|
super();
|
@@ -29,6 +29,11 @@ export class BaseKeyProvider extends (EventEmitter as new () => TypedEventEmitte
|
|
29
29
|
*/
|
30
30
|
protected onSetEncryptionKey(key: CryptoKey, participantIdentity?: string, keyIndex?: number) {
|
31
31
|
const keyInfo: KeyInfo = { key, participantIdentity, keyIndex };
|
32
|
+
if (!this.options.sharedKey && !participantIdentity) {
|
33
|
+
throw new Error(
|
34
|
+
'participant identity needs to be passed for encryption key if sharedKey option is false',
|
35
|
+
);
|
36
|
+
}
|
32
37
|
this.keyInfoMap.set(`${participantIdentity ?? 'shared'}-${keyIndex ?? 0}`, keyInfo);
|
33
38
|
this.emit(KeyProviderEvent.SetKey, keyInfo);
|
34
39
|
}
|
package/src/e2ee/types.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import type { LogLevel } from '../logger';
|
1
2
|
import type { VideoCodec } from '../room/track/options';
|
2
3
|
import type { BaseKeyProvider } from './KeyProvider';
|
3
4
|
|
@@ -10,6 +11,7 @@ export interface InitMessage extends BaseMessage {
|
|
10
11
|
kind: 'init';
|
11
12
|
data: {
|
12
13
|
keyProviderOptions: KeyProviderOptions;
|
14
|
+
loglevel: LogLevel;
|
13
15
|
};
|
14
16
|
}
|
15
17
|
|
@@ -83,6 +83,14 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
83
83
|
this.sifGuard = new SifGuard();
|
84
84
|
}
|
85
85
|
|
86
|
+
private get logContext() {
|
87
|
+
return {
|
88
|
+
identity: this.participantIdentity,
|
89
|
+
trackId: this.trackId,
|
90
|
+
fallbackCodec: this.videoCodec,
|
91
|
+
};
|
92
|
+
}
|
93
|
+
|
86
94
|
/**
|
87
95
|
* Assign a different participant to the cryptor.
|
88
96
|
* useful for transceiver re-use
|
@@ -96,6 +104,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
96
104
|
}
|
97
105
|
|
98
106
|
unsetParticipant() {
|
107
|
+
workerLogger.debug('unsetting participant', this.logContext);
|
99
108
|
this.participantIdentity = undefined;
|
100
109
|
}
|
101
110
|
|
@@ -143,6 +152,13 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
143
152
|
this.videoCodec = codec;
|
144
153
|
}
|
145
154
|
|
155
|
+
workerLogger.debug('Setting up frame cryptor transform', {
|
156
|
+
operation,
|
157
|
+
passedTrackId: trackId,
|
158
|
+
codec,
|
159
|
+
...this.logContext,
|
160
|
+
});
|
161
|
+
|
146
162
|
const transformFn = operation === 'encode' ? this.encodeFunction : this.decodeFunction;
|
147
163
|
const transformStream = new TransformStream({
|
148
164
|
transform: transformFn.bind(this),
|
@@ -159,6 +175,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
159
175
|
}
|
160
176
|
|
161
177
|
setSifTrailer(trailer: Uint8Array) {
|
178
|
+
workerLogger.debug('setting SIF trailer', { ...this.logContext, trailer });
|
162
179
|
this.sifTrailer = trailer;
|
163
180
|
}
|
164
181
|
|
@@ -212,6 +229,8 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
212
229
|
encodedFrame.timestamp,
|
213
230
|
);
|
214
231
|
let frameInfo = this.getUnencryptedBytes(encodedFrame);
|
232
|
+
workerLogger.debug('frameInfo for encoded frame', { ...frameInfo, ...this.logContext });
|
233
|
+
|
215
234
|
// Thіs is not encrypted and contains the VP8 payload descriptor or the Opus TOC byte.
|
216
235
|
const frameHeader = new Uint8Array(encodedFrame.data, 0, frameInfo.unencryptedBytes);
|
217
236
|
|
@@ -262,6 +281,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
262
281
|
workerLogger.error(e);
|
263
282
|
}
|
264
283
|
} else {
|
284
|
+
workerLogger.debug('failed to decrypt, emitting error', this.logContext);
|
265
285
|
this.emit(
|
266
286
|
CryptorEvent.Error,
|
267
287
|
new CryptorError(`encryption key missing for encoding`, CryptorErrorReason.MissingKey),
|
@@ -284,11 +304,13 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
284
304
|
// skip for decryption for empty dtx frames
|
285
305
|
encodedFrame.data.byteLength === 0
|
286
306
|
) {
|
307
|
+
workerLogger.debug('skipping empty frame', this.logContext);
|
287
308
|
this.sifGuard.recordUserFrame();
|
288
309
|
return controller.enqueue(encodedFrame);
|
289
310
|
}
|
290
311
|
|
291
312
|
if (isFrameServerInjected(encodedFrame.data, this.sifTrailer)) {
|
313
|
+
workerLogger.debug('enqueue SIF', this.logContext);
|
292
314
|
this.sifGuard.recordSif();
|
293
315
|
|
294
316
|
if (this.sifGuard.isSifAllowed()) {
|
@@ -312,6 +334,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
312
334
|
const decodedFrame = await this.decryptFrame(encodedFrame, keyIndex);
|
313
335
|
this.keys.decryptionSuccess();
|
314
336
|
if (decodedFrame) {
|
337
|
+
workerLogger.debug('enqueue decrypted frame', this.logContext);
|
315
338
|
return controller.enqueue(decodedFrame);
|
316
339
|
}
|
317
340
|
} catch (error) {
|
@@ -352,6 +375,8 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
352
375
|
throw new TypeError(`no encryption key found for decryption of ${this.participantIdentity}`);
|
353
376
|
}
|
354
377
|
let frameInfo = this.getUnencryptedBytes(encodedFrame);
|
378
|
+
workerLogger.debug('frameInfo for decoded frame', { ...frameInfo, ...this.logContext });
|
379
|
+
|
355
380
|
// Construct frame trailer. Similar to the frame header described in
|
356
381
|
// https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
|
357
382
|
// but we put it at the end.
|
@@ -566,6 +591,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
566
591
|
// @ts-expect-error payloadType is not yet part of the typescript definition and currently not supported in Safari
|
567
592
|
const payloadType = frame.getMetadata().payloadType;
|
568
593
|
const codec = payloadType ? this.rtpMap.get(payloadType) : undefined;
|
594
|
+
workerLogger.debug('reading codec from frame', { codec, ...this.logContext });
|
569
595
|
return codec;
|
570
596
|
}
|
571
597
|
}
|
@@ -133,15 +133,19 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
|
|
133
133
|
|
134
134
|
/**
|
135
135
|
* takes in a key material with `deriveBits` and `deriveKey` set as key usages
|
136
|
-
* and derives encryption keys from the material and sets it on the key ring
|
136
|
+
* and derives encryption keys from the material and sets it on the key ring buffers
|
137
137
|
* together with the material
|
138
138
|
* also updates the currentKeyIndex
|
139
139
|
*/
|
140
|
-
async setKeyFromMaterial(material: CryptoKey, keyIndex
|
141
|
-
const newIndex = keyIndex >= 0 ? keyIndex % this.cryptoKeyRing.length : -1;
|
142
|
-
workerLogger.debug(`setting new key with index ${newIndex}`);
|
140
|
+
async setKeyFromMaterial(material: CryptoKey, keyIndex: number, emitRatchetEvent = false) {
|
143
141
|
const keySet = await deriveKeys(material, this.keyProviderOptions.ratchetSalt);
|
144
|
-
|
142
|
+
const newIndex = keyIndex >= 0 ? keyIndex % this.cryptoKeyRing.length : this.currentKeyIndex;
|
143
|
+
workerLogger.debug(`setting new key with index ${keyIndex}`, {
|
144
|
+
usage: material.usages,
|
145
|
+
algorithm: material.algorithm,
|
146
|
+
ratchetSalt: this.keyProviderOptions.ratchetSalt,
|
147
|
+
});
|
148
|
+
this.setKeySet(keySet, newIndex, emitRatchetEvent);
|
145
149
|
if (newIndex >= 0) this.currentKeyIndex = newIndex;
|
146
150
|
}
|
147
151
|
|
@@ -21,8 +21,6 @@ let isEncryptionEnabled: boolean = false;
|
|
21
21
|
|
22
22
|
let useSharedKey: boolean = false;
|
23
23
|
|
24
|
-
let sharedKey: CryptoKey | undefined;
|
25
|
-
|
26
24
|
let sifTrailer: Uint8Array | undefined;
|
27
25
|
|
28
26
|
let keyProviderOptions: KeyProviderOptions = KEY_PROVIDER_DEFAULTS;
|
@@ -34,6 +32,7 @@ onmessage = (ev) => {
|
|
34
32
|
|
35
33
|
switch (kind) {
|
36
34
|
case 'init':
|
35
|
+
workerLogger.setLevel(data.loglevel);
|
37
36
|
workerLogger.info('worker initialized');
|
38
37
|
keyProviderOptions = data.keyProviderOptions;
|
39
38
|
useSharedKey = !!data.keyProviderOptions.sharedKey;
|
@@ -72,10 +71,9 @@ onmessage = (ev) => {
|
|
72
71
|
break;
|
73
72
|
case 'setKey':
|
74
73
|
if (useSharedKey) {
|
75
|
-
workerLogger.warn('set shared key');
|
76
74
|
setSharedKey(data.key, data.keyIndex);
|
77
75
|
} else if (data.participantIdentity) {
|
78
|
-
workerLogger.
|
76
|
+
workerLogger.info(
|
79
77
|
`set participant sender key ${data.participantIdentity} index ${data.keyIndex}`,
|
80
78
|
);
|
81
79
|
getParticipantKeyHandler(data.participantIdentity).setKey(data.key, data.keyIndex);
|
@@ -125,9 +123,7 @@ async function handleRatchetRequest(data: RatchetRequestMessage['data']) {
|
|
125
123
|
}
|
126
124
|
|
127
125
|
function getTrackCryptor(participantIdentity: string, trackId: string) {
|
128
|
-
let cryptor = participantCryptors.find(
|
129
|
-
(c) => c.getParticipantIdentity() === participantIdentity && c.getTrackId() === trackId,
|
130
|
-
);
|
126
|
+
let cryptor = participantCryptors.find((c) => c.getTrackId() === trackId);
|
131
127
|
if (!cryptor) {
|
132
128
|
workerLogger.info('creating new cryptor for', { participantIdentity });
|
133
129
|
if (!keyProviderOptions) {
|
@@ -146,8 +142,7 @@ function getTrackCryptor(participantIdentity: string, trackId: string) {
|
|
146
142
|
// assign new participant id to track cryptor and pass in correct key handler
|
147
143
|
cryptor.setParticipant(participantIdentity, getParticipantKeyHandler(participantIdentity));
|
148
144
|
}
|
149
|
-
|
150
|
-
}
|
145
|
+
|
151
146
|
return cryptor;
|
152
147
|
}
|
153
148
|
|
@@ -158,9 +153,6 @@ function getParticipantKeyHandler(participantIdentity: string) {
|
|
158
153
|
let keys = participantKeys.get(participantIdentity);
|
159
154
|
if (!keys) {
|
160
155
|
keys = new ParticipantKeyHandler(participantIdentity, keyProviderOptions);
|
161
|
-
if (sharedKey) {
|
162
|
-
keys.setKey(sharedKey);
|
163
|
-
}
|
164
156
|
keys.on(KeyHandlerEvent.KeyRatcheted, emitRatchetedKeys);
|
165
157
|
participantKeys.set(participantIdentity, keys);
|
166
158
|
}
|
@@ -169,24 +161,32 @@ function getParticipantKeyHandler(participantIdentity: string) {
|
|
169
161
|
|
170
162
|
function getSharedKeyHandler() {
|
171
163
|
if (!sharedKeyHandler) {
|
164
|
+
workerLogger.debug('creating new shared key handler');
|
172
165
|
sharedKeyHandler = new ParticipantKeyHandler('shared-key', keyProviderOptions);
|
173
166
|
}
|
174
167
|
return sharedKeyHandler;
|
175
168
|
}
|
176
169
|
|
177
170
|
function unsetCryptorParticipant(trackId: string, participantIdentity: string) {
|
178
|
-
participantCryptors
|
179
|
-
|
180
|
-
|
171
|
+
const cryptor = participantCryptors.find(
|
172
|
+
(c) => c.getParticipantIdentity() === participantIdentity && c.getTrackId() === trackId,
|
173
|
+
);
|
174
|
+
if (!cryptor) {
|
175
|
+
workerLogger.warn('Could not unset participant on cryptor', { trackId, participantIdentity });
|
176
|
+
} else {
|
177
|
+
cryptor.unsetParticipant();
|
178
|
+
}
|
181
179
|
}
|
182
180
|
|
183
181
|
function setEncryptionEnabled(enable: boolean, participantIdentity: string) {
|
182
|
+
workerLogger.debug(`setting encryption enabled for all tracks of ${participantIdentity}`, {
|
183
|
+
enable,
|
184
|
+
});
|
184
185
|
encryptionEnabledMap.set(participantIdentity, enable);
|
185
186
|
}
|
186
187
|
|
187
188
|
function setSharedKey(key: CryptoKey, index?: number) {
|
188
|
-
workerLogger.
|
189
|
-
sharedKey = key;
|
189
|
+
workerLogger.info('set shared key', { index });
|
190
190
|
getSharedKeyHandler().setKey(key, index);
|
191
191
|
}
|
192
192
|
|
package/src/index.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { LogLevel, getLogger, setLogExtension, setLogLevel } from './logger';
|
1
|
+
import { LogLevel, LoggerNames, getLogger, setLogExtension, setLogLevel } from './logger';
|
2
2
|
import { DataPacket_Kind, DisconnectReason } from './proto/livekit_models_pb';
|
3
3
|
import DefaultReconnectPolicy from './room/DefaultReconnectPolicy';
|
4
4
|
import Room, { ConnectionState } from './room/Room';
|
@@ -55,6 +55,7 @@ export {
|
|
55
55
|
supportsVP9,
|
56
56
|
createAudioAnalyser,
|
57
57
|
LogLevel,
|
58
|
+
LoggerNames,
|
58
59
|
getLogger,
|
59
60
|
Room,
|
60
61
|
ConnectionState,
|
package/src/logger.ts
CHANGED
@@ -31,6 +31,8 @@ export type StructuredLogger = {
|
|
31
31
|
warn: (msg: string, context?: object) => void;
|
32
32
|
error: (msg: string, context?: object) => void;
|
33
33
|
setDefaultLevel: (level: log.LogLevelDesc) => void;
|
34
|
+
setLevel: (level: log.LogLevelDesc) => void;
|
35
|
+
getLevel: () => number;
|
34
36
|
};
|
35
37
|
|
36
38
|
let livekitLogger = log.getLogger('livekit');
|
@@ -80,7 +80,16 @@ export default class DeviceManager {
|
|
80
80
|
// device has been chosen
|
81
81
|
const devices = await this.getDevices(kind);
|
82
82
|
|
83
|
-
|
83
|
+
// `default` devices will have the same groupId as the entry with the actual device id so we store the counts for each group id
|
84
|
+
const groupIdCounts = new Map(devices.map((d) => [d.groupId, 0]));
|
85
|
+
|
86
|
+
devices.forEach((d) => groupIdCounts.set(d.groupId, (groupIdCounts.get(d.groupId) ?? 0) + 1));
|
87
|
+
|
88
|
+
const device = devices.find(
|
89
|
+
(d) =>
|
90
|
+
(groupId === d.groupId || (groupIdCounts.get(d.groupId) ?? 0) > 1) &&
|
91
|
+
d.deviceId !== defaultId,
|
92
|
+
);
|
84
93
|
|
85
94
|
return device?.deviceId;
|
86
95
|
}
|
package/src/room/RTCEngine.ts
CHANGED
@@ -418,6 +418,19 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
418
418
|
);
|
419
419
|
}
|
420
420
|
}
|
421
|
+
|
422
|
+
// detect cases where both signal client and peer connection are severed and assume that user has lost network connection
|
423
|
+
const isSignalSevered =
|
424
|
+
this.client.isDisconnected ||
|
425
|
+
this.client.currentState === SignalConnectionState.RECONNECTING;
|
426
|
+
const isPCSevered = [
|
427
|
+
PCTransportState.FAILED,
|
428
|
+
PCTransportState.CLOSING,
|
429
|
+
PCTransportState.CLOSED,
|
430
|
+
].includes(connectionState);
|
431
|
+
if (isSignalSevered && isPCSevered && !this._isClosed) {
|
432
|
+
this.emit(EngineEvent.Offline);
|
433
|
+
}
|
421
434
|
};
|
422
435
|
this.pcManager.onTrack = (ev: RTCTrackEvent) => {
|
423
436
|
this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
|
@@ -1400,4 +1413,5 @@ export type EngineEventCallbacks = {
|
|
1400
1413
|
subscribedQualityUpdate: (update: SubscribedQualityUpdate) => void;
|
1401
1414
|
localTrackUnpublished: (unpublishedResponse: TrackUnpublishedResponse) => void;
|
1402
1415
|
remoteMute: (trackSid: string, muted: boolean) => void;
|
1416
|
+
offline: () => void;
|
1403
1417
|
};
|
package/src/room/Room.ts
CHANGED
@@ -60,6 +60,7 @@ import type RemoteTrack from './track/RemoteTrack';
|
|
60
60
|
import RemoteTrackPublication from './track/RemoteTrackPublication';
|
61
61
|
import { Track } from './track/Track';
|
62
62
|
import type { TrackPublication } from './track/TrackPublication';
|
63
|
+
import type { TrackProcessor } from './track/processor/types';
|
63
64
|
import type { AdaptiveStreamSettings } from './track/types';
|
64
65
|
import { getNewAudioContext, sourceToKind } from './track/utils';
|
65
66
|
import type { SimulationOptions, SimulationScenario } from './types';
|
@@ -348,6 +349,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
348
349
|
})
|
349
350
|
.on(EngineEvent.Restarting, this.handleRestarting)
|
350
351
|
.on(EngineEvent.SignalRestarted, this.handleSignalRestarted)
|
352
|
+
.on(EngineEvent.Offline, () => {
|
353
|
+
if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
|
354
|
+
this.emit(RoomEvent.Reconnecting);
|
355
|
+
}
|
356
|
+
})
|
351
357
|
.on(EngineEvent.DCBufferStatusChanged, (status, kind) => {
|
352
358
|
this.emit(RoomEvent.DCBufferStatusChanged, status, kind);
|
353
359
|
});
|
@@ -573,16 +579,23 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
573
579
|
this.localParticipant.sid = pi.sid;
|
574
580
|
this.localParticipant.identity = pi.identity;
|
575
581
|
|
582
|
+
if (this.options.e2ee && this.e2eeManager) {
|
583
|
+
try {
|
584
|
+
this.e2eeManager.setSifTrailer(joinResponse.sifTrailer);
|
585
|
+
} catch (e: any) {
|
586
|
+
this.log.error(e instanceof Error ? e.message : 'Could not set SifTrailer', {
|
587
|
+
...this.logContext,
|
588
|
+
error: e,
|
589
|
+
});
|
590
|
+
}
|
591
|
+
}
|
592
|
+
|
576
593
|
// populate remote participants, these should not trigger new events
|
577
594
|
this.handleParticipantUpdates([pi, ...joinResponse.otherParticipants]);
|
578
595
|
|
579
596
|
if (joinResponse.room) {
|
580
597
|
this.handleRoomUpdate(joinResponse.room);
|
581
598
|
}
|
582
|
-
|
583
|
-
if (this.options.e2ee && this.e2eeManager) {
|
584
|
-
this.e2eeManager.setSifTrailer(joinResponse.sifTrailer);
|
585
|
-
}
|
586
599
|
};
|
587
600
|
|
588
601
|
private attemptConnection = async (
|
@@ -857,6 +870,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
857
870
|
}
|
858
871
|
|
859
872
|
private onPageLeave = async () => {
|
873
|
+
this.log.info('Page leave detected, disconnecting', this.logContext);
|
860
874
|
await this.disconnect();
|
861
875
|
};
|
862
876
|
|
@@ -1034,6 +1048,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1034
1048
|
) {
|
1035
1049
|
throw new Error('cannot switch audio output, setSinkId not supported');
|
1036
1050
|
}
|
1051
|
+
if (this.options.webAudioMix) {
|
1052
|
+
// setting `default` for web audio output doesn't work, so we need to normalize the id before
|
1053
|
+
deviceId =
|
1054
|
+
(await DeviceManager.getInstance().normalizeDeviceId('audiooutput', deviceId)) ?? '';
|
1055
|
+
}
|
1037
1056
|
this.options.audioOutput ??= {};
|
1038
1057
|
const prevDeviceId = this.options.audioOutput.deviceId;
|
1039
1058
|
this.options.audioOutput.deviceId = deviceId;
|
@@ -1775,7 +1794,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1775
1794
|
this.emit(RoomEvent.TrackUnmuted, pub, this.localParticipant);
|
1776
1795
|
};
|
1777
1796
|
|
1797
|
+
private onTrackProcessorUpdate = (processor?: TrackProcessor<Track.Kind, any>) => {
|
1798
|
+
processor?.onPublish?.(this);
|
1799
|
+
};
|
1800
|
+
|
1778
1801
|
private onLocalTrackPublished = async (pub: LocalTrackPublication) => {
|
1802
|
+
pub.track?.on(TrackEvent.TrackProcessorUpdate, this.onTrackProcessorUpdate);
|
1779
1803
|
pub.track?.getProcessor()?.onPublish?.(this);
|
1780
1804
|
|
1781
1805
|
this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant);
|
@@ -1799,6 +1823,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1799
1823
|
};
|
1800
1824
|
|
1801
1825
|
private onLocalTrackUnpublished = (pub: LocalTrackPublication) => {
|
1826
|
+
pub.track?.off(TrackEvent.TrackProcessorUpdate, this.onTrackProcessorUpdate);
|
1802
1827
|
this.emit(RoomEvent.LocalTrackUnpublished, pub, this.localParticipant);
|
1803
1828
|
};
|
1804
1829
|
|
package/src/room/events.ts
CHANGED
@@ -491,6 +491,7 @@ export enum EngineEvent {
|
|
491
491
|
RemoteMute = 'remoteMute',
|
492
492
|
SubscribedQualityUpdate = 'subscribedQualityUpdate',
|
493
493
|
LocalTrackUnpublished = 'localTrackUnpublished',
|
494
|
+
Offline = 'offline',
|
494
495
|
}
|
495
496
|
|
496
497
|
export enum TrackEvent {
|
@@ -552,4 +553,8 @@ export enum TrackEvent {
|
|
552
553
|
* Fires on RemoteTrackPublication
|
553
554
|
*/
|
554
555
|
SubscriptionFailed = 'subscriptionFailed',
|
556
|
+
/**
|
557
|
+
* @internal
|
558
|
+
*/
|
559
|
+
TrackProcessorUpdate = 'trackProcessorUpdate',
|
555
560
|
}
|
@@ -513,6 +513,10 @@ export default class LocalParticipant extends Participant {
|
|
513
513
|
track: LocalTrack | MediaStreamTrack,
|
514
514
|
options?: TrackPublishOptions,
|
515
515
|
): Promise<LocalTrackPublication> {
|
516
|
+
if (track instanceof LocalAudioTrack) {
|
517
|
+
track.setAudioContext(this.audioContext);
|
518
|
+
}
|
519
|
+
|
516
520
|
await this.reconnectFuture?.promise;
|
517
521
|
if (track instanceof LocalTrack && this.pendingPublishPromises.has(track)) {
|
518
522
|
await this.pendingPublishPromises.get(track);
|
@@ -566,10 +570,6 @@ export default class LocalParticipant extends Participant {
|
|
566
570
|
});
|
567
571
|
}
|
568
572
|
|
569
|
-
if (track instanceof LocalAudioTrack) {
|
570
|
-
track.setAudioContext(this.audioContext);
|
571
|
-
}
|
572
|
-
|
573
573
|
// is it already published? if so skip
|
574
574
|
let existingPublication: LocalTrackPublication | undefined;
|
575
575
|
this.trackPublications.forEach((publication) => {
|
@@ -51,6 +51,11 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
|
|
51
51
|
async mute(): Promise<typeof this> {
|
52
52
|
const unlock = await this.muteLock.lock();
|
53
53
|
try {
|
54
|
+
if (this.isMuted) {
|
55
|
+
this.log.debug('Track already muted', this.logContext);
|
56
|
+
return this;
|
57
|
+
}
|
58
|
+
|
54
59
|
// disabled special handling as it will cause BT headsets to switch communication modes
|
55
60
|
if (this.source === Track.Source.Microphone && this.stopOnMute && !this.isUserProvided) {
|
56
61
|
this.log.debug('stopping mic track', this.logContext);
|
@@ -67,6 +72,11 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
|
|
67
72
|
async unmute(): Promise<typeof this> {
|
68
73
|
const unlock = await this.muteLock.lock();
|
69
74
|
try {
|
75
|
+
if (!this.isMuted) {
|
76
|
+
this.log.debug('Track already unmuted', this.logContext);
|
77
|
+
return this;
|
78
|
+
}
|
79
|
+
|
70
80
|
const deviceHasChanged =
|
71
81
|
this._constraints.deviceId &&
|
72
82
|
this._mediaStreamTrack.getSettings().deviceId !==
|
@@ -163,6 +173,7 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
|
|
163
173
|
if (this.processor.processedTrack) {
|
164
174
|
await this.sender?.replaceTrack(this.processor.processedTrack);
|
165
175
|
}
|
176
|
+
this.emit(TrackEvent.TrackProcessorUpdate, this.processor);
|
166
177
|
} finally {
|
167
178
|
unlock();
|
168
179
|
}
|