@memberjunction/livekit-room-server 6.2.0-edge.1 → 6.2.0-edge.3

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 (78) hide show
  1. package/README.md +85 -0
  2. package/dist/index.d.ts +17 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +17 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/livekit-agent-room-coordinator.d.ts +105 -8
  7. package/dist/livekit-agent-room-coordinator.d.ts.map +1 -1
  8. package/dist/livekit-agent-room-coordinator.js +125 -25
  9. package/dist/livekit-agent-room-coordinator.js.map +1 -1
  10. package/dist/livekit-egress-service.d.ts +7 -0
  11. package/dist/livekit-egress-service.d.ts.map +1 -1
  12. package/dist/livekit-egress-service.js +11 -0
  13. package/dist/livekit-egress-service.js.map +1 -1
  14. package/dist/livekit-native-module.d.ts +18 -0
  15. package/dist/livekit-native-module.d.ts.map +1 -0
  16. package/dist/livekit-native-module.js +20 -0
  17. package/dist/livekit-native-module.js.map +1 -0
  18. package/dist/livekit-sip-service.d.ts +141 -0
  19. package/dist/livekit-sip-service.d.ts.map +1 -0
  20. package/dist/livekit-sip-service.js +169 -0
  21. package/dist/livekit-sip-service.js.map +1 -0
  22. package/dist/livekit-token-service.d.ts +6 -0
  23. package/dist/livekit-token-service.d.ts.map +1 -1
  24. package/dist/livekit-token-service.js +8 -0
  25. package/dist/livekit-token-service.js.map +1 -1
  26. package/dist/livekit-webhook.d.ts +65 -0
  27. package/dist/livekit-webhook.d.ts.map +1 -0
  28. package/dist/livekit-webhook.js +65 -0
  29. package/dist/livekit-webhook.js.map +1 -0
  30. package/dist/meeting-dial-in-service.d.ts +75 -0
  31. package/dist/meeting-dial-in-service.d.ts.map +1 -0
  32. package/dist/meeting-dial-in-service.js +175 -0
  33. package/dist/meeting-dial-in-service.js.map +1 -0
  34. package/dist/room-audio/audio-decoder.d.ts +40 -0
  35. package/dist/room-audio/audio-decoder.d.ts.map +1 -0
  36. package/dist/room-audio/audio-decoder.js +205 -0
  37. package/dist/room-audio/audio-decoder.js.map +1 -0
  38. package/dist/room-audio/comfort-tone.d.ts +23 -0
  39. package/dist/room-audio/comfort-tone.d.ts.map +1 -0
  40. package/dist/room-audio/comfort-tone.js +70 -0
  41. package/dist/room-audio/comfort-tone.js.map +1 -0
  42. package/dist/room-audio/pcm-frame-pump.d.ts +134 -0
  43. package/dist/room-audio/pcm-frame-pump.d.ts.map +1 -0
  44. package/dist/room-audio/pcm-frame-pump.js +253 -0
  45. package/dist/room-audio/pcm-frame-pump.js.map +1 -0
  46. package/dist/room-audio/room-audio-decode-cache.d.ts +37 -0
  47. package/dist/room-audio/room-audio-decode-cache.d.ts.map +1 -0
  48. package/dist/room-audio/room-audio-decode-cache.js +75 -0
  49. package/dist/room-audio/room-audio-decode-cache.js.map +1 -0
  50. package/dist/room-audio/room-audio-player.d.ts +178 -0
  51. package/dist/room-audio/room-audio-player.d.ts.map +1 -0
  52. package/dist/room-audio/room-audio-player.js +404 -0
  53. package/dist/room-audio/room-audio-player.js.map +1 -0
  54. package/dist/room-audio/room-audio-sources.d.ts +47 -0
  55. package/dist/room-audio/room-audio-sources.d.ts.map +1 -0
  56. package/dist/room-audio/room-audio-sources.js +116 -0
  57. package/dist/room-audio/room-audio-sources.js.map +1 -0
  58. package/dist/room-audio/room-speech-synthesizer.d.ts +55 -0
  59. package/dist/room-audio/room-speech-synthesizer.d.ts.map +1 -0
  60. package/dist/room-audio/room-speech-synthesizer.js +56 -0
  61. package/dist/room-audio/room-speech-synthesizer.js.map +1 -0
  62. package/dist/room-authorization.d.ts +58 -0
  63. package/dist/room-authorization.d.ts.map +1 -0
  64. package/dist/room-authorization.js +212 -0
  65. package/dist/room-authorization.js.map +1 -0
  66. package/dist/room-handoff/handoff-offer-registry.d.ts +137 -0
  67. package/dist/room-handoff/handoff-offer-registry.d.ts.map +1 -0
  68. package/dist/room-handoff/handoff-offer-registry.js +521 -0
  69. package/dist/room-handoff/handoff-offer-registry.js.map +1 -0
  70. package/dist/room-handoff/handoff-types.d.ts +170 -0
  71. package/dist/room-handoff/handoff-types.d.ts.map +1 -0
  72. package/dist/room-handoff/handoff-types.js +16 -0
  73. package/dist/room-handoff/handoff-types.js.map +1 -0
  74. package/dist/room-handoff/room-handoff-engine.d.ts +141 -0
  75. package/dist/room-handoff/room-handoff-engine.d.ts.map +1 -0
  76. package/dist/room-handoff/room-handoff-engine.js +530 -0
  77. package/dist/room-handoff/room-handoff-engine.js.map +1 -0
  78. package/package.json +14 -9
@@ -0,0 +1,178 @@
1
+ /**
2
+ * @fileoverview {@link RoomAudioPlayer} — joins a LiveKit room as a small publish-only bot and plays audio into it:
3
+ * contact-center hold music, with spoken announcements ducked over the top ("you are caller number three").
4
+ *
5
+ * The audio comes from an `MJ: Files` row, an https URL, raw PCM, or the built-in comfort tone. It is decoded once
6
+ * (WAV or MP3), resampled to {@link ROOM_AUDIO_SAMPLE_RATE}, cached by source, and paced into the room in 20 ms
7
+ * frames by a {@link PcmFramePump}. Each playback is a {@link RoomAudioHandle} the caller pauses, resumes, announces
8
+ * through and stops; the player keeps a registry so a room's playbacks can be found and stopped together (the
9
+ * `room_finished` webhook does this), and a playback whose bot is disconnected by the server removes itself.
10
+ *
11
+ * It reuses the agent bridge's plumbing rather than its own: bot tokens from {@link LiveKitTokenService}, the native
12
+ * room client through `@memberjunction/ai-bridge-livekit`'s module loader at the same specifier the agent coordinator
13
+ * resolves, and the PCM16 resampler from `@memberjunction/ai-bridge-base`.
14
+ *
15
+ * @module @memberjunction/livekit-room-server
16
+ */
17
+ import { BaseSingleton } from '@memberjunction/global';
18
+ import { type IMetadataProvider, type UserInfo } from '@memberjunction/core';
19
+ import { type NativeModuleLoader } from '@memberjunction/ai-bridge-livekit';
20
+ import { LiveKitTokenService } from '../livekit-token-service.js';
21
+ import { type PumpClock } from './pcm-frame-pump.js';
22
+ import { type RoomAudioFileReader, type RoomAudioUrlFetcher } from './room-audio-sources.js';
23
+ import type { IRoomSpeechSynthesizer } from './room-speech-synthesizer.js';
24
+ /** The rate the player publishes at (Hz). Music sounds noticeably duller at the 24 kHz the agent voice uses. */
25
+ export declare const ROOM_AUDIO_SAMPLE_RATE = 48000;
26
+ /** What to play: an `MJ: Files` row, an https URL, raw mono PCM16, or the built-in comfort tone. */
27
+ export type RoomAudioSource = {
28
+ Kind: 'File';
29
+ FileID: string;
30
+ } | {
31
+ Kind: 'Url';
32
+ Url: string;
33
+ } | {
34
+ Kind: 'Pcm';
35
+ Pcm: Int16Array;
36
+ SampleRate: number;
37
+ } | {
38
+ Kind: 'ComfortTone';
39
+ };
40
+ /** Parameters for {@link RoomAudioPlayer.Start}. */
41
+ export interface StartRoomAudioParams {
42
+ /** The LiveKit room to play into. */
43
+ RoomName: string;
44
+ /** What to play. */
45
+ Source: RoomAudioSource;
46
+ /** Loop the audio until stopped. Default `true`; when `false` the bot leaves the room once the audio ends. */
47
+ Loop?: boolean;
48
+ /** The bot's display name in the room. Default `'On hold'`. */
49
+ DisplayName?: string;
50
+ /** The bot's participant identity. Default `hold-<playback id>`. */
51
+ Identity?: string;
52
+ /** The user the file load (and any announcement synthesis) runs as. */
53
+ ContextUser: UserInfo;
54
+ /** The metadata provider for the file load. Default: the global provider. */
55
+ Provider?: IMetadataProvider;
56
+ }
57
+ /**
58
+ * Why a playback ended, as reported by {@link RoomAudioHandle.Ended}:
59
+ * - `Completed`: non-looping audio played to its end (every frame went to the room).
60
+ * - `Stopped`: {@link RoomAudioHandle.Stop} was called (or {@link RoomAudioPlayer.StopAllInRoom}).
61
+ * - `Failed`: the bot's audio sink failed mid-playback, so the audio was cut short.
62
+ * - `Disconnected`: the server dropped the bot (room closed, participant removed) before the audio ended.
63
+ */
64
+ export type RoomAudioEndReason = 'Completed' | 'Stopped' | 'Failed' | 'Disconnected';
65
+ /** An announcement: text to speak through the speech synthesizer, or ready-made mono PCM16. */
66
+ export type RoomAudioClip = {
67
+ Text: string;
68
+ } | {
69
+ Pcm: Int16Array;
70
+ SampleRate: number;
71
+ };
72
+ /** A live playback in one room. */
73
+ export interface RoomAudioHandle {
74
+ /** This playback's id (also the default identity suffix of its bot). */
75
+ readonly PlaybackID: string;
76
+ /** The room it plays into. */
77
+ readonly RoomName: string;
78
+ /** True while audio is being sent (false once paused or stopped). */
79
+ readonly IsPlaying: boolean;
80
+ /** Why the playback ended, or `null` while it is still live. */
81
+ readonly EndReason: RoomAudioEndReason | null;
82
+ /**
83
+ * Resolves once the playback has ended and its bot has left the room, with the reason. Only `Completed` means the
84
+ * whole clip reached the room — use it to gate anything that must follow a full play, e.g. starting a recording
85
+ * only after a recording disclosure has been heard. A looping playback never completes on its own; it resolves
86
+ * `Stopped` when stopped. The first reason wins. Never rejects.
87
+ */
88
+ readonly Ended: Promise<RoomAudioEndReason>;
89
+ /**
90
+ * Plays a clip over the music, ducking the music while it plays. Resolves `true` once the clip has been sent in
91
+ * full; `false` when it could not be played (no speech synthesizer installed, synthesis failed, or the playback
92
+ * stopped first). Never rejects.
93
+ */
94
+ Announce(clip: RoomAudioClip): Promise<boolean>;
95
+ /** Stops sending audio, keeping the position. */
96
+ Pause(): void;
97
+ /** Continues after {@link Pause}. */
98
+ Resume(): void;
99
+ /** Stops playback and disconnects the bot. Idempotent; never rejects. */
100
+ Stop(): Promise<void>;
101
+ }
102
+ /** The part of {@link LiveKitTokenService} the player uses — a seam for tests. */
103
+ export type RoomAudioTokenMinter = Pick<LiveKitTokenService, 'MintToken'>;
104
+ /**
105
+ * Plays audio into LiveKit rooms. A process-wide singleton so the playback registry (and the `room_finished` cleanup
106
+ * that reads it) is shared by every caller.
107
+ */
108
+ export declare class RoomAudioPlayer extends BaseSingleton<RoomAudioPlayer> {
109
+ private readonly active;
110
+ private readonly decodeCache;
111
+ private tokenMinter;
112
+ private loadNativeModule;
113
+ private nativeModuleSpecifierOverride?;
114
+ private fileReader;
115
+ private urlFetcher;
116
+ private clock;
117
+ private speechSynthesizer?;
118
+ private warnedNoSynthesizer;
119
+ private comfortTone?;
120
+ /** BaseSingleton requires a protected constructor. */
121
+ protected constructor();
122
+ /** The process-wide player. */
123
+ static get Instance(): RoomAudioPlayer;
124
+ /**
125
+ * Installs the text-to-speech used by {@link RoomAudioHandle.Announce} with `{ Text }` — e.g. an
126
+ * `MJRoomSpeechSynthesizer`. None is installed by default (a voice is vendor-specific); without one a text
127
+ * announcement returns `false` and the player logs the omission once.
128
+ *
129
+ * @param synthesizer The synthesizer, or `undefined` to remove it.
130
+ */
131
+ SetSpeechSynthesizer(synthesizer: IRoomSpeechSynthesizer | undefined): void;
132
+ /** Overrides where bot tokens come from (tests; a host with its own credentials). */
133
+ SetTokenService(minter: RoomAudioTokenMinter): void;
134
+ /** Overrides the native room-module loader (tests inject a fake module). */
135
+ SetNativeModuleLoader(loader: NativeModuleLoader): void;
136
+ /** Overrides the native room-module specifier; `undefined` returns to `LIVEKIT_NATIVE_MODULE` / the default. */
137
+ SetNativeModuleSpecifier(specifier: string | undefined): void;
138
+ /** Overrides how `MJ: Files` bytes are read (tests). */
139
+ SetFileReader(reader: RoomAudioFileReader): void;
140
+ /** Overrides how URLs are fetched (tests). */
141
+ SetUrlFetcher(fetcher: RoomAudioUrlFetcher): void;
142
+ /** Overrides the pacing clock (tests). */
143
+ SetClock(clock: PumpClock): void;
144
+ /** Forgets every cached decode (e.g. after a hold file is replaced under the same id). */
145
+ ClearDecodeCache(): void;
146
+ /**
147
+ * Starts playing `params.Source` into `params.RoomName`: loads and decodes it (cached by source), joins the room as
148
+ * a publish-only bot, and paces the audio in.
149
+ *
150
+ * @returns The live playback.
151
+ * @throws {Error} when the source cannot be loaded or decoded, LiveKit is not configured, or the bot cannot join.
152
+ */
153
+ Start(params: StartRoomAudioParams): Promise<RoomAudioHandle>;
154
+ /** The live playbacks in a room (room names compare case-insensitively). */
155
+ GetActive(roomName: string): RoomAudioHandle[];
156
+ /**
157
+ * Stops every playback in a room. Never throws.
158
+ *
159
+ * @returns How many playbacks were stopped.
160
+ */
161
+ StopAllInRoom(roomName: string): Promise<number>;
162
+ /** Resolves the source to mono PCM16 at {@link ROOM_AUDIO_SAMPLE_RATE}, through the decode cache where it can. */
163
+ private resolvePcm;
164
+ private decodeContent;
165
+ /** Joins the room as the playback's bot, registers the playback, and starts the pump. */
166
+ private joinAndPlay;
167
+ /** A publish-only bot token: it never needs to hear the room, and it carries no data channel. */
168
+ private mintBotToken;
169
+ /** Connects the bot; on failure always releases the client before rethrowing. */
170
+ private connectClient;
171
+ private createPump;
172
+ private playbackOwner;
173
+ private deregister;
174
+ /** The server dropped the bot (room closed, participant removed): stop and forget the playback. */
175
+ private handleServerDisconnect;
176
+ private synthesize;
177
+ }
178
+ //# sourceMappingURL=room-audio-player.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"room-audio-player.d.ts","sourceRoot":"","sources":["../../src/room-audio/room-audio-player.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,404 @@
1
+ /**
2
+ * @fileoverview {@link RoomAudioPlayer} — joins a LiveKit room as a small publish-only bot and plays audio into it:
3
+ * contact-center hold music, with spoken announcements ducked over the top ("you are caller number three").
4
+ *
5
+ * The audio comes from an `MJ: Files` row, an https URL, raw PCM, or the built-in comfort tone. It is decoded once
6
+ * (WAV or MP3), resampled to {@link ROOM_AUDIO_SAMPLE_RATE}, cached by source, and paced into the room in 20 ms
7
+ * frames by a {@link PcmFramePump}. Each playback is a {@link RoomAudioHandle} the caller pauses, resumes, announces
8
+ * through and stops; the player keeps a registry so a room's playbacks can be found and stopped together (the
9
+ * `room_finished` webhook does this), and a playback whose bot is disconnected by the server removes itself.
10
+ *
11
+ * It reuses the agent bridge's plumbing rather than its own: bot tokens from {@link LiveKitTokenService}, the native
12
+ * room client through `@memberjunction/ai-bridge-livekit`'s module loader at the same specifier the agent coordinator
13
+ * resolves, and the PCM16 resampler from `@memberjunction/ai-bridge-base`.
14
+ *
15
+ * @module @memberjunction/livekit-room-server
16
+ */
17
+ import { randomUUID } from 'node:crypto';
18
+ import { BaseSingleton, NormalizeUUID } from '@memberjunction/global';
19
+ import { LogError, LogStatus } from '@memberjunction/core';
20
+ import { ResamplePcm16 } from '@memberjunction/ai-bridge-base';
21
+ import { DefaultNativeLoader } from '@memberjunction/ai-bridge-livekit';
22
+ import { LiveKitTokenService } from '../livekit-token-service.js';
23
+ import { ResolveLiveKitNativeModuleSpecifier } from '../livekit-native-module.js';
24
+ import { AssertAudioDurationWithinCap, DecodeAudio } from './audio-decoder.js';
25
+ import { GenerateComfortTone } from './comfort-tone.js';
26
+ import { PcmFramePump, SystemPumpClock } from './pcm-frame-pump.js';
27
+ import { RoomAudioDecodeCache } from './room-audio-decode-cache.js';
28
+ import { AssertHttpsAudioUrl, FetchRoomAudioUrl, ReadMJFileAudio, } from './room-audio-sources.js';
29
+ /** How often a finished playback checks whether its already-sent audio has played out, in ms. */
30
+ const DRAIN_TICK_MS = 10;
31
+ /** The rate the player publishes at (Hz). Music sounds noticeably duller at the 24 kHz the agent voice uses. */
32
+ export const ROOM_AUDIO_SAMPLE_RATE = 48000;
33
+ /** One playback: its bot's room client and its frame pump. Created only by {@link RoomAudioPlayer}. */
34
+ class RoomAudioPlayback {
35
+ constructor(PlaybackID, RoomName, client, contextUser, owner) {
36
+ this.PlaybackID = PlaybackID;
37
+ this.RoomName = RoomName;
38
+ this.client = client;
39
+ this.contextUser = contextUser;
40
+ this.owner = owner;
41
+ this.pump = null;
42
+ this.stopPromise = null;
43
+ this.endReason = null;
44
+ this.drainTicker = null;
45
+ this.resolveEnded = () => undefined;
46
+ this.Ended = new Promise((resolve) => {
47
+ this.resolveEnded = resolve;
48
+ });
49
+ }
50
+ get EndReason() {
51
+ return this.endReason;
52
+ }
53
+ get IsPlaying() {
54
+ return this.stopPromise === null && this.pump !== null && this.pump.IsRunning && !this.pump.IsPaused;
55
+ }
56
+ /** True once {@link Stop} has begun. */
57
+ get IsStopped() {
58
+ return this.stopPromise !== null;
59
+ }
60
+ /** Attaches and starts the pump (once the bot is in the room). */
61
+ Play(pump) {
62
+ this.pump = pump;
63
+ pump.Start();
64
+ }
65
+ async Announce(clip) {
66
+ if (this.IsStopped || !this.pump) {
67
+ return false;
68
+ }
69
+ try {
70
+ const audio = 'Text' in clip ? await this.owner.Synthesize(clip.Text, this.contextUser) : validateClipPcm(clip);
71
+ if (!audio || this.IsStopped) {
72
+ return false;
73
+ }
74
+ return await this.pump.InsertClip(ResamplePcm16(audio.Pcm, audio.SampleRate, ROOM_AUDIO_SAMPLE_RATE));
75
+ }
76
+ catch (err) {
77
+ LogError(`[RoomAudioPlayer] announcement in room ${this.RoomName} (playback ${this.PlaybackID}) failed: ${errorText(err)}`);
78
+ return false;
79
+ }
80
+ }
81
+ Pause() {
82
+ this.pump?.Pause();
83
+ }
84
+ Resume() {
85
+ this.pump?.Resume();
86
+ }
87
+ Stop() {
88
+ return this.EndWith('Stopped');
89
+ }
90
+ /**
91
+ * Ends the playback for `reason` (the first reason recorded wins), stops the pump, disconnects the bot, and
92
+ * resolves {@link Ended} once the bot has left. Idempotent; never rejects.
93
+ */
94
+ EndWith(reason) {
95
+ this.endReason ??= reason;
96
+ this.stopPromise ??= this.stopOnce();
97
+ return this.stopPromise;
98
+ }
99
+ /**
100
+ * The audio ran out: wait until the frames already sent ahead of real time have played, then end `Completed`, so
101
+ * the bot does not leave (dropping its queued tail) before the last of the clip is heard. A Stop or server
102
+ * disconnect during the wait wins instead.
103
+ */
104
+ CompleteAfterDrain(clock) {
105
+ const drainMs = this.pump?.QueuedAheadMs ?? 0;
106
+ if (drainMs <= 0 || this.IsStopped) {
107
+ void this.EndWith('Completed');
108
+ return;
109
+ }
110
+ const deadline = clock.Now() + drainMs;
111
+ this.drainTicker = clock.StartTicker(() => {
112
+ if (clock.Now() >= deadline) {
113
+ void this.EndWith('Completed');
114
+ }
115
+ }, DRAIN_TICK_MS);
116
+ }
117
+ /** Stops the pump, then always disconnects the bot and leaves the registry, whatever failed before. */
118
+ async stopOnce() {
119
+ this.drainTicker?.Cancel();
120
+ this.drainTicker = null;
121
+ try {
122
+ this.pump?.Stop();
123
+ }
124
+ catch (err) {
125
+ LogError(`[RoomAudioPlayer] stopping the pump for playback ${this.PlaybackID} failed: ${errorText(err)}`);
126
+ }
127
+ finally {
128
+ try {
129
+ await this.client.disconnect();
130
+ }
131
+ catch (err) {
132
+ LogError(`[RoomAudioPlayer] disconnecting playback ${this.PlaybackID} from room ${this.RoomName} failed: ${errorText(err)}`);
133
+ }
134
+ finally {
135
+ this.owner.Deregister(this);
136
+ this.resolveEnded(this.endReason ?? 'Stopped');
137
+ }
138
+ }
139
+ }
140
+ }
141
+ /**
142
+ * Plays audio into LiveKit rooms. A process-wide singleton so the playback registry (and the `room_finished` cleanup
143
+ * that reads it) is shared by every caller.
144
+ */
145
+ export class RoomAudioPlayer extends BaseSingleton {
146
+ /** BaseSingleton requires a protected constructor. */
147
+ constructor() {
148
+ super();
149
+ this.active = new Map();
150
+ this.decodeCache = new RoomAudioDecodeCache();
151
+ this.tokenMinter = new LiveKitTokenService();
152
+ this.loadNativeModule = DefaultNativeLoader;
153
+ this.fileReader = ReadMJFileAudio;
154
+ this.urlFetcher = FetchRoomAudioUrl;
155
+ this.clock = SystemPumpClock;
156
+ this.warnedNoSynthesizer = false;
157
+ }
158
+ /** The process-wide player. */
159
+ static get Instance() {
160
+ return super.getInstance();
161
+ }
162
+ /**
163
+ * Installs the text-to-speech used by {@link RoomAudioHandle.Announce} with `{ Text }` — e.g. an
164
+ * `MJRoomSpeechSynthesizer`. None is installed by default (a voice is vendor-specific); without one a text
165
+ * announcement returns `false` and the player logs the omission once.
166
+ *
167
+ * @param synthesizer The synthesizer, or `undefined` to remove it.
168
+ */
169
+ SetSpeechSynthesizer(synthesizer) {
170
+ this.speechSynthesizer = synthesizer;
171
+ this.warnedNoSynthesizer = false;
172
+ }
173
+ /** Overrides where bot tokens come from (tests; a host with its own credentials). */
174
+ SetTokenService(minter) {
175
+ this.tokenMinter = minter;
176
+ }
177
+ /** Overrides the native room-module loader (tests inject a fake module). */
178
+ SetNativeModuleLoader(loader) {
179
+ this.loadNativeModule = loader;
180
+ }
181
+ /** Overrides the native room-module specifier; `undefined` returns to `LIVEKIT_NATIVE_MODULE` / the default. */
182
+ SetNativeModuleSpecifier(specifier) {
183
+ this.nativeModuleSpecifierOverride = specifier;
184
+ }
185
+ /** Overrides how `MJ: Files` bytes are read (tests). */
186
+ SetFileReader(reader) {
187
+ this.fileReader = reader;
188
+ }
189
+ /** Overrides how URLs are fetched (tests). */
190
+ SetUrlFetcher(fetcher) {
191
+ this.urlFetcher = fetcher;
192
+ }
193
+ /** Overrides the pacing clock (tests). */
194
+ SetClock(clock) {
195
+ this.clock = clock;
196
+ }
197
+ /** Forgets every cached decode (e.g. after a hold file is replaced under the same id). */
198
+ ClearDecodeCache() {
199
+ this.decodeCache.Clear();
200
+ }
201
+ /**
202
+ * Starts playing `params.Source` into `params.RoomName`: loads and decodes it (cached by source), joins the room as
203
+ * a publish-only bot, and paces the audio in.
204
+ *
205
+ * @returns The live playback.
206
+ * @throws {Error} when the source cannot be loaded or decoded, LiveKit is not configured, or the bot cannot join.
207
+ */
208
+ async Start(params) {
209
+ assertStartParams(params);
210
+ const pcm = await this.resolvePcm(params);
211
+ return this.joinAndPlay(params, pcm);
212
+ }
213
+ /** The live playbacks in a room (room names compare case-insensitively). */
214
+ GetActive(roomName) {
215
+ const key = roomKey(roomName);
216
+ return [...this.active.values()].filter((playback) => roomKey(playback.RoomName) === key);
217
+ }
218
+ /**
219
+ * Stops every playback in a room. Never throws.
220
+ *
221
+ * @returns How many playbacks were stopped.
222
+ */
223
+ async StopAllInRoom(roomName) {
224
+ const playbacks = this.GetActive(roomName);
225
+ await Promise.all(playbacks.map((playback) => playback.Stop()));
226
+ if (playbacks.length > 0) {
227
+ LogStatus(`[RoomAudioPlayer] stopped ${playbacks.length} playback(s) in room ${roomName}`);
228
+ }
229
+ return playbacks.length;
230
+ }
231
+ // ── Loading ────────────────────────────────────────────────────────────────────────────────────────────────────
232
+ /** Resolves the source to mono PCM16 at {@link ROOM_AUDIO_SAMPLE_RATE}, through the decode cache where it can. */
233
+ async resolvePcm(params) {
234
+ const source = params.Source;
235
+ switch (source.Kind) {
236
+ case 'ComfortTone':
237
+ this.comfortTone ??= GenerateComfortTone(ROOM_AUDIO_SAMPLE_RATE);
238
+ return this.comfortTone;
239
+ case 'Pcm':
240
+ return resampleToRoomRate(validateClipPcm(source));
241
+ case 'File':
242
+ return this.decodeCache.GetOrLoad(`file:${NormalizeUUID(source.FileID)}`, async () => this.decodeContent(await this.fileReader(source.FileID, params.ContextUser, params.Provider)));
243
+ case 'Url': {
244
+ const url = AssertHttpsAudioUrl(source.Url).href; // refuse plain http before anything is fetched
245
+ return this.decodeCache.GetOrLoad(`url:${url}`, async () => this.decodeContent(await this.urlFetcher(url)));
246
+ }
247
+ default:
248
+ return assertNeverSource(source);
249
+ }
250
+ }
251
+ async decodeContent(content) {
252
+ const decoded = await DecodeAudio(content.Bytes, { MimeType: content.MimeType, FileName: content.FileName });
253
+ return resampleToRoomRate(decoded);
254
+ }
255
+ // ── Joining ────────────────────────────────────────────────────────────────────────────────────────────────────
256
+ /** Joins the room as the playback's bot, registers the playback, and starts the pump. */
257
+ async joinAndPlay(params, pcm) {
258
+ const playbackID = randomUUID();
259
+ const displayName = params.DisplayName ?? 'On hold';
260
+ const token = await this.mintBotToken(params.RoomName, params.Identity ?? `hold-${playbackID}`, displayName);
261
+ const roomModule = await this.loadNativeModule(ResolveLiveKitNativeModuleSpecifier(this.nativeModuleSpecifierOverride));
262
+ const client = roomModule.createRoomClient({ OutboundSampleRate: ROOM_AUDIO_SAMPLE_RATE, InboundSampleRate: ROOM_AUDIO_SAMPLE_RATE });
263
+ const playback = new RoomAudioPlayback(playbackID, params.RoomName, client, params.ContextUser, this.playbackOwner());
264
+ client.onDisconnected(() => this.handleServerDisconnect(playback));
265
+ await this.connectClient(client, token, displayName);
266
+ if (playback.IsStopped) {
267
+ throw new Error(`Room audio bot was disconnected from room ${params.RoomName} as soon as it joined.`);
268
+ }
269
+ this.active.set(playbackID, playback);
270
+ playback.Play(this.createPump(playback, client, pcm, params.Loop ?? true));
271
+ LogStatus(`[RoomAudioPlayer] playing ${describeSource(params.Source)} into room ${params.RoomName} (playback ${playbackID})`);
272
+ return playback;
273
+ }
274
+ /** A publish-only bot token: it never needs to hear the room, and it carries no data channel. */
275
+ mintBotToken(roomName, identity, displayName) {
276
+ return this.tokenMinter.MintToken({
277
+ RoomName: roomName,
278
+ Identity: identity,
279
+ DisplayName: displayName,
280
+ Role: 'agent',
281
+ CanSubscribe: false,
282
+ CanPublishData: false,
283
+ Metadata: { mjRoomAudio: true },
284
+ });
285
+ }
286
+ /** Connects the bot; on failure always releases the client before rethrowing. */
287
+ async connectClient(client, token, displayName) {
288
+ try {
289
+ await client.connect({ url: token.ServerUrl, token: token.Token, name: displayName });
290
+ }
291
+ catch (err) {
292
+ try {
293
+ await client.disconnect();
294
+ }
295
+ catch (disconnectErr) {
296
+ LogError(`[RoomAudioPlayer] releasing a client that failed to join room ${token.RoomName} also failed: ${errorText(disconnectErr)}`);
297
+ }
298
+ throw new Error(`Room audio could not join room ${token.RoomName}: ${errorText(err)}`);
299
+ }
300
+ }
301
+ createPump(playback, client, pcm, loop) {
302
+ return new PcmFramePump({
303
+ Music: pcm,
304
+ SampleRate: ROOM_AUDIO_SAMPLE_RATE,
305
+ Loop: loop,
306
+ Clock: this.clock,
307
+ Sink: (frame) => client.publishAudio(frame.buffer),
308
+ OnEnded: (reason) => {
309
+ // 'Stopped' means the playback stopped the pump itself; otherwise the audio ran out (no loop) or the sink
310
+ // broke, and the bot leaves the room with that reason.
311
+ if (reason === 'Completed') {
312
+ playback.CompleteAfterDrain(this.clock);
313
+ }
314
+ else if (reason === 'SinkError') {
315
+ void playback.EndWith('Failed');
316
+ }
317
+ },
318
+ });
319
+ }
320
+ // ── Registry + speech ──────────────────────────────────────────────────────────────────────────────────────────
321
+ playbackOwner() {
322
+ return {
323
+ Synthesize: (text, contextUser) => this.synthesize(text, contextUser),
324
+ Deregister: (playback) => this.deregister(playback),
325
+ };
326
+ }
327
+ deregister(playback) {
328
+ if (this.active.get(playback.PlaybackID) === playback) {
329
+ this.active.delete(playback.PlaybackID);
330
+ }
331
+ }
332
+ /** The server dropped the bot (room closed, participant removed): stop and forget the playback. */
333
+ handleServerDisconnect(playback) {
334
+ if (!playback.IsStopped) {
335
+ LogStatus(`[RoomAudioPlayer] playback ${playback.PlaybackID} was disconnected from room ${playback.RoomName}; stopping it`);
336
+ void playback.EndWith('Disconnected');
337
+ }
338
+ }
339
+ async synthesize(text, contextUser) {
340
+ if (!this.speechSynthesizer) {
341
+ if (!this.warnedNoSynthesizer) {
342
+ this.warnedNoSynthesizer = true;
343
+ LogError('[RoomAudioPlayer] a text announcement was requested but no speech synthesizer is installed; it was skipped. ' +
344
+ 'Call RoomAudioPlayer.Instance.SetSpeechSynthesizer(new MJRoomSpeechSynthesizer({ Voice: ... })) at startup.');
345
+ }
346
+ return null;
347
+ }
348
+ try {
349
+ return await this.speechSynthesizer.Synthesize(text, contextUser);
350
+ }
351
+ catch (err) {
352
+ LogError(`[RoomAudioPlayer] the speech synthesizer failed: ${errorText(err)}`);
353
+ return null;
354
+ }
355
+ }
356
+ }
357
+ // ── Helpers ──────────────────────────────────────────────────────────────────────────────────────────────────────
358
+ function assertStartParams(params) {
359
+ if (!params.RoomName?.trim()) {
360
+ throw new Error('RoomAudioPlayer.Start needs a RoomName.');
361
+ }
362
+ if (!params.ContextUser) {
363
+ throw new Error('RoomAudioPlayer.Start needs a ContextUser (the user the audio is loaded as).');
364
+ }
365
+ if (!params.Source) {
366
+ throw new Error('RoomAudioPlayer.Start needs a Source.');
367
+ }
368
+ }
369
+ /** Checks caller-supplied PCM: non-empty, a positive rate, and within the duration cap. */
370
+ function validateClipPcm(clip) {
371
+ if (!(clip.SampleRate > 0)) {
372
+ throw new Error(`Room audio PCM needs a positive SampleRate; got ${clip.SampleRate}.`);
373
+ }
374
+ if (clip.Pcm.length === 0) {
375
+ throw new Error('Room audio PCM is empty.');
376
+ }
377
+ AssertAudioDurationWithinCap(clip.Pcm.length, clip.SampleRate);
378
+ return { Pcm: clip.Pcm, SampleRate: clip.SampleRate };
379
+ }
380
+ function resampleToRoomRate(audio) {
381
+ return ResamplePcm16(audio.Pcm, audio.SampleRate, ROOM_AUDIO_SAMPLE_RATE);
382
+ }
383
+ function describeSource(source) {
384
+ switch (source.Kind) {
385
+ case 'File':
386
+ return `file ${source.FileID}`;
387
+ case 'Url':
388
+ return `url ${source.Url}`;
389
+ case 'Pcm':
390
+ return 'caller-supplied PCM';
391
+ default:
392
+ return 'the comfort tone';
393
+ }
394
+ }
395
+ function assertNeverSource(source) {
396
+ throw new Error(`Unknown room audio source kind: ${JSON.stringify(source)}`);
397
+ }
398
+ function roomKey(roomName) {
399
+ return roomName.trim().toLowerCase();
400
+ }
401
+ function errorText(err) {
402
+ return err instanceof Error ? err.message : String(err);
403
+ }
404
+ //# sourceMappingURL=room-audio-player.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"room-audio-player.js","sourceRoot":"","sources":["../../src/room-audio/room-audio-player.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @fileoverview Where the room audio player gets an audio file's bytes: an `MJ: Files` row (read from its storage
3
+ * provider through `@memberjunction/storage`), or an https URL (fetched through the SSRF-safe `SafeFetch`, with a
4
+ * timeout and a byte cap). Both return {@link RoomAudioFileContent}, which `DecodeAudio` turns into PCM.
5
+ *
6
+ * @module @memberjunction/livekit-room-server
7
+ */
8
+ import { type IMetadataProvider, type UserInfo } from '@memberjunction/core';
9
+ /** An audio file's bytes plus what is known about its format. */
10
+ export interface RoomAudioFileContent {
11
+ /** The file's bytes. */
12
+ Bytes: Uint8Array;
13
+ /** The MIME type, when known. */
14
+ MimeType?: string;
15
+ /** The file name, when known. */
16
+ FileName?: string;
17
+ }
18
+ /** Reads an `MJ: Files` row's bytes. The default is {@link ReadMJFileAudio}; tests inject a fake. */
19
+ export type RoomAudioFileReader = (fileID: string, contextUser: UserInfo, provider?: IMetadataProvider) => Promise<RoomAudioFileContent>;
20
+ /** Fetches an https URL's bytes. The default is {@link FetchRoomAudioUrl}; tests inject a fake. */
21
+ export type RoomAudioUrlFetcher = (url: string) => Promise<RoomAudioFileContent>;
22
+ /** The largest file the player will download from a URL, in bytes (25 MB). */
23
+ export declare const MAX_ROOM_AUDIO_URL_BYTES: number;
24
+ /** How long a URL download may take before it is abandoned, in ms. */
25
+ export declare const ROOM_AUDIO_URL_TIMEOUT_MS = 20000;
26
+ /**
27
+ * Reads an `MJ: Files` row's bytes from its storage provider, as the given user (so the user's read permission on
28
+ * the file applies).
29
+ *
30
+ * @throws {Error} when the row cannot be loaded, no storage account serves its provider, or the download fails.
31
+ */
32
+ export declare function ReadMJFileAudio(fileID: string, contextUser: UserInfo, provider?: IMetadataProvider): Promise<RoomAudioFileContent>;
33
+ /**
34
+ * Checks that `url` is an absolute https URL. The player refuses plain http: hold audio is fetched by the server, and
35
+ * a cleartext fetch would let anyone on the path substitute what callers hear.
36
+ *
37
+ * @throws {Error} when the URL is malformed or not https.
38
+ */
39
+ export declare function AssertHttpsAudioUrl(url: string): URL;
40
+ /**
41
+ * Downloads an https audio URL through `SafeFetch` (every redirect hop re-checked against private/reserved
42
+ * addresses), abandoning it after {@link ROOM_AUDIO_URL_TIMEOUT_MS} or once it passes {@link MAX_ROOM_AUDIO_URL_BYTES}.
43
+ *
44
+ * @throws {Error} on a non-https URL, an HTTP error status, a timeout, or a body over the byte cap.
45
+ */
46
+ export declare function FetchRoomAudioUrl(url: string): Promise<RoomAudioFileContent>;
47
+ //# sourceMappingURL=room-audio-sources.d.ts.map