Text to speech pattern in Android

后端 未结 2 1966
情深已故
情深已故 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: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.
     *
     * 

    Usage (in the activity that "speaks"): *

  • 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 *
  • Call startService after successfully checking for speech data *
  • 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. *
  • call `.speak(textToSpeak)` when ready to speak (after which we cannot call `.speak()` again) * *

    NB: We assume that the activity that started this service checks that TTS data is * available. *

    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 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)

    
    

提交回复
热议问题