Search code examples
androidtext-to-speechlogcatintentservice

How to run an Android TTS in an IntentService


I've looked at several threads asking about running TTS from a service, (sorry, can't remember which ones) but none of them help.

The IntentService starts just fine, but it's destroyed before onInit() is run which is what I believe is causing this logcat:

    07-28 18:58:24.716    2305-2305/me.arthurtucker.alfredjr D/MainActivity: onPrefChange
07-28 18:58:24.786    2305-2305/me.arthurtucker.alfredjr D/MainActivity: made new intent
07-28 18:58:24.794    2305-2305/me.arthurtucker.alfredjr D/MainActivity: started service
07-28 18:58:25.044    2305-2305/me.arthurtucker.alfredjr I/SpeachService: onCreate() ran
07-28 18:58:25.255    2305-2573/me.arthurtucker.alfredjr I/TextToSpeech: Sucessfully bound to com.google.android.tts
07-28 18:58:25.255    2305-2573/me.arthurtucker.alfredjr I/SpeachService: onHandleIntent ran
07-28 18:58:25.255    2305-2305/me.arthurtucker.alfredjr W/TextToSpeech: stop failed: not bound to TTS engine
07-28 18:58:25.255    2305-2305/me.arthurtucker.alfredjr W/TextToSpeech: shutdown failed: not bound to TTS engine
07-28 18:58:25.255    2305-2305/me.arthurtucker.alfredjr I/SpeachService: onDestroy() ran
07-28 18:58:25.286    2305-2305/me.arthurtucker.alfredjr E/ActivityThread: Service me.arthurtucker.alfredjr.SpeechService has leaked ServiceConnection android.speech.tts.TextToSpeech$Connection@37ad3e10 that was originally bound here
        android.app.ServiceConnectionLeaked: Service me.arthurtucker.alfredjr.SpeechService has leaked ServiceConnection android.speech.tts.TextToSpeech$Connection@37ad3e10 that was originally bound here
        at android.app.LoadedApk$ServiceDispatcher.<init>(LoadedApk.java:969)
        at android.app.LoadedApk.getServiceDispatcher(LoadedApk.java:863)
        at android.app.ContextImpl.bindService(ContextImpl.java:1437)
        at android.app.ContextImpl.bindService(ContextImpl.java:1426)
        at android.content.ContextWrapper.bindService(ContextWrapper.java:473)
        at android.speech.tts.TextToSpeech.connectToEngine(TextToSpeech.java:627)
        at android.speech.tts.TextToSpeech.initTts(TextToSpeech.java:597)
        at android.speech.tts.TextToSpeech.<init>(TextToSpeech.java:553)
        at android.speech.tts.TextToSpeech.<init>(TextToSpeech.java:527)
        at android.speech.tts.TextToSpeech.<init>(TextToSpeech.java:512)
        at me.arthurtucker.alfredjr.SpeechService.onHandleIntent(SpeechService.java:37)
        at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:137)
        at android.os.HandlerThread.run(HandlerThread.java:60)

I've tried running sayString() in onCreate(), onHandleIntent(), and now onInit(). When in onCreate() or onHandleIntent() the logcat reports something like: "speak failed: not bound to tts engine”.

Is there a way to have the IntentService wait for onInit() to run before destroying?

My code is below.

    public class SpeechService extends IntentService implements TextToSpeech.OnInitListener, TextToSpeech.OnUtteranceCompletedListener {
    private static TextToSpeech myTTS;
    private Main mActivity;
    private static final String TAG = "SpeachService";
    //private String msg;

    public SpeechService() {
        super("SpeechService");
    }

    @Override
    public void onCreate() {
        mActivity = new Main();
        super.onCreate();
        Log.i(TAG, "onCreate() ran");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Intent checkTTSIntent = new Intent();
        checkTTSIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
        myTTS = new TextToSpeech(this, this);
        //String msg = intent.getStringExtra("imsg");
        Log.i(TAG, "onHandleIntent ran");
    }

    public void sayString(String string) {
        if (myTTS != null) {
            if (mActivity.mEnabled) {
                HashMap<String, String> hashMap = new HashMap<String, String>();
                hashMap.put(TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS, "true");
                myTTS.speak(string, 1, hashMap);
            } else {
                myTTS.speak(string, 1, null);
            }
        }

        Log.i(TAG, "sayString() ran");
    }

    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        if (myTTS != null) {
            myTTS.stop();
            myTTS.shutdown();
        }
        super.onDestroy();
        Log.i(TAG, "onDestroy() ran");
    }

    @Override
    public void onInit(int initStatus) {
        if (initStatus == TextToSpeech.SUCCESS) {
            if (myTTS.isLanguageAvailable(Locale.UK) == TextToSpeech.LANG_AVAILABLE) {
                myTTS.setLanguage(Locale.UK);
            }
            sayString("IT WORKS");
        } else if (initStatus == TextToSpeech.ERROR) {
            mActivity.makeToast("Failed to start Text-to-Speech Service", true);
        }
        Log.i(TAG, "onInit() ran");
    }

    @Override
    public void onUtteranceCompleted(String s) {
        stopSelf();
        Log.i(TAG, "onUtteranceCompleted() ran");
    }
}

I'd be willing to try something completely different method, as long as it does something similar.


Solution

  • You are not calling onInit() explicitly. IntentService is destroyed once the onHandleIntent method is invoked. Make sure you do all your work in onHandleIntent method.

    onHandleIntent(Intent intent) from the Documentation:

    This method is invoked on the worker thread with a request to process. Only one Intent is processed at a time, but the processing happens on a worker thread that runs independently from other application logic. So, if this code takes a long time, it will hold up other requests to the same IntentService, but it will not hold up anything else. When all requests have been handled, the IntentService stops itself, so you should not call stopSelf().