@estuary-ai/sdk 0.1.13 → 0.1.14

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.
@@ -4,15 +4,13 @@ import { EstuaryError } from './chunk-64CWCRPS.mjs';
4
4
  var LiveKitVoiceManager = class {
5
5
  socketManager;
6
6
  logger;
7
- livekitModule;
8
7
  room = null;
9
8
  // livekit-client Room (dynamically imported)
10
9
  _isMuted = false;
11
10
  _isActive = false;
12
- constructor(socketManager, logger, livekitModule) {
11
+ constructor(socketManager, logger) {
13
12
  this.socketManager = socketManager;
14
13
  this.logger = logger;
15
- this.livekitModule = livekitModule;
16
14
  }
17
15
  get isMuted() {
18
16
  return this._isMuted;
@@ -27,26 +25,20 @@ var LiveKitVoiceManager = class {
27
25
  let Room;
28
26
  let RoomEvent;
29
27
  let Track;
30
- if (this.livekitModule) {
31
- Room = this.livekitModule.Room;
32
- RoomEvent = this.livekitModule.RoomEvent;
33
- Track = this.livekitModule.Track;
34
- } else {
35
- try {
36
- const specifier = ["livekit", "client"].join("-");
37
- const lk = await import(
38
- /* @vite-ignore */
39
- specifier
40
- );
41
- Room = lk.Room;
42
- RoomEvent = lk.RoomEvent;
43
- Track = lk.Track;
44
- } catch {
45
- throw new EstuaryError(
46
- "LIVEKIT_UNAVAILABLE" /* LIVEKIT_UNAVAILABLE */,
47
- "livekit-client package is not installed"
48
- );
49
- }
28
+ try {
29
+ const specifier = ["livekit", "client"].join("-");
30
+ const lk = await import(
31
+ /* @vite-ignore */
32
+ specifier
33
+ );
34
+ Room = lk.Room;
35
+ RoomEvent = lk.RoomEvent;
36
+ Track = lk.Track;
37
+ } catch {
38
+ throw new EstuaryError(
39
+ "LIVEKIT_UNAVAILABLE" /* LIVEKIT_UNAVAILABLE */,
40
+ "livekit-client package is not installed"
41
+ );
50
42
  }
51
43
  const tokenData = await this.requestToken();
52
44
  this.room = new Room({
@@ -160,5 +152,5 @@ var LiveKitVoiceManager = class {
160
152
  };
161
153
 
162
154
  export { LiveKitVoiceManager };
163
- //# sourceMappingURL=livekit-voice-WSOF4UL6.mjs.map
164
- //# sourceMappingURL=livekit-voice-WSOF4UL6.mjs.map
155
+ //# sourceMappingURL=livekit-voice-H6WZ44LM.mjs.map
156
+ //# sourceMappingURL=livekit-voice-H6WZ44LM.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/voice/livekit-voice.ts"],"names":[],"mappings":"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","file":"livekit-voice-H6WZ44LM.mjs","sourcesContent":["import type { VoiceManager, LiveKitTokenResponse } from '../types';\nimport type { SocketManager } from '../connection/socket-manager';\nimport type { Logger } from '../utils/logger';\nimport { EstuaryError, ErrorCode } from '../errors';\n\nexport class LiveKitVoiceManager implements VoiceManager {\n private socketManager: SocketManager;\n private logger: Logger;\n private room: any = null; // livekit-client Room (dynamically imported)\n private _isMuted = false;\n private _isActive = false;\n\n constructor(socketManager: SocketManager, logger: Logger) {\n this.socketManager = socketManager;\n this.logger = logger;\n }\n\n get isMuted(): boolean {\n return this._isMuted;\n }\n\n get isActive(): boolean {\n return this._isActive;\n }\n\n async start(): Promise<void> {\n if (this._isActive) {\n throw new EstuaryError(ErrorCode.VOICE_ALREADY_ACTIVE, 'Voice is already active');\n }\n\n let Room: any;\n let RoomEvent: any;\n let Track: any;\n try {\n const specifier = ['livekit', 'client'].join('-');\n const lk = await import(/* @vite-ignore */ specifier);\n Room = lk.Room;\n RoomEvent = lk.RoomEvent;\n Track = lk.Track;\n } catch {\n throw new EstuaryError(\n ErrorCode.LIVEKIT_UNAVAILABLE,\n 'livekit-client package is not installed',\n );\n }\n\n // Request token from server\n const tokenData = await this.requestToken();\n\n // Create and configure room\n this.room = new Room({\n adaptiveStream: true,\n dynacast: true,\n audioCaptureDefaults: {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n\n // Handle remote audio tracks (bot audio)\n this.room.on(RoomEvent.TrackSubscribed, (\n track: any,\n _publication: any,\n participant: any,\n ) => {\n if (track.kind === Track.Kind.Audio) {\n this.logger.debug('Bot audio track subscribed from', participant.identity);\n const audioElement = track.attach();\n audioElement.autoplay = true;\n audioElement.style.display = 'none';\n if (typeof document !== 'undefined') {\n document.body.appendChild(audioElement);\n }\n audioElement.play().catch(() => {});\n }\n });\n\n this.room.on(RoomEvent.TrackUnsubscribed, (track: any) => {\n if (track.kind === Track.Kind.Audio) {\n track.detach().forEach((el: HTMLMediaElement) => el.remove());\n }\n });\n\n this.room.on(RoomEvent.Disconnected, () => {\n this.logger.debug('LiveKit room disconnected');\n this._isActive = false;\n });\n\n // Connect to room\n try {\n await this.room.connect(tokenData.url, tokenData.token);\n this.logger.debug('Connected to LiveKit room:', tokenData.room);\n } catch (err) {\n this.room = null;\n throw new EstuaryError(\n ErrorCode.CONNECTION_FAILED,\n 'Failed to connect to LiveKit room',\n err,\n );\n }\n\n // Enable microphone\n try {\n await this.room.localParticipant.setMicrophoneEnabled(true);\n this.logger.debug('Microphone enabled');\n } catch (err) {\n this.room.disconnect();\n this.room = null;\n throw new EstuaryError(\n ErrorCode.MICROPHONE_DENIED,\n 'Failed to enable microphone',\n err,\n );\n }\n\n // Notify backend\n this.socketManager.emitEvent('livekit_join', { room: tokenData.room });\n this._isActive = true;\n this.logger.debug('LiveKit voice started');\n }\n\n async stop(): Promise<void> {\n if (!this._isActive) return;\n\n try {\n this.socketManager.emitEvent('livekit_leave');\n } catch {\n // May not be connected\n }\n\n if (this.room) {\n // Stop local tracks\n for (const [, publication] of this.room.localParticipant.trackPublications) {\n if (publication.track) {\n publication.track.stop();\n }\n }\n this.room.disconnect();\n this.room = null;\n }\n\n this._isActive = false;\n this._isMuted = false;\n this.logger.debug('LiveKit voice stopped');\n }\n\n toggleMute(): void {\n if (!this._isActive || !this.room) return;\n this._isMuted = !this._isMuted;\n this.room.localParticipant.setMicrophoneEnabled(!this._isMuted);\n this.logger.debug('Mute toggled:', this._isMuted);\n }\n\n dispose(): void {\n if (this.room) {\n this.room.disconnect();\n this.room = null;\n }\n this._isActive = false;\n this._isMuted = false;\n }\n\n private requestToken(): Promise<LiveKitTokenResponse> {\n return new Promise<LiveKitTokenResponse>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.socketManager.onLiveKitToken(() => {}); // clear callback\n reject(new EstuaryError(\n ErrorCode.CONNECTION_TIMEOUT,\n 'Timed out waiting for LiveKit token',\n ));\n }, 10000);\n\n this.socketManager.onLiveKitToken((data: LiveKitTokenResponse) => {\n clearTimeout(timeout);\n resolve(data);\n });\n\n this.socketManager.emitEvent('livekit_token');\n });\n }\n}\n"]}
@@ -9,6 +9,7 @@ var WebSocketVoiceManager = class {
9
9
  mediaStream = null;
10
10
  scriptProcessor = null;
11
11
  sourceNode = null;
12
+ zeroGainNode = null;
12
13
  _isMuted = false;
13
14
  _isSuppressed = false;
14
15
  _isActive = false;
@@ -72,7 +73,10 @@ var WebSocketVoiceManager = class {
72
73
  }
73
74
  };
74
75
  this.sourceNode.connect(this.scriptProcessor);
75
- this.scriptProcessor.connect(this.audioContext.destination);
76
+ this.zeroGainNode = this.audioContext.createGain();
77
+ this.zeroGainNode.gain.value = 0;
78
+ this.scriptProcessor.connect(this.zeroGainNode);
79
+ this.zeroGainNode.connect(this.audioContext.destination);
76
80
  this._isActive = true;
77
81
  this.socketManager.emitEvent("start_voice");
78
82
  this.logger.debug("WebSocket voice started");
@@ -113,6 +117,10 @@ var WebSocketVoiceManager = class {
113
117
  this.scriptProcessor.disconnect();
114
118
  this.scriptProcessor = null;
115
119
  }
120
+ if (this.zeroGainNode) {
121
+ this.zeroGainNode.disconnect();
122
+ this.zeroGainNode = null;
123
+ }
116
124
  if (this.sourceNode) {
117
125
  this.sourceNode.disconnect();
118
126
  this.sourceNode = null;
@@ -163,5 +171,5 @@ function uint8ArrayToBase64(bytes) {
163
171
  }
164
172
 
165
173
  export { WebSocketVoiceManager };
166
- //# sourceMappingURL=websocket-voice-HYHCIYEW.mjs.map
167
- //# sourceMappingURL=websocket-voice-HYHCIYEW.mjs.map
174
+ //# sourceMappingURL=websocket-voice-CTOOXDRQ.mjs.map
175
+ //# sourceMappingURL=websocket-voice-CTOOXDRQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/voice/websocket-voice.ts"],"names":[],"mappings":"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","file":"websocket-voice-CTOOXDRQ.mjs","sourcesContent":["import type { VoiceManager } from '../types';\nimport type { SocketManager } from '../connection/socket-manager';\nimport type { Logger } from '../utils/logger';\nimport { EstuaryError, ErrorCode } from '../errors';\n\nexport class WebSocketVoiceManager implements VoiceManager {\n private socketManager: SocketManager;\n private sampleRate: number;\n private logger: Logger;\n private audioContext: AudioContext | null = null;\n private mediaStream: MediaStream | null = null;\n private scriptProcessor: ScriptProcessorNode | null = null;\n private sourceNode: MediaStreamAudioSourceNode | null = null;\n private zeroGainNode: GainNode | null = null;\n private _isMuted = false;\n private _isSuppressed = false;\n private _isActive = false;\n\n constructor(socketManager: SocketManager, sampleRate: number, logger: Logger) {\n this.socketManager = socketManager;\n this.sampleRate = sampleRate;\n this.logger = logger;\n }\n\n get isMuted(): boolean {\n return this._isMuted;\n }\n\n get isActive(): boolean {\n return this._isActive;\n }\n\n async start(): Promise<void> {\n if (this._isActive) {\n throw new EstuaryError(ErrorCode.VOICE_ALREADY_ACTIVE, 'Voice is already active');\n }\n\n if (typeof AudioContext === 'undefined' && typeof (globalThis as any).webkitAudioContext === 'undefined') {\n throw new EstuaryError(ErrorCode.VOICE_NOT_SUPPORTED, 'AudioContext is not available in this environment');\n }\n\n let stream: MediaStream;\n try {\n stream = await navigator.mediaDevices.getUserMedia({\n audio: {\n sampleRate: this.sampleRate,\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n } catch (err) {\n throw new EstuaryError(\n ErrorCode.MICROPHONE_DENIED,\n 'Microphone access denied',\n err,\n );\n }\n\n this.mediaStream = stream;\n const AudioCtx = globalThis.AudioContext || (globalThis as any).webkitAudioContext;\n this.audioContext = new AudioCtx({ sampleRate: this.sampleRate });\n\n this.sourceNode = this.audioContext.createMediaStreamSource(stream);\n this.scriptProcessor = this.audioContext.createScriptProcessor(4096, 1, 1);\n\n const nativeRate = this.audioContext.sampleRate;\n const targetRate = this.sampleRate;\n\n this.scriptProcessor.onaudioprocess = (event: AudioProcessingEvent) => {\n if (this._isMuted || this._isSuppressed) return;\n\n const inputData = event.inputBuffer.getChannelData(0);\n let pcmFloat: Float32Array;\n\n if (nativeRate !== targetRate) {\n pcmFloat = resample(inputData, nativeRate, targetRate);\n } else {\n pcmFloat = inputData;\n }\n\n const pcm16 = float32ToInt16(pcmFloat);\n const base64 = uint8ArrayToBase64(new Uint8Array(pcm16.buffer));\n\n try {\n this.socketManager.emitEvent('stream_audio', { audio: base64 });\n } catch {\n // Not connected — ignore, will be handled by disconnect logic\n }\n };\n\n this.sourceNode.connect(this.scriptProcessor);\n this.zeroGainNode = this.audioContext.createGain();\n this.zeroGainNode.gain.value = 0;\n this.scriptProcessor.connect(this.zeroGainNode);\n this.zeroGainNode.connect(this.audioContext.destination);\n\n this._isActive = true;\n this.socketManager.emitEvent('start_voice');\n this.logger.debug('WebSocket voice started');\n }\n\n async stop(): Promise<void> {\n if (!this._isActive) return;\n\n try {\n this.socketManager.emitEvent('stop_voice');\n } catch {\n // May not be connected\n }\n\n this.cleanup();\n this._isActive = false;\n this._isMuted = false;\n this._isSuppressed = false;\n this.logger.debug('WebSocket voice stopped');\n }\n\n toggleMute(): void {\n if (!this._isActive || !this.mediaStream) return;\n this._isMuted = !this._isMuted;\n for (const track of this.mediaStream.getAudioTracks()) {\n track.enabled = !this._isMuted;\n }\n this.logger.debug('Mute toggled:', this._isMuted);\n }\n\n setSuppressed(suppressed: boolean): void {\n this._isSuppressed = suppressed;\n this.logger.debug('Audio suppression:', suppressed ? 'on' : 'off');\n }\n\n dispose(): void {\n this.cleanup();\n this._isActive = false;\n this._isMuted = false;\n this._isSuppressed = false;\n }\n\n private cleanup(): void {\n if (this.scriptProcessor) {\n this.scriptProcessor.onaudioprocess = null;\n this.scriptProcessor.disconnect();\n this.scriptProcessor = null;\n }\n if (this.zeroGainNode) {\n this.zeroGainNode.disconnect();\n this.zeroGainNode = null;\n }\n if (this.sourceNode) {\n this.sourceNode.disconnect();\n this.sourceNode = null;\n }\n if (this.mediaStream) {\n for (const track of this.mediaStream.getTracks()) {\n track.stop();\n }\n this.mediaStream = null;\n }\n if (this.audioContext) {\n this.audioContext.close().catch(() => {});\n this.audioContext = null;\n }\n }\n}\n\nfunction resample(input: Float32Array, fromRate: number, toRate: number): Float32Array {\n const ratio = fromRate / toRate;\n const outputLength = Math.round(input.length / ratio);\n const output = new Float32Array(outputLength);\n for (let i = 0; i < outputLength; i++) {\n const srcIndex = i * ratio;\n const low = Math.floor(srcIndex);\n const high = Math.min(low + 1, input.length - 1);\n const frac = srcIndex - low;\n output[i] = input[low] * (1 - frac) + input[high] * frac;\n }\n return output;\n}\n\nfunction float32ToInt16(float32: Float32Array): Int16Array {\n const int16 = new Int16Array(float32.length);\n for (let i = 0; i < float32.length; i++) {\n const clamped = Math.max(-1, Math.min(1, float32[i]));\n int16[i] = clamped < 0 ? clamped * 0x8000 : clamped * 0x7fff;\n }\n return int16;\n}\n\nfunction uint8ArrayToBase64(bytes: Uint8Array): string {\n if (typeof btoa === 'function') {\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary);\n }\n // Node.js fallback\n return Buffer.from(bytes).toString('base64');\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@estuary-ai/sdk",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "TypeScript SDK for the Estuary real-time AI conversation platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/voice/livekit-voice.ts"],"names":[],"mappings":"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","file":"livekit-voice-WSOF4UL6.mjs","sourcesContent":["import type { VoiceManager, LiveKitTokenResponse } from '../types';\nimport type { SocketManager } from '../connection/socket-manager';\nimport type { Logger } from '../utils/logger';\nimport { EstuaryError, ErrorCode } from '../errors';\n\nexport class LiveKitVoiceManager implements VoiceManager {\n private socketManager: SocketManager;\n private logger: Logger;\n private livekitModule: any;\n private room: any = null; // livekit-client Room (dynamically imported)\n private _isMuted = false;\n private _isActive = false;\n\n constructor(socketManager: SocketManager, logger: Logger, livekitModule?: any) {\n this.socketManager = socketManager;\n this.logger = logger;\n this.livekitModule = livekitModule;\n }\n\n get isMuted(): boolean {\n return this._isMuted;\n }\n\n get isActive(): boolean {\n return this._isActive;\n }\n\n async start(): Promise<void> {\n if (this._isActive) {\n throw new EstuaryError(ErrorCode.VOICE_ALREADY_ACTIVE, 'Voice is already active');\n }\n\n let Room: any;\n let RoomEvent: any;\n let Track: any;\n if (this.livekitModule) {\n Room = this.livekitModule.Room;\n RoomEvent = this.livekitModule.RoomEvent;\n Track = this.livekitModule.Track;\n } else {\n try {\n const specifier = ['livekit', 'client'].join('-');\n const lk = await import(/* @vite-ignore */ specifier);\n Room = lk.Room;\n RoomEvent = lk.RoomEvent;\n Track = lk.Track;\n } catch {\n throw new EstuaryError(\n ErrorCode.LIVEKIT_UNAVAILABLE,\n 'livekit-client package is not installed',\n );\n }\n }\n\n // Request token from server\n const tokenData = await this.requestToken();\n\n // Create and configure room\n this.room = new Room({\n adaptiveStream: true,\n dynacast: true,\n audioCaptureDefaults: {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n\n // Handle remote audio tracks (bot audio)\n this.room.on(RoomEvent.TrackSubscribed, (\n track: any,\n _publication: any,\n participant: any,\n ) => {\n if (track.kind === Track.Kind.Audio) {\n this.logger.debug('Bot audio track subscribed from', participant.identity);\n const audioElement = track.attach();\n audioElement.autoplay = true;\n audioElement.style.display = 'none';\n if (typeof document !== 'undefined') {\n document.body.appendChild(audioElement);\n }\n audioElement.play().catch(() => {});\n }\n });\n\n this.room.on(RoomEvent.TrackUnsubscribed, (track: any) => {\n if (track.kind === Track.Kind.Audio) {\n track.detach().forEach((el: HTMLMediaElement) => el.remove());\n }\n });\n\n this.room.on(RoomEvent.Disconnected, () => {\n this.logger.debug('LiveKit room disconnected');\n this._isActive = false;\n });\n\n // Connect to room\n try {\n await this.room.connect(tokenData.url, tokenData.token);\n this.logger.debug('Connected to LiveKit room:', tokenData.room);\n } catch (err) {\n this.room = null;\n throw new EstuaryError(\n ErrorCode.CONNECTION_FAILED,\n 'Failed to connect to LiveKit room',\n err,\n );\n }\n\n // Enable microphone\n try {\n await this.room.localParticipant.setMicrophoneEnabled(true);\n this.logger.debug('Microphone enabled');\n } catch (err) {\n this.room.disconnect();\n this.room = null;\n throw new EstuaryError(\n ErrorCode.MICROPHONE_DENIED,\n 'Failed to enable microphone',\n err,\n );\n }\n\n // Notify backend\n this.socketManager.emitEvent('livekit_join', { room: tokenData.room });\n this._isActive = true;\n this.logger.debug('LiveKit voice started');\n }\n\n async stop(): Promise<void> {\n if (!this._isActive) return;\n\n try {\n this.socketManager.emitEvent('livekit_leave');\n } catch {\n // May not be connected\n }\n\n if (this.room) {\n // Stop local tracks\n for (const [, publication] of this.room.localParticipant.trackPublications) {\n if (publication.track) {\n publication.track.stop();\n }\n }\n this.room.disconnect();\n this.room = null;\n }\n\n this._isActive = false;\n this._isMuted = false;\n this.logger.debug('LiveKit voice stopped');\n }\n\n toggleMute(): void {\n if (!this._isActive || !this.room) return;\n this._isMuted = !this._isMuted;\n this.room.localParticipant.setMicrophoneEnabled(!this._isMuted);\n this.logger.debug('Mute toggled:', this._isMuted);\n }\n\n dispose(): void {\n if (this.room) {\n this.room.disconnect();\n this.room = null;\n }\n this._isActive = false;\n this._isMuted = false;\n }\n\n private requestToken(): Promise<LiveKitTokenResponse> {\n return new Promise<LiveKitTokenResponse>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.socketManager.onLiveKitToken(() => {}); // clear callback\n reject(new EstuaryError(\n ErrorCode.CONNECTION_TIMEOUT,\n 'Timed out waiting for LiveKit token',\n ));\n }, 10000);\n\n this.socketManager.onLiveKitToken((data: LiveKitTokenResponse) => {\n clearTimeout(timeout);\n resolve(data);\n });\n\n this.socketManager.emitEvent('livekit_token');\n });\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/voice/websocket-voice.ts"],"names":[],"mappings":"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","file":"websocket-voice-HYHCIYEW.mjs","sourcesContent":["import type { VoiceManager } from '../types';\nimport type { SocketManager } from '../connection/socket-manager';\nimport type { Logger } from '../utils/logger';\nimport { EstuaryError, ErrorCode } from '../errors';\n\nexport class WebSocketVoiceManager implements VoiceManager {\n private socketManager: SocketManager;\n private sampleRate: number;\n private logger: Logger;\n private audioContext: AudioContext | null = null;\n private mediaStream: MediaStream | null = null;\n private scriptProcessor: ScriptProcessorNode | null = null;\n private sourceNode: MediaStreamAudioSourceNode | null = null;\n private _isMuted = false;\n private _isSuppressed = false;\n private _isActive = false;\n\n constructor(socketManager: SocketManager, sampleRate: number, logger: Logger) {\n this.socketManager = socketManager;\n this.sampleRate = sampleRate;\n this.logger = logger;\n }\n\n get isMuted(): boolean {\n return this._isMuted;\n }\n\n get isActive(): boolean {\n return this._isActive;\n }\n\n async start(): Promise<void> {\n if (this._isActive) {\n throw new EstuaryError(ErrorCode.VOICE_ALREADY_ACTIVE, 'Voice is already active');\n }\n\n if (typeof AudioContext === 'undefined' && typeof (globalThis as any).webkitAudioContext === 'undefined') {\n throw new EstuaryError(ErrorCode.VOICE_NOT_SUPPORTED, 'AudioContext is not available in this environment');\n }\n\n let stream: MediaStream;\n try {\n stream = await navigator.mediaDevices.getUserMedia({\n audio: {\n sampleRate: this.sampleRate,\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n } catch (err) {\n throw new EstuaryError(\n ErrorCode.MICROPHONE_DENIED,\n 'Microphone access denied',\n err,\n );\n }\n\n this.mediaStream = stream;\n const AudioCtx = globalThis.AudioContext || (globalThis as any).webkitAudioContext;\n this.audioContext = new AudioCtx({ sampleRate: this.sampleRate });\n\n this.sourceNode = this.audioContext.createMediaStreamSource(stream);\n this.scriptProcessor = this.audioContext.createScriptProcessor(4096, 1, 1);\n\n const nativeRate = this.audioContext.sampleRate;\n const targetRate = this.sampleRate;\n\n this.scriptProcessor.onaudioprocess = (event: AudioProcessingEvent) => {\n if (this._isMuted || this._isSuppressed) return;\n\n const inputData = event.inputBuffer.getChannelData(0);\n let pcmFloat: Float32Array;\n\n if (nativeRate !== targetRate) {\n pcmFloat = resample(inputData, nativeRate, targetRate);\n } else {\n pcmFloat = inputData;\n }\n\n const pcm16 = float32ToInt16(pcmFloat);\n const base64 = uint8ArrayToBase64(new Uint8Array(pcm16.buffer));\n\n try {\n this.socketManager.emitEvent('stream_audio', { audio: base64 });\n } catch {\n // Not connected — ignore, will be handled by disconnect logic\n }\n };\n\n this.sourceNode.connect(this.scriptProcessor);\n this.scriptProcessor.connect(this.audioContext.destination);\n\n this._isActive = true;\n this.socketManager.emitEvent('start_voice');\n this.logger.debug('WebSocket voice started');\n }\n\n async stop(): Promise<void> {\n if (!this._isActive) return;\n\n try {\n this.socketManager.emitEvent('stop_voice');\n } catch {\n // May not be connected\n }\n\n this.cleanup();\n this._isActive = false;\n this._isMuted = false;\n this._isSuppressed = false;\n this.logger.debug('WebSocket voice stopped');\n }\n\n toggleMute(): void {\n if (!this._isActive || !this.mediaStream) return;\n this._isMuted = !this._isMuted;\n for (const track of this.mediaStream.getAudioTracks()) {\n track.enabled = !this._isMuted;\n }\n this.logger.debug('Mute toggled:', this._isMuted);\n }\n\n setSuppressed(suppressed: boolean): void {\n this._isSuppressed = suppressed;\n this.logger.debug('Audio suppression:', suppressed ? 'on' : 'off');\n }\n\n dispose(): void {\n this.cleanup();\n this._isActive = false;\n this._isMuted = false;\n this._isSuppressed = false;\n }\n\n private cleanup(): void {\n if (this.scriptProcessor) {\n this.scriptProcessor.onaudioprocess = null;\n this.scriptProcessor.disconnect();\n this.scriptProcessor = null;\n }\n if (this.sourceNode) {\n this.sourceNode.disconnect();\n this.sourceNode = null;\n }\n if (this.mediaStream) {\n for (const track of this.mediaStream.getTracks()) {\n track.stop();\n }\n this.mediaStream = null;\n }\n if (this.audioContext) {\n this.audioContext.close().catch(() => {});\n this.audioContext = null;\n }\n }\n}\n\nfunction resample(input: Float32Array, fromRate: number, toRate: number): Float32Array {\n const ratio = fromRate / toRate;\n const outputLength = Math.round(input.length / ratio);\n const output = new Float32Array(outputLength);\n for (let i = 0; i < outputLength; i++) {\n const srcIndex = i * ratio;\n const low = Math.floor(srcIndex);\n const high = Math.min(low + 1, input.length - 1);\n const frac = srcIndex - low;\n output[i] = input[low] * (1 - frac) + input[high] * frac;\n }\n return output;\n}\n\nfunction float32ToInt16(float32: Float32Array): Int16Array {\n const int16 = new Int16Array(float32.length);\n for (let i = 0; i < float32.length; i++) {\n const clamped = Math.max(-1, Math.min(1, float32[i]));\n int16[i] = clamped < 0 ? clamped * 0x8000 : clamped * 0x7fff;\n }\n return int16;\n}\n\nfunction uint8ArrayToBase64(bytes: Uint8Array): string {\n if (typeof btoa === 'function') {\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary);\n }\n // Node.js fallback\n return Buffer.from(bytes).toString('base64');\n}\n"]}