summaryrefslogtreecommitdiffstats
path: root/dom/media/webspeech/synth/test/nsFakeSynthServices.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webspeech/synth/test/nsFakeSynthServices.cpp')
-rw-r--r--dom/media/webspeech/synth/test/nsFakeSynthServices.cpp401
1 files changed, 401 insertions, 0 deletions
diff --git a/dom/media/webspeech/synth/test/nsFakeSynthServices.cpp b/dom/media/webspeech/synth/test/nsFakeSynthServices.cpp
new file mode 100644
index 000000000..582ff3551
--- /dev/null
+++ b/dom/media/webspeech/synth/test/nsFakeSynthServices.cpp
@@ -0,0 +1,401 @@
+/* -*- 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 "nsIWeakReferenceUtils.h"
+#include "SharedBuffer.h"
+#include "nsISimpleEnumerator.h"
+
+#include "mozilla/dom/nsSynthVoiceRegistry.h"
+#include "mozilla/dom/nsSpeechTask.h"
+
+#include "nsThreadUtils.h"
+#include "prenv.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/DebugOnly.h"
+
+#define CHANNELS 1
+#define SAMPLERATE 1600
+
+namespace mozilla {
+namespace 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 sDirectVoices[] = {
+ {"urn:moz-tts:fake-direct:bob", "Bob Marley", "en-JM", true, 0},
+ {"urn:moz-tts:fake-direct:amy", "Amy Winehouse", "en-GB", false, 0},
+ {"urn:moz-tts:fake-direct:lenny", "Leonard Cohen", "en-CA", false, 0},
+ {"urn:moz-tts:fake-direct:celine", "Celine Dion", "fr-CA", false, 0},
+ {"urn:moz-tts:fake-direct:julie", "Julieta Venegas", "es-MX", false, },
+};
+
+static const VoiceDetails sIndirectVoices[] = {
+ {"urn:moz-tts:fake-indirect:zanetta", "Zanetta Farussi", "it-IT", false, 0},
+ {"urn:moz-tts:fake-indirect:margherita", "Margherita Durastanti", "it-IT-noevents-noend", false, eSuppressEvents | eSuppressEnd},
+ {"urn:moz-tts:fake-indirect:teresa", "Teresa Cornelys", "it-IT-noend", false, eSuppressEnd},
+ {"urn:moz-tts:fake-indirect:cecilia", "Cecilia Bartoli", "it-IT-failatstart", false, eFailAtStart},
+ {"urn:moz-tts:fake-indirect: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() { }
+
+ 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)
+
+// FakeDirectAudioSynth
+
+class FakeDirectAudioSynth : public nsISpeechService
+{
+
+public:
+ FakeDirectAudioSynth() { }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISPEECHSERVICE
+
+private:
+ virtual ~FakeDirectAudioSynth() { }
+};
+
+NS_IMPL_ISUPPORTS(FakeDirectAudioSynth, nsISpeechService)
+
+NS_IMETHODIMP
+FakeDirectAudioSynth::Speak(const nsAString& aText, const nsAString& aUri,
+ float aVolume, float aRate, float aPitch,
+ nsISpeechTask* aTask)
+{
+ class Runnable final : public mozilla::Runnable
+ {
+ public:
+ Runnable(nsISpeechTask* aTask, const nsAString& aText) :
+ mTask(aTask), mText(aText)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ RefPtr<FakeSynthCallback> cb = new FakeSynthCallback(nullptr);
+ mTask->Setup(cb, CHANNELS, SAMPLERATE, 2);
+
+ // Just an arbitrary multiplier. Pretend that each character is
+ // synthesized to 40 frames.
+ uint32_t frames_length = 40 * mText.Length();
+ auto frames = MakeUnique<int16_t[]>(frames_length);
+ mTask->SendAudioNative(frames.get(), frames_length);
+
+ mTask->SendAudioNative(nullptr, 0);
+
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsISpeechTask> mTask;
+ nsString mText;
+ };
+
+ nsCOMPtr<nsIRunnable> runnable = new Runnable(aTask, aText);
+ NS_DispatchToMainThread(runnable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FakeDirectAudioSynth::GetServiceType(SpeechServiceType* aServiceType)
+{
+ *aServiceType = nsISpeechService::SERVICETYPE_DIRECT_AUDIO;
+ return NS_OK;
+}
+
+// FakeDirectAudioSynth
+
+class FakeIndirectAudioSynth : public nsISpeechService
+{
+
+public:
+ FakeIndirectAudioSynth() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISPEECHSERVICE
+
+private:
+ virtual ~FakeIndirectAudioSynth() { }
+};
+
+NS_IMPL_ISUPPORTS(FakeIndirectAudioSynth, nsISpeechService)
+
+NS_IMETHODIMP
+FakeIndirectAudioSynth::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) :
+ 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) :
+ 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) :
+ 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 (uint32_t i = 0; i < ArrayLength(sIndirectVoices); i++) {
+ if (aUri.EqualsASCII(sIndirectVoices[i].uri)) {
+ flags = sIndirectVoices[i].flags;
+ }
+ }
+
+ if (flags & eFailAtStart) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<FakeSynthCallback> cb = new FakeSynthCallback(
+ (flags & eSuppressEvents) ? nullptr : aTask);
+
+ aTask->Setup(cb, 0, 0, 0);
+
+ 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;
+}
+
+NS_IMETHODIMP
+FakeIndirectAudioSynth::GetServiceType(SpeechServiceType* aServiceType)
+{
+ *aServiceType = nsISpeechService::SERVICETYPE_INDIRECT_AUDIO;
+ 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)
+
+nsFakeSynthServices::nsFakeSynthServices()
+{
+}
+
+nsFakeSynthServices::~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()
+{
+ mDirectService = new FakeDirectAudioSynth();
+ AddVoices(mDirectService, sDirectVoices, ArrayLength(sDirectVoices));
+
+ mIndirectService = new FakeIndirectAudioSynth();
+ AddVoices(mIndirectService, sIndirectVoices, ArrayLength(sIndirectVoices));
+}
+
+// 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(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();
+ }
+
+ return sSingleton;
+}
+
+already_AddRefed<nsFakeSynthServices>
+nsFakeSynthServices::GetInstanceForService()
+{
+ RefPtr<nsFakeSynthServices> picoService = GetInstance();
+ return picoService.forget();
+}
+
+void
+nsFakeSynthServices::Shutdown()
+{
+ if (!sSingleton) {
+ return;
+ }
+
+ sSingleton = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla