@memberjunction/livekit-room-core 6.2.0-edge.2 → 6.2.0-edge.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -1
- package/dist/agent-avatar.d.ts +18 -0
- package/dist/agent-avatar.d.ts.map +1 -0
- package/dist/agent-avatar.js +25 -0
- package/dist/agent-avatar.js.map +1 -0
- package/dist/audio-meter.d.ts +24 -8
- package/dist/audio-meter.d.ts.map +1 -1
- package/dist/audio-meter.js +19 -8
- package/dist/audio-meter.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/livekit-preview-room-controller.d.ts +162 -0
- package/dist/livekit-preview-room-controller.d.ts.map +1 -0
- package/dist/livekit-preview-room-controller.js +475 -0
- package/dist/livekit-preview-room-controller.js.map +1 -0
- package/dist/livekit-preview.d.ts +6 -1
- package/dist/livekit-preview.d.ts.map +1 -1
- package/dist/livekit-preview.js +6 -1
- package/dist/livekit-preview.js.map +1 -1
- package/dist/livekit-room-controller.d.ts +132 -11
- package/dist/livekit-room-controller.d.ts.map +1 -1
- package/dist/livekit-room-controller.js +174 -35
- package/dist/livekit-room-controller.js.map +1 -1
- package/dist/livekit-screen-share.d.ts +73 -0
- package/dist/livekit-screen-share.d.ts.map +1 -0
- package/dist/livekit-screen-share.js +137 -0
- package/dist/livekit-screen-share.js.map +1 -0
- package/dist/media-adapters.d.ts +26 -0
- package/dist/media-adapters.d.ts.map +1 -0
- package/dist/media-adapters.js +101 -0
- package/dist/media-adapters.js.map +1 -0
- package/dist/types.d.ts +78 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -2
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { Participant, Room, type RoomOptions } from 'livekit-client';
|
|
16
16
|
import { Observable } from 'rxjs';
|
|
17
|
+
import { type DisplayCaptureOptions, type DisplayCaptureResult, type DisplayCaptureSurface } from '@memberjunction/ai-realtime-client/media';
|
|
17
18
|
import { LiveKitRoomEventBus } from './events.js';
|
|
18
19
|
import { LiveKitBackgroundEffect, LiveKitConnectionStatus, LiveKitDevice, LiveKitParticipantRole, LiveKitRoomConnectOptions, LiveKitRoomState } from './types.js';
|
|
19
20
|
/**
|
|
@@ -43,12 +44,82 @@ export interface LiveKitRoomControllerOptions {
|
|
|
43
44
|
RoleResolver?: LiveKitRoleResolver;
|
|
44
45
|
/** Supply a shared event bus (e.g. so a host can subscribe before the controller connects). */
|
|
45
46
|
EventBus?: LiveKitRoomEventBus;
|
|
47
|
+
/**
|
|
48
|
+
* Opens the browser's share picker for a screen share. Defaults to `/media`'s `RequestDisplayCapture`, as in the
|
|
49
|
+
* preview room; a test passes its own.
|
|
50
|
+
*/
|
|
51
|
+
RequestScreenShare?: (options: DisplayCaptureOptions) => Promise<DisplayCaptureResult>;
|
|
46
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* What a meeting room UI drives: the connection, the local microphone, camera and screen share, the data channel,
|
|
55
|
+
* effects, devices, and the room's state and events. {@link LiveKitRoomController} is the LiveKit implementation;
|
|
56
|
+
* another implementation (a test fake, a simulated room) runs the same UI without a LiveKit server.
|
|
57
|
+
*/
|
|
58
|
+
export interface ILiveKitRoomController {
|
|
59
|
+
/** The room's cancelable event bus (Before-events + notifications). */
|
|
60
|
+
readonly Events: LiveKitRoomEventBus;
|
|
61
|
+
/** The room-state snapshot: the current value on subscribe, then every change. */
|
|
62
|
+
readonly State$: Observable<LiveKitRoomState>;
|
|
63
|
+
/** The current room-state snapshot. */
|
|
64
|
+
readonly State: LiveKitRoomState;
|
|
65
|
+
/** The current connection status. */
|
|
66
|
+
readonly Status: LiveKitConnectionStatus;
|
|
67
|
+
/**
|
|
68
|
+
* Joins the room and brings the local participant online with the requested media. Only a failed connection fails
|
|
69
|
+
* the join; a microphone or camera that fails to start is left off and reported as a `device` error.
|
|
70
|
+
*/
|
|
71
|
+
Connect(serverUrl: string, token: string, options?: LiveKitRoomConnectOptions): Promise<void>;
|
|
72
|
+
/** Leaves the room. Resolves `false` when a `beforeDisconnect` handler canceled it. */
|
|
73
|
+
Disconnect(userInitiated?: boolean): Promise<boolean>;
|
|
74
|
+
/** Leaves the room and releases everything; the controller is not used again. */
|
|
75
|
+
Dispose(): void;
|
|
76
|
+
/** Turns the local microphone on or off. */
|
|
77
|
+
SetMicrophoneEnabled(enabled: boolean): Promise<void>;
|
|
78
|
+
/** Turns the local camera on or off. */
|
|
79
|
+
SetCameraEnabled(enabled: boolean): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Starts or stops sharing. `request` is the kind of surface the browser's picker offers first, or the share's
|
|
82
|
+
* `/media` options: a surface preference, or one panel of the page to share alone (`Panel`) with its name
|
|
83
|
+
* (`PanelLabel`).
|
|
84
|
+
*/
|
|
85
|
+
SetScreenShareEnabled(enabled: boolean, request?: DisplayCaptureSurface | DisplayCaptureOptions): Promise<void>;
|
|
86
|
+
/** Toggles the microphone and resolves the new state. */
|
|
87
|
+
ToggleMicrophone(): Promise<boolean>;
|
|
88
|
+
/** Toggles the camera and resolves the new state. */
|
|
89
|
+
ToggleCamera(): Promise<boolean>;
|
|
90
|
+
/** Toggles screen sharing and resolves the new state. */
|
|
91
|
+
ToggleScreenShare(): Promise<boolean>;
|
|
92
|
+
/** Shares something else: stops the current share, then asks the browser's picker again, with `request` as above. */
|
|
93
|
+
ChangeScreenShare(request?: DisplayCaptureSurface | DisplayCaptureOptions): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Sends a text message to the room on the data channel. Resolves `true` when the message went out, and `false` when it
|
|
96
|
+
* did not: no room, a `beforeSendData` handler canceled it, or the send failed (also raised as an `error` of kind
|
|
97
|
+
* `data`).
|
|
98
|
+
*/
|
|
99
|
+
SendData(text: string, topic?: string): Promise<boolean>;
|
|
100
|
+
/** Resumes audio playback after a browser autoplay block (call from a user gesture). */
|
|
101
|
+
StartAudio(): Promise<void>;
|
|
102
|
+
/** Turns the noise filter on or off; resolves `false` when it cannot be applied. */
|
|
103
|
+
SetNoiseFilterEnabled(enabled: boolean): Promise<boolean>;
|
|
104
|
+
/** Applies a camera background effect; resolves `false` when it cannot be applied. */
|
|
105
|
+
SetBackgroundEffect(effect: LiveKitBackgroundEffect): Promise<boolean>;
|
|
106
|
+
/** Lists the devices of a kind. */
|
|
107
|
+
ListDevices(kind: LiveKitDevice['Kind']): Promise<LiveKitDevice[]>;
|
|
108
|
+
/** The device in use for a kind, or `null` when unknown or not connected. */
|
|
109
|
+
GetActiveDeviceId(kind: LiveKitDevice['Kind']): string | null;
|
|
110
|
+
/** Moves a kind to another device. */
|
|
111
|
+
SwitchDevice(kind: LiveKitDevice['Kind'], deviceId: string): Promise<void>;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Speech-tuned room options for LiveKit connections in MJ Meet and agent interactions.
|
|
115
|
+
* Prioritizes low latency, natural interruptions, packet loss resilience (RED), and voice quality.
|
|
116
|
+
*/
|
|
117
|
+
export declare const DEFAULT_SPEECH_ROOM_OPTIONS: RoomOptions;
|
|
47
118
|
/**
|
|
48
119
|
* Controls a single LiveKit room: connect/disconnect, local-media toggles, data messages, device
|
|
49
120
|
* switching, and a normalized observable state snapshot. One controller == one room connection.
|
|
50
121
|
*/
|
|
51
|
-
export declare class LiveKitRoomController {
|
|
122
|
+
export declare class LiveKitRoomController implements ILiveKitRoomController {
|
|
52
123
|
private room;
|
|
53
124
|
private readonly roomFactory;
|
|
54
125
|
private readonly roleResolver;
|
|
@@ -56,6 +127,8 @@ export declare class LiveKitRoomController {
|
|
|
56
127
|
private backgroundEffect;
|
|
57
128
|
private e2eeEnabled;
|
|
58
129
|
private e2eeKeyProvider;
|
|
130
|
+
/** The local screen share, through `/media` display capture. */
|
|
131
|
+
private readonly screenShare;
|
|
59
132
|
private readonly stateSubject;
|
|
60
133
|
private readonly textDecoder;
|
|
61
134
|
private readonly textEncoder;
|
|
@@ -85,6 +158,12 @@ export declare class LiveKitRoomController {
|
|
|
85
158
|
/**
|
|
86
159
|
* Connects to a LiveKit room and brings the local participant online with the requested media.
|
|
87
160
|
*
|
|
161
|
+
* Only the connection can fail the join: then it rejects, the status is `'error'` and a `connect` error is emitted.
|
|
162
|
+
* The microphone and camera are turned on once connected, each on its own; one that fails (the browser refused it,
|
|
163
|
+
* or it is in use) is left off and reported as a `device` error, and the join goes on, so the user is in the room
|
|
164
|
+
* and can turn it on again. If the user leaves while a device starts (the browser's permission prompt can stay open
|
|
165
|
+
* a while), no other device starts and no `connected` is emitted.
|
|
166
|
+
*
|
|
88
167
|
* @param serverUrl The LiveKit server URL (e.g. `wss://livekit.myorg.com`).
|
|
89
168
|
* @param token The signed access token authorizing this participant to join a specific room.
|
|
90
169
|
* @param options Initial media + display-name options.
|
|
@@ -106,21 +185,36 @@ export declare class LiveKitRoomController {
|
|
|
106
185
|
SetMicrophoneEnabled(enabled: boolean): Promise<void>;
|
|
107
186
|
/** Enables or disables the local camera. */
|
|
108
187
|
SetCameraEnabled(enabled: boolean): Promise<void>;
|
|
109
|
-
/**
|
|
110
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Starts or stops local screen sharing, through `/media` display capture published as this participant's screen
|
|
190
|
+
* share (capped at 1080p and 30 fps, as LiveKit's own capture is). `request` is the kind of surface the browser's
|
|
191
|
+
* picker offers first (Chromium reads it; the user can still pick another kind), or the share's options: one panel
|
|
192
|
+
* of the page to share alone (`Panel`, Chrome and Edge), with its name (`PanelLabel`). A picker the user closes
|
|
193
|
+
* shares nothing and is no error; a share the browser refuses is a `device` error.
|
|
194
|
+
*/
|
|
195
|
+
SetScreenShareEnabled(enabled: boolean, request?: DisplayCaptureSurface | DisplayCaptureOptions): Promise<void>;
|
|
111
196
|
/** Toggles the microphone and returns the new state. */
|
|
112
197
|
ToggleMicrophone(): Promise<boolean>;
|
|
113
198
|
/** Toggles the camera and returns the new state. */
|
|
114
199
|
ToggleCamera(): Promise<boolean>;
|
|
115
200
|
/** Toggles screen sharing and returns the new state. */
|
|
116
201
|
ToggleScreenShare(): Promise<boolean>;
|
|
202
|
+
/**
|
|
203
|
+
* Shares something else: stops the current share, then asks the browser's picker again, with `request` as for
|
|
204
|
+
* {@link SetScreenShareEnabled}. Others see the share end and start again; a user who cancels the picker is left not
|
|
205
|
+
* sharing. With no share running, it just starts one.
|
|
206
|
+
*/
|
|
207
|
+
ChangeScreenShare(request?: DisplayCaptureSurface | DisplayCaptureOptions): Promise<void>;
|
|
117
208
|
/**
|
|
118
209
|
* Sends a text message on the LiveKit data channel (reliable publish to all participants).
|
|
119
210
|
*
|
|
120
211
|
* @param text The message text.
|
|
121
212
|
* @param topic Optional topic to publish under (for routing on the receiving side).
|
|
213
|
+
* @returns `true` once LiveKit sent the message (while LiveKit reconnects, it holds the message and sends it when the
|
|
214
|
+
* session resumes); `false` when it was not sent: no room, a `beforeSendData` handler canceled it, or the publish
|
|
215
|
+
* failed, which is also raised as an `error` of kind `data`.
|
|
122
216
|
*/
|
|
123
|
-
SendData(text: string, topic?: string): Promise<
|
|
217
|
+
SendData(text: string, topic?: string): Promise<boolean>;
|
|
124
218
|
/**
|
|
125
219
|
* Resumes audio playback after a browser autoplay block (must be called from a user-gesture handler).
|
|
126
220
|
* Clears {@link LiveKitRoomState.AudioPlaybackBlocked} on success.
|
|
@@ -185,7 +279,10 @@ export declare class LiveKitRoomController {
|
|
|
185
279
|
private handleDisconnected;
|
|
186
280
|
/** Rebuilds the full state snapshot from the live room and emits it. */
|
|
187
281
|
private rebuildState;
|
|
188
|
-
/**
|
|
282
|
+
/**
|
|
283
|
+
* Maps a livekit-client participant onto the normalized {@link LiveKitParticipantView}. An agent whose bot says the
|
|
284
|
+
* meeting can't show its avatar carries why ({@link LiveKitParticipantView.AvatarAudioOnly}).
|
|
285
|
+
*/
|
|
189
286
|
private buildView;
|
|
190
287
|
/**
|
|
191
288
|
* Debounced "is speaking": true while the raw flag is set OR within {@link LiveKitRoomController.SPEAKING_HOLD_MS}
|
|
@@ -198,15 +295,35 @@ export declare class LiveKitRoomController {
|
|
|
198
295
|
private scheduleSpeakingHoldRefresh;
|
|
199
296
|
/** Whether the participant has a published, unmuted track for the given source. */
|
|
200
297
|
private hasLiveTrack;
|
|
201
|
-
/**
|
|
298
|
+
/**
|
|
299
|
+
* Reads the local-media toggle state from the room's local participant, with what is shared while sharing: the kind
|
|
300
|
+
* of surface, and a shared panel's name while the published screen track is that panel's.
|
|
301
|
+
*/
|
|
202
302
|
private readLocalMedia;
|
|
203
|
-
/**
|
|
303
|
+
/**
|
|
304
|
+
* Toggles a local-media kind via the local participant, surfacing failures as device errors that name the kind and
|
|
305
|
+
* whether it was being turned on or off. `screenRequest` is what a screen share that starts asks the browser's picker
|
|
306
|
+
* for.
|
|
307
|
+
*/
|
|
204
308
|
private toggleLocalMedia;
|
|
205
|
-
/**
|
|
309
|
+
/** Starts sharing through `/media` display capture, or stops. A share the browser refuses throws, with its reason. */
|
|
310
|
+
private setScreenShare;
|
|
311
|
+
/** The browser ended the local share ("Stop sharing", or the shared surface closed): the room stops showing it. */
|
|
312
|
+
private onScreenShareEnded;
|
|
313
|
+
/**
|
|
314
|
+
* Turns on the requested initial media (mic on by default, camera off — voice-first), honoring device ids. Each
|
|
315
|
+
* device is turned on by itself: one that fails is left off and reported, and the other still starts. The camera is
|
|
316
|
+
* not started once the user has left.
|
|
317
|
+
*/
|
|
206
318
|
private applyInitialMedia;
|
|
319
|
+
/**
|
|
320
|
+
* Turns on one device while joining. A failure is a `device` error that names the device being turned on, as when
|
|
321
|
+
* the user turns it on later (LiveKit reports it first, as its own `MediaDevicesError`); the device stays off.
|
|
322
|
+
*/
|
|
323
|
+
private startInitialDevice;
|
|
207
324
|
/** Applies the requested initial cloud effects (noise filter, background) after media is published. */
|
|
208
325
|
private applyInitialEffects;
|
|
209
|
-
/** Merges E2EE into the livekit-client room options
|
|
326
|
+
/** Merges speech defaults and E2EE into the livekit-client room options. */
|
|
210
327
|
private buildRoomOptions;
|
|
211
328
|
/** Sets the E2EE key from the passphrase and enables encryption (no-op when not requested). */
|
|
212
329
|
private enableE2EEIfRequested;
|
|
@@ -218,9 +335,13 @@ export declare class LiveKitRoomController {
|
|
|
218
335
|
private mapDisconnectReason;
|
|
219
336
|
/** Pushes a partial update onto the current state snapshot, emits it, and fires `stateChanged`. */
|
|
220
337
|
private patchState;
|
|
221
|
-
/** Emits a normalized error via the `error` event. */
|
|
338
|
+
/** Emits a normalized error via the `error` event; a device error about one device names it (`Device`). */
|
|
222
339
|
private emitError;
|
|
223
|
-
/**
|
|
340
|
+
/**
|
|
341
|
+
* The room state before joining, which a leave or a disconnect merges back over the current state: nobody in the
|
|
342
|
+
* room, the user included, and no room name. It sets every field ({@link LiveKitRoomResetState}), since a field left
|
|
343
|
+
* out would keep its value through the merge.
|
|
344
|
+
*/
|
|
224
345
|
private initialState;
|
|
225
346
|
}
|
|
226
347
|
//# sourceMappingURL=livekit-room-controller.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"livekit-room-controller.d.ts","sourceRoot":"","sources":["../src/livekit-room-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"livekit-room-controller.d.ts","sourceRoot":"","sources":["../src/livekit-room-controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,10 +12,15 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @module @memberjunction/livekit-room-core
|
|
14
14
|
*/
|
|
15
|
-
import { ConnectionQuality, ConnectionState, DisconnectReason, ExternalE2EEKeyProvider, Room, RoomEvent, Track, } from 'livekit-client';
|
|
15
|
+
import { AudioPresets, ConnectionQuality, ConnectionState, DisconnectReason, ExternalE2EEKeyProvider, Room, RoomEvent, Track, } from 'livekit-client';
|
|
16
16
|
import { BehaviorSubject } from 'rxjs';
|
|
17
|
+
import { AllowsAgentVision, IsAgentWatching } from '@memberjunction/ai';
|
|
18
|
+
import { CapturedSurfaceOf, RequestDisplayCapture, } from '@memberjunction/ai-realtime-client/media';
|
|
19
|
+
import { ReadAvatarAudioOnly } from './agent-avatar.js';
|
|
17
20
|
import { LiveKitRoomEventBus } from './events.js';
|
|
18
21
|
import { ApplyBackgroundEffect, ApplyNoiseFilter } from './livekit-effects.js';
|
|
22
|
+
import { LiveKitScreenShare } from './livekit-screen-share.js';
|
|
23
|
+
import { ToMediaDeviceKind } from './media-adapters.js';
|
|
19
24
|
/** The default factory — constructs a real livekit-client `Room`. */
|
|
20
25
|
export const DefaultRoomFactory = (options) => new Room(options);
|
|
21
26
|
/** @deprecated Use {@link DefaultRoomFactory}. */
|
|
@@ -42,6 +47,23 @@ export const DefaultRoleResolver = (participant) => {
|
|
|
42
47
|
};
|
|
43
48
|
/** @deprecated Use {@link DefaultRoleResolver}. */
|
|
44
49
|
export const defaultRoleResolver = DefaultRoleResolver;
|
|
50
|
+
/**
|
|
51
|
+
* Speech-tuned room options for LiveKit connections in MJ Meet and agent interactions.
|
|
52
|
+
* Prioritizes low latency, natural interruptions, packet loss resilience (RED), and voice quality.
|
|
53
|
+
*/
|
|
54
|
+
export const DEFAULT_SPEECH_ROOM_OPTIONS = {
|
|
55
|
+
publishDefaults: {
|
|
56
|
+
dtx: false,
|
|
57
|
+
red: true,
|
|
58
|
+
audioPreset: AudioPresets.speech,
|
|
59
|
+
},
|
|
60
|
+
audioCaptureDefaults: {
|
|
61
|
+
voiceIsolation: false,
|
|
62
|
+
echoCancellation: true,
|
|
63
|
+
noiseSuppression: true,
|
|
64
|
+
autoGainControl: true,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
45
67
|
/**
|
|
46
68
|
* Controls a single LiveKit room: connect/disconnect, local-media toggles, data messages, device
|
|
47
69
|
* switching, and a normalized observable state snapshot. One controller == one room connection.
|
|
@@ -69,6 +91,7 @@ export class LiveKitRoomController {
|
|
|
69
91
|
this.roomFactory = options.RoomFactory ?? DefaultRoomFactory;
|
|
70
92
|
this.roleResolver = options.RoleResolver ?? DefaultRoleResolver;
|
|
71
93
|
this.Events = options.EventBus ?? new LiveKitRoomEventBus();
|
|
94
|
+
this.screenShare = new LiveKitScreenShare(options.RequestScreenShare ?? RequestDisplayCapture, () => this.onScreenShareEnded());
|
|
72
95
|
this.stateSubject = new BehaviorSubject(this.initialState());
|
|
73
96
|
}
|
|
74
97
|
// ── Observable surface ────────────────────────────────────────────────────────────
|
|
@@ -88,6 +111,12 @@ export class LiveKitRoomController {
|
|
|
88
111
|
/**
|
|
89
112
|
* Connects to a LiveKit room and brings the local participant online with the requested media.
|
|
90
113
|
*
|
|
114
|
+
* Only the connection can fail the join: then it rejects, the status is `'error'` and a `connect` error is emitted.
|
|
115
|
+
* The microphone and camera are turned on once connected, each on its own; one that fails (the browser refused it,
|
|
116
|
+
* or it is in use) is left off and reported as a `device` error, and the join goes on, so the user is in the room
|
|
117
|
+
* and can turn it on again. If the user leaves while a device starts (the browser's permission prompt can stay open
|
|
118
|
+
* a while), no other device starts and no `connected` is emitted.
|
|
119
|
+
*
|
|
91
120
|
* @param serverUrl The LiveKit server URL (e.g. `wss://livekit.myorg.com`).
|
|
92
121
|
* @param token The signed access token authorizing this participant to join a specific room.
|
|
93
122
|
* @param options Initial media + display-name options.
|
|
@@ -111,16 +140,19 @@ export class LiveKitRoomController {
|
|
|
111
140
|
if (before.Options.DisplayName) {
|
|
112
141
|
await room.localParticipant.setName(before.Options.DisplayName);
|
|
113
142
|
}
|
|
114
|
-
await this.applyInitialMedia(room, before.Options);
|
|
115
|
-
await this.applyInitialEffects(before.Options);
|
|
116
|
-
this.rebuildState();
|
|
117
|
-
this.Events.Emit('connected', { State: this.stateSubject.value });
|
|
118
143
|
}
|
|
119
144
|
catch (err) {
|
|
120
145
|
this.emitError('connect', 'Failed to connect to the room.', err);
|
|
121
146
|
this.patchState({ Status: 'error' });
|
|
122
147
|
throw err;
|
|
123
148
|
}
|
|
149
|
+
await this.applyInitialMedia(room, before.Options);
|
|
150
|
+
if (this.room !== room) {
|
|
151
|
+
return; // left while the devices were starting
|
|
152
|
+
}
|
|
153
|
+
await this.applyInitialEffects(before.Options);
|
|
154
|
+
this.rebuildState();
|
|
155
|
+
this.Events.Emit('connected', { State: this.stateSubject.value });
|
|
124
156
|
}
|
|
125
157
|
/**
|
|
126
158
|
* Disconnects from the room and releases all resources. Safe to call when not connected.
|
|
@@ -139,6 +171,8 @@ export class LiveKitRoomController {
|
|
|
139
171
|
const room = this.room;
|
|
140
172
|
this.room = null;
|
|
141
173
|
this.resetEffectState();
|
|
174
|
+
// Leaving drops every publication; the share's capture is ours to release (and a picker still open, to cancel).
|
|
175
|
+
await this.screenShare.Stop(null);
|
|
142
176
|
if (room) {
|
|
143
177
|
try {
|
|
144
178
|
await room.disconnect();
|
|
@@ -176,9 +210,15 @@ export class LiveKitRoomController {
|
|
|
176
210
|
async SetCameraEnabled(enabled) {
|
|
177
211
|
await this.toggleLocalMedia('camera', enabled);
|
|
178
212
|
}
|
|
179
|
-
/**
|
|
180
|
-
|
|
181
|
-
|
|
213
|
+
/**
|
|
214
|
+
* Starts or stops local screen sharing, through `/media` display capture published as this participant's screen
|
|
215
|
+
* share (capped at 1080p and 30 fps, as LiveKit's own capture is). `request` is the kind of surface the browser's
|
|
216
|
+
* picker offers first (Chromium reads it; the user can still pick another kind), or the share's options: one panel
|
|
217
|
+
* of the page to share alone (`Panel`, Chrome and Edge), with its name (`PanelLabel`). A picker the user closes
|
|
218
|
+
* shares nothing and is no error; a share the browser refuses is a `device` error.
|
|
219
|
+
*/
|
|
220
|
+
async SetScreenShareEnabled(enabled, request) {
|
|
221
|
+
await this.toggleLocalMedia('screen', enabled, request);
|
|
182
222
|
}
|
|
183
223
|
/** Toggles the microphone and returns the new state. */
|
|
184
224
|
async ToggleMicrophone() {
|
|
@@ -198,27 +238,43 @@ export class LiveKitRoomController {
|
|
|
198
238
|
await this.SetScreenShareEnabled(next);
|
|
199
239
|
return next;
|
|
200
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Shares something else: stops the current share, then asks the browser's picker again, with `request` as for
|
|
243
|
+
* {@link SetScreenShareEnabled}. Others see the share end and start again; a user who cancels the picker is left not
|
|
244
|
+
* sharing. With no share running, it just starts one.
|
|
245
|
+
*/
|
|
246
|
+
async ChangeScreenShare(request) {
|
|
247
|
+
if (this.stateSubject.value.LocalMedia.ScreenShareEnabled) {
|
|
248
|
+
await this.SetScreenShareEnabled(false);
|
|
249
|
+
}
|
|
250
|
+
await this.SetScreenShareEnabled(true, request);
|
|
251
|
+
}
|
|
201
252
|
// ── Data channel ────────────────────────────────────────────────────────────────
|
|
202
253
|
/**
|
|
203
254
|
* Sends a text message on the LiveKit data channel (reliable publish to all participants).
|
|
204
255
|
*
|
|
205
256
|
* @param text The message text.
|
|
206
257
|
* @param topic Optional topic to publish under (for routing on the receiving side).
|
|
258
|
+
* @returns `true` once LiveKit sent the message (while LiveKit reconnects, it holds the message and sends it when the
|
|
259
|
+
* session resumes); `false` when it was not sent: no room, a `beforeSendData` handler canceled it, or the publish
|
|
260
|
+
* failed, which is also raised as an `error` of kind `data`.
|
|
207
261
|
*/
|
|
208
262
|
async SendData(text, topic) {
|
|
209
263
|
if (!this.room) {
|
|
210
|
-
return;
|
|
264
|
+
return false;
|
|
211
265
|
}
|
|
212
266
|
const before = this.Events.Emit('beforeSendData', { Text: text, Topic: topic, Cancel: false });
|
|
213
267
|
if (before.Cancel) {
|
|
214
|
-
return;
|
|
268
|
+
return false;
|
|
215
269
|
}
|
|
216
270
|
try {
|
|
217
271
|
const payload = this.textEncoder.encode(before.Text);
|
|
218
272
|
await this.room.localParticipant.publishData(payload, { reliable: true, topic: before.Topic });
|
|
273
|
+
return true;
|
|
219
274
|
}
|
|
220
275
|
catch (err) {
|
|
221
276
|
this.emitError('data', 'Failed to send data message.', err);
|
|
277
|
+
return false;
|
|
222
278
|
}
|
|
223
279
|
}
|
|
224
280
|
// ── Audio playback / cloud effects ────────────────────────────────────────────────
|
|
@@ -235,7 +291,7 @@ export class LiveKitRoomController {
|
|
|
235
291
|
this.patchState({ AudioPlaybackBlocked: !this.room.canPlaybackAudio });
|
|
236
292
|
}
|
|
237
293
|
catch (err) {
|
|
238
|
-
this.emitError('device', 'Failed to start audio playback.', err);
|
|
294
|
+
this.emitError('device', 'Failed to start audio playback.', err, { Media: 'speaker', Change: 'on' });
|
|
239
295
|
}
|
|
240
296
|
}
|
|
241
297
|
/**
|
|
@@ -325,7 +381,8 @@ export class LiveKitRoomController {
|
|
|
325
381
|
await this.room.switchActiveDevice(kind, before.DeviceId);
|
|
326
382
|
}
|
|
327
383
|
catch (err) {
|
|
328
|
-
|
|
384
|
+
const device = { Media: ToMediaDeviceKind(kind), Change: 'switch' };
|
|
385
|
+
this.emitError('device', `Failed to switch ${kind} device.`, err, device);
|
|
329
386
|
}
|
|
330
387
|
}
|
|
331
388
|
// ── internals: event wiring ───────────────────────────────────────────────────────
|
|
@@ -343,6 +400,7 @@ export class LiveKitRoomController {
|
|
|
343
400
|
.on(RoomEvent.TrackMuted, rebuild)
|
|
344
401
|
.on(RoomEvent.TrackUnmuted, rebuild)
|
|
345
402
|
.on(RoomEvent.ConnectionQualityChanged, rebuild)
|
|
403
|
+
.on(RoomEvent.ParticipantAttributesChanged, rebuild)
|
|
346
404
|
.on(RoomEvent.ActiveSpeakersChanged, (speakers) => this.handleActiveSpeakers(speakers))
|
|
347
405
|
.on(RoomEvent.ConnectionStateChanged, (state) => this.handleConnectionState(state))
|
|
348
406
|
.on(RoomEvent.Reconnecting, () => {
|
|
@@ -355,7 +413,7 @@ export class LiveKitRoomController {
|
|
|
355
413
|
})
|
|
356
414
|
.on(RoomEvent.DataReceived, (payload, participant, _kind, topic) => this.handleData(payload, participant, topic))
|
|
357
415
|
.on(RoomEvent.Disconnected, (reason) => this.handleDisconnected(reason))
|
|
358
|
-
.on(RoomEvent.MediaDevicesError, (err) => this.emitError('device', err.message, err))
|
|
416
|
+
.on(RoomEvent.MediaDevicesError, (err, kind) => this.emitError('device', err.message, err, kind ? { Media: ToMediaDeviceKind(kind) } : undefined))
|
|
359
417
|
.on(RoomEvent.AudioPlaybackStatusChanged, () => this.handleAudioPlayback());
|
|
360
418
|
}
|
|
361
419
|
/** Reflects browser autoplay-policy changes into state + the `audioPlaybackChanged` event. */
|
|
@@ -444,21 +502,29 @@ export class LiveKitRoomController {
|
|
|
444
502
|
NoiseFilterEnabled: this.noiseFilterEnabled,
|
|
445
503
|
BackgroundEffect: this.backgroundEffect,
|
|
446
504
|
E2EEEnabled: this.e2eeEnabled,
|
|
505
|
+
AgentWatching: agentWatchesIn(room),
|
|
447
506
|
});
|
|
448
507
|
}
|
|
449
|
-
/**
|
|
508
|
+
/**
|
|
509
|
+
* Maps a livekit-client participant onto the normalized {@link LiveKitParticipantView}. An agent whose bot says the
|
|
510
|
+
* meeting can't show its avatar carries why ({@link LiveKitParticipantView.AvatarAudioOnly}).
|
|
511
|
+
*/
|
|
450
512
|
buildView(participant) {
|
|
513
|
+
const role = this.roleResolver(participant);
|
|
514
|
+
const avatarAudioOnly = role === 'agent' ? ReadAvatarAudioOnly(participant.attributes) : undefined;
|
|
451
515
|
return {
|
|
452
516
|
Identity: participant.identity,
|
|
453
517
|
DisplayName: participant.name && participant.name.length > 0 ? participant.name : participant.identity,
|
|
454
518
|
IsLocal: this.room?.localParticipant === participant,
|
|
455
|
-
Role:
|
|
519
|
+
Role: role,
|
|
456
520
|
IsSpeaking: this.computeIsSpeaking(participant),
|
|
457
521
|
AudioLevel: participant.audioLevel ?? 0,
|
|
458
522
|
HasAudio: this.hasLiveTrack(participant, Track.Source.Microphone),
|
|
459
523
|
HasVideo: this.hasLiveTrack(participant, Track.Source.Camera),
|
|
460
524
|
IsScreenSharing: this.hasLiveTrack(participant, Track.Source.ScreenShare),
|
|
461
525
|
ConnectionQuality: this.mapConnectionQuality(participant.connectionQuality),
|
|
526
|
+
AgentCanSee: AllowsAgentVision(participant.attributes) && this.room !== null && agentWatchesIn(this.room),
|
|
527
|
+
...(avatarAudioOnly ? { AvatarAudioOnly: avatarAudioOnly } : {}),
|
|
462
528
|
Raw: participant,
|
|
463
529
|
};
|
|
464
530
|
}
|
|
@@ -498,17 +564,30 @@ export class LiveKitRoomController {
|
|
|
498
564
|
const pub = participant.getTrackPublication(source);
|
|
499
565
|
return pub != null && !pub.isMuted;
|
|
500
566
|
}
|
|
501
|
-
/**
|
|
567
|
+
/**
|
|
568
|
+
* Reads the local-media toggle state from the room's local participant, with what is shared while sharing: the kind
|
|
569
|
+
* of surface, and a shared panel's name while the published screen track is that panel's.
|
|
570
|
+
*/
|
|
502
571
|
readLocalMedia(room) {
|
|
503
572
|
const lp = room.localParticipant;
|
|
573
|
+
const screen = lp?.getTrackPublication(Track.Source.ScreenShare)?.track?.mediaStreamTrack;
|
|
574
|
+
const capture = this.screenShare.Capture;
|
|
575
|
+
const panelLabel = capture && capture.Track === screen ? capture.PanelLabel : undefined;
|
|
504
576
|
return {
|
|
505
577
|
MicrophoneEnabled: lp ? lp.isMicrophoneEnabled : false,
|
|
506
578
|
CameraEnabled: lp ? lp.isCameraEnabled : false,
|
|
507
579
|
ScreenShareEnabled: lp ? lp.isScreenShareEnabled : false,
|
|
580
|
+
...(screen ? { ScreenShareSurface: CapturedSurfaceOf(screen) } : {}),
|
|
581
|
+
...(panelLabel ? { ScreenSharePanelLabel: panelLabel } : {}),
|
|
582
|
+
AgentVisionOn: lp ? AllowsAgentVision(lp.attributes) : false,
|
|
508
583
|
};
|
|
509
584
|
}
|
|
510
|
-
/**
|
|
511
|
-
|
|
585
|
+
/**
|
|
586
|
+
* Toggles a local-media kind via the local participant, surfacing failures as device errors that name the kind and
|
|
587
|
+
* whether it was being turned on or off. `screenRequest` is what a screen share that starts asks the browser's picker
|
|
588
|
+
* for.
|
|
589
|
+
*/
|
|
590
|
+
async toggleLocalMedia(kind, enabled, screenRequest) {
|
|
512
591
|
if (!this.room) {
|
|
513
592
|
return;
|
|
514
593
|
}
|
|
@@ -525,24 +604,61 @@ export class LiveKitRoomController {
|
|
|
525
604
|
await lp.setCameraEnabled(enabled);
|
|
526
605
|
}
|
|
527
606
|
else {
|
|
528
|
-
await
|
|
607
|
+
await this.setScreenShare(lp, enabled, screenRequest);
|
|
529
608
|
}
|
|
530
609
|
this.rebuildState();
|
|
531
610
|
this.Events.Emit('localMediaChanged', this.stateSubject.value.LocalMedia);
|
|
532
611
|
}
|
|
533
612
|
catch (err) {
|
|
534
|
-
|
|
613
|
+
const device = { Media: kind, Change: enabled ? 'on' : 'off' };
|
|
614
|
+
this.emitError('device', `Failed to ${enabled ? 'enable' : 'disable'} ${kind}.`, err, device);
|
|
535
615
|
}
|
|
536
616
|
}
|
|
537
|
-
/**
|
|
617
|
+
/** Starts sharing through `/media` display capture, or stops. A share the browser refuses throws, with its reason. */
|
|
618
|
+
async setScreenShare(lp, enabled, request) {
|
|
619
|
+
if (!enabled) {
|
|
620
|
+
await this.screenShare.Stop(lp);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const outcome = await this.screenShare.Start(lp, request);
|
|
624
|
+
if (outcome.Status === 'failed') {
|
|
625
|
+
throw new Error(outcome.Message);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
/** The browser ended the local share ("Stop sharing", or the shared surface closed): the room stops showing it. */
|
|
629
|
+
onScreenShareEnded() {
|
|
630
|
+
if (!this.room) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
this.rebuildState();
|
|
634
|
+
this.Events.Emit('localMediaChanged', this.stateSubject.value.LocalMedia);
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Turns on the requested initial media (mic on by default, camera off — voice-first), honoring device ids. Each
|
|
638
|
+
* device is turned on by itself: one that fails is left off and reported, and the other still starts. The camera is
|
|
639
|
+
* not started once the user has left.
|
|
640
|
+
*/
|
|
538
641
|
async applyInitialMedia(room, options) {
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
await
|
|
642
|
+
const lp = room.localParticipant;
|
|
643
|
+
if (options.EnableMicrophone ?? true) {
|
|
644
|
+
const device = options.MicrophoneDeviceId ? { deviceId: options.MicrophoneDeviceId } : undefined;
|
|
645
|
+
await this.startInitialDevice('microphone', () => lp.setMicrophoneEnabled(true, device));
|
|
543
646
|
}
|
|
544
|
-
if (
|
|
545
|
-
|
|
647
|
+
if ((options.EnableCamera ?? false) && this.room === room) {
|
|
648
|
+
const device = options.CameraDeviceId ? { deviceId: options.CameraDeviceId } : undefined;
|
|
649
|
+
await this.startInitialDevice('camera', () => lp.setCameraEnabled(true, device));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Turns on one device while joining. A failure is a `device` error that names the device being turned on, as when
|
|
654
|
+
* the user turns it on later (LiveKit reports it first, as its own `MediaDevicesError`); the device stays off.
|
|
655
|
+
*/
|
|
656
|
+
async startInitialDevice(kind, start) {
|
|
657
|
+
try {
|
|
658
|
+
await start();
|
|
659
|
+
}
|
|
660
|
+
catch (err) {
|
|
661
|
+
this.emitError('device', `Failed to enable ${kind}.`, err, { Media: kind, Change: 'on' });
|
|
546
662
|
}
|
|
547
663
|
}
|
|
548
664
|
/** Applies the requested initial cloud effects (noise filter, background) after media is published. */
|
|
@@ -554,15 +670,27 @@ export class LiveKitRoomController {
|
|
|
554
670
|
await this.SetBackgroundEffect(options.BackgroundEffect);
|
|
555
671
|
}
|
|
556
672
|
}
|
|
557
|
-
/** Merges E2EE into the livekit-client room options
|
|
673
|
+
/** Merges speech defaults and E2EE into the livekit-client room options. */
|
|
558
674
|
buildRoomOptions(options) {
|
|
675
|
+
const merged = {
|
|
676
|
+
...DEFAULT_SPEECH_ROOM_OPTIONS,
|
|
677
|
+
...options.RoomOptions,
|
|
678
|
+
publishDefaults: {
|
|
679
|
+
...DEFAULT_SPEECH_ROOM_OPTIONS.publishDefaults,
|
|
680
|
+
...options.RoomOptions?.publishDefaults,
|
|
681
|
+
},
|
|
682
|
+
audioCaptureDefaults: {
|
|
683
|
+
...(typeof DEFAULT_SPEECH_ROOM_OPTIONS.audioCaptureDefaults === 'object' ? DEFAULT_SPEECH_ROOM_OPTIONS.audioCaptureDefaults : {}),
|
|
684
|
+
...(typeof options.RoomOptions?.audioCaptureDefaults === 'object' ? options.RoomOptions?.audioCaptureDefaults : {}),
|
|
685
|
+
},
|
|
686
|
+
};
|
|
559
687
|
this.e2eeKeyProvider = null;
|
|
560
688
|
if (!options.E2EE) {
|
|
561
|
-
return
|
|
689
|
+
return merged;
|
|
562
690
|
}
|
|
563
691
|
this.e2eeKeyProvider = new ExternalE2EEKeyProvider();
|
|
564
692
|
return {
|
|
565
|
-
...
|
|
693
|
+
...merged,
|
|
566
694
|
e2ee: { keyProvider: this.e2eeKeyProvider, worker: options.E2EE.Worker },
|
|
567
695
|
};
|
|
568
696
|
}
|
|
@@ -622,7 +750,7 @@ export class LiveKitRoomController {
|
|
|
622
750
|
case DisconnectReason.CONNECTION_TIMEOUT:
|
|
623
751
|
return 'connection-lost';
|
|
624
752
|
default:
|
|
625
|
-
return reason == null ? '
|
|
753
|
+
return reason == null ? 'connection-lost' : 'unknown';
|
|
626
754
|
}
|
|
627
755
|
}
|
|
628
756
|
/** Pushes a partial update onto the current state snapshot, emits it, and fires `stateChanged`. */
|
|
@@ -631,14 +759,20 @@ export class LiveKitRoomController {
|
|
|
631
759
|
this.stateSubject.next(next);
|
|
632
760
|
this.Events.Emit('stateChanged', next);
|
|
633
761
|
}
|
|
634
|
-
/** Emits a normalized error via the `error` event. */
|
|
635
|
-
emitError(kind, message, cause) {
|
|
636
|
-
this.Events.Emit('error', { Kind: kind, Message: message, Cause: cause });
|
|
762
|
+
/** Emits a normalized error via the `error` event; a device error about one device names it (`Device`). */
|
|
763
|
+
emitError(kind, message, cause, device) {
|
|
764
|
+
this.Events.Emit('error', { Kind: kind, Message: message, Cause: cause, ...(device ? { Device: device } : {}) });
|
|
637
765
|
}
|
|
638
|
-
/**
|
|
766
|
+
/**
|
|
767
|
+
* The room state before joining, which a leave or a disconnect merges back over the current state: nobody in the
|
|
768
|
+
* room, the user included, and no room name. It sets every field ({@link LiveKitRoomResetState}), since a field left
|
|
769
|
+
* out would keep its value through the merge.
|
|
770
|
+
*/
|
|
639
771
|
initialState() {
|
|
640
772
|
return {
|
|
641
773
|
Status: 'idle',
|
|
774
|
+
RoomName: undefined,
|
|
775
|
+
Local: undefined,
|
|
642
776
|
Remote: [],
|
|
643
777
|
ActiveSpeakerIdentities: [],
|
|
644
778
|
LocalMedia: { MicrophoneEnabled: false, CameraEnabled: false, ScreenShareEnabled: false },
|
|
@@ -646,7 +780,12 @@ export class LiveKitRoomController {
|
|
|
646
780
|
NoiseFilterEnabled: false,
|
|
647
781
|
BackgroundEffect: { Kind: 'none' },
|
|
648
782
|
E2EEEnabled: false,
|
|
783
|
+
AgentWatching: false,
|
|
649
784
|
};
|
|
650
785
|
}
|
|
651
786
|
}
|
|
787
|
+
/** Whether an agent in the room watches the cameras and screens people let it see (its bot's attributes say so). */
|
|
788
|
+
function agentWatchesIn(room) {
|
|
789
|
+
return Array.from(room.remoteParticipants.values()).some((p) => IsAgentWatching(p.attributes));
|
|
790
|
+
}
|
|
652
791
|
//# sourceMappingURL=livekit-room-controller.js.map
|