diff --git a/@applications/web/src/features/voice/VoiceSession.ts b/@applications/web/src/features/voice/VoiceSession.ts index dc776af..6d8b0e1 100644 --- a/@applications/web/src/features/voice/VoiceSession.ts +++ b/@applications/web/src/features/voice/VoiceSession.ts @@ -36,6 +36,7 @@ export interface VoiceSessionCallbacks { onTtsEnd: (event: TtsEndEvent) => void; onListening: () => void; onStateChange: (state: VoiceSessionState) => void; + onError: (message: string) => void; } export class VoiceSession { @@ -47,6 +48,7 @@ export class VoiceSession { constructor( private readonly apiBaseUrl: string, + private readonly socketBaseUrl: string, private readonly sessionId: string, private readonly callbacks: VoiceSessionCallbacks, ) {} @@ -73,7 +75,7 @@ export class VoiceSession { this._setState('connecting'); this.client = new VoiceClient({ - baseUrl: this.apiBaseUrl, + baseUrl: this.socketBaseUrl, sessionId: this.sessionId, reconnect: true, maxReconnectAttempts: 5, @@ -87,8 +89,9 @@ export class VoiceSession { this._setState('disconnected'); }, - onError: (_event) => { + onError: () => { this._setState('error'); + this.callbacks.onError('Voice connection failed. Tap the mic to reconnect.'); }, onEvent: (event: VoiceEvent) => { @@ -127,10 +130,12 @@ export class VoiceSession { sendAudioFrame(frame: ArrayBuffer): void { if (!this.client?.isConnected()) return; - // Decode the worklet's output to extract the PCM Int16Array + // Decode the worklet's output to extract the PCM Int16Array. + // Int16Array requires 2-byte-aligned offsets; byte 5 is not aligned, + // so slice from byte 5 to get a new, zero-offset ArrayBuffer. const view = new DataView(frame); const seq = view.getUint32(1, false); - const pcm = new Int16Array(frame, 5, 960); + const pcm = new Int16Array(frame.slice(5)); this.client.sendAudio({ seq, pcm }); this.micSeq = seq; @@ -177,8 +182,9 @@ export class VoiceSession { break; } case 'error': { - // Errors are non-fatal — surface to caller via state change this._setState('error'); + const errorEvent = event as VoiceEvent & { message?: string }; + this.callbacks.onError(errorEvent.message ?? 'Connection error'); break; } }