@m4trix/core 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/hooks/use-conversation/useConversation.ts","../../src/utility/Logger.ts","../../src/react/adapter/VoiceEndpointAdapter.ts","../../src/react/utility/audio/InputAudioController.ts","../../src/react/utility/audio/WebAudioInputAudioController.ts","../../src/react/utility/audio/OutputAudioController.ts","../../src/react/utility/audio/AudioElementOutputAudioController.ts","../../src/react/hooks/use-conversation/useSocketConversation.ts","../../src/react/adapter/socket/VoiceSocketAdapter.ts","../../src/react/adapter/socket/VoiceSocketIOAdapter.ts","../../src/react/utility/audio/WebAudioOutputAudioController.ts"],"names":["useState","useRef","useEffect","useCallback"],"mappings":"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","sourcesContent":["'use client';\n\nimport { useState, useRef, useEffect, useCallback } from 'react';\nimport {\n VoiceEndpointAdapter,\n BaseVoiceEndpointAdapter,\n} from '../../adapter/VoiceEndpointAdapter';\nimport { Logger } from '../../../utility/Logger';\nimport { WebAudioInputAudioController } from '../../utility/audio/WebAudioInputAudioController';\nimport { OutputAudioController } from '../../utility/audio/OutputAudioController';\nimport type {\n BaseUseConversationOptions,\n DownstreamMode,\n VoiceAgentState,\n} from './shared-types';\nimport { AudioElementOutputAudioController } from '../../utility/audio/AudioElementOutputAudioController';\nimport { InputAudioController } from '../../utility/audio/InputAudioController';\n\n// Types\nexport type EndpointConversationOptions<\n T extends Record<string, unknown> = Record<string, unknown>,\n> = BaseUseConversationOptions & {\n downstreamMode?: DownstreamMode;\n endpointConfig?: {\n baseUrl?: string;\n endpointAdapter?: VoiceEndpointAdapter;\n headers?: Record<string, string>;\n };\n requestData?: T;\n overrideInputAudioController?: InputAudioController;\n};\n\nexport interface UseEndpointConversationResult {\n startRecording: () => void;\n stopRecording: () => void;\n enableHandsFreeRecording?: () => void;\n voiceAgentState: VoiceAgentState;\n error: Error | null;\n audioContext: AudioContext | null;\n}\n\nLogger.enableGlobalLogging();\n\n/**\n * A hook for managing voice conversations in React applications using HTTP endpoints and Web Audio API\n */\nexport function useConversation<T extends Record<string, unknown>>(\n endpoint: string,\n {\n onStartRecording,\n onStopRecording,\n onReceive,\n autoPlay = true,\n downstreamMode = 'STREAM',\n onError,\n audioConfig = {},\n requestData = {} as T,\n endpointConfig = {},\n }: EndpointConversationOptions<T>\n): UseEndpointConversationResult {\n // Refs\n const { current: logger } = useRef<Logger>(\n new Logger('@m4trix/core > useConversation')\n );\n const inputAudioControllerRef = useRef<\n WebAudioInputAudioController | undefined\n >(undefined);\n const outputAudioControllerRef = useRef<OutputAudioController | undefined>(\n undefined\n );\n\n const endpointAdapterRef = useRef<VoiceEndpointAdapter | undefined>(\n undefined\n );\n\n // State\n const [voiceAgentState, setVoiceAgentState] =\n useState<VoiceAgentState>('READY');\n const [error, setError] = useState<Error | null>(null);\n\n // ================================================\n // =============== Callbacks ======================\n // ================================================\n\n const handleError = useCallback(\n (state: VoiceAgentState, err: Error) => {\n setError(err);\n logger.error(`Error during ${state}:`, err);\n onError?.(state, err);\n },\n [onError]\n );\n\n const startRecording = useCallback(() => {\n if (inputAudioControllerRef.current) {\n try {\n logger.debug('Starting recording');\n setVoiceAgentState('RECORDING');\n inputAudioControllerRef.current.startRecording({\n onError: (err) => {\n handleError('RECORDING', err);\n },\n });\n onStartRecording?.();\n } catch (err) {\n if (err instanceof Error) {\n handleError('RECORDING', err);\n }\n }\n }\n }, [onStartRecording, handleError]);\n\n const stopRecording = useCallback(async () => {\n if (inputAudioControllerRef.current) {\n try {\n logger.debug('Stopping recording');\n await inputAudioControllerRef.current.stopRecording({\n onRecordingCompleted: async (allData) => {\n setVoiceAgentState('PROCESSING');\n try {\n // Send the recording to the endpoint\n const response = await endpointAdapterRef.current?.sendVoiceFile({\n blob: allData,\n metadata: requestData,\n });\n\n if (!response) {\n throw new Error('No response received from endpoint');\n }\n\n setVoiceAgentState('RESPONDING');\n\n // Play the response\n if (autoPlay) {\n if (downstreamMode === 'STREAM') {\n await outputAudioControllerRef.current?.playAudioStream({\n response,\n onComplete: () => {\n setVoiceAgentState('READY');\n },\n });\n } else if (downstreamMode === 'DOWNLOAD') {\n const responseBlob = await response.blob();\n await outputAudioControllerRef.current?.playAudio({\n source: responseBlob,\n onComplete: () => {\n setVoiceAgentState('READY');\n },\n });\n }\n } else {\n setVoiceAgentState('READY');\n }\n\n // Call onReceive with the recording and response\n onReceive?.(\n allData,\n async () => {\n // Play response function\n if (outputAudioControllerRef.current) {\n if (downstreamMode === 'STREAM') {\n return outputAudioControllerRef.current.playAudioStream({\n response,\n onComplete: () => {\n setVoiceAgentState('READY');\n },\n });\n } else {\n const responseBlob = await response.blob();\n return outputAudioControllerRef.current.playAudio({\n source: responseBlob,\n onComplete: () => {\n setVoiceAgentState('READY');\n },\n });\n }\n }\n },\n async () => {\n // Stop response function\n if (outputAudioControllerRef.current) {\n return outputAudioControllerRef.current.stopPlayback();\n }\n }\n );\n } catch (err) {\n if (err instanceof Error) {\n handleError('PROCESSING', err);\n }\n setVoiceAgentState('READY');\n }\n },\n });\n onStopRecording?.();\n } catch (err) {\n if (err instanceof Error) {\n handleError('RECORDING', err);\n }\n }\n }\n }, [\n onStopRecording,\n requestData,\n autoPlay,\n downstreamMode,\n handleError,\n onReceive,\n ]);\n\n // Setup endpoint adapter and audio controllers\n useEffect(() => {\n if (endpointAdapterRef.current) {\n return;\n }\n\n try {\n // Set up endpoint adapter\n const endpointAdapter = endpointConfig.endpointAdapter\n ? endpointConfig.endpointAdapter\n : new BaseVoiceEndpointAdapter({\n baseUrl: endpointConfig.baseUrl,\n endpoint: endpoint,\n headers: endpointConfig.headers,\n });\n\n endpointAdapterRef.current = endpointAdapter;\n\n // Set up audio controllers\n if (!inputAudioControllerRef.current) {\n inputAudioControllerRef.current = new WebAudioInputAudioController(\n audioConfig\n );\n }\n\n if (!outputAudioControllerRef.current) {\n outputAudioControllerRef.current =\n new AudioElementOutputAudioController();\n }\n } catch (err) {\n if (err instanceof Error) {\n handleError('READY', err);\n }\n }\n }, [endpoint, endpointConfig, audioConfig, handleError]);\n\n // On Mount and on unmount, cleanup the audio controller\n useEffect(() => {\n return () => {\n inputAudioControllerRef.current?.cleanup();\n outputAudioControllerRef.current?.cleanup();\n };\n }, []);\n\n // Return the public API\n return {\n startRecording,\n stopRecording,\n voiceAgentState,\n error,\n audioContext: inputAudioControllerRef.current?.audioContext || null,\n };\n}\n","type LogLevel = 'log' | 'debug' | 'info' | 'warn' | 'error';\n\nexport class Logger {\n private static globalEnabled = false;\n\n constructor(private namespace: string = '') {}\n\n static enableGlobalLogging(): void {\n Logger.globalEnabled = true;\n }\n\n static disableGlobalLogging(): void {\n Logger.globalEnabled = false;\n }\n\n private formatPrefix(): string {\n return this.namespace ? `[${this.namespace}]` : '';\n }\n\n private logIfEnabled(level: LogLevel, ...args: unknown[]): void {\n if (!Logger.globalEnabled) return;\n\n const prefix = this.formatPrefix();\n if (prefix) {\n console[level](prefix, ...args);\n } else {\n console[level](...args);\n }\n }\n\n log(...args: unknown[]): void {\n this.logIfEnabled('log', ...args);\n }\n\n debug(...args: unknown[]): void {\n this.logIfEnabled('debug', ...args);\n }\n\n info(...args: unknown[]): void {\n this.logIfEnabled('info', ...args);\n }\n\n warn(...args: unknown[]): void {\n this.logIfEnabled('warn', ...args);\n }\n\n error(...args: unknown[]): void {\n this.logIfEnabled('error', ...args);\n }\n}\n","import { Logger } from '../../utility/Logger';\n\nexport interface VoiceEndpointConfig {\n baseUrl?: string;\n endpoint: string;\n headers?: Record<string, string>;\n}\n\nexport interface SendVoiceFileParams {\n blob: Blob;\n metadata?: Record<string, unknown>;\n onChunk?: (chunk: Uint8Array) => void;\n onComplete?: (response: Blob) => void;\n}\n\n/**\n * Adapter for sending voice files to an API endpoint\n */\nexport abstract class VoiceEndpointAdapter {\n protected config: VoiceEndpointConfig;\n protected logger = new Logger('SuTr > EndpointAdapter');\n\n constructor(config: VoiceEndpointConfig) {\n this.config = config;\n }\n\n /**\n * Send a voice file to the API endpoint and return a Pump stream of audio chunks\n */\n abstract sendVoiceFile(params: SendVoiceFileParams): Promise<Response>;\n}\n\n/**\n * Adapter for sending voice files to an API endpoint\n */\nexport class BaseVoiceEndpointAdapter extends VoiceEndpointAdapter {\n constructor(config: VoiceEndpointConfig) {\n super(config);\n }\n\n /**\n * Send a voice file to the API endpoint and return a Pump stream of audio chunks\n */\n async sendVoiceFile({\n blob,\n metadata,\n }: SendVoiceFileParams): Promise<Response> {\n const formData = new FormData();\n formData.append('audio', blob);\n if (metadata) {\n formData.append('metadata', JSON.stringify(metadata));\n }\n this.logger.debug('Sending voice file to', this.config.endpoint, formData);\n const response = await fetch(\n `${this.config.baseUrl || ''}${this.config.endpoint}`,\n {\n method: 'POST',\n headers: this.config.headers,\n body: formData,\n }\n );\n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${await response.text()}`);\n }\n if (!response.body) {\n throw new Error('No response body');\n }\n // Return a Pump stream of Uint8Array chunks\n return response;\n }\n}\n","import { Logger } from '../../../utility/Logger';\n\n/**\n * Represents the current state of the voice agent in the conversation flow.\n */\nexport type VoiceAgentState =\n | 'READY'\n | 'RECORDING'\n | 'UPSTREAMING'\n | 'PROCESSING'\n | 'DOWNSTREAMING'\n | 'RESPONDING';\n\n/**\n * Represents the state of the Web Audio API context and nodes.\n */\nexport type AudioContextState = {\n context: AudioContext | null;\n source: MediaStreamAudioSourceNode | null;\n analyser: AnalyserNode | null;\n};\n\n/**\n * Configuration options for audio processing.\n */\nexport type AudioProcessingConfig = {\n sampleRate: number;\n channelCount: number;\n};\n\nexport type StartRecordingCallbacks = {\n onRecordedChunk?: (chunk: Blob) => Promise<void> | void;\n onError?: (error: Error) => Promise<void> | void;\n};\n\nexport type StopRecordingCallbacks = {\n onRecordingCompleted?: (allData: Blob) => Promise<void> | void;\n onError?: (error: Error) => Promise<void> | void;\n};\n\n/**\n * Controller for managing audio input operations.\n * Handles recording from microphone.\n */\nexport abstract class InputAudioController {\n protected logger = new Logger('@m4trix/core > InputAudioController');\n\n constructor() {}\n\n public abstract startRecording({\n onRecordedChunk,\n onError,\n }: StartRecordingCallbacks): Promise<void>;\n\n public abstract stopRecording({\n onRecordingCompleted,\n }: StopRecordingCallbacks): Promise<void>;\n\n /**\n * Cleans up all audio recording resources.\n */\n public abstract cleanup(): void;\n}\n","import {\n AudioProcessingConfig,\n InputAudioController,\n} from './InputAudioController';\nimport { AudioContextState } from './InputAudioController';\n\n/**\n * Represents the current state of the voice agent in the conversation flow.\n */\nexport type VoiceAgentState =\n | 'READY'\n | 'RECORDING'\n | 'UPSTREAMING'\n | 'PROCESSING'\n | 'DOWNSTREAMING'\n | 'RESPONDING';\n\nexport type StartRecordingCallbacks = {\n onRecordedChunk?: (chunk: Blob) => Promise<void> | void;\n onError?: (error: Error) => Promise<void> | void;\n};\n\nexport type StopRecordingCallbacks = {\n onRecordingCompleted?: (allData: Blob) => Promise<void> | void;\n onError?: (error: Error) => Promise<void> | void;\n};\n\nconst DEFAULT_SLICING_INTERVAL = 3_000; // 3 seconds\n\n/**\n * Controller for managing audio input operations.\n * Handles recording from microphone.\n */\nexport class WebAudioInputAudioController extends InputAudioController {\n // ─── Recording state ─────────────────────────────────────────────────────\n private audioContextState: AudioContextState = {\n context: null,\n source: null,\n analyser: null,\n };\n private mediaRecorder: MediaRecorder | null = null;\n private recordedChunks: Blob[] = [];\n private recordingStream: MediaStream | null = null;\n\n constructor(private audioConfig: Partial<AudioProcessingConfig> = {}) {\n super();\n }\n\n public get audioContext(): AudioContext | null {\n return this.audioContextState.context;\n }\n\n private async createAudioContext(): Promise<AudioContextState> {\n const context = new AudioContext({\n sampleRate: this.audioConfig.sampleRate || 16_000,\n latencyHint: 'interactive',\n });\n const analyser = context.createAnalyser();\n analyser.fftSize = 2048;\n return { context, source: null, analyser };\n }\n\n private async cleanupAudioContext(): Promise<void> {\n this.logger.debug('Cleaning up audio context');\n const { source, context } = this.audioContextState;\n if (source) source.disconnect();\n if (context) await context.close();\n this.audioContextState = { context: null, source: null, analyser: null };\n }\n\n public async startRecording({\n onRecordedChunk,\n onError,\n }: StartRecordingCallbacks = {}): Promise<void> {\n try {\n this.logger.debug('Starting recording');\n this.recordedChunks = [];\n\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n this.recordingStream = stream;\n\n if (!this.audioContextState.context) {\n this.audioContextState = await this.createAudioContext();\n }\n\n this.mediaRecorder = new MediaRecorder(stream, {\n mimeType: 'audio/webm;codecs=opus',\n });\n\n this.mediaRecorder.ondataavailable = (e: BlobEvent): void => {\n if (e.data.size > 0) {\n this.recordedChunks.push(e.data);\n onRecordedChunk?.(e.data);\n this.logger.debug('Recorded chunk', e.data.size);\n }\n };\n\n this.mediaRecorder.start(DEFAULT_SLICING_INTERVAL);\n this.logger.debug('MediaRecorder started');\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error('Failed to start recording');\n this.logger.error(error);\n onError?.(error);\n }\n }\n\n public async stopRecording({\n onRecordingCompleted,\n }: StopRecordingCallbacks = {}): Promise<void> {\n this.logger.debug('Stopping recording');\n if (!this.mediaRecorder || this.mediaRecorder.state === 'inactive') return;\n\n await new Promise<void>((resolve) => {\n this.mediaRecorder!.onstop = async (): Promise<void> => {\n if (this.recordedChunks.length) {\n const blob = new Blob(this.recordedChunks, { type: 'audio/webm' });\n onRecordingCompleted?.(blob);\n this.logger.debug('Recording completed', blob.size);\n }\n this.recordingStream?.getTracks().forEach((t) => t.stop());\n this.recordingStream = null;\n await this.cleanupAudioContext();\n resolve();\n };\n this.mediaRecorder!.stop();\n });\n }\n\n /**\n * Cleans up all audio recording resources.\n */\n public cleanup(): void {\n this.cleanupAudioContext();\n if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {\n this.mediaRecorder.stop();\n }\n if (this.recordingStream) {\n this.recordingStream.getTracks().forEach((t) => t.stop());\n this.recordingStream = null;\n }\n }\n}\n","import { Logger } from '../../../utility/Logger';\n\n/**\n * Represents the current state of the voice agent in the conversation flow.\n */\nexport type VoiceAgentState =\n | 'READY'\n | 'RECORDING'\n | 'UPSTREAMING'\n | 'PROCESSING'\n | 'DOWNSTREAMING'\n | 'RESPONDING';\n\nexport type PlayAudioStreamParams = {\n response: Response;\n mimeCodec?: string;\n onComplete?: () => void;\n};\n\nexport type PlayAudioParams = {\n source: Blob | string;\n onComplete?: () => void;\n};\n\nexport type InitializeChunkStreamParams = {\n onComplete?: () => void;\n mimeCodec?: string;\n};\n\n/**\n * Abstract controller for managing audio output operations.\n * Defines the interface for playing back audio responses.\n */\nexport abstract class OutputAudioController {\n protected logger: Logger;\n\n constructor(loggerName: string) {\n this.logger = new Logger(loggerName);\n }\n\n /**\n * Play either a Blob or a URL string.\n */\n public abstract playAudio(params: PlayAudioParams): Promise<void>;\n\n /**\n * Stream audio from a Response.\n */\n public abstract playAudioStream(params: PlayAudioStreamParams): Promise<void>;\n\n /**\n * Initialize a streaming audio context for chunk-based playback.\n */\n public abstract initializeChunkStream(\n params: InitializeChunkStreamParams\n ): Promise<{\n addChunkToStream: (chunk: ArrayBuffer | Blob) => Promise<void>;\n endChunkStream: () => void;\n }>;\n\n /**\n * Stop any ongoing audio playback.\n */\n public abstract stopPlayback(): Promise<void>;\n\n /**\n * Cleans up all audio playback resources.\n */\n public abstract cleanup(): void;\n}\n","import {\n OutputAudioController,\n PlayAudioParams,\n PlayAudioStreamParams,\n InitializeChunkStreamParams,\n} from './OutputAudioController';\n\n/**\n * Web API implementation of the OutputAudioController.\n * Uses Web Audio API and MediaSource Extensions for audio playback.\n */\n\nexport class AudioElementOutputAudioController extends OutputAudioController {\n // ─── Playback state ──────────────────────────────────────────────────────\n private currentHtmlAudio: HTMLAudioElement | null = null;\n private currentAudioUrl: string | null = null;\n\n constructor() {\n super('@m4trix/core > WebApiOutputAudioController');\n }\n\n // ─── One-shot playback ────────────────────────────────────────────────────\n /**\n * Play either a Blob or a URL string.\n * Uses <audio> under the hood for maximum browser compatibility.\n */\n public async playAudio({\n source,\n onComplete,\n }: PlayAudioParams): Promise<void> {\n // Tear down any previous playback\n if (this.currentHtmlAudio) {\n this.currentHtmlAudio.pause();\n this.currentHtmlAudio.src = '';\n if (this.currentAudioUrl && source instanceof Blob) {\n URL.revokeObjectURL(this.currentAudioUrl);\n }\n }\n\n const audio = new Audio();\n this.currentHtmlAudio = audio;\n\n let url: string;\n if (source instanceof Blob) {\n url = URL.createObjectURL(source);\n this.currentAudioUrl = url;\n audio.onended = (): void => {\n URL.revokeObjectURL(url);\n onComplete?.();\n };\n } else {\n url = source;\n }\n\n audio.src = url;\n try {\n await audio.play();\n } catch (err) {\n this.logger.error('Playback failed, user gesture may be required', err);\n // UI can retry via user interaction\n }\n }\n\n // ─── Streaming playback ──────────────────────────────────────────────────\n /**\n * Stream audio from a Response via MediaSource Extensions.\n * @param params.response The fetch Response whose body is an audio stream\n * @param params.mimeCodec MIME type+codec string, e.g. 'audio/mpeg'\n * @param params.onComplete Optional callback once the stream ends\n */\n public async playAudioStream({\n response,\n mimeCodec = 'audio/mpeg',\n onComplete,\n }: PlayAudioStreamParams): Promise<void> {\n // 1) Validation\n if (!response.ok || !response.body) {\n throw new Error(`Invalid response (${response.status})`);\n }\n if (\n typeof MediaSource === 'undefined' ||\n !MediaSource.isTypeSupported(mimeCodec)\n ) {\n throw new Error(`Unsupported MIME type or codec: ${mimeCodec}`);\n }\n\n // 2) Stop any prior playback\n await this.stopPlayback();\n\n // 3) Create MediaSource + <audio>\n const mediaSource = new MediaSource();\n const url = URL.createObjectURL(mediaSource);\n this.currentAudioUrl = url;\n\n const audio = new Audio(url);\n this.currentHtmlAudio = audio;\n audio.autoplay = true;\n audio.onended = (): void => {\n URL.revokeObjectURL(url);\n this.currentAudioUrl = null;\n onComplete?.();\n };\n\n // 4) Pump incoming bytes into the SourceBuffer\n mediaSource.addEventListener(\n 'sourceopen',\n () => {\n const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);\n const reader = response.body!.getReader();\n\n const pump = async (): Promise<void> => {\n const { done, value } = await reader.read();\n if (done) {\n mediaSource.endOfStream();\n return;\n }\n if (value) {\n sourceBuffer.appendBuffer(value);\n }\n if (sourceBuffer.updating) {\n sourceBuffer.addEventListener('updateend', pump, { once: true });\n } else {\n pump();\n }\n };\n\n pump();\n },\n { once: true }\n );\n\n // 5) Kick off playback\n try {\n await audio.play();\n } catch (err) {\n this.logger.error(\n 'Streaming playback failed, user gesture may be required',\n err\n );\n }\n }\n\n // ─── Chunk-based streaming playback ─────────────────────────────────────\n /**\n * Initialize a streaming audio context for chunk-based playback.\n * This creates the necessary MediaSource and SourceBuffer for subsequent chunk additions.\n * Returns functions to add chunks and end the stream, encapsulated in a closure.\n *\n * @param mimeCodec MIME type+codec string, e.g. 'audio/mpeg'\n * @param onComplete Optional callback once the stream ends\n * @returns Object containing functions to add chunks and end the stream\n */\n public async initializeChunkStream({\n onComplete,\n mimeCodec = 'audio/mpeg',\n }: InitializeChunkStreamParams): Promise<{\n addChunkToStream: (chunk: ArrayBuffer | Blob) => Promise<void>;\n endChunkStream: () => void;\n }> {\n this.logger.debug(`Initializing chunk stream with codec: ${mimeCodec}`);\n\n // 1) Check for MediaSource support and codec support\n if (typeof MediaSource === 'undefined') {\n throw new Error('MediaSource API is not supported in this browser');\n }\n\n // Check for codec support before proceeding\n if (!MediaSource.isTypeSupported(mimeCodec)) {\n this.logger.warn(\n `Codec ${mimeCodec} not supported, falling back to standard audio/mpeg`\n );\n mimeCodec = 'audio/mpeg';\n\n // Double check for mpeg support\n if (!MediaSource.isTypeSupported(mimeCodec)) {\n throw new Error(\n 'Neither the specified codec nor the fallback codec are supported'\n );\n }\n }\n\n // 2) Stop any prior playback\n await this.stopPlayback();\n\n // 3) Create MediaSource + <audio>\n const mediaSource = new MediaSource();\n let sourceBuffer: SourceBuffer | null = null;\n\n const url = URL.createObjectURL(mediaSource);\n this.currentAudioUrl = url;\n\n const audio = new Audio(url);\n this.currentHtmlAudio = audio;\n\n // Prepare audio element\n audio.autoplay = false;\n\n // Enable audio element for debugging\n audio.controls = true; // Make controls visible for debugging\n audio.style.display = 'none'; // Hide element but keep it active\n document.body.appendChild(audio); // Attach to DOM for better browser support\n\n // Track playback state\n let playbackStarted = false;\n let hasReceivedFirstChunk = false;\n let receivedChunksCount = 0;\n\n // Create a queue to handle chunks while buffer is updating\n const pendingChunks: ArrayBuffer[] = [];\n let isProcessingQueue = false;\n\n // 4) Wait for MediaSource to be ready\n this.logger.debug('Waiting for MediaSource to open...');\n await new Promise<void>((resolve, reject) => {\n // Set timeout for MediaSource open\n const timeout = setTimeout(() => {\n reject(new Error('MediaSource failed to open (timeout)'));\n }, 5000);\n\n mediaSource.addEventListener(\n 'sourceopen',\n () => {\n clearTimeout(timeout);\n this.logger.debug('MediaSource open event received');\n\n try {\n sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);\n // Increase buffer size for smoother playback\n if (\n mediaSource.duration === Infinity ||\n isNaN(mediaSource.duration)\n ) {\n mediaSource.duration = 1000; // Set a large duration to allow for more buffering\n }\n this.logger.debug('SourceBuffer created successfully');\n resolve();\n } catch (err) {\n reject(new Error(`Failed to create SourceBuffer: ${err}`));\n }\n },\n { once: true }\n );\n });\n\n const logger = this.logger;\n\n // Process the queue of pending chunks\n const processQueue = async (): Promise<void> => {\n if (!sourceBuffer || pendingChunks.length === 0 || isProcessingQueue) {\n return;\n }\n\n isProcessingQueue = true;\n\n try {\n while (pendingChunks.length > 0) {\n if (sourceBuffer.updating) {\n // Wait for the current update to complete before processing more chunks\n await new Promise<void>((resolve) => {\n sourceBuffer!.addEventListener('updateend', () => resolve(), {\n once: true,\n });\n });\n }\n\n // Get the next chunk from the queue\n const nextChunk = pendingChunks.shift();\n if (!nextChunk) continue;\n\n try {\n sourceBuffer.appendBuffer(nextChunk);\n logger.debug(\n `Processed queued chunk of size ${nextChunk.byteLength}`\n );\n\n // Start playback on first successful append if not started yet\n if (!playbackStarted && hasReceivedFirstChunk) {\n await tryStartPlayback();\n }\n\n // Wait for this append to complete\n await new Promise<void>((resolve) => {\n sourceBuffer!.addEventListener('updateend', () => resolve(), {\n once: true,\n });\n });\n } catch (err) {\n logger.error('Error appending queued chunk to source buffer', err);\n // Continue processing the queue despite errors\n }\n }\n } finally {\n isProcessingQueue = false;\n }\n };\n\n // Try to start audio playback with proper error handling\n const tryStartPlayback = async (): Promise<void> => {\n if (playbackStarted) return;\n\n playbackStarted = true;\n logger.debug('Attempting to start audio playback...');\n\n // Ensure we have enough data before playing\n // (wait for at least 3 chunks or enough buffered time)\n if (\n receivedChunksCount < 3 &&\n audio.buffered.length > 0 &&\n audio.buffered.end(0) < 0.5\n ) {\n logger.debug('Not enough data buffered yet, delaying playback');\n return;\n }\n\n try {\n // Ensure audio element is ready\n if (audio.readyState === 0) {\n logger.debug(\n 'Audio element not ready yet, waiting for canplay event'\n );\n await new Promise<void>((resolve) => {\n audio.addEventListener('canplay', () => resolve(), { once: true });\n });\n }\n\n await audio.play();\n logger.debug('Successfully started audio playback');\n } catch (err) {\n logger.error('Failed to start playback', err);\n\n // Try again with user interaction simulation\n document.addEventListener(\n 'click',\n async () => {\n try {\n await audio.play();\n logger.debug('Started playback after user interaction');\n } catch (innerErr) {\n logger.error(\n 'Still failed to play after user interaction',\n innerErr\n );\n }\n },\n { once: true }\n );\n }\n };\n\n // Define function for adding chunks\n const addChunkToStream = async (\n chunk: ArrayBuffer | Blob\n ): Promise<void> => {\n if (!sourceBuffer) {\n throw new Error(\n 'Streaming context was closed or not properly initialized.'\n );\n }\n\n // Convert Blob to ArrayBuffer if needed\n let arrayBufferChunk: ArrayBuffer;\n if (chunk instanceof Blob) {\n logger.debug('Converting Blob to ArrayBuffer');\n arrayBufferChunk = await chunk.arrayBuffer();\n } else {\n arrayBufferChunk = chunk;\n }\n\n // Skip empty chunks\n if (!arrayBufferChunk || arrayBufferChunk.byteLength === 0) {\n logger.warn('Received empty chunk, skipping');\n return;\n }\n\n // Log first chunk received\n if (!hasReceivedFirstChunk) {\n hasReceivedFirstChunk = true;\n logger.debug(\n `First chunk received, size: ${arrayBufferChunk.byteLength} bytes`\n );\n }\n\n receivedChunksCount++;\n\n // Add the chunk to the queue\n pendingChunks.push(arrayBufferChunk);\n logger.debug(\n `Added chunk #${receivedChunksCount} to queue (size: ${arrayBufferChunk.byteLength} bytes)`\n );\n\n // Start processing the queue if not already processing\n await processQueue();\n\n // Try to start playback if we have enough data (and not started yet)\n if (\n !playbackStarted &&\n hasReceivedFirstChunk &&\n receivedChunksCount >= 3\n ) {\n await tryStartPlayback();\n }\n };\n\n // Define function for ending the stream\n const endChunkStream = (): void => {\n if (mediaSource && mediaSource.readyState === 'open') {\n try {\n // Wait for any pending chunks to be processed\n if (\n pendingChunks.length > 0 ||\n (sourceBuffer && sourceBuffer.updating)\n ) {\n logger.debug('Waiting for pending chunks before ending stream');\n setTimeout(() => endChunkStream(), 200);\n return;\n }\n\n if (hasReceivedFirstChunk) {\n mediaSource.endOfStream();\n logger.debug('MediaSource stream ended successfully');\n } else {\n logger.warn('Stream ended without receiving any chunks');\n }\n } catch (err) {\n logger.error('Error ending MediaSource stream', err);\n }\n }\n\n // Clean up audio element and URL\n audio.onended = null;\n\n // Remove from DOM if we added it\n if (audio.parentNode) {\n audio.parentNode.removeChild(audio);\n }\n\n if (this.currentAudioUrl === url) {\n this.currentAudioUrl = null;\n URL.revokeObjectURL(url);\n }\n\n // Reset references to allow garbage collection\n sourceBuffer = null;\n };\n\n // Set up completion handler\n audio.onended = (): void => {\n logger.debug('Audio playback completed');\n endChunkStream();\n onComplete?.();\n };\n\n return {\n addChunkToStream,\n endChunkStream,\n };\n }\n\n /**\n * Stop any ongoing HTMLAudioElement playback.\n */\n public async stopPlayback(): Promise<void> {\n if (this.currentHtmlAudio) {\n try {\n this.currentHtmlAudio.pause();\n this.currentHtmlAudio.src = '';\n } catch (err) {\n this.logger.error('Error stopping playback', err);\n }\n this.currentHtmlAudio = null;\n }\n if (this.currentAudioUrl) {\n URL.revokeObjectURL(this.currentAudioUrl);\n this.currentAudioUrl = null;\n }\n }\n\n /**\n * Cleans up all audio playback resources.\n */\n public cleanup(): void {\n this.stopPlayback();\n }\n}\n","'use client';\n// Voice conversation hook for handling audio recording and playback via socket connection\nimport { useState, useRef, useEffect, useCallback } from 'react';\nimport { VoiceSocketAdapter } from '../../adapter/socket/VoiceSocketAdapter';\nimport { VoiceSocketIOAdapter } from '../../adapter/socket/VoiceSocketIOAdapter';\nimport { Logger } from '../../../utility/Logger';\nimport { WebAudioInputAudioController } from '../../utility/audio/WebAudioInputAudioController';\nimport { OutputAudioController } from '../../utility/audio/OutputAudioController';\nimport { WebAudioOutputAudioController } from '../../utility/audio/WebAudioOutputAudioController';\nimport type {\n BaseUseConversationOptions,\n DownstreamMode,\n UpstreamMode,\n VoiceAgentState,\n} from './shared-types';\n\nexport type SocketConversationOptions<\n T extends Record<string, unknown> = Record<string, unknown>,\n> = BaseUseConversationOptions & {\n scope?: string;\n downstreamMode?: DownstreamMode;\n upstreamMode?: UpstreamMode;\n handsFreeEnginePlugin?: unknown[];\n socketConfig?: {\n baseUrl?: string;\n socketAdapter?: VoiceSocketAdapter;\n headers?: Record<string, string>;\n };\n requestData?: T;\n};\n\nexport interface UseSocketConversationResult {\n startRecording: () => void;\n stopRecording: () => void;\n enableHandsFreeRecording?: () => void;\n voiceAgentState: VoiceAgentState;\n error: Error | null;\n audioContext: AudioContext | null;\n socket: unknown | null;\n}\n\nLogger.enableGlobalLogging();\n\n/**\n * A hook for managing voice conversations in React applications using WebSockets and Web Audio API\n */\nexport function useSocketConversation<T extends Record<string, unknown>>({\n scope,\n onStartRecording,\n onStopRecording,\n onReceive,\n upstreamMode = 'STREAM_WHILE_TALK',\n onError,\n audioConfig = {},\n socketConfig = {},\n}: SocketConversationOptions<T>): UseSocketConversationResult {\n // Refs\n const { current: logger } = useRef<Logger>(\n new Logger('SuTr > useSocketConversation')\n );\n const inputAudioControllerRef = useRef<\n WebAudioInputAudioController | undefined\n >(undefined);\n\n const outputAudioControllerRef = useRef<OutputAudioController | undefined>(\n undefined\n );\n\n const socketAdapterRef = useRef<VoiceSocketAdapter | undefined>(undefined);\n const [socket, setSocket] = useState<unknown | null>(null);\n\n // State\n const [voiceAgentState, setVoiceAgentState] =\n useState<VoiceAgentState>('READY');\n const [error, setError] = useState<Error | null>(null);\n\n // ================================================\n // =============== Inferred Config ================\n // ================================================\n\n const shouldStreamWhileTalk = upstreamMode === 'STREAM_WHILE_TALK';\n\n // ================================================\n // =============== Callbacks ======================\n // ================================================\n\n const handleError = useCallback(\n (state: VoiceAgentState, err: Error) => {\n setError(err);\n logger.error(`Error during ${state}:`, err);\n onError?.(state, err);\n },\n [onError]\n );\n\n const subscribeToSocketEventsForChunkDownstreaming = useCallback(\n async (socketAdapter: VoiceSocketAdapter) => {\n logger.debug('Setting up audio stream for receiving chunks');\n\n try {\n const { addChunkToStream, endChunkStream } =\n await outputAudioControllerRef.current!.initializeChunkStream({\n mimeCodec: 'audio/mpeg',\n onComplete: () => {\n logger.debug('Audio stream playback completed');\n setVoiceAgentState('READY');\n },\n });\n\n // Keep track of received chunks for debugging\n let chunkCount = 0;\n\n const chunkReceivedEmitter = async (chunk: unknown): Promise<void> => {\n if (chunk instanceof ArrayBuffer) {\n chunkCount++;\n logger.debug(\n `Received voice chunk #${chunkCount} from socket, size: ${chunk.byteLength} bytes`\n );\n\n if (!chunk || chunk.byteLength === 0) {\n logger.warn('Received empty chunk, skipping');\n return;\n }\n\n try {\n await addChunkToStream(chunk);\n logger.debug(\n `Successfully added chunk #${chunkCount} to audio stream`\n );\n } catch (err) {\n logger.error(\n `Failed to add chunk #${chunkCount} to audio stream`,\n err\n );\n if (err instanceof Error) {\n handleError('DOWNSTREAMING', err);\n }\n }\n }\n };\n\n socketAdapter.on('chunk-received', chunkReceivedEmitter);\n\n const endOfStreamEmitter = (): void => {\n logger.debug(\n `Received end of stream signal after ${chunkCount} chunks, ending chunk stream`\n );\n endChunkStream();\n setVoiceAgentState('READY');\n };\n\n socketAdapter.on('received-end-of-response-stream', endOfStreamEmitter);\n\n // Returning a cleanup callback to remove the event listeners\n return () => {\n logger.debug('Cleaning up socket event listeners');\n socketAdapter.off('chunk-received', chunkReceivedEmitter);\n socketAdapter.off(\n 'received-end-of-response-stream',\n endOfStreamEmitter\n );\n endChunkStream();\n };\n } catch (err) {\n if (err instanceof Error) {\n handleError('DOWNSTREAMING', err);\n }\n return () => {};\n }\n },\n [handleError]\n );\n\n const hookupSocketAdapter = useCallback(\n async (socketAdapter: VoiceSocketAdapter) => {\n logger.debug('Connecting to socket...');\n\n try {\n await socketAdapter.connect();\n\n socketAdapter.on('connect', () => {\n logger.debug('Socket adapter connected');\n setVoiceAgentState('READY');\n });\n\n socketAdapter.on('disconnect', () => {\n logger.debug('Socket adapter disconnected');\n });\n\n socketAdapter.on('error', (err: unknown) => {\n if (err instanceof Error) {\n handleError(voiceAgentState, err);\n } else {\n handleError(voiceAgentState, new Error('Unknown error'));\n }\n });\n\n setSocket(socketAdapter.exposeSocket<unknown>());\n } catch (err) {\n if (err instanceof Error) {\n handleError('READY', err);\n }\n }\n },\n [handleError, voiceAgentState]\n );\n\n const startRecording = useCallback(() => {\n if (inputAudioControllerRef.current) {\n try {\n logger.debug('Starting recording');\n setVoiceAgentState('RECORDING');\n inputAudioControllerRef.current.startRecording({\n onRecordedChunk: async (chunk) => {\n if (shouldStreamWhileTalk) {\n try {\n // We do not set UPSTREAMING since we are recording while talking\n await socketAdapterRef.current?.sendVoiceChunk(chunk);\n } catch (err) {\n if (err instanceof Error) {\n handleError('RECORDING', err);\n }\n }\n }\n },\n });\n onStartRecording?.();\n } catch (err) {\n if (err instanceof Error) {\n handleError('RECORDING', err);\n }\n }\n }\n }, [onStartRecording, shouldStreamWhileTalk, handleError]);\n\n const stopRecording = useCallback(async () => {\n if (inputAudioControllerRef.current) {\n try {\n logger.debug('Stopping recording');\n await inputAudioControllerRef.current.stopRecording({\n onRecordingCompleted: async (allData) => {\n setVoiceAgentState('PROCESSING');\n try {\n if (shouldStreamWhileTalk) {\n logger.debug('Committing voice message');\n await socketAdapterRef.current?.commitVoiceMessage();\n } else {\n await socketAdapterRef.current?.sendVoiceFile(allData);\n }\n\n setVoiceAgentState('DOWNSTREAMING');\n await subscribeToSocketEventsForChunkDownstreaming(\n socketAdapterRef.current!\n );\n\n // Handle receiving the audio response\n onReceive?.(\n allData,\n async () => {\n // Play response function\n if (outputAudioControllerRef.current) {\n // Use stopPlayback because it should also have logic to resume or start\n return outputAudioControllerRef.current.stopPlayback();\n }\n },\n async () => {\n // Stop response function\n if (outputAudioControllerRef.current) {\n return outputAudioControllerRef.current.stopPlayback();\n }\n }\n );\n\n // Event listeners are cleaned up automatically\n } catch (err) {\n if (err instanceof Error) {\n handleError('PROCESSING', err);\n }\n }\n },\n });\n onStopRecording?.();\n } catch (err) {\n if (err instanceof Error) {\n handleError('RECORDING', err);\n }\n }\n }\n }, [\n onStopRecording,\n handleError,\n subscribeToSocketEventsForChunkDownstreaming,\n onReceive,\n ]);\n\n // Setup Socket Adapter and AudioControllers\n useEffect(() => {\n if (socketAdapterRef.current) {\n return;\n }\n\n try {\n // Set up socket adapter\n const socketAdapter = socketConfig.socketAdapter\n ? socketConfig.socketAdapter\n : new VoiceSocketIOAdapter({\n scope,\n baseUrl: socketConfig.baseUrl || '',\n headers: socketConfig.headers,\n });\n\n socketAdapterRef.current = socketAdapter;\n\n if (!socketAdapter.isConnected()) {\n hookupSocketAdapter(socketAdapter);\n }\n\n // Set up audio controllers\n if (!inputAudioControllerRef.current) {\n inputAudioControllerRef.current = new WebAudioInputAudioController(\n audioConfig\n );\n }\n\n if (!outputAudioControllerRef.current) {\n outputAudioControllerRef.current = new WebAudioOutputAudioController();\n }\n } catch (err) {\n if (err instanceof Error) {\n handleError('READY', err);\n }\n }\n }, [scope, socketConfig, hookupSocketAdapter, audioConfig, handleError]);\n\n // On Mount and on unmount, cleanup the audio controller\n useEffect(() => {\n return () => {\n inputAudioControllerRef.current?.cleanup();\n outputAudioControllerRef.current?.cleanup();\n if (socketAdapterRef.current) {\n socketAdapterRef.current.disconnect();\n socketAdapterRef.current = undefined;\n }\n };\n }, []);\n\n // Return the public API\n return {\n startRecording,\n stopRecording,\n voiceAgentState,\n error,\n audioContext: inputAudioControllerRef.current?.audioContext || null,\n socket,\n };\n}\n","import { Logger } from '../../../utility/Logger';\n\nexport interface VoiceSocketConfig {\n scope?: string;\n baseUrl: string;\n protocols?: string | string[];\n headers?: Record<string, string>;\n autoReconnect?: boolean;\n}\n\nexport interface VoiceSocketMessage {\n type: 'chunk' | 'file';\n data: string; // base64 encoded\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Base class for voice socket adapters that handles voice data transmission.\n *\n * Emits:\n * - \"connect\"\n * - \"disconnect\"\n * - \"error\" (with Error)\n * - \"chunk-received\" (ArrayBuffer)\n * - \"received-end-of-response-stream\"\n * - \"chunk-sent\" (ArrayBuffer | Blob)\n * - \"file-received\" (Blob)\n * - \"file-sent\" (Blob)\n * - \"control-message\" (object)\n */\nexport abstract class VoiceSocketAdapter {\n protected config: VoiceSocketConfig;\n protected _isConnected = false;\n protected logger = new Logger('@m4trix/core > VoiceSocketAdapter');\n protected emitter = new Emitter();\n\n constructor(config: VoiceSocketConfig) {\n this.config = config;\n }\n\n on(event: string, listener: (data?: unknown) => void): void {\n this.emitter.on(event, listener);\n }\n\n off(event: string, listener: (data?: unknown) => void): void {\n this.emitter.off(event, listener);\n }\n\n once(event: string, listener: (data?: unknown) => void): void {\n this.emitter.once(event, listener);\n }\n\n protected emit(event: string, data?: unknown): void {\n this.emitter.emit(event, data);\n }\n\n isConnected(): boolean {\n return this._isConnected;\n }\n\n abstract connect(): Promise<void>;\n abstract disconnect(): void;\n abstract exposeSocket<T>(): T | null;\n\n abstract sendVoiceChunk(\n chunk: ArrayBuffer | Blob,\n metadata?: Record<string, unknown>\n ): Promise<void>;\n\n abstract commitVoiceMessage(): void;\n\n abstract sendVoiceFile(blob: Blob, metadata?: Record<string, unknown>): void;\n\n protected abstract onVoiceChunkReceived(chunk: ArrayBuffer): void;\n protected abstract onReceivedEndOfResponseStream(): void;\n protected abstract onVoiceFileReceived(blob: Blob): void;\n}\ntype Listener<T> = (event: T) => void;\n\nexport class Emitter {\n private target = new EventTarget();\n\n on<T>(type: string, listener: Listener<T>): void {\n this.target.addEventListener(type, listener as EventListener);\n }\n\n off<T>(type: string, listener: Listener<T>): void {\n this.target.removeEventListener(type, listener as EventListener);\n }\n\n once<T>(type: string, listener: Listener<T>): void {\n const wrapper = (event: Event): void => {\n this.off(type, wrapper);\n listener((event as CustomEvent).detail);\n };\n this.on(type, wrapper);\n }\n\n emit<T>(type: string, detail?: T): void {\n this.target.dispatchEvent(new CustomEvent(type, { detail }));\n }\n}\n","import { VoiceSocketAdapter, VoiceSocketConfig } from './VoiceSocketAdapter';\nimport { Socket, io } from 'socket.io-client';\n\n// Define an interface that extends the VoiceSocketAdapter with Socket.IO specific properties\nexport class VoiceSocketIOAdapter extends VoiceSocketAdapter {\n protected socket: Socket | null = null;\n\n constructor(config: VoiceSocketConfig) {\n super(config);\n }\n\n async connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (!this.socket) {\n this.socket = io(this.config.baseUrl, {\n extraHeaders: this.config.headers,\n autoConnect: true,\n });\n }\n\n this.socket.on('connect', () => {\n this._isConnected = true;\n this.logger.debug('Connected to socket');\n this.emit('connect');\n resolve();\n });\n\n this.socket.on('disconnect', () => {\n this._isConnected = false;\n this.emit('disconnect');\n this.logger.debug('Disconnected from socket');\n if (this.config.autoReconnect) this.connect(); // reconnect if configured\n });\n\n this.socket.on('connect_error', (error) => {\n this.logger.error('Error connecting to socket', error);\n this.emit('error', error);\n reject(error);\n });\n\n this.socket.on('voice:chunk_received', (chunk: ArrayBuffer) => {\n this.logger.debug('Received voice chunk', chunk.byteLength);\n this.onVoiceChunkReceived(chunk);\n });\n\n this.socket.on('voice:received_end_of_response_stream', () => {\n this.logger.debug('Received end of response stream');\n this.onReceivedEndOfResponseStream();\n });\n\n this.socket.on('voice:file_received', (blob: Blob) => {\n this.logger.debug('Received voice file');\n this.onVoiceFileReceived(blob);\n });\n\n // TODO: remove?\n this.socket.on('control-message', (message: Record<string, unknown>) => {\n this.logger.debug('Received control message', message);\n this.emit('control-message', message);\n });\n });\n }\n\n disconnect(): void {\n this.socket?.disconnect();\n this.socket = null;\n this._isConnected = false;\n }\n\n exposeSocket<T>(): T | null {\n return this.socket as T | null;\n }\n\n async sendVoiceChunk(\n chunk: ArrayBuffer | Blob,\n metadata?: Record<string, unknown>\n ): Promise<void> {\n this.logger.debug(\n 'Sending voice chunk %i',\n chunk instanceof Blob ? chunk.size : chunk.byteLength\n );\n if (!this.socket || !this.isConnected)\n throw new Error('Socket not connected');\n\n let chunkToSend: ArrayBuffer;\n if (chunk instanceof Blob) {\n chunkToSend = await chunk.arrayBuffer();\n } else {\n chunkToSend = chunk;\n }\n\n this.logger.debug('[Socket] Sending voice chunk', chunkToSend.byteLength);\n\n this.socket.emit('voice:send_chunk', chunkToSend, metadata);\n this.emit('chunk_sent', chunk);\n }\n\n sendVoiceFile(blob: Blob, metadata?: Record<string, unknown>): void {\n this.logger.debug('Sending voice file', blob, metadata);\n if (!this.socket || !this.isConnected)\n throw new Error('Socket not connected');\n\n this.socket.emit('voice:send_file', blob, metadata);\n this.emit('file-sent', blob);\n }\n\n commitVoiceMessage(): void {\n if (!this.socket || !this.isConnected)\n throw new Error('Socket not connected');\n\n this.socket.emit('voice:commit');\n }\n\n protected onVoiceChunkReceived(chunk: ArrayBuffer): void {\n this.emit('chunk-received', chunk);\n }\n\n protected onVoiceFileReceived(blob: Blob): void {\n this.emit('file-received', blob);\n }\n\n protected onReceivedEndOfResponseStream(): void {\n this.emit('received-end-of-response-stream');\n }\n}\n","/*\n * Web Audio API implementation of the OutputAudioController interface.\n * ---------------------------------------------------------------------\n * **PCM streaming variant – byte‑accurate**\n * Handles raw 16‑bit little‑endian PCM from ElevenLabs (`pcm_24000`). The\n * byte‑level buffer realigns odd‑sized packets before converting to\n * `AudioBuffer`s, eliminating white‑noise artefacts. Implements all methods\n * required by `OutputAudioController`.\n */\n\nimport {\n OutputAudioController,\n PlayAudioParams,\n InitializeChunkStreamParams,\n} from './OutputAudioController';\n\n// ─── PCM constants ─────────────────────────────────────────────────────\nconst STREAM_SAMPLE_RATE = 24_000; // match ElevenLabs request\nconst CHANNELS = 1; // mono\nconst SLICE_DURATION_S = 0.25; // schedule 250ms\nconst FRAMES_PER_SLICE = Math.floor(STREAM_SAMPLE_RATE * SLICE_DURATION_S);\nconst BYTES_PER_SLICE = FRAMES_PER_SLICE * 2; // 16‑bit → 2B per frame\n\n/** Scheduler jitter window (seconds). */\nconst SCHED_TOLERANCE = 0.05;\n\nexport class WebAudioOutputAudioController extends OutputAudioController {\n private readonly audioCtx = new AudioContext();\n private readonly gain = this.audioCtx.createGain();\n\n private nextPlayTime = 0;\n private activeSources = new Set<AudioBufferSourceNode>();\n private userGestureHookAttached = false;\n\n constructor() {\n super('@m4trix/core > WebAudioOutputAudioController');\n this.gain.connect(this.audioCtx.destination);\n this.resetScheduler();\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // One‑shot playback\n // ─────────────────────────────────────────────────────────────────────\n public async playAudio({\n source,\n onComplete,\n }: PlayAudioParams): Promise<void> {\n await this.stopPlayback();\n const buf = await this.sourceToArrayBuffer(source);\n const decoded = await this.decode(buf);\n await this.ensureContextRunning();\n const src = this.createSource(decoded, this.audioCtx.currentTime);\n src.onended = (): void => {\n this.activeSources.delete(src);\n onComplete?.();\n };\n }\n\n public async playAudioStream(): Promise<void> {\n /* reserved for future MSE path */\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // PCM streaming\n // ─────────────────────────────────────────────────────────────────────\n public async initializeChunkStream({\n onComplete,\n }: InitializeChunkStreamParams): Promise<{\n addChunkToStream: (chunk: ArrayBuffer | Blob) => Promise<void>;\n endChunkStream: () => void;\n }> {\n await this.stopPlayback();\n await this.ensureContextRunning();\n this.resetScheduler();\n\n let streamEnded = false;\n let pending = new Uint8Array(0); // may hold an odd byte\n\n const addChunkToStream = async (pkt: ArrayBuffer | Blob): Promise<void> => {\n if (streamEnded) {\n this.logger.warn('Attempt to add chunk after stream ended – ignoring.');\n return;\n }\n const bytes = new Uint8Array(\n pkt instanceof Blob ? await pkt.arrayBuffer() : pkt\n );\n if (bytes.length === 0) return;\n\n const merged = new Uint8Array(pending.length + bytes.length);\n merged.set(pending);\n merged.set(bytes, pending.length);\n pending = merged;\n\n if (pending.length % 2 === 1) return; // keep lone byte for next packet\n\n while (pending.length >= BYTES_PER_SLICE) {\n const sliceBytes = pending.slice(0, BYTES_PER_SLICE);\n pending = pending.slice(BYTES_PER_SLICE);\n\n // copy into an aligned buffer to avoid RangeError when the\n // underlying byteOffset is odd\n const aligned = sliceBytes.buffer.slice(\n sliceBytes.byteOffset,\n sliceBytes.byteOffset + sliceBytes.byteLength\n );\n const int16 = new Int16Array(aligned);\n const buf = this.audioCtx.createBuffer(\n CHANNELS,\n int16.length,\n STREAM_SAMPLE_RATE\n );\n const data = buf.getChannelData(0);\n for (let i = 0; i < int16.length; i++) data[i] = int16[i] / 32768;\n this.scheduleBuffer(buf);\n }\n };\n\n const endChunkStream = (): void => {\n if (streamEnded) return;\n streamEnded = true;\n if (onComplete) {\n if (this.activeSources.size === 0) onComplete();\n else {\n const last = Array.from(this.activeSources).pop();\n if (last) {\n const prev = last.onended;\n last.onended = (e): void => {\n if (prev) prev.call(last, e);\n onComplete();\n };\n }\n }\n }\n };\n\n return { addChunkToStream, endChunkStream };\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Buffer scheduling helpers\n // ─────────────────────────────────────────────────────────────────────\n private scheduleBuffer(buf: AudioBuffer): void {\n if (this.nextPlayTime < this.audioCtx.currentTime + SCHED_TOLERANCE) {\n this.nextPlayTime = this.audioCtx.currentTime + SCHED_TOLERANCE;\n }\n this.createSource(buf, this.nextPlayTime);\n this.nextPlayTime += buf.duration;\n }\n\n private createSource(buf: AudioBuffer, when: number): AudioBufferSourceNode {\n const src = this.audioCtx.createBufferSource();\n src.buffer = buf;\n src.connect(this.gain);\n src.start(when);\n this.activeSources.add(src);\n src.onended = (): void => {\n this.activeSources.delete(src);\n };\n return src;\n }\n\n private resetScheduler(): void {\n this.nextPlayTime = this.audioCtx.currentTime;\n }\n\n // ─── External resource helpers ───────────────────────────────────────\n private sourceToArrayBuffer(src: Blob | string): Promise<ArrayBuffer> {\n return typeof src === 'string'\n ? fetch(src).then((r) => {\n if (!r.ok) throw new Error(`${r.status}`);\n return r.arrayBuffer();\n })\n : src.arrayBuffer();\n }\n\n private decode(buf: ArrayBuffer): Promise<AudioBuffer> {\n return new Promise((res, rej) =>\n this.audioCtx.decodeAudioData(buf, res, rej)\n );\n }\n\n // ─── Lifecycle methods ───────────────────────────────────────────────\n public async stopPlayback(): Promise<void> {\n for (const src of this.activeSources) {\n try {\n src.stop();\n } catch {\n /* ignore */\n }\n src.disconnect();\n }\n this.activeSources.clear();\n this.resetScheduler();\n }\n\n public cleanup(): void {\n this.stopPlayback();\n if (this.audioCtx.state !== 'closed') this.audioCtx.close();\n }\n\n // ─── Autoplay‑policy helper ──────────────────────────────────────────\n private async ensureContextRunning(): Promise<void> {\n if (this.audioCtx.state !== 'suspended') return;\n\n try {\n await this.audioCtx.resume();\n } catch {\n /* ignore */\n }\n if ((this.audioCtx.state as string) === 'running') return;\n\n if (!this.userGestureHookAttached) {\n this.userGestureHookAttached = true;\n const resume = async (): Promise<void> => {\n try {\n await this.audioCtx.resume();\n } catch {\n /* ignore */\n }\n if (this.audioCtx.state === 'running')\n document.removeEventListener('click', resume);\n };\n document.addEventListener('click', resume);\n }\n }\n}\n"]}