Skip to content

Commit b9be360

Browse files
SpeechRecognition fixes (#2562)
1 parent ac8a592 commit b9be360

8 files changed

+93
-27
lines changed

src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/OfflineSpeechToTextImplementation.android.cs

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using System.Diagnostics.CodeAnalysis;
2-
using System.Globalization;
32
using System.Runtime.Versioning;
43
using Android.Content;
54
using Android.Runtime;
65
using Android.Speech;
6+
using Java.Lang;
7+
using Java.Util.Concurrent;
78
using Microsoft.Maui.ApplicationModel;
9+
using Exception = System.Exception;
810

911
namespace CommunityToolkit.Maui.Media;
1012

@@ -13,21 +15,20 @@ public sealed partial class OfflineSpeechToTextImplementation
1315
{
1416
SpeechRecognizer? speechRecognizer;
1517
SpeechRecognitionListener? listener;
16-
SpeechToTextState currentState = SpeechToTextState.Stopped;
1718

1819
/// <inheritdoc />
1920
public SpeechToTextState CurrentState
2021
{
21-
get => currentState;
22+
get;
2223
private set
2324
{
24-
if (currentState != value)
25+
if (field != value)
2526
{
26-
currentState = value;
27-
OnSpeechToTextStateChanged(currentState);
27+
field = value;
28+
OnSpeechToTextStateChanged(field);
2829
}
2930
}
30-
}
31+
} = SpeechToTextState.Stopped;
3132

3233
/// <inheritdoc />
3334
public ValueTask DisposeAsync()
@@ -43,22 +44,24 @@ public ValueTask DisposeAsync()
4344
static Intent CreateSpeechIntent(SpeechToTextOptions options)
4445
{
4546
var intent = new Intent(RecognizerIntent.ActionRecognizeSpeech);
46-
intent.PutExtra(RecognizerIntent.ExtraLanguagePreference, Java.Util.Locale.Default.ToString());
47-
intent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);
47+
48+
intent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);
4849
intent.PutExtra(RecognizerIntent.ExtraCallingPackage, Application.Context.PackageName);
4950
intent.PutExtra(RecognizerIntent.ExtraPartialResults, options.ShouldReportPartialResults);
50-
51-
var javaLocale = Java.Util.Locale.ForLanguageTag(options.Culture.Name).ToString();
51+
52+
var javaLocale = Java.Util.Locale.ForLanguageTag(options.Culture.Name).ToLanguageTag();
5253
intent.PutExtra(RecognizerIntent.ExtraLanguage, javaLocale);
54+
intent.PutExtra(RecognizerIntent.ExtraLanguagePreference, javaLocale);
55+
intent.PutExtra(RecognizerIntent.ExtraOnlyReturnLanguagePreference, javaLocale);
5356

5457
return intent;
5558
}
5659

57-
static bool IsSpeechRecognitionAvailable() => OperatingSystem.IsAndroidVersionAtLeast(33) && SpeechRecognizer.IsOnDeviceRecognitionAvailable(Application.Context);
60+
static bool IsSpeechRecognitionAvailable() => OperatingSystem.IsAndroidVersionAtLeast(34) && SpeechRecognizer.IsOnDeviceRecognitionAvailable(Application.Context);
5861

5962
[MemberNotNull(nameof(speechRecognizer), nameof(listener))]
60-
[SupportedOSPlatform("Android33.0")]
61-
void InternalStartListening(SpeechToTextOptions options)
63+
[SupportedOSPlatform("Android34.0")]
64+
async Task InternalStartListening(SpeechToTextOptions options, CancellationToken token = default)
6265
{
6366
if (!IsSpeechRecognitionAvailable())
6467
{
@@ -68,14 +71,28 @@ void InternalStartListening(SpeechToTextOptions options)
6871
var recognizerIntent = CreateSpeechIntent(options);
6972

7073
speechRecognizer = SpeechRecognizer.CreateOnDeviceSpeechRecognizer(Application.Context);
71-
speechRecognizer.TriggerModelDownload(recognizerIntent);
72-
7374
listener = new SpeechRecognitionListener(this)
7475
{
7576
Error = HandleListenerError,
7677
PartialResults = HandleListenerPartialResults,
7778
Results = HandleListenerResults
7879
};
80+
81+
var recognitionSupportTask = new TaskCompletionSource<RecognitionSupport>();
82+
speechRecognizer.CheckRecognitionSupport(recognizerIntent, new Executor(), new RecognitionSupportCallback(recognitionSupportTask));
83+
var recognitionSupportResult = await recognitionSupportTask.Task;
84+
if (!recognitionSupportResult.InstalledOnDeviceLanguages.Contains(options.Culture.Name))
85+
{
86+
if (!recognitionSupportResult.SupportedOnDeviceLanguages.Contains(options.Culture.Name))
87+
{
88+
throw new NotSupportedException($"Culture '{options.Culture.Name}' is not supported");
89+
}
90+
91+
var downloadLanguageTask = new TaskCompletionSource();
92+
speechRecognizer.TriggerModelDownload(recognizerIntent, new Executor(), new ModelDownloadListener(downloadLanguageTask));
93+
await downloadLanguageTask.Task.WaitAsync(token);
94+
}
95+
7996
speechRecognizer.SetRecognitionListener(listener);
8097
speechRecognizer.StartListening(recognizerIntent);
8198
}
@@ -161,4 +178,47 @@ static void SendResults(Bundle? bundle, Action<string> action)
161178
action.Invoke(matches[0]);
162179
}
163180
}
181+
}
182+
183+
[SupportedOSPlatform("Android33.0")]
184+
class RecognitionSupportCallback(TaskCompletionSource<RecognitionSupport> recognitionSupportTask) : Java.Lang.Object, IRecognitionSupportCallback
185+
{
186+
public void OnError(int error)
187+
{
188+
recognitionSupportTask.TrySetException(new Exception(error.ToString()));
189+
}
190+
191+
public void OnSupportResult(RecognitionSupport recognitionSupport)
192+
{
193+
recognitionSupportTask.TrySetResult(recognitionSupport);
194+
}
195+
}
196+
197+
class Executor : Java.Lang.Object, IExecutor
198+
{
199+
public void Execute(IRunnable? command)
200+
{
201+
command?.Run();
202+
}
203+
}
204+
205+
class ModelDownloadListener(TaskCompletionSource downloadLanguageTask) : Java.Lang.Object, IModelDownloadListener
206+
{
207+
public void OnError(SpeechRecognizerError error)
208+
{
209+
downloadLanguageTask.SetException(new Exception(error.ToString()));
210+
}
211+
212+
public void OnProgress(int completedPercent)
213+
{
214+
}
215+
216+
public void OnScheduled()
217+
{
218+
}
219+
220+
public void OnSuccess()
221+
{
222+
downloadLanguageTask.SetResult();
223+
}
164224
}

src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/OfflineSpeechToTextImplementation.ios.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public sealed partial class OfflineSpeechToTextImplementation
1212
[MemberNotNull(nameof(audioEngine), nameof(recognitionTask), nameof(liveSpeechRequest))]
1313
[SupportedOSPlatform("ios13.0")]
1414
[SupportedOSPlatform("maccatalyst")]
15-
void InternalStartListening(SpeechToTextOptions options)
15+
Task InternalStartListening(SpeechToTextOptions options, CancellationToken token = default)
1616
{
1717
if (!UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
1818
{
@@ -80,5 +80,7 @@ void InternalStartListening(SpeechToTextOptions options)
8080
}
8181
}
8282
});
83+
84+
return Task.CompletedTask;
8385
}
8486
}

src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/OfflineSpeechToTextImplementation.macos.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace CommunityToolkit.Maui.Media;
99
public sealed partial class OfflineSpeechToTextImplementation
1010
{
1111
[MemberNotNull(nameof(audioEngine), nameof(recognitionTask), nameof(liveSpeechRequest))]
12-
void InternalStartListening(SpeechToTextOptions options)
12+
Task InternalStartListening(SpeechToTextOptions options, CancellationToken token = default)
1313
{
1414
speechRecognizer = new SFSpeechRecognizer(NSLocale.FromLocaleIdentifier(options.Culture.Name));
1515
speechRecognizer.SupportsOnDeviceRecognition = true;
@@ -91,5 +91,7 @@ void InternalStartListening(SpeechToTextOptions options)
9191
}
9292
}
9393
});
94+
95+
return Task.CompletedTask;
9496
}
9597
}

src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/OfflineSpeechToTextImplementation.net.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public ValueTask DisposeAsync()
1111
return ValueTask.CompletedTask;
1212
}
1313

14-
void InternalStartListening(SpeechToTextOptions options)
14+
Task InternalStartListening(SpeechToTextOptions options, CancellationToken token = default)
1515
{
1616
throw new NotSupportedException();
1717
}

src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/OfflineSpeechToTextImplementation.shared.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public async Task StartListenAsync(SpeechToTextOptions options, CancellationToke
4242
throw new PermissionException($"{nameof(Permissions)}.{nameof(Permissions.Microphone)} Not Granted");
4343
}
4444

45-
InternalStartListening(options);
45+
await InternalStartListening(options, cancellationToken);
4646
}
4747

4848
/// <inheritdoc/>

src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/OfflineSpeechToTextImplementation.tizen.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ void Initialize()
105105
}
106106
}
107107

108-
void InternalStartListening(SpeechToTextOptions options)
108+
Task InternalStartListening(SpeechToTextOptions options, CancellationToken token = default)
109109
{
110110
Initialize();
111111

@@ -118,5 +118,6 @@ void InternalStartListening(SpeechToTextOptions options)
118118
: RecognitionType.Free;
119119

120120
sttClient.Start(options.Culture.Name, recognitionType);
121+
return Task.CompletedTask;
121122
}
122123
}

src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/OfflineSpeechToTextImplementation.windows.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public ValueTask DisposeAsync()
3434
return ValueTask.CompletedTask;
3535
}
3636

37-
void InternalStartListening(SpeechToTextOptions options)
37+
Task InternalStartListening(SpeechToTextOptions options, CancellationToken token = default)
3838
{
3939
Initialize(options);
4040

@@ -49,6 +49,7 @@ void InternalStartListening(SpeechToTextOptions options)
4949
offlineSpeechRecognizer.RecognizeCompleted += OnRecognizeCompleted;
5050
offlineSpeechRecognizer.SpeechRecognized += OnSpeechRecognized;
5151
offlineSpeechRecognizer.RecognizeAsync(RecognizeMode.Multiple);
52+
return Task.CompletedTask;
5253
}
5354

5455
void OnRecognizeCompleted(object? sender, RecognizeCompletedEventArgs args)

src/CommunityToolkit.Maui.Core/Essentials/SpeechToText/SpeechToTextImplementation.android.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Diagnostics.CodeAnalysis;
2-
using System.Globalization;
32
using Android.Content;
43
using Android.Runtime;
54
using Android.Speech;
@@ -12,7 +11,6 @@ public sealed partial class SpeechToTextImplementation
1211
{
1312
SpeechRecognizer? speechRecognizer;
1413
SpeechRecognitionListener? listener;
15-
CultureInfo? cultureInfo;
1614

1715
/// <inheritdoc />
1816
public SpeechToTextState CurrentState
@@ -42,13 +40,15 @@ public ValueTask DisposeAsync()
4240
static Intent CreateSpeechIntent(SpeechToTextOptions options)
4341
{
4442
var intent = new Intent(RecognizerIntent.ActionRecognizeSpeech);
45-
intent.PutExtra(RecognizerIntent.ExtraLanguagePreference, Java.Util.Locale.Default.ToString());
43+
4644
intent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);
4745
intent.PutExtra(RecognizerIntent.ExtraCallingPackage, Application.Context.PackageName);
4846
intent.PutExtra(RecognizerIntent.ExtraPartialResults, options.ShouldReportPartialResults);
49-
50-
var javaLocale = Java.Util.Locale.ForLanguageTag(options.Culture.Name).ToString();
47+
48+
var javaLocale = Java.Util.Locale.ForLanguageTag(options.Culture.Name).ToLanguageTag();
5149
intent.PutExtra(RecognizerIntent.ExtraLanguage, javaLocale);
50+
intent.PutExtra(RecognizerIntent.ExtraLanguagePreference, javaLocale);
51+
intent.PutExtra(RecognizerIntent.ExtraOnlyReturnLanguagePreference, javaLocale);
5252

5353
return intent;
5454
}

0 commit comments

Comments
 (0)