tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SpeechSynthesisService.java (7172B)


      1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil -*- */
      2 /* vim: set ts=20 sts=4 et sw=4: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 package org.mozilla.gecko;
      8 
      9 import android.content.Context;
     10 import android.speech.tts.TextToSpeech;
     11 import android.speech.tts.UtteranceProgressListener;
     12 import android.util.Log;
     13 import java.util.HashMap;
     14 import java.util.HashSet;
     15 import java.util.Locale;
     16 import java.util.Set;
     17 import java.util.UUID;
     18 import java.util.concurrent.atomic.AtomicBoolean;
     19 import org.mozilla.gecko.annotation.WrapForJNI;
     20 import org.mozilla.gecko.util.ThreadUtils;
     21 
     22 public class SpeechSynthesisService {
     23  private static final String LOGTAG = "GeckoSpeechSynthesis";
     24  // Object type is used to make it easier to remove android.speech dependencies using Proguard.
     25  private static Object sTTS;
     26 
     27  @WrapForJNI(calledFrom = "gecko")
     28  public static void initSynth() {
     29    initSynthInternal();
     30  }
     31 
     32  // Extra internal method to make it easier to remove android.speech dependencies using Proguard.
     33  private static void initSynthInternal() {
     34    if (sTTS != null) {
     35      return;
     36    }
     37 
     38    final Context ctx = GeckoAppShell.getApplicationContext();
     39 
     40    sTTS =
     41        new TextToSpeech(
     42            ctx,
     43            new TextToSpeech.OnInitListener() {
     44              @Override
     45              public void onInit(final int status) {
     46                if (status != TextToSpeech.SUCCESS) {
     47                  Log.w(LOGTAG, "Failed to initialize TextToSpeech");
     48                  return;
     49                }
     50 
     51                setUtteranceListener();
     52                registerVoicesByLocale();
     53              }
     54            });
     55  }
     56 
     57  private static TextToSpeech getTTS() {
     58    return (TextToSpeech) sTTS;
     59  }
     60 
     61  private static void registerVoicesByLocale() {
     62    ThreadUtils.postToBackgroundThread(
     63        new Runnable() {
     64          @Override
     65          public void run() {
     66            final TextToSpeech tss = getTTS();
     67            if (tss == null) {
     68              Log.w(LOGTAG, "TextToSpeech is not initialized");
     69              return;
     70            }
     71            final Locale defaultLocale = tss.getDefaultLanguage();
     72            for (final Locale locale : getAvailableLanguages()) {
     73              final Set<String> features = tss.getFeatures(locale);
     74              final boolean isLocal =
     75                  features != null
     76                      && features.contains(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
     77              final String localeStr = locale.toString();
     78              registerVoice(
     79                  "moz-tts:android:" + localeStr,
     80                  locale.getDisplayName(),
     81                  localeStr.replace("_", "-"),
     82                  !isLocal,
     83                  defaultLocale == locale);
     84            }
     85            doneRegisteringVoices();
     86          }
     87        });
     88  }
     89 
     90  private static Set<Locale> getAvailableLanguages() {
     91    final Set<Locale> availableLanguages = getTTS().getAvailableLanguages();
     92    if (availableLanguages != null) {
     93      return availableLanguages;
     94    }
     95 
     96    final Set<Locale> locales = new HashSet<Locale>();
     97    for (final Locale locale : Locale.getAvailableLocales()) {
     98      if (locale.getVariant().isEmpty() && getTTS().isLanguageAvailable(locale) > 0) {
     99        locales.add(locale);
    100      }
    101    }
    102 
    103    return locales;
    104  }
    105 
    106  @WrapForJNI(dispatchTo = "gecko")
    107  private static native void registerVoice(
    108      String uri, String name, String locale, boolean isNetwork, boolean isDefault);
    109 
    110  @WrapForJNI(dispatchTo = "gecko")
    111  private static native void doneRegisteringVoices();
    112 
    113  @WrapForJNI(calledFrom = "gecko")
    114  public static String speak(
    115      final String uri,
    116      final String text,
    117      final float rate,
    118      final float pitch,
    119      final float volume) {
    120    final AtomicBoolean result = new AtomicBoolean(false);
    121    final String utteranceId = UUID.randomUUID().toString();
    122    speakInternal(uri, text, rate, pitch, volume, utteranceId, result);
    123    return result.get() ? utteranceId : null;
    124  }
    125 
    126  // Extra internal method to make it easier to remove android.speech dependencies using Proguard.
    127  private static void speakInternal(
    128      final String uri,
    129      final String text,
    130      final float rate,
    131      final float pitch,
    132      final float volume,
    133      final String utteranceId,
    134      final AtomicBoolean result) {
    135    if (sTTS == null) {
    136      Log.w(LOGTAG, "TextToSpeech is not initialized");
    137      return;
    138    }
    139 
    140    final HashMap<String, String> params = new HashMap<String, String>();
    141    params.put(TextToSpeech.Engine.KEY_PARAM_VOLUME, Float.toString(volume));
    142    params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
    143    final TextToSpeech tss = (TextToSpeech) sTTS;
    144    tss.setLanguage(new Locale(uri.substring("moz-tts:android:".length())));
    145    tss.setSpeechRate(rate);
    146    tss.setPitch(pitch);
    147    final int speakRes = tss.speak(text, TextToSpeech.QUEUE_FLUSH, params);
    148    result.set(speakRes == TextToSpeech.SUCCESS);
    149  }
    150 
    151  private static void setUtteranceListener() {
    152    if (sTTS == null) {
    153      Log.w(LOGTAG, "TextToSpeech is not initialized");
    154      return;
    155    }
    156 
    157    getTTS()
    158        .setOnUtteranceProgressListener(
    159            new UtteranceProgressListener() {
    160              @Override
    161              public void onDone(final String utteranceId) {
    162                dispatchEnd(utteranceId);
    163              }
    164 
    165              @Override
    166              public void onError(final String utteranceId) {
    167                dispatchError(utteranceId);
    168              }
    169 
    170              @Override
    171              public void onStart(final String utteranceId) {
    172                dispatchStart(utteranceId);
    173              }
    174 
    175              @Override
    176              public void onStop(final String utteranceId, final boolean interrupted) {
    177                if (interrupted) {
    178                  dispatchEnd(utteranceId);
    179                } else {
    180                  // utterance isn't started yet.
    181                  dispatchError(utteranceId);
    182                }
    183              }
    184 
    185              public void onRangeStart(
    186                  final String utteranceId, final int start, final int end, final int frame) {
    187                dispatchBoundary(utteranceId, start, end);
    188              }
    189            });
    190  }
    191 
    192  @WrapForJNI(dispatchTo = "gecko")
    193  private static native void dispatchStart(String utteranceId);
    194 
    195  @WrapForJNI(dispatchTo = "gecko")
    196  private static native void dispatchEnd(String utteranceId);
    197 
    198  @WrapForJNI(dispatchTo = "gecko")
    199  private static native void dispatchError(String utteranceId);
    200 
    201  @WrapForJNI(dispatchTo = "gecko")
    202  private static native void dispatchBoundary(String utteranceId, int start, int end);
    203 
    204  @WrapForJNI(calledFrom = "gecko")
    205  public static void stop() {
    206    stopInternal();
    207  }
    208 
    209  // Extra internal method to make it easier to remove android.speech dependencies using Proguard.
    210  private static void stopInternal() {
    211    if (sTTS == null) {
    212      Log.w(LOGTAG, "TextToSpeech is not initialized");
    213      return;
    214    }
    215 
    216    getTTS().stop();
    217  }
    218 }