livekit-client 2.0.2 → 2.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
}
|