Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.Versioning;
using Android.Content;
using Android.Runtime;
using Android.Speech;
using Java.Lang;
using Java.Util.Concurrent;
using Microsoft.Maui.ApplicationModel;
using Exception = System.Exception;

namespace CommunityToolkit.Maui.Media;

Expand All @@ -13,21 +15,20 @@ public sealed partial class OfflineSpeechToTextImplementation
{
SpeechRecognizer? speechRecognizer;
SpeechRecognitionListener? listener;
SpeechToTextState currentState = SpeechToTextState.Stopped;

/// <inheritdoc />
public SpeechToTextState CurrentState
{
get => currentState;
get;
private set
{
if (currentState != value)
if (field != value)
{
currentState = value;
OnSpeechToTextStateChanged(currentState);
field = value;
OnSpeechToTextStateChanged(field);
}
}
}
} = SpeechToTextState.Stopped;

/// <inheritdoc />
public ValueTask DisposeAsync()
Expand All @@ -43,22 +44,24 @@ public ValueTask DisposeAsync()
static Intent CreateSpeechIntent(SpeechToTextOptions options)
{
var intent = new Intent(RecognizerIntent.ActionRecognizeSpeech);
intent.PutExtra(RecognizerIntent.ExtraLanguagePreference, Java.Util.Locale.Default.ToString());
intent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);

intent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);
intent.PutExtra(RecognizerIntent.ExtraCallingPackage, Application.Context.PackageName);
intent.PutExtra(RecognizerIntent.ExtraPartialResults, options.ShouldReportPartialResults);

var javaLocale = Java.Util.Locale.ForLanguageTag(options.Culture.Name).ToString();
var javaLocale = Java.Util.Locale.ForLanguageTag(options.Culture.Name).ToLanguageTag();
intent.PutExtra(RecognizerIntent.ExtraLanguage, javaLocale);
intent.PutExtra(RecognizerIntent.ExtraLanguagePreference, javaLocale);
intent.PutExtra(RecognizerIntent.ExtraOnlyReturnLanguagePreference, javaLocale);

return intent;
}

static bool IsSpeechRecognitionAvailable() => OperatingSystem.IsAndroidVersionAtLeast(33) && SpeechRecognizer.IsOnDeviceRecognitionAvailable(Application.Context);
static bool IsSpeechRecognitionAvailable() => OperatingSystem.IsAndroidVersionAtLeast(34) && SpeechRecognizer.IsOnDeviceRecognitionAvailable(Application.Context);

[MemberNotNull(nameof(speechRecognizer), nameof(listener))]
[SupportedOSPlatform("Android33.0")]
void InternalStartListening(SpeechToTextOptions options)
[SupportedOSPlatform("Android34.0")]
async Task InternalStartListening(SpeechToTextOptions options, CancellationToken token = default)
{
if (!IsSpeechRecognitionAvailable())
{
Expand All @@ -68,14 +71,28 @@ void InternalStartListening(SpeechToTextOptions options)
var recognizerIntent = CreateSpeechIntent(options);

speechRecognizer = SpeechRecognizer.CreateOnDeviceSpeechRecognizer(Application.Context);
speechRecognizer.TriggerModelDownload(recognizerIntent);

listener = new SpeechRecognitionListener(this)
{
Error = HandleListenerError,
PartialResults = HandleListenerPartialResults,
Results = HandleListenerResults
};

var recognitionSupportTask = new TaskCompletionSource<RecognitionSupport>();
speechRecognizer.CheckRecognitionSupport(recognizerIntent, new Executor(), new RecognitionSupportCallback(recognitionSupportTask));
var recognitionSupportResult = await recognitionSupportTask.Task;
if (!recognitionSupportResult.InstalledOnDeviceLanguages.Contains(options.Culture.Name))
{
if (!recognitionSupportResult.SupportedOnDeviceLanguages.Contains(options.Culture.Name))
{
throw new NotSupportedException($"Culture '{options.Culture.Name}' is not supported");
}

var downloadLanguageTask = new TaskCompletionSource();
speechRecognizer.TriggerModelDownload(recognizerIntent, new Executor(), new ModelDownloadListener(downloadLanguageTask));
await downloadLanguageTask.Task.WaitAsync(token);
}

speechRecognizer.SetRecognitionListener(listener);
speechRecognizer.StartListening(recognizerIntent);
}
Expand Down Expand Up @@ -161,4 +178,47 @@ static void SendResults(Bundle? bundle, Action<string> action)
action.Invoke(matches[0]);
}
}
}

[SupportedOSPlatform("Android33.0")]
class RecognitionSupportCallback(TaskCompletionSource<RecognitionSupport> recognitionSupportTask) : Java.Lang.Object, IRecognitionSupportCallback
{
public void OnError(int error)
{
recognitionSupportTask.TrySetException(new Exception(error.ToString()));
}

public void OnSupportResult(RecognitionSupport recognitionSupport)
{
recognitionSupportTask.TrySetResult(recognitionSupport);
}
}

class Executor : Java.Lang.Object, IExecutor
{
public void Execute(IRunnable? command)
{
command?.Run();
}
}

class ModelDownloadListener(TaskCompletionSource downloadLanguageTask) : Java.Lang.Object, IModelDownloadListener
{
public void OnError(SpeechRecognizerError error)
{
downloadLanguageTask.SetException(new Exception(error.ToString()));
}

public void OnProgress(int completedPercent)
{
}

public void OnScheduled()
{
}

public void OnSuccess()
{
downloadLanguageTask.SetResult();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public sealed partial class OfflineSpeechToTextImplementation
[MemberNotNull(nameof(audioEngine), nameof(recognitionTask), nameof(liveSpeechRequest))]
[SupportedOSPlatform("ios13.0")]
[SupportedOSPlatform("maccatalyst")]
void InternalStartListening(SpeechToTextOptions options)
Task InternalStartListening(SpeechToTextOptions options, CancellationToken token = default)
{
if (!UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
{
Expand Down Expand Up @@ -80,5 +80,7 @@ void InternalStartListening(SpeechToTextOptions options)
}
}
});

return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace CommunityToolkit.Maui.Media;
public sealed partial class OfflineSpeechToTextImplementation
{
[MemberNotNull(nameof(audioEngine), nameof(recognitionTask), nameof(liveSpeechRequest))]
void InternalStartListening(SpeechToTextOptions options)
Task InternalStartListening(SpeechToTextOptions options, CancellationToken token = default)
{
speechRecognizer = new SFSpeechRecognizer(NSLocale.FromLocaleIdentifier(options.Culture.Name));
speechRecognizer.SupportsOnDeviceRecognition = true;
Expand Down Expand Up @@ -91,5 +91,7 @@ void InternalStartListening(SpeechToTextOptions options)
}
}
});

return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public ValueTask DisposeAsync()
return ValueTask.CompletedTask;
}

void InternalStartListening(SpeechToTextOptions options)
Task InternalStartListening(SpeechToTextOptions options, CancellationToken token = default)
{
throw new NotSupportedException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public async Task StartListenAsync(SpeechToTextOptions options, CancellationToke
throw new PermissionException($"{nameof(Permissions)}.{nameof(Permissions.Microphone)} Not Granted");
}

InternalStartListening(options);
await InternalStartListening(options, cancellationToken);
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ void Initialize()
}
}

void InternalStartListening(SpeechToTextOptions options)
Task InternalStartListening(SpeechToTextOptions options, CancellationToken token = default)
{
Initialize();

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

sttClient.Start(options.Culture.Name, recognitionType);
return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public ValueTask DisposeAsync()
return ValueTask.CompletedTask;
}

void InternalStartListening(SpeechToTextOptions options)
Task InternalStartListening(SpeechToTextOptions options, CancellationToken token = default)
{
Initialize(options);

Expand All @@ -49,6 +49,7 @@ void InternalStartListening(SpeechToTextOptions options)
offlineSpeechRecognizer.RecognizeCompleted += OnRecognizeCompleted;
offlineSpeechRecognizer.SpeechRecognized += OnSpeechRecognized;
offlineSpeechRecognizer.RecognizeAsync(RecognizeMode.Multiple);
return Task.CompletedTask;
}

void OnRecognizeCompleted(object? sender, RecognizeCompletedEventArgs args)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Android.Content;
using Android.Runtime;
using Android.Speech;
Expand All @@ -12,7 +11,6 @@ public sealed partial class SpeechToTextImplementation
{
SpeechRecognizer? speechRecognizer;
SpeechRecognitionListener? listener;
CultureInfo? cultureInfo;

/// <inheritdoc />
public SpeechToTextState CurrentState
Expand Down Expand Up @@ -42,13 +40,15 @@ public ValueTask DisposeAsync()
static Intent CreateSpeechIntent(SpeechToTextOptions options)
{
var intent = new Intent(RecognizerIntent.ActionRecognizeSpeech);
intent.PutExtra(RecognizerIntent.ExtraLanguagePreference, Java.Util.Locale.Default.ToString());

intent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);
intent.PutExtra(RecognizerIntent.ExtraCallingPackage, Application.Context.PackageName);
intent.PutExtra(RecognizerIntent.ExtraPartialResults, options.ShouldReportPartialResults);

var javaLocale = Java.Util.Locale.ForLanguageTag(options.Culture.Name).ToString();
var javaLocale = Java.Util.Locale.ForLanguageTag(options.Culture.Name).ToLanguageTag();
intent.PutExtra(RecognizerIntent.ExtraLanguage, javaLocale);
intent.PutExtra(RecognizerIntent.ExtraLanguagePreference, javaLocale);
intent.PutExtra(RecognizerIntent.ExtraOnlyReturnLanguagePreference, javaLocale);

return intent;
}
Expand Down
Loading