Reputation: 7234
The TextToSpeech constructor looks like it's designed to be 'owned' by an Activity. I'm producing an app with multiple different Activities, and I don't want to have to initialise a new TextToSpeech instance for each - I want the speech to carry on smoothly even if the Activity is changing.
My idea is to have a static TextToSpeech object accessed by all activities, initialised by the first one.
Upvotes: 5
Views: 3082
Reputation: 5986
TextToSpeech is not thread-safe with respect to the GUI, because a TextToSpeech listener method is called from a non-GUI thread.
If your listener methods interact with the GUI, you will have to include code to put the GUI changes into the Looper for the GUI thread.
There are plenty of examples of how to wrap a GUI command in a Handler and post it on the GUI thread's looper. Here is a sketch of what you'd do:
public class SpeechUtteranceListener extends UtteranceProgressListener {
@Override
public void onDone(String utteranceId) {
Runnable guiCommand = new Runnable() {
@Override
public void run() {
someButton.setEnabled(true);
}
}
};
runOnUiThread(asrStartCommand);
}
private void runOnUiThread(Runnable command){
Looper.getMainLooper().post(command);
}
}
Upvotes: 0
Reputation: 1
The above was helpful in helping me resolve this issue. In my case, I had also had a fragment and so, I did the following:
From a fragment (from a fragment, you want to say "getActivity().getApplicationContext()" instead of just "getApplicationContext()") :
public void onActivityResult(int requestCode, int resultCode, Intent data){
if(requestCode == MY_DATA_CHECK_CODE){
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
tts = new TextToSpeech(getActivity().getApplicationContext(), new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
if(status == TextToSpeech.SUCCESS){
result = tts.setLanguage(Locale.UK);
}
}
});
} else {
// missing data, install it
Intent installIntent = new Intent();
// The ACTION_INSTALL_TTS_DATA intent will take the user to Android Market, and will let the user initiate the download
installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installIntent);
}
}
}
Upvotes: 0
Reputation: 7234
Thanks to those that told me to pass the ApplicationContext. Turned out that was the easy bit... The hard bit was whether the TextToSpeech object is guaranteed thread-safe.
Thanks for answers telling me how to make something thread-safe / assuming that it is, but the question was about whether the object already is. I probably should have said, I'm fine with implementing thread-safety, but I wanted to know whether I need to bother. And I don't want to assume thread-safety without being certain.
I ran the following and it seemed to work. So I assume the Android SDK TTS is thread-safe, but can't find any documentation saying that it's safe to assume this across all devices, so I'll be wrapping my TTS instance for the time being!
package com.example.testproject;
import java.util.Random;
import android.os.Bundle;
import android.app.Activity;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
public class TestActivity extends Activity implements OnInitListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tts = new TextToSpeech(getApplicationContext(), this);
}
TextToSpeech tts = null;
@Override
public void onInit(int arg0) {
for (int i = 0; i < 100; ++i) {
class Irritate implements Runnable {
Irritate(int iIn) {
i = iIn;
}
@Override
public void run() {
Random r = new Random();
try {
Thread.sleep(r.nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
tts.speak(Integer.toString(i), TextToSpeech.QUEUE_ADD, null);
}
int i;
}
Thread t = new Thread(new Irritate(i));
t.start();
}
}
}
Upvotes: 3
Reputation: 6457
I have never tried that, but I think you can pass an Application context as the parameter in the constructor, not necessarily an Activity.
But paying attention to the documentation, I see that the TTS engine has its own queing system, so you can call speak several times without worrying about the thread timing.
Regardind to your questions, I'm not sure about the number two, but as I wrote first, I would try passing an Application context, rather than Activity context.
About number one, well, there is one instance per engine at a time, I guess. And you normally have just one engine, but again, if the engine controls queries queuing, don't worry about the threads.
Upvotes: 4
Reputation: 8531
I've always used TTS as an Activity that I startedForResult. I just fire an intent to it and then wait for it to come back. If I remember correctly, if returns an array of answers sorted by confidence. So you if you don't have a Context, then I don't believe there is another way to call it (at least using this model). Not sure if there is an object reference that you can get for it.
However, if there is, to use your idea. Then you can just extend Application and hold the static reference to your TTS in there. That way it's visible to all your Activities. I think this is answer you are looking for.
Upvotes: 1