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.
This commit is contained in:
@@ -149,6 +149,9 @@ class TextToSpeechService {
|
||||
Future<void> dispose() async {
|
||||
await _eventSubscription?.cancel();
|
||||
_eventSubscription = null;
|
||||
|
||||
// Reset the singleton state for next session
|
||||
await TtsManager.instance.reset();
|
||||
}
|
||||
|
||||
/// Updates TTS settings.
|
||||
|
||||
@@ -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<void> 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<void> dispose() async {
|
||||
await stop();
|
||||
|
||||
@@ -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<void> _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);
|
||||
|
||||
@@ -1026,12 +1026,12 @@ class VoiceInputService {
|
||||
});
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
stopListening();
|
||||
unawaited(_disposeVadHandler());
|
||||
unawaited(_microphonePermissionProbe.dispose());
|
||||
Future<void> dispose() async {
|
||||
await stopListening();
|
||||
await _disposeVadHandler();
|
||||
await _microphonePermissionProbe.dispose();
|
||||
try {
|
||||
_speech.stop();
|
||||
await _speech.stop();
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user