-
Notifications
You must be signed in to change notification settings - Fork 492
Expand file tree
/
Copy pathSpeechToTextViewModel.cs
More file actions
185 lines (148 loc) · 5.76 KB
/
SpeechToTextViewModel.cs
File metadata and controls
185 lines (148 loc) · 5.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Globalization;
using CommunityToolkit.Maui.Alerts;
using CommunityToolkit.Maui.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace CommunityToolkit.Maui.Sample.ViewModels.Essentials;
public partial class SpeechToTextViewModel : BaseViewModel, IAsyncDisposable
{
const string defaultLanguage = "en-US";
readonly ITextToSpeech textToSpeech;
readonly ISpeechToText speechToText;
public SpeechToTextViewModel(ITextToSpeech textToSpeech, [FromKeyedServices("Online")] ISpeechToText speechToText)
{
this.textToSpeech = textToSpeech;
this.speechToText = speechToText;
Locales.CollectionChanged += HandleLocalesCollectionChanged;
this.speechToText.StateChanged += HandleSpeechToTextStateChanged;
this.speechToText.RecognitionResultUpdated += HandleRecognitionResultUpdated;
this.speechToText.RecognitionResultCompleted += HandleRecognitionResultCompleted;
}
public ObservableCollection<Locale> Locales { get; } = [];
public SpeechToTextState? State => speechToText.CurrentState;
[ObservableProperty]
public partial Locale? CurrentLocale { get; set; }
[ObservableProperty]
public partial string? RecognitionText { get; set; } = "Welcome to .NET MAUI Community Toolkit!";
[ObservableProperty, NotifyCanExecuteChangedFor(nameof(StartListenCommand))]
public partial bool CanStartListenExecute { get; set; } = true;
[ObservableProperty, NotifyCanExecuteChangedFor(nameof(StopListenCommand))]
public partial bool CanStopListenExecute { get; set; } = false;
public async ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);
Locales.CollectionChanged -= HandleLocalesCollectionChanged;
this.speechToText.StateChanged -= HandleSpeechToTextStateChanged;
this.speechToText.RecognitionResultUpdated -= HandleRecognitionResultUpdated;
this.speechToText.RecognitionResultCompleted -= HandleRecognitionResultCompleted;
await speechToText.DisposeAsync();
}
static async Task<bool> ArePermissionsGranted(ISpeechToText speechToText)
{
var microphonePermissionStatus = await Permissions.RequestAsync<Permissions.Microphone>();
var isSpeechToTextPermissionsGranted = await speechToText.RequestPermissions(CancellationToken.None);
return microphonePermissionStatus is PermissionStatus.Granted
&& isSpeechToTextPermissionsGranted;
}
[RelayCommand]
async Task SetLocales(CancellationToken token)
{
Locales.Clear();
IReadOnlyList<Locale> locales = [.. await textToSpeech.GetLocalesAsync().WaitAsync(token)];
foreach (var locale in locales.OrderBy(x => x.Language).ThenBy(x => x.Name))
{
Locales.Add(locale);
}
var currentLocale = locales.FirstOrDefault(l => l.Language.Equals(CultureInfo.CurrentUICulture.Name, StringComparison.OrdinalIgnoreCase))
?? locales.FirstOrDefault(l => l.Language.Equals(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, StringComparison.OrdinalIgnoreCase));
CurrentLocale = currentLocale ?? locales[0];
}
[RelayCommand]
async Task Play(CancellationToken cancellationToken)
{
var timeoutCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
await textToSpeech.SpeakAsync(RecognitionText ?? "Welcome to .NET MAUI Community Toolkit!", new()
{
Locale = CurrentLocale,
Pitch = 1,
Volume = 1
}, cancellationToken).WaitAsync(timeoutCancellationTokenSource.Token);
}
catch (TaskCanceledException)
{
await Toast.Make("Playback automatically stopped after 5 seconds").Show(cancellationToken);
#if IOS
await Toast.Make("If you did not hear playback, test again on a physical iOS device").Show(cancellationToken);
#endif
}
}
[RelayCommand(CanExecute = nameof(CanStartListenExecute))]
async Task StartListen(CancellationToken cancellationToken)
{
CanStartListenExecute = false;
CanStopListenExecute = true;
var isGranted = await ArePermissionsGranted(speechToText);
if (!isGranted)
{
await Toast.Make("Permission not granted").Show(cancellationToken);
CanStartListenExecute = true;
CanStopListenExecute = false;
return;
}
if (Connectivity.NetworkAccess is not NetworkAccess.Internet)
{
await Toast.Make("Internet connection is required").Show(cancellationToken);
CanStartListenExecute = true;
CanStopListenExecute = false;
return;
}
const string beginSpeakingPrompt = "Begin speaking...";
RecognitionText = beginSpeakingPrompt;
try
{
await speechToText.StartListenAsync(new SpeechToTextOptions
{
Culture = CultureInfo.GetCultureInfo(CurrentLocale?.Language ?? defaultLanguage),
AutoStopSilenceTimeout = TimeSpan.FromSeconds(5),
ShouldReportPartialResults = true
}, cancellationToken);
if (RecognitionText is beginSpeakingPrompt)
{
RecognitionText = string.Empty;
}
}
catch
{
CanStartListenExecute = true;
CanStopListenExecute = false;
throw;
}
}
[RelayCommand(CanExecute = nameof(CanStopListenExecute))]
Task StopListen(CancellationToken cancellationToken)
{
CanStartListenExecute = true;
CanStopListenExecute = false;
return speechToText.StopListenAsync(cancellationToken);
}
void HandleRecognitionResultUpdated(object? sender, SpeechToTextRecognitionResultUpdatedEventArgs e)
{
RecognitionText += $" {e.RecognitionResult}";
}
void HandleRecognitionResultCompleted(object? sender, SpeechToTextRecognitionResultCompletedEventArgs e)
{
RecognitionText = e.RecognitionResult.IsSuccessful ? e.RecognitionResult.Text : e.RecognitionResult.Exception.Message;
}
void HandleSpeechToTextStateChanged(object? sender, SpeechToTextStateChangedEventArgs e)
{
OnPropertyChanged(nameof(State));
}
void HandleLocalesCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(nameof(CurrentLocale));
}
}