@estuary-ai/sdk 0.1.24 → 0.1.25
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/dist/index.mjs +501 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-W5QYPYX3.mjs +0 -30
- package/dist/chunk-W5QYPYX3.mjs.map +0 -1
- package/dist/livekit-voice-PV3TGH2Q.mjs +0 -246
- package/dist/livekit-voice-PV3TGH2Q.mjs.map +0 -1
- package/dist/websocket-voice-6DMYBGHP.mjs +0 -177
- package/dist/websocket-voice-6DMYBGHP.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,485 @@
|
|
|
1
|
-
import { EstuaryError } from './chunk-W5QYPYX3.mjs';
|
|
2
|
-
export { ErrorCode, EstuaryError } from './chunk-W5QYPYX3.mjs';
|
|
3
1
|
import { io } from 'socket.io-client';
|
|
4
2
|
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __esm = (fn, res) => function __init() {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
};
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/errors.ts
|
|
14
|
+
var ErrorCode, EstuaryError;
|
|
15
|
+
var init_errors = __esm({
|
|
16
|
+
"src/errors.ts"() {
|
|
17
|
+
ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
18
|
+
ErrorCode2["CONNECTION_FAILED"] = "CONNECTION_FAILED";
|
|
19
|
+
ErrorCode2["AUTH_FAILED"] = "AUTH_FAILED";
|
|
20
|
+
ErrorCode2["CONNECTION_TIMEOUT"] = "CONNECTION_TIMEOUT";
|
|
21
|
+
ErrorCode2["QUOTA_EXCEEDED"] = "QUOTA_EXCEEDED";
|
|
22
|
+
ErrorCode2["VOICE_NOT_SUPPORTED"] = "VOICE_NOT_SUPPORTED";
|
|
23
|
+
ErrorCode2["VOICE_ALREADY_ACTIVE"] = "VOICE_ALREADY_ACTIVE";
|
|
24
|
+
ErrorCode2["VOICE_NOT_ACTIVE"] = "VOICE_NOT_ACTIVE";
|
|
25
|
+
ErrorCode2["LIVEKIT_UNAVAILABLE"] = "LIVEKIT_UNAVAILABLE";
|
|
26
|
+
ErrorCode2["MICROPHONE_DENIED"] = "MICROPHONE_DENIED";
|
|
27
|
+
ErrorCode2["NOT_CONNECTED"] = "NOT_CONNECTED";
|
|
28
|
+
ErrorCode2["REST_ERROR"] = "REST_ERROR";
|
|
29
|
+
ErrorCode2["UNKNOWN"] = "UNKNOWN";
|
|
30
|
+
return ErrorCode2;
|
|
31
|
+
})(ErrorCode || {});
|
|
32
|
+
EstuaryError = class extends Error {
|
|
33
|
+
code;
|
|
34
|
+
details;
|
|
35
|
+
constructor(code, message, details) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "EstuaryError";
|
|
38
|
+
this.code = code;
|
|
39
|
+
this.details = details;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// src/audio/audio-utils.ts
|
|
46
|
+
function resample(input, fromRate, toRate) {
|
|
47
|
+
const ratio = fromRate / toRate;
|
|
48
|
+
const outputLength = Math.round(input.length / ratio);
|
|
49
|
+
const output = new Float32Array(outputLength);
|
|
50
|
+
for (let i = 0; i < outputLength; i++) {
|
|
51
|
+
const srcIndex = i * ratio;
|
|
52
|
+
const low = Math.floor(srcIndex);
|
|
53
|
+
const high = Math.min(low + 1, input.length - 1);
|
|
54
|
+
const frac = srcIndex - low;
|
|
55
|
+
output[i] = input[low] * (1 - frac) + input[high] * frac;
|
|
56
|
+
}
|
|
57
|
+
return output;
|
|
58
|
+
}
|
|
59
|
+
function float32ToInt16(float32) {
|
|
60
|
+
const int16 = new Int16Array(float32.length);
|
|
61
|
+
for (let i = 0; i < float32.length; i++) {
|
|
62
|
+
const clamped = Math.max(-1, Math.min(1, float32[i]));
|
|
63
|
+
int16[i] = clamped < 0 ? clamped * 32768 : clamped * 32767;
|
|
64
|
+
}
|
|
65
|
+
return int16;
|
|
66
|
+
}
|
|
67
|
+
function uint8ArrayToBase64(bytes) {
|
|
68
|
+
if (typeof btoa === "function") {
|
|
69
|
+
let binary = "";
|
|
70
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
71
|
+
binary += String.fromCharCode(bytes[i]);
|
|
72
|
+
}
|
|
73
|
+
return btoa(binary);
|
|
74
|
+
}
|
|
75
|
+
return Buffer.from(bytes).toString("base64");
|
|
76
|
+
}
|
|
77
|
+
var init_audio_utils = __esm({
|
|
78
|
+
"src/audio/audio-utils.ts"() {
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// src/voice/websocket-voice.ts
|
|
83
|
+
var websocket_voice_exports = {};
|
|
84
|
+
__export(websocket_voice_exports, {
|
|
85
|
+
WebSocketVoiceManager: () => WebSocketVoiceManager
|
|
86
|
+
});
|
|
87
|
+
var WebSocketVoiceManager;
|
|
88
|
+
var init_websocket_voice = __esm({
|
|
89
|
+
"src/voice/websocket-voice.ts"() {
|
|
90
|
+
init_errors();
|
|
91
|
+
init_audio_utils();
|
|
92
|
+
WebSocketVoiceManager = class {
|
|
93
|
+
socketManager;
|
|
94
|
+
sampleRate;
|
|
95
|
+
logger;
|
|
96
|
+
audioContext = null;
|
|
97
|
+
mediaStream = null;
|
|
98
|
+
scriptProcessor = null;
|
|
99
|
+
sourceNode = null;
|
|
100
|
+
zeroGainNode = null;
|
|
101
|
+
_isMuted = false;
|
|
102
|
+
_isSuppressed = false;
|
|
103
|
+
_isActive = false;
|
|
104
|
+
constructor(socketManager, sampleRate, logger) {
|
|
105
|
+
this.socketManager = socketManager;
|
|
106
|
+
this.sampleRate = sampleRate;
|
|
107
|
+
this.logger = logger;
|
|
108
|
+
}
|
|
109
|
+
get isMuted() {
|
|
110
|
+
return this._isMuted;
|
|
111
|
+
}
|
|
112
|
+
get isActive() {
|
|
113
|
+
return this._isActive;
|
|
114
|
+
}
|
|
115
|
+
async start() {
|
|
116
|
+
if (this._isActive) {
|
|
117
|
+
throw new EstuaryError("VOICE_ALREADY_ACTIVE" /* VOICE_ALREADY_ACTIVE */, "Voice is already active");
|
|
118
|
+
}
|
|
119
|
+
if (typeof AudioContext === "undefined" && typeof globalThis.webkitAudioContext === "undefined") {
|
|
120
|
+
throw new EstuaryError("VOICE_NOT_SUPPORTED" /* VOICE_NOT_SUPPORTED */, "AudioContext is not available in this environment");
|
|
121
|
+
}
|
|
122
|
+
let stream;
|
|
123
|
+
try {
|
|
124
|
+
stream = await navigator.mediaDevices.getUserMedia({
|
|
125
|
+
audio: {
|
|
126
|
+
sampleRate: this.sampleRate,
|
|
127
|
+
channelCount: 1,
|
|
128
|
+
echoCancellation: true,
|
|
129
|
+
noiseSuppression: true,
|
|
130
|
+
autoGainControl: true
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
} catch (err) {
|
|
134
|
+
throw new EstuaryError(
|
|
135
|
+
"MICROPHONE_DENIED" /* MICROPHONE_DENIED */,
|
|
136
|
+
"Microphone access denied",
|
|
137
|
+
err
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
this.mediaStream = stream;
|
|
141
|
+
const AudioCtx = globalThis.AudioContext || globalThis.webkitAudioContext;
|
|
142
|
+
this.audioContext = new AudioCtx({ sampleRate: this.sampleRate });
|
|
143
|
+
this.sourceNode = this.audioContext.createMediaStreamSource(stream);
|
|
144
|
+
this.scriptProcessor = this.audioContext.createScriptProcessor(4096, 1, 1);
|
|
145
|
+
const nativeRate = this.audioContext.sampleRate;
|
|
146
|
+
const targetRate = this.sampleRate;
|
|
147
|
+
this.scriptProcessor.onaudioprocess = (event) => {
|
|
148
|
+
if (this._isMuted || this._isSuppressed) return;
|
|
149
|
+
const inputData = event.inputBuffer.getChannelData(0);
|
|
150
|
+
let pcmFloat;
|
|
151
|
+
if (nativeRate !== targetRate) {
|
|
152
|
+
pcmFloat = resample(inputData, nativeRate, targetRate);
|
|
153
|
+
} else {
|
|
154
|
+
pcmFloat = inputData;
|
|
155
|
+
}
|
|
156
|
+
const pcm16 = float32ToInt16(pcmFloat);
|
|
157
|
+
const base64 = uint8ArrayToBase64(new Uint8Array(pcm16.buffer));
|
|
158
|
+
try {
|
|
159
|
+
this.socketManager.emitEvent("stream_audio", { audio: base64 });
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
this.sourceNode.connect(this.scriptProcessor);
|
|
164
|
+
this.zeroGainNode = this.audioContext.createGain();
|
|
165
|
+
this.zeroGainNode.gain.value = 0;
|
|
166
|
+
this.scriptProcessor.connect(this.zeroGainNode);
|
|
167
|
+
this.zeroGainNode.connect(this.audioContext.destination);
|
|
168
|
+
this._isActive = true;
|
|
169
|
+
this.socketManager.emitEvent("start_voice");
|
|
170
|
+
this.logger.debug("WebSocket voice started");
|
|
171
|
+
}
|
|
172
|
+
async stop() {
|
|
173
|
+
if (!this._isActive) return;
|
|
174
|
+
try {
|
|
175
|
+
this.socketManager.emitEvent("stop_voice");
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
this.cleanup();
|
|
179
|
+
this._isActive = false;
|
|
180
|
+
this._isMuted = false;
|
|
181
|
+
this._isSuppressed = false;
|
|
182
|
+
this.logger.debug("WebSocket voice stopped");
|
|
183
|
+
}
|
|
184
|
+
toggleMute() {
|
|
185
|
+
if (!this._isActive || !this.mediaStream) return;
|
|
186
|
+
this._isMuted = !this._isMuted;
|
|
187
|
+
for (const track of this.mediaStream.getAudioTracks()) {
|
|
188
|
+
track.enabled = !this._isMuted;
|
|
189
|
+
}
|
|
190
|
+
this.logger.debug("Mute toggled:", this._isMuted);
|
|
191
|
+
}
|
|
192
|
+
setSuppressed(suppressed) {
|
|
193
|
+
this._isSuppressed = suppressed;
|
|
194
|
+
this.logger.debug("Audio suppression:", suppressed ? "on" : "off");
|
|
195
|
+
}
|
|
196
|
+
dispose() {
|
|
197
|
+
this.cleanup();
|
|
198
|
+
this._isActive = false;
|
|
199
|
+
this._isMuted = false;
|
|
200
|
+
this._isSuppressed = false;
|
|
201
|
+
}
|
|
202
|
+
cleanup() {
|
|
203
|
+
if (this.scriptProcessor) {
|
|
204
|
+
this.scriptProcessor.onaudioprocess = null;
|
|
205
|
+
this.scriptProcessor.disconnect();
|
|
206
|
+
this.scriptProcessor = null;
|
|
207
|
+
}
|
|
208
|
+
if (this.zeroGainNode) {
|
|
209
|
+
this.zeroGainNode.disconnect();
|
|
210
|
+
this.zeroGainNode = null;
|
|
211
|
+
}
|
|
212
|
+
if (this.sourceNode) {
|
|
213
|
+
this.sourceNode.disconnect();
|
|
214
|
+
this.sourceNode = null;
|
|
215
|
+
}
|
|
216
|
+
if (this.mediaStream) {
|
|
217
|
+
for (const track of this.mediaStream.getTracks()) {
|
|
218
|
+
track.stop();
|
|
219
|
+
}
|
|
220
|
+
this.mediaStream = null;
|
|
221
|
+
}
|
|
222
|
+
if (this.audioContext) {
|
|
223
|
+
this.audioContext.close().catch(() => {
|
|
224
|
+
});
|
|
225
|
+
this.audioContext = null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// src/voice/livekit-voice.ts
|
|
233
|
+
var livekit_voice_exports = {};
|
|
234
|
+
__export(livekit_voice_exports, {
|
|
235
|
+
LiveKitVoiceManager: () => LiveKitVoiceManager
|
|
236
|
+
});
|
|
237
|
+
var LiveKitVoiceManager;
|
|
238
|
+
var init_livekit_voice = __esm({
|
|
239
|
+
"src/voice/livekit-voice.ts"() {
|
|
240
|
+
init_errors();
|
|
241
|
+
LiveKitVoiceManager = class {
|
|
242
|
+
socketManager;
|
|
243
|
+
logger;
|
|
244
|
+
room = null;
|
|
245
|
+
// livekit-client Room (dynamically imported)
|
|
246
|
+
_isMuted = false;
|
|
247
|
+
_isActive = false;
|
|
248
|
+
speakingStateCallback = null;
|
|
249
|
+
audioLevelCallback = null;
|
|
250
|
+
// Audio analyser (via livekit-client's createAudioAnalyser)
|
|
251
|
+
calculateVolume = null;
|
|
252
|
+
analyserCleanup = null;
|
|
253
|
+
audioLevelPollTimer = null;
|
|
254
|
+
_isBotSpeaking = false;
|
|
255
|
+
constructor(socketManager, logger) {
|
|
256
|
+
this.socketManager = socketManager;
|
|
257
|
+
this.logger = logger;
|
|
258
|
+
}
|
|
259
|
+
get isMuted() {
|
|
260
|
+
return this._isMuted;
|
|
261
|
+
}
|
|
262
|
+
get isActive() {
|
|
263
|
+
return this._isActive;
|
|
264
|
+
}
|
|
265
|
+
setSpeakingStateCallback(cb) {
|
|
266
|
+
this.speakingStateCallback = cb;
|
|
267
|
+
}
|
|
268
|
+
setAudioLevelCallback(cb) {
|
|
269
|
+
this.audioLevelCallback = cb;
|
|
270
|
+
}
|
|
271
|
+
async start() {
|
|
272
|
+
if (this._isActive) {
|
|
273
|
+
throw new EstuaryError("VOICE_ALREADY_ACTIVE" /* VOICE_ALREADY_ACTIVE */, "Voice is already active");
|
|
274
|
+
}
|
|
275
|
+
let Room;
|
|
276
|
+
let RoomEvent;
|
|
277
|
+
let Track;
|
|
278
|
+
try {
|
|
279
|
+
const lk = await import('livekit-client');
|
|
280
|
+
Room = lk.Room;
|
|
281
|
+
RoomEvent = lk.RoomEvent;
|
|
282
|
+
Track = lk.Track;
|
|
283
|
+
} catch {
|
|
284
|
+
throw new EstuaryError(
|
|
285
|
+
"LIVEKIT_UNAVAILABLE" /* LIVEKIT_UNAVAILABLE */,
|
|
286
|
+
"livekit-client package is not installed"
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
const tokenData = await this.requestToken();
|
|
290
|
+
this.room = new Room({
|
|
291
|
+
adaptiveStream: true,
|
|
292
|
+
dynacast: true,
|
|
293
|
+
audioCaptureDefaults: {
|
|
294
|
+
echoCancellation: true,
|
|
295
|
+
noiseSuppression: true,
|
|
296
|
+
autoGainControl: true
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
this.room.on(RoomEvent.TrackSubscribed, (track, _publication, participant) => {
|
|
300
|
+
if (track.kind === Track.Kind.Audio) {
|
|
301
|
+
this.logger.debug("Bot audio track subscribed from", participant.identity);
|
|
302
|
+
const audioElement = track.attach();
|
|
303
|
+
audioElement.autoplay = true;
|
|
304
|
+
audioElement.style.display = "none";
|
|
305
|
+
if (typeof document !== "undefined") {
|
|
306
|
+
document.body.appendChild(audioElement);
|
|
307
|
+
}
|
|
308
|
+
audioElement.play().catch(() => {
|
|
309
|
+
});
|
|
310
|
+
this.setupAnalyser(track);
|
|
311
|
+
if (this._isBotSpeaking) {
|
|
312
|
+
setTimeout(() => this.startAudioLevelPolling(), 50);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
this.room.on(RoomEvent.TrackUnsubscribed, (track) => {
|
|
317
|
+
if (track.kind === Track.Kind.Audio) {
|
|
318
|
+
this.teardownAnalyser();
|
|
319
|
+
track.detach().forEach((el) => el.remove());
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
this.room.on(RoomEvent.Disconnected, () => {
|
|
323
|
+
this.logger.debug("LiveKit room disconnected");
|
|
324
|
+
this._isActive = false;
|
|
325
|
+
this._isBotSpeaking = false;
|
|
326
|
+
this.teardownAnalyser();
|
|
327
|
+
this.speakingStateCallback?.(false);
|
|
328
|
+
});
|
|
329
|
+
try {
|
|
330
|
+
await this.room.connect(tokenData.url, tokenData.token);
|
|
331
|
+
this.logger.debug("Connected to LiveKit room:", tokenData.room);
|
|
332
|
+
} catch (err) {
|
|
333
|
+
this.room = null;
|
|
334
|
+
const reason = err instanceof Error ? `: ${err.message}` : "";
|
|
335
|
+
throw new EstuaryError(
|
|
336
|
+
"CONNECTION_FAILED" /* CONNECTION_FAILED */,
|
|
337
|
+
`Failed to connect to LiveKit room${reason}`,
|
|
338
|
+
err
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
this.room.on(
|
|
342
|
+
RoomEvent.ParticipantAttributesChanged,
|
|
343
|
+
(changedAttributes, participant) => {
|
|
344
|
+
if (participant === this.room?.localParticipant) return;
|
|
345
|
+
const state = changedAttributes["estuary.state"];
|
|
346
|
+
if (state === "speaking") {
|
|
347
|
+
this._isBotSpeaking = true;
|
|
348
|
+
this.speakingStateCallback?.(true);
|
|
349
|
+
this.startAudioLevelPolling();
|
|
350
|
+
} else if (state === "idle") {
|
|
351
|
+
this._isBotSpeaking = false;
|
|
352
|
+
this.stopAudioLevelPolling();
|
|
353
|
+
this.speakingStateCallback?.(false);
|
|
354
|
+
this.audioLevelCallback?.(0);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
try {
|
|
359
|
+
await this.room.localParticipant.setMicrophoneEnabled(true);
|
|
360
|
+
this.logger.debug("Microphone enabled");
|
|
361
|
+
} catch (err) {
|
|
362
|
+
this.room.disconnect();
|
|
363
|
+
this.room = null;
|
|
364
|
+
const reason = err instanceof Error ? `: ${err.message}` : "";
|
|
365
|
+
throw new EstuaryError(
|
|
366
|
+
"MICROPHONE_DENIED" /* MICROPHONE_DENIED */,
|
|
367
|
+
`Failed to enable microphone${reason}`,
|
|
368
|
+
err
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
this.socketManager.emitEvent("livekit_join", { room: tokenData.room });
|
|
372
|
+
this._isActive = true;
|
|
373
|
+
this.logger.debug("LiveKit voice started");
|
|
374
|
+
}
|
|
375
|
+
async stop() {
|
|
376
|
+
if (!this._isActive) return;
|
|
377
|
+
try {
|
|
378
|
+
this.socketManager.emitEvent("livekit_leave");
|
|
379
|
+
} catch {
|
|
380
|
+
}
|
|
381
|
+
this._isBotSpeaking = false;
|
|
382
|
+
this.teardownAnalyser();
|
|
383
|
+
this.speakingStateCallback?.(false);
|
|
384
|
+
if (this.room) {
|
|
385
|
+
for (const [, publication] of this.room.localParticipant.trackPublications) {
|
|
386
|
+
if (publication.track) {
|
|
387
|
+
publication.track.stop();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
this.room.disconnect();
|
|
391
|
+
this.room = null;
|
|
392
|
+
}
|
|
393
|
+
this._isActive = false;
|
|
394
|
+
this._isMuted = false;
|
|
395
|
+
this.logger.debug("LiveKit voice stopped");
|
|
396
|
+
}
|
|
397
|
+
toggleMute() {
|
|
398
|
+
if (!this._isActive || !this.room) return;
|
|
399
|
+
this._isMuted = !this._isMuted;
|
|
400
|
+
this.room.localParticipant.setMicrophoneEnabled(!this._isMuted);
|
|
401
|
+
this.logger.debug("Mute toggled:", this._isMuted);
|
|
402
|
+
}
|
|
403
|
+
dispose() {
|
|
404
|
+
this.speakingStateCallback = null;
|
|
405
|
+
this.audioLevelCallback = null;
|
|
406
|
+
this._isBotSpeaking = false;
|
|
407
|
+
this.teardownAnalyser();
|
|
408
|
+
if (this.room) {
|
|
409
|
+
this.room.disconnect();
|
|
410
|
+
this.room = null;
|
|
411
|
+
}
|
|
412
|
+
this._isActive = false;
|
|
413
|
+
this._isMuted = false;
|
|
414
|
+
}
|
|
415
|
+
// ─── Audio Analyser (livekit-client built-in) ───────────────────
|
|
416
|
+
async setupAnalyser(track) {
|
|
417
|
+
this.teardownAnalyser();
|
|
418
|
+
try {
|
|
419
|
+
const { createAudioAnalyser } = await import('livekit-client');
|
|
420
|
+
const { analyser, calculateVolume, cleanup } = createAudioAnalyser(track, {
|
|
421
|
+
fftSize: 256,
|
|
422
|
+
smoothingTimeConstant: 0.3
|
|
423
|
+
});
|
|
424
|
+
if (analyser.context.state === "suspended") {
|
|
425
|
+
await analyser.context.resume();
|
|
426
|
+
}
|
|
427
|
+
this.calculateVolume = calculateVolume;
|
|
428
|
+
this.analyserCleanup = cleanup;
|
|
429
|
+
this.logger.debug("Audio analyser created for bot track");
|
|
430
|
+
} catch (err) {
|
|
431
|
+
this.logger.debug("Failed to create audio analyser:", err);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
teardownAnalyser() {
|
|
435
|
+
this.stopAudioLevelPolling();
|
|
436
|
+
if (this.analyserCleanup) {
|
|
437
|
+
this.analyserCleanup().catch(() => {
|
|
438
|
+
});
|
|
439
|
+
this.analyserCleanup = null;
|
|
440
|
+
}
|
|
441
|
+
this.calculateVolume = null;
|
|
442
|
+
}
|
|
443
|
+
startAudioLevelPolling() {
|
|
444
|
+
if (this.audioLevelPollTimer !== null) return;
|
|
445
|
+
if (!this.calculateVolume) return;
|
|
446
|
+
this.audioLevelPollTimer = setInterval(() => {
|
|
447
|
+
if (!this.calculateVolume) {
|
|
448
|
+
this.stopAudioLevelPolling();
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const vol = this.calculateVolume();
|
|
452
|
+
this.audioLevelCallback?.(vol);
|
|
453
|
+
}, 33);
|
|
454
|
+
}
|
|
455
|
+
stopAudioLevelPolling() {
|
|
456
|
+
if (this.audioLevelPollTimer !== null) {
|
|
457
|
+
clearInterval(this.audioLevelPollTimer);
|
|
458
|
+
this.audioLevelPollTimer = null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// ─── Private ────────────────────────────────────────────────────
|
|
462
|
+
requestToken() {
|
|
463
|
+
return new Promise((resolve, reject) => {
|
|
464
|
+
const timeout = setTimeout(() => {
|
|
465
|
+
this.socketManager.onLiveKitToken(() => {
|
|
466
|
+
});
|
|
467
|
+
reject(new EstuaryError(
|
|
468
|
+
"CONNECTION_TIMEOUT" /* CONNECTION_TIMEOUT */,
|
|
469
|
+
"Timed out waiting for LiveKit token"
|
|
470
|
+
));
|
|
471
|
+
}, 1e4);
|
|
472
|
+
this.socketManager.onLiveKitToken((data) => {
|
|
473
|
+
clearTimeout(timeout);
|
|
474
|
+
resolve(data);
|
|
475
|
+
});
|
|
476
|
+
this.socketManager.emitEvent("livekit_token");
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
5
483
|
// src/utils/event-emitter.ts
|
|
6
484
|
var TypedEventEmitter = class {
|
|
7
485
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -61,6 +539,9 @@ var TypedEventEmitter = class {
|
|
|
61
539
|
}
|
|
62
540
|
};
|
|
63
541
|
|
|
542
|
+
// src/connection/socket-manager.ts
|
|
543
|
+
init_errors();
|
|
544
|
+
|
|
64
545
|
// src/types.ts
|
|
65
546
|
var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
|
|
66
547
|
ConnectionState2["Disconnected"] = "disconnected";
|
|
@@ -361,30 +842,31 @@ async function isLiveKitAvailable() {
|
|
|
361
842
|
}
|
|
362
843
|
async function createVoiceManager(transport, socketManager, sampleRate, logger) {
|
|
363
844
|
if (transport === "websocket") {
|
|
364
|
-
const { WebSocketVoiceManager } = await
|
|
365
|
-
return new
|
|
845
|
+
const { WebSocketVoiceManager: WebSocketVoiceManager2 } = await Promise.resolve().then(() => (init_websocket_voice(), websocket_voice_exports));
|
|
846
|
+
return new WebSocketVoiceManager2(socketManager, sampleRate, logger);
|
|
366
847
|
}
|
|
367
848
|
if (transport === "livekit") {
|
|
368
849
|
if (await isLiveKitAvailable()) {
|
|
369
|
-
const { LiveKitVoiceManager } = await
|
|
370
|
-
return new
|
|
850
|
+
const { LiveKitVoiceManager: LiveKitVoiceManager2 } = await Promise.resolve().then(() => (init_livekit_voice(), livekit_voice_exports));
|
|
851
|
+
return new LiveKitVoiceManager2(socketManager, logger);
|
|
371
852
|
}
|
|
372
853
|
logger.warn("livekit-client not installed, falling back to WebSocket voice");
|
|
373
|
-
const { WebSocketVoiceManager } = await
|
|
374
|
-
return new
|
|
854
|
+
const { WebSocketVoiceManager: WebSocketVoiceManager2 } = await Promise.resolve().then(() => (init_websocket_voice(), websocket_voice_exports));
|
|
855
|
+
return new WebSocketVoiceManager2(socketManager, sampleRate, logger);
|
|
375
856
|
}
|
|
376
857
|
if (transport === "auto") {
|
|
377
858
|
if (await isLiveKitAvailable()) {
|
|
378
|
-
const { LiveKitVoiceManager } = await
|
|
379
|
-
return new
|
|
859
|
+
const { LiveKitVoiceManager: LiveKitVoiceManager2 } = await Promise.resolve().then(() => (init_livekit_voice(), livekit_voice_exports));
|
|
860
|
+
return new LiveKitVoiceManager2(socketManager, logger);
|
|
380
861
|
}
|
|
381
|
-
const { WebSocketVoiceManager } = await
|
|
382
|
-
return new
|
|
862
|
+
const { WebSocketVoiceManager: WebSocketVoiceManager2 } = await Promise.resolve().then(() => (init_websocket_voice(), websocket_voice_exports));
|
|
863
|
+
return new WebSocketVoiceManager2(socketManager, sampleRate, logger);
|
|
383
864
|
}
|
|
384
865
|
return null;
|
|
385
866
|
}
|
|
386
867
|
|
|
387
868
|
// src/rest/rest-client.ts
|
|
869
|
+
init_errors();
|
|
388
870
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
389
871
|
var RestClient = class {
|
|
390
872
|
baseUrl;
|
|
@@ -705,6 +1187,9 @@ var Logger = class {
|
|
|
705
1187
|
}
|
|
706
1188
|
};
|
|
707
1189
|
|
|
1190
|
+
// src/client.ts
|
|
1191
|
+
init_errors();
|
|
1192
|
+
|
|
708
1193
|
// src/utils/action-parser.ts
|
|
709
1194
|
var ACTION_TAG_RE = /<action\s+([^>]*?)\/>/gi;
|
|
710
1195
|
var ATTR_RE = /(\w+)\s*=\s*"([^"]*)"|(\w+)\s*=\s*'([^']*)'/g;
|
|
@@ -1044,6 +1529,9 @@ var EstuaryClient = class extends TypedEventEmitter {
|
|
|
1044
1529
|
}
|
|
1045
1530
|
};
|
|
1046
1531
|
|
|
1047
|
-
|
|
1532
|
+
// src/index.ts
|
|
1533
|
+
init_errors();
|
|
1534
|
+
|
|
1535
|
+
export { ConnectionState, ErrorCode, EstuaryClient, EstuaryError, parseActions };
|
|
1048
1536
|
//# sourceMappingURL=index.mjs.map
|
|
1049
1537
|
//# sourceMappingURL=index.mjs.map
|