Hi All, I have an app that uses the native text to speech api via a plugin. The problem with it is that it works 3 out of 10 times in a test app and less than that in my main app. I'm not really a java programmer thankfully. It is a little bit more reliable if I use speak, but I need a sound file so I am using synthesisToFile. I'm hoping someone can get the success rate up a bit. Here is the java code which i place in Assets > Plugins > Android folder and let gradle build it. I've tried a lot of things that don't work, which are commented out. UtteranceProgressListener never works no matter what I do. Code (CSharp): package ir.hoseinporazar.androidtts; import android.content.Context; import android.os.Build; import android.os.Bundle; import android.speech.tts.TextToSpeech; import android.speech.tts.UtteranceProgressListener; import android.widget.Toast; import java.io.File; import java.util.HashMap; import java.util.Locale; import com.unity3d.player.UnityPlayer; public class TTS { private Context context; private TextToSpeech t1; private String textToSpeak="hello"; private String filePath = ""; private static TTS instance; public float Speed=1f; public float Pitch=1f; private String _gameObject; private String _completedCallback; private String _errorCallback; public TTS() { this.instance = this; } public static TTS instance() { if(instance == null) { instance = new TTS(); } return instance; } public void setContext(Context context) { this.context = context; } public void showMessage(String message) { Toast.makeText(this.context, message, Toast.LENGTH_SHORT).show(); } String Error=""; public void TTSFile(String text, String file, String gameobject, String errorCallback, String completedCallback) { textToSpeak=text; filePath = file; this._gameObject=gameobject; this._errorCallback=errorCallback; this._completedCallback = completedCallback; t1=new TextToSpeech(context, new TextToSpeech.OnInitListener() { @Override public void onInit(int status) { if(status==TextToSpeech.SUCCESS){ int result=t1.setLanguage(Locale.US); if(result==TextToSpeech.LANG_MISSING_DATA||result==TextToSpeech.LANG_NOT_SUPPORTED){ Error="This language is not supported!"; } //} }else{ Error="TTS Initialization failed!"; } } }); //t1.setSpeechRate(Speed); // t1.setPitch(Pitch); t1.setOnUtteranceProgressListener(new UtteranceProgressListener() { @Override public void onStart(String s) {} @Override public void onDone(String utteranceId) { } @Override public void onError(String s) { UnityPlayer.UnitySendMessage(_gameObject, _errorCallback, s); } }); //if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){ // Bundle params = new Bundle(); // String utteranceId=this.hashCode() + ""; // File fileTTS = new File(filePath); // t1.synthesizeToFile(textToSpeak, params, fileTTS, utteranceId); //}else{ HashMap<String, String> map = new HashMap<>(); map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "UniqueID"); int result = t1.synthesizeToFile(textToSpeak, map, filePath); if(result == TextToSpeech.SUCCESS) { UnityPlayer.UnitySendMessage(_gameObject, _completedCallback, filePath); } else { UnityPlayer.UnitySendMessage(_gameObject, this._errorCallback, Error); } } public void SetLang(String loc){ switch (loc){ case "UK": if(t1!=null) t1.setLanguage(Locale.UK); break; case "US": if(t1!=null) t1.setLanguage(Locale.US); break; } } } Here is the text to speech csharp files that interacts with the java code. In the test project I just have a canvas and a button, with an onclick event that calls the speak method with a string. Code (CSharp): using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; [RequireComponent(typeof(AudioSource))] public class TextToSpeech : MonoBehaviour { AudioSource audioSource; private AndroidJavaObject TTSExample = null; private AndroidJavaObject activityContext = null; private Locale _lang; public Locale Language { get { return _lang; } set { SetLanguage(value); } } private float _pitch, _speed; public float Pitch { get { return _pitch; } set { SetPitch(value); } } public float Speed { get { return _speed; } set { SetSpeed(value); } } public delegate void OnErrorCallbackHandler(string error); private OnErrorCallbackHandler _callback; List<string> filePaths = new List<string>(); int count = 5; int index = 0; void Start() { audioSource = GetComponent<AudioSource>(); if (Application.platform == RuntimePlatform.Android) { Initialize(); } filePaths.Add(Path.Combine(Application.persistentDataPath, "Temp0.wav")); filePaths.Add(Path.Combine(Application.persistentDataPath, "Temp1.wav")); filePaths.Add(Path.Combine(Application.persistentDataPath, "Temp2.wav")); filePaths.Add(Path.Combine(Application.persistentDataPath, "Temp3.wav")); filePaths.Add(Path.Combine(Application.persistentDataPath, "Temp4.wav")); } public enum Locale { UK = 0, US = 1 } public void Speak(string toSay) { if (!string.IsNullOrWhiteSpace(toSay)) { // Round Robin of file paths to avoid collisions SpeakToFile(toSay, filePaths[index % count]); index++; } } public void SpeakToFile(string toSay, string file) { if (TTSExample == null) { Initialize(); } TTSExample.Call("TTSFile", toSay, file, gameObject.name, "OnError", "OnCompleted"); } public void OnCompleted(string filePath) { StartCoroutine(LoadAudioFile(filePath)); } IEnumerator LoadAudioFile(string filePath) { yield return new WaitForSeconds(1); WWW www = new WWW("file:///" + filePath); yield return www; audioSource.clip = www.GetAudioClip(); audioSource.Play(); } public void OnError(string error) { if (_callback != null) { if (error.Length > 0) { _callback.Invoke(error); } } ShowToast(error); } public void SetLanguage(Locale lan) { this._lang = lan; string[] Language = new string[] { "UK", "US" }; if (TTSExample == null) { Initialize(); } TTSExample.Call("SetLang", Language[(int)lan]); } public void SetSpeed(float speed) { this._speed = speed; if (TTSExample == null) { Initialize(); } TTSExample.Set<float>("Speed", speed); } public void SetPitch(float pitch) { this._pitch = pitch; if (TTSExample == null) { Initialize(); } TTSExample.Set<float>("Pitch", pitch); } private void Initialize() { if (TTSExample == null) { using (AndroidJavaClass activityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) { activityContext = activityClass.GetStatic<AndroidJavaObject>("currentActivity"); } using (AndroidJavaClass pluginClass = new AndroidJavaClass("ir.hoseinporazar.androidtts.TTS")) { if (pluginClass != null) { TTSExample = pluginClass.CallStatic<AndroidJavaObject>("instance"); TTSExample.Call("setContext", activityContext); } } } } public void ShowToast(string msg) { if (TTSExample == null) { using (AndroidJavaClass activityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) { activityContext = activityClass.GetStatic<AndroidJavaObject>("currentActivity"); } using (AndroidJavaClass pluginClass = new AndroidJavaClass("ir.hoseinporazar.androidtts.TTS")) { if (pluginClass != null) { TTSExample = pluginClass.CallStatic<AndroidJavaObject>("instance"); TTSExample.Call("setContext", activityContext); activityContext.Call("runOnUiThread", new AndroidJavaRunnable(() => { TTSExample.Call("showMessage", msg); })); } } } else { activityContext.Call("runOnUiThread", new AndroidJavaRunnable(() => { TTSExample.Call("showMessage", msg); })); } } }