@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.
Files changed (36) hide show
  1. package/README.md +32 -1
  2. package/dist/agent-avatar.d.ts +18 -0
  3. package/dist/agent-avatar.d.ts.map +1 -0
  4. package/dist/agent-avatar.js +25 -0
  5. package/dist/agent-avatar.js.map +1 -0
  6. package/dist/audio-meter.d.ts +24 -8
  7. package/dist/audio-meter.d.ts.map +1 -1
  8. package/dist/audio-meter.js +19 -8
  9. package/dist/audio-meter.js.map +1 -1
  10. package/dist/index.d.ts +5 -3
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -2
  13. package/dist/index.js.map +1 -1
  14. package/dist/livekit-preview-room-controller.d.ts +162 -0
  15. package/dist/livekit-preview-room-controller.d.ts.map +1 -0
  16. package/dist/livekit-preview-room-controller.js +475 -0
  17. package/dist/livekit-preview-room-controller.js.map +1 -0
  18. package/dist/livekit-preview.d.ts +6 -1
  19. package/dist/livekit-preview.d.ts.map +1 -1
  20. package/dist/livekit-preview.js +6 -1
  21. package/dist/livekit-preview.js.map +1 -1
  22. package/dist/livekit-room-controller.d.ts +132 -11
  23. package/dist/livekit-room-controller.d.ts.map +1 -1
  24. package/dist/livekit-room-controller.js +174 -35
  25. package/dist/livekit-room-controller.js.map +1 -1
  26. package/dist/livekit-screen-share.d.ts +73 -0
  27. package/dist/livekit-screen-share.d.ts.map +1 -0
  28. package/dist/livekit-screen-share.js +137 -0
  29. package/dist/livekit-screen-share.js.map +1 -0
  30. package/dist/media-adapters.d.ts +26 -0
  31. package/dist/media-adapters.d.ts.map +1 -0
  32. package/dist/media-adapters.js +101 -0
  33. package/dist/media-adapters.js.map +1 -0
  34. package/dist/types.d.ts +78 -5
  35. package/dist/types.d.ts.map +1 -1
  36. 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
- /** Starts or stops local screen sharing. */
110
- SetScreenShareEnabled(enabled: boolean): Promise<void>;
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<void>;
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
- /** Maps a livekit-client participant onto the normalized {@link LiveKitParticipantView}. */
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
- /** Reads the local-media toggle state from the room's local participant. */
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
- /** Toggles a local-media kind via the local participant, surfacing failures as device errors. */
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
- /** Applies the requested initial media (mic on by default, camera off — voice-first), honoring device ids. */
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 when requested. */
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
- /** The initial / reset room state. */
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":"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"}
1
+ {"version":3,"file":"livekit-room-controller.d.ts","sourceRoot":"","sources":["../src/livekit-room-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAML,WAAW,EAEX,IAAI,EAOJ,KAAK,WAAW,EACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAmB,UAAU,EAAE,MAAM,MAAM,CAAC;AAEnD,OAAO,EAGL,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC3B,MAAM,0CAA0C,CAAC;AAElD,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAI/C,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,aAAa,EAIb,sBAAsB,EAEtB,yBAAyB,EAEzB,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAEjB;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,CAAC,EAAE,WAAW,KAAK,IAAI,CAAC;AAEjE,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,EAAE,kBAAmD,CAAC;AAErF,kDAAkD;AAClD,eAAO,MAAM,kBAAkB,EAAE,kBAAuC,CAAC;AAEzE,iGAAiG;AACjG,MAAM,MAAM,mBAAmB,GAAG,CAAC,WAAW,EAAE,WAAW,KAAK,sBAAsB,CAAC;AAEvF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,mBAcjC,CAAC;AAEF,mDAAmD;AACnD,eAAO,MAAM,mBAAmB,EAAE,mBAAyC,CAAC;AAE5E,0FAA0F;AAC1F,MAAM,WAAW,4BAA4B;IAC3C,yDAAyD;IACzD,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,oDAAoD;IACpD,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACxF;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,gBAAgB,CAAC,CAAC;IAC9C,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,qCAAqC;IACrC,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;IACzC;;;OAGG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9F,uFAAuF;IACvF,UAAU,CAAC,aAAa,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,iFAAiF;IACjF,OAAO,IAAI,IAAI,CAAC;IAChB,4CAA4C;IAC5C,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,wCAAwC;IACxC,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD;;;;OAIG;IACH,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChH,yDAAyD;IACzD,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,qDAAqD;IACrD,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,yDAAyD;IACzD,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACtC,qHAAqH;IACrH,iBAAiB,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1F;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,wFAAwF;IACxF,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,oFAAoF;IACpF,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,sFAAsF;IACtF,mBAAmB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvE,mCAAmC;IACnC,WAAW,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IACnE,6EAA6E;IAC7E,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;IAC9D,sCAAsC;IACtC,YAAY,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E;AAED;;;GAGG;AACH,eAAO,MAAM,2BAA2B,EAAE,WAYzC,CAAC;AAEF;;;GAGG;AACH,qBAAa,qBAAsB,YAAW,sBAAsB;IAClE,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IACnD,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,gBAAgB,CAA6C;IACrE,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAwC;IAC/D,gEAAgE;IAChE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IAEjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoC;IACjE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IAEjD;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAEhE,8GAA8G;IAC9G,OAAO,CAAC,iBAAiB,CAA8C;IAEvE,gIAAgI;IAChI,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;IAEhD;;;OAGG;IACH,SAAgB,MAAM,EAAE,mBAAmB,CAAC;gBAEhC,OAAO,GAAE,4BAAiC;IAUtD,sGAAsG;IACtG,IAAW,MAAM,IAAI,UAAU,CAAC,gBAAgB,CAAC,CAEhD;IAED,qDAAqD;IACrD,IAAW,KAAK,IAAI,gBAAgB,CAEnC;IAED,+DAA+D;IAC/D,IAAW,MAAM,IAAI,uBAAuB,CAE3C;IAID;;;;;;;;;;;;OAYG;IACU,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,yBAA8B,GAAG,OAAO,CAAC,IAAI,CAAC;IAkC9G;;;;;;OAMG;IACU,UAAU,CAAC,aAAa,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAuB/D,kFAAkF;IAClF,OAAO,CAAC,gBAAgB;IAMxB,sFAAsF;IAC/E,OAAO,IAAI,IAAI;IAatB,gDAAgD;IACnC,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlE,4CAA4C;IAC/B,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9D;;;;;;OAMG;IACU,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5H,wDAAwD;IAC3C,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAMjD,oDAAoD;IACvC,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC;IAM7C,wDAAwD;IAC3C,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC;IAMlD;;;;OAIG;IACU,iBAAiB,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAStG;;;;;;;;OAQG;IACU,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAoBrE;;;OAGG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAYxC;;;;;OAKG;IACU,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IActE;;;;;OAKG;IACU,mBAAmB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBnF;;;;;OAKG;IACU,WAAW,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAU/E;;;;;;;OAOG;IACI,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI;IAOpE;;;;;OAKG;IACU,YAAY,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBvF,2EAA2E;IAC3E,OAAO,CAAC,cAAc;IAkCtB,8FAA8F;IAC9F,OAAO,CAAC,mBAAmB;IAM3B,oEAAoE;IACpE,OAAO,CAAC,kBAAkB;IAK1B,gEAAgE;IAChE,OAAO,CAAC,kBAAkB;IAK1B,+EAA+E;IAC/E,OAAO,CAAC,uBAAuB;IAK/B,6EAA6E;IAC7E,OAAO,CAAC,qBAAqB;IAK7B,4FAA4F;IAC5F,OAAO,CAAC,oBAAoB;IAQ5B,8EAA8E;IAC9E,OAAO,CAAC,qBAAqB;IAI7B,+CAA+C;IAC/C,OAAO,CAAC,UAAU;IAiBlB,uFAAuF;IACvF,OAAO,CAAC,kBAAkB;IAa1B,wEAAwE;IACxE,OAAO,CAAC,YAAY;IAqBpB;;;OAGG;IACH,OAAO,CAAC,SAAS;IAoBjB;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAczB,kHAAkH;IAClH,OAAO,CAAC,2BAA2B;IAYnC,mFAAmF;IACnF,OAAO,CAAC,YAAY;IAKpB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAetB;;;;OAIG;YACW,gBAAgB;IA6B9B,sHAAsH;YACxG,cAAc;IAe5B,mHAAmH;IACnH,OAAO,CAAC,kBAAkB;IAQ1B;;;;OAIG;YACW,iBAAiB;IAY/B;;;OAGG;YACW,kBAAkB;IAQhC,uGAAuG;YACzF,mBAAmB;IASjC,4EAA4E;IAC5E,OAAO,CAAC,gBAAgB;IAwBxB,+FAA+F;YACjF,qBAAqB;IAUnC,+FAA+F;IAC/F,OAAO,CAAC,mBAAmB;IAgB3B,wEAAwE;IACxE,OAAO,CAAC,oBAAoB;IAe5B,gGAAgG;IAChG,OAAO,CAAC,mBAAmB;IAmB3B,mGAAmG;IACnG,OAAO,CAAC,UAAU;IAMlB,2GAA2G;IAC3G,OAAO,CAAC,SAAS;IAIjB;;;;OAIG;IACH,OAAO,CAAC,YAAY;CAerB"}
@@ -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
- /** Starts or stops local screen sharing. */
180
- async SetScreenShareEnabled(enabled) {
181
- await this.toggleLocalMedia('screen', enabled);
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
- this.emitError('device', `Failed to switch ${kind} device.`, err);
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
- /** Maps a livekit-client participant onto the normalized {@link LiveKitParticipantView}. */
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: this.roleResolver(participant),
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
- /** Reads the local-media toggle state from the room's local participant. */
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
- /** Toggles a local-media kind via the local participant, surfacing failures as device errors. */
511
- async toggleLocalMedia(kind, enabled) {
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 lp.setScreenShareEnabled(enabled);
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
- this.emitError('device', `Failed to ${enabled ? 'enable' : 'disable'} ${kind}.`, err);
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
- /** Applies the requested initial media (mic on by default, camera off — voice-first), honoring device ids. */
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 wantMic = options.EnableMicrophone ?? true;
540
- const wantCam = options.EnableCamera ?? false;
541
- if (wantMic) {
542
- await room.localParticipant.setMicrophoneEnabled(true, options.MicrophoneDeviceId ? { deviceId: options.MicrophoneDeviceId } : undefined);
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 (wantCam) {
545
- await room.localParticipant.setCameraEnabled(true, options.CameraDeviceId ? { deviceId: options.CameraDeviceId } : undefined);
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 when requested. */
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 options.RoomOptions;
689
+ return merged;
562
690
  }
563
691
  this.e2eeKeyProvider = new ExternalE2EEKeyProvider();
564
692
  return {
565
- ...options.RoomOptions,
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 ? 'client-initiated' : 'unknown';
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
- /** The initial / reset room state. */
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