From dc2495dca091ce6f9cda40b6c0ed5f915b30cea7 Mon Sep 17 00:00:00 2001 From: cogwheel <172976095+cogwheel0@users.noreply.github.com> Date: Thu, 5 Feb 2026 17:53:09 +0530 Subject: [PATCH] fix(voice-call): Improve async handling and state management Refactor voice call service to handle asynchronous operations more precisely. Update method signatures to be async, use unawaited for non-blocking calls, and ensure proper state reset between sessions. Improve error handling and resource management for voice input and text-to-speech services. --- ...kotlin-compiler-8345835825128208846.salive | 0 .../chat/services/text_to_speech_service.dart | 3 +++ lib/features/chat/services/tts_manager.dart | 22 +++++++++++++++++ .../chat/services/voice_call_service.dart | 24 +++++++++++-------- .../chat/services/voice_input_service.dart | 10 ++++---- 5 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 android/.kotlin/sessions/kotlin-compiler-8345835825128208846.salive diff --git a/android/.kotlin/sessions/kotlin-compiler-8345835825128208846.salive b/android/.kotlin/sessions/kotlin-compiler-8345835825128208846.salive new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/chat/services/text_to_speech_service.dart b/lib/features/chat/services/text_to_speech_service.dart index ee7c036..755e2f9 100644 --- a/lib/features/chat/services/text_to_speech_service.dart +++ b/lib/features/chat/services/text_to_speech_service.dart @@ -149,6 +149,9 @@ class TextToSpeechService { Future dispose() async { await _eventSubscription?.cancel(); _eventSubscription = null; + + // Reset the singleton state for next session + await TtsManager.instance.reset(); } /// Updates TTS settings. diff --git a/lib/features/chat/services/tts_manager.dart b/lib/features/chat/services/tts_manager.dart index dc744c1..8fa4c84 100644 --- a/lib/features/chat/services/tts_manager.dart +++ b/lib/features/chat/services/tts_manager.dart @@ -376,6 +376,28 @@ class TtsManager { } } + /// Resets the manager state for a new session. + /// + /// Call this between voice calls to ensure clean state. This clears + /// playback buffers and resets session tracking without destroying + /// the singleton instance. + Future reset() async { + await stop(); + + // Reset playback state + _resetPlaybackState(); + _activeSession = null; + _sessionCounter = 0; + + // Reset server audio buffer + _serverAudioBuffer.clear(); + _serverWaitingForNext = false; + + // Reset cached voice defaults so they're refetched if needed + _serverDefaultVoice = null; + _serverDefaultVoiceFuture = null; + } + /// Disposes the manager and releases resources. Future dispose() async { await stop(); diff --git a/lib/features/chat/services/voice_call_service.dart b/lib/features/chat/services/voice_call_service.dart index fd65d26..b284935 100644 --- a/lib/features/chat/services/voice_call_service.dart +++ b/lib/features/chat/services/voice_call_service.dart @@ -395,7 +395,7 @@ class VoiceCallService { _activeAssistantMessageId = null; _responseCompleted = false; _listeningSuspendedForSpeech = false; - _resetServerAudio(stopPlayback: true); + await _resetServerAudio(stopPlayback: true); if (_pauseReasons.isNotEmpty) { _listeningPaused = true; @@ -584,7 +584,11 @@ class VoiceCallService { _speechQueue.clear(); _enqueuedSentenceCount = 0; _responseCompleted = false; - _resetServerAudio(stopPlayback: true); + // Fire-and-forget: This is a synchronous event handler where blocking + // would delay socket event processing. The audio player stop is fast + // and race conditions are acceptable here since new playback will + // wait for this session to complete anyway. + unawaited(_resetServerAudio(stopPlayback: true)); if (_isSpeaking) { _isSpeaking = false; unawaited(_tts.stop()); @@ -749,14 +753,14 @@ class VoiceCallService { _maybeResumeListeningAfterSpeech(); } - void _resetServerAudio({bool stopPlayback = false}) { + Future _resetServerAudio({bool stopPlayback = false}) async { _serverAudioBuffer.clear(); _pendingServerAudioFetches = 0; _serverAudioSession++; _nextServerChunkId = 0; _nextServerPlaybackId = 0; if (stopPlayback) { - unawaited(_serverAudioPlayer.stop()); + await _serverAudioPlayer.stop(); _isSpeaking = false; } _serverPipelineActive = false; @@ -819,7 +823,7 @@ class VoiceCallService { if (_isDisposed) return; _isSpeaking = false; _speechQueue.clear(); - _resetServerAudio(stopPlayback: true); + unawaited(_resetServerAudio(stopPlayback: true)); _listeningSuspendedForSpeech = false; _updateState(VoiceCallState.error); // Try to recover by restarting listening @@ -865,7 +869,7 @@ class VoiceCallService { _listeningSuspendedForSpeech = false; _activeAssistantMessageId = null; _isSpeaking = false; - _resetServerAudio(stopPlayback: true); + await _resetServerAudio(stopPlayback: true); _updateState(VoiceCallState.disconnected); } @@ -911,7 +915,7 @@ class VoiceCallService { _enqueuedSentenceCount = 0; _responseCompleted = false; _listeningSuspendedForSpeech = false; - _resetServerAudio(stopPlayback: true); + await _resetServerAudio(stopPlayback: true); await _tts.stop(); _isSpeaking = false; _accumulatedResponse = ''; @@ -985,7 +989,7 @@ class VoiceCallService { _enqueuedSentenceCount = 0; _responseCompleted = false; _listeningSuspendedForSpeech = false; - _resetServerAudio(stopPlayback: true); + unawaited(_resetServerAudio(stopPlayback: true)); pauseListening(reason: VoiceCallPauseReason.mute); } else { resumeListening(reason: VoiceCallPauseReason.mute); @@ -1006,7 +1010,7 @@ class VoiceCallService { _callKitEventSubscription = null; _socketSubscription?.dispose(); - _voiceInput.dispose(); + await _voiceInput.dispose(); await _tts.dispose(); await _serverAudioStateSub?.cancel(); await _serverAudioPlayer.dispose(); @@ -1029,7 +1033,7 @@ class VoiceCallService { } } -@Riverpod(keepAlive: true) +@riverpod VoiceCallService voiceCallService(Ref ref) { final voiceInput = ref.watch(voiceInputServiceProvider); final api = ref.watch(apiServiceProvider); diff --git a/lib/features/chat/services/voice_input_service.dart b/lib/features/chat/services/voice_input_service.dart index fa35575..9857c9b 100644 --- a/lib/features/chat/services/voice_input_service.dart +++ b/lib/features/chat/services/voice_input_service.dart @@ -1026,12 +1026,12 @@ class VoiceInputService { }); } - void dispose() { - stopListening(); - unawaited(_disposeVadHandler()); - unawaited(_microphonePermissionProbe.dispose()); + Future dispose() async { + await stopListening(); + await _disposeVadHandler(); + await _microphonePermissionProbe.dispose(); try { - _speech.stop(); + await _speech.stop(); } catch (_) {} } }