1

I am trying out Microsoft .NET MAUI that currently in Preview stage. I try to make a small Android app that will use Google voice recognizer service as a way to let user navigate the app. Just a small demo to see what can I do with it. This is also my first time to actually write a Xamarin/MAUI project, so I am not really sure what I can actually do wit the platform.

The problem is that I would like to have this Google service to always on (without timeout) or auto-close then re-open when timeout. In short, I want user to never actually have to deal with this screen:

enter image description here

My intention is that the will be a background thread to keep asking user to say the command, only stop when user do, and the service will always ready to receive the speech. However, I am unable to keep the above service always on or auto-close=>reopen when timeout.

I am search around and it seems that I cannot change the timeout of the service, so the only way is trying to auto-close=>reopen the service, but I don't know how to.

The below is my code, could you guy give me some direction with it?

1. The login page: only have username and password field, use will be asked to say the username. If it is exist, then asked to say password.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiDemo.LoginPage"
             BackgroundColor="White">
    
    <ContentPage.Content>
        <StackLayout Margin="30" VerticalOptions="StartAndExpand">

            <Label 
                x:Name="lblTitle"
                HorizontalTextAlignment="Center"
                FontSize="Large"
                FontAttributes="Bold"
                />

            <Label/>
            
            <Button
                x:Name="btnSpeak"
                Text="Start"
                Clicked="btnSpeak_Clicked"
                FontAttributes="Bold"
                BackgroundColor="DarkGreen"
                />

            <Label/>

            <Label 
                x:Name="lblUsername"
                Text="Username" 
                FontAttributes="Bold"
                />
            <Entry
                x:Name="txtUsername"
                TextColor="Black"
                FontSize="18"
                VerticalOptions="StartAndExpand"
                HorizontalOptions="Fill" 
                IsReadOnly="True"
                />

            <Label/>

            <Label
                x:Name="lblPassword"
                Text="Password"
                FontAttributes="Bold"
                />
            <Entry
                x:Name="txtPassword"
                IsPassword="True"
                TextColor="Black"
                FontSize="18"
                VerticalOptions="StartAndExpand"
                HorizontalOptions="Fill" 
                IsReadOnly="True"
                />

            <Label/>

            <Label
                x:Name="lblDisplayname"
                Text="Name"
                FontAttributes="Bold"
                />
            <Label
                x:Name="txtDisplayname"
                />

            <Label/>

            <Label 
                x:Name="lblMessage"
                Text=""/>

        </StackLayout>
    </ContentPage.Content>
</ContentPage>
using MauiDemo.Common;
using MauiDemo.Speech;
using Microsoft.Maui.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace MauiDemo
{
    public partial class LoginPage : ContentPage
    {
        private string _field = string.Empty;
        private int _waitTime = 2000;

        public List<Language> Languages { get; }

        private SpeechToTextImplementation _speechRecongnitionInstance;

        private struct VoiceMode
        {
            int Username = 1;
            int Password = 2;
        }

        public LoginPage()
        {
            InitializeComponent();
            this.lblTitle.Text = "Login" + App.Status;

            CheckMicrophone();

            CommonData.CurrentField = string.Empty;

            try
            {
                _speechRecongnitionInstance = new SpeechToTextImplementation();
                _speechRecongnitionInstance.Language = DefaultData.SettingLanguage;
            }
            catch (Exception ex)
            {
                DisplayAlert("Error", ex.Message, "OK");
            }

            MessagingCenter.Subscribe<ISpeechToText, string>(this, "STT", (sender, args) =>
            {
                ReceivedUsernameAsync(args);
            });

            MessagingCenter.Subscribe<ISpeechToText>(this, "Final", (sender) =>
            {
                btnSpeak.IsEnabled = true;
            });

            MessagingCenter.Subscribe<IMessageSender, string>(this, "STT", (sender, args) =>
            {
                SpeechToTextRecievedAsync(args);
            });

            isReceiveUsername = false;
            isReceivePassword = false;
            RequestUsername();
        }

        protected override void OnDisappearing()
        {
            CommonData.CurrentField = string.Empty;
            base.OnDisappearing();
        }

        private async void btnSpeak_Clicked(Object sender, EventArgs e)
        {
            isReceiveUsername = false;
            isReceivePassword = false;
            await RequestUsername();
        }

        private async void SpeechToTextRecievedAsync(string args)
        {
            switch (_field)
            {
                case "Username":
                    await this.ReceivedUsernameAsync(args);
                    break;

                case "Password":
                    await this.ReceivedPasswordAsync(args);
                    break;

            }
        }

        bool isReceiveUsername = false;
        bool isReceivePassword = false;

        private async Task ReceivedUsernameAsync(string args)
        {
            txtUsername.Text = args.Replace(" ", string.Empty);
            lblMessage.Text = string.Empty;

            if (string.IsNullOrWhiteSpace(txtUsername.Text))
            {
                isReceiveUsername = false;
            }
            else
            {
                isReceiveUsername = true;
                var checkUser = DefaultData.Users.Where(x => x.Username.ToLower().Equals(txtUsername.Text.ToLower()));
                if (checkUser.Any())
                {
                    await RequestPassword();
                }
                else
                {
                    string message = CommonData.GetMessage(MessageCode.WrongUsername);
                    lblMessage.Text = message;
                    isReceiveUsername = false;
                    await RequestUsername(message);
                }
            }
        }

        private async Task ReceivedPasswordAsync(string args)
        {
            txtPassword.Text = args.Replace(" ", string.Empty);
            lblMessage.Text = string.Empty;

            if (string.IsNullOrWhiteSpace(txtPassword.Text))
            {
                isReceivePassword = false;
            }
            else
            {
                isReceivePassword = true;
                var checkUser = DefaultData.Users.Where(x => x.Username.ToLower().Equals(txtUsername.Text.ToLower()) && x.Password.Equals(txtPassword.Text));
                if (checkUser.Any())
                {
                    _field = "";
                    lblDisplayname.Text = checkUser.FirstOrDefault().Displayname;

                    string msg = CommonData.GetMessage(MessageCode.LoginSuccess);
                    await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
                        msg
                        , crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
                        , speakRate: DefaultData.SettingSpeed
                        , pitch: DefaultData.SettingPitch
                        );

                    await Navigation.PushAsync(new MainPage());
                }
                else
                {
                    string message = CommonData.GetMessage(MessageCode.WrongPassword);
                    lblMessage.Text = message;
                    isReceivePassword = false;
                    await RequestPassword(message);
                }
            }
        }

        private async Task RepeatVoiceUsername(string message)
        {
            do
            {
                //_speechRecongnitionInstance.StopSpeechToText();
                //_speechRecongnitionInstance.StartSpeechToText();
                await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
                    message
                    , crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
                    , speakRate: DefaultData.SettingSpeed
                    , pitch: DefaultData.SettingPitch
                    );
                Thread.Sleep(_waitTime);
            }
            while (!isReceiveUsername);
        }

        private async Task RepeatVoicePassword(string message)
        {
            do
            {
                //_speechRecongnitionInstance.StopSpeechToText();
                //_speechRecongnitionInstance.StartSpeechToText();
                await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
                    message
                    , crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
                    , speakRate: DefaultData.SettingSpeed
                    , pitch: DefaultData.SettingPitch
                    );
                Thread.Sleep(_waitTime);
            }
            while (!isReceivePassword);
        }

        private bool CheckMicrophone()
        {
            string rec = Android.Content.PM.PackageManager.FeatureMicrophone;
            if (rec != "android.hardware.microphone")
            {
                // no microphone, no recording. Disable the button and output an alert
                DisplayAlert("Error", CommonData.GetMessage(MessageCode.SettingSaveSuccess), "OK");
                btnSpeak.IsEnabled = false;
                return false;
            }
            return true;
        }

        private async Task RequestUsername(string message = "")
        {
            _field = "Username";
            isReceiveUsername = false;
            txtUsername.Text = string.Empty;
            lblDisplayname.Text = string.Empty;
            txtUsername.Focus();
            message =  (message.IsNullOrWhiteSpace() ? CommonData.GetMessage(MessageCode.InputUsername) : message);

            Task.Run(() => RepeatVoiceUsername(message));
            
            _speechRecongnitionInstance.StartSpeechToText(_field);
        }

        private async Task RequestPassword(string message = "")
        {
            _field = "Password";
            isReceivePassword = false;
            txtPassword.Text = string.Empty;
            lblDisplayname.Text = string.Empty;
            txtPassword.Focus();
            message = (message.IsNullOrWhiteSpace() ? CommonData.GetMessage(MessageCode.InputPassword) : message);

            Task.Run(() => RepeatVoicePassword(message));

            _speechRecongnitionInstance.StartSpeechToText(_field);
        }
    }
}

2. The Speech Recognizer class:

using Android.App;
using Android.Content;
using Android.Speech;
using Java.Util;
using Plugin.CurrentActivity;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace MauiDemo.Speech
{
    public class SpeechToTextImplementation
    {
        public static AutoResetEvent autoEvent = new AutoResetEvent(false);
        private readonly int VOICE = 10;
        private Activity _activity;
        private float _timeOut = 3;
        private string _text;

        public SpeechToTextImplementation()
        {
            _activity = CrossCurrentActivity.Current.Activity;

        }
        public SpeechToTextImplementation(string text)
        {
            _text = text;
            _activity = CrossCurrentActivity.Current.Activity;
        }

        public string Language;

        public void StartSpeechToText()
        {
            StartRecordingAndRecognizing();
        }

        public void StartSpeechToText(string text)
        {
            _text = text;
            StartRecordingAndRecognizing();
        }

        private async void StartRecordingAndRecognizing()
        {
            string rec = global::Android.Content.PM.PackageManager.FeatureMicrophone;
            if (rec == "android.hardware.microphone")
            {
                try
                {
                    var locale = Locale.Default;
                    if (!string.IsNullOrWhiteSpace(Language))
                    {
                        locale = new Locale(Language);
                    }
                    Intent voiceIntent = new Intent(RecognizerIntent.ActionRecognizeSpeech);
                    voiceIntent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);

                    voiceIntent.PutExtra(RecognizerIntent.ExtraPrompt, _text);
                    voiceIntent.PutExtra(RecognizerIntent.ExtraSpeechInputCompleteSilenceLengthMillis, _timeOut * 1000);
                    voiceIntent.PutExtra(RecognizerIntent.ExtraSpeechInputPossiblyCompleteSilenceLengthMillis, _timeOut * 1000);
                    voiceIntent.PutExtra(RecognizerIntent.ExtraSpeechInputMinimumLengthMillis, _timeOut * 1000);
                    voiceIntent.PutExtra(RecognizerIntent.ExtraMaxResults, 1);

                    voiceIntent.PutExtra(RecognizerIntent.ExtraLanguage, locale.ToString());
                    _activity.StartActivityForResult(voiceIntent, VOICE);

                    await Task.Run(() => { autoEvent.WaitOne(new TimeSpan(0, 2, 0)); });

                }
                catch (ActivityNotFoundException ex)
                {
                    String appPackageName = "com.google.android.googlequicksearchbox";
                    try
                    {
                        Intent intent = new Intent(Intent.ActionView, global::Android.Net.Uri.Parse("market://details?id=" + appPackageName));
                        _activity.StartActivityForResult(intent, VOICE);

                    }
                    catch (ActivityNotFoundException e)
                    {
                        Intent intent = new Intent(Intent.ActionView, global::Android.Net.Uri.Parse("https://play.google.com/store/apps/details?id=" + appPackageName));
                        _activity.StartActivityForResult(intent, VOICE);
                    }
                }

            }
            else
            {
                throw new Exception("No mic found");
            }
        }

        public void StopSpeechToText()
        {
            // Do something here to close the service
        }
    }
}

3. MainActivity:

using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Speech;
using MauiDemo.Common;
using Microsoft.Maui;
using Microsoft.Maui.Controls;

namespace MauiDemo
{
    [Activity(Label = "Maui Demo", Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
    public class MainActivity : MauiAppCompatActivity, IMessageSender
    {
        private readonly int VOICE = 10;

        protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
        {

            if (requestCode == VOICE)
            {
                if (resultCode == Result.Ok)
                {
                    var matches = data.GetStringArrayListExtra(RecognizerIntent.ExtraResults);
                    if (matches.Count != 0)
                    {
                        string textInput = matches[0];
                        MessagingCenter.Send<IMessageSender, string>(this, "STT", textInput);
                    }
                    else
                    {
                        MessagingCenter.Send<IMessageSender, string>(this, "STT", "");
                    }
                }
            }
            base.OnActivityResult(requestCode, resultCode, data);
        }
    }
}

4. MainApplication

using Android.App;
using Android.OS;
using Android.Runtime;
using Microsoft.Maui;
using Microsoft.Maui.Hosting;
using Plugin.CurrentActivity;
using System;

namespace MauiDemo
{
    [Application]
    public class MainApplication : MauiApplication
    {
        public MainApplication(IntPtr handle, JniHandleOwnership ownership)
            : base(handle, ownership)
        {
        }

        protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
        public override void OnCreate()
        {
            base.OnCreate();
            CrossCurrentActivity.Current.Init(this);
        }

        public override void OnTerminate()
        {
            base.OnTerminate();
        }

        public void OnActivityCreated(Activity activity, Bundle savedInstanceState)
        {
            CrossCurrentActivity.Current.Activity = activity;
        }

        public void OnActivityDestroyed(Activity activity)
        {
        }

        public void OnActivityPaused(Activity activity)
        {
        }

        public void OnActivityResumed(Activity activity)
        {
            CrossCurrentActivity.Current.Activity = activity;
        }

        public void OnActivitySaveInstanceState(Activity activity, Bundle outState)
        {
        }

        public void OnActivityStarted(Activity activity)
        {
            CrossCurrentActivity.Current.Activity = activity;
        }

        public void OnActivityStopped(Activity activity)
        {
        }
    }
}
N.D.H.Vu
  • 127
  • 1
  • 16

1 Answers1

2

After struggle for a few days without any success, I found a new way to do this thing by using SpeechRecognizer class, instead of using a Google service. With this, I am able to have a better control on the process.

To use SpeechRecognizer, I copied the code in "Create platform microphone services" for permission from this Microsoft page: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/azure-cognitive-services/speech-recognition

I have update my code as below:

  1. Login page: currently is named Prototype2.
using MauiDemo.Common;
using MauiDemo.Speech;
using Microsoft.Maui.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace MauiDemo.View
{
    public partial class Prototype2 : ContentPage
    {
        private string _field = string.Empty;
        private int _waitTime = 2000;

        public List<Language> Languages { get; }

        private SpeechToTextImplementation2 _speechRecognizer;

        //private BackgroundWorker worker = new BackgroundWorker();

        private struct VoiceMode
        {
            int Username = 1;
            int Password = 2;
        }

        public Prototype2()
        {
            InitializeComponent();
            this.lblTitle.Text = "Prototype2" + App.Status;

            CheckMicrophone();

            CommonData.CurrentField = string.Empty;

            try
            {
                _speechRecognizer = new SpeechToTextImplementation2();
                _speechRecognizer.Language = DefaultData.SettingLanguage;
            }
            catch (Exception ex)
            {
                DisplayAlert("Error", ex.Message, "OK");
            }

            MessagingCenter.Subscribe<ISpeechToText, string>(this, "STT", (sender, args) =>
            {
                ReceivedUsernameAsync(args);
            });

            MessagingCenter.Subscribe<ISpeechToText>(this, "Final", (sender) =>
            {
                btnSpeak.IsEnabled = true;
            });

            MessagingCenter.Subscribe<IMessageSender, string>(this, "STT", (sender, args) =>
            {
                SpeechToTextRecievedAsync(args);
            });

            isReceiveUsername = false;
            isReceivePassword = false;
            RequestUsername(true);
        }

        protected override void OnDisappearing()
        {
            CommonData.CurrentField = string.Empty;
            base.OnDisappearing();
        }

        private async void btnSpeak_Clicked(Object sender, EventArgs e)
        {
            isReceiveUsername = false;
            isReceivePassword = false;
            await RequestUsername(true);
        }

        private async void SpeechToTextRecievedAsync(string args)
        {
            switch (_field)
            {
                case "Username":
                    await this.ReceivedUsernameAsync(args);
                    break;

                case "Password":
                    await this.ReceivedPasswordAsync(args);
                    break;

            }
        }

        bool isReceiveUsername = false;
        bool isReceivePassword = false;

        private async Task ReceivedUsernameAsync(string args)
        {
            txtUsername.Text = args.Replace(" ", string.Empty);
            lblMessage.Text = string.Empty;

            if (string.IsNullOrWhiteSpace(txtUsername.Text))
            {
                isReceiveUsername = false;
            }
            else
            {
                isReceiveUsername = true;
                var checkUser = DefaultData.Users.Where(x => x.Username.ToLower().Equals(txtUsername.Text.ToLower()));
                if (checkUser.Any())
                {
                    await RequestPassword(true);
                }
                else
                {
                    string message = CommonData.GetMessage(MessageCode.WrongUsername);
                    lblMessage.Text = message;
                    isReceiveUsername = false;
                    await RequestUsername(false, message);
                }
            }
        }

        private async Task ReceivedPasswordAsync(string args)
        {
            txtPassword.Text = args.Replace(" ", string.Empty);
            lblMessage.Text = string.Empty;

            if (string.IsNullOrWhiteSpace(txtPassword.Text))
            {
                isReceivePassword = false;
            }
            else
            {
                isReceivePassword = true;
                var checkUser = DefaultData.Users.Where(x => x.Username.ToLower().Equals(txtUsername.Text.ToLower()) && x.Password.Equals(txtPassword.Text));
                if (checkUser.Any())
                {
                    _field = "";
                    lblDisplayname.Text = checkUser.FirstOrDefault().Displayname;

                    string msg = CommonData.GetMessage(MessageCode.LoginSuccess);
                    await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
                        msg
                        , crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
                        , speakRate: DefaultData.SettingSpeed
                        , pitch: DefaultData.SettingPitch
                        );

                    await Navigation.PushAsync(new MainPage());
                }
                else
                {
                    string message = CommonData.GetMessage(MessageCode.WrongPassword);
                    lblMessage.Text = message;
                    isReceivePassword = false;
                    await RequestPassword(false, message);
                }
            }
        }

        private async Task RepeatVoiceUsername(string message)
        {
            do
            {
                await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
                    message
                    , crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
                    , speakRate: DefaultData.SettingSpeed
                    , pitch: DefaultData.SettingPitch
                    );
                Thread.Sleep(_waitTime);
            }
            while (!isReceiveUsername);
        }

        private async Task RepeatVoicePassword(string message)
        {
            do
            {
                await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
                    message
                    , crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
                    , speakRate: DefaultData.SettingSpeed
                    , pitch: DefaultData.SettingPitch
                    );
                Thread.Sleep(_waitTime);
            }
            while (!isReceivePassword);
        }

        private bool CheckMicrophone()
        {
            string rec = Android.Content.PM.PackageManager.FeatureMicrophone;
            if (rec != "android.hardware.microphone")
            {
                // no microphone, no recording. Disable the button and output an alert
                DisplayAlert("Error", CommonData.GetMessage(MessageCode.SettingSaveSuccess), "OK");
                btnSpeak.IsEnabled = false;
                return false;
            }
            return true;
        }

        private async Task RequestUsername(bool isRepeat, string message = "")
        {
            _field = "Username";
            isReceiveUsername = false;
            //txtUsername.Text = string.Empty;
            //lblDisplayname.Text = string.Empty;
            txtUsername.Focus();
            message = (message.IsNullOrWhiteSpace() ? CommonData.GetMessage(MessageCode.InputUsername) : message);
            if (isRepeat)
            {
                Task.Run(() => RepeatVoiceUsername(message));
            }
            else
            {
                await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
                    message
                    , crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
                    , speakRate: DefaultData.SettingSpeed
                    , pitch: DefaultData.SettingPitch
                    );
            }
            _speechRecognizer.StartListening();
        }

        private async Task RequestPassword(bool isRepeat, string message = "")
        {
            _field = "Password";
            isReceivePassword = false;
            //txtPassword.Text = string.Empty;
            //lblDisplayname.Text = string.Empty;
            txtPassword.Focus();
            message = (message.IsNullOrWhiteSpace() ? CommonData.GetMessage(MessageCode.InputPassword) : message);
            if (isRepeat)
            {
                Task.Run(() => RepeatVoicePassword(message));
            }
            else
            {
                await Plugin.TextToSpeech.CrossTextToSpeech.Current.Speak(
                    message
                    , crossLocale: CommonData.GetCrossLocale(DefaultData.SettingLanguage)
                    , speakRate: DefaultData.SettingSpeed
                    , pitch: DefaultData.SettingPitch
                    );
            }
            _speechRecognizer.StartListening();
        }
    }
}
  1. New Microphone Service to handle the permission
using Android.App;
using Android.Content.PM;
using Android.OS;
using AndroidX.Core.App;
using Google.Android.Material.Snackbar;
using System.Threading.Tasks;

namespace MauiDemo.Speech
{
    public class MicrophoneService
    {
        public const int RecordAudioPermissionCode = 1;
        private TaskCompletionSource<bool> tcsPermissions;
        string[] permissions = new string[] { Manifest.Permission.RecordAudio };

        public MicrophoneService()
        {
            tcsPermissions = new TaskCompletionSource<bool>();
        }

        public Task<bool> GetPermissionAsync()
        {

            if ((int)Build.VERSION.SdkInt < 23)
            {
                tcsPermissions.TrySetResult(true);
            }
            else
            {
                var currentActivity = MainActivity.Instance;
                if (ActivityCompat.CheckSelfPermission(currentActivity, Manifest.Permission.RecordAudio) != (int)Permission.Granted)
                {
                    RequestMicPermissions();
                }
                else
                {
                    tcsPermissions.TrySetResult(true);
                }

            }

            return tcsPermissions.Task;
        }

        public void OnRequestPermissionResult(bool isGranted)
        {
            tcsPermissions.TrySetResult(isGranted);
        }

        void RequestMicPermissions()
        {
            if (ActivityCompat.ShouldShowRequestPermissionRationale(MainActivity.Instance, Manifest.Permission.RecordAudio))
            {
                Snackbar.Make(MainActivity.Instance.FindViewById(Android.Resource.Id.Content),
                        "Microphone permissions are required for speech transcription!",
                        Snackbar.LengthIndefinite)
                        .SetAction("Ok", v =>
                        {
                            ((Activity)MainActivity.Instance).RequestPermissions(permissions, RecordAudioPermissionCode);
                        })
                        .Show();
            }
            else
            {
                ActivityCompat.RequestPermissions((Activity)MainActivity.Instance, permissions, RecordAudioPermissionCode);
            }
        }
    }
}
  1. New Speech=>Text class to use SpeechRecognizer: Mostly take frrm this How to increase the voice listen time in Google Recognizer Intent(Speech Recognition) Android
using Android;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Speech;
using AndroidX.Core.App;
using Java.Util;
using MauiDemo.Common;
using Microsoft.Maui.Controls;
using Plugin.CurrentActivity;
using System.Threading;

namespace MauiDemo.Speech
{

    public class SpeechToTextImplementation2 : Java.Lang.Object, IRecognitionListener, IMessageSender
    {
        public static AutoResetEvent autoEvent = new AutoResetEvent(false);
        private readonly int VOICE = 10;
        private Activity _activity;
        private float _timeOut = 3;
        private SpeechRecognizer _speech;
        private Intent _speechIntent;
        public string Words;
        public string Language;
        private MicrophoneService micService;

        public SpeechToTextImplementation2()
        {
            micService = new MicrophoneService();
            _activity = CrossCurrentActivity.Current.Activity;
            var locale = Locale.Default;
            if (!string.IsNullOrWhiteSpace(Language))
            {
                locale = new Locale(Language);
            }
            _speech = SpeechRecognizer.CreateSpeechRecognizer(this._activity);
            _speech.SetRecognitionListener(this);
            _speechIntent = new Intent(RecognizerIntent.ActionRecognizeSpeech);
            _speechIntent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);

            _speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputCompleteSilenceLengthMillis, _timeOut * 1000);
            _speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputPossiblyCompleteSilenceLengthMillis, _timeOut * 1000);
            _speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputMinimumLengthMillis, _timeOut * 1000);
            _speechIntent.PutExtra(RecognizerIntent.ExtraMaxResults, 1);

            _speechIntent.PutExtra(RecognizerIntent.ExtraLanguage, locale.ToString());

        }


        void RestartListening()
        {
            var locale = Locale.Default;
            if (!string.IsNullOrWhiteSpace(Language))
            {
                locale = new Locale(Language);
            }

            _speech.Destroy();
            _speech = SpeechRecognizer.CreateSpeechRecognizer(this._activity);
            _speech.SetRecognitionListener(this);
            _speechIntent = new Intent(RecognizerIntent.ActionRecognizeSpeech);
            _speechIntent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);
            _speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputCompleteSilenceLengthMillis, _timeOut * 1000);
            _speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputPossiblyCompleteSilenceLengthMillis, _timeOut * 1000);
            _speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputMinimumLengthMillis, _timeOut * 1000);
            _speechIntent.PutExtra(RecognizerIntent.ExtraMaxResults, 1);
            _speechIntent.PutExtra(RecognizerIntent.ExtraLanguage, locale.ToString());
            StartListening();
        }

        public async void StartListening()
        {
            bool isMicEnabled = await micService.GetPermissionAsync();
            if (!isMicEnabled)
            {
                Words = "Please grant access to the microphone!";
                return;
            }
            _speech.StartListening(_speechIntent);
        }

        public void StopListening()
        {
            _speech.StopListening();
        }

        public void OnBeginningOfSpeech()
        {

        }

        public void OnBufferReceived(byte[] buffer)
        {
        }

        public void OnEndOfSpeech()
        {

        }

        public void OnError([GeneratedEnum] SpeechRecognizerError error)
        {
            Words = error.ToString();
            MessagingCenter.Send<IMessageSender, string>(this, "Error", Words);
            RestartListening();
        }

        public void OnEvent(int eventType, Bundle @params)
        {
        }

        public void OnPartialResults(Bundle partialResults)
        {
        }

        public void OnReadyForSpeech(Bundle @params)
        {
        }

        public void OnResults(Bundle results)
        {

            var matches = results.GetStringArrayList(SpeechRecognizer.ResultsRecognition);
            if (matches == null)
                Words = "Null";
            else
                if (matches.Count != 0)
                Words = matches[0];
            else
                Words = "";

            MessagingCenter.Send<IMessageSender, string>(this, "STT", Words);

            RestartListening();
        }

        public void OnRmsChanged(float rmsdB)
        {

        }
    }
}
  1. Update MainActivities for permission
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
using Android.Speech;
using MauiDemo.Common;
using MauiDemo.Speech;
using Microsoft.Maui;
using Microsoft.Maui.Controls;

namespace MauiDemo
{
    [Activity(Label = "Maui Demo", Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
    public class MainActivity : MauiAppCompatActivity, IMessageSender
    {


        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            Instance = this;
            micService = new MicrophoneService();
        }


        private readonly int VOICE = 10;

        protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
        {

            if (requestCode == VOICE)
            {
                if (resultCode == Result.Ok)
                {
                    var matches = data.GetStringArrayListExtra(RecognizerIntent.ExtraResults);
                    if (matches.Count != 0)
                    {
                        string textInput = matches[0];
                        MessagingCenter.Send<IMessageSender, string>(this, "STT", textInput);
                    }
                    else
                    {
                        MessagingCenter.Send<IMessageSender, string>(this, "STT", "");
                    }

                    //SpeechToTextImplementation.autoEvent.Set();
                }
            }
            base.OnActivityResult(requestCode, resultCode, data);
        }

        MicrophoneService micService;
        internal static MainActivity Instance { get; private set; }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            // ...
            switch (requestCode)
            {
                case MicrophoneService.RecordAudioPermissionCode:
                    if (grantResults[0] == Permission.Granted)
                    {
                        micService.OnRequestPermissionResult(true);
                    }
                    else
                    {
                        micService.OnRequestPermissionResult(false);
                    }
                    break;
            }
        }
    }
}

Feel free to check out the code, but I will not use this for anything serious because it does not runs properly yet.

Any opinion to improve for the code will be really appreciated, as I really want to get good with this MAUI platform.

N.D.H.Vu
  • 127
  • 1
  • 16