Skip to content

Commit d83a9f1

Browse files
committed
use the same workflow on watch and phone
1 parent 1742c78 commit d83a9f1

File tree

2 files changed

+213
-33
lines changed

2 files changed

+213
-33
lines changed

lib/services/ai_service.dart

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:agixt/services/watch_service.dart';
1010
import 'package:agixt/services/wake_word_service.dart';
1111
import 'package:agixt/services/voice_input_service.dart';
1212
import 'package:agixt/services/tts_service.dart';
13+
import 'package:agixt/services/audio_player_service.dart';
1314
import 'package:flutter/foundation.dart';
1415
import 'package:flutter/services.dart'; // Import Services
1516

@@ -25,6 +26,7 @@ class AIService {
2526
final WakeWordService _wakeWordService = WakeWordService.singleton;
2627
final VoiceInputService _voiceInputService = VoiceInputService.singleton;
2728
final TTSService _ttsService = TTSService.singleton;
29+
final AudioPlayerService _audioPlayerService = AudioPlayerService.singleton;
2830
WhisperService? _whisperService;
2931
final AGiXTChatWidget _chatWidget = AGiXTChatWidget();
3032
final AGiXTWebSocketService _webSocketService = AGiXTWebSocketService();
@@ -55,7 +57,7 @@ class AIService {
5557
// Method channel handler will be set up when needed
5658
}
5759

58-
/// Initialize new services (watch, wake word, voice input, TTS)
60+
/// Initialize new services (watch, wake word, voice input, TTS, audio player)
5961
Future<void> _initNewServices() async {
6062
try {
6163
// Initialize watch service
@@ -70,6 +72,9 @@ class AIService {
7072
// Initialize TTS service
7173
await _ttsService.initialize();
7274

75+
// Initialize audio player service for streaming PCM playback
76+
await _audioPlayerService.initialize();
77+
7378
// Set up wake word listener
7479
_wakeWordSubscription = _wakeWordService.eventStream.listen(
7580
_handleWakeWordEvent,
@@ -385,7 +390,8 @@ class AIService {
385390
if (_bluetoothManager.isConnected) {
386391
final now = DateTime.now();
387392
if (lastGlassesUpdate == null ||
388-
now.difference(lastGlassesUpdate!) > glassesUpdateInterval) {
393+
now.difference(lastGlassesUpdate!) >
394+
glassesUpdateInterval) {
389395
lastGlassesUpdate = now;
390396
// Send full accumulated text so far
391397
await _bluetoothManager.sendAIResponse(
@@ -397,38 +403,48 @@ class AIService {
397403
break;
398404

399405
case ChatStreamEventType.audioHeader:
400-
// Audio format info - used for watch playback
406+
// Audio format info - start streaming playback
401407
if (event.sampleRate != null) {
402408
debugPrint(
403409
'AIService: Audio header - ${event.sampleRate}Hz, ${event.bitsPerSample}bit, ${event.channels}ch');
404410
audioHeaderSent = true;
405-
// Send audio header to watch if connected
411+
// Send audio header to watch if connected, otherwise start phone playback
406412
if (_watchService.isConnected) {
407413
await _watchService.sendAudioHeader(
408414
sampleRate: event.sampleRate!,
409415
bitsPerSample: event.bitsPerSample ?? 16,
410416
channels: event.channels ?? 1,
411417
);
418+
} else {
419+
// Start streaming audio on phone speaker
420+
await _audioPlayerService.startStreaming(
421+
sampleRate: event.sampleRate!,
422+
bitsPerSample: event.bitsPerSample ?? 16,
423+
channels: event.channels ?? 1,
424+
);
412425
}
413426
}
414427
break;
415428

416429
case ChatStreamEventType.audioChunk:
417-
// Stream audio to watch speaker
430+
// Stream audio to watch speaker or phone speaker
418431
if (event.audioData != null && audioHeaderSent) {
419-
// Only mark as received if watch is connected and audio is actually played
432+
hasReceivedAudio = true;
420433
if (_watchService.isConnected) {
421-
hasReceivedAudio = true;
422434
await _watchService.sendAudioChunk(event.audioData!);
435+
} else {
436+
// Play on phone speaker
437+
await _audioPlayerService.feedAudioChunk(event.audioData!);
423438
}
424-
// If watch not connected, audio is not played - fallback TTS will be used
425439
}
426440
break;
427441

428442
case ChatStreamEventType.audioEnd:
429443
// Audio streaming complete
430444
if (_watchService.isConnected) {
431445
await _watchService.sendAudioEnd();
446+
} else {
447+
await _audioPlayerService.stopStreaming();
432448
}
433449
debugPrint('AIService: Audio streaming complete');
434450
break;
@@ -454,18 +470,16 @@ class AIService {
454470
if (_watchService.isConnected) {
455471
await _watchService.displayMessage(fullResponse, durationMs: 10000);
456472
}
457-
// If no audio was streamed (no watch connected or AGiXT didn't return audio),
458-
// use phone TTS as fallback
459-
if (!hasReceivedAudio && _ttsService.shouldUseTTS()) {
460-
debugPrint('AIService: No streaming audio received, using phone TTS');
461-
await _ttsService.speak(fullResponse);
462-
}
473+
// Note: Audio is streamed in real-time via audioHeader/audioChunk events
474+
// No need for TTS fallback - AGiXT sends audio during the stream
463475
} else {
464476
await _showErrorMessage('No response from AGiXT');
465477
}
466478
} catch (e) {
467479
debugPrint('AIService: Error processing audio: $e');
468480
await _showErrorMessage('Error processing voice input');
481+
// Stop any playing audio on error
482+
await _audioPlayerService.stopStreaming();
469483
} finally {
470484
_isProcessing = false;
471485
// Resume wake word listening if enabled
@@ -717,7 +731,8 @@ class AIService {
717731
if (_bluetoothManager.isConnected) {
718732
final now = DateTime.now();
719733
if (lastGlassesUpdate == null ||
720-
now.difference(lastGlassesUpdate!) > glassesUpdateInterval) {
734+
now.difference(lastGlassesUpdate!) >
735+
glassesUpdateInterval) {
721736
lastGlassesUpdate = now;
722737
await _bluetoothManager.sendAIResponse(
723738
responseBuffer.toString(),
@@ -728,7 +743,7 @@ class AIService {
728743
break;
729744

730745
case ChatStreamEventType.audioHeader:
731-
// Send audio format to watch
746+
// Send audio format to watch or start phone playback
732747
if (event.sampleRate != null) {
733748
debugPrint(
734749
'AIService: Audio header - ${event.sampleRate}Hz, ${event.bitsPerSample}bit, ${event.channels}ch');
@@ -739,26 +754,36 @@ class AIService {
739754
bitsPerSample: event.bitsPerSample ?? 16,
740755
channels: event.channels ?? 1,
741756
);
757+
} else {
758+
// Start streaming audio on phone speaker
759+
await _audioPlayerService.startStreaming(
760+
sampleRate: event.sampleRate!,
761+
bitsPerSample: event.bitsPerSample ?? 16,
762+
channels: event.channels ?? 1,
763+
);
742764
}
743765
}
744766
break;
745767

746768
case ChatStreamEventType.audioChunk:
747-
// Stream audio to watch speaker
769+
// Stream audio to watch speaker or phone speaker
748770
if (event.audioData != null && audioHeaderSent) {
749-
// Only mark as received if watch is connected and audio is actually played
771+
hasReceivedAudio = true;
750772
if (_watchService.isConnected) {
751-
hasReceivedAudio = true;
752773
await _watchService.sendAudioChunk(event.audioData!);
774+
} else {
775+
// Play on phone speaker
776+
await _audioPlayerService.feedAudioChunk(event.audioData!);
753777
}
754-
// If watch not connected, audio is not played - fallback TTS will be used
755778
}
756779
break;
757780

758781
case ChatStreamEventType.audioEnd:
759782
// Audio streaming complete
760783
if (_watchService.isConnected) {
761784
await _watchService.sendAudioEnd();
785+
} else {
786+
await _audioPlayerService.stopStreaming();
762787
}
763788
debugPrint('AIService: Audio streaming complete');
764789
break;
@@ -782,17 +807,15 @@ class AIService {
782807
if (_watchService.isConnected) {
783808
await _watchService.displayMessage(response, durationMs: 10000);
784809
}
785-
// If no audio was streamed, use phone TTS as fallback
786-
if (!hasReceivedAudio && _ttsService.shouldUseTTS()) {
787-
debugPrint('AIService: No streaming audio received, using phone TTS');
788-
await _ttsService.speak(response);
789-
}
810+
// Note: Audio is streamed in real-time via audioHeader/audioChunk events
790811
} else {
791812
await _showErrorMessage('No response from AGiXT');
792813
}
793814
} catch (e) {
794815
debugPrint('Error sending message to AGiXT: $e');
795816
await _showErrorMessage('Failed to get response from AGiXT');
817+
// Stop any playing audio on error
818+
await _audioPlayerService.stopStreaming();
796819
}
797820
}
798821

@@ -863,7 +886,8 @@ class AIService {
863886
if (_bluetoothManager.isConnected) {
864887
final now = DateTime.now();
865888
if (lastGlassesUpdate == null ||
866-
now.difference(lastGlassesUpdate!) > glassesUpdateInterval) {
889+
now.difference(lastGlassesUpdate!) >
890+
glassesUpdateInterval) {
867891
lastGlassesUpdate = now;
868892
await _bluetoothManager.sendAIResponse(
869893
responseBuffer.toString(),
@@ -882,24 +906,34 @@ class AIService {
882906
bitsPerSample: event.bitsPerSample ?? 16,
883907
channels: event.channels ?? 1,
884908
);
909+
} else {
910+
// Start streaming audio on phone speaker
911+
await _audioPlayerService.startStreaming(
912+
sampleRate: event.sampleRate!,
913+
bitsPerSample: event.bitsPerSample ?? 16,
914+
channels: event.channels ?? 1,
915+
);
885916
}
886917
}
887918
break;
888919

889920
case ChatStreamEventType.audioChunk:
890921
if (event.audioData != null && audioHeaderSent) {
891-
// Only mark as received if watch is connected and audio is actually played
922+
hasReceivedAudio = true;
892923
if (_watchService.isConnected) {
893-
hasReceivedAudio = true;
894924
await _watchService.sendAudioChunk(event.audioData!);
925+
} else {
926+
// Play on phone speaker
927+
await _audioPlayerService.feedAudioChunk(event.audioData!);
895928
}
896-
// If watch not connected, audio is not played - fallback TTS will be used
897929
}
898930
break;
899931

900932
case ChatStreamEventType.audioEnd:
901933
if (_watchService.isConnected) {
902934
await _watchService.sendAudioEnd();
935+
} else {
936+
await _audioPlayerService.stopStreaming();
903937
}
904938
break;
905939

@@ -916,16 +950,15 @@ class AIService {
916950
if (_watchService.isConnected) {
917951
await _watchService.displayMessage(response, durationMs: 10000);
918952
}
919-
// Fallback to phone TTS if no streaming audio
920-
if (!hasReceivedAudio && _ttsService.shouldUseTTS()) {
921-
await _ttsService.speak(response);
922-
}
953+
// Note: Audio is streamed in real-time via audioHeader/audioChunk events
923954
} else {
924955
await _showErrorMessage('No response from AGiXT');
925956
}
926957
} catch (e) {
927958
debugPrint('Error sending message to AGiXT: $e');
928959
await _showErrorMessage('Failed to get response from AGiXT');
960+
// Stop any playing audio on error
961+
await _audioPlayerService.stopStreaming();
929962
}
930963
}
931964

0 commit comments

Comments
 (0)