livekit-client 1.11.3 → 1.12.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +1 -3
- package/dist/livekit-client.e2ee.worker.js +2 -0
- package/dist/livekit-client.e2ee.worker.js.map +1 -0
- package/dist/livekit-client.e2ee.worker.mjs +1525 -0
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -0
- package/dist/livekit-client.esm.mjs +1462 -660
- 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 +4 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/turn.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/websocket.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +45 -0
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -0
- package/dist/src/e2ee/KeyProvider.d.ts +42 -0
- package/dist/src/e2ee/KeyProvider.d.ts.map +1 -0
- package/dist/src/e2ee/constants.d.ts +14 -0
- package/dist/src/e2ee/constants.d.ts.map +1 -0
- package/dist/src/e2ee/errors.d.ts +11 -0
- package/dist/src/e2ee/errors.d.ts.map +1 -0
- package/dist/src/e2ee/index.d.ts +4 -0
- package/dist/src/e2ee/index.d.ts.map +1 -0
- package/dist/src/e2ee/types.d.ts +129 -0
- package/dist/src/e2ee/types.d.ts.map +1 -0
- package/dist/src/e2ee/utils.d.ts +24 -0
- package/dist/src/e2ee/utils.d.ts.map +1 -0
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +175 -0
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -0
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +46 -0
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -0
- package/dist/src/e2ee/worker/e2ee.worker.d.ts +2 -0
- package/dist/src/e2ee/worker/e2ee.worker.d.ts.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/logger.d.ts +4 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/options.d.ts +5 -0
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/proto/livekit_models.d.ts +2 -2
- package/dist/src/proto/livekit_models.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +3 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +17 -3
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +10 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +14 -2
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +9 -2
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +1 -0
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +3 -2
- 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/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/TrackPublication.d.ts +3 -0
- package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/facingMode.d.ts +41 -0
- package/dist/src/room/track/facingMode.d.ts.map +1 -0
- package/dist/src/room/track/options.d.ts +2 -2
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts +5 -35
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +2 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/test/MockMediaStreamTrack.d.ts.map +1 -1
- package/dist/ts4.2/src/api/SignalClient.d.ts +4 -1
- package/dist/ts4.2/src/e2ee/E2eeManager.d.ts +45 -0
- package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +42 -0
- package/dist/ts4.2/src/e2ee/constants.d.ts +14 -0
- package/dist/ts4.2/src/e2ee/errors.d.ts +11 -0
- package/dist/ts4.2/src/e2ee/index.d.ts +4 -0
- package/dist/ts4.2/src/e2ee/types.d.ts +129 -0
- package/dist/ts4.2/src/e2ee/utils.d.ts +24 -0
- package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +175 -0
- package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +46 -0
- package/dist/ts4.2/src/e2ee/worker/e2ee.worker.d.ts +2 -0
- package/dist/ts4.2/src/index.d.ts +2 -0
- package/dist/ts4.2/src/logger.d.ts +4 -1
- package/dist/ts4.2/src/options.d.ts +5 -0
- package/dist/ts4.2/src/proto/livekit_models.d.ts +2 -2
- package/dist/ts4.2/src/room/PCTransport.d.ts +3 -1
- package/dist/ts4.2/src/room/RTCEngine.d.ts +17 -3
- package/dist/ts4.2/src/room/Room.d.ts +10 -1
- package/dist/ts4.2/src/room/events.d.ts +14 -2
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +9 -2
- package/dist/ts4.2/src/room/participant/Participant.d.ts +1 -0
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +3 -2
- package/dist/ts4.2/src/room/track/TrackPublication.d.ts +3 -0
- package/dist/ts4.2/src/room/track/facingMode.d.ts +41 -0
- package/dist/ts4.2/src/room/track/options.d.ts +6 -6
- package/dist/ts4.2/src/room/track/utils.d.ts +5 -35
- package/dist/ts4.2/src/room/utils.d.ts +2 -0
- package/package.json +17 -7
- package/src/api/SignalClient.ts +28 -9
- package/src/connectionHelper/checks/turn.ts +1 -0
- package/src/connectionHelper/checks/websocket.ts +1 -0
- package/src/e2ee/E2eeManager.ts +374 -0
- package/src/e2ee/KeyProvider.ts +77 -0
- package/src/e2ee/constants.ts +40 -0
- package/src/e2ee/errors.ts +16 -0
- package/src/e2ee/index.ts +3 -0
- package/src/e2ee/types.ts +160 -0
- package/src/e2ee/utils.ts +127 -0
- package/src/e2ee/worker/FrameCryptor.test.ts +21 -0
- package/src/e2ee/worker/FrameCryptor.ts +614 -0
- package/src/e2ee/worker/ParticipantKeyHandler.ts +129 -0
- package/src/e2ee/worker/e2ee.worker.ts +217 -0
- package/src/e2ee/worker/tsconfig.json +6 -0
- package/src/index.ts +2 -0
- package/src/logger.ts +10 -2
- package/src/options.ts +6 -0
- package/src/proto/livekit_models.ts +12 -12
- package/src/room/PCTransport.ts +39 -9
- package/src/room/RTCEngine.ts +127 -34
- package/src/room/Room.ts +83 -30
- package/src/room/defaults.ts +1 -1
- package/src/room/events.ts +14 -0
- package/src/room/participant/LocalParticipant.ts +82 -10
- package/src/room/participant/Participant.ts +4 -0
- package/src/room/track/LocalAudioTrack.ts +11 -4
- package/src/room/track/LocalTrack.ts +50 -43
- package/src/room/track/LocalVideoTrack.ts +5 -3
- package/src/room/track/RemoteVideoTrack.ts +2 -2
- package/src/room/track/TrackPublication.ts +9 -1
- package/src/room/track/facingMode.test.ts +30 -0
- package/src/room/track/facingMode.ts +103 -0
- package/src/room/track/options.ts +3 -2
- package/src/room/track/utils.test.ts +1 -30
- package/src/room/track/utils.ts +16 -91
- package/src/room/utils.ts +5 -0
- package/src/room/worker.d.ts +4 -0
- package/src/test/MockMediaStreamTrack.ts +1 -0
package/src/room/RTCEngine.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import EventEmitter from 'eventemitter3';
|
2
|
+
import type { MediaAttributes } from 'sdp-transform';
|
2
3
|
import { SignalClient } from '../api/SignalClient';
|
3
4
|
import type { SignalOptions } from '../api/SignalClient';
|
4
5
|
import log from '../logger';
|
@@ -9,17 +10,23 @@ import {
|
|
9
10
|
DataPacket,
|
10
11
|
DataPacket_Kind,
|
11
12
|
DisconnectReason,
|
13
|
+
ParticipantInfo,
|
12
14
|
ReconnectReason,
|
15
|
+
Room as RoomModel,
|
13
16
|
SpeakerInfo,
|
14
17
|
TrackInfo,
|
15
18
|
UserPacket,
|
16
19
|
} from '../proto/livekit_models';
|
17
20
|
import {
|
18
21
|
AddTrackRequest,
|
22
|
+
ConnectionQualityUpdate,
|
19
23
|
JoinResponse,
|
20
24
|
LeaveRequest,
|
21
25
|
ReconnectResponse,
|
22
26
|
SignalTarget,
|
27
|
+
StreamStateUpdate,
|
28
|
+
SubscriptionPermissionUpdate,
|
29
|
+
SubscriptionResponse,
|
23
30
|
TrackPublishedResponse,
|
24
31
|
} from '../proto/livekit_rtc';
|
25
32
|
import PCTransport, { PCEvents } from './PCTransport';
|
@@ -43,6 +50,7 @@ import type { TrackPublishOptions, VideoCodec } from './track/options';
|
|
43
50
|
import {
|
44
51
|
Mutex,
|
45
52
|
isCloud,
|
53
|
+
isVideoCodec,
|
46
54
|
isWeb,
|
47
55
|
sleep,
|
48
56
|
supportsAddTrack,
|
@@ -161,6 +169,17 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
161
169
|
[DataPacket_Kind.LOSSY, true],
|
162
170
|
[DataPacket_Kind.RELIABLE, true],
|
163
171
|
]);
|
172
|
+
|
173
|
+
this.client.onParticipantUpdate = (updates) =>
|
174
|
+
this.emit(EngineEvent.ParticipantUpdate, updates);
|
175
|
+
this.client.onConnectionQuality = (update) =>
|
176
|
+
this.emit(EngineEvent.ConnectionQualityUpdate, update);
|
177
|
+
this.client.onRoomUpdate = (update) => this.emit(EngineEvent.RoomUpdate, update);
|
178
|
+
this.client.onSubscriptionError = (resp) => this.emit(EngineEvent.SubscriptionError, resp);
|
179
|
+
this.client.onSubscriptionPermissionUpdate = (update) =>
|
180
|
+
this.emit(EngineEvent.SubscriptionPermissionUpdate, update);
|
181
|
+
this.client.onSpeakersChanged = (update) => this.emit(EngineEvent.SpeakersChanged, update);
|
182
|
+
this.client.onStreamStateUpdate = (update) => this.emit(EngineEvent.StreamStateChanged, update);
|
164
183
|
}
|
165
184
|
|
166
185
|
async join(
|
@@ -187,6 +206,8 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
187
206
|
if (!this.subscriberPrimary) {
|
188
207
|
this.negotiate();
|
189
208
|
}
|
209
|
+
this.setupSignalClientCallbacks();
|
210
|
+
|
190
211
|
this.clientConfiguration = joinResponse.clientConfiguration;
|
191
212
|
|
192
213
|
return joinResponse;
|
@@ -217,30 +238,63 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
217
238
|
this.removeAllListeners();
|
218
239
|
this.deregisterOnLineListener();
|
219
240
|
this.clearPendingReconnect();
|
220
|
-
|
221
|
-
|
222
|
-
try {
|
223
|
-
// TODO: react-native-webrtc doesn't have removeTrack yet.
|
224
|
-
if (this.publisher?.pc.removeTrack) {
|
225
|
-
this.publisher?.pc.removeTrack(sender);
|
226
|
-
}
|
227
|
-
} catch (e) {
|
228
|
-
log.warn('could not removeTrack', { error: e });
|
229
|
-
}
|
230
|
-
});
|
231
|
-
this.publisher.close();
|
232
|
-
this.publisher = undefined;
|
233
|
-
}
|
234
|
-
if (this.subscriber) {
|
235
|
-
this.subscriber.close();
|
236
|
-
this.subscriber = undefined;
|
237
|
-
}
|
238
|
-
await this.client.close();
|
241
|
+
await this.cleanupPeerConnections();
|
242
|
+
await this.cleanupClient();
|
239
243
|
} finally {
|
240
244
|
unlock();
|
241
245
|
}
|
242
246
|
}
|
243
247
|
|
248
|
+
async cleanupPeerConnections() {
|
249
|
+
if (this.publisher && this.publisher.pc.signalingState !== 'closed') {
|
250
|
+
this.publisher.pc.getSenders().forEach((sender) => {
|
251
|
+
try {
|
252
|
+
// TODO: react-native-webrtc doesn't have removeTrack yet.
|
253
|
+
if (this.publisher?.pc.removeTrack) {
|
254
|
+
this.publisher?.pc.removeTrack(sender);
|
255
|
+
}
|
256
|
+
} catch (e) {
|
257
|
+
log.warn('could not removeTrack', { error: e });
|
258
|
+
}
|
259
|
+
});
|
260
|
+
}
|
261
|
+
if (this.publisher) {
|
262
|
+
this.publisher.close();
|
263
|
+
this.publisher = undefined;
|
264
|
+
}
|
265
|
+
if (this.subscriber) {
|
266
|
+
this.subscriber.close();
|
267
|
+
this.subscriber = undefined;
|
268
|
+
}
|
269
|
+
|
270
|
+
this.primaryPC = undefined;
|
271
|
+
|
272
|
+
const dcCleanup = (dc: RTCDataChannel | undefined) => {
|
273
|
+
if (!dc) return;
|
274
|
+
dc.close();
|
275
|
+
dc.onbufferedamountlow = null;
|
276
|
+
dc.onclose = null;
|
277
|
+
dc.onclosing = null;
|
278
|
+
dc.onerror = null;
|
279
|
+
dc.onmessage = null;
|
280
|
+
dc.onopen = null;
|
281
|
+
};
|
282
|
+
dcCleanup(this.lossyDC);
|
283
|
+
dcCleanup(this.lossyDCSub);
|
284
|
+
dcCleanup(this.reliableDC);
|
285
|
+
dcCleanup(this.reliableDCSub);
|
286
|
+
|
287
|
+
this.lossyDC = undefined;
|
288
|
+
this.lossyDCSub = undefined;
|
289
|
+
this.reliableDC = undefined;
|
290
|
+
this.reliableDCSub = undefined;
|
291
|
+
}
|
292
|
+
|
293
|
+
async cleanupClient() {
|
294
|
+
await this.client.close();
|
295
|
+
this.client.resetCallbacks();
|
296
|
+
}
|
297
|
+
|
244
298
|
addTrack(req: AddTrackRequest): Promise<TrackInfo> {
|
245
299
|
if (this.pendingTrackResolvers[req.cid]) {
|
246
300
|
throw new TrackInvalidError('a track with the same ID has already been published');
|
@@ -313,6 +367,14 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
313
367
|
this.participantSid = joinResponse.participant?.sid;
|
314
368
|
|
315
369
|
const rtcConfig = this.makeRTCConfiguration(joinResponse);
|
370
|
+
|
371
|
+
if (this.signalOpts?.e2eeEnabled) {
|
372
|
+
log.debug('E2EE - setting up transports with insertable streams');
|
373
|
+
// this makes sure that no data is sent before the transforms are ready
|
374
|
+
// @ts-ignore
|
375
|
+
rtcConfig.encodedInsertableStreams = true;
|
376
|
+
}
|
377
|
+
|
316
378
|
const googConstraints = { optional: [{ googDscp: true }] };
|
317
379
|
this.publisher = new PCTransport(rtcConfig, googConstraints);
|
318
380
|
this.subscriber = new PCTransport(rtcConfig);
|
@@ -384,7 +446,9 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
384
446
|
};
|
385
447
|
|
386
448
|
this.createDataChannels();
|
449
|
+
}
|
387
450
|
|
451
|
+
private setupSignalClientCallbacks() {
|
388
452
|
// configure signaling client
|
389
453
|
this.client.onAnswer = async (sd) => {
|
390
454
|
if (!this.publisher) {
|
@@ -392,7 +456,7 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
392
456
|
}
|
393
457
|
log.debug('received server answer', {
|
394
458
|
RTCSdpType: sd.type,
|
395
|
-
signalingState: this.publisher.pc.signalingState,
|
459
|
+
signalingState: this.publisher.pc.signalingState.toString(),
|
396
460
|
});
|
397
461
|
await this.publisher.setRemoteDescription(sd);
|
398
462
|
};
|
@@ -417,7 +481,7 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
417
481
|
}
|
418
482
|
log.debug('received server offer', {
|
419
483
|
RTCSdpType: sd.type,
|
420
|
-
signalingState: this.subscriber.pc.signalingState,
|
484
|
+
signalingState: this.subscriber.pc.signalingState.toString(),
|
421
485
|
});
|
422
486
|
await this.subscriber.setRemoteDescription(sd);
|
423
487
|
|
@@ -646,11 +710,13 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
646
710
|
encodings?: RTCRtpEncodingParameters[],
|
647
711
|
) {
|
648
712
|
if (supportsTransceiver()) {
|
649
|
-
|
713
|
+
const sender = await this.createTransceiverRTCRtpSender(track, opts, encodings);
|
714
|
+
return sender;
|
650
715
|
}
|
651
716
|
if (supportsAddTrack()) {
|
652
|
-
log.
|
653
|
-
|
717
|
+
log.warn('using add-track fallback');
|
718
|
+
const sender = await this.createRTCRtpSender(track.mediaStreamTrack);
|
719
|
+
return sender;
|
654
720
|
}
|
655
721
|
throw new UnexpectedConnectionState('Required webRTC APIs not supported on this device');
|
656
722
|
}
|
@@ -662,7 +728,6 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
662
728
|
encodings?: RTCRtpEncodingParameters[],
|
663
729
|
) {
|
664
730
|
// store RTCRtpSender
|
665
|
-
// @ts-ignore
|
666
731
|
if (supportsTransceiver()) {
|
667
732
|
return this.createSimulcastTransceiverSender(track, simulcastTrack, opts, encodings);
|
668
733
|
}
|
@@ -683,7 +748,13 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
683
748
|
throw new UnexpectedConnectionState('publisher is closed');
|
684
749
|
}
|
685
750
|
|
686
|
-
const
|
751
|
+
const streams: MediaStream[] = [];
|
752
|
+
|
753
|
+
if (track.mediaStream) {
|
754
|
+
streams.push(track.mediaStream);
|
755
|
+
}
|
756
|
+
|
757
|
+
const transceiverInit: RTCRtpTransceiverInit = { direction: 'sendonly', streams };
|
687
758
|
if (encodings) {
|
688
759
|
transceiverInit.sendEncodings = encodings;
|
689
760
|
}
|
@@ -692,6 +763,7 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
692
763
|
track.mediaStreamTrack,
|
693
764
|
transceiverInit,
|
694
765
|
);
|
766
|
+
|
695
767
|
if (track.kind === Track.Kind.Video && opts.videoCodec) {
|
696
768
|
this.setPreferredCodec(transceiver, track.kind, opts.videoCodec);
|
697
769
|
track.codec = opts.videoCodec;
|
@@ -819,7 +891,7 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
819
891
|
}
|
820
892
|
|
821
893
|
if (recoverable) {
|
822
|
-
this.handleDisconnect('reconnect', ReconnectReason.
|
894
|
+
this.handleDisconnect('reconnect', ReconnectReason.RR_UNKNOWN);
|
823
895
|
} else {
|
824
896
|
log.info(
|
825
897
|
`could not recover connection after ${this.reconnectAttempts} attempts, ${
|
@@ -858,12 +930,8 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
858
930
|
if (this.client.isConnected) {
|
859
931
|
await this.client.sendLeave();
|
860
932
|
}
|
861
|
-
await this.
|
862
|
-
this.
|
863
|
-
this.publisher?.close();
|
864
|
-
this.publisher = undefined;
|
865
|
-
this.subscriber?.close();
|
866
|
-
this.subscriber = undefined;
|
933
|
+
await this.cleanupPeerConnections();
|
934
|
+
await this.cleanupClient();
|
867
935
|
|
868
936
|
let joinResponse: JoinResponse;
|
869
937
|
try {
|
@@ -919,6 +987,7 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
919
987
|
this.emit(EngineEvent.Resuming);
|
920
988
|
|
921
989
|
try {
|
990
|
+
this.setupSignalClientCallbacks();
|
922
991
|
const res = await this.client.reconnect(this.url, this.token, this.participantSid, reason);
|
923
992
|
if (res) {
|
924
993
|
const rtcConfig = this.makeRTCConfiguration(res);
|
@@ -1178,6 +1247,9 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
1178
1247
|
return;
|
1179
1248
|
};
|
1180
1249
|
|
1250
|
+
if (this.isClosed) {
|
1251
|
+
reject('cannot negotiate on closed engine');
|
1252
|
+
}
|
1181
1253
|
this.on(EngineEvent.Closing, handleClosed);
|
1182
1254
|
|
1183
1255
|
const negotiationTimeout = setTimeout(() => {
|
@@ -1197,13 +1269,24 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
|
|
1197
1269
|
});
|
1198
1270
|
});
|
1199
1271
|
|
1272
|
+
this.publisher.once(PCEvents.RTPVideoPayloadTypes, (rtpTypes: MediaAttributes['rtp']) => {
|
1273
|
+
const rtpMap = new Map<number, VideoCodec>();
|
1274
|
+
rtpTypes.forEach((rtp) => {
|
1275
|
+
const codec = rtp.codec.toLowerCase();
|
1276
|
+
if (isVideoCodec(codec)) {
|
1277
|
+
rtpMap.set(rtp.payload, codec);
|
1278
|
+
}
|
1279
|
+
});
|
1280
|
+
this.emit(EngineEvent.RTPVideoMapUpdate, rtpMap);
|
1281
|
+
});
|
1282
|
+
|
1200
1283
|
this.publisher.negotiate((e) => {
|
1201
1284
|
cleanup();
|
1202
1285
|
reject(e);
|
1203
1286
|
if (e instanceof NegotiationError) {
|
1204
1287
|
this.fullReconnectOnNext = true;
|
1205
1288
|
}
|
1206
|
-
this.handleDisconnect('negotiation', ReconnectReason.
|
1289
|
+
this.handleDisconnect('negotiation', ReconnectReason.RR_UNKNOWN);
|
1207
1290
|
});
|
1208
1291
|
});
|
1209
1292
|
}
|
@@ -1318,5 +1401,15 @@ export type EngineEventCallbacks = {
|
|
1318
1401
|
activeSpeakersUpdate: (speakers: Array<SpeakerInfo>) => void;
|
1319
1402
|
dataPacketReceived: (userPacket: UserPacket, kind: DataPacket_Kind) => void;
|
1320
1403
|
transportsCreated: (publisher: PCTransport, subscriber: PCTransport) => void;
|
1404
|
+
/** @internal */
|
1405
|
+
trackSenderAdded: (track: Track, sender: RTCRtpSender) => void;
|
1406
|
+
rtpVideoMapUpdate: (rtpMap: Map<number, VideoCodec>) => void;
|
1321
1407
|
dcBufferStatusChanged: (isLow: boolean, kind: DataPacket_Kind) => void;
|
1408
|
+
participantUpdate: (infos: ParticipantInfo[]) => void;
|
1409
|
+
roomUpdate: (room: RoomModel) => void;
|
1410
|
+
connectionQualityUpdate: (update: ConnectionQualityUpdate) => void;
|
1411
|
+
speakersChanged: (speakerUpdates: SpeakerInfo[]) => void;
|
1412
|
+
streamStateChanged: (update: StreamStateUpdate) => void;
|
1413
|
+
subscriptionError: (resp: SubscriptionResponse) => void;
|
1414
|
+
subscriptionPermissionUpdate: (update: SubscriptionPermissionUpdate) => void;
|
1322
1415
|
};
|
package/src/room/Room.ts
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
import EventEmitter from 'eventemitter3';
|
2
2
|
import 'webrtc-adapter';
|
3
3
|
import { toProtoSessionDescription } from '../api/SignalClient';
|
4
|
+
import { EncryptionEvent } from '../e2ee';
|
5
|
+
import { E2EEManager } from '../e2ee/E2eeManager';
|
4
6
|
import log from '../logger';
|
5
7
|
import type {
|
6
8
|
InternalRoomConnectOptions,
|
@@ -56,7 +58,7 @@ import RemoteTrackPublication from './track/RemoteTrackPublication';
|
|
56
58
|
import { Track } from './track/Track';
|
57
59
|
import type { TrackPublication } from './track/TrackPublication';
|
58
60
|
import type { AdaptiveStreamSettings } from './track/types';
|
59
|
-
import { getNewAudioContext } from './track/utils';
|
61
|
+
import { getNewAudioContext, sourceToKind } from './track/utils';
|
60
62
|
import type { SimulationOptions, SimulationScenario } from './types';
|
61
63
|
import {
|
62
64
|
Future,
|
@@ -112,6 +114,9 @@ class Room extends EventEmitter<RoomEventCallbacks> {
|
|
112
114
|
/** options of room */
|
113
115
|
options: InternalRoomOptions;
|
114
116
|
|
117
|
+
/** reflects the sender encryption status of the local participant */
|
118
|
+
isE2EEEnabled: boolean = false;
|
119
|
+
|
115
120
|
private roomInfo?: RoomModel;
|
116
121
|
|
117
122
|
private identityToSid: Map<string, string>;
|
@@ -131,12 +136,12 @@ class Room extends EventEmitter<RoomEventCallbacks> {
|
|
131
136
|
|
132
137
|
private disconnectLock: Mutex;
|
133
138
|
|
139
|
+
private e2eeManager: E2EEManager | undefined;
|
140
|
+
|
134
141
|
private cachedParticipantSids: Array<string>;
|
135
142
|
|
136
143
|
private connectionReconcileInterval?: ReturnType<typeof setInterval>;
|
137
144
|
|
138
|
-
private activeDeviceMap: Map<MediaDeviceKind, string>;
|
139
|
-
|
140
145
|
/**
|
141
146
|
* Creates a new Room, the primary construct for a LiveKit session.
|
142
147
|
* @param options
|
@@ -164,15 +169,17 @@ class Room extends EventEmitter<RoomEventCallbacks> {
|
|
164
169
|
this.maybeCreateEngine();
|
165
170
|
|
166
171
|
this.disconnectLock = new Mutex();
|
167
|
-
|
172
|
+
|
173
|
+
this.localParticipant = new LocalParticipant('', '', this.engine, this.options);
|
174
|
+
|
168
175
|
if (this.options.videoCaptureDefaults.deviceId) {
|
169
|
-
this.activeDeviceMap.set(
|
176
|
+
this.localParticipant.activeDeviceMap.set(
|
170
177
|
'videoinput',
|
171
178
|
unwrapConstraint(this.options.videoCaptureDefaults.deviceId),
|
172
179
|
);
|
173
180
|
}
|
174
181
|
if (this.options.audioCaptureDefaults.deviceId) {
|
175
|
-
this.activeDeviceMap.set(
|
182
|
+
this.localParticipant.activeDeviceMap.set(
|
176
183
|
'audioinput',
|
177
184
|
unwrapConstraint(this.options.audioCaptureDefaults.deviceId),
|
178
185
|
);
|
@@ -181,7 +188,42 @@ class Room extends EventEmitter<RoomEventCallbacks> {
|
|
181
188
|
this.switchActiveDevice('audiooutput', unwrapConstraint(this.options.audioOutput.deviceId));
|
182
189
|
}
|
183
190
|
|
184
|
-
|
191
|
+
if (this.options.e2ee) {
|
192
|
+
this.setupE2EE();
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
196
|
+
/**
|
197
|
+
* @experimental
|
198
|
+
*/
|
199
|
+
async setE2EEEnabled(enabled: boolean) {
|
200
|
+
if (this.e2eeManager) {
|
201
|
+
await Promise.all([
|
202
|
+
this.localParticipant.setE2EEEnabled(enabled),
|
203
|
+
this.e2eeManager.setParticipantCryptorEnabled(enabled),
|
204
|
+
]);
|
205
|
+
} else {
|
206
|
+
throw Error('e2ee not configured, please set e2ee settings within the room options');
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
private setupE2EE() {
|
211
|
+
if (this.options.e2ee) {
|
212
|
+
this.e2eeManager = new E2EEManager(this.options.e2ee);
|
213
|
+
this.e2eeManager.on(
|
214
|
+
EncryptionEvent.ParticipantEncryptionStatusChanged,
|
215
|
+
(enabled, participant) => {
|
216
|
+
if (participant instanceof LocalParticipant) {
|
217
|
+
this.isE2EEEnabled = enabled;
|
218
|
+
}
|
219
|
+
this.emit(RoomEvent.ParticipantEncryptionStatusChanged, enabled, participant);
|
220
|
+
},
|
221
|
+
);
|
222
|
+
this.e2eeManager.on(EncryptionEvent.Error, (error) =>
|
223
|
+
this.emit(RoomEvent.EncryptionError, error),
|
224
|
+
);
|
225
|
+
this.e2eeManager?.setup(this);
|
226
|
+
}
|
185
227
|
}
|
186
228
|
|
187
229
|
/**
|
@@ -221,15 +263,14 @@ class Room extends EventEmitter<RoomEventCallbacks> {
|
|
221
263
|
|
222
264
|
this.engine = new RTCEngine(this.options);
|
223
265
|
|
224
|
-
this.engine.client.onParticipantUpdate = this.handleParticipantUpdates;
|
225
|
-
this.engine.client.onRoomUpdate = this.handleRoomUpdate;
|
226
|
-
this.engine.client.onSpeakersChanged = this.handleSpeakersChanged;
|
227
|
-
this.engine.client.onStreamStateUpdate = this.handleStreamStateUpdate;
|
228
|
-
this.engine.client.onSubscriptionPermissionUpdate = this.handleSubscriptionPermissionUpdate;
|
229
|
-
this.engine.client.onConnectionQuality = this.handleConnectionQualityUpdate;
|
230
|
-
this.engine.client.onSubscriptionError = this.handleSubscriptionError;
|
231
|
-
|
232
266
|
this.engine
|
267
|
+
.on(EngineEvent.ParticipantUpdate, this.handleParticipantUpdates)
|
268
|
+
.on(EngineEvent.RoomUpdate, this.handleRoomUpdate)
|
269
|
+
.on(EngineEvent.SpeakersChanged, this.handleSpeakersChanged)
|
270
|
+
.on(EngineEvent.StreamStateChanged, this.handleStreamStateUpdate)
|
271
|
+
.on(EngineEvent.ConnectionQualityUpdate, this.handleConnectionQualityUpdate)
|
272
|
+
.on(EngineEvent.SubscriptionError, this.handleSubscriptionError)
|
273
|
+
.on(EngineEvent.SubscriptionPermissionUpdate, this.handleSubscriptionPermissionUpdate)
|
233
274
|
.on(
|
234
275
|
EngineEvent.MediaTrackAdded,
|
235
276
|
(mediaTrack: MediaStreamTrack, stream: MediaStream, receiver?: RTCRtpReceiver) => {
|
@@ -275,6 +316,9 @@ class Room extends EventEmitter<RoomEventCallbacks> {
|
|
275
316
|
if (this.localParticipant) {
|
276
317
|
this.localParticipant.setupEngine(this.engine);
|
277
318
|
}
|
319
|
+
if (this.e2eeManager) {
|
320
|
+
this.e2eeManager.setupEngine(this.engine);
|
321
|
+
}
|
278
322
|
}
|
279
323
|
|
280
324
|
/**
|
@@ -394,6 +438,7 @@ class Room extends EventEmitter<RoomEventCallbacks> {
|
|
394
438
|
adaptiveStream:
|
395
439
|
typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
|
396
440
|
maxRetries: connectOptions.maxRetries,
|
441
|
+
e2eeEnabled: !!this.e2eeManager,
|
397
442
|
},
|
398
443
|
abortController.signal,
|
399
444
|
);
|
@@ -588,10 +633,8 @@ class Room extends EventEmitter<RoomEventCallbacks> {
|
|
588
633
|
let req: SimulateScenario | undefined;
|
589
634
|
switch (scenario) {
|
590
635
|
case 'signal-reconnect':
|
591
|
-
|
592
|
-
|
593
|
-
this.engine.client.onClose('simulate disconnect');
|
594
|
-
}
|
636
|
+
// @ts-expect-error function is private
|
637
|
+
await this.engine.client.handleOnClose('simulate disconnect');
|
595
638
|
break;
|
596
639
|
case 'speaker':
|
597
640
|
req = SimulateScenario.fromPartial({
|
@@ -627,17 +670,13 @@ class Room extends EventEmitter<RoomEventCallbacks> {
|
|
627
670
|
break;
|
628
671
|
case 'resume-reconnect':
|
629
672
|
this.engine.failNext();
|
630
|
-
|
631
|
-
|
632
|
-
this.engine.client.onClose('simulate resume-reconnect');
|
633
|
-
}
|
673
|
+
// @ts-expect-error function is private
|
674
|
+
await this.engine.client.handleOnClose('simulate resume-disconnect');
|
634
675
|
break;
|
635
676
|
case 'full-reconnect':
|
636
677
|
this.engine.fullReconnectOnNext = true;
|
637
|
-
|
638
|
-
|
639
|
-
this.engine.client.onClose('simulate full-reconnect');
|
640
|
-
}
|
678
|
+
// @ts-expect-error function is private
|
679
|
+
await this.engine.client.handleOnClose('simulate full-reconnect');
|
641
680
|
break;
|
642
681
|
case 'force-tcp':
|
643
682
|
case 'force-tls':
|
@@ -744,7 +783,7 @@ class Room extends EventEmitter<RoomEventCallbacks> {
|
|
744
783
|
}
|
745
784
|
|
746
785
|
getActiveDevice(kind: MediaDeviceKind): string | undefined {
|
747
|
-
return this.activeDeviceMap.get(kind);
|
786
|
+
return this.localParticipant.activeDeviceMap.get(kind);
|
748
787
|
}
|
749
788
|
|
750
789
|
/**
|
@@ -818,7 +857,7 @@ class Room extends EventEmitter<RoomEventCallbacks> {
|
|
818
857
|
}
|
819
858
|
}
|
820
859
|
if (deviceHasChanged && success) {
|
821
|
-
this.activeDeviceMap.set(kind, deviceId);
|
860
|
+
this.localParticipant.activeDeviceMap.set(kind, deviceId);
|
822
861
|
this.emit(RoomEvent.ActiveDeviceChanged, kind, deviceId);
|
823
862
|
}
|
824
863
|
|
@@ -1524,6 +1563,16 @@ class Room extends EventEmitter<RoomEventCallbacks> {
|
|
1524
1563
|
this.emit(RoomEvent.LocalAudioSilenceDetected, pub);
|
1525
1564
|
}
|
1526
1565
|
}
|
1566
|
+
const deviceId = await pub.track?.getDeviceId();
|
1567
|
+
const deviceKind = sourceToKind(pub.source);
|
1568
|
+
if (
|
1569
|
+
deviceKind &&
|
1570
|
+
deviceId &&
|
1571
|
+
deviceId !== this.localParticipant.activeDeviceMap.get(deviceKind)
|
1572
|
+
) {
|
1573
|
+
this.localParticipant.activeDeviceMap.set(deviceKind, deviceId);
|
1574
|
+
this.emit(RoomEvent.ActiveDeviceChanged, deviceKind, deviceId);
|
1575
|
+
}
|
1527
1576
|
};
|
1528
1577
|
|
1529
1578
|
private onLocalTrackUnpublished = (pub: LocalTrackPublication) => {
|
@@ -1597,7 +1646,9 @@ class Room extends EventEmitter<RoomEventCallbacks> {
|
|
1597
1646
|
}),
|
1598
1647
|
new LocalVideoTrack(
|
1599
1648
|
publishOptions.useRealTracks
|
1600
|
-
? (
|
1649
|
+
? (
|
1650
|
+
await window.navigator.mediaDevices.getUserMedia({ video: true })
|
1651
|
+
).getVideoTracks()[0]
|
1601
1652
|
: createDummyVideoStreamTrack(
|
1602
1653
|
160 * participantOptions.aspectRatios[0] ?? 1,
|
1603
1654
|
160,
|
@@ -1760,6 +1811,8 @@ export type RoomEventCallbacks = {
|
|
1760
1811
|
audioPlaybackChanged: (playing: boolean) => void;
|
1761
1812
|
signalConnected: () => void;
|
1762
1813
|
recordingStatusChanged: (recording: boolean) => void;
|
1814
|
+
participantEncryptionStatusChanged: (encrypted: boolean, participant?: Participant) => void;
|
1815
|
+
encryptionError: (error: Error) => void;
|
1763
1816
|
dcBufferStatusChanged: (isLow: boolean, kind: DataPacket_Kind) => void;
|
1764
1817
|
activeDeviceChanged: (kind: MediaDeviceKind, deviceId: string) => void;
|
1765
1818
|
};
|
package/src/room/defaults.ts
CHANGED
@@ -20,7 +20,7 @@ export const publishDefaults: TrackPublishDefaults = {
|
|
20
20
|
screenShareEncoding: ScreenSharePresets.h1080fps15.encoding,
|
21
21
|
stopMicTrackOnMute: false,
|
22
22
|
videoCodec: 'vp8',
|
23
|
-
backupCodec:
|
23
|
+
backupCodec: false,
|
24
24
|
} as const;
|
25
25
|
|
26
26
|
export const audioDefaults: AudioCaptureOptions = {
|
package/src/room/events.ts
CHANGED
@@ -273,6 +273,9 @@ export enum RoomEvent {
|
|
273
273
|
*/
|
274
274
|
RecordingStatusChanged = 'recordingStatusChanged',
|
275
275
|
|
276
|
+
ParticipantEncryptionStatusChanged = 'participantEncryptionStatusChanged',
|
277
|
+
|
278
|
+
EncryptionError = 'encryptionError',
|
276
279
|
/**
|
277
280
|
* Emits whenever the current buffer status of a data channel changes
|
278
281
|
* args: (isLow: boolean, kind: [[DataPacket_Kind]])
|
@@ -443,6 +446,9 @@ export enum ParticipantEvent {
|
|
443
446
|
* args: (prevPermissions: [[ParticipantPermission]])
|
444
447
|
*/
|
445
448
|
ParticipantPermissionsChanged = 'participantPermissionsChanged',
|
449
|
+
|
450
|
+
/** @internal */
|
451
|
+
PCTrackAdded = 'pcTrackAdded',
|
446
452
|
}
|
447
453
|
|
448
454
|
/** @internal */
|
@@ -460,7 +466,15 @@ export enum EngineEvent {
|
|
460
466
|
MediaTrackAdded = 'mediaTrackAdded',
|
461
467
|
ActiveSpeakersUpdate = 'activeSpeakersUpdate',
|
462
468
|
DataPacketReceived = 'dataPacketReceived',
|
469
|
+
RTPVideoMapUpdate = 'rtpVideoMapUpdate',
|
463
470
|
DCBufferStatusChanged = 'dcBufferStatusChanged',
|
471
|
+
ParticipantUpdate = 'participantUpdate',
|
472
|
+
RoomUpdate = 'roomUpdate',
|
473
|
+
SpeakersChanged = 'speakersChanged',
|
474
|
+
StreamStateChanged = 'streamStateChanged',
|
475
|
+
ConnectionQualityUpdate = 'connectionQualityUpdate',
|
476
|
+
SubscriptionError = 'subscriptionError',
|
477
|
+
SubscriptionPermissionUpdate = 'subscriptionPermissionUpdate',
|
464
478
|
}
|
465
479
|
|
466
480
|
export enum TrackEvent {
|