Android TextToSpeech Initialization Blocks/Freezes UI Thread - android

I wrote the following code:
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
private TextToSpeech mTTS;
#Override
protected void onPause() {
super.onPause();
if (mTTS != null) {
mTTS.stop();
mTTS.shutdown();
}
}
#Override
protected void onResume() {
super.onResume();
mTTS = new TextToSpeech(getApplicationContext(),
new TextToSpeech.OnInitListener() {
#Override
public void onInit(int status) {
if(status != TextToSpeech.ERROR){
mTTS.setLanguage(Locale.ENGLISH);
mTTS.speak("Hello!", TextToSpeech.QUEUE_FLUSH, null);
}
}
});
}
public void onButtonClick(View view) {
mTTS.speak("Hello!", TextToSpeech.QUEUE_FLUSH, null);
}
}
But this code: mTTS = new TextToSpeech(... freezes the UI thread for 5–8 seconds.
I noticed that the delay happens on this line in logcat (first line):
07-13 11:51:11.304 5296-5296/com.example.TextToSpeachTest I/TextToSpeech﹕ Connected to ComponentInfo{com.google.android.tts/com.google.android.tts.service.GoogleTTSService}
07-13 11:51:17.317 5296-5296/com.example.TextToSpeachTest I/Choreographer﹕ Skipped 391 frames! The application may be doing too much work on its main thread.
I tried to put it inside AsyncTask:
#Override
protected void onResume() {
super.onResume();
MyAsyncTask newTask = new MyAsyncTask() {
protected void onPostExecute(Boolean result) {
}
};
newTask.context = getApplicationContext();
newTask.execute();
}
...
class MyAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private TextToSpeech mTTS;
public Context context;
#Override
protected Boolean doInBackground(Void... arg0) {
mTTS = new TextToSpeech(context,
new TextToSpeech.OnInitListener() {
#Override
public void onInit(int status) {
if(status != TextToSpeech.ERROR){
mTTS.setLanguage(Locale.ENGLISH);
mTTS.speak("Hello!", TextToSpeech.QUEUE_FLUSH, null);
}
}
});
return true;
}
}
But nothing changed. Could you please advise a proper solution/idea?
This delay manifests on my Phone LG L4 II (E440). On Nexus 10 – no delays.
I tried different talking apps from Play Store on my LG L4. On some apps there is also UI blocking, but some work without the blocking. This means – it is possible to implement. But how?

Your code is OK, you don't need to call TextToSpeech constructor from a different thread.
Actually, the problem is in the implementation of some TextToSpeech libraries that use main UI thread to do the speech processing.
The solution is making the TextToSpeech processing to run in a separated process, different from the process of the UI thread. In that way, UI interaction and speech processing is done in the main thread of two different process. The only way that I found to do it is creating the TextToSpeech object and control code in an Android Service with its own process.
In order to create a Service with its own process, configuration in AndroidManifest.xml must include the property: android:process:
<service
android:name=".TtsService"
android:enabled="true"
android:exported="false"
android:process="com.example.ttsservice">
</service>
This introduce complexity because now the Activity must bind the Service using a Messenger or AIDL (http://developer.android.com/guide/components/bound-services.html) to manage the TextToSpeech features.

You dont have to do the speech on a background thread.
IMO - you need to implement (TextToSpeech.OnInitListener, UtteranceProgressListener)
tts.setOnUtteranceProgressListener(new UtteranceProgressListener()
{
#Override
public void onDone(String utteranceId)
{
onDoneSpeaking(utteranceId);
}
#Override
public void onError(String utteranceId)
{
}
#Override
public void onStart(String utteranceId)
{
}
});
...
public void onInit(int status) {
if ( status == TextToSpeech.SUCCESS ) {
int result = tts.setLanguage( Locale.US );
if ( result == TextToSpeech.LANG_MISSING_DATA ||
result == TextToSpeech.LANG_NOT_SUPPORTED ) {
Log.e("MainActivity#onInit", "set lang error with "+result);
}
else {
tts.speak(mcomment.substring(left, right), TextToSpeech.QUEUE_FLUSH, null);

Related

TextToSpeech API

In Android TextToSpeech, How come we know either Speech is finished. I want to change icon after speech finished. I declared tts.speak() in Button Onclick Listener. i used thread postdelayed handler to change icon after some time. but the text to read changes different at different times.It didnt worked out. CheckThemestart(),ChangeThemeStop() are functions to change the icon.
i declared the following in OnCreate() :
tts = new TextToSpeech(getContext(), new
TextToSpeech.OnInitListener() {
#Override
public void onInit(int status) {
if (status != TextToSpeech.ERROR) {
tts.setLanguage(Locale.UK);
tts.speak("",TextToSpeech.QUEUE_FLUSH,null);
}
Button-onClickListener :
if (!tts.isSpeaking()) {
CheckThemeStart();
tts.speak(plainText, TextToSpeech.QUEUE_FLUSH, null);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
//change icon after spoken
CheckThemeStop();
}
}, 15000);
} else {
CheckThemeStop();
tts.stop();
}
You can register UtteranceProgressListener for tts start and end times and error handling.
tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
#Override
public void onStart(String s) {
}
#Override
public void onDone(String s) {
}
#Override
public void onError(String s) {
}
});
If we declare a hasmap parameter with unique string ID and pass that id to compare utterence id if matches it execute what you declare inside onUtterenceCompletedListener().
The following is code snippet.
HashMap<String,String> params=new HashMap<String, String>();
params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,"SpeakID");
tts.speak(plainText, TextToSpeech.QUEUE_FLUSH,params);
tts.setOnUtteranceCompletedListener(new TextToSpeech.OnUtteranceCompletedListener() {
#Override
public void onUtteranceCompleted(String utteranceId) {
if(utteranceId.equals("SpeakID"))
{
CheckThemeStop();
}
}
});

When to use the UtteranceProgressListener

I have a FrameLayout with two ImageButtons (Play, Stop). By default
Play button is VISIBLE, Stop button is GONE
Clicking the Play starts the TTS Engine which reads the text. On Completion of reading the text, I want to set the Visibility of
Play to GONE, Stop to VISIBLE
Should I use the UtteranceProgressListener to serve the purpose? If not,
How can I perform the above action?
What is the purpose of UtteranceProgressListener?
Did you perhaps mean that:
reading starts -> Play is gone and Stop is visible
reading ends -> Play is visible, Stop is gone
Anyway, the purpose of UtteranceProgressListener is exactly what you are describing. It's used to monitor the progress of the speech synthesis.
You can add an "utterance id" (here "helloText") to any text that is spoken out:
tts.speak("Hello Stack Overflow!", TextToSpeech.QUEUE_ADD, "helloText");
But that's not really necessary in your case, so the last parameter can be null:
tts.speak("Hello Stack Overflow!", TextToSpeech.QUEUE_ADD, null);
The UtteranceProgressListener should be added before calling speak(). You could do that for example in the TTS initialization callback onInit() if the TTS status is TextToSpeech.SUCCESS.
It can be a separate class or just an anonymous inner class:
speech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
#Override
public void onStart(String utteranceId) {
// Speaking started.
}
#Override
public void onDone(String utteranceId) {
// Speaking stopped.
}
}
#Override
public void onError(String utteranceId) {
// There was an error.
}
});
The onStart() method is triggered when speaking starts (soon after calling speak()) so that's one possible place to switch the visible button. For example the Play button could be switched to a Stop button.
The onDone() method is triggered when speaking is finished and it's another possible place to switch the visible button. For example the Stop button could be switched to a Play button.
And as you can see the "utterance id" is available in both methods if you provided a one in the speak() method call. It would be useful if you needed to know exactly which text is being spoken/finished being spoken/failed with an error.
UtteranceProgressListener can be used to identify when the TTS is completed. Try this following code which shows a toast after TTS completed.
public class MainActivity extends AppCompatActivity implements TextToSpeech.OnInitListener{
private boolean initialized;
private String queuedText;
private String TAG = "TTS";
private TextToSpeech tts;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tts = new TextToSpeech(this /* context */, this /* listener */);
tts.setOnUtteranceProgressListener(mProgressListener);
speak("hello world");
}
public void speak(String text) {
if (!initialized) {
queuedText = text;
return;
}
queuedText = null;
setTtsListener(); // no longer creates a new UtteranceProgressListener each time
HashMap<String, String> map = new HashMap<String, String>();
map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "MessageId");
tts.speak(text, TextToSpeech.QUEUE_ADD, map);
}
private void setTtsListener() {
}
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
initialized = true;
tts.setLanguage(Locale.ENGLISH);
if (queuedText != null) {
speak(queuedText);
}
}
}
private abstract class runnable implements Runnable {
}
private UtteranceProgressListener mProgressListener = new UtteranceProgressListener() {
#Override
public void onStart(String utteranceId) {
} // Do nothing
#Override
public void onError(String utteranceId) {
} // Do nothing.
#Override
public void onDone(String utteranceId) {
new Thread()
{
public void run()
{
MainActivity.this.runOnUiThread(new runnable()
{
public void run()
{
Toast.makeText(getBaseContext(), "TTS Completed", Toast.LENGTH_SHORT).show();
}
});
}
}.start();
}
};
}

TTS fails to operate and activity fails to launch

I've put together an activity that uses the TTS API and code I've used in another project for the same purpose. However, TTS fails to operate and the activity fails to launch all together. I've studied my code and cannot decipher where the problem is. The code matches my other project's code exactly, and the code in the other project works as expected. The log cat data is hard to understand, so hopefully someone in this community can help me find the problem. I think it may be an Android Studio issue. The other project is installed within Eclipse Luna. It's got to be the TTS code because the activity in question was launching just fine before I added the TTS code. I've attached my code and the log cat info.
ADDITIONAL INFO: Perhaps it's the behavior I am trying to implement. I am attempting to send a saved string to the TTS engine upon the loading of the activity into view, using a timer to launch TTS 1 second after the view initializes. I'm not sure, however, if I can launch an instance of TTS without a button press to execute it. My other project, which works, calls the TTS engine on the click of a button...???
public class InfoGreetView extends ActionBarActivity implements TextToSpeech.OnInitListener, TextToSpeech.OnUtteranceCompletedListener {
TextView infoGreetPrompt = (TextView) findViewById(R.id.infoGreetPrompt);
TextToSpeech textToSpeech;
Timer timer = new Timer();
Timer timer2 = new Timer();
TimerTask timerTask;
TimerTask timerTask2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_info_greet_view);
textToSpeech = new TextToSpeech(this,this);
timerTask = new TimerTask() {
#Override
public void run() {
Vibrator vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(500);
startPlayback();
}
};
timer.schedule(timerTask,1000);
}
#Override
public void onDestroy(){
if (textToSpeech != null){
textToSpeech.stop();
textToSpeech.shutdown();
}
super.onDestroy();
}
#Override
public void onInit(int status){
if (status == TextToSpeech.SUCCESS){
int result = textToSpeech.setLanguage(Locale.getDefault());
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED){
Toast.makeText(getApplicationContext(), R.string.messagePlaybackInterrupted, Toast.LENGTH_LONG).show();
}
}
else{
Toast.makeText(getApplicationContext(), R.string.messagePlaybackInterrupted, Toast.LENGTH_LONG).show();
}
}
public void startPlayback(){
String infoGreetViewPrompt = infoGreetPrompt.getText().toString();
HashMap<String, String> myHashRender = new HashMap<String, String>();
myHashRender.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "completed");
textToSpeech.speak(infoGreetViewPrompt, TextToSpeech.QUEUE_FLUSH, myHashRender);
}
#Override
public void onUtteranceCompleted(String utteranceId){
timerTask2 = new TimerTask() {
#Override
public void run() {
Intent displayGuestMessageView = new Intent(InfoGreetView.this, GuestMessageView.class);
startActivity(displayGuestMessageView);
}
};
timer2.schedule(timerTask2, 1000);
}
Hooray! I figured out what the issue was. First of all, my dependencies in Android Studio were not completed. I had to add the compile 'com.android.support:support-v4:20.0.0' jar; all I had in there was compile 'com.android.support:appcompat-v7:20.0.0'. I believe this is why TTS was not operating.
Second, I had tried to mimick Xcode's viewDidAppear method along with a timer so that TTS would begin when the view appeared, within onResume(). However, this was not working. The Log Cat said something about not binding to TTS. So, I threw my timer into onCreate() and instead of throwing my startPlayback() method separately from a call within my timer, I simply added the code within startPlayback() into my timer. Why? For some reason, the call wasn't happening. Here is my new and perfectly operating code!
public class InfoGreetView extends ActionBarActivity implements TextToSpeech.OnInitListener, TextToSpeech.OnUtteranceCompletedListener {
TextView infoGreetPrompt;
TextToSpeech textToSpeech;
ImageView attentionButton;
Timer timer = new Timer();
TimerTask timerTask;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_info_greet_view);
attentionButton = (ImageView) findViewById(R.id.attentionButton);
infoGreetPrompt = (TextView) findViewById(R.id.infoGreetPrompt);
textToSpeech = new TextToSpeech(this,this);
timerTask = new TimerTask() {
#Override
public void run() {
String text = infoGreetPrompt.getText().toString();
HashMap<String, String> myHashRender = new HashMap<String, String>();
myHashRender.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "completed");
textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, myHashRender);
}
};
timer.schedule(timerTask,500);
}
#Override
protected void onDestroy(){
super.onDestroy();
if (textToSpeech != null){
textToSpeech.stop();
textToSpeech.shutdown();
}
}
#Override
public void onInit(int status){
if (status == TextToSpeech.SUCCESS){
int result = textToSpeech.setLanguage(Locale.getDefault());
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED){
Toast.makeText(getApplicationContext(), R.string.languageNotSupported, Toast.LENGTH_LONG).show();
}
}
else{
Toast.makeText(getApplicationContext(), R.string.messagePlaybackInterrupted, Toast.LENGTH_LONG).show();
}
textToSpeech.setOnUtteranceCompletedListener(this);
}
#Override
public void onUtteranceCompleted(String utteranceId){
Intent displayGuestMessageView = new Intent(InfoGreetView.this, GuestMessageView.class);
startActivity(displayGuestMessageView);
}
}

TTS leaked service connection that was orignially bound here

I'm building an app in which there is tts part, in the main activity i'm creating an instance of tts like
public class Translator extends Activity implements OnClickListener{
TextToSpeech tts;
ArrayList<TTS.resultData> textsToBeSpoken = new ArrayList<TTS.resultData>();
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_translator);
initTTS();//calling to initialise tts;
}
}
and i'm calling the initTTS() method from onCreate() method;
public void initTTS(){
tts = new TextToSpeech(Translator.this, new TextToSpeech.OnInitListener() {
#Override
public void onInit(int status) {
toast("TTS ready to use");
tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
#Override
public void onStart(String utteranceId) {
log("Started speaking");
}
#Override
public void onError(String utteranceId) {
log("Error in processing Text to speech");
}
#Override
public void onDone(String utteranceId) {
log("Text to speech finished previewing");
}
});
}
});
}
and there is one more function called
public void speakUpSon(){
HashMap<String, String> params = new HashMap<String, String>();
if(textsToBeSpoken.size() > 0){
for(int i = 0; i < textsToBeSpoken.size(); i++){
if(getCanProceedSpeaking()){
int index = i;
params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "finished Speaking of index : " + i);
System.out.println(textsToBeSpoken.get(i));
tts.speak(textsToBeSpoken.get(i)._originalTxt, TextToSpeech.QUEUE_ADD, params);
textsToBeSpoken.remove(index);
}
}
}
}
in other thread somewere in the app i'll be inserting the objects to speak in textsToBeSpoken, and in other thread there'll be loop were it'll check for the size of the textsToBeSpoken if the size > 0, it'll call the speakUpSon() method.
until here everything works fine, but i get the following error messages
08-14 11:42:04.370: E/ActivityThread(4945): Activity com.PI.prototype.translator.Translator has leaked ServiceConnection android.speech.tts.TextToSpeech$Connection#b4669088 that was originally bound here
You have to call tts.shutdown() somewhere in your code, it is best in onStop() and call initTTS(); in onStart()

Android text to speech setOnUtteranceCompleted not applicable

I'm trying to get the activity to finish after it's finished speaking but for some reason I cannot fathom it tells me that the setOnUtteranceCompleted not applicable for text to speech. I'm new to android programming so please be gentle :-)
Here's the code...
public class SpeakActivity extends Activity implements OnUtteranceCompletedListener{
Random randnum = new Random();
TextToSpeech tts = null;
private boolean ttsIsInit = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_speak);
// Show the Up button in the action bar.
setupActionBar();
startTextToSpeech();
}
void startTextToSpeech(){
final int randint = randnum.nextInt(4);
final String text = ((GlobVars) this.getApplication()).getResponse(randint);
tts = new TextToSpeech(this, new OnInitListener() {
public void onInit(int status) {
tts.setOnUtteranceCompletedListener(this);
if (status == TextToSpeech.SUCCESS) {
ttsIsInit = true;
if (tts.isLanguageAvailable(Locale.ENGLISH) >= 0){
tts.setLanguage(Locale.ENGLISH);
}
tts.setPitch(0.5f);
tts.setSpeechRate(0.5f);
if (tts != null && ttsIsInit) {
Log.d("got ere", "spoken");
tts.speak(text, TextToSpeech.QUEUE_ADD, null);
}
}
}
});
}
// shut down tts to free the TTS resources
#Override
public void onDestroy() {
if (tts != null) {
tts.stop();
tts.shutdown();
}
super.onDestroy();
}
#Override
public void onUtteranceCompleted(String arg0) {
((GlobVars) this.getApplication()).setListen(true);
this.finish();
}
}
I am ot sure but as per the docs of setOnUtteranceCompletedListener(), you might need to use TextToSpeech.OnUtteranceCompletedListener listener as an argument. I think the way to use the function is as below. Note that use runOnUIThread method in case you want to make any changes to the UI on the call of the onUtteranceCompleted function.
TextToSpeech tts= new TextToSpeech(context, new OnInitListener() {
#Override
public void onInit(int status) {
tts.setOnUtteranceCompletedListener(new OnUtteranceCompletedListener() {
#Override
public void onUtteranceCompleted(String utteranceId) {
//Do things here
}
});
}
});
Source of above : Check onUtteranceCompleted does not get called? question.
Hope this helps.

Categories

Resources