1

I'd like to ask you for some help with Android TextToSpeech feature. Basically, I'd like to develop a simple AI which speaks, asking a question then waits for an answer, and at last, based on answer asks another question and so on, until user pronounces a keyword which stops everything.

Now I know TextToSpeech has to be initialized before using speak method, and I'm trying to take this into account by using onActivityResult method.

Below some code:

Activity class:

public class MainActivity extends AppCompatActivity implements OnInitListener, Button.OnClickListener{
    Button sayHello;
    TextView textView;
    private static final int CHECK_DATA = 0;
    private static final Locale defaultLocale = Locale.UK;   // British English
    private static final String TAG = "TTS";
    private TextToSpeech tts;

    private boolean isInit = false;

sayIt Method: used to speak:

public void sayIt(String text, boolean flushQ){
        if(isInit){
            if(flushQ){
                tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, null);
            } else {
                tts.speak(text, TextToSpeech.QUEUE_ADD, null, null);
            }
        } else {
            Log.i(TAG, "Failure: TTS instance not properly initialized");
        }
    }

TextToSpeech Listener:

@Override
    public void onInit(int status){
        if(status == TextToSpeech.SUCCESS){
            isInit = true;

            // Enable input text field and speak button now that we are initialized
            sayHello.setEnabled(true);

            // Set to a language locale after checking availability
            Log.i(TAG, "available="+tts.isLanguageAvailable(Locale.UK));
            tts.setLanguage(defaultLocale);
            // Examples of voice controls.  Set to defaults of 1.0.
            tts.setPitch(1.0F);
            tts.setSpeechRate(1.0F);
            // Issue a greeting and instructions in the default language
            tts.speak("Initialized!", TextToSpeech.QUEUE_FLUSH, null, Integer.toString(12));
        } else {
            isInit = false;
            Log.i(TAG, "Failure: TTS instance not properly initialized");
        }
    }

Button Listener:

@Override
    public void onClick(View v){
        if(isInit)
            sayIt("You clicked!", true);
    }

onActivityResult Method:

// Create the TTS instance if TextToSpeech language data are installed on device.  If not
    // installed, attempt to install it on the device.
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == CHECK_DATA) {
            if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {

                // Success, so create the TTS instance.  But can't use it to speak until
                // the onInit(status) callback defined below runs, indicating initialization.

                Log.i(TAG, "Success, let's talk");
                tts = new TextToSpeech(this,  this);

                // Use static Locales method to list available locales on device
                Locale[] locales = Locale.getAvailableLocales();
                Log.i(TAG,"Locales Available on Device:");
                for(int i=0; i<locales.length; i++){
                    String temp = "Locale "+i+": "+locales[i]+" Language="
                            +locales[i].getDisplayLanguage();
                    if(locales[i].getDisplayCountry() != "") {
                        temp += " Country="+locales[i].getDisplayCountry();
                    }
                    Log.i(TAG, temp);
                }

            } else {
                // missing data, so install it on the device
                Log.i(TAG, "Missing Data; Install it");
                Intent installIntent = new Intent();
                installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
                startActivity(installIntent);
            }
        }
    }

And, at last, onCreate Method:

@Override
    public void onCreate(Bundle savedInstance){
        super.onCreate(savedInstance);
        setContentView(R.layout.activity_main);

        sayHello = findViewById(R.id.sayBtn);
        textView = findViewById(R.id.textView);

        sayHello.setEnabled(false);
        sayHello.setOnClickListener(this);
        Intent checkIntent = new Intent();
        checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
        startActivityForResult(checkIntent, CHECK_DATA);
        /* THIS SPEAK DOES NOT WORK! */
        sayIt("Speech from method!", true);
    }

Issue is: Button successfully gets enabled when onInit method initialises TextToSpeech and successfully pronounces text. My goal is to make the Activity speak from onCreate method, since at the moment it only works from onInit and onClick listeners, bot not in onCreate, even if I check for tts initialization using onActivityResult.

Basically I want the TextToSpeech to speak with no Buttons involved.

I know very similar questions were already posted, but none solved my problem. Have some idea? Hope I've been clear, Thank you!

UPDATE: Log shows ERROR detected occurs in else branch of onInit method, where Log.i(TAG, "Failure: TTS instance not properly initialized"); line is.

Sergio
  • 354
  • 1
  • 12
  • It seems that TTS requires some time to init. So if you call it straight from `onCreate()` it is still not initialized. You may experiment to call it with some delay using method described here https://stackoverflow.com/questions/3072173/how-to-call-a-method-after-a-delay-in-android – bvk256 Jul 22 '19 at 12:29
  • You have to wait until `onInit()` is run with the `TextToSpeech.SUCCESS` status. You already have the `isInit` variable, so use that to check. And if you want to speak something as soon as possible then trigger it from `onInit()`. – Markus Kauppinen Jul 22 '19 at 12:37
  • Already tried using boolean value in order to check initialization, did not worked. @bvk256 suggestion worked perfectly though. Thank you very much! – Sergio Jul 22 '19 at 14:56
  • 1
    @SabrinaAldrovandi please note that timeout time may be different on each device, so proper testing is required – bvk256 Jul 22 '19 at 14:57
  • @bvk256 Yes, I'm developing this app in order to use it on a limited amount of devices, so I'll test this on each one and see how to arrange timeout time. – Sergio Jul 22 '19 at 15:02
  • Why don't you just make a method myOnCreate() and call that from inside onInit() ? https://stackoverflow.com/questions/52233235/setonutteranceprogresslistener-not-at-all-working-for-text-to-speech-for-api-2/52266610#52266610 – Nerdy Bunz Jul 23 '19 at 03:04
  • Adding a delay seems pretty desperate and brittle - I was hoping for a more solid solution. – Mike Nov 29 '22 at 06:15

1 Answers1

0

SOLUTION: The only thing to do here is to wait a little time in order to let TextToSpeech initialize for good. A good way seems to be by using a delayed Handler as follows:

final Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //Waiting for RobotTextToSpeech initialization for 1500ms
                rtts.speak("This speak will work!");
                rtts.speak("This other speak will work too!");
            }
        }, 1500);
    }

By doing this, looks like TextToSpeech works well even in onCreate method, we just have to wait little time. Hope this can help.

Sergio
  • 354
  • 1
  • 12