Google's Text-To-Speech API from Android app

2019-01-29 07:28发布

问题:

I want to use Google's Text-To-Speech API in an Android app but I only could find the way to do it from web (Chrome). This is my first attempt to play "Hello world" from the app.

playTTS is the onClick and it is been executed, but no sound is played. Is there any JS/Java library I need to import? Is it possible to generate an audio file from it?

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    myBrowser = new WebView(this);
    if (savedInstanceState == null) {
        getSupportFragmentManager().beginTransaction()
                .add(R.id.container, new PlaceholderFragment()).commit();
    }
}

public void playTTS(View view) {
    myBrowser.loadUrl("javascript:speechSynthesis.speak(
         SpeechSynthesisUtterance('Hello World'))");
}

回答1:

In Android java code your Activity/other Class should implement TextToSpeech.OnInitListener. You will get a TextToSpeech instance by calling TextToSpeech(context, this). (Where context refers to your application's Context -- can be this in an Activity.) You will then receive a onInit() callback with status which tells whether the TTS engine is available or not.

You can talk by calling tts.speak(textToBeSpoken, TextToSpeech.QUEUE_FLUSH, null) or tts.speak(textToBeSpoken, TextToSpeech.QUEUE_ADD, null). The first one will interrupt any "utterance" that is still being spoken and the latter one will add the new "utterance" to a queue. The last parameter is not mandatory. It could be an "utterance id" defined by you in case you want to monitor the TTS status by setting an UtteranceProgressListener. (Not necessary)

In Java code a simple "TTS talker" class could be something like:

public class MyTtsTalker implements TextToSpeech.OnInitListener {

  private TextToSpeech tts;
  private boolean ttsOk;

  // The constructor will create a TextToSpeech instance.
  MyTtsTalker(Context context) {
    tts = new TextToSpeech(context, this);
  }

  @Override
  // OnInitListener method to receive the TTS engine status
  public void onInit(int status) {
    if (status == TextToSpeech.SUCCESS) {
      ttsOk = true;
    }
    else {
      ttsOk = false;
    }
  }

  // A method to speak something
  @SuppressWarnings("deprecation") // Support older API levels too.
  public void speak(String text, Boolean override) {
    if (ttsOk) {
      if (override) {
        tts.speak(text, TextToSpeech.QUEUE_FLUSH, null);    
      }    
      else {
        tts.speak(text, TextToSpeech.QUEUE_ADD, null);    
      }
    }
  }
}


回答2:

Code for TTS:


import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.util.Log;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import android.widget.Toast;

import java.util.HashMap;
import java.util.Locale;

public class KiwixTextToSpeech {

    public static final String TAG_ASKQ = "askq;

    private Context context;

    private OnSpeakingListener onSpeakingListener;

    private WebView webView;

    private TextToSpeech tts;

    private boolean initialized = false;

    /**
     * Constructor.
     *
     * @param context               the context to create TextToSpeech with
     * @param webView               {@link android.webkit.WebView} to take contents from
     * @param onInitSucceedListener listener that receives event when initialization of TTS is done
     *                              (and does not receive if it failed)
     * @param onSpeakingListener    listener that receives an event when speaking just started or
     *                              ended
     */
    public KiwixTextToSpeech(Context context, WebView webView,
            final OnInitSucceedListener onInitSucceedListener,
            final OnSpeakingListener onSpeakingListener) {
        Log.d(TAG_ASKQ, "Initializing TextToSpeech");

        this.context = context;
        this.onSpeakingListener = onSpeakingListener;
        this.webView = webView;
        this.webView.addJavascriptInterface(new TTSJavaScriptInterface(), "tts");

        initTTS(onInitSucceedListener);
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
    private void initTTS(final OnInitSucceedListener onInitSucceedListener) {
        tts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
            @Override
            public void onInit(int status) {
                if (status == TextToSpeech.SUCCESS) {
                    Log.d(TAG_ASKQ, "TextToSpeech was initialized successfully.");
                    initialized = true;
                    onInitSucceedListener.onInitSucceed();
                } else {
                    Log.e(TAG_ASKQ, "Initilization of TextToSpeech Failed!");
                }
            }
        });

        tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
            @Override
            public void onStart(String utteranceId) {
            }

            @Override
            public void onDone(String utteranceId) {
                Log.e(TAG_ASKQ, "TextToSpeech: " + utteranceId);
                onSpeakingListener.onSpeakingEnded();
            }

            @Override
            public void onError(String utteranceId) {
                Log.e(TAG_ASKQ, "TextToSpeech: " + utteranceId);
                onSpeakingListener.onSpeakingEnded();
            }
        });
    }

    /**
     * Reads the currently selected text in the WebView.
     */
    public void readSelection() {
      webView.loadUrl("javascript:tts.speakAloud(window.getSelection().toString());", null);
    }

    /**
     * Starts speaking the WebView content aloud (or stops it if TTS is speaking now).
     */
    public void readAloud() {
        if (tts.isSpeaking()) {
            if (tts.stop() == TextToSpeech.SUCCESS) {
                onSpeakingListener.onSpeakingEnded();
            }
        } else {
            Locale locale = LanguageUtils.ISO3ToLocale(ZimContentProvider.getLanguage());
            int result;
            if (locale == null
                    || (result = tts.isLanguageAvailable(locale)) == TextToSpeech.LANG_MISSING_DATA
                    || result == TextToSpeech.LANG_NOT_SUPPORTED) {
                Log.d(TAG_ASKQ, "TextToSpeech: language not supported: " +
                        ZimContentProvider.getLanguage() + " (" + locale.getLanguage() + ")");
                Toast.makeText(context,
                        context.getResources().getString(R.string.tts_lang_not_supported),
                        Toast.LENGTH_LONG).show();
            } else {
                tts.setLanguage(locale);

                // We use JavaScript to get the content of the page conveniently, earlier making some
                // changes in the page
                webView.loadUrl("javascript:" +
                        "body = document.getElementsByTagName('body')[0].cloneNode(true);" +
                        // Remove some elements that are shouldn't be read (table of contents,
                        // references numbers, thumbnail captions, duplicated title, etc.)
                        "toRemove = body.querySelectorAll('sup.reference, #toc, .thumbcaption, " +
                        "    title, .navbox');" +
                        "Array.prototype.forEach.call(toRemove, function(elem) {" +
                        "    elem.parentElement.removeChild(elem);" +
                        "});" +
                        "tts.speakAloud(body.innerText);");
            }
        }
    }

    /**
     * Returns whether the TTS is initialized.
     *
     * @return <code>true</code> if TTS is initialized; <code>false</code> otherwise
     */
    public boolean isInitialized() {
        return initialized;
    }

    /**
     * Releases the resources used by the engine.
     *
     * @see android.speech.tts.TextToSpeech#shutdown()
     */
    public void shutdown() {
        tts.shutdown();
    }

    /**
     * The listener which is notified when initialization of the TextToSpeech engine is successfully
     * done.
     */
    public interface OnInitSucceedListener {

        public void onInitSucceed();
    }

    /**
     * The listener that is notified when speaking starts or stops (regardless of whether it was a
     * result of error, user, or because whole text was read).
     *
     * Note that the methods of this interface may not be called from the UI thread.
     */
    public interface OnSpeakingListener {

        public void onSpeakingStarted();

        public void onSpeakingEnded();
    }

    private class TTSJavaScriptInterface {

        @JavascriptInterface
        @SuppressWarnings("unused")
        public void speakAloud(String content) {
            String[] lines = content.split("\n");
            for (int i = 0; i < lines.length - 1; i++) {
                String line = lines[i];
                tts.speak(line, TextToSpeech.QUEUE_ADD, null);
            }

            HashMap<String, String> params = new HashMap<>();
            // The utterance ID isn't actually used anywhere, the param is passed only to force
            // the utterance listener to be notified
            params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "kiwixLastMessage");
            tts.speak(lines[lines.length - 1], TextToSpeech.QUEUE_ADD, params);

            if (lines.length > 0) {
                onSpeakingListener.onSpeakingStarted();
            }
        }
    }
}