livekit-client 2.11.4 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +25 -14
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +155 -22
- 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 +2 -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 +8 -5
- package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
- package/dist/src/e2ee/events.d.ts +8 -3
- package/dist/src/e2ee/events.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +5 -1
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +4 -4
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +2 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +2 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +22 -2
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +13 -0
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/create.d.ts.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/ts4.2/src/api/SignalClient.d.ts +2 -1
- package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +8 -5
- package/dist/ts4.2/src/e2ee/events.d.ts +8 -3
- package/dist/ts4.2/src/e2ee/types.d.ts +5 -1
- package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +4 -4
- package/dist/ts4.2/src/room/RTCEngine.d.ts +2 -1
- package/dist/ts4.2/src/room/Room.d.ts +2 -0
- package/dist/ts4.2/src/room/events.d.ts +22 -2
- package/dist/ts4.2/src/room/participant/Participant.d.ts +13 -0
- package/dist/ts4.2/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/api/SignalClient.ts +10 -0
- package/src/e2ee/E2eeManager.ts +6 -1
- package/src/e2ee/KeyProvider.ts +13 -6
- package/src/e2ee/events.ts +12 -3
- package/src/e2ee/types.ts +8 -1
- package/src/e2ee/worker/FrameCryptor.ts +8 -4
- package/src/e2ee/worker/ParticipantKeyHandler.test.ts +104 -4
- package/src/e2ee/worker/ParticipantKeyHandler.ts +22 -23
- package/src/e2ee/worker/e2ee.worker.ts +7 -2
- package/src/room/RTCEngine.ts +8 -2
- package/src/room/Room.ts +25 -0
- package/src/room/events.ts +23 -0
- package/src/room/participant/LocalParticipant.ts +1 -5
- package/src/room/participant/Participant.ts +47 -2
- package/src/room/track/RemoteAudioTrack.ts +3 -2
- package/src/room/track/create.ts +3 -5
- package/src/version.ts +1 -1
@@ -11,6 +11,7 @@ import type {
|
|
11
11
|
KeyProviderOptions,
|
12
12
|
RatchetMessage,
|
13
13
|
RatchetRequestMessage,
|
14
|
+
RatchetResult,
|
14
15
|
} from '../types';
|
15
16
|
import { FrameCryptor, encryptionEnabledMap } from './FrameCryptor';
|
16
17
|
import { ParticipantKeyHandler } from './ParticipantKeyHandler';
|
@@ -229,13 +230,17 @@ function setupCryptorErrorEvents(cryptor: FrameCryptor) {
|
|
229
230
|
});
|
230
231
|
}
|
231
232
|
|
232
|
-
function emitRatchetedKeys(
|
233
|
+
function emitRatchetedKeys(
|
234
|
+
ratchetResult: RatchetResult,
|
235
|
+
participantIdentity: string,
|
236
|
+
keyIndex?: number,
|
237
|
+
) {
|
233
238
|
const msg: RatchetMessage = {
|
234
239
|
kind: `ratchetKey`,
|
235
240
|
data: {
|
236
241
|
participantIdentity,
|
237
242
|
keyIndex,
|
238
|
-
|
243
|
+
ratchetResult,
|
239
244
|
},
|
240
245
|
};
|
241
246
|
postMessage(msg);
|
package/src/room/RTCEngine.ts
CHANGED
@@ -16,6 +16,7 @@ import {
|
|
16
16
|
type ReconnectResponse,
|
17
17
|
RequestResponse,
|
18
18
|
Room as RoomModel,
|
19
|
+
RoomMovedResponse,
|
19
20
|
RpcAck,
|
20
21
|
RpcResponse,
|
21
22
|
SignalTarget,
|
@@ -529,6 +530,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
529
530
|
this.emit(EngineEvent.SubscribedQualityUpdate, update);
|
530
531
|
};
|
531
532
|
|
533
|
+
this.client.onRoomMoved = (res: RoomMovedResponse) => {
|
534
|
+
this.participantSid = res.participant?.sid;
|
535
|
+
this.emit(EngineEvent.RoomMoved, res);
|
536
|
+
};
|
537
|
+
|
532
538
|
this.client.onClose = () => {
|
533
539
|
this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED);
|
534
540
|
};
|
@@ -616,8 +622,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
616
622
|
|
617
623
|
// create data channels
|
618
624
|
this.lossyDC = this.pcManager.createPublisherDataChannel(lossyDataChannel, {
|
619
|
-
|
620
|
-
ordered: true,
|
625
|
+
ordered: false,
|
621
626
|
maxRetransmits: 0,
|
622
627
|
});
|
623
628
|
this.reliableDC = this.pcManager.createPublisherDataChannel(reliableDataChannel, {
|
@@ -1493,6 +1498,7 @@ export type EngineEventCallbacks = {
|
|
1493
1498
|
dcBufferStatusChanged: (isLow: boolean, kind: DataPacket_Kind) => void;
|
1494
1499
|
participantUpdate: (infos: ParticipantInfo[]) => void;
|
1495
1500
|
roomUpdate: (room: RoomModel) => void;
|
1501
|
+
roomMoved: (room: RoomMovedResponse) => void;
|
1496
1502
|
connectionQualityUpdate: (update: ConnectionQualityUpdate) => void;
|
1497
1503
|
speakersChanged: (speakerUpdates: SpeakerInfo[]) => void;
|
1498
1504
|
streamStateChanged: (update: StreamStateUpdate) => void;
|
package/src/room/Room.ts
CHANGED
@@ -589,6 +589,25 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
589
589
|
trackPublication,
|
590
590
|
this.localParticipant,
|
591
591
|
);
|
592
|
+
})
|
593
|
+
.on(EngineEvent.RoomMoved, (roomMoved) => {
|
594
|
+
this.log.debug('room moved', roomMoved);
|
595
|
+
|
596
|
+
if (roomMoved.room) {
|
597
|
+
this.handleRoomUpdate(roomMoved.room);
|
598
|
+
}
|
599
|
+
|
600
|
+
this.remoteParticipants.forEach((participant, identity) => {
|
601
|
+
this.handleParticipantDisconnected(identity, participant);
|
602
|
+
});
|
603
|
+
|
604
|
+
this.emit(RoomEvent.Moved, roomMoved.room!.name, roomMoved.token);
|
605
|
+
|
606
|
+
if (roomMoved.participant) {
|
607
|
+
this.handleParticipantUpdates([roomMoved.participant, ...roomMoved.otherParticipants]);
|
608
|
+
} else {
|
609
|
+
this.handleParticipantUpdates(roomMoved.otherParticipants);
|
610
|
+
}
|
592
611
|
});
|
593
612
|
|
594
613
|
if (this.localParticipant) {
|
@@ -1625,6 +1644,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1625
1644
|
participant.unpublishTrack(publication.trackSid, true);
|
1626
1645
|
});
|
1627
1646
|
this.emit(RoomEvent.ParticipantDisconnected, participant);
|
1647
|
+
participant.setDisconnected();
|
1628
1648
|
this.localParticipant?.handleParticipantDisconnected(participant.identity);
|
1629
1649
|
}
|
1630
1650
|
|
@@ -2221,6 +2241,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
2221
2241
|
status,
|
2222
2242
|
participant,
|
2223
2243
|
);
|
2244
|
+
})
|
2245
|
+
.on(ParticipantEvent.Active, () => {
|
2246
|
+
this.emitWhenConnected(RoomEvent.ParticipantActive, participant);
|
2224
2247
|
});
|
2225
2248
|
|
2226
2249
|
// update info at the end after callbacks have been set up
|
@@ -2611,6 +2634,7 @@ export type RoomEventCallbacks = {
|
|
2611
2634
|
reconnected: () => void;
|
2612
2635
|
disconnected: (reason?: DisconnectReason) => void;
|
2613
2636
|
connectionStateChanged: (state: ConnectionState) => void;
|
2637
|
+
moved: (name: string, token: string) => void;
|
2614
2638
|
mediaDevicesChanged: () => void;
|
2615
2639
|
participantConnected: (participant: RemoteParticipant) => void;
|
2616
2640
|
participantDisconnected: (participant: RemoteParticipant) => void;
|
@@ -2694,4 +2718,5 @@ export type RoomEventCallbacks = {
|
|
2694
2718
|
chatMessage: (message: ChatMessage, participant?: RemoteParticipant | LocalParticipant) => void;
|
2695
2719
|
localTrackSubscribed: (publication: LocalTrackPublication, participant: LocalParticipant) => void;
|
2696
2720
|
metricsReceived: (metrics: MetricsBatch, participant?: Participant) => void;
|
2721
|
+
participantActive: (participant: Participant) => void;
|
2697
2722
|
};
|
package/src/room/events.ts
CHANGED
@@ -52,6 +52,16 @@ export enum RoomEvent {
|
|
52
52
|
*/
|
53
53
|
ConnectionStateChanged = 'connectionStateChanged',
|
54
54
|
|
55
|
+
/**
|
56
|
+
* When participant has been moved to a different room by the service request.
|
57
|
+
* The behavior looks like the participant has been disconnected and reconnected to a different room
|
58
|
+
* seamlessly without connection state transition.
|
59
|
+
* A new token will be provided for reconnecting to the new room if needed.
|
60
|
+
*
|
61
|
+
* args: ([[room: string, token: string]])
|
62
|
+
*/
|
63
|
+
Moved = 'moved',
|
64
|
+
|
55
65
|
/**
|
56
66
|
* When input or output devices on the machine have changed.
|
57
67
|
*/
|
@@ -193,6 +203,13 @@ export enum RoomEvent {
|
|
193
203
|
*/
|
194
204
|
ParticipantAttributesChanged = 'participantAttributesChanged',
|
195
205
|
|
206
|
+
/**
|
207
|
+
* Emitted when the participant's state changes to ACTIVE and is ready to send/receive data messages
|
208
|
+
*
|
209
|
+
* args: (participant: [[Participant]])
|
210
|
+
*/
|
211
|
+
ParticipantActive = 'participantActive',
|
212
|
+
|
196
213
|
/**
|
197
214
|
* Room metadata is a simple way for app-specific state to be pushed to
|
198
215
|
* all users.
|
@@ -530,6 +547,11 @@ export enum ParticipantEvent {
|
|
530
547
|
|
531
548
|
/** only emitted on local participant */
|
532
549
|
ChatMessage = 'chatMessage',
|
550
|
+
|
551
|
+
/**
|
552
|
+
* Emitted when the participant's state changes to ACTIVE and is ready to send/receive data messages
|
553
|
+
*/
|
554
|
+
Active = 'active',
|
533
555
|
}
|
534
556
|
|
535
557
|
/** @internal */
|
@@ -563,6 +585,7 @@ export enum EngineEvent {
|
|
563
585
|
Offline = 'offline',
|
564
586
|
SignalRequestResponse = 'signalRequestResponse',
|
565
587
|
SignalConnected = 'signalConnected',
|
588
|
+
RoomMoved = 'roomMoved',
|
566
589
|
}
|
567
590
|
|
568
591
|
export enum TrackEvent {
|
@@ -1780,6 +1780,7 @@ export default class LocalParticipant extends Participant {
|
|
1780
1780
|
streamId,
|
1781
1781
|
topic: info.topic,
|
1782
1782
|
timestamp: numberToBigInt(Date.now()),
|
1783
|
+
attributes: info.attributes,
|
1783
1784
|
contentHeader: {
|
1784
1785
|
case: 'byteHeader',
|
1785
1786
|
value: new DataStream_ByteHeader({
|
@@ -2053,11 +2054,6 @@ export default class LocalParticipant extends Participant {
|
|
2053
2054
|
|
2054
2055
|
/** @internal */
|
2055
2056
|
updateInfo(info: ParticipantInfo): boolean {
|
2056
|
-
if (info.sid !== this.sid) {
|
2057
|
-
// drop updates that specify a wrong sid.
|
2058
|
-
// the sid for local participant is only explicitly set on join and full reconnect
|
2059
|
-
return false;
|
2060
|
-
}
|
2061
2057
|
if (!super.updateInfo(info)) {
|
2062
2058
|
return false;
|
2063
2059
|
}
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import {
|
2
2
|
DataPacket_Kind,
|
3
3
|
ParticipantInfo,
|
4
|
+
ParticipantInfo_State,
|
4
5
|
ParticipantInfo_Kind as ParticipantKind,
|
5
6
|
ParticipantPermission,
|
6
7
|
ConnectionQuality as ProtoQuality,
|
@@ -18,7 +19,7 @@ import { Track } from '../track/Track';
|
|
18
19
|
import type { TrackPublication } from '../track/TrackPublication';
|
19
20
|
import { diffAttributes } from '../track/utils';
|
20
21
|
import type { ChatMessage, LoggerOptions, TranscriptionSegment } from '../types';
|
21
|
-
import { isAudioTrack } from '../utils';
|
22
|
+
import { Future, isAudioTrack } from '../utils';
|
22
23
|
|
23
24
|
export enum ConnectionQuality {
|
24
25
|
Excellent = 'excellent',
|
@@ -93,6 +94,8 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
|
|
93
94
|
|
94
95
|
protected loggerOptions?: LoggerOptions;
|
95
96
|
|
97
|
+
protected activeFuture?: Future<void>;
|
98
|
+
|
96
99
|
protected get logContext() {
|
97
100
|
return {
|
98
101
|
...this.loggerOptions?.loggerContextCb?.(),
|
@@ -110,6 +113,10 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
|
|
110
113
|
return this.permissions?.agent || this.kind === ParticipantKind.AGENT;
|
111
114
|
}
|
112
115
|
|
116
|
+
get isActive() {
|
117
|
+
return this.participantInfo?.state === ParticipantInfo_State.ACTIVE;
|
118
|
+
}
|
119
|
+
|
113
120
|
get kind() {
|
114
121
|
return this._kind;
|
115
122
|
}
|
@@ -173,6 +180,28 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
|
|
173
180
|
}
|
174
181
|
}
|
175
182
|
|
183
|
+
/**
|
184
|
+
* Waits until the participant is active and ready to receive data messages
|
185
|
+
* @returns a promise that resolves when the participant is active
|
186
|
+
*/
|
187
|
+
waitUntilActive(): Promise<void> {
|
188
|
+
if (this.isActive) {
|
189
|
+
return Promise.resolve();
|
190
|
+
}
|
191
|
+
|
192
|
+
if (this.activeFuture) {
|
193
|
+
return this.activeFuture.promise;
|
194
|
+
}
|
195
|
+
|
196
|
+
this.activeFuture = new Future<void>();
|
197
|
+
|
198
|
+
this.once(ParticipantEvent.Active, () => {
|
199
|
+
this.activeFuture?.resolve?.();
|
200
|
+
this.activeFuture = undefined;
|
201
|
+
});
|
202
|
+
return this.activeFuture.promise;
|
203
|
+
}
|
204
|
+
|
176
205
|
get connectionQuality(): ConnectionQuality {
|
177
206
|
return this._connectionQuality;
|
178
207
|
}
|
@@ -224,12 +253,17 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
|
|
224
253
|
this._setName(info.name);
|
225
254
|
this._setMetadata(info.metadata);
|
226
255
|
this._setAttributes(info.attributes);
|
256
|
+
if (
|
257
|
+
info.state === ParticipantInfo_State.ACTIVE &&
|
258
|
+
this.participantInfo?.state !== ParticipantInfo_State.ACTIVE
|
259
|
+
) {
|
260
|
+
this.emit(ParticipantEvent.Active);
|
261
|
+
}
|
227
262
|
if (info.permission) {
|
228
263
|
this.setPermissions(info.permission);
|
229
264
|
}
|
230
265
|
// set this last so setMetadata can detect changes
|
231
266
|
this.participantInfo = info;
|
232
|
-
this.log.trace('update participant info', { ...this.logContext, info });
|
233
267
|
return true;
|
234
268
|
}
|
235
269
|
|
@@ -310,6 +344,16 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
|
|
310
344
|
}
|
311
345
|
}
|
312
346
|
|
347
|
+
/**
|
348
|
+
* @internal
|
349
|
+
*/
|
350
|
+
setDisconnected() {
|
351
|
+
if (this.activeFuture) {
|
352
|
+
this.activeFuture.reject?.(new Error('Participant disconnected'));
|
353
|
+
this.activeFuture = undefined;
|
354
|
+
}
|
355
|
+
}
|
356
|
+
|
313
357
|
/**
|
314
358
|
* @internal
|
315
359
|
*/
|
@@ -387,4 +431,5 @@ export type ParticipantEventCallbacks = {
|
|
387
431
|
attributesChanged: (changedAttributes: Record<string, string>) => void;
|
388
432
|
localTrackSubscribed: (trackPublication: LocalTrackPublication) => void;
|
389
433
|
chatMessage: (msg: ChatMessage) => void;
|
434
|
+
active: () => void;
|
390
435
|
};
|
@@ -104,8 +104,9 @@ export default class RemoteAudioTrack extends RemoteTrack<Track.Kind.Audio> {
|
|
104
104
|
}
|
105
105
|
|
106
106
|
if (this.sinkId && supportsSetSinkId(element)) {
|
107
|
-
|
108
|
-
|
107
|
+
element.setSinkId(this.sinkId).catch((e) => {
|
108
|
+
this.log.error('Failed to set sink id on remote audio track', e, this.logContext);
|
109
|
+
});
|
109
110
|
}
|
110
111
|
if (this.audioContext && needsNewWebAudioConnection) {
|
111
112
|
this.log.debug('using audio context mapping', this.logContext);
|
package/src/room/track/create.ts
CHANGED
@@ -78,18 +78,16 @@ export async function createLocalTracks(
|
|
78
78
|
deviceId: { ideal: deviceId },
|
79
79
|
};
|
80
80
|
}
|
81
|
-
// TODO if internal options don't have device Id specified, set it to 'default'
|
82
81
|
if (
|
83
82
|
internalOptions.audio === true ||
|
84
83
|
(typeof internalOptions.audio === 'object' && !internalOptions.audio.deviceId)
|
85
84
|
) {
|
86
85
|
internalOptions.audio = { deviceId: 'default' };
|
87
86
|
}
|
88
|
-
if (
|
89
|
-
internalOptions.video === true ||
|
90
|
-
(typeof internalOptions.video === 'object' && !internalOptions.video.deviceId)
|
91
|
-
) {
|
87
|
+
if (internalOptions.video === true) {
|
92
88
|
internalOptions.video = { deviceId: 'default' };
|
89
|
+
} else if (typeof internalOptions.video === 'object' && !internalOptions.video.deviceId) {
|
90
|
+
internalOptions.video.deviceId = 'default';
|
93
91
|
}
|
94
92
|
const opts = mergeDefaultOptions(internalOptions, audioDefaults, videoDefaults);
|
95
93
|
const constraints = constraintsForOptions(opts);
|
package/src/version.ts
CHANGED