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