Line data Source code
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 "nsISupports.h"
8 : #include "nsFakeSynthServices.h"
9 : #include "nsPrintfCString.h"
10 : #include "nsIWeakReferenceUtils.h"
11 : #include "SharedBuffer.h"
12 : #include "nsISimpleEnumerator.h"
13 :
14 : #include "mozilla/dom/nsSynthVoiceRegistry.h"
15 : #include "mozilla/dom/nsSpeechTask.h"
16 :
17 : #include "nsThreadUtils.h"
18 : #include "prenv.h"
19 : #include "mozilla/Preferences.h"
20 : #include "mozilla/DebugOnly.h"
21 :
22 : #define CHANNELS 1
23 : #define SAMPLERATE 1600
24 :
25 : namespace mozilla {
26 : namespace dom {
27 :
28 3 : StaticRefPtr<nsFakeSynthServices> nsFakeSynthServices::sSingleton;
29 :
30 : enum VoiceFlags
31 : {
32 : eSuppressEvents = 1,
33 : eSuppressEnd = 2,
34 : eFailAtStart = 4,
35 : eFail = 8
36 : };
37 :
38 : struct VoiceDetails
39 : {
40 : const char* uri;
41 : const char* name;
42 : const char* lang;
43 : bool defaultVoice;
44 : uint32_t flags;
45 : };
46 :
47 : static const VoiceDetails sDirectVoices[] = {
48 : {"urn:moz-tts:fake-direct:bob", "Bob Marley", "en-JM", true, 0},
49 : {"urn:moz-tts:fake-direct:amy", "Amy Winehouse", "en-GB", false, 0},
50 : {"urn:moz-tts:fake-direct:lenny", "Leonard Cohen", "en-CA", false, 0},
51 : {"urn:moz-tts:fake-direct:celine", "Celine Dion", "fr-CA", false, 0},
52 : {"urn:moz-tts:fake-direct:julie", "Julieta Venegas", "es-MX", false, },
53 : };
54 :
55 : static const VoiceDetails sIndirectVoices[] = {
56 : {"urn:moz-tts:fake-indirect:zanetta", "Zanetta Farussi", "it-IT", false, 0},
57 : {"urn:moz-tts:fake-indirect:margherita", "Margherita Durastanti", "it-IT-noevents-noend", false, eSuppressEvents | eSuppressEnd},
58 : {"urn:moz-tts:fake-indirect:teresa", "Teresa Cornelys", "it-IT-noend", false, eSuppressEnd},
59 : {"urn:moz-tts:fake-indirect:cecilia", "Cecilia Bartoli", "it-IT-failatstart", false, eFailAtStart},
60 : {"urn:moz-tts:fake-indirect:gottardo", "Gottardo Aldighieri", "it-IT-fail", false, eFail},
61 : };
62 :
63 : // FakeSynthCallback
64 : class FakeSynthCallback : public nsISpeechTaskCallback
65 : {
66 : public:
67 0 : explicit FakeSynthCallback(nsISpeechTask* aTask) : mTask(aTask) { }
68 : NS_DECL_CYCLE_COLLECTING_ISUPPORTS
69 0 : NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(FakeSynthCallback, nsISpeechTaskCallback)
70 :
71 0 : NS_IMETHOD OnPause() override
72 : {
73 0 : if (mTask) {
74 0 : mTask->DispatchPause(1.5, 1);
75 : }
76 :
77 0 : return NS_OK;
78 : }
79 :
80 0 : NS_IMETHOD OnResume() override
81 : {
82 0 : if (mTask) {
83 0 : mTask->DispatchResume(1.5, 1);
84 : }
85 :
86 0 : return NS_OK;
87 : }
88 :
89 0 : NS_IMETHOD OnCancel() override
90 : {
91 0 : if (mTask) {
92 0 : mTask->DispatchEnd(1.5, 1);
93 : }
94 :
95 0 : return NS_OK;
96 : }
97 :
98 0 : NS_IMETHOD OnVolumeChanged(float aVolume) override
99 : {
100 0 : return NS_OK;
101 : }
102 :
103 : private:
104 0 : virtual ~FakeSynthCallback() { }
105 :
106 : nsCOMPtr<nsISpeechTask> mTask;
107 : };
108 :
109 0 : NS_IMPL_CYCLE_COLLECTION(FakeSynthCallback, mTask);
110 :
111 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FakeSynthCallback)
112 0 : NS_INTERFACE_MAP_ENTRY(nsISpeechTaskCallback)
113 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTaskCallback)
114 0 : NS_INTERFACE_MAP_END
115 :
116 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(FakeSynthCallback)
117 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(FakeSynthCallback)
118 :
119 : // FakeDirectAudioSynth
120 :
121 : class FakeDirectAudioSynth : public nsISpeechService
122 : {
123 :
124 : public:
125 0 : FakeDirectAudioSynth() { }
126 :
127 : NS_DECL_ISUPPORTS
128 : NS_DECL_NSISPEECHSERVICE
129 :
130 : private:
131 0 : virtual ~FakeDirectAudioSynth() { }
132 : };
133 :
134 0 : NS_IMPL_ISUPPORTS(FakeDirectAudioSynth, nsISpeechService)
135 :
136 : NS_IMETHODIMP
137 0 : FakeDirectAudioSynth::Speak(const nsAString& aText, const nsAString& aUri,
138 : float aVolume, float aRate, float aPitch,
139 : nsISpeechTask* aTask)
140 : {
141 0 : class Runnable final : public mozilla::Runnable
142 : {
143 : public:
144 0 : Runnable(nsISpeechTask* aTask, const nsAString& aText)
145 0 : : mozilla::Runnable("Runnable")
146 : , mTask(aTask)
147 0 : , mText(aText)
148 : {
149 0 : }
150 :
151 0 : NS_IMETHOD Run() override
152 : {
153 0 : RefPtr<FakeSynthCallback> cb = new FakeSynthCallback(nullptr);
154 0 : mTask->Setup(cb, CHANNELS, SAMPLERATE, 2);
155 :
156 : // Just an arbitrary multiplier. Pretend that each character is
157 : // synthesized to 40 frames.
158 0 : uint32_t frames_length = 40 * mText.Length();
159 0 : auto frames = MakeUnique<int16_t[]>(frames_length);
160 0 : mTask->SendAudioNative(frames.get(), frames_length);
161 :
162 0 : mTask->SendAudioNative(nullptr, 0);
163 :
164 0 : return NS_OK;
165 : }
166 :
167 : private:
168 : nsCOMPtr<nsISpeechTask> mTask;
169 : nsString mText;
170 : };
171 :
172 0 : nsCOMPtr<nsIRunnable> runnable = new Runnable(aTask, aText);
173 0 : NS_DispatchToMainThread(runnable);
174 0 : return NS_OK;
175 : }
176 :
177 : NS_IMETHODIMP
178 0 : FakeDirectAudioSynth::GetServiceType(SpeechServiceType* aServiceType)
179 : {
180 0 : *aServiceType = nsISpeechService::SERVICETYPE_DIRECT_AUDIO;
181 0 : return NS_OK;
182 : }
183 :
184 : // FakeDirectAudioSynth
185 :
186 : class FakeIndirectAudioSynth : public nsISpeechService
187 : {
188 :
189 : public:
190 0 : FakeIndirectAudioSynth() {}
191 :
192 : NS_DECL_ISUPPORTS
193 : NS_DECL_NSISPEECHSERVICE
194 :
195 : private:
196 0 : virtual ~FakeIndirectAudioSynth() { }
197 : };
198 :
199 0 : NS_IMPL_ISUPPORTS(FakeIndirectAudioSynth, nsISpeechService)
200 :
201 : NS_IMETHODIMP
202 0 : FakeIndirectAudioSynth::Speak(const nsAString& aText, const nsAString& aUri,
203 : float aVolume, float aRate, float aPitch,
204 : nsISpeechTask* aTask)
205 : {
206 0 : class DispatchStart final : public Runnable
207 : {
208 : public:
209 0 : explicit DispatchStart(nsISpeechTask* aTask)
210 0 : : mozilla::Runnable("DispatchStart")
211 0 : , mTask(aTask)
212 : {
213 0 : }
214 :
215 0 : NS_IMETHOD Run() override
216 : {
217 0 : mTask->DispatchStart();
218 :
219 0 : return NS_OK;
220 : }
221 :
222 : private:
223 : nsCOMPtr<nsISpeechTask> mTask;
224 : };
225 :
226 0 : class DispatchEnd final : public Runnable
227 : {
228 : public:
229 0 : DispatchEnd(nsISpeechTask* aTask, const nsAString& aText)
230 0 : : mozilla::Runnable("DispatchEnd")
231 : , mTask(aTask)
232 0 : , mText(aText)
233 : {
234 0 : }
235 :
236 0 : NS_IMETHOD Run() override
237 : {
238 0 : mTask->DispatchEnd(mText.Length()/2, mText.Length());
239 :
240 0 : return NS_OK;
241 : }
242 :
243 : private:
244 : nsCOMPtr<nsISpeechTask> mTask;
245 : nsString mText;
246 : };
247 :
248 0 : class DispatchError final : public Runnable
249 : {
250 : public:
251 0 : DispatchError(nsISpeechTask* aTask, const nsAString& aText)
252 0 : : mozilla::Runnable("DispatchError")
253 : , mTask(aTask)
254 0 : , mText(aText)
255 : {
256 0 : }
257 :
258 0 : NS_IMETHOD Run() override
259 : {
260 0 : mTask->DispatchError(mText.Length()/2, mText.Length());
261 :
262 0 : return NS_OK;
263 : }
264 :
265 : private:
266 : nsCOMPtr<nsISpeechTask> mTask;
267 : nsString mText;
268 : };
269 :
270 0 : uint32_t flags = 0;
271 0 : for (uint32_t i = 0; i < ArrayLength(sIndirectVoices); i++) {
272 0 : if (aUri.EqualsASCII(sIndirectVoices[i].uri)) {
273 0 : flags = sIndirectVoices[i].flags;
274 : }
275 : }
276 :
277 0 : if (flags & eFailAtStart) {
278 0 : return NS_ERROR_FAILURE;
279 : }
280 :
281 : RefPtr<FakeSynthCallback> cb = new FakeSynthCallback(
282 0 : (flags & eSuppressEvents) ? nullptr : aTask);
283 :
284 0 : aTask->Setup(cb, 0, 0, 0);
285 :
286 0 : nsCOMPtr<nsIRunnable> runnable = new DispatchStart(aTask);
287 0 : NS_DispatchToMainThread(runnable);
288 :
289 0 : if (flags & eFail) {
290 0 : runnable = new DispatchError(aTask, aText);
291 0 : NS_DispatchToMainThread(runnable);
292 0 : } else if ((flags & eSuppressEnd) == 0) {
293 0 : runnable = new DispatchEnd(aTask, aText);
294 0 : NS_DispatchToMainThread(runnable);
295 : }
296 :
297 0 : return NS_OK;
298 : }
299 :
300 : NS_IMETHODIMP
301 0 : FakeIndirectAudioSynth::GetServiceType(SpeechServiceType* aServiceType)
302 : {
303 0 : *aServiceType = nsISpeechService::SERVICETYPE_INDIRECT_AUDIO;
304 0 : return NS_OK;
305 : }
306 :
307 : // nsFakeSynthService
308 :
309 0 : NS_INTERFACE_MAP_BEGIN(nsFakeSynthServices)
310 0 : NS_INTERFACE_MAP_ENTRY(nsIObserver)
311 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
312 0 : NS_INTERFACE_MAP_END
313 :
314 0 : NS_IMPL_ADDREF(nsFakeSynthServices)
315 0 : NS_IMPL_RELEASE(nsFakeSynthServices)
316 :
317 0 : nsFakeSynthServices::nsFakeSynthServices()
318 : {
319 0 : }
320 :
321 0 : nsFakeSynthServices::~nsFakeSynthServices()
322 : {
323 0 : }
324 :
325 : static void
326 0 : AddVoices(nsISpeechService* aService, const VoiceDetails* aVoices, uint32_t aLength)
327 : {
328 0 : RefPtr<nsSynthVoiceRegistry> registry = nsSynthVoiceRegistry::GetInstance();
329 0 : for (uint32_t i = 0; i < aLength; i++) {
330 0 : NS_ConvertUTF8toUTF16 name(aVoices[i].name);
331 0 : NS_ConvertUTF8toUTF16 uri(aVoices[i].uri);
332 0 : NS_ConvertUTF8toUTF16 lang(aVoices[i].lang);
333 : // These services can handle more than one utterance at a time and have
334 : // several speaking simultaniously. So, aQueuesUtterances == false
335 0 : registry->AddVoice(aService, uri, name, lang, true, false);
336 0 : if (aVoices[i].defaultVoice) {
337 0 : registry->SetDefaultVoice(uri, true);
338 : }
339 : }
340 :
341 0 : registry->NotifyVoicesChanged();
342 0 : }
343 :
344 : void
345 0 : nsFakeSynthServices::Init()
346 : {
347 0 : mDirectService = new FakeDirectAudioSynth();
348 0 : AddVoices(mDirectService, sDirectVoices, ArrayLength(sDirectVoices));
349 :
350 0 : mIndirectService = new FakeIndirectAudioSynth();
351 0 : AddVoices(mIndirectService, sIndirectVoices, ArrayLength(sIndirectVoices));
352 0 : }
353 :
354 : // nsIObserver
355 :
356 : NS_IMETHODIMP
357 0 : nsFakeSynthServices::Observe(nsISupports* aSubject, const char* aTopic,
358 : const char16_t* aData)
359 : {
360 0 : MOZ_ASSERT(NS_IsMainThread());
361 0 : if(NS_WARN_IF(!(!strcmp(aTopic, "speech-synth-started")))) {
362 0 : return NS_ERROR_UNEXPECTED;
363 : }
364 :
365 0 : if (Preferences::GetBool("media.webspeech.synth.test")) {
366 0 : NS_DispatchToMainThread(NewRunnableMethod(
367 0 : "dom::nsFakeSynthServices::Init", this, &nsFakeSynthServices::Init));
368 : }
369 :
370 0 : return NS_OK;
371 : }
372 :
373 : // static methods
374 :
375 : nsFakeSynthServices*
376 0 : nsFakeSynthServices::GetInstance()
377 : {
378 0 : MOZ_ASSERT(NS_IsMainThread());
379 0 : if (!XRE_IsParentProcess()) {
380 0 : MOZ_ASSERT(false, "nsFakeSynthServices can only be started on main gecko process");
381 : return nullptr;
382 : }
383 :
384 0 : if (!sSingleton) {
385 0 : sSingleton = new nsFakeSynthServices();
386 : }
387 :
388 0 : return sSingleton;
389 : }
390 :
391 : already_AddRefed<nsFakeSynthServices>
392 0 : nsFakeSynthServices::GetInstanceForService()
393 : {
394 0 : RefPtr<nsFakeSynthServices> picoService = GetInstance();
395 0 : return picoService.forget();
396 : }
397 :
398 : void
399 0 : nsFakeSynthServices::Shutdown()
400 : {
401 0 : if (!sSingleton) {
402 0 : return;
403 : }
404 :
405 0 : sSingleton = nullptr;
406 : }
407 :
408 : } // namespace dom
409 9 : } // namespace mozilla
|