From 8a89d23e1b15259a7ecd03ff1fddfdfcd8095f27 Mon Sep 17 00:00:00 2001 From: weishu Date: Mon, 19 Jan 2026 19:14:46 +0800 Subject: [PATCH] fix: properly clean up voice session resources on disconnect - Add resetRealtimeSessionState() function to reset session state - Store and stop media stream from permission request to prevent resource leak - Call reset function when voice session disconnects --- web/src/realtime/RealtimeSession.ts | 5 +++++ web/src/realtime/RealtimeVoiceSession.tsx | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/web/src/realtime/RealtimeSession.ts b/web/src/realtime/RealtimeSession.ts index 5d947bba..fffc5ed7 100644 --- a/web/src/realtime/RealtimeSession.ts +++ b/web/src/realtime/RealtimeSession.ts @@ -38,6 +38,11 @@ export async function stopRealtimeSession() { } } +export function resetRealtimeSessionState() { + currentSessionId = null + voiceSessionStarted = false +} + export function registerVoiceSession(session: VoiceSession) { if (voiceSession) { console.warn('[Voice] Voice session already registered, replacing with new one') diff --git a/web/src/realtime/RealtimeVoiceSession.tsx b/web/src/realtime/RealtimeVoiceSession.tsx index 16e97a77..71e00b71 100644 --- a/web/src/realtime/RealtimeVoiceSession.tsx +++ b/web/src/realtime/RealtimeVoiceSession.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useCallback, useState } from 'react' import { useConversation } from '@elevenlabs/react' -import { registerVoiceSession } from './RealtimeSession' +import { registerVoiceSession, resetRealtimeSessionState } from './RealtimeSession' import { realtimeClientTools, registerSessionStore } from './realtimeClientTools' import { fetchVoiceToken } from '@/api/voice' import type { VoiceSession, VoiceSessionConfig, ConversationStatus, StatusCallback } from './types' @@ -35,12 +35,15 @@ class RealtimeVoiceSessionImpl implements VoiceSession { statusCallback?.('connecting') // Request microphone permission first + let permissionStream: MediaStream | null = null try { - await navigator.mediaDevices.getUserMedia({ audio: true }) + permissionStream = await navigator.mediaDevices.getUserMedia({ audio: true }) } catch (error) { console.error('[Voice] Failed to get microphone permission:', error) statusCallback?.('error', 'Microphone permission denied') throw error + } finally { + permissionStream?.getTracks().forEach((track) => track.stop()) } // Fetch conversation token from server @@ -169,6 +172,7 @@ export function RealtimeVoiceSession({ const handleDisconnect = useCallback(() => { if (DEBUG) console.log('[Voice] Realtime session disconnected') + resetRealtimeSessionState() onStatusChange?.('disconnected') }, [onStatusChange])