@memberjunction/livekit-room-server 6.2.0-edge.2 → 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.
- package/README.md +85 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/livekit-agent-room-coordinator.d.ts +105 -8
- package/dist/livekit-agent-room-coordinator.d.ts.map +1 -1
- package/dist/livekit-agent-room-coordinator.js +125 -25
- package/dist/livekit-agent-room-coordinator.js.map +1 -1
- package/dist/livekit-egress-service.d.ts +7 -0
- package/dist/livekit-egress-service.d.ts.map +1 -1
- package/dist/livekit-egress-service.js +11 -0
- package/dist/livekit-egress-service.js.map +1 -1
- package/dist/livekit-native-module.d.ts +18 -0
- package/dist/livekit-native-module.d.ts.map +1 -0
- package/dist/livekit-native-module.js +20 -0
- package/dist/livekit-native-module.js.map +1 -0
- package/dist/livekit-sip-service.d.ts +141 -0
- package/dist/livekit-sip-service.d.ts.map +1 -0
- package/dist/livekit-sip-service.js +169 -0
- package/dist/livekit-sip-service.js.map +1 -0
- package/dist/livekit-token-service.d.ts +6 -0
- package/dist/livekit-token-service.d.ts.map +1 -1
- package/dist/livekit-token-service.js +8 -0
- package/dist/livekit-token-service.js.map +1 -1
- package/dist/livekit-webhook.d.ts +65 -0
- package/dist/livekit-webhook.d.ts.map +1 -0
- package/dist/livekit-webhook.js +65 -0
- package/dist/livekit-webhook.js.map +1 -0
- package/dist/meeting-dial-in-service.d.ts +75 -0
- package/dist/meeting-dial-in-service.d.ts.map +1 -0
- package/dist/meeting-dial-in-service.js +175 -0
- package/dist/meeting-dial-in-service.js.map +1 -0
- package/dist/room-audio/audio-decoder.d.ts +40 -0
- package/dist/room-audio/audio-decoder.d.ts.map +1 -0
- package/dist/room-audio/audio-decoder.js +205 -0
- package/dist/room-audio/audio-decoder.js.map +1 -0
- package/dist/room-audio/comfort-tone.d.ts +23 -0
- package/dist/room-audio/comfort-tone.d.ts.map +1 -0
- package/dist/room-audio/comfort-tone.js +70 -0
- package/dist/room-audio/comfort-tone.js.map +1 -0
- package/dist/room-audio/pcm-frame-pump.d.ts +134 -0
- package/dist/room-audio/pcm-frame-pump.d.ts.map +1 -0
- package/dist/room-audio/pcm-frame-pump.js +253 -0
- package/dist/room-audio/pcm-frame-pump.js.map +1 -0
- package/dist/room-audio/room-audio-decode-cache.d.ts +37 -0
- package/dist/room-audio/room-audio-decode-cache.d.ts.map +1 -0
- package/dist/room-audio/room-audio-decode-cache.js +75 -0
- package/dist/room-audio/room-audio-decode-cache.js.map +1 -0
- package/dist/room-audio/room-audio-player.d.ts +178 -0
- package/dist/room-audio/room-audio-player.d.ts.map +1 -0
- package/dist/room-audio/room-audio-player.js +404 -0
- package/dist/room-audio/room-audio-player.js.map +1 -0
- package/dist/room-audio/room-audio-sources.d.ts +47 -0
- package/dist/room-audio/room-audio-sources.d.ts.map +1 -0
- package/dist/room-audio/room-audio-sources.js +116 -0
- package/dist/room-audio/room-audio-sources.js.map +1 -0
- package/dist/room-audio/room-speech-synthesizer.d.ts +55 -0
- package/dist/room-audio/room-speech-synthesizer.d.ts.map +1 -0
- package/dist/room-audio/room-speech-synthesizer.js +56 -0
- package/dist/room-audio/room-speech-synthesizer.js.map +1 -0
- package/dist/room-authorization.d.ts +58 -0
- package/dist/room-authorization.d.ts.map +1 -0
- package/dist/room-authorization.js +212 -0
- package/dist/room-authorization.js.map +1 -0
- package/dist/room-handoff/handoff-offer-registry.d.ts +137 -0
- package/dist/room-handoff/handoff-offer-registry.d.ts.map +1 -0
- package/dist/room-handoff/handoff-offer-registry.js +521 -0
- package/dist/room-handoff/handoff-offer-registry.js.map +1 -0
- package/dist/room-handoff/handoff-types.d.ts +170 -0
- package/dist/room-handoff/handoff-types.d.ts.map +1 -0
- package/dist/room-handoff/handoff-types.js +16 -0
- package/dist/room-handoff/handoff-types.js.map +1 -0
- package/dist/room-handoff/room-handoff-engine.d.ts +141 -0
- package/dist/room-handoff/room-handoff-engine.d.ts.map +1 -0
- package/dist/room-handoff/room-handoff-engine.js +530 -0
- package/dist/room-handoff/room-handoff-engine.js.map +1 -0
- package/package.json +14 -9
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview {@link PcmFramePump} — plays a PCM16 buffer out to a sink in real time, one 20 ms frame at a time.
|
|
3
|
+
*
|
|
4
|
+
* The native LiveKit wrapper's `publishAudio` queues whatever it is given and drains it as fast as the WebRTC audio
|
|
5
|
+
* source accepts it; it does not pace. Handing it a whole song would buffer minutes of audio in memory and make pause,
|
|
6
|
+
* stop and announcements lag by that much. So the pump paces itself: a fast ticker compares monotonic elapsed time
|
|
7
|
+
* with frames sent and emits only what is due, plus a small lead (~150 ms) that absorbs timer jitter. The lead is all
|
|
8
|
+
* that is ever outstanding downstream.
|
|
9
|
+
*
|
|
10
|
+
* - **Drift correction** — frames due are computed from the elapsed time since the timeline started, never by
|
|
11
|
+
* counting ticks, so a late tick sends two frames and the average rate stays exact.
|
|
12
|
+
* - **Bounded catch-up** — after a long stall (a blocked event loop) the pump does not burst the backlog; it drops it
|
|
13
|
+
* and restarts the timeline, so the downstream queue can never grow past the lead plus a small margin.
|
|
14
|
+
* - **Loop**, **Pause/Resume** (resume restarts the timeline — no burst), and **InsertClip**: the music fades down to a
|
|
15
|
+
* duck level, the clip plays over it, and the music fades back up.
|
|
16
|
+
*
|
|
17
|
+
* The clock and ticker are injectable so tests drive time by hand.
|
|
18
|
+
*
|
|
19
|
+
* @module @memberjunction/livekit-room-server
|
|
20
|
+
*/
|
|
21
|
+
/** A running ticker the pump can cancel. */
|
|
22
|
+
export interface PumpTicker {
|
|
23
|
+
/** Stops the ticker. Idempotent. */
|
|
24
|
+
Cancel(): void;
|
|
25
|
+
}
|
|
26
|
+
/** The pump's source of time and ticks — `performance.now()` + `setInterval` in production, a fake in tests. */
|
|
27
|
+
export interface PumpClock {
|
|
28
|
+
/** Monotonic milliseconds. */
|
|
29
|
+
Now(): number;
|
|
30
|
+
/** Calls `callback` every `intervalMs` until cancelled. */
|
|
31
|
+
StartTicker(callback: () => void, intervalMs: number): PumpTicker;
|
|
32
|
+
}
|
|
33
|
+
/** Why a pump stopped: its (non-looping) audio ran out, it was stopped, or the sink threw. */
|
|
34
|
+
export type PumpEndReason = 'Completed' | 'Stopped' | 'SinkError';
|
|
35
|
+
/** Construction options for {@link PcmFramePump}. */
|
|
36
|
+
export interface PcmFramePumpOptions {
|
|
37
|
+
/** Mono PCM16 to play, at {@link SampleRate}. Must not be empty. */
|
|
38
|
+
Music: Int16Array;
|
|
39
|
+
/** The rate of {@link Music}, of inserted clips, and of the frames handed to {@link Sink}. */
|
|
40
|
+
SampleRate: number;
|
|
41
|
+
/** Receives each frame. Every frame is a freshly allocated array the sink may keep. */
|
|
42
|
+
Sink: (frame: Int16Array<ArrayBuffer>) => void;
|
|
43
|
+
/** Loop {@link Music} forever. Default `true`. */
|
|
44
|
+
Loop?: boolean;
|
|
45
|
+
/** Frame duration in ms. Default 20. */
|
|
46
|
+
FrameMs?: number;
|
|
47
|
+
/** How far ahead of real time the pump keeps the sink, in ms. Default 150. */
|
|
48
|
+
LeadMs?: number;
|
|
49
|
+
/** How long the music takes to fade down for a clip (and back up after), in ms. Default 150. */
|
|
50
|
+
DuckMs?: number;
|
|
51
|
+
/** The music's level while a clip plays, 0–1. Default 0.15. */
|
|
52
|
+
DuckLevel?: number;
|
|
53
|
+
/** Time source. Default: {@link SystemPumpClock}. */
|
|
54
|
+
Clock?: PumpClock;
|
|
55
|
+
/** Called once when the pump stops, with the reason. */
|
|
56
|
+
OnEnded?: (reason: PumpEndReason) => void;
|
|
57
|
+
}
|
|
58
|
+
/** The production clock: `performance.now()` and an unref'd `setInterval` (a playing pump never holds the process open). */
|
|
59
|
+
export declare const SystemPumpClock: PumpClock;
|
|
60
|
+
/** Paces mono PCM16 out to a sink in real time. See the file overview. */
|
|
61
|
+
export declare class PcmFramePump {
|
|
62
|
+
private readonly music;
|
|
63
|
+
private readonly sampleRate;
|
|
64
|
+
private readonly sink;
|
|
65
|
+
private readonly loop;
|
|
66
|
+
private readonly frameMs;
|
|
67
|
+
private readonly samplesPerFrame;
|
|
68
|
+
private readonly leadFrames;
|
|
69
|
+
private readonly maxBurstFrames;
|
|
70
|
+
private readonly duckLevel;
|
|
71
|
+
private readonly gainStep;
|
|
72
|
+
private readonly clock;
|
|
73
|
+
private readonly onEnded?;
|
|
74
|
+
private ticker;
|
|
75
|
+
private running;
|
|
76
|
+
private paused;
|
|
77
|
+
private ended;
|
|
78
|
+
private timelineStart;
|
|
79
|
+
private framesOnTimeline;
|
|
80
|
+
private framesSent;
|
|
81
|
+
private musicPosition;
|
|
82
|
+
private musicDone;
|
|
83
|
+
private gain;
|
|
84
|
+
private readonly clipQueue;
|
|
85
|
+
private currentClip;
|
|
86
|
+
private clipPosition;
|
|
87
|
+
/** @throws {Error} when the music is empty or a timing option is not positive. */
|
|
88
|
+
constructor(options: PcmFramePumpOptions);
|
|
89
|
+
/** True between {@link Start} and the pump ending (including while paused). */
|
|
90
|
+
get IsRunning(): boolean;
|
|
91
|
+
/** True while paused. */
|
|
92
|
+
get IsPaused(): boolean;
|
|
93
|
+
/** Frames handed to the sink so far. */
|
|
94
|
+
get FramesSent(): number;
|
|
95
|
+
/** Samples per frame at the pump's rate. */
|
|
96
|
+
get SamplesPerFrame(): number;
|
|
97
|
+
/** Starts playback: sends the lead immediately, then paces in real time. A second call is a no-op. */
|
|
98
|
+
Start(): void;
|
|
99
|
+
/** Stops sending frames until {@link Resume}. Playback position, ducking and queued clips are kept. */
|
|
100
|
+
Pause(): void;
|
|
101
|
+
/** Continues after {@link Pause} from where it left off, on a fresh timeline (so nothing bursts). */
|
|
102
|
+
Resume(): void;
|
|
103
|
+
/**
|
|
104
|
+
* How much already-sent audio is still ahead of real time, in ms: the pump keeps the sink up to `LeadMs` ahead, so
|
|
105
|
+
* after the last frame is sent the room is still playing this much. Zero once real time has caught up.
|
|
106
|
+
*/
|
|
107
|
+
get QueuedAheadMs(): number;
|
|
108
|
+
/**
|
|
109
|
+
* Plays `pcm` over the music: the music fades down to the duck level, the clip plays, the music fades back up.
|
|
110
|
+
* Clips queue behind one another.
|
|
111
|
+
*
|
|
112
|
+
* @param pcm Mono PCM16 at the pump's sample rate.
|
|
113
|
+
* @returns Resolves `true` once the clip has been sent in full, `false` if the pump stopped first or the clip is empty.
|
|
114
|
+
*/
|
|
115
|
+
InsertClip(pcm: Int16Array): Promise<boolean>;
|
|
116
|
+
/** Stops the pump for good. Idempotent; pending clips resolve `false`. */
|
|
117
|
+
Stop(): void;
|
|
118
|
+
private restartTimeline;
|
|
119
|
+
private tick;
|
|
120
|
+
/** Frames owed to the sink now, given elapsed time and the lead. Drops the backlog after a long stall. */
|
|
121
|
+
private framesDue;
|
|
122
|
+
private emitFrame;
|
|
123
|
+
/** The next output sample in [-1, 1]: ducked music plus whatever clip is playing. */
|
|
124
|
+
private nextSample;
|
|
125
|
+
/** Moves the music gain one step toward its target: the duck level while a clip is queued or playing, else full. */
|
|
126
|
+
private advanceGain;
|
|
127
|
+
private startQueuedClipIfDucked;
|
|
128
|
+
private nextMusicSample;
|
|
129
|
+
private nextClipSample;
|
|
130
|
+
/** Ends the pump once: cancels the ticker, fails any pending clips, reports the reason. */
|
|
131
|
+
private finish;
|
|
132
|
+
private failPendingClips;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=pcm-frame-pump.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pcm-frame-pump.d.ts","sourceRoot":"","sources":["../../src/room-audio/pcm-frame-pump.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview {@link PcmFramePump} — plays a PCM16 buffer out to a sink in real time, one 20 ms frame at a time.
|
|
3
|
+
*
|
|
4
|
+
* The native LiveKit wrapper's `publishAudio` queues whatever it is given and drains it as fast as the WebRTC audio
|
|
5
|
+
* source accepts it; it does not pace. Handing it a whole song would buffer minutes of audio in memory and make pause,
|
|
6
|
+
* stop and announcements lag by that much. So the pump paces itself: a fast ticker compares monotonic elapsed time
|
|
7
|
+
* with frames sent and emits only what is due, plus a small lead (~150 ms) that absorbs timer jitter. The lead is all
|
|
8
|
+
* that is ever outstanding downstream.
|
|
9
|
+
*
|
|
10
|
+
* - **Drift correction** — frames due are computed from the elapsed time since the timeline started, never by
|
|
11
|
+
* counting ticks, so a late tick sends two frames and the average rate stays exact.
|
|
12
|
+
* - **Bounded catch-up** — after a long stall (a blocked event loop) the pump does not burst the backlog; it drops it
|
|
13
|
+
* and restarts the timeline, so the downstream queue can never grow past the lead plus a small margin.
|
|
14
|
+
* - **Loop**, **Pause/Resume** (resume restarts the timeline — no burst), and **InsertClip**: the music fades down to a
|
|
15
|
+
* duck level, the clip plays over it, and the music fades back up.
|
|
16
|
+
*
|
|
17
|
+
* The clock and ticker are injectable so tests drive time by hand.
|
|
18
|
+
*
|
|
19
|
+
* @module @memberjunction/livekit-room-server
|
|
20
|
+
*/
|
|
21
|
+
import { LogError } from '@memberjunction/core';
|
|
22
|
+
import { FloatToPcm16 } from './audio-decoder.js';
|
|
23
|
+
/** How often the ticker fires, in ms. Shorter than a frame so lateness never exceeds about half a frame. */
|
|
24
|
+
const TICK_MS = 10;
|
|
25
|
+
/** How far behind real time the pump will catch up before it drops the backlog instead, in ms. */
|
|
26
|
+
const MAX_CATCH_UP_MS = 200;
|
|
27
|
+
/** The production clock: `performance.now()` and an unref'd `setInterval` (a playing pump never holds the process open). */
|
|
28
|
+
export const SystemPumpClock = {
|
|
29
|
+
Now: () => performance.now(),
|
|
30
|
+
StartTicker: (callback, intervalMs) => {
|
|
31
|
+
const handle = setInterval(callback, intervalMs);
|
|
32
|
+
handle.unref?.();
|
|
33
|
+
return { Cancel: () => clearInterval(handle) };
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
/** Paces mono PCM16 out to a sink in real time. See the file overview. */
|
|
37
|
+
export class PcmFramePump {
|
|
38
|
+
/** @throws {Error} when the music is empty or a timing option is not positive. */
|
|
39
|
+
constructor(options) {
|
|
40
|
+
this.ticker = null;
|
|
41
|
+
this.running = false;
|
|
42
|
+
this.paused = false;
|
|
43
|
+
this.ended = false;
|
|
44
|
+
this.timelineStart = 0;
|
|
45
|
+
this.framesOnTimeline = 0;
|
|
46
|
+
this.framesSent = 0;
|
|
47
|
+
this.musicPosition = 0;
|
|
48
|
+
this.musicDone = false;
|
|
49
|
+
this.gain = 1;
|
|
50
|
+
this.clipQueue = [];
|
|
51
|
+
this.currentClip = null;
|
|
52
|
+
this.clipPosition = 0;
|
|
53
|
+
if (options.Music.length === 0)
|
|
54
|
+
throw new Error('PcmFramePump needs non-empty audio to play.');
|
|
55
|
+
if (!(options.SampleRate > 0))
|
|
56
|
+
throw new Error(`PcmFramePump needs a positive sample rate; got ${options.SampleRate}.`);
|
|
57
|
+
this.music = options.Music;
|
|
58
|
+
this.sampleRate = options.SampleRate;
|
|
59
|
+
this.sink = options.Sink;
|
|
60
|
+
this.loop = options.Loop ?? true;
|
|
61
|
+
this.frameMs = options.FrameMs ?? 20;
|
|
62
|
+
this.samplesPerFrame = Math.max(1, Math.round((this.sampleRate * this.frameMs) / 1000));
|
|
63
|
+
this.leadFrames = Math.max(1, Math.ceil((options.LeadMs ?? 150) / this.frameMs));
|
|
64
|
+
this.maxBurstFrames = this.leadFrames + Math.ceil(MAX_CATCH_UP_MS / this.frameMs);
|
|
65
|
+
this.duckLevel = Math.min(1, Math.max(0, options.DuckLevel ?? 0.15));
|
|
66
|
+
const duckSamples = Math.max(1, Math.round(((options.DuckMs ?? 150) * this.sampleRate) / 1000));
|
|
67
|
+
this.gainStep = (1 - this.duckLevel) / duckSamples;
|
|
68
|
+
this.clock = options.Clock ?? SystemPumpClock;
|
|
69
|
+
this.onEnded = options.OnEnded;
|
|
70
|
+
}
|
|
71
|
+
/** True between {@link Start} and the pump ending (including while paused). */
|
|
72
|
+
get IsRunning() {
|
|
73
|
+
return this.running;
|
|
74
|
+
}
|
|
75
|
+
/** True while paused. */
|
|
76
|
+
get IsPaused() {
|
|
77
|
+
return this.paused;
|
|
78
|
+
}
|
|
79
|
+
/** Frames handed to the sink so far. */
|
|
80
|
+
get FramesSent() {
|
|
81
|
+
return this.framesSent;
|
|
82
|
+
}
|
|
83
|
+
/** Samples per frame at the pump's rate. */
|
|
84
|
+
get SamplesPerFrame() {
|
|
85
|
+
return this.samplesPerFrame;
|
|
86
|
+
}
|
|
87
|
+
/** Starts playback: sends the lead immediately, then paces in real time. A second call is a no-op. */
|
|
88
|
+
Start() {
|
|
89
|
+
if (this.running || this.ended) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.running = true;
|
|
93
|
+
this.restartTimeline();
|
|
94
|
+
this.ticker = this.clock.StartTicker(() => this.tick(), TICK_MS);
|
|
95
|
+
this.tick();
|
|
96
|
+
}
|
|
97
|
+
/** Stops sending frames until {@link Resume}. Playback position, ducking and queued clips are kept. */
|
|
98
|
+
Pause() {
|
|
99
|
+
if (this.running) {
|
|
100
|
+
this.paused = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Continues after {@link Pause} from where it left off, on a fresh timeline (so nothing bursts). */
|
|
104
|
+
Resume() {
|
|
105
|
+
if (!this.running || !this.paused) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
this.paused = false;
|
|
109
|
+
this.restartTimeline();
|
|
110
|
+
this.tick();
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* How much already-sent audio is still ahead of real time, in ms: the pump keeps the sink up to `LeadMs` ahead, so
|
|
114
|
+
* after the last frame is sent the room is still playing this much. Zero once real time has caught up.
|
|
115
|
+
*/
|
|
116
|
+
get QueuedAheadMs() {
|
|
117
|
+
const sentUntil = this.timelineStart + this.framesOnTimeline * this.frameMs;
|
|
118
|
+
return Math.max(0, sentUntil - this.clock.Now());
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Plays `pcm` over the music: the music fades down to the duck level, the clip plays, the music fades back up.
|
|
122
|
+
* Clips queue behind one another.
|
|
123
|
+
*
|
|
124
|
+
* @param pcm Mono PCM16 at the pump's sample rate.
|
|
125
|
+
* @returns Resolves `true` once the clip has been sent in full, `false` if the pump stopped first or the clip is empty.
|
|
126
|
+
*/
|
|
127
|
+
InsertClip(pcm) {
|
|
128
|
+
if (!this.running || pcm.length === 0) {
|
|
129
|
+
return Promise.resolve(false);
|
|
130
|
+
}
|
|
131
|
+
return new Promise((resolve) => this.clipQueue.push({ Pcm: pcm, Done: resolve }));
|
|
132
|
+
}
|
|
133
|
+
/** Stops the pump for good. Idempotent; pending clips resolve `false`. */
|
|
134
|
+
Stop() {
|
|
135
|
+
this.finish('Stopped');
|
|
136
|
+
}
|
|
137
|
+
restartTimeline() {
|
|
138
|
+
this.timelineStart = this.clock.Now();
|
|
139
|
+
this.framesOnTimeline = 0;
|
|
140
|
+
}
|
|
141
|
+
tick() {
|
|
142
|
+
if (!this.running || this.paused) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const due = this.framesDue();
|
|
147
|
+
for (let i = 0; i < due && this.running && !this.paused; i++) {
|
|
148
|
+
this.emitFrame();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
LogError(`[PcmFramePump] the sink failed; stopping playback: ${err instanceof Error ? err.message : String(err)}`);
|
|
153
|
+
this.finish('SinkError');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** Frames owed to the sink now, given elapsed time and the lead. Drops the backlog after a long stall. */
|
|
157
|
+
framesDue() {
|
|
158
|
+
const elapsed = this.clock.Now() - this.timelineStart;
|
|
159
|
+
const target = Math.floor(elapsed / this.frameMs) + this.leadFrames;
|
|
160
|
+
const due = target - this.framesOnTimeline;
|
|
161
|
+
if (due > this.maxBurstFrames) {
|
|
162
|
+
this.restartTimeline(); // stalled too long: skip the missed audio rather than flood the sink
|
|
163
|
+
return this.leadFrames;
|
|
164
|
+
}
|
|
165
|
+
return Math.max(0, due);
|
|
166
|
+
}
|
|
167
|
+
emitFrame() {
|
|
168
|
+
const frame = new Int16Array(this.samplesPerFrame);
|
|
169
|
+
for (let i = 0; i < frame.length; i++) {
|
|
170
|
+
frame[i] = FloatToPcm16(this.nextSample());
|
|
171
|
+
}
|
|
172
|
+
this.framesOnTimeline++;
|
|
173
|
+
this.framesSent++;
|
|
174
|
+
this.sink(frame);
|
|
175
|
+
if (this.musicDone && !this.currentClip && this.clipQueue.length === 0) {
|
|
176
|
+
this.finish('Completed');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** The next output sample in [-1, 1]: ducked music plus whatever clip is playing. */
|
|
180
|
+
nextSample() {
|
|
181
|
+
this.advanceGain();
|
|
182
|
+
this.startQueuedClipIfDucked();
|
|
183
|
+
return this.nextMusicSample() * this.gain + this.nextClipSample();
|
|
184
|
+
}
|
|
185
|
+
/** Moves the music gain one step toward its target: the duck level while a clip is queued or playing, else full. */
|
|
186
|
+
advanceGain() {
|
|
187
|
+
const ducking = this.currentClip !== null || this.clipQueue.length > 0;
|
|
188
|
+
const target = ducking ? this.duckLevel : 1;
|
|
189
|
+
if (this.gain > target) {
|
|
190
|
+
this.gain = Math.max(target, this.gain - this.gainStep);
|
|
191
|
+
}
|
|
192
|
+
else if (this.gain < target) {
|
|
193
|
+
this.gain = Math.min(target, this.gain + this.gainStep);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
startQueuedClipIfDucked() {
|
|
197
|
+
if (!this.currentClip && this.clipQueue.length > 0 && this.gain <= this.duckLevel) {
|
|
198
|
+
this.currentClip = this.clipQueue.shift() ?? null;
|
|
199
|
+
this.clipPosition = 0;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
nextMusicSample() {
|
|
203
|
+
if (this.musicDone) {
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
const sample = this.music[this.musicPosition] / 32768;
|
|
207
|
+
this.musicPosition++;
|
|
208
|
+
if (this.musicPosition >= this.music.length) {
|
|
209
|
+
this.musicPosition = 0;
|
|
210
|
+
this.musicDone = !this.loop;
|
|
211
|
+
}
|
|
212
|
+
return sample;
|
|
213
|
+
}
|
|
214
|
+
nextClipSample() {
|
|
215
|
+
const clip = this.currentClip;
|
|
216
|
+
if (!clip) {
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
const sample = clip.Pcm[this.clipPosition] / 32768;
|
|
220
|
+
this.clipPosition++;
|
|
221
|
+
if (this.clipPosition >= clip.Pcm.length) {
|
|
222
|
+
this.currentClip = null;
|
|
223
|
+
clip.Done(true);
|
|
224
|
+
}
|
|
225
|
+
return sample;
|
|
226
|
+
}
|
|
227
|
+
/** Ends the pump once: cancels the ticker, fails any pending clips, reports the reason. */
|
|
228
|
+
finish(reason) {
|
|
229
|
+
if (this.ended) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
this.ended = true;
|
|
233
|
+
this.running = false;
|
|
234
|
+
this.paused = false;
|
|
235
|
+
try {
|
|
236
|
+
this.ticker?.Cancel();
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
this.ticker = null;
|
|
240
|
+
this.failPendingClips();
|
|
241
|
+
this.onEnded?.(reason);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
failPendingClips() {
|
|
245
|
+
const pending = this.currentClip ? [this.currentClip, ...this.clipQueue] : [...this.clipQueue];
|
|
246
|
+
this.currentClip = null;
|
|
247
|
+
this.clipQueue.length = 0;
|
|
248
|
+
for (const clip of pending) {
|
|
249
|
+
clip.Done(false);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=pcm-frame-pump.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pcm-frame-pump.js","sourceRoot":"","sources":["../../src/room-audio/pcm-frame-pump.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview {@link RoomAudioDecodeCache} — a small LRU of ready-to-play PCM keyed by source (an `MJ: Files` id or
|
|
3
|
+
* a URL), so every caller placed on hold in the same queue does not re-download and re-decode the same file.
|
|
4
|
+
*
|
|
5
|
+
* It caches the in-flight PROMISE, so ten callers arriving at once share one download. A failed load is evicted so
|
|
6
|
+
* the next caller retries. Bounded both by entry count and by total samples held.
|
|
7
|
+
*
|
|
8
|
+
* @module @memberjunction/livekit-room-server
|
|
9
|
+
*/
|
|
10
|
+
/** Default maximum number of cached sources. */
|
|
11
|
+
export declare const DEFAULT_DECODE_CACHE_ENTRIES = 8;
|
|
12
|
+
/** Default maximum total samples held across all entries (128 Mi samples = 256 MB of PCM16). */
|
|
13
|
+
export declare const DEFAULT_DECODE_CACHE_MAX_SAMPLES: number;
|
|
14
|
+
/** A bounded, promise-sharing LRU of decoded PCM. */
|
|
15
|
+
export declare class RoomAudioDecodeCache {
|
|
16
|
+
private readonly maxEntries;
|
|
17
|
+
private readonly maxSamples;
|
|
18
|
+
private readonly slots;
|
|
19
|
+
/**
|
|
20
|
+
* @param maxEntries Maximum number of cached sources.
|
|
21
|
+
* @param maxSamples Maximum total samples held across all resolved entries.
|
|
22
|
+
*/
|
|
23
|
+
constructor(maxEntries?: number, maxSamples?: number);
|
|
24
|
+
/** How many sources are cached (or loading). */
|
|
25
|
+
get Size(): number;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the cached PCM for `key`, or runs `load` once and caches its result. Concurrent callers for the same key
|
|
28
|
+
* share one load. A rejected load is removed so the next call retries.
|
|
29
|
+
*/
|
|
30
|
+
GetOrLoad(key: string, load: () => Promise<Int16Array>): Promise<Int16Array>;
|
|
31
|
+
/** Empties the cache. */
|
|
32
|
+
Clear(): void;
|
|
33
|
+
/** Drops least-recently-used entries until both bounds hold (never the most recent one). */
|
|
34
|
+
private evictOverflow;
|
|
35
|
+
private totalSamples;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=room-audio-decode-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"room-audio-decode-cache.d.ts","sourceRoot":"","sources":["../../src/room-audio/room-audio-decode-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,gDAAgD;AAChD,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,gGAAgG;AAChG,eAAO,MAAM,gCAAgC,QAAoB,CAAC;AAQlE,qDAAqD;AACrD,qBAAa,oBAAoB;IAQ7B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAR7B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IAEtD;;;OAGG;gBAEgB,UAAU,GAAE,MAAqC,EACjD,UAAU,GAAE,MAAyC;IAGxE,gDAAgD;IAChD,IAAW,IAAI,IAAI,MAAM,CAExB;IAED;;;OAGG;IACI,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;IAwBnF,yBAAyB;IAClB,KAAK,IAAI,IAAI;IAIpB,4FAA4F;IAC5F,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,YAAY;CAOrB"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview {@link RoomAudioDecodeCache} — a small LRU of ready-to-play PCM keyed by source (an `MJ: Files` id or
|
|
3
|
+
* a URL), so every caller placed on hold in the same queue does not re-download and re-decode the same file.
|
|
4
|
+
*
|
|
5
|
+
* It caches the in-flight PROMISE, so ten callers arriving at once share one download. A failed load is evicted so
|
|
6
|
+
* the next caller retries. Bounded both by entry count and by total samples held.
|
|
7
|
+
*
|
|
8
|
+
* @module @memberjunction/livekit-room-server
|
|
9
|
+
*/
|
|
10
|
+
/** Default maximum number of cached sources. */
|
|
11
|
+
export const DEFAULT_DECODE_CACHE_ENTRIES = 8;
|
|
12
|
+
/** Default maximum total samples held across all entries (128 Mi samples = 256 MB of PCM16). */
|
|
13
|
+
export const DEFAULT_DECODE_CACHE_MAX_SAMPLES = 128 * 1024 * 1024;
|
|
14
|
+
/** A bounded, promise-sharing LRU of decoded PCM. */
|
|
15
|
+
export class RoomAudioDecodeCache {
|
|
16
|
+
/**
|
|
17
|
+
* @param maxEntries Maximum number of cached sources.
|
|
18
|
+
* @param maxSamples Maximum total samples held across all resolved entries.
|
|
19
|
+
*/
|
|
20
|
+
constructor(maxEntries = DEFAULT_DECODE_CACHE_ENTRIES, maxSamples = DEFAULT_DECODE_CACHE_MAX_SAMPLES) {
|
|
21
|
+
this.maxEntries = maxEntries;
|
|
22
|
+
this.maxSamples = maxSamples;
|
|
23
|
+
this.slots = new Map();
|
|
24
|
+
}
|
|
25
|
+
/** How many sources are cached (or loading). */
|
|
26
|
+
get Size() {
|
|
27
|
+
return this.slots.size;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Returns the cached PCM for `key`, or runs `load` once and caches its result. Concurrent callers for the same key
|
|
31
|
+
* share one load. A rejected load is removed so the next call retries.
|
|
32
|
+
*/
|
|
33
|
+
GetOrLoad(key, load) {
|
|
34
|
+
const existing = this.slots.get(key);
|
|
35
|
+
if (existing) {
|
|
36
|
+
this.slots.delete(key); // re-insert: Map order is the recency order
|
|
37
|
+
this.slots.set(key, existing);
|
|
38
|
+
return existing.Load;
|
|
39
|
+
}
|
|
40
|
+
const slot = { Load: load(), Samples: 0 };
|
|
41
|
+
this.slots.set(key, slot);
|
|
42
|
+
this.evictOverflow();
|
|
43
|
+
slot.Load.then((pcm) => {
|
|
44
|
+
slot.Samples = pcm.length;
|
|
45
|
+
this.evictOverflow();
|
|
46
|
+
}, () => {
|
|
47
|
+
if (this.slots.get(key) === slot) {
|
|
48
|
+
this.slots.delete(key); // the caller sees the rejection; the cache just forgets it
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return slot.Load;
|
|
52
|
+
}
|
|
53
|
+
/** Empties the cache. */
|
|
54
|
+
Clear() {
|
|
55
|
+
this.slots.clear();
|
|
56
|
+
}
|
|
57
|
+
/** Drops least-recently-used entries until both bounds hold (never the most recent one). */
|
|
58
|
+
evictOverflow() {
|
|
59
|
+
while (this.slots.size > 1 && (this.slots.size > this.maxEntries || this.totalSamples() > this.maxSamples)) {
|
|
60
|
+
const oldest = this.slots.keys().next().value;
|
|
61
|
+
if (oldest === undefined) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.slots.delete(oldest);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
totalSamples() {
|
|
68
|
+
let total = 0;
|
|
69
|
+
for (const slot of this.slots.values()) {
|
|
70
|
+
total += slot.Samples;
|
|
71
|
+
}
|
|
72
|
+
return total;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=room-audio-decode-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"room-audio-decode-cache.js","sourceRoot":"","sources":["../../src/room-audio/room-audio-decode-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,gDAAgD;AAChD,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC;AAE9C,gGAAgG;AAChG,MAAM,CAAC,MAAM,gCAAgC,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAQlE,qDAAqD;AACrD,MAAM,OAAO,oBAAoB;IAG/B;;;OAGG;IACH,YACmB,aAAqB,4BAA4B,EACjD,aAAqB,gCAAgC;QADrD,eAAU,GAAV,UAAU,CAAuC;QACjD,eAAU,GAAV,UAAU,CAA2C;QARvD,UAAK,GAAG,IAAI,GAAG,EAAqB,CAAC;IASnD,CAAC;IAEJ,gDAAgD;IAChD,IAAW,IAAI;QACb,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;IAED;;;OAGG;IACI,SAAS,CAAC,GAAW,EAAE,IAA+B;QAC3D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,4CAA4C;YACpE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;YAC9B,OAAO,QAAQ,CAAC,IAAI,CAAC;QACvB,CAAC;QACD,MAAM,IAAI,GAAc,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,IAAI,CAAC,IAAI,CACZ,CAAC,GAAG,EAAE,EAAE;YACN,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC;YAC1B,IAAI,CAAC,aAAa,EAAE,CAAC;QACvB,CAAC,EACD,GAAG,EAAE;YACH,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;gBACjC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,2DAA2D;YACrF,CAAC;QACH,CAAC,CACF,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED,yBAAyB;IAClB,KAAK;QACV,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,4FAA4F;IACpF,aAAa;QACnB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,YAAY,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3G,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YAC9C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,OAAO;YACT,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAEO,YAAY;QAClB,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACvC,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC;QACxB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
|