Search code examples
androidxamarinmvvmcross

How can I use Android TextToSpeak in a MVVMCross plugin?


I have seen plenty of examples of how to use Android TextToSpeak in an Activity, and have also managed to get this to work just fine. I've also managed to get it to work using a bound service in a plugin, but it seems overcomplicated for my purposes. Here is my VoiceService class:

public class VoiceService : IVoiceService, TextToSpeech.IOnInitListener
{
    public event EventHandler FinishedSpeakingEventHandler;

    private TextToSpeech _tts;

    public void Init()
    {
        // Use a speech progress listener so we get notified when the service finishes speaking the prompt
        var progressListener = new SpeechProgressListener();
        progressListener.FinishedSpeakingEventHandler += OnUtteranceCompleted;

        //_tts = new TextToSpeech(Application.Context, this);
        _tts = new TextToSpeech(Mvx.Resolve<IMvxAndroidCurrentTopActivity>().Activity, this);
        _tts.SetOnUtteranceProgressListener(progressListener);
    }

    public void OnInit(OperationResult status)
    {

        // THIS EVENT NEVER FIRES!

        Console.WriteLine("VoiceService TextToSpeech Initialised. Status: " + status);
        if (status == OperationResult.Success)
        {

        }
    }

    public void Speak(string prompt)
    {

        if (!string.IsNullOrEmpty(prompt))
        {
            var map = new Dictionary<string, string> { { TextToSpeech.Engine.KeyParamUtteranceId, new Guid().ToString() } };
            _tts.Speak(prompt, QueueMode.Flush, map);

            Console.WriteLine("tts_Speak: " + prompt);
        }
        else
        {
            Console.WriteLine("tts_Speak: PROMPT IS NULL OR EMPTY!");
        }
    }

    /// <summary>
    /// When we finish speaking, call the event handler
    /// </summary>
    public void OnUtteranceCompleted(object sender, EventArgs e)
    {
        if (FinishedSpeakingEventHandler != null)
        {
            FinishedSpeakingEventHandler(this, new EventArgs());
        }
    }

    public void Dispose()
    {
        //throw new NotImplementedException();
    }

    public IntPtr Handle { get; private set; }
}

Note that the OnInit method never gets called.

In my viewmodel I'd like to do this:

            _voiceService.Init();
            _voiceService.FinishedSpeakingEventHandler += _voiceService_FinishedSpeakingEventHandler;

            ... and then later ...

            _voiceService.Speak(prompt);

When I do this I get these messages in the output:

10-13 08:13:59.734 I/TextToSpeech( 2298): Sucessfully bound to com.google.android.tts (happens when I create the new TTS object)

and

10-13 08:14:43.924 W/TextToSpeech( 2298): speak failed: not bound to TTS engine (when I call tts.Speak(prompt))

If I was using an activity I would create an intent to get this to work, but I'm unsure how to do that in a plugin.

Thanks in advance,

David


Solution

  • Don't implement Handle yourself, instead derive from Java.Lang.Object

    public class VoiceService : Java.Lang.Object, IVoiceService, TextToSpeech.IOnInitListener
    

    and remove your Dispose() and Handle implementation

    More info here: http://developer.xamarin.com/guides/android/advanced_topics/java_integration_overview/android_callable_wrappers/

    Also, I suggest you take an async approach when implementing your service, which would make calling it from view-model something like

    await MvxResolve<ITextToSpeechService>().SpeakAsync(text);