Text to speech pattern in Android

后端 未结 2 1965
情深已故
情深已故 2020-12-22 06:16

I\'m trying to use TextToSpeech in my app.

I wrote it as a bound service. Problem appears when I need to read text before activity stops. It says just half of text

相关标签:
2条回答
  • 2020-12-22 06:37

    Instead of just bind service, start your service using startService() and then bind. After reading the text in onUtteranceCompleted() call stopSelf().

    0 讨论(0)
  • 2020-12-22 06:54

    Here is my implementation - as inspired by Hoan's answer (and added many years later but hopefully useful to someone else to save them having to work it out as I did.

    import android.app.Service;
    import android.content.Intent;
    import android.os.Binder;
    import android.os.IBinder;
    import android.speech.tts.TextToSpeech;
    import android.speech.tts.UtteranceProgressListener;
    
    import java.util.LinkedList;
    import java.util.Locale;
    import java.util.Queue;
    
    import timber.log.Timber;
    
    /**
     * This is a simplified text to speech service where each "instance" (each time startService is
     * called) is only expected to speak once (we don't check this but the service gets stopped after it
     * has spoken once). This was created for the case where we want an announcement to be made just
     * before finishing an activity so couldn't do it in the activity itself because it gets destroyed
     * before it finishes speaking.
     *
     * <p>Usage (in the activity that "speaks"):
     * <li>Check there is text to speech data available on the device. See:
     *     https://android-developers.googleblog.com/2009/09/introduction-to-text-to-speech-in.html
     * <li>Call startService after successfully checking for speech data
     * <li>Bind the activity to the service onStart (and unbind onStop). Getting an instance of the service to later pass
     * the text to speak though to.
     * <li> call `.speak(textToSpeak)` when ready to speak (after which we cannot call `.speak()` again)
     *
     *     <p>NB: We assume that the activity that started this service checks that TTS data is
     *     available.
     *     <p>NB: You need to start the service a few seconds before asking it to speak as the text to speech api doesn't
     *     start immediately.
     */
    public class TTSSingleUtteranceService extends Service {
      private boolean initDone = false;
      private TextToSpeech textToSpeech;
      Queue<Integer> startIds = new LinkedList<>();
      // Binder given to clients
      private final IBinder binder = new TTSBinder();
    
      /**
       * Class used for the client Binder. Because we know this service always runs in the same process
       * as its clients, we don't need to deal with IPC.
       */
      public class TTSBinder extends Binder {
        public TTSSingleUtteranceService getService() {
          // Return this instance of TTSService so clients can call public methods
          return TTSSingleUtteranceService.this;
        }
      }
    
      @Override
      public void onCreate() {
        super.onCreate();
        // Initialise the TextToSpeech object
        textToSpeech =
            new TextToSpeech(
                this,
                status -> {
                  if (status == TextToSpeech.SUCCESS) {
                    initDone = true;
                    Locale locale = Locale.getDefault();
                    int ttsLang = textToSpeech.setLanguage(locale);
                    if (ttsLang == TextToSpeech.LANG_MISSING_DATA
                        || ttsLang == TextToSpeech.LANG_NOT_SUPPORTED) {
                      Timber.e("TTS: Language %s is not supported!", locale);
                    }
                    textToSpeech.setOnUtteranceProgressListener(
                        new UtteranceProgressListener() {
                          @Override
                          public void onStart(String utteranceId) {
                            Timber.i("Utterance started");
                          }
    
                          @Override
                          public void onDone(String utteranceId) {
                            Timber.i("Utterance completed. Shutting down service");
                            if (!startIds.isEmpty()) {
                              stopSelf(startIds.remove());
                            } else {
                              stopSelf();
                            }
                          }
    
                          @Override
                          public void onError(String utteranceId) {
                            Timber.i("Utterance errored");
                          }
                        });
                    Timber.i("TTS: Initialization success with locale=%s.", locale);
                  } else {
                    Timber.e("Text to speech initialisation failed");
                  }
                });
      }
    
      @Override
      public int onStartCommand(Intent intent, int flags, int startId) {
        // add this startId to the queue of ids so that we can shutdown each one in order
        startIds.add(startId);
        // If we get killed, after returning from here, do not recreate the service unless there are
        // pending intents to deliver.
        return START_NOT_STICKY;
      }
    
      @Override
      public IBinder onBind(Intent intent) {
        return binder;
      }
    
      @Override
      public void onDestroy() {
        Timber.i("Destroying TTS service");
        if (textToSpeech != null) {
          textToSpeech.shutdown();
        }
        super.onDestroy();
      }
    
      /** method for clients */
      public void speak(String text) {
        Timber.i("speak called with text=%s", text);
        if (text != null && initDone) {
          String utteranceId = String.valueOf(text.hashCode());
          textToSpeech.speak(text, TextToSpeech.QUEUE_ADD, null, utteranceId);
        } else {
          Timber.e("TTSError: textToSpeech is null or hasn't finished initialising!");
        }
      }
    }
    

    I then use this from the Android Activity like so

    public class MyActivity extends AppCompatActivity {
      TTSSingleUtteranceService ttsService;
      boolean boundToTtsService = false;
    
      @Override
      protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Check there is text to speech data available on the device and then will start the text to
        // speech service in onActivityResult
        Intent checkIntent = new Intent();
        checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
        startActivityForResult(checkIntent, TTS_CHECK_DATA_AVAILABLE);
      }
    
      @Override
      protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, TTSSingleUtteranceService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
      }
    
      @Override
      protected void onStop() {
        super.onStop();
        unbindService(connection);
        boundToTtsService = false;
      }
    
      /** Defines callbacks for service binding, passed to bindService() */
      private ServiceConnection connection =
          new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName className, IBinder service) {
              // We've bound to TTSService, cast the IBinder and get the TTSService instance
              TTSSingleUtteranceService.TTSBinder binder = (TTSSingleUtteranceService.TTSBinder) service;
              ttsService = binder.getService();
              boundToTtsService = true;
            }
    
            @Override
            public void onServiceDisconnected(ComponentName arg0) {
              boundToTtsService = false;
            }
          };
    
      public void saveClicked() {
        // this is called to save the data entered and then speak the results and finish the activity
        // perform needed logic
        // Speak text
        if (boundToTtsService) {
          ttsService.speak("This is the text that will be spoken");
        }
        finish();
      }
    
      @Override
      protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == TTS_CHECK_DATA_AVAILABLE) {
          if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
            // success, start the TTS service
            startService(new Intent(this, TTSSingleUtteranceService.class));
          } else {
            // missing data, install it
            Timber.i("TTS: Missing text to speech resources - redirecting user to install them.");
            Intent installIntent = new Intent();
            installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
            startActivity(installIntent);
          }
        }
      }
    }
    

    Finally don't forget to add the service to your AndroidManifest.xml (within the application section)

    <service android:name=".util.TTSSingleUtteranceService" />
    
    0 讨论(0)
提交回复
热议问题