Text to speech pattern in Android

て烟熏妆下的殇ゞ 提交于 2021-02-04 08:37:07

问题


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 because activity calls unbind and finish itself.

What is better pattern to write Text to speech if I dont want write it direct into actvivity?


回答1:


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




回答2:


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" />


来源:https://stackoverflow.com/questions/19072553/text-to-speech-pattern-in-android

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!