nsFakeSynthServices.cpp (7617B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 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 "nsFakeSynthServices.h" 8 9 #include "SharedBuffer.h" 10 #include "mozilla/ClearOnShutdown.h" 11 #include "mozilla/Preferences.h" 12 #include "mozilla/dom/nsSpeechTask.h" 13 #include "mozilla/dom/nsSynthVoiceRegistry.h" 14 #include "nsISupports.h" 15 #include "nsPrintfCString.h" 16 #include "nsThreadUtils.h" 17 #include "nsXULAppAPI.h" 18 #include "prenv.h" 19 20 #define CHANNELS 1 21 #define SAMPLERATE 1600 22 23 namespace mozilla::dom { 24 25 StaticRefPtr<nsFakeSynthServices> nsFakeSynthServices::sSingleton; 26 27 enum VoiceFlags { 28 eSuppressEvents = 1, 29 eSuppressEnd = 2, 30 eFailAtStart = 4, 31 eFail = 8 32 }; 33 34 struct VoiceDetails { 35 const char* uri; 36 const char* name; 37 const char* lang; 38 bool defaultVoice; 39 uint32_t flags; 40 }; 41 42 static const VoiceDetails sVoices[] = { 43 {"urn:moz-tts:fake:bob", "Bob Marley", "en-JM", true, 0}, 44 {"urn:moz-tts:fake:amy", "Amy Winehouse", "en-GB", false, 0}, 45 {"urn:moz-tts:fake:lenny", "Leonard Cohen", "en-CA", false, 0}, 46 {"urn:moz-tts:fake:celine", "Celine Dion", "fr-CA", false, 0}, 47 { 48 "urn:moz-tts:fake:julie", 49 "Julieta Venegas", 50 "es-MX", 51 false, 52 }, 53 {"urn:moz-tts:fake:zanetta", "Zanetta Farussi", "it-IT", false, 0}, 54 {"urn:moz-tts:fake:margherita", "Margherita Durastanti", 55 "it-IT-noevents-noend", false, eSuppressEvents | eSuppressEnd}, 56 {"urn:moz-tts:fake:teresa", "Teresa Cornelys", "it-IT-noend", false, 57 eSuppressEnd}, 58 {"urn:moz-tts:fake:cecilia", "Cecilia Bartoli", "it-IT-failatstart", false, 59 eFailAtStart}, 60 {"urn:moz-tts:fake:gottardo", "Gottardo Aldighieri", "it-IT-fail", false, 61 eFail}, 62 }; 63 64 // FakeSynthCallback 65 class FakeSynthCallback : public nsISpeechTaskCallback { 66 public: 67 explicit FakeSynthCallback(nsISpeechTask* aTask) : mTask(aTask) {} 68 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 69 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(FakeSynthCallback, 70 nsISpeechTaskCallback) 71 72 NS_IMETHOD OnPause() override { 73 if (mTask) { 74 mTask->DispatchPause(1.5, 1); 75 } 76 77 return NS_OK; 78 } 79 80 NS_IMETHOD OnResume() override { 81 if (mTask) { 82 mTask->DispatchResume(1.5, 1); 83 } 84 85 return NS_OK; 86 } 87 88 NS_IMETHOD OnCancel() override { 89 if (mTask) { 90 mTask->DispatchEnd(1.5, 1); 91 } 92 93 return NS_OK; 94 } 95 96 NS_IMETHOD OnVolumeChanged(float aVolume) override { return NS_OK; } 97 98 private: 99 virtual ~FakeSynthCallback() = default; 100 101 nsCOMPtr<nsISpeechTask> mTask; 102 }; 103 104 NS_IMPL_CYCLE_COLLECTION(FakeSynthCallback, mTask); 105 106 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FakeSynthCallback) 107 NS_INTERFACE_MAP_ENTRY(nsISpeechTaskCallback) 108 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTaskCallback) 109 NS_INTERFACE_MAP_END 110 111 NS_IMPL_CYCLE_COLLECTING_ADDREF(FakeSynthCallback) 112 NS_IMPL_CYCLE_COLLECTING_RELEASE(FakeSynthCallback) 113 114 // FakeSpeechSynth 115 116 class FakeSpeechSynth : public nsISpeechService { 117 public: 118 FakeSpeechSynth() = default; 119 120 NS_DECL_ISUPPORTS 121 NS_DECL_NSISPEECHSERVICE 122 123 private: 124 virtual ~FakeSpeechSynth() = default; 125 }; 126 127 NS_IMPL_ISUPPORTS(FakeSpeechSynth, nsISpeechService) 128 129 NS_IMETHODIMP 130 FakeSpeechSynth::Speak(const nsAString& aText, const nsAString& aUri, 131 float aVolume, float aRate, float aPitch, 132 nsISpeechTask* aTask) { 133 class DispatchStart final : public Runnable { 134 public: 135 explicit DispatchStart(nsISpeechTask* aTask) 136 : mozilla::Runnable("DispatchStart"), mTask(aTask) {} 137 138 NS_IMETHOD Run() override { 139 mTask->DispatchStart(); 140 141 return NS_OK; 142 } 143 144 private: 145 nsCOMPtr<nsISpeechTask> mTask; 146 }; 147 148 class DispatchEnd final : public Runnable { 149 public: 150 DispatchEnd(nsISpeechTask* aTask, const nsAString& aText) 151 : mozilla::Runnable("DispatchEnd"), mTask(aTask), mText(aText) {} 152 153 NS_IMETHOD Run() override { 154 mTask->DispatchEnd(mText.Length() / 2, mText.Length()); 155 156 return NS_OK; 157 } 158 159 private: 160 nsCOMPtr<nsISpeechTask> mTask; 161 nsString mText; 162 }; 163 164 class DispatchError final : public Runnable { 165 public: 166 DispatchError(nsISpeechTask* aTask, const nsAString& aText) 167 : mozilla::Runnable("DispatchError"), mTask(aTask), mText(aText) {} 168 169 NS_IMETHOD Run() override { 170 mTask->DispatchError(mText.Length() / 2, mText.Length()); 171 172 return NS_OK; 173 } 174 175 private: 176 nsCOMPtr<nsISpeechTask> mTask; 177 nsString mText; 178 }; 179 180 uint32_t flags = 0; 181 for (VoiceDetails voice : sVoices) { 182 if (aUri.EqualsASCII(voice.uri)) { 183 flags = voice.flags; 184 break; 185 } 186 } 187 188 if (flags & eFailAtStart) { 189 return NS_ERROR_FAILURE; 190 } 191 192 RefPtr<FakeSynthCallback> cb = 193 new FakeSynthCallback((flags & eSuppressEvents) ? nullptr : aTask); 194 195 aTask->Setup(cb); 196 197 nsCOMPtr<nsIRunnable> runnable = new DispatchStart(aTask); 198 NS_DispatchToMainThread(runnable); 199 200 if (flags & eFail) { 201 runnable = new DispatchError(aTask, aText); 202 NS_DispatchToMainThread(runnable); 203 } else if ((flags & eSuppressEnd) == 0) { 204 runnable = new DispatchEnd(aTask, aText); 205 NS_DispatchToMainThread(runnable); 206 } 207 208 return NS_OK; 209 } 210 211 // nsFakeSynthService 212 213 NS_INTERFACE_MAP_BEGIN(nsFakeSynthServices) 214 NS_INTERFACE_MAP_ENTRY(nsIObserver) 215 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver) 216 NS_INTERFACE_MAP_END 217 218 NS_IMPL_ADDREF(nsFakeSynthServices) 219 NS_IMPL_RELEASE(nsFakeSynthServices) 220 221 static void AddVoices(nsISpeechService* aService, const VoiceDetails* aVoices, 222 uint32_t aLength) { 223 RefPtr<nsSynthVoiceRegistry> registry = nsSynthVoiceRegistry::GetInstance(); 224 for (uint32_t i = 0; i < aLength; i++) { 225 NS_ConvertUTF8toUTF16 name(aVoices[i].name); 226 NS_ConvertUTF8toUTF16 uri(aVoices[i].uri); 227 NS_ConvertUTF8toUTF16 lang(aVoices[i].lang); 228 // These services can handle more than one utterance at a time and have 229 // several speaking simultaniously. So, aQueuesUtterances == false 230 registry->AddVoice(aService, uri, name, lang, true, false); 231 if (aVoices[i].defaultVoice) { 232 registry->SetDefaultVoice(uri, true); 233 } 234 } 235 236 registry->NotifyVoicesChanged(); 237 } 238 239 void nsFakeSynthServices::Init() { 240 mSynthService = new FakeSpeechSynth(); 241 AddVoices(mSynthService, sVoices, std::size(sVoices)); 242 } 243 244 // nsIObserver 245 246 NS_IMETHODIMP 247 nsFakeSynthServices::Observe(nsISupports* aSubject, const char* aTopic, 248 const char16_t* aData) { 249 MOZ_ASSERT(NS_IsMainThread()); 250 if (NS_WARN_IF(!(!strcmp(aTopic, "speech-synth-started")))) { 251 return NS_ERROR_UNEXPECTED; 252 } 253 254 if (Preferences::GetBool("media.webspeech.synth.test")) { 255 NS_DispatchToMainThread(NewRunnableMethod( 256 "dom::nsFakeSynthServices::Init", this, &nsFakeSynthServices::Init)); 257 } 258 259 return NS_OK; 260 } 261 262 // static methods 263 264 nsFakeSynthServices* nsFakeSynthServices::GetInstance() { 265 MOZ_ASSERT(NS_IsMainThread()); 266 if (!XRE_IsParentProcess()) { 267 MOZ_ASSERT(false, 268 "nsFakeSynthServices can only be started on main gecko process"); 269 return nullptr; 270 } 271 272 if (!sSingleton) { 273 sSingleton = new nsFakeSynthServices(); 274 ClearOnShutdown(&sSingleton); 275 } 276 277 return sSingleton; 278 } 279 280 already_AddRefed<nsFakeSynthServices> 281 nsFakeSynthServices::GetInstanceForService() { 282 RefPtr<nsFakeSynthServices> picoService = GetInstance(); 283 return picoService.forget(); 284 } 285 286 } // namespace mozilla::dom