@memberjunction/ai-bridge-livekit 0.0.1 → 5.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,368 @@
1
+ /**
2
+ * @fileoverview `LiveKitBridge` — the **MJ-native multi-party room** Realtime Bridge driver. Unlike
3
+ * every other bridge (which connects OUT to a 3rd-party meeting/telephony platform), LiveKit is a
4
+ * **self-hosted WebRTC SFU that MJ runs itself** — the "MJ-native room"
5
+ * (`/plans/realtime/realtime-bridges-architecture.md` §4c). The architecture treats a Zoom meeting and
6
+ * an MJ-native LiveKit room **identically** — both are multi-party media transports — so this is
7
+ * "*another bridge, not a special build*."
8
+ *
9
+ * Implements the {@link BaseRealtimeBridge} contract against an injectable {@link ILiveKitRoomSdk} seam
10
+ * so it builds + unit-tests with NO network and NO real LiveKit SDK (the Zoom `SetSdkFactory`
11
+ * testability pattern).
12
+ *
13
+ * LiveKit capability coverage (per the §4c / §8 seed row): on-demand join, **full** audio/video/screen
14
+ * in+out (LiveKit does the lot), and per-participant diarization (`SpeakerDiarization`) — the SFU
15
+ * delivers tracks per participant, so speaker labels come free. No scheduled/invite/telephony features.
16
+ * Those virtual base methods keep throwing `BridgeCapabilityNotSupportedError`.
17
+ *
18
+ * ## Echo / self-audio
19
+ * A LiveKit SFU **never delivers a participant its own published track back**, so the bot does not hear
20
+ * its own voice — no echo gate is required (see {@link wireInboundAudio}). This is exactly the property
21
+ * §4c relies on for putting **multiple agents in one room**: each agent hears the *others'* mixed audio
22
+ * natively, not its own, so two agents converse without a transcript-relay hack.
23
+ *
24
+ * @module @memberjunction/ai-bridge-livekit
25
+ * @author MemberJunction.com
26
+ */
27
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
28
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
29
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
30
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
31
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
32
+ };
33
+ import { RegisterClass } from '@memberjunction/global';
34
+ import { LogError } from '@memberjunction/core';
35
+ import { BaseRealtimeBridge, } from '@memberjunction/ai-bridge-base';
36
+ import { LiveKitMeetingControlsEventSource } from './livekit-meeting-controls.js';
37
+ /**
38
+ * The `DriverClass` key {@link LiveKitBridge} registers under. A `MJ: AI Bridge Providers` row with
39
+ * `DriverClass = 'LiveKitBridge'` resolves to this driver via the `ClassFactory`.
40
+ */
41
+ export const LIVEKIT_BRIDGE_DRIVER_CLASS = 'LiveKitBridge';
42
+ /**
43
+ * Maps a LiveKit participant role to the bridge's {@link BridgeParticipantRole}. The bot (`IsLocal`) is
44
+ * surfaced as `'Agent'`.
45
+ */
46
+ function mapParticipantRole(role, isLocal) {
47
+ if (isLocal) {
48
+ return 'Agent';
49
+ }
50
+ switch (role) {
51
+ case 'Host':
52
+ return 'Host';
53
+ case 'CoHost':
54
+ return 'CoHost';
55
+ default:
56
+ return 'Participant';
57
+ }
58
+ }
59
+ /**
60
+ * The bot-identity convention the LiveKit room coordinator mints (`agent-<agentSessionId>`). A bridge only
61
+ * knows its OWN bot via `IsLocal`; OTHER agents in a multi-agent room are REMOTE participants, so they must
62
+ * be recognized by this identity prefix. Without it every other agent reads as a human — breaking
63
+ * turn-taking's agent-exclusion (an agent treats another agent's speech as being addressed) AND the
64
+ * "are any humans still present?" occupancy check the engine uses to auto-leave an empty room.
65
+ */
66
+ function isAgentParticipantIdentity(identity) {
67
+ return typeof identity === 'string' && identity.toLowerCase().startsWith('agent-');
68
+ }
69
+ /** Maps a LiveKit participant onto the bridge's {@link BridgeParticipantInfo}. */
70
+ function toBridgeParticipant(p) {
71
+ return {
72
+ ExternalId: p.Identity,
73
+ DisplayName: p.DisplayName,
74
+ Role: mapParticipantRole(p.Role, p.IsLocal),
75
+ // The local bot OR any remote agent bot (by identity convention) counts as an agent, not a human.
76
+ IsAgent: p.IsLocal === true || isAgentParticipantIdentity(p.Identity),
77
+ };
78
+ }
79
+ /**
80
+ * Realtime Bridge driver for the **MJ-native LiveKit room**.
81
+ *
82
+ * Construct the driver with the default constructor (the engine's `ClassFactory` path) — it lazily
83
+ * builds the LiveKit room SDK from the {@link sdkFactory} at {@link Connect} time. Tests inject a
84
+ * `FakeLiveKitRoomSdk` by overriding the factory via {@link SetSdkFactory} (the creation seam) before
85
+ * connecting.
86
+ *
87
+ * Registered via `@RegisterClass(BaseRealtimeBridge, 'LiveKitBridge')`.
88
+ */
89
+ let LiveKitBridge = class LiveKitBridge extends BaseRealtimeBridge {
90
+ constructor() {
91
+ super(...arguments);
92
+ /** The live LiveKit SDK seam for this session, created at {@link Connect}. */
93
+ this.sdk = null;
94
+ /** The bot's own participant identity in the joined room (set at {@link Connect}). */
95
+ this.botIdentity = null;
96
+ /** The Meeting Controls event source for this session (only when diarization is supported). */
97
+ this.meetingControls = null;
98
+ /**
99
+ * The SDK creation seam. Defaults to a factory that throws an explicit "bind the real LiveKit SDK"
100
+ * error, since this package ships WITHOUT the real SDK adapter (a deployment concern). Production
101
+ * sets a real factory via {@link SetSdkFactory}; tests inject a `FakeLiveKitRoomSdk`.
102
+ */
103
+ this.sdkFactory = () => {
104
+ throw new Error('LiveKitBridge has no LiveKit room SDK bound. Call LiveKitBridge.SetSdkFactory(...) with a factory ' +
105
+ 'that builds an ILiveKitRoomSdk over the real livekit-server-sdk + a room client, or inject a fake in tests.');
106
+ };
107
+ }
108
+ /**
109
+ * Sets the {@link LiveKitRoomSdkFactory} this driver uses to construct its SDK seam at connect — the
110
+ * creation seam (mirroring Zoom's `SetSdkFactory`). Production binds the real LiveKit room SDK
111
+ * adapter here; tests inject a `FakeLiveKitRoomSdk`.
112
+ *
113
+ * @param factory The factory that builds the {@link ILiveKitRoomSdk} for a session.
114
+ */
115
+ SetSdkFactory(factory) {
116
+ this.sdkFactory = factory;
117
+ }
118
+ // ── Abstract — every bridge MUST implement ───────────────────────────────────────
119
+ /**
120
+ * Connects to the MJ-native LiveKit room and brings the bot online. Captures the capability context,
121
+ * builds the SDK from the {@link sdkFactory}, wires the inbound per-participant audio path and
122
+ * room-disconnected callback, connects, and (when the provider diarizes) constructs + seeds the
123
+ * Meeting Controls event source.
124
+ *
125
+ * @param ctx The bridge context (features, provider name, the room URL as `Address`, config carrying
126
+ * the signed access token).
127
+ * @returns The bot identity + room (external connection) identifiers, persisted by the engine.
128
+ */
129
+ async Connect(ctx) {
130
+ this.applyContext(ctx);
131
+ this.RequireFeature('AudioIn'); // a LiveKit room bridge requires bidirectional audio at minimum
132
+ this.RequireFeature('AudioOut');
133
+ this.sdk = this.sdkFactory(ctx.Configuration);
134
+ this.wireInboundAudio(this.sdk);
135
+ this.sdk.onDisconnected(() => this.handleRoomDisconnected());
136
+ // Roster diarization is native to LiveKit (per-participant tracks); only stand up the Meeting
137
+ // Controls source when the provider advertises it (the engine also gates on this flag).
138
+ if (this.features.SpeakerDiarization === true) {
139
+ this.meetingControls = new LiveKitMeetingControlsEventSource(this.sdk);
140
+ }
141
+ // The driver owns the single SDK subscription set and fans out to BOTH its own roster handler
142
+ // and the Meeting Controls source (the SDK seam is latest-handler-wins, so there is one owner).
143
+ this.wireRoster(this.sdk);
144
+ const result = await this.sdk.connect(this.buildConnectArgs(ctx));
145
+ this.botIdentity = result.BotIdentity;
146
+ // Seed the initial roster now that the bot has joined.
147
+ await this.refreshRoster(this.sdk);
148
+ return { BotParticipantId: result.BotIdentity, ExternalConnectionId: result.RoomName };
149
+ }
150
+ /**
151
+ * Disconnects from the LiveKit room and releases all SDK resources. Tolerant of teardown errors.
152
+ *
153
+ * @param _reason Why the disconnect happened (LiveKit teardown is uniform; the bot simply leaves).
154
+ */
155
+ async Disconnect(_reason) {
156
+ const sdk = this.sdk;
157
+ this.sdk = null;
158
+ this.botIdentity = null;
159
+ this.meetingControls = null;
160
+ this.mediaHandler = undefined;
161
+ this.participantHandler = undefined;
162
+ if (sdk) {
163
+ try {
164
+ await sdk.disconnect();
165
+ }
166
+ catch (err) {
167
+ LogError(`[LiveKitBridge] disconnect() failed: ${err instanceof Error ? err.message : String(err)}`);
168
+ }
169
+ }
170
+ }
171
+ /**
172
+ * Sends an outbound media frame into the room. LiveKit does the FULL media set, so audio, video, and
173
+ * screen are all published on their respective tracks (gated by the directional capability flags —
174
+ * the realtime models light audio first, video/screen ride the same path once a model emits them).
175
+ *
176
+ * @param track The outbound track the frame targets.
177
+ * @param frame The media frame to send.
178
+ */
179
+ /** Flushes the agent's queued outbound voice on barge-in (the user interrupted the agent). */
180
+ FlushOutboundMedia() {
181
+ this.sdk?.flushOutboundAudio();
182
+ }
183
+ SendMedia(track, frame) {
184
+ if (!this.sdk) {
185
+ return; // not connected — drop
186
+ }
187
+ const bytes = this.framePcm(frame);
188
+ if (!bytes) {
189
+ return;
190
+ }
191
+ switch (track) {
192
+ case 'audio-out':
193
+ this.sdk.publishAudioFrame(bytes);
194
+ break;
195
+ case 'video-out':
196
+ if (this.features.VideoOut === true) {
197
+ this.sdk.publishVideoFrame(bytes);
198
+ }
199
+ break;
200
+ case 'screen-out':
201
+ if (this.features.ScreenOut === true) {
202
+ this.sdk.publishScreenFrame(bytes);
203
+ }
204
+ break;
205
+ default:
206
+ // An inbound track was passed to SendMedia — ignore (defensive).
207
+ break;
208
+ }
209
+ }
210
+ /**
211
+ * Registers the inbound-media handler. The driver forwards each raw per-participant audio frame
212
+ * (with its speaker identity) to this handler; the engine routes it to `IRealtimeSession.SendInput`.
213
+ *
214
+ * @param handler Invoked with each inbound media frame.
215
+ */
216
+ OnMedia(handler) {
217
+ this.mediaHandler = handler;
218
+ }
219
+ // ── Capability-gated virtuals LiveKit supports (gated by SupportedFeatures) ───────
220
+ /**
221
+ * Returns the current LiveKit participant roster (gated by `SpeakerDiarization`). Re-asserts the
222
+ * flag (defense-in-depth) so even an engine-bypassing caller cannot pull a roster a disabled
223
+ * provider forbids.
224
+ *
225
+ * @returns The current participants.
226
+ * @throws {BridgeCapabilityNotSupportedError} when diarization is not enabled for the provider.
227
+ */
228
+ async GetParticipants() {
229
+ this.RequireFeature('SpeakerDiarization');
230
+ if (!this.sdk) {
231
+ return [];
232
+ }
233
+ const participants = await this.sdk.getParticipants();
234
+ return participants.map(toBridgeParticipant);
235
+ }
236
+ /**
237
+ * Registers a roster-change handler (gated by `SpeakerDiarization`). The driver fires it from the
238
+ * SDK's participant join/leave stream with the full current roster.
239
+ *
240
+ * @param handler Invoked with the updated participant list on each change.
241
+ * @throws {BridgeCapabilityNotSupportedError} when diarization is not enabled.
242
+ */
243
+ OnParticipantChange(handler) {
244
+ this.RequireFeature('SpeakerDiarization');
245
+ this.participantHandler = handler;
246
+ }
247
+ /**
248
+ * Returns the LiveKit Meeting Controls event source for this session (roster · speaking · mute), or
249
+ * `null` when diarization is off (no roster to facilitate). The engine wires the Meeting Controls
250
+ * channel from this.
251
+ */
252
+ GetMeetingControlsEventSource() {
253
+ return this.meetingControls;
254
+ }
255
+ // ── LiveKit-native surfaces (used by the channel plane / turn-taking) ────────────
256
+ /**
257
+ * Sends a message on the LiveKit data channel — the room-native "chat". Exposed for the channel
258
+ * plane / turn-taking hybrid mode (the social-cost-free "raise hand"). Best-effort — a failure is
259
+ * logged, never fatal.
260
+ *
261
+ * @param text The data/chat message to send.
262
+ */
263
+ async SendDataMessage(text) {
264
+ if (!this.sdk) {
265
+ return;
266
+ }
267
+ try {
268
+ await this.sdk.sendDataMessage(text);
269
+ }
270
+ catch (err) {
271
+ LogError(`[LiveKitBridge] sendDataMessage failed: ${err instanceof Error ? err.message : String(err)}`);
272
+ }
273
+ }
274
+ /** The bot's participant identity in the joined room (or `null` before {@link Connect}). */
275
+ get BotIdentity() {
276
+ return this.botIdentity;
277
+ }
278
+ // ── internals ────────────────────────────────────────────────────────────────────
279
+ /**
280
+ * Wires the SDK's inbound per-participant audio callback to a diarized inbound
281
+ * {@link BridgeMediaFrame}. The SFU never echoes the bot's own audio, so no self-audio gate is
282
+ * needed; the frame's `SpeakerLabel` is the source participant identity.
283
+ */
284
+ wireInboundAudio(sdk) {
285
+ sdk.onAudioTrack((frame) => {
286
+ // Mark the speaking participant on the Meeting Controls source for who's-speaking perception.
287
+ this.meetingControls?.NotifySpeaking([frame.ParticipantIdentity]);
288
+ this.mediaHandler?.({
289
+ Track: 'audio-in',
290
+ Bytes: frame.Pcm,
291
+ SpeakerLabel: frame.ParticipantIdentity,
292
+ TimestampMs: frame.TimestampMs ?? Date.now(),
293
+ });
294
+ });
295
+ }
296
+ /**
297
+ * Wires the SDK's participant join/leave streams ONCE (the driver is the single owner; the SDK seam
298
+ * is latest-handler-wins). Each roster change fans out to both the driver's own roster-change
299
+ * handler and the Meeting Controls source.
300
+ */
301
+ wireRoster(sdk) {
302
+ sdk.onParticipantJoin(() => void this.refreshRoster(sdk));
303
+ sdk.onParticipantLeave(() => void this.refreshRoster(sdk));
304
+ }
305
+ /** Pulls the current roster from the SDK and fans it out to both the participant handler + Meeting Controls. */
306
+ async refreshRoster(sdk) {
307
+ try {
308
+ const participants = await sdk.getParticipants();
309
+ this.participantHandler?.(participants.map(toBridgeParticipant));
310
+ this.meetingControls?.IngestRoster(participants);
311
+ }
312
+ catch (err) {
313
+ LogError(`[LiveKitBridge] roster refresh failed: ${err instanceof Error ? err.message : String(err)}`);
314
+ }
315
+ }
316
+ /** Handles the SDK's room-disconnected signal: surface an empty roster so the engine sees everyone gone. */
317
+ handleRoomDisconnected() {
318
+ this.participantHandler?.([]);
319
+ this.meetingControls?.IngestRoster([]);
320
+ }
321
+ /** Builds the SDK connect args from the bridge context (Address is the room URL; token from config). */
322
+ buildConnectArgs(ctx) {
323
+ const config = ctx.Configuration ?? {};
324
+ return {
325
+ RoomUrl: ctx.Address,
326
+ AccessToken: typeof config.AccessToken === 'string' ? config.AccessToken : '',
327
+ BotDisplayName: typeof config.BotDisplayName === 'string' ? config.BotDisplayName : `${ctx.ProviderName} Agent`,
328
+ };
329
+ }
330
+ /** Returns the bytes of an outbound media frame, preferring the binary payload. */
331
+ framePcm(frame) {
332
+ if (frame.Bytes) {
333
+ return frame.Bytes;
334
+ }
335
+ if (frame.Base64) {
336
+ return this.decodeBase64(frame.Base64);
337
+ }
338
+ return undefined;
339
+ }
340
+ /** Decodes a base64 payload to an `ArrayBuffer` (Node `Buffer` fast path, `atob` fallback). */
341
+ decodeBase64(base64) {
342
+ if (typeof Buffer !== 'undefined') {
343
+ const buf = Buffer.from(base64, 'base64');
344
+ const out = new Uint8Array(buf.byteLength);
345
+ out.set(buf);
346
+ return out.buffer;
347
+ }
348
+ const binary = atob(base64);
349
+ const bytes = new Uint8Array(binary.length);
350
+ for (let i = 0; i < binary.length; i++) {
351
+ bytes[i] = binary.charCodeAt(i);
352
+ }
353
+ return bytes.buffer;
354
+ }
355
+ };
356
+ LiveKitBridge = __decorate([
357
+ RegisterClass(BaseRealtimeBridge, LIVEKIT_BRIDGE_DRIVER_CLASS)
358
+ ], LiveKitBridge);
359
+ export { LiveKitBridge };
360
+ /**
361
+ * Tree-shaking-prevention loader. Modern bundlers cannot see the `@RegisterClass` dynamic registration
362
+ * of {@link LiveKitBridge} and may eliminate it. Import and call this no-op from a static code path
363
+ * (the package entry point does) so the `ClassFactory` can resolve `'LiveKitBridge'`.
364
+ */
365
+ export function LoadLiveKitBridge() {
366
+ // Intentionally empty — referencing the module is what prevents tree-shaking.
367
+ }
368
+ //# sourceMappingURL=livekit-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"livekit-bridge.js","sourceRoot":"","sources":["../src/livekit-bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Adapts the {@link ILiveKitRoomSdk} participant / speaking stream into an
3
+ * {@link IBridgeMeetingControlsEventSource} so the bridge engine can wire the **Meeting Controls**
4
+ * facilitator channel (roster · who's-speaking · hand-raise queue) for a LiveKit room session. This is
5
+ * the §4b "the bridge contributes a channel" pattern realized for the MJ-native room — no channel logic
6
+ * lives here, only the platform→channel signal mapping.
7
+ *
8
+ * ## What LiveKit contributes (vs. Zoom)
9
+ * - **Roster + speaking**: native (per-participant tracks → diarization → speaking attribution).
10
+ * - **Hand-raise**: LiveKit has **no built-in raise-hand**. The channel still offers the facilitator
11
+ * hand-raise *queue* (an MJ construct), but no PLATFORM hand-raise signal arrives, so
12
+ * {@link IngestHandRaise} is available for an app that layers raise-hand over data messages — it is
13
+ * simply never fired by the room itself.
14
+ * - **Mute**: LiveKit room-admin can mute a participant's published track, so the `Mute` capability is
15
+ * advertised and {@link MuteParticipant} actuates it through the SDK adapter.
16
+ */
17
+ import { IBridgeMeetingControlsEventSource, BridgeMeetingParticipant, BridgeMeetingControlsCapability } from '@memberjunction/ai-bridge-base';
18
+ import { ILiveKitRoomSdk, LiveKitParticipant } from './livekit-sdk.js';
19
+ /** Maps one LiveKit participant onto the channel's {@link BridgeMeetingParticipant} shape. */
20
+ export declare function toMeetingParticipant(p: LiveKitParticipant): BridgeMeetingParticipant;
21
+ /**
22
+ * LiveKit's adapter to the Meeting Controls channel's event source. It maintains a live roster and
23
+ * mirrors diarized speaking signals, and actuates mute through the SDK adapter.
24
+ *
25
+ * **The driver owns the SDK subscriptions** (the SDK seam is "latest handler wins") and **feeds this
26
+ * source imperatively** via {@link IngestRoster} / {@link IngestSpeaking} / {@link IngestHandRaise}.
27
+ * This source only needs the SDK for the one *action* it actuates — {@link MuteParticipant}. The
28
+ * Meeting Controls channel subscribes the three `On*` streams in turn.
29
+ *
30
+ * The driver constructs ONE of these per session (only when the provider has `SpeakerDiarization`, i.e.
31
+ * there is a roster to facilitate) and returns it from `LiveKitBridge.GetMeetingControlsEventSource`.
32
+ */
33
+ export declare class LiveKitMeetingControlsEventSource implements IBridgeMeetingControlsEventSource {
34
+ private readonly sdk;
35
+ /** Live roster keyed by participant identity (lowercased), kept current by {@link IngestRoster}. */
36
+ private readonly roster;
37
+ private rosterHandler?;
38
+ private speakingHandler?;
39
+ private handRaiseHandler?;
40
+ /** LiveKit room-admin can mute a published track, so the facilitator `MuteParticipant` tool is offered. */
41
+ readonly Capabilities: ReadonlyArray<BridgeMeetingControlsCapability>;
42
+ /**
43
+ * @param sdk The LiveKit SDK seam used only to actuate {@link MuteParticipant} — all *perception* is
44
+ * fed in by the driver via the `Ingest*` methods so the SDK keeps a single subscriber per event.
45
+ */
46
+ constructor(sdk: ILiveKitRoomSdk);
47
+ /**
48
+ * Replaces the roster with a fresh snapshot from the driver and emits it to the channel. Called by
49
+ * the driver after the initial connect and on every participant join/leave.
50
+ *
51
+ * @param participants The full current LiveKit roster.
52
+ */
53
+ IngestRoster(participants: LiveKitParticipant[]): void;
54
+ /**
55
+ * Forwards a hand-raise/lower signal to the channel. LiveKit emits none natively; this exists for an
56
+ * app that layers raise-hand over data messages.
57
+ *
58
+ * @param participantId The participant whose hand changed.
59
+ * @param raised Whether the hand is now raised.
60
+ */
61
+ IngestHandRaise(participantId: string, raised: boolean): void;
62
+ /**
63
+ * Forwards a diarized speaking-set change from the driver to the channel.
64
+ *
65
+ * @param participantIds The identities currently speaking.
66
+ */
67
+ IngestSpeaking(participantIds: string[]): void;
68
+ /**
69
+ * Convenience alias for {@link IngestSpeaking}, kept because "notify" reads naturally at the
70
+ * driver's inbound-audio call site.
71
+ *
72
+ * @param participantIds The identities currently speaking.
73
+ */
74
+ NotifySpeaking(participantIds: string[]): void;
75
+ /** @inheritdoc */
76
+ OnRosterChange(handler: (participants: BridgeMeetingParticipant[]) => void): void;
77
+ /** @inheritdoc */
78
+ OnSpeakingChange(handler: (participantIds: string[]) => void): void;
79
+ /** @inheritdoc */
80
+ OnHandRaiseChange(handler: (participantId: string, raised: boolean) => void): void;
81
+ /** @inheritdoc */
82
+ MuteParticipant(participantId: string): Promise<void>;
83
+ /** Actuates a mute through the SDK adapter's data-channel admin path. */
84
+ private muteViaSdk;
85
+ private emitRoster;
86
+ private key;
87
+ }
88
+ //# sourceMappingURL=livekit-meeting-controls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"livekit-meeting-controls.d.ts","sourceRoot":"","sources":["../src/livekit-meeting-controls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EACH,iCAAiC,EACjC,wBAAwB,EACxB,+BAA+B,EAElC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAA0B,MAAM,eAAe,CAAC;AAoB5F,8FAA8F;AAC9F,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,kBAAkB,GAAG,wBAAwB,CAOpF;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,iCAAkC,YAAW,iCAAiC;IAe3E,OAAO,CAAC,QAAQ,CAAC,GAAG;IAdhC,oGAAoG;IACpG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+C;IAEtE,OAAO,CAAC,aAAa,CAAC,CAAqD;IAC3E,OAAO,CAAC,eAAe,CAAC,CAAqC;IAC7D,OAAO,CAAC,gBAAgB,CAAC,CAAmD;IAE5E,2GAA2G;IAC3G,SAAgB,YAAY,EAAE,aAAa,CAAC,+BAA+B,CAAC,CAAY;IAExF;;;OAGG;gBAC0B,GAAG,EAAE,eAAe;IAIjD;;;;;OAKG;IACI,YAAY,CAAC,YAAY,EAAE,kBAAkB,EAAE,GAAG,IAAI;IAQ7D;;;;;;OAMG;IACI,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IAIpE;;;;OAIG;IACI,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,IAAI;IAIrD;;;;;OAKG;IACI,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,IAAI;IAMrD,kBAAkB;IACX,cAAc,CAAC,OAAO,EAAE,CAAC,YAAY,EAAE,wBAAwB,EAAE,KAAK,IAAI,GAAG,IAAI;IAMxF,kBAAkB;IACX,gBAAgB,CAAC,OAAO,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,IAAI,GAAG,IAAI;IAI1E,kBAAkB;IACX,iBAAiB,CAAC,OAAO,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI;IAIzF,kBAAkB;IACL,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQlE,yEAAyE;YAC3D,UAAU;IAMxB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,GAAG;CAGd"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Adapts the {@link ILiveKitRoomSdk} participant / speaking stream into an
3
+ * {@link IBridgeMeetingControlsEventSource} so the bridge engine can wire the **Meeting Controls**
4
+ * facilitator channel (roster · who's-speaking · hand-raise queue) for a LiveKit room session. This is
5
+ * the §4b "the bridge contributes a channel" pattern realized for the MJ-native room — no channel logic
6
+ * lives here, only the platform→channel signal mapping.
7
+ *
8
+ * ## What LiveKit contributes (vs. Zoom)
9
+ * - **Roster + speaking**: native (per-participant tracks → diarization → speaking attribution).
10
+ * - **Hand-raise**: LiveKit has **no built-in raise-hand**. The channel still offers the facilitator
11
+ * hand-raise *queue* (an MJ construct), but no PLATFORM hand-raise signal arrives, so
12
+ * {@link IngestHandRaise} is available for an app that layers raise-hand over data messages — it is
13
+ * simply never fired by the room itself.
14
+ * - **Mute**: LiveKit room-admin can mute a participant's published track, so the `Mute` capability is
15
+ * advertised and {@link MuteParticipant} actuates it through the SDK adapter.
16
+ */
17
+ /**
18
+ * Maps a LiveKit participant role to the bridge's meeting-participant role. LiveKit has no distinct
19
+ * "Agent" role; the bot is flagged via `IsLocal` and surfaced as `'Agent'`.
20
+ */
21
+ function mapRole(role, isLocal) {
22
+ if (isLocal) {
23
+ return 'Agent';
24
+ }
25
+ switch (role) {
26
+ case 'Host':
27
+ return 'Host';
28
+ case 'CoHost':
29
+ return 'CoHost';
30
+ default:
31
+ return 'Participant';
32
+ }
33
+ }
34
+ /** Maps one LiveKit participant onto the channel's {@link BridgeMeetingParticipant} shape. */
35
+ export function toMeetingParticipant(p) {
36
+ return {
37
+ ParticipantId: p.Identity,
38
+ DisplayName: p.DisplayName,
39
+ Role: mapRole(p.Role, p.IsLocal),
40
+ IsAgent: p.IsLocal === true,
41
+ };
42
+ }
43
+ /**
44
+ * LiveKit's adapter to the Meeting Controls channel's event source. It maintains a live roster and
45
+ * mirrors diarized speaking signals, and actuates mute through the SDK adapter.
46
+ *
47
+ * **The driver owns the SDK subscriptions** (the SDK seam is "latest handler wins") and **feeds this
48
+ * source imperatively** via {@link IngestRoster} / {@link IngestSpeaking} / {@link IngestHandRaise}.
49
+ * This source only needs the SDK for the one *action* it actuates — {@link MuteParticipant}. The
50
+ * Meeting Controls channel subscribes the three `On*` streams in turn.
51
+ *
52
+ * The driver constructs ONE of these per session (only when the provider has `SpeakerDiarization`, i.e.
53
+ * there is a roster to facilitate) and returns it from `LiveKitBridge.GetMeetingControlsEventSource`.
54
+ */
55
+ export class LiveKitMeetingControlsEventSource {
56
+ /**
57
+ * @param sdk The LiveKit SDK seam used only to actuate {@link MuteParticipant} — all *perception* is
58
+ * fed in by the driver via the `Ingest*` methods so the SDK keeps a single subscriber per event.
59
+ */
60
+ constructor(sdk) {
61
+ this.sdk = sdk;
62
+ /** Live roster keyed by participant identity (lowercased), kept current by {@link IngestRoster}. */
63
+ this.roster = new Map();
64
+ /** LiveKit room-admin can mute a published track, so the facilitator `MuteParticipant` tool is offered. */
65
+ this.Capabilities = ['Mute'];
66
+ }
67
+ // ── Driver-fed perception (the driver owns the SDK subscriptions) ─────────────────
68
+ /**
69
+ * Replaces the roster with a fresh snapshot from the driver and emits it to the channel. Called by
70
+ * the driver after the initial connect and on every participant join/leave.
71
+ *
72
+ * @param participants The full current LiveKit roster.
73
+ */
74
+ IngestRoster(participants) {
75
+ this.roster.clear();
76
+ for (const p of participants) {
77
+ this.roster.set(this.key(p.Identity), toMeetingParticipant(p));
78
+ }
79
+ this.emitRoster();
80
+ }
81
+ /**
82
+ * Forwards a hand-raise/lower signal to the channel. LiveKit emits none natively; this exists for an
83
+ * app that layers raise-hand over data messages.
84
+ *
85
+ * @param participantId The participant whose hand changed.
86
+ * @param raised Whether the hand is now raised.
87
+ */
88
+ IngestHandRaise(participantId, raised) {
89
+ this.handRaiseHandler?.(participantId, raised);
90
+ }
91
+ /**
92
+ * Forwards a diarized speaking-set change from the driver to the channel.
93
+ *
94
+ * @param participantIds The identities currently speaking.
95
+ */
96
+ IngestSpeaking(participantIds) {
97
+ this.speakingHandler?.(participantIds);
98
+ }
99
+ /**
100
+ * Convenience alias for {@link IngestSpeaking}, kept because "notify" reads naturally at the
101
+ * driver's inbound-audio call site.
102
+ *
103
+ * @param participantIds The identities currently speaking.
104
+ */
105
+ NotifySpeaking(participantIds) {
106
+ this.IngestSpeaking(participantIds);
107
+ }
108
+ // ── IBridgeMeetingControlsEventSource ────────────────────────────────────────────
109
+ /** @inheritdoc */
110
+ OnRosterChange(handler) {
111
+ this.rosterHandler = handler;
112
+ // Emit the current roster immediately so a late subscriber is not blank.
113
+ this.emitRoster();
114
+ }
115
+ /** @inheritdoc */
116
+ OnSpeakingChange(handler) {
117
+ this.speakingHandler = handler;
118
+ }
119
+ /** @inheritdoc */
120
+ OnHandRaiseChange(handler) {
121
+ this.handRaiseHandler = handler;
122
+ }
123
+ /** @inheritdoc */
124
+ async MuteParticipant(participantId) {
125
+ // LiveKit mute is actuated by the room-admin path; the SDK adapter maps this onto its
126
+ // mute-published-track call. The fake captures it for tests.
127
+ await this.muteViaSdk(participantId);
128
+ }
129
+ // ── internals ────────────────────────────────────────────────────────────────────
130
+ /** Actuates a mute through the SDK adapter's data-channel admin path. */
131
+ async muteViaSdk(participantId) {
132
+ // The SDK seam does not surface a dedicated mute primitive (room-admin mute is a server-SDK
133
+ // concern bound at deployment); we signal it as a structured data message the adapter recognizes.
134
+ await this.sdk.sendDataMessage(`__mj_mute:${participantId}`);
135
+ }
136
+ emitRoster() {
137
+ this.rosterHandler?.(Array.from(this.roster.values()));
138
+ }
139
+ key(identity) {
140
+ return identity.trim().toLowerCase();
141
+ }
142
+ }
143
+ //# sourceMappingURL=livekit-meeting-controls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"livekit-meeting-controls.js","sourceRoot":"","sources":["../src/livekit-meeting-controls.ts"],"names":[],"mappings":"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"}