livekit-client 2.3.2 → 2.4.1
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 +8 -6
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +268 -45
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts +5 -2
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/reconnect.d.ts.map +1 -1
- package/dist/src/e2ee/errors.d.ts +2 -1
- package/dist/src/e2ee/errors.d.ts.map +1 -1
- package/dist/src/e2ee/index.d.ts +1 -0
- package/dist/src/e2ee/index.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/logger.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/errors.d.ts +5 -0
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +15 -2
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +14 -6
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +8 -0
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/timers.d.ts +4 -4
- package/dist/src/room/timers.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts +1 -0
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/ts4.2/src/api/SignalClient.d.ts +5 -2
- package/dist/ts4.2/src/e2ee/errors.d.ts +2 -1
- package/dist/ts4.2/src/e2ee/index.d.ts +1 -0
- 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/errors.d.ts +5 -0
- package/dist/ts4.2/src/room/events.d.ts +15 -2
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +14 -6
- package/dist/ts4.2/src/room/participant/Participant.d.ts +8 -0
- package/dist/ts4.2/src/room/timers.d.ts +4 -4
- package/dist/ts4.2/src/room/track/utils.d.ts +1 -0
- package/package.json +2 -2
- package/src/api/SignalClient.ts +24 -2
- package/src/connectionHelper/checks/reconnect.ts +6 -3
- package/src/e2ee/errors.ts +8 -1
- package/src/e2ee/index.ts +1 -0
- package/src/e2ee/worker/FrameCryptor.ts +15 -3
- package/src/logger.ts +4 -3
- package/src/room/DeviceManager.ts +1 -1
- package/src/room/RTCEngine.ts +3 -0
- package/src/room/Room.ts +25 -3
- package/src/room/errors.ts +11 -0
- package/src/room/events.ts +15 -0
- package/src/room/participant/LocalParticipant.ts +92 -10
- package/src/room/participant/Participant.ts +23 -0
- package/src/room/track/utils.test.ts +35 -1
- package/src/room/track/utils.ts +22 -0
@@ -26,6 +26,7 @@ export default class LocalParticipant extends Participant {
|
|
26
26
|
private roomOptions;
|
27
27
|
private encryptionType;
|
28
28
|
private reconnectFuture?;
|
29
|
+
private pendingSignalRequests;
|
29
30
|
/** @internal */
|
30
31
|
constructor(sid: string, identity: string, engine: RTCEngine, options: InternalRoomOptions);
|
31
32
|
get lastCameraError(): Error | undefined;
|
@@ -40,22 +41,29 @@ export default class LocalParticipant extends Participant {
|
|
40
41
|
private handleReconnecting;
|
41
42
|
private handleReconnected;
|
42
43
|
private handleDisconnected;
|
44
|
+
private handleSignalRequestError;
|
43
45
|
/**
|
44
46
|
* Sets and updates the metadata of the local participant.
|
45
|
-
* The change does not take immediate effect.
|
46
|
-
* If successful, a `ParticipantEvent.MetadataChanged` event will be emitted on the local participant.
|
47
47
|
* Note: this requires `canUpdateOwnMetadata` permission.
|
48
|
+
* method will throw if the user doesn't have the required permissions
|
48
49
|
* @param metadata
|
49
50
|
*/
|
50
|
-
setMetadata(metadata: string): void
|
51
|
+
setMetadata(metadata: string): Promise<void>;
|
51
52
|
/**
|
52
53
|
* Sets and updates the name of the local participant.
|
53
|
-
* The change does not take immediate effect.
|
54
|
-
* If successful, a `ParticipantEvent.ParticipantNameChanged` event will be emitted on the local participant.
|
55
54
|
* Note: this requires `canUpdateOwnMetadata` permission.
|
55
|
+
* method will throw if the user doesn't have the required permissions
|
56
56
|
* @param metadata
|
57
57
|
*/
|
58
|
-
setName(name: string): void
|
58
|
+
setName(name: string): Promise<void>;
|
59
|
+
/**
|
60
|
+
* Set or update participant attributes. It will make updates only to keys that
|
61
|
+
* are present in `attributes`, and will not override others.
|
62
|
+
* Note: this requires `canUpdateOwnMetadata` permission.
|
63
|
+
* @param attributes attributes to update
|
64
|
+
*/
|
65
|
+
setAttributes(attributes: Record<string, string>): Promise<void>;
|
66
|
+
private requestMetadataUpdate;
|
59
67
|
/**
|
60
68
|
* Enable or disable a participant's camera track.
|
61
69
|
*
|
@@ -39,6 +39,7 @@ export default class Participant extends Participant_base {
|
|
39
39
|
name?: string;
|
40
40
|
/** client metadata, opaque to livekit */
|
41
41
|
metadata?: string;
|
42
|
+
private _attributes;
|
42
43
|
lastSpokeAt?: Date | undefined;
|
43
44
|
permissions?: ParticipantPermission;
|
44
45
|
protected _kind: ParticipantKind;
|
@@ -52,6 +53,8 @@ export default class Participant extends Participant_base {
|
|
52
53
|
get isEncrypted(): boolean;
|
53
54
|
get isAgent(): boolean;
|
54
55
|
get kind(): ParticipantKind;
|
56
|
+
/** participant attributes, similar to metadata, but as a key/value map */
|
57
|
+
get attributes(): Readonly<Record<string, string>>;
|
55
58
|
/** @internal */
|
56
59
|
constructor(sid: string, identity: string, name?: string, metadata?: string, loggerOptions?: LoggerOptions, kind?: ParticipantKind);
|
57
60
|
getTrackPublications(): TrackPublication[];
|
@@ -78,6 +81,10 @@ export default class Participant extends Participant_base {
|
|
78
81
|
**/
|
79
82
|
private _setMetadata;
|
80
83
|
private _setName;
|
84
|
+
/**
|
85
|
+
* Updates metadata from server
|
86
|
+
**/
|
87
|
+
private _setAttributes;
|
81
88
|
/** @internal */
|
82
89
|
setPermissions(permissions: ParticipantPermission): boolean;
|
83
90
|
/** @internal */
|
@@ -113,5 +120,6 @@ export type ParticipantEventCallbacks = {
|
|
113
120
|
audioStreamAcquired: () => void;
|
114
121
|
participantPermissionsChanged: (prevPermissions?: ParticipantPermission) => void;
|
115
122
|
trackSubscriptionStatusChanged: (publication: RemoteTrackPublication, status: TrackPublication.SubscriptionStatus) => void;
|
123
|
+
attributesChanged: (changedAttributes: Record<string, string>) => void;
|
116
124
|
};
|
117
125
|
//# sourceMappingURL=Participant.d.ts.map
|
@@ -4,9 +4,9 @@
|
|
4
4
|
* that the timer fires on time.
|
5
5
|
*/
|
6
6
|
export default class CriticalTimers {
|
7
|
-
static setTimeout: (
|
8
|
-
static setInterval: (
|
9
|
-
static clearTimeout: (
|
10
|
-
static clearInterval: (
|
7
|
+
static setTimeout: (callback: (args: void) => void, ms?: number | undefined) => NodeJS.Timeout;
|
8
|
+
static setInterval: (callback: (args: void) => void, ms?: number | undefined) => NodeJS.Timeout;
|
9
|
+
static clearTimeout: (timeoutId: string | number | NodeJS.Timeout | undefined) => void;
|
10
|
+
static clearInterval: (intervalId: string | number | NodeJS.Timeout | undefined) => void;
|
11
11
|
}
|
12
12
|
//# sourceMappingURL=timers.d.ts.map
|
@@ -30,4 +30,5 @@ export declare function mimeTypeToVideoCodecString(mimeType: string): "vp8" | "h
|
|
30
30
|
export declare function getTrackPublicationInfo<T extends TrackPublication>(tracks: T[]): TrackPublishedResponse[];
|
31
31
|
export declare function getLogContextFromTrack(track: Track | TrackPublication): Record<string, unknown>;
|
32
32
|
export declare function supportsSynchronizationSources(): boolean;
|
33
|
+
export declare function diffAttributes(oldValues: Record<string, string> | undefined, newValues: Record<string, string> | undefined): Record<string, string>;
|
33
34
|
//# sourceMappingURL=utils.d.ts.map
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "livekit-client",
|
3
|
-
"version": "2.
|
3
|
+
"version": "2.4.1",
|
4
4
|
"description": "JavaScript/TypeScript client SDK for LiveKit",
|
5
5
|
"main": "./dist/livekit-client.umd.js",
|
6
6
|
"unpkg": "./dist/livekit-client.umd.js",
|
@@ -36,7 +36,7 @@
|
|
36
36
|
"author": "David Zhao <david@davidzhao.com>",
|
37
37
|
"license": "Apache-2.0",
|
38
38
|
"dependencies": {
|
39
|
-
"@livekit/protocol": "1.19.
|
39
|
+
"@livekit/protocol": "1.19.1",
|
40
40
|
"events": "^3.3.0",
|
41
41
|
"loglevel": "^1.8.0",
|
42
42
|
"sdp-transform": "^2.14.1",
|
package/src/api/SignalClient.ts
CHANGED
@@ -4,6 +4,7 @@ import {
|
|
4
4
|
ClientInfo,
|
5
5
|
ConnectionQualityUpdate,
|
6
6
|
DisconnectReason,
|
7
|
+
ErrorResponse,
|
7
8
|
JoinResponse,
|
8
9
|
LeaveRequest,
|
9
10
|
LeaveRequest_Action,
|
@@ -141,6 +142,8 @@ export class SignalClient {
|
|
141
142
|
|
142
143
|
onLeave?: (leave: LeaveRequest) => void;
|
143
144
|
|
145
|
+
onErrorResponse?: (error: ErrorResponse) => void;
|
146
|
+
|
144
147
|
connectOptions?: ConnectOpts;
|
145
148
|
|
146
149
|
ws?: WebSocket;
|
@@ -163,6 +166,11 @@ export class SignalClient {
|
|
163
166
|
);
|
164
167
|
}
|
165
168
|
|
169
|
+
private getNextRequestId() {
|
170
|
+
this._requestId += 1;
|
171
|
+
return this._requestId;
|
172
|
+
}
|
173
|
+
|
166
174
|
private options?: SignalOptions;
|
167
175
|
|
168
176
|
private pingTimeout: ReturnType<typeof setTimeout> | undefined;
|
@@ -183,6 +191,8 @@ export class SignalClient {
|
|
183
191
|
|
184
192
|
private loggerContextCb?: LoggerOptions['loggerContextCb'];
|
185
193
|
|
194
|
+
private _requestId = 0;
|
195
|
+
|
186
196
|
constructor(useJSON: boolean = false, loggerOptions: LoggerOptions = {}) {
|
187
197
|
this.log = getLogger(loggerOptions.loggerName ?? LoggerNames.Signal);
|
188
198
|
this.loggerContextCb = loggerOptions.loggerContextCb;
|
@@ -511,14 +521,22 @@ export class SignalClient {
|
|
511
521
|
});
|
512
522
|
}
|
513
523
|
|
514
|
-
sendUpdateLocalMetadata(
|
515
|
-
|
524
|
+
async sendUpdateLocalMetadata(
|
525
|
+
metadata: string,
|
526
|
+
name: string,
|
527
|
+
attributes: Record<string, string> = {},
|
528
|
+
) {
|
529
|
+
const requestId = this.getNextRequestId();
|
530
|
+
await this.sendRequest({
|
516
531
|
case: 'updateMetadata',
|
517
532
|
value: new UpdateParticipantMetadata({
|
533
|
+
requestId,
|
518
534
|
metadata,
|
519
535
|
name,
|
536
|
+
attributes,
|
520
537
|
}),
|
521
538
|
});
|
539
|
+
return requestId;
|
522
540
|
}
|
523
541
|
|
524
542
|
sendUpdateTrackSettings(settings: UpdateTrackSettings) {
|
@@ -721,6 +739,10 @@ export class SignalClient {
|
|
721
739
|
this.rtt = Date.now() - Number.parseInt(msg.value.lastPingTimestamp.toString());
|
722
740
|
this.resetPingTimeout();
|
723
741
|
pingHandled = true;
|
742
|
+
} else if (msg.case === 'errorResponse') {
|
743
|
+
if (this.onErrorResponse) {
|
744
|
+
this.onErrorResponse(msg.value);
|
745
|
+
}
|
724
746
|
} else {
|
725
747
|
this.log.debug('unsupported message', { ...this.logContext, msgCase: msg.case });
|
726
748
|
}
|
@@ -18,10 +18,13 @@ export class ReconnectCheck extends Checker {
|
|
18
18
|
reconnectResolver = resolve;
|
19
19
|
});
|
20
20
|
|
21
|
+
const handleReconnecting = () => {
|
22
|
+
reconnectingTriggered = true;
|
23
|
+
};
|
24
|
+
|
21
25
|
room
|
22
|
-
.on(RoomEvent.
|
23
|
-
|
24
|
-
})
|
26
|
+
.on(RoomEvent.SignalReconnecting, handleReconnecting)
|
27
|
+
.on(RoomEvent.Reconnecting, handleReconnecting)
|
25
28
|
.on(RoomEvent.Reconnected, () => {
|
26
29
|
reconnected = true;
|
27
30
|
reconnectResolver(true);
|
package/src/e2ee/errors.ts
CHANGED
@@ -9,8 +9,15 @@ export enum CryptorErrorReason {
|
|
9
9
|
export class CryptorError extends LivekitError {
|
10
10
|
reason: CryptorErrorReason;
|
11
11
|
|
12
|
-
|
12
|
+
participantIdentity?: string;
|
13
|
+
|
14
|
+
constructor(
|
15
|
+
message?: string,
|
16
|
+
reason: CryptorErrorReason = CryptorErrorReason.InternalError,
|
17
|
+
participantIdentity?: string,
|
18
|
+
) {
|
13
19
|
super(40, message);
|
14
20
|
this.reason = reason;
|
21
|
+
this.participantIdentity = participantIdentity;
|
15
22
|
}
|
16
23
|
}
|
package/src/e2ee/index.ts
CHANGED
@@ -183,7 +183,12 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
183
183
|
.pipeTo(writable)
|
184
184
|
.catch((e) => {
|
185
185
|
workerLogger.warn(e);
|
186
|
-
this.emit(
|
186
|
+
this.emit(
|
187
|
+
CryptorEvent.Error,
|
188
|
+
e instanceof CryptorError
|
189
|
+
? e
|
190
|
+
: new CryptorError(e.message, undefined, this.participantIdentity),
|
191
|
+
);
|
187
192
|
});
|
188
193
|
this.trackId = trackId;
|
189
194
|
}
|
@@ -294,10 +299,14 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
294
299
|
workerLogger.error(e);
|
295
300
|
}
|
296
301
|
} else {
|
297
|
-
workerLogger.debug('failed to
|
302
|
+
workerLogger.debug('failed to encrypt, emitting error', this.logContext);
|
298
303
|
this.emit(
|
299
304
|
CryptorEvent.Error,
|
300
|
-
new CryptorError(
|
305
|
+
new CryptorError(
|
306
|
+
`encryption key missing for encoding`,
|
307
|
+
CryptorErrorReason.MissingKey,
|
308
|
+
this.participantIdentity,
|
309
|
+
),
|
301
310
|
);
|
302
311
|
}
|
303
312
|
}
|
@@ -367,6 +376,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
367
376
|
new CryptorError(
|
368
377
|
`missing key at index ${keyIndex} for participant ${this.participantIdentity}`,
|
369
378
|
CryptorErrorReason.MissingKey,
|
379
|
+
this.participantIdentity,
|
370
380
|
),
|
371
381
|
);
|
372
382
|
}
|
@@ -487,12 +497,14 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
487
497
|
throw new CryptorError(
|
488
498
|
`valid key missing for participant ${this.participantIdentity}`,
|
489
499
|
CryptorErrorReason.InvalidKey,
|
500
|
+
this.participantIdentity,
|
490
501
|
);
|
491
502
|
}
|
492
503
|
} else {
|
493
504
|
throw new CryptorError(
|
494
505
|
`Decryption failed: ${error.message}`,
|
495
506
|
CryptorErrorReason.InvalidKey,
|
507
|
+
this.participantIdentity,
|
496
508
|
);
|
497
509
|
}
|
498
510
|
}
|
package/src/logger.ts
CHANGED
@@ -54,9 +54,10 @@ export function getLogger(name: string) {
|
|
54
54
|
export function setLogLevel(level: LogLevel | LogLevelString, loggerName?: LoggerNames) {
|
55
55
|
if (loggerName) {
|
56
56
|
log.getLogger(loggerName).setLevel(level);
|
57
|
-
}
|
58
|
-
|
59
|
-
|
57
|
+
} else {
|
58
|
+
for (const logger of livekitLoggers) {
|
59
|
+
logger.setLevel(level);
|
60
|
+
}
|
60
61
|
}
|
61
62
|
}
|
62
63
|
|
@@ -41,7 +41,7 @@ export default class DeviceManager {
|
|
41
41
|
!(isSafari() && this.hasDeviceInUse(kind))
|
42
42
|
) {
|
43
43
|
const isDummyDeviceOrEmpty =
|
44
|
-
devices.length === 0 ||
|
44
|
+
devices.filter((d) => d.kind === kind).length === 0 ||
|
45
45
|
devices.some((device) => {
|
46
46
|
const noLabel = device.label === '';
|
47
47
|
const isRelevant = kind ? device.kind === kind : true;
|
package/src/room/RTCEngine.ts
CHANGED
@@ -7,6 +7,7 @@ import {
|
|
7
7
|
DataPacket,
|
8
8
|
DataPacket_Kind,
|
9
9
|
DisconnectReason,
|
10
|
+
ErrorResponse,
|
10
11
|
type JoinResponse,
|
11
12
|
type LeaveRequest,
|
12
13
|
LeaveRequest_Action,
|
@@ -193,6 +194,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
193
194
|
this.emit(EngineEvent.SubscriptionPermissionUpdate, update);
|
194
195
|
this.client.onSpeakersChanged = (update) => this.emit(EngineEvent.SpeakersChanged, update);
|
195
196
|
this.client.onStreamStateUpdate = (update) => this.emit(EngineEvent.StreamStateChanged, update);
|
197
|
+
this.client.onErrorResponse = (error) => this.emit(EngineEvent.SignalRequestError, error);
|
196
198
|
}
|
197
199
|
|
198
200
|
/** @internal */
|
@@ -1412,6 +1414,7 @@ export type EngineEventCallbacks = {
|
|
1412
1414
|
localTrackUnpublished: (unpublishedResponse: TrackUnpublishedResponse) => void;
|
1413
1415
|
remoteMute: (trackSid: string, muted: boolean) => void;
|
1414
1416
|
offline: () => void;
|
1417
|
+
signalRequestError: (error: ErrorResponse) => void;
|
1415
1418
|
};
|
1416
1419
|
|
1417
1420
|
function supportOptionalDatachannel(protocol: number | undefined): boolean {
|
package/src/room/Room.ts
CHANGED
@@ -468,9 +468,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
468
468
|
// trigger the first fetch without waiting for a response
|
469
469
|
// if initial connection fails, this will speed up picking regional url
|
470
470
|
// on subsequent runs
|
471
|
-
this.regionUrlProvider
|
472
|
-
|
473
|
-
|
471
|
+
this.regionUrlProvider
|
472
|
+
.fetchRegionSettings()
|
473
|
+
.then((settings) => {
|
474
|
+
this.regionUrlProvider?.setServerReportedRegions(settings);
|
475
|
+
})
|
476
|
+
.catch((e) => {
|
477
|
+
this.log.warn('could not fetch region settings', { ...this.logContext, error: e });
|
478
|
+
});
|
474
479
|
}
|
475
480
|
|
476
481
|
const connectFn = async (
|
@@ -1116,6 +1121,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1116
1121
|
this.localParticipant
|
1117
1122
|
.on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
|
1118
1123
|
.on(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged)
|
1124
|
+
.on(ParticipantEvent.AttributesChanged, this.onLocalAttributesChanged)
|
1119
1125
|
.on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
|
1120
1126
|
.on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
|
1121
1127
|
.on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
|
@@ -1294,6 +1300,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1294
1300
|
this.localParticipant
|
1295
1301
|
.off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
|
1296
1302
|
.off(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged)
|
1303
|
+
.off(ParticipantEvent.AttributesChanged, this.onLocalAttributesChanged)
|
1297
1304
|
.off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
|
1298
1305
|
.off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
|
1299
1306
|
.off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
|
@@ -1717,6 +1724,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1717
1724
|
.on(ParticipantEvent.ParticipantNameChanged, (name) => {
|
1718
1725
|
this.emitWhenConnected(RoomEvent.ParticipantNameChanged, name, participant);
|
1719
1726
|
})
|
1727
|
+
.on(ParticipantEvent.AttributesChanged, (changedAttributes: Record<string, string>) => {
|
1728
|
+
this.emitWhenConnected(
|
1729
|
+
RoomEvent.ParticipantAttributesChanged,
|
1730
|
+
changedAttributes,
|
1731
|
+
participant,
|
1732
|
+
);
|
1733
|
+
})
|
1720
1734
|
.on(ParticipantEvent.ConnectionQualityChanged, (quality: ConnectionQuality) => {
|
1721
1735
|
this.emitWhenConnected(RoomEvent.ConnectionQualityChanged, quality, participant);
|
1722
1736
|
})
|
@@ -1865,6 +1879,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1865
1879
|
this.emit(RoomEvent.ParticipantNameChanged, name, this.localParticipant);
|
1866
1880
|
};
|
1867
1881
|
|
1882
|
+
private onLocalAttributesChanged = (changedAttributes: Record<string, string>) => {
|
1883
|
+
this.emit(RoomEvent.ParticipantAttributesChanged, changedAttributes, this.localParticipant);
|
1884
|
+
};
|
1885
|
+
|
1868
1886
|
private onLocalTrackMuted = (pub: TrackPublication) => {
|
1869
1887
|
this.emit(RoomEvent.TrackMuted, pub, this.localParticipant);
|
1870
1888
|
};
|
@@ -2131,6 +2149,10 @@ export type RoomEventCallbacks = {
|
|
2131
2149
|
prevPermissions: ParticipantPermission | undefined,
|
2132
2150
|
participant: RemoteParticipant | LocalParticipant,
|
2133
2151
|
) => void;
|
2152
|
+
participantAttributesChanged: (
|
2153
|
+
changedAttributes: Record<string, string>,
|
2154
|
+
participant: RemoteParticipant | LocalParticipant,
|
2155
|
+
) => void;
|
2134
2156
|
activeSpeakersChanged: (speakers: Array<Participant>) => void;
|
2135
2157
|
roomMetadataChanged: (metadata: string) => void;
|
2136
2158
|
dataReceived: (
|
package/src/room/errors.ts
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
import { ErrorResponse_Reason } from '@livekit/protocol';
|
2
|
+
|
1
3
|
export class LivekitError extends Error {
|
2
4
|
code: number;
|
3
5
|
|
@@ -63,6 +65,15 @@ export class PublishDataError extends LivekitError {
|
|
63
65
|
}
|
64
66
|
}
|
65
67
|
|
68
|
+
export class SignalRequestError extends LivekitError {
|
69
|
+
reason: ErrorResponse_Reason;
|
70
|
+
|
71
|
+
constructor(message: string, reason: ErrorResponse_Reason = ErrorResponse_Reason.UNKNOWN) {
|
72
|
+
super(15, message);
|
73
|
+
this.reason = reason;
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
66
77
|
export enum MediaDeviceFailure {
|
67
78
|
// user rejected permissions
|
68
79
|
PermissionDenied = 'PermissionDenied',
|
package/src/room/events.ts
CHANGED
@@ -185,6 +185,13 @@ export enum RoomEvent {
|
|
185
185
|
*/
|
186
186
|
ParticipantNameChanged = 'participantNameChanged',
|
187
187
|
|
188
|
+
/**
|
189
|
+
* Participant attributes is an app-specific key value state to be pushed to
|
190
|
+
* all users.
|
191
|
+
* When a participant's attributes changed, this event will be emitted with the changed attributes and the participant
|
192
|
+
*/
|
193
|
+
ParticipantAttributesChanged = 'participantAttributesChanged',
|
194
|
+
|
188
195
|
/**
|
189
196
|
* Room metadata is a simple way for app-specific state to be pushed to
|
190
197
|
* all users.
|
@@ -495,6 +502,13 @@ export enum ParticipantEvent {
|
|
495
502
|
|
496
503
|
/** @internal */
|
497
504
|
PCTrackAdded = 'pcTrackAdded',
|
505
|
+
|
506
|
+
/**
|
507
|
+
* Participant attributes is an app-specific key value state to be pushed to
|
508
|
+
* all users.
|
509
|
+
* When a participant's attributes changed, this event will be emitted with the changed attributes
|
510
|
+
*/
|
511
|
+
AttributesChanged = 'attributesChanged',
|
498
512
|
}
|
499
513
|
|
500
514
|
/** @internal */
|
@@ -525,6 +539,7 @@ export enum EngineEvent {
|
|
525
539
|
SubscribedQualityUpdate = 'subscribedQualityUpdate',
|
526
540
|
LocalTrackUnpublished = 'localTrackUnpublished',
|
527
541
|
Offline = 'offline',
|
542
|
+
SignalRequestError = 'signalRequestError',
|
528
543
|
}
|
529
544
|
|
530
545
|
export enum TrackEvent {
|
@@ -3,6 +3,7 @@ import {
|
|
3
3
|
DataPacket,
|
4
4
|
DataPacket_Kind,
|
5
5
|
Encryption_Type,
|
6
|
+
ErrorResponse,
|
6
7
|
ParticipantInfo,
|
7
8
|
ParticipantPermission,
|
8
9
|
SimulcastCodec,
|
@@ -14,7 +15,13 @@ import type { InternalRoomOptions } from '../../options';
|
|
14
15
|
import { PCTransportState } from '../PCTransportManager';
|
15
16
|
import type RTCEngine from '../RTCEngine';
|
16
17
|
import { defaultVideoCodec } from '../defaults';
|
17
|
-
import {
|
18
|
+
import {
|
19
|
+
DeviceUnsupportedError,
|
20
|
+
LivekitError,
|
21
|
+
SignalRequestError,
|
22
|
+
TrackInvalidError,
|
23
|
+
UnexpectedConnectionState,
|
24
|
+
} from '../errors';
|
18
25
|
import { EngineEvent, ParticipantEvent, TrackEvent } from '../events';
|
19
26
|
import LocalAudioTrack from '../track/LocalAudioTrack';
|
20
27
|
import LocalTrack from '../track/LocalTrack';
|
@@ -46,6 +53,7 @@ import {
|
|
46
53
|
isSVCCodec,
|
47
54
|
isSafari17,
|
48
55
|
isWeb,
|
56
|
+
sleep,
|
49
57
|
supportsAV1,
|
50
58
|
supportsVP9,
|
51
59
|
} from '../utils';
|
@@ -92,6 +100,15 @@ export default class LocalParticipant extends Participant {
|
|
92
100
|
|
93
101
|
private reconnectFuture?: Future<void>;
|
94
102
|
|
103
|
+
private pendingSignalRequests: Map<
|
104
|
+
number,
|
105
|
+
{
|
106
|
+
resolve: (arg: any) => void;
|
107
|
+
reject: (reason: LivekitError) => void;
|
108
|
+
values: Partial<Record<keyof LocalParticipant, any>>;
|
109
|
+
}
|
110
|
+
>;
|
111
|
+
|
95
112
|
/** @internal */
|
96
113
|
constructor(sid: string, identity: string, engine: RTCEngine, options: InternalRoomOptions) {
|
97
114
|
super(sid, identity, undefined, undefined, {
|
@@ -105,6 +122,7 @@ export default class LocalParticipant extends Participant {
|
|
105
122
|
this.roomOptions = options;
|
106
123
|
this.setupEngine(engine);
|
107
124
|
this.activeDeviceMap = new Map();
|
125
|
+
this.pendingSignalRequests = new Map();
|
108
126
|
}
|
109
127
|
|
110
128
|
get lastCameraError(): Error | undefined {
|
@@ -158,7 +176,8 @@ export default class LocalParticipant extends Participant {
|
|
158
176
|
.on(EngineEvent.Resuming, this.handleReconnecting)
|
159
177
|
.on(EngineEvent.LocalTrackUnpublished, this.handleLocalTrackUnpublished)
|
160
178
|
.on(EngineEvent.SubscribedQualityUpdate, this.handleSubscribedQualityUpdate)
|
161
|
-
.on(EngineEvent.Disconnected, this.handleDisconnected)
|
179
|
+
.on(EngineEvent.Disconnected, this.handleDisconnected)
|
180
|
+
.on(EngineEvent.SignalRequestError, this.handleSignalRequestError);
|
162
181
|
}
|
163
182
|
|
164
183
|
private handleReconnecting = () => {
|
@@ -181,26 +200,89 @@ export default class LocalParticipant extends Participant {
|
|
181
200
|
}
|
182
201
|
};
|
183
202
|
|
203
|
+
private handleSignalRequestError = (error: ErrorResponse) => {
|
204
|
+
const { requestId, reason, message } = error;
|
205
|
+
const failedRequest = this.pendingSignalRequests.get(requestId);
|
206
|
+
if (failedRequest) {
|
207
|
+
failedRequest.reject(new SignalRequestError(message, reason));
|
208
|
+
this.pendingSignalRequests.delete(requestId);
|
209
|
+
}
|
210
|
+
};
|
211
|
+
|
184
212
|
/**
|
185
213
|
* Sets and updates the metadata of the local participant.
|
186
|
-
* The change does not take immediate effect.
|
187
|
-
* If successful, a `ParticipantEvent.MetadataChanged` event will be emitted on the local participant.
|
188
214
|
* Note: this requires `canUpdateOwnMetadata` permission.
|
215
|
+
* method will throw if the user doesn't have the required permissions
|
189
216
|
* @param metadata
|
190
217
|
*/
|
191
|
-
setMetadata(metadata: string): void {
|
192
|
-
this.
|
218
|
+
async setMetadata(metadata: string): Promise<void> {
|
219
|
+
await this.requestMetadataUpdate({ metadata });
|
193
220
|
}
|
194
221
|
|
195
222
|
/**
|
196
223
|
* Sets and updates the name of the local participant.
|
197
|
-
* The change does not take immediate effect.
|
198
|
-
* If successful, a `ParticipantEvent.ParticipantNameChanged` event will be emitted on the local participant.
|
199
224
|
* Note: this requires `canUpdateOwnMetadata` permission.
|
225
|
+
* method will throw if the user doesn't have the required permissions
|
200
226
|
* @param metadata
|
201
227
|
*/
|
202
|
-
setName(name: string): void {
|
203
|
-
this.
|
228
|
+
async setName(name: string): Promise<void> {
|
229
|
+
await this.requestMetadataUpdate({ name });
|
230
|
+
}
|
231
|
+
|
232
|
+
/**
|
233
|
+
* Set or update participant attributes. It will make updates only to keys that
|
234
|
+
* are present in `attributes`, and will not override others.
|
235
|
+
* Note: this requires `canUpdateOwnMetadata` permission.
|
236
|
+
* @param attributes attributes to update
|
237
|
+
*/
|
238
|
+
async setAttributes(attributes: Record<string, string>) {
|
239
|
+
await this.requestMetadataUpdate({ attributes });
|
240
|
+
}
|
241
|
+
|
242
|
+
private async requestMetadataUpdate({
|
243
|
+
metadata,
|
244
|
+
name,
|
245
|
+
attributes,
|
246
|
+
}: {
|
247
|
+
metadata?: string;
|
248
|
+
name?: string;
|
249
|
+
attributes?: Record<string, string>;
|
250
|
+
}) {
|
251
|
+
return new Promise<void>(async (resolve, reject) => {
|
252
|
+
try {
|
253
|
+
let isRejected = false;
|
254
|
+
const requestId = await this.engine.client.sendUpdateLocalMetadata(
|
255
|
+
metadata ?? this.metadata ?? '',
|
256
|
+
name ?? this.name ?? '',
|
257
|
+
attributes,
|
258
|
+
);
|
259
|
+
const startTime = performance.now();
|
260
|
+
this.pendingSignalRequests.set(requestId, {
|
261
|
+
resolve,
|
262
|
+
reject: (error: LivekitError) => {
|
263
|
+
reject(error);
|
264
|
+
isRejected = true;
|
265
|
+
},
|
266
|
+
values: { name, metadata, attributes },
|
267
|
+
});
|
268
|
+
while (performance.now() - startTime < 5_000 && !isRejected) {
|
269
|
+
if (
|
270
|
+
(!name || this.name === name) &&
|
271
|
+
(!metadata || this.metadata === metadata) &&
|
272
|
+
(!attributes ||
|
273
|
+
Object.entries(attributes).every(([key, value]) => this.attributes[key] === value))
|
274
|
+
) {
|
275
|
+
this.pendingSignalRequests.delete(requestId);
|
276
|
+
resolve();
|
277
|
+
return;
|
278
|
+
}
|
279
|
+
await sleep(50);
|
280
|
+
}
|
281
|
+
reject(new SignalRequestError('Request to update local metadata timed out'));
|
282
|
+
} catch (e: any) {
|
283
|
+
if (e instanceof Error) reject(e);
|
284
|
+
}
|
285
|
+
});
|
204
286
|
}
|
205
287
|
|
206
288
|
/**
|