I need to instantiate a TextToSpeech object and set a given language (which is set programmatically and may vary). I know I can use setLanguage() but that will only work if the language is available in the TTS engine that partictual TextToSpeech instance is using. I know I can check whether a language is available by means of myTTS.isLanguageAvailable() but that will only tell me whether the language is available on the current engine.
The problem is that the user may have more than one TTS engine installed and the desired language may be available in one of them but not in the default one. In that case I want to find the engine, use it and set the language.
So I need to loop through the available TTS engines and "ask" each one whether it has the desired language available.
I have tried this:
mUserLocale=new Locale("it-IT"); //just an example
mTextToSpeech=new TextToSpeech(getApplicationContext(), this);
if (mTextToSpeech.isLanguageAvailable(mUserLocale)<0) {
List<TextToSpeech.EngineInfo> engines=mTextToSpeech.getEngines();
int currentmatchquality=-1;
String defaultTTSEngine=mTextToSpeech.getDefaultEngine();
mTextToSpeech.shutdown();
mTextToSpeech=null;
for (int i=0; i<engines.size(); i++) {
TextToSpeech.EngineInfo engineinfo=engines.get(i);
Log.d("MainActivity", "Examining TTS engine "+engineinfo.name);
if (engineinfo.name.equals(defaultTTSEngine)) {
Log.d("MainActivity", "Skipping default TTS engine "+engineinfo.name);
continue;
}
TextToSpeech candidateTTS=new TextToSpeech(getApplicationContext(),this,engineinfo.name);
int matchquality=candidateTTS.isLanguageAvailable(mUserLocale);
if (matchquality>currentmatchquality) {
Log.d("MainActivity", "Selecting TTS engine "+engineinfo.name);
mTextToSpeech.shutdown();
mTextToSpeech=candidateTTS;
mTTSEngine=engineinfo.name;
currentmatchquality=matchquality;
}
else {
Log.d("MainActivity", " "+mUserLocale.toString()+" not available on this engine: "+matchquality);
}
}
if (mTTSEngine==null) mTTSEngine=defaultTTSEngine;
mTextToSpeech=new TextToSpeech(getApplicationContext(),this,mTTSEngine);
}
if (mTextToSpeech.isLanguageAvailable(mUserLocale)>=0) mTextToSpeech.setLanguage(mUserLocale);
The problem is that I systematically get -2 as the result from isLanguageAvailable:
D/MainActivity﹕ Examining TTS engine com.google.android.tts
I/TextToSpeech﹕ Sucessfully bound to com.google.android.tts
W/TextToSpeech﹕ isLanguageAvailable failed: not bound to TTS engine
D/MainActivity﹕ it-it not available on this engine: -2
I guess it's because I need to wait for the init event of the TTS before I query it for an available language.
That would be cumbersome. Is there a way to loop through existing TTS engines and check whether a language is available in each of them (or get a list of all available languages per engine) without instantiating a TextToSpeech object for each engine and waiting for it to be initialized??
Here is the setup I use for debugging - You could use elements of it in production:
// Container Class
public class ContainerVoiceEngine {
private String label;
private String packageName;
private ArrayList<String> voices;
private Intent intent;
public ContainerVoiceEngine() {
}
public ContainerVoiceEngine(final String label, final String packageName, final ArrayList<String> voices, final Intent intent) {
this.label = label;
this.packageName = packageName;
this.voices = voices;
this.intent = intent;
}
public Intent getIntent() {
return intent;
}
public void setIntent(final Intent intent) {
this.intent = intent;
}
public String getLabel() {
return label;
}
public void setLabel(final String label) {
this.label = label;
}
public String getPackageName() {
return packageName;
}
public void setPackageName(final String packageName) {
this.packageName = packageName;
}
public ArrayList<String> getVoices() {
return voices;
}
public void setVoices(final ArrayList<String> voices) {
this.voices = voices;
}
}
// Usage within an Activity - Debugging only!
private ArrayList<ContainerVoiceEngine> containerVEArray;
private int requestCount;
private void getEngines() {
requestCount = 0;
final Intent ttsIntent = new Intent();
ttsIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
final PackageManager pm = getPackageManager();
final List<ResolveInfo> list = pm.queryIntentActivities(ttsIntent, PackageManager.GET_META_DATA);
containerVEArray = new ArrayList<ContainerVoiceEngine>(list.size());
for (int i = 0; i < list.size(); i++) {
final ContainerVoiceEngine cve = new ContainerVoiceEngine();
cve.setLabel(list.get(i).loadLabel(pm).toString());
cve.setPackageName(list.get(i).activityInfo.applicationInfo.packageName);
final Intent getIntent = new Intent();
getIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
getIntent.setPackage(cve.getPackageName());
getIntent.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
getIntent.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES);
cve.setIntent(getIntent);
containerVEArray.add(cve);
}
Log.d("TAG", "containerVEArray: " + containerVEArray.size());
for (int i = 0; i < containerVEArray.size(); i++) {
startActivityForResult(containerVEArray.get(i).getIntent(), i);
}
}
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
Log.i("TAG", "onActivityResult: requestCount: " + " - requestCode: " + requestCode);
requestCount++;
try {
if (data != null) {
final Bundle bundle = data.getExtras();
if (bundle != null) {
Log.d("TAG", containerVEArray.get(requestCode).getLabel() + " - Bundle Data");
final Set<String> keys = bundle.keySet();
final Iterator<String> it = keys.iterator();
while (it.hasNext()) {
final String key = it.next();
Log.d("TAG", "Key: " + key + " = " + bundle.get(key));
}
}
if (data.hasExtra("availableVoices")) {
containerVEArray.get(requestCode).setVoices(data.getStringArrayListExtra("availableVoices"));
} else {
containerVEArray.get(requestCode).setVoices(new ArrayList<String>());
}
}
if (requestCount == containerVEArray.size()) {
for (int i = 0; i < containerVEArray.size(); i++) {
Log.v("TAG", "cve: " + containerVEArray.get(i).getLabel() + " - "
+ containerVEArray.get(i).getVoices().size() + " - " + containerVEArray.get(i).getVoices().toString());
}
}
} catch (final IndexOutOfBoundsException e) {
Log.e("TAG", "IndexOutOfBoundsException");
e.printStackTrace();
} catch (final NullPointerException e) {
Log.e("TAG", "NullPointerException");
e.printStackTrace();
} catch (final Exception e) {
Log.e("TAG", "Exception");
e.printStackTrace();
}
}
Hope that helps.
Edit
Further to your comment, these 'voices' are in actual fact the languages, in the sense that they return a representative Locale in their structure. Be aware though, the Locale is in a different format to the System Locale, which is really annoying and needs to be sorted in a future Android update.
Take a look at my answer here regarding the pitfalls with using tts.setLanguage(Locale). The question also deals with creating a Locale - Locale loc = new Locale(String).
You can use a loop for the ArrayList of available languages to try and construct a Locale from each, for each Engine.
Hope that helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With