dom/media/webspeech/synth/test/nsFakeSynthServices.cpp
author Mozilla Releng Treescript <release+treescript@mozilla.org>
Wed, 19 Jan 2022 06:57:01 +0000
changeset 604799 184fd64dd78347ea17dbd5aef4a9d8e40fcc9f7f
parent 558319 93fabad45659d172b723c9606215d3acaab54df1
permissions -rw-r--r--
no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD ach -> 977c0eee5774e3bd19a25900142d1dca3eb2ee29 af -> 162654a20c86192e124df9766ec5ecd54143d085 an -> d10834e1c7ba839ba9c372205f4371c4d3c19791 ar -> 3be417e9326ebc9e369fc77f1fd98481a39b07cf ast -> 41dcea23a9d890ee00cb4cc3797559fe461244bc az -> 0366cdbc61ed5620510c848b85cd593d376c75f3 be -> 62bb6ed3829ec227748a86410cf038052f673b54 bg -> 19ee7b31e0becafa054013a3c8c09844638f244c bn -> 010d5f74034cb2c18fd1100e0d0888a245d52a60 bo -> 97059fd9d1a83e254bf84da12f1612062e82d271 br -> a9e3b8ba10759c8a4f7da6b7970911ded7d04ad6 brx -> 573ec21d6a1aebf6a342533bb06b77c79b6e994b bs -> 9ebb98165b36860f938bfb97c1956090e8daf920 ca -> caf5515093962f3f92c92aea68dacd4c3e520a8d ca-valencia -> d8f8751d595bf879afe5bfa8e3854ee5b6d1b977 cak -> 0bbd8b94c9c02fa5ec6a3e5fd3bf3e69b0432829 ckb -> bd043d903599db44feb371bf29d4a3d510a48a0a cs -> ae677a6bee277f507e673c3fcd2d56088b2caafb cy -> c46474099266c675d8ddc8fbcecd23a8a11a174e da -> 4595dd6f7ed91af507076eb0a95a44f4d0646404 de -> d3fca46cd495000d26fd26d3a2776b8f4dae786c dsb -> 611248df8f629860d012cbc8f3a36acfd8744b46 el -> 22d62ecd4c214d52af254d87c2da7410bd571fb2 en-CA -> 501d80026e4fdeac79e20b41cb8087c7ec3c3ba2 en-GB -> ec99571b02068fd4bf160416b5b7e35379c899a2 eo -> 6f7723c2bfbf5e3aeb1134909464bbc099c86a4f es-AR -> e4e8f59ee9824548910f3281d02c5727caa36dd2 es-CL -> 08ee034825af181840c14c2bee5a7c20d0aa59ac es-ES -> 46729ef124e5cf1f0a3e85d50c16a069602cfed2 es-MX -> 43859d642bd044060a4d2089f4b30de8bb470e43 et -> 7cc4df8eca6d3b6c4e3073ed35770034e9be79ca eu -> 863a0a1a315b55c18694e29430c272baba355b4c fa -> 60cff0755aaf290a159cb6968c4909bf9ecae2fa ff -> ec175d67f9056e5fd2cb6568ee546336efb77d5c fi -> 6e37410128f2d675c3414ba9c6341af81b4521ff fr -> ce1d665cec91d4d29160860dba2350f22f2f4df7 fy-NL -> 5e4350e39e2a7797d6bd8e1c1bb0b15252397010 ga-IE -> 2d955174d7ab6068656b2f02e4d8c689bb2399bf gd -> 49032dc335b8b5e9ecd10435f878598bffa6541a gl -> d8761e8f84efac80d6a7482931a01cecd66dd60d gn -> aa33a53b688bed9cedeb8a3eaf0466b9451d2e18 gu-IN -> d2c670d798b925090af6321cb3270fa6f806108d he -> 3385ae23e3b569ba9c0c1aca4df25277b81a108e hi-IN -> 274838883f7f1f14d5f14d5116e7df11cc404911 hr -> a01b7ce3515d0b2f05e473625148f11a264da0a2 hsb -> f137d0e6746dc23beff4745474df87c33fae353b hu -> 39e18e316a85b5aaada8c560d51317b5e28b77d8 hy-AM -> 41e938f8ebf1581d1bc76bd088fc1ac43bb83e0b hye -> 5843bc7348ba6a533afffe08eedc48c623963542 ia -> 031f226bad1d97adf265f75fcb467a18c223c606 id -> 4d4de9751872149e1c99dcfce2b7ce4835b65589 is -> 5753814cd9664948b3ac5ace293b3d46304f0cf2 it -> 3448943cfadfc5a361bd367c7e6be58f86cc61a1 ja -> c4be75596e316966ab1dc9857a9be6b0a7bc2ef9 ja-JP-mac -> 476eca379b8b21857655d6c23908b3e2a1b09aee ka -> 2d66f0c2bd7a06ac1ad6ad02a09a26ff1e60e3cb kab -> 409893635bedbe52fa8b85784844e15756d4a8fd kk -> c68316baa4a0dd041abb4e8efd557339fb0d9cb4 km -> c04f31c98e50315177467bd4e079200d3b38b835 kn -> 0bc693a82c819a27e71f41828c040e7be4b92785 ko -> 88096d30e35f344fcb61f08900340b31fca79cc7 lij -> e46553ebd09bbabad095ccc4c8b7d147f895a475 lo -> 942d94da48979eb60bcd62d825f7f9337314999f lt -> 6a447408fe67e37ee56a85d8bea523219dac52e1 ltg -> 06e42a8ee9e5211fa8812542a6bd3c22e3c540ba lv -> 0059bd3f14ced8ec1ef0f2765b655fe4ee537e91 meh -> 54cb55f92ef9d93d0481b3806b725d08035d950f mk -> c473f0b380bd14c98754cb38cdc287c5b0df99a0 mr -> b3382076ee961b51f0e2a7369c08aaf03fb8574d ms -> ec4c315ae032575bed799d8faa57dd4441e5bd9e my -> 25d516d33a6c2f8881683c5530f468e2c25ab6eb nb-NO -> 490c2d401359fd2204ad7aa806a73107f3befc27 ne-NP -> 5676e8e0b93e18fb5eeb238953c512d4f407340c nl -> 3fbbd92a2f116150e9921f13e4c9fe37d6948464 nn-NO -> 7d8c6d43ad98b2a8a0dce7ef05234b66cb818244 oc -> 8379011f2b605f1597fc61e4585520911ca58567 pa-IN -> b28bb037d4687370eebb7a892982b31bddea32b0 pl -> 3193b4b820e7c37bdb9153ab9c525f18210a501a pt-BR -> ce1ab16cc1acd0fa7ae898feeba46c6d497fe981 pt-PT -> e0910097929ee117291ede6675c6e0add124221b rm -> cdce5683cd6621c0ca31eb6b6ffd209481fa3c85 ro -> ba7fe96d6c98e01a48f442d520c39d119c7e0727 ru -> dc6d46ab977eb359c21a0f6dfe5b22e1fa60fda0 sat -> 3ab6969e38e316491894b42527f2287dbbd7d626 sc -> 14a1b41f804a89dd00d978056ebfc966a638cbb8 scn -> 6729c4bfb82228012e51e2077cf8f2ce254e8fe6 sco -> 8aed4c4ac1dd708f213d350a46617cd3faca0a09 si -> 639820754f8563d1fccf89f48c4b155db6a3ae8d sk -> 588464d4ecd69c9a12ffd0e41ccb5e4a7f16e25e sl -> e7aa581fc059a4e59ae56eb03790d0d7e4129a42 son -> 1541de0c90d66751ae72c24d4fa8105a585cf203 sq -> 05efafdd60c5a69d6d75adf8ccb97c5b07aee239 sr -> e7c7978d2fd74f4e2098253a41b099604b88d61b sv-SE -> fbeae2bbc5837470c90793f92a1cc90b7d6fd51f szl -> 000ee773591f17bcd9fc5ed5eb504000a016ce5b ta -> 2545635e99da1c3bdfc3536ba8c6a68a7498d014 te -> 0c5e4eb71c5ffe04c651f377355ead0f976960cd tg -> 8c5d8e78f165f7aef1f2597a67944cf8c101f906 th -> b6c8c65ece954839a983d48aa92a348b84867fa7 tl -> c7615e24b3453bb59a41eb017db2e04c781c8782 tr -> 08e01536e9eab024fceabe205921d815094af748 trs -> 43debe20e58afdd09ddbcd2f7dd62039c1e8c5f8 uk -> a4d58069863410d7b878690855604f86b12c300b ur -> 354acd5f98b1a03c6960231c570fedb771588d86 uz -> f8e8cc127eaf07ab77bf51c37ec494d2721e8d2e vi -> e24285029832c0cc588e8e12aa2212dff982d623 wo -> 625cc2e007db82c90ecddd39e34f1969c4088e09 xh -> 09a4bf9ceb7fe52e7fd54b2c8a15c16c6a4c56f2 zh-CN -> 5c57a47c29f1c8105d8c268617a00c54e4a8ba8c zh-TW -> f86c570fdc59c21666014d9c81f61c9ebeac0d72

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsISupports.h"
#include "nsFakeSynthServices.h"
#include "nsPrintfCString.h"
#include "SharedBuffer.h"

#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/nsSynthVoiceRegistry.h"
#include "mozilla/dom/nsSpeechTask.h"

#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "prenv.h"
#include "mozilla/Preferences.h"
#include "mozilla/DebugOnly.h"

#define CHANNELS 1
#define SAMPLERATE 1600

namespace mozilla::dom {

StaticRefPtr<nsFakeSynthServices> nsFakeSynthServices::sSingleton;

enum VoiceFlags {
  eSuppressEvents = 1,
  eSuppressEnd = 2,
  eFailAtStart = 4,
  eFail = 8
};

struct VoiceDetails {
  const char* uri;
  const char* name;
  const char* lang;
  bool defaultVoice;
  uint32_t flags;
};

static const VoiceDetails sVoices[] = {
    {"urn:moz-tts:fake:bob", "Bob Marley", "en-JM", true, 0},
    {"urn:moz-tts:fake:amy", "Amy Winehouse", "en-GB", false, 0},
    {"urn:moz-tts:fake:lenny", "Leonard Cohen", "en-CA", false, 0},
    {"urn:moz-tts:fake:celine", "Celine Dion", "fr-CA", false, 0},
    {
        "urn:moz-tts:fake:julie",
        "Julieta Venegas",
        "es-MX",
        false,
    },
    {"urn:moz-tts:fake:zanetta", "Zanetta Farussi", "it-IT", false, 0},
    {"urn:moz-tts:fake:margherita", "Margherita Durastanti",
     "it-IT-noevents-noend", false, eSuppressEvents | eSuppressEnd},
    {"urn:moz-tts:fake:teresa", "Teresa Cornelys", "it-IT-noend", false,
     eSuppressEnd},
    {"urn:moz-tts:fake:cecilia", "Cecilia Bartoli", "it-IT-failatstart", false,
     eFailAtStart},
    {"urn:moz-tts:fake:gottardo", "Gottardo Aldighieri", "it-IT-fail", false,
     eFail},
};

// FakeSynthCallback
class FakeSynthCallback : public nsISpeechTaskCallback {
 public:
  explicit FakeSynthCallback(nsISpeechTask* aTask) : mTask(aTask) {}
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(FakeSynthCallback,
                                           nsISpeechTaskCallback)

  NS_IMETHOD OnPause() override {
    if (mTask) {
      mTask->DispatchPause(1.5, 1);
    }

    return NS_OK;
  }

  NS_IMETHOD OnResume() override {
    if (mTask) {
      mTask->DispatchResume(1.5, 1);
    }

    return NS_OK;
  }

  NS_IMETHOD OnCancel() override {
    if (mTask) {
      mTask->DispatchEnd(1.5, 1);
    }

    return NS_OK;
  }

  NS_IMETHOD OnVolumeChanged(float aVolume) override { return NS_OK; }

 private:
  virtual ~FakeSynthCallback() = default;

  nsCOMPtr<nsISpeechTask> mTask;
};

NS_IMPL_CYCLE_COLLECTION(FakeSynthCallback, mTask);

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FakeSynthCallback)
  NS_INTERFACE_MAP_ENTRY(nsISpeechTaskCallback)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTaskCallback)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(FakeSynthCallback)
NS_IMPL_CYCLE_COLLECTING_RELEASE(FakeSynthCallback)

// FakeSpeechSynth

class FakeSpeechSynth : public nsISpeechService {
 public:
  FakeSpeechSynth() = default;

  NS_DECL_ISUPPORTS
  NS_DECL_NSISPEECHSERVICE

 private:
  virtual ~FakeSpeechSynth() = default;
};

NS_IMPL_ISUPPORTS(FakeSpeechSynth, nsISpeechService)

NS_IMETHODIMP
FakeSpeechSynth::Speak(const nsAString& aText, const nsAString& aUri,
                       float aVolume, float aRate, float aPitch,
                       nsISpeechTask* aTask) {
  class DispatchStart final : public Runnable {
   public:
    explicit DispatchStart(nsISpeechTask* aTask)
        : mozilla::Runnable("DispatchStart"), mTask(aTask) {}

    NS_IMETHOD Run() override {
      mTask->DispatchStart();

      return NS_OK;
    }

   private:
    nsCOMPtr<nsISpeechTask> mTask;
  };

  class DispatchEnd final : public Runnable {
   public:
    DispatchEnd(nsISpeechTask* aTask, const nsAString& aText)
        : mozilla::Runnable("DispatchEnd"), mTask(aTask), mText(aText) {}

    NS_IMETHOD Run() override {
      mTask->DispatchEnd(mText.Length() / 2, mText.Length());

      return NS_OK;
    }

   private:
    nsCOMPtr<nsISpeechTask> mTask;
    nsString mText;
  };

  class DispatchError final : public Runnable {
   public:
    DispatchError(nsISpeechTask* aTask, const nsAString& aText)
        : mozilla::Runnable("DispatchError"), mTask(aTask), mText(aText) {}

    NS_IMETHOD Run() override {
      mTask->DispatchError(mText.Length() / 2, mText.Length());

      return NS_OK;
    }

   private:
    nsCOMPtr<nsISpeechTask> mTask;
    nsString mText;
  };

  uint32_t flags = 0;
  for (VoiceDetails voice : sVoices) {
    if (aUri.EqualsASCII(voice.uri)) {
      flags = voice.flags;
      break;
    }
  }

  if (flags & eFailAtStart) {
    return NS_ERROR_FAILURE;
  }

  RefPtr<FakeSynthCallback> cb =
      new FakeSynthCallback((flags & eSuppressEvents) ? nullptr : aTask);

  aTask->Setup(cb);

  nsCOMPtr<nsIRunnable> runnable = new DispatchStart(aTask);
  NS_DispatchToMainThread(runnable);

  if (flags & eFail) {
    runnable = new DispatchError(aTask, aText);
    NS_DispatchToMainThread(runnable);
  } else if ((flags & eSuppressEnd) == 0) {
    runnable = new DispatchEnd(aTask, aText);
    NS_DispatchToMainThread(runnable);
  }

  return NS_OK;
}

// nsFakeSynthService

NS_INTERFACE_MAP_BEGIN(nsFakeSynthServices)
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
NS_INTERFACE_MAP_END

NS_IMPL_ADDREF(nsFakeSynthServices)
NS_IMPL_RELEASE(nsFakeSynthServices)

static void AddVoices(nsISpeechService* aService, const VoiceDetails* aVoices,
                      uint32_t aLength) {
  RefPtr<nsSynthVoiceRegistry> registry = nsSynthVoiceRegistry::GetInstance();
  for (uint32_t i = 0; i < aLength; i++) {
    NS_ConvertUTF8toUTF16 name(aVoices[i].name);
    NS_ConvertUTF8toUTF16 uri(aVoices[i].uri);
    NS_ConvertUTF8toUTF16 lang(aVoices[i].lang);
    // These services can handle more than one utterance at a time and have
    // several speaking simultaniously. So, aQueuesUtterances == false
    registry->AddVoice(aService, uri, name, lang, true, false);
    if (aVoices[i].defaultVoice) {
      registry->SetDefaultVoice(uri, true);
    }
  }

  registry->NotifyVoicesChanged();
}

void nsFakeSynthServices::Init() {
  mSynthService = new FakeSpeechSynth();
  AddVoices(mSynthService, sVoices, ArrayLength(sVoices));
}

// nsIObserver

NS_IMETHODIMP
nsFakeSynthServices::Observe(nsISupports* aSubject, const char* aTopic,
                             const char16_t* aData) {
  MOZ_ASSERT(NS_IsMainThread());
  if (NS_WARN_IF(!(!strcmp(aTopic, "speech-synth-started")))) {
    return NS_ERROR_UNEXPECTED;
  }

  if (Preferences::GetBool("media.webspeech.synth.test")) {
    NS_DispatchToMainThread(NewRunnableMethod(
        "dom::nsFakeSynthServices::Init", this, &nsFakeSynthServices::Init));
  }

  return NS_OK;
}

// static methods

nsFakeSynthServices* nsFakeSynthServices::GetInstance() {
  MOZ_ASSERT(NS_IsMainThread());
  if (!XRE_IsParentProcess()) {
    MOZ_ASSERT(false,
               "nsFakeSynthServices can only be started on main gecko process");
    return nullptr;
  }

  if (!sSingleton) {
    sSingleton = new nsFakeSynthServices();
    ClearOnShutdown(&sSingleton);
  }

  return sSingleton;
}

already_AddRefed<nsFakeSynthServices>
nsFakeSynthServices::GetInstanceForService() {
  RefPtr<nsFakeSynthServices> picoService = GetInstance();
  return picoService.forget();
}

}  // namespace mozilla::dom