tor-browser

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

SpeechSynthesisService.cpp (6924B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      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 #include "SpeechSynthesisService.h"
      8 
      9 #include <android/log.h>
     10 
     11 #include "mozilla/ClearOnShutdown.h"
     12 #include "mozilla/Preferences.h"
     13 #include "mozilla/StaticPrefs_media.h"
     14 #include "mozilla/dom/nsSynthVoiceRegistry.h"
     15 #include "mozilla/jni/Utils.h"
     16 #include "nsXULAppAPI.h"
     17 
     18 #define ALOG(args...) \
     19  __android_log_print(ANDROID_LOG_INFO, "GeckoSpeechSynthesis", ##args)
     20 
     21 namespace mozilla {
     22 namespace dom {
     23 
     24 StaticRefPtr<SpeechSynthesisService> SpeechSynthesisService::sSingleton;
     25 
     26 class AndroidSpeechCallback final : public nsISpeechTaskCallback {
     27 public:
     28  AndroidSpeechCallback() {}
     29 
     30  NS_DECL_ISUPPORTS
     31 
     32  NS_IMETHOD OnResume() override { return NS_OK; }
     33 
     34  NS_IMETHOD OnPause() override { return NS_OK; }
     35 
     36  NS_IMETHOD OnCancel() override {
     37    java::SpeechSynthesisService::Stop();
     38    return NS_OK;
     39  }
     40 
     41  NS_IMETHOD OnVolumeChanged(float aVolume) override { return NS_OK; }
     42 
     43 private:
     44  ~AndroidSpeechCallback() {}
     45 };
     46 
     47 NS_IMPL_ISUPPORTS(AndroidSpeechCallback, nsISpeechTaskCallback)
     48 
     49 NS_IMPL_ISUPPORTS(SpeechSynthesisService, nsISpeechService)
     50 
     51 void SpeechSynthesisService::Setup() {
     52  ALOG("SpeechSynthesisService::Setup");
     53 
     54  if (!StaticPrefs::media_webspeech_synth_enabled() ||
     55      Preferences::GetBool("media.webspeech.synth.test")) {
     56    return;
     57  }
     58 
     59  if (!jni::IsAvailable()) {
     60    NS_WARNING("Failed to initialize speech synthesis");
     61    return;
     62  }
     63 
     64  Init();
     65  java::SpeechSynthesisService::InitSynth();
     66 }
     67 
     68 // nsISpeechService
     69 
     70 NS_IMETHODIMP
     71 SpeechSynthesisService::Speak(const nsAString& aText, const nsAString& aUri,
     72                              float aVolume, float aRate, float aPitch,
     73                              nsISpeechTask* aTask) {
     74  if (mTask) {
     75    NS_WARNING("Service only supports one speech task at a time.");
     76    return NS_ERROR_NOT_AVAILABLE;
     77  }
     78 
     79  RefPtr<AndroidSpeechCallback> callback = new AndroidSpeechCallback();
     80  nsresult rv = aTask->Setup(callback);
     81 
     82  if (NS_FAILED(rv)) {
     83    return rv;
     84  }
     85 
     86  jni::String::LocalRef utteranceId =
     87      java::SpeechSynthesisService::Speak(aUri, aText, aRate, aPitch, aVolume);
     88  if (!utteranceId) {
     89    return NS_ERROR_NOT_AVAILABLE;
     90  }
     91 
     92  mTaskUtteranceId = utteranceId->ToCString();
     93  mTask = aTask;
     94  mTaskTextLength = aText.Length();
     95  mTaskTextOffset = 0;
     96 
     97  return NS_OK;
     98 }
     99 
    100 SpeechSynthesisService* SpeechSynthesisService::GetInstance(bool aCreate) {
    101  if (XRE_GetProcessType() != GeckoProcessType_Default) {
    102    MOZ_ASSERT(
    103        false,
    104        "SpeechSynthesisService can only be started on main gecko process");
    105    return nullptr;
    106  }
    107 
    108  if (!sSingleton && aCreate) {
    109    sSingleton = new SpeechSynthesisService();
    110    sSingleton->Setup();
    111    ClearOnShutdown(&sSingleton);
    112  }
    113 
    114  return sSingleton;
    115 }
    116 
    117 already_AddRefed<SpeechSynthesisService>
    118 SpeechSynthesisService::GetInstanceForService() {
    119  MOZ_ASSERT(NS_IsMainThread());
    120  RefPtr<SpeechSynthesisService> sapiService = GetInstance();
    121  return sapiService.forget();
    122 }
    123 
    124 // JNI
    125 
    126 void SpeechSynthesisService::RegisterVoice(jni::String::Param aUri,
    127                                           jni::String::Param aName,
    128                                           jni::String::Param aLocale,
    129                                           bool aIsNetwork, bool aIsDefault) {
    130  nsSynthVoiceRegistry* registry = nsSynthVoiceRegistry::GetInstance();
    131  SpeechSynthesisService* service = SpeechSynthesisService::GetInstance(false);
    132  // This service can only speak one utterance at a time, so we set
    133  // aQueuesUtterances to true in order to track global state and schedule
    134  // access to this service.
    135  DebugOnly<nsresult> rv =
    136      registry->AddVoice(service, aUri->ToString(), aName->ToString(),
    137                         aLocale->ToString(), !aIsNetwork, true);
    138 
    139  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to add voice");
    140 
    141  if (aIsDefault) {
    142    DebugOnly<nsresult> rv = registry->SetDefaultVoice(aUri->ToString(), true);
    143 
    144    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to set voice as default");
    145  }
    146 }
    147 
    148 void SpeechSynthesisService::DoneRegisteringVoices() {
    149  nsSynthVoiceRegistry* registry = nsSynthVoiceRegistry::GetInstance();
    150  registry->NotifyVoicesChanged();
    151 }
    152 
    153 void SpeechSynthesisService::DispatchStart(jni::String::Param aUtteranceId) {
    154  if (sSingleton) {
    155    MOZ_ASSERT(sSingleton->mTaskUtteranceId.Equals(aUtteranceId->ToCString()));
    156    nsCOMPtr<nsISpeechTask> task = sSingleton->mTask;
    157    if (task) {
    158      sSingleton->mTaskStartTime = TimeStamp::Now();
    159      DebugOnly<nsresult> rv = task->DispatchStart();
    160      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to dispatch start");
    161    }
    162  }
    163 }
    164 
    165 void SpeechSynthesisService::DispatchEnd(jni::String::Param aUtteranceId) {
    166  if (sSingleton) {
    167    // In API older than 23, we will sometimes call this function
    168    // without providing an utterance ID.
    169    MOZ_ASSERT(!aUtteranceId ||
    170               sSingleton->mTaskUtteranceId.Equals(aUtteranceId->ToCString()));
    171    nsCOMPtr<nsISpeechTask> task = sSingleton->mTask;
    172    sSingleton->mTask = nullptr;
    173    if (task) {
    174      TimeStamp startTime = sSingleton->mTaskStartTime;
    175      DebugOnly<nsresult> rv =
    176          task->DispatchEnd((TimeStamp::Now() - startTime).ToSeconds(),
    177                            sSingleton->mTaskTextLength);
    178      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to dispatch start");
    179    }
    180  }
    181 }
    182 
    183 void SpeechSynthesisService::DispatchError(jni::String::Param aUtteranceId) {
    184  if (sSingleton) {
    185    MOZ_ASSERT(sSingleton->mTaskUtteranceId.Equals(aUtteranceId->ToCString()));
    186    nsCOMPtr<nsISpeechTask> task = sSingleton->mTask;
    187    sSingleton->mTask = nullptr;
    188    if (task) {
    189      TimeStamp startTime = sSingleton->mTaskStartTime;
    190      DebugOnly<nsresult> rv =
    191          task->DispatchError((TimeStamp::Now() - startTime).ToSeconds(),
    192                              sSingleton->mTaskTextOffset);
    193      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to dispatch start");
    194    }
    195  }
    196 }
    197 
    198 void SpeechSynthesisService::DispatchBoundary(jni::String::Param aUtteranceId,
    199                                              int32_t aStart, int32_t aEnd) {
    200  if (sSingleton) {
    201    MOZ_ASSERT(sSingleton->mTaskUtteranceId.Equals(aUtteranceId->ToCString()));
    202    nsCOMPtr<nsISpeechTask> task = sSingleton->mTask;
    203    if (task) {
    204      TimeStamp startTime = sSingleton->mTaskStartTime;
    205      sSingleton->mTaskTextOffset = aStart;
    206      DebugOnly<nsresult> rv = task->DispatchBoundary(
    207          u"word"_ns, (TimeStamp::Now() - startTime).ToSeconds(), aStart,
    208          aEnd - aStart, 1);
    209      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to dispatch boundary");
    210    }
    211  }
    212 }
    213 
    214 }  // namespace dom
    215 }  // namespace mozilla