44using System . IO ;
55using System . Runtime . Versioning ;
66using System . Threading . Tasks ;
7+ using Microsoft . Extensions . Logging ;
78using PySpeechService . Client ;
89using PySpeechService . TextToSpeech ;
910using TrackerCouncil . Smz3 . Data . Options ;
@@ -15,6 +16,8 @@ namespace TrackerCouncil.Smz3.Tracking.Services;
1516internal class PyTextToSpeechCommunicator : ICommunicator
1617{
1718 private readonly IPySpeechService _pySpeechService ;
19+ private readonly ILogger < PyTextToSpeechCommunicator > _logger ;
20+ private DateTime ? _startSpeakingTime ;
1821 private ( string onnxPath , string jsonPath ) ? _defaultPrimaryVoice ;
1922 private ( string onnxPath , string jsonPath ) ? _defaultAltVoice ;
2023 private ( string onnxPath , string jsonPath ) ? _primaryVoice ;
@@ -25,9 +28,10 @@ internal class PyTextToSpeechCommunicator : ICommunicator
2528 private int volume ;
2629 private ConcurrentDictionary < string , SpeechRequest > _pendingRequests = [ ] ;
2730
28- public PyTextToSpeechCommunicator ( IPySpeechService pySpeechService , TrackerOptionsAccessor trackerOptionsAccessor )
31+ public PyTextToSpeechCommunicator ( IPySpeechService pySpeechService , TrackerOptionsAccessor trackerOptionsAccessor , ILogger < PyTextToSpeechCommunicator > logger )
2932 {
3033 _pySpeechService = pySpeechService ;
34+ _logger = logger ;
3135
3236 // Check to see if the user has the tracker voice files to use
3337 var piperPath = Path . Combine ( Directories . AppDataFolder , "PiperModels" ) ;
@@ -42,47 +46,70 @@ public PyTextToSpeechCommunicator(IPySpeechService pySpeechService, TrackerOptio
4246 volume = trackerOptionsAccessor . Options ? . TextToSpeechVolume ?? 100 ;
4347 _ = Initialize ( ) ;
4448
45- _pySpeechService . Initialized += ( _ , _ ) =>
49+ _pySpeechService . Initialized += PySpeechServiceOnInitialized ;
50+ _pySpeechService . SpeakCommandResponded += PySpeechServiceOnSpeakCommandResponded ;
51+
52+ _isEnabled = trackerOptionsAccessor . Options ? . VoiceFrequency != Shared . Enums . TrackerVoiceFrequency . Disabled ;
53+ }
54+
55+ private void PySpeechServiceOnSpeakCommandResponded ( object ? sender , SpeakCommandResponseEventArgs args )
56+ {
57+ SpeechRequest ? request = null ;
58+
59+ _logger . LogInformation ( "Response: {Id}" , args . Response . MessageId ) ;
60+
61+ if ( string . IsNullOrEmpty ( args . Response . MessageId ) || ! _pendingRequests . TryGetValue ( args . Response . MessageId , out request ) )
4662 {
47- _ = Initialize ( ) ;
48- } ;
63+ _logger . LogError ( "Received PySpeechService SpeakCommandResponse with no valid message id" ) ;
64+ }
4965
50- _pySpeechService . SpeakCommandResponded += ( _ , args ) =>
66+ if ( args . Response . IsStartOfChunk )
67+ {
68+ VisemeReached ? . Invoke ( this , new SpeakingUpdatedEventArgs ( true , request ) ) ;
69+ }
70+ else if ( args . Response . IsEndOfChunk )
5171 {
52- _pendingRequests . TryGetValue ( args . Response . FullMessage , out var request ) ;
72+ VisemeReached ? . Invoke ( this , new SpeakingUpdatedEventArgs ( false , request ) ) ;
73+ }
5374
54- if ( args . Response . IsStartOfChunk )
55- {
56- VisemeReached ? . Invoke ( this , new SpeakingUpdatedEventArgs ( true , request ) ) ;
57- }
58- else if ( args . Response . IsEndOfChunk )
75+ if ( args . Response . IsStartOfMessage )
76+ {
77+ if ( _startSpeakingTime == null )
5978 {
60- VisemeReached ? . Invoke ( this , new SpeakingUpdatedEventArgs ( false , request ) ) ;
79+ _startSpeakingTime = DateTime . Now ;
6180 }
6281
63- if ( args . Response . IsStartOfMessage )
82+ _isSpeaking = true ;
83+ SpeakStarted ? . Invoke ( this , EventArgs . Empty ) ;
84+ }
85+ else if ( args . Response . IsEndOfMessage )
86+ {
87+ if ( request != null )
6488 {
65- _isSpeaking = true ;
66- SpeakStarted ? . Invoke ( this , EventArgs . Empty ) ;
89+ _pendingRequests . TryRemove (
90+ new KeyValuePair < string , SpeechRequest > ( args . Response . MessageId ! , request ) ) ;
6791 }
68- else if ( args . Response . IsEndOfMessage )
92+
93+ if ( ! args . Response . HasAnotherRequest )
6994 {
70- SpeakCompleted ? . Invoke ( this , new SpeakCompletedEventArgs ( TimeSpan . FromSeconds ( 3 ) ) ) ;
71-
72- if ( request != null )
73- {
74- _pendingRequests . TryRemove (
75- new KeyValuePair < string , SpeechRequest > ( args . Response . FullMessage , request ) ) ;
76- }
77-
78- if ( ! args . Response . HasAnotherRequest )
79- {
80- _isSpeaking = false ;
81- }
95+ var duration = DateTime . Now - _startSpeakingTime ;
96+ _startSpeakingTime = null ;
97+ SpeakCompleted ? . Invoke ( this , new SpeakCompletedEventArgs ( duration ?? TimeSpan . Zero , request ) ) ;
98+ _isSpeaking = false ;
8299 }
83- } ;
100+ }
101+ }
84102
85- _isEnabled = trackerOptionsAccessor . Options ? . VoiceFrequency != Shared . Enums . TrackerVoiceFrequency . Disabled ;
103+ private async void PySpeechServiceOnInitialized ( object ? sender , EventArgs args )
104+ {
105+ try
106+ {
107+ await Initialize ( ) ;
108+ }
109+ catch ( Exception e )
110+ {
111+ _logger . LogError ( e , "Error initializing PySpeechService" ) ;
112+ }
86113 }
87114
88115 public void UseAlternateVoice ( bool useAlt = true )
@@ -133,15 +160,18 @@ public void Say(SpeechRequest request)
133160 {
134161 if ( ! _isEnabled || ! _pySpeechService . IsSpeechEnabled ) return ;
135162
136- _pendingRequests . TryAdd ( request . Text , request ) ;
163+ var messageId = Guid . NewGuid ( ) . ToString ( ) ;
164+ _pendingRequests . TryAdd ( messageId , request ) ;
165+
166+ _logger . LogInformation ( "Request: {Id}" , messageId ) ;
137167
138168 if ( request . Wait )
139169 {
140- _pySpeechService . Speak ( request . Text , GetSpeechSettings ( ) ) ;
170+ _pySpeechService . Speak ( request . Text , GetSpeechSettings ( ) , messageId ) ;
141171 }
142172 else
143173 {
144- _pySpeechService . SpeakAsync ( request . Text , GetSpeechSettings ( ) ) ;
174+ _pySpeechService . SpeakAsync ( request . Text , GetSpeechSettings ( ) , messageId ) ;
145175 }
146176 }
147177
@@ -188,4 +218,10 @@ public void UpdateVolume(int newVolume)
188218 public event EventHandler ? SpeakStarted ;
189219 public event EventHandler < SpeakCompletedEventArgs > ? SpeakCompleted ;
190220 public event EventHandler < SpeakingUpdatedEventArgs > ? VisemeReached ;
221+
222+ public void Dispose ( )
223+ {
224+ _pySpeechService . Initialized -= PySpeechServiceOnInitialized ;
225+ _pySpeechService . SpeakCommandResponded -= PySpeechServiceOnSpeakCommandResponded ;
226+ }
191227}
0 commit comments