diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/media/webspeech/synth/android | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/webspeech/synth/android')
4 files changed, 319 insertions, 0 deletions
diff --git a/dom/media/webspeech/synth/android/SpeechSynthesisService.cpp b/dom/media/webspeech/synth/android/SpeechSynthesisService.cpp new file mode 100644 index 0000000000..1b6e4b6125 --- /dev/null +++ b/dom/media/webspeech/synth/android/SpeechSynthesisService.cpp @@ -0,0 +1,215 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SpeechSynthesisService.h" + +#include <android/log.h> + +#include "nsXULAppAPI.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/nsSynthVoiceRegistry.h" +#include "mozilla/jni/Utils.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_media.h" + +#define ALOG(args...) \ + __android_log_print(ANDROID_LOG_INFO, "GeckoSpeechSynthesis", ##args) + +namespace mozilla { +namespace dom { + +StaticRefPtr<SpeechSynthesisService> SpeechSynthesisService::sSingleton; + +class AndroidSpeechCallback final : public nsISpeechTaskCallback { + public: + AndroidSpeechCallback() {} + + NS_DECL_ISUPPORTS + + NS_IMETHOD OnResume() override { return NS_OK; } + + NS_IMETHOD OnPause() override { return NS_OK; } + + NS_IMETHOD OnCancel() override { + java::SpeechSynthesisService::Stop(); + return NS_OK; + } + + NS_IMETHOD OnVolumeChanged(float aVolume) override { return NS_OK; } + + private: + ~AndroidSpeechCallback() {} +}; + +NS_IMPL_ISUPPORTS(AndroidSpeechCallback, nsISpeechTaskCallback) + +NS_IMPL_ISUPPORTS(SpeechSynthesisService, nsISpeechService) + +void SpeechSynthesisService::Setup() { + ALOG("SpeechSynthesisService::Setup"); + + if (!StaticPrefs::media_webspeech_synth_enabled() || + Preferences::GetBool("media.webspeech.synth.test")) { + return; + } + + if (!jni::IsAvailable()) { + NS_WARNING("Failed to initialize speech synthesis"); + return; + } + + Init(); + java::SpeechSynthesisService::InitSynth(); +} + +// nsISpeechService + +NS_IMETHODIMP +SpeechSynthesisService::Speak(const nsAString& aText, const nsAString& aUri, + float aVolume, float aRate, float aPitch, + nsISpeechTask* aTask) { + if (mTask) { + NS_WARNING("Service only supports one speech task at a time."); + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr<AndroidSpeechCallback> callback = new AndroidSpeechCallback(); + nsresult rv = aTask->Setup(callback); + + if (NS_FAILED(rv)) { + return rv; + } + + jni::String::LocalRef utteranceId = + java::SpeechSynthesisService::Speak(aUri, aText, aRate, aPitch, aVolume); + if (!utteranceId) { + return NS_ERROR_NOT_AVAILABLE; + } + + mTaskUtteranceId = utteranceId->ToCString(); + mTask = aTask; + mTaskTextLength = aText.Length(); + mTaskTextOffset = 0; + + return NS_OK; +} + +SpeechSynthesisService* SpeechSynthesisService::GetInstance(bool aCreate) { + if (XRE_GetProcessType() != GeckoProcessType_Default) { + MOZ_ASSERT( + false, + "SpeechSynthesisService can only be started on main gecko process"); + return nullptr; + } + + if (!sSingleton && aCreate) { + sSingleton = new SpeechSynthesisService(); + sSingleton->Setup(); + ClearOnShutdown(&sSingleton); + } + + return sSingleton; +} + +already_AddRefed<SpeechSynthesisService> +SpeechSynthesisService::GetInstanceForService() { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<SpeechSynthesisService> sapiService = GetInstance(); + return sapiService.forget(); +} + +// JNI + +void SpeechSynthesisService::RegisterVoice(jni::String::Param aUri, + jni::String::Param aName, + jni::String::Param aLocale, + bool aIsNetwork, bool aIsDefault) { + nsSynthVoiceRegistry* registry = nsSynthVoiceRegistry::GetInstance(); + SpeechSynthesisService* service = SpeechSynthesisService::GetInstance(false); + // This service can only speak one utterance at a time, so we set + // aQueuesUtterances to true in order to track global state and schedule + // access to this service. + DebugOnly<nsresult> rv = + registry->AddVoice(service, aUri->ToString(), aName->ToString(), + aLocale->ToString(), !aIsNetwork, true); + + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to add voice"); + + if (aIsDefault) { + DebugOnly<nsresult> rv = registry->SetDefaultVoice(aUri->ToString(), true); + + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to set voice as default"); + } +} + +void SpeechSynthesisService::DoneRegisteringVoices() { + nsSynthVoiceRegistry* registry = nsSynthVoiceRegistry::GetInstance(); + registry->NotifyVoicesChanged(); +} + +void SpeechSynthesisService::DispatchStart(jni::String::Param aUtteranceId) { + if (sSingleton) { + MOZ_ASSERT(sSingleton->mTaskUtteranceId.Equals(aUtteranceId->ToCString())); + nsCOMPtr<nsISpeechTask> task = sSingleton->mTask; + if (task) { + sSingleton->mTaskStartTime = TimeStamp::Now(); + DebugOnly<nsresult> rv = task->DispatchStart(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to dispatch start"); + } + } +} + +void SpeechSynthesisService::DispatchEnd(jni::String::Param aUtteranceId) { + if (sSingleton) { + // In API older than 23, we will sometimes call this function + // without providing an utterance ID. + MOZ_ASSERT(!aUtteranceId || + sSingleton->mTaskUtteranceId.Equals(aUtteranceId->ToCString())); + nsCOMPtr<nsISpeechTask> task = sSingleton->mTask; + sSingleton->mTask = nullptr; + if (task) { + TimeStamp startTime = sSingleton->mTaskStartTime; + DebugOnly<nsresult> rv = + task->DispatchEnd((TimeStamp::Now() - startTime).ToSeconds(), + sSingleton->mTaskTextLength); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to dispatch start"); + } + } +} + +void SpeechSynthesisService::DispatchError(jni::String::Param aUtteranceId) { + if (sSingleton) { + MOZ_ASSERT(sSingleton->mTaskUtteranceId.Equals(aUtteranceId->ToCString())); + nsCOMPtr<nsISpeechTask> task = sSingleton->mTask; + sSingleton->mTask = nullptr; + if (task) { + TimeStamp startTime = sSingleton->mTaskStartTime; + DebugOnly<nsresult> rv = + task->DispatchError((TimeStamp::Now() - startTime).ToSeconds(), + sSingleton->mTaskTextOffset); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to dispatch start"); + } + } +} + +void SpeechSynthesisService::DispatchBoundary(jni::String::Param aUtteranceId, + int32_t aStart, int32_t aEnd) { + if (sSingleton) { + MOZ_ASSERT(sSingleton->mTaskUtteranceId.Equals(aUtteranceId->ToCString())); + nsCOMPtr<nsISpeechTask> task = sSingleton->mTask; + if (task) { + TimeStamp startTime = sSingleton->mTaskStartTime; + sSingleton->mTaskTextOffset = aStart; + DebugOnly<nsresult> rv = task->DispatchBoundary( + u"word"_ns, (TimeStamp::Now() - startTime).ToSeconds(), aStart, + aEnd - aStart, 1); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to dispatch boundary"); + } + } +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/media/webspeech/synth/android/SpeechSynthesisService.h b/dom/media/webspeech/synth/android/SpeechSynthesisService.h new file mode 100644 index 0000000000..98c5143cf6 --- /dev/null +++ b/dom/media/webspeech/synth/android/SpeechSynthesisService.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_SpeechSynthesisService_h +#define mozilla_dom_SpeechSynthesisService_h + +#include "nsISpeechService.h" +#include "mozilla/java/SpeechSynthesisServiceNatives.h" +#include "mozilla/StaticPtr.h" + +namespace mozilla { +namespace dom { + +class SpeechSynthesisService final + : public nsISpeechService, + public java::SpeechSynthesisService::Natives<SpeechSynthesisService> { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISPEECHSERVICE + + SpeechSynthesisService(){}; + + void Setup(); + + static void DoneRegisteringVoices(); + + static void RegisterVoice(jni::String::Param aUri, jni::String::Param aName, + jni::String::Param aLocale, bool aIsNetwork, + bool aIsDefault); + + static void DispatchStart(jni::String::Param aUtteranceId); + + static void DispatchEnd(jni::String::Param aUtteranceId); + + static void DispatchError(jni::String::Param aUtteranceId); + + static void DispatchBoundary(jni::String::Param aUtteranceId, int32_t aStart, + int32_t aEnd); + + static SpeechSynthesisService* GetInstance(bool aCreate = true); + static already_AddRefed<SpeechSynthesisService> GetInstanceForService(); + + static StaticRefPtr<SpeechSynthesisService> sSingleton; + + private: + virtual ~SpeechSynthesisService(){}; + + nsCOMPtr<nsISpeechTask> mTask; + + // Unique ID assigned to utterance when it is sent to system service. + nsCString mTaskUtteranceId; + + // Time stamp from the moment the utterance is started. + TimeStamp mTaskStartTime; + + // Length of text of the utterance. + uint32_t mTaskTextLength; + + // Current offset in characters of what has been spoken. + uint32_t mTaskTextOffset; +}; + +} // namespace dom +} // namespace mozilla +#endif diff --git a/dom/media/webspeech/synth/android/components.conf b/dom/media/webspeech/synth/android/components.conf new file mode 100644 index 0000000000..4c35954fcc --- /dev/null +++ b/dom/media/webspeech/synth/android/components.conf @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'cid': '{311b2dab-f4d3-4be4-8123-6732313d95c2}', + 'contract_ids': ['@mozilla.org/androidspeechsynth;1'], + 'singleton': True, + 'type': 'mozilla::dom::SpeechSynthesisService', + 'headers': ['/dom/media/webspeech/synth/android/SpeechSynthesisService.h'], + 'constructor': 'mozilla::dom::SpeechSynthesisService::GetInstanceForService', + 'categories': {"speech-synth-started": 'Android Speech Synth'}, + }, +] diff --git a/dom/media/webspeech/synth/android/moz.build b/dom/media/webspeech/synth/android/moz.build new file mode 100644 index 0000000000..348c157f3c --- /dev/null +++ b/dom/media/webspeech/synth/android/moz.build @@ -0,0 +1,19 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS.mozilla.dom += ["SpeechSynthesisService.h"] + +UNIFIED_SOURCES += [ + "SpeechSynthesisService.cpp", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" |