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 "nsILocaleService.h"
8 : #include "nsISpeechService.h"
9 : #include "nsServiceManagerUtils.h"
10 : #include "nsCategoryManagerUtils.h"
11 :
12 : #include "MediaPrefs.h"
13 : #include "SpeechSynthesisUtterance.h"
14 : #include "SpeechSynthesisVoice.h"
15 : #include "nsSynthVoiceRegistry.h"
16 : #include "nsSpeechTask.h"
17 : #include "AudioChannelService.h"
18 :
19 : #include "nsString.h"
20 : #include "mozilla/StaticPtr.h"
21 : #include "mozilla/dom/ContentChild.h"
22 : #include "mozilla/dom/ContentParent.h"
23 : #include "mozilla/Unused.h"
24 :
25 : #include "SpeechSynthesisChild.h"
26 : #include "SpeechSynthesisParent.h"
27 :
28 : #undef LOG
29 : extern mozilla::LogModule* GetSpeechSynthLog();
30 : #define LOG(type, msg) MOZ_LOG(GetSpeechSynthLog(), type, msg)
31 :
32 : namespace {
33 :
34 : void
35 0 : GetAllSpeechSynthActors(InfallibleTArray<mozilla::dom::SpeechSynthesisParent*>& aActors)
36 : {
37 0 : MOZ_ASSERT(NS_IsMainThread());
38 0 : MOZ_ASSERT(aActors.IsEmpty());
39 :
40 0 : AutoTArray<mozilla::dom::ContentParent*, 20> contentActors;
41 0 : mozilla::dom::ContentParent::GetAll(contentActors);
42 :
43 0 : for (uint32_t contentIndex = 0;
44 0 : contentIndex < contentActors.Length();
45 : ++contentIndex) {
46 0 : MOZ_ASSERT(contentActors[contentIndex]);
47 :
48 0 : AutoTArray<mozilla::dom::PSpeechSynthesisParent*, 5> speechsynthActors;
49 0 : contentActors[contentIndex]->ManagedPSpeechSynthesisParent(speechsynthActors);
50 :
51 0 : for (uint32_t speechsynthIndex = 0;
52 0 : speechsynthIndex < speechsynthActors.Length();
53 : ++speechsynthIndex) {
54 0 : MOZ_ASSERT(speechsynthActors[speechsynthIndex]);
55 :
56 : mozilla::dom::SpeechSynthesisParent* actor =
57 0 : static_cast<mozilla::dom::SpeechSynthesisParent*>(speechsynthActors[speechsynthIndex]);
58 0 : aActors.AppendElement(actor);
59 : }
60 : }
61 0 : }
62 :
63 : } // namespace
64 :
65 : namespace mozilla {
66 : namespace dom {
67 :
68 : // VoiceData
69 :
70 : class VoiceData final
71 : {
72 : private:
73 : // Private destructor, to discourage deletion outside of Release():
74 0 : ~VoiceData() {}
75 :
76 : public:
77 0 : VoiceData(nsISpeechService* aService, const nsAString& aUri,
78 : const nsAString& aName, const nsAString& aLang,
79 : bool aIsLocal, bool aQueuesUtterances)
80 0 : : mService(aService)
81 : , mUri(aUri)
82 : , mName(aName)
83 : , mLang(aLang)
84 : , mIsLocal(aIsLocal)
85 0 : , mIsQueued(aQueuesUtterances) {}
86 :
87 0 : NS_INLINE_DECL_REFCOUNTING(VoiceData)
88 :
89 : nsCOMPtr<nsISpeechService> mService;
90 :
91 : nsString mUri;
92 :
93 : nsString mName;
94 :
95 : nsString mLang;
96 :
97 : bool mIsLocal;
98 :
99 : bool mIsQueued;
100 : };
101 :
102 : // GlobalQueueItem
103 :
104 : class GlobalQueueItem final
105 : {
106 : private:
107 : // Private destructor, to discourage deletion outside of Release():
108 0 : ~GlobalQueueItem() {}
109 :
110 : public:
111 0 : GlobalQueueItem(VoiceData* aVoice, nsSpeechTask* aTask, const nsAString& aText,
112 : const float& aVolume, const float& aRate, const float& aPitch)
113 0 : : mVoice(aVoice)
114 : , mTask(aTask)
115 : , mText(aText)
116 0 : , mVolume(aVolume)
117 0 : , mRate(aRate)
118 0 : , mPitch(aPitch) {}
119 :
120 0 : NS_INLINE_DECL_REFCOUNTING(GlobalQueueItem)
121 :
122 : RefPtr<VoiceData> mVoice;
123 :
124 : RefPtr<nsSpeechTask> mTask;
125 :
126 : nsString mText;
127 :
128 : float mVolume;
129 :
130 : float mRate;
131 :
132 : float mPitch;
133 :
134 : bool mIsLocal;
135 : };
136 :
137 : // nsSynthVoiceRegistry
138 :
139 3 : static StaticRefPtr<nsSynthVoiceRegistry> gSynthVoiceRegistry;
140 :
141 0 : NS_IMPL_ISUPPORTS(nsSynthVoiceRegistry, nsISynthVoiceRegistry)
142 :
143 0 : nsSynthVoiceRegistry::nsSynthVoiceRegistry()
144 : : mSpeechSynthChild(nullptr)
145 : , mUseGlobalQueue(false)
146 0 : , mIsSpeaking(false)
147 : {
148 0 : if (XRE_IsContentProcess()) {
149 :
150 0 : mSpeechSynthChild = new SpeechSynthesisChild();
151 0 : ContentChild::GetSingleton()->SendPSpeechSynthesisConstructor(mSpeechSynthChild);
152 : }
153 0 : }
154 :
155 0 : nsSynthVoiceRegistry::~nsSynthVoiceRegistry()
156 : {
157 0 : LOG(LogLevel::Debug, ("~nsSynthVoiceRegistry"));
158 :
159 : // mSpeechSynthChild's lifecycle is managed by the Content protocol.
160 0 : mSpeechSynthChild = nullptr;
161 :
162 0 : mUriVoiceMap.Clear();
163 0 : }
164 :
165 : nsSynthVoiceRegistry*
166 0 : nsSynthVoiceRegistry::GetInstance()
167 : {
168 0 : MOZ_ASSERT(NS_IsMainThread());
169 :
170 0 : if (!gSynthVoiceRegistry) {
171 0 : gSynthVoiceRegistry = new nsSynthVoiceRegistry();
172 0 : if (XRE_IsParentProcess()) {
173 : // Start up all speech synth services.
174 : NS_CreateServicesFromCategory(NS_SPEECH_SYNTH_STARTED, nullptr,
175 0 : NS_SPEECH_SYNTH_STARTED);
176 : }
177 : }
178 :
179 0 : return gSynthVoiceRegistry;
180 : }
181 :
182 : already_AddRefed<nsSynthVoiceRegistry>
183 0 : nsSynthVoiceRegistry::GetInstanceForService()
184 : {
185 0 : RefPtr<nsSynthVoiceRegistry> registry = GetInstance();
186 :
187 0 : return registry.forget();
188 : }
189 :
190 : void
191 0 : nsSynthVoiceRegistry::Shutdown()
192 : {
193 0 : LOG(LogLevel::Debug, ("[%s] nsSynthVoiceRegistry::Shutdown()",
194 : (XRE_IsContentProcess()) ? "Content" : "Default"));
195 0 : gSynthVoiceRegistry = nullptr;
196 0 : }
197 :
198 : bool
199 0 : nsSynthVoiceRegistry::SendInitialVoicesAndState(SpeechSynthesisParent* aParent)
200 : {
201 0 : MOZ_ASSERT(XRE_IsParentProcess());
202 :
203 0 : InfallibleTArray<RemoteVoice> voices;
204 0 : InfallibleTArray<nsString> defaults;
205 :
206 0 : for (uint32_t i=0; i < mVoices.Length(); ++i) {
207 0 : RefPtr<VoiceData> voice = mVoices[i];
208 :
209 0 : voices.AppendElement(RemoteVoice(voice->mUri, voice->mName, voice->mLang,
210 0 : voice->mIsLocal, voice->mIsQueued));
211 : }
212 :
213 0 : for (uint32_t i=0; i < mDefaultVoices.Length(); ++i) {
214 0 : defaults.AppendElement(mDefaultVoices[i]->mUri);
215 : }
216 :
217 0 : return aParent->SendInitialVoicesAndState(voices, defaults, IsSpeaking());
218 : }
219 :
220 : void
221 0 : nsSynthVoiceRegistry::RecvInitialVoicesAndState(const nsTArray<RemoteVoice>& aVoices,
222 : const nsTArray<nsString>& aDefaults,
223 : const bool& aIsSpeaking)
224 : {
225 : // We really should have a local instance since this is a directed response to
226 : // an Init() call.
227 0 : MOZ_ASSERT(gSynthVoiceRegistry);
228 :
229 0 : for (uint32_t i = 0; i < aVoices.Length(); ++i) {
230 0 : RemoteVoice voice = aVoices[i];
231 0 : gSynthVoiceRegistry->AddVoiceImpl(nullptr, voice.voiceURI(),
232 0 : voice.name(), voice.lang(),
233 0 : voice.localService(), voice.queued());
234 : }
235 :
236 0 : for (uint32_t i = 0; i < aDefaults.Length(); ++i) {
237 0 : gSynthVoiceRegistry->SetDefaultVoice(aDefaults[i], true);
238 : }
239 :
240 0 : gSynthVoiceRegistry->mIsSpeaking = aIsSpeaking;
241 :
242 0 : if (aVoices.Length()) {
243 0 : gSynthVoiceRegistry->NotifyVoicesChanged();
244 : }
245 0 : }
246 :
247 : void
248 0 : nsSynthVoiceRegistry::RecvRemoveVoice(const nsAString& aUri)
249 : {
250 : // If we dont have a local instance of the registry yet, we will recieve current
251 : // voices at contruction time.
252 0 : if(!gSynthVoiceRegistry) {
253 0 : return;
254 : }
255 :
256 0 : gSynthVoiceRegistry->RemoveVoice(nullptr, aUri);
257 : }
258 :
259 : void
260 0 : nsSynthVoiceRegistry::RecvAddVoice(const RemoteVoice& aVoice)
261 : {
262 : // If we dont have a local instance of the registry yet, we will recieve current
263 : // voices at contruction time.
264 0 : if(!gSynthVoiceRegistry) {
265 0 : return;
266 : }
267 :
268 0 : gSynthVoiceRegistry->AddVoiceImpl(nullptr, aVoice.voiceURI(),
269 0 : aVoice.name(), aVoice.lang(),
270 0 : aVoice.localService(), aVoice.queued());
271 : }
272 :
273 : void
274 0 : nsSynthVoiceRegistry::RecvSetDefaultVoice(const nsAString& aUri, bool aIsDefault)
275 : {
276 : // If we dont have a local instance of the registry yet, we will recieve current
277 : // voices at contruction time.
278 0 : if(!gSynthVoiceRegistry) {
279 0 : return;
280 : }
281 :
282 0 : gSynthVoiceRegistry->SetDefaultVoice(aUri, aIsDefault);
283 : }
284 :
285 : void
286 0 : nsSynthVoiceRegistry::RecvIsSpeakingChanged(bool aIsSpeaking)
287 : {
288 : // If we dont have a local instance of the registry yet, we will get the
289 : // speaking state on construction.
290 0 : if(!gSynthVoiceRegistry) {
291 0 : return;
292 : }
293 :
294 0 : gSynthVoiceRegistry->mIsSpeaking = aIsSpeaking;
295 : }
296 :
297 : void
298 0 : nsSynthVoiceRegistry::RecvNotifyVoicesChanged()
299 : {
300 : // If we dont have a local instance of the registry yet, we don't care.
301 0 : if(!gSynthVoiceRegistry) {
302 0 : return;
303 : }
304 :
305 0 : gSynthVoiceRegistry->NotifyVoicesChanged();
306 : }
307 :
308 : NS_IMETHODIMP
309 0 : nsSynthVoiceRegistry::AddVoice(nsISpeechService* aService,
310 : const nsAString& aUri,
311 : const nsAString& aName,
312 : const nsAString& aLang,
313 : bool aLocalService,
314 : bool aQueuesUtterances)
315 : {
316 0 : LOG(LogLevel::Debug,
317 : ("nsSynthVoiceRegistry::AddVoice uri='%s' name='%s' lang='%s' local=%s queued=%s",
318 : NS_ConvertUTF16toUTF8(aUri).get(), NS_ConvertUTF16toUTF8(aName).get(),
319 : NS_ConvertUTF16toUTF8(aLang).get(),
320 : aLocalService ? "true" : "false",
321 : aQueuesUtterances ? "true" : "false"));
322 :
323 0 : if(NS_WARN_IF(XRE_IsContentProcess())) {
324 0 : return NS_ERROR_NOT_AVAILABLE;
325 : }
326 :
327 0 : return AddVoiceImpl(aService, aUri, aName, aLang, aLocalService, aQueuesUtterances);
328 : }
329 :
330 : NS_IMETHODIMP
331 0 : nsSynthVoiceRegistry::RemoveVoice(nsISpeechService* aService,
332 : const nsAString& aUri)
333 : {
334 0 : LOG(LogLevel::Debug,
335 : ("nsSynthVoiceRegistry::RemoveVoice uri='%s' (%s)",
336 : NS_ConvertUTF16toUTF8(aUri).get(),
337 : (XRE_IsContentProcess()) ? "child" : "parent"));
338 :
339 0 : bool found = false;
340 0 : VoiceData* retval = mUriVoiceMap.GetWeak(aUri, &found);
341 :
342 0 : if(NS_WARN_IF(!(found))) {
343 0 : return NS_ERROR_NOT_AVAILABLE;
344 : }
345 0 : if(NS_WARN_IF(!(aService == retval->mService))) {
346 0 : return NS_ERROR_INVALID_ARG;
347 : }
348 :
349 0 : mVoices.RemoveElement(retval);
350 0 : mDefaultVoices.RemoveElement(retval);
351 0 : mUriVoiceMap.Remove(aUri);
352 :
353 0 : if (retval->mIsQueued && !MediaPrefs::WebSpeechForceGlobal()) {
354 : // Check if this is the last queued voice, and disable the global queue if
355 : // it is.
356 0 : bool queued = false;
357 0 : for (uint32_t i = 0; i < mVoices.Length(); i++) {
358 0 : VoiceData* voice = mVoices[i];
359 0 : if (voice->mIsQueued) {
360 0 : queued = true;
361 0 : break;
362 : }
363 : }
364 0 : if (!queued) {
365 0 : mUseGlobalQueue = false;
366 : }
367 : }
368 :
369 0 : nsTArray<SpeechSynthesisParent*> ssplist;
370 0 : GetAllSpeechSynthActors(ssplist);
371 :
372 0 : for (uint32_t i = 0; i < ssplist.Length(); ++i)
373 0 : Unused << ssplist[i]->SendVoiceRemoved(nsString(aUri));
374 :
375 0 : return NS_OK;
376 : }
377 :
378 : NS_IMETHODIMP
379 0 : nsSynthVoiceRegistry::NotifyVoicesChanged()
380 : {
381 0 : if (XRE_IsParentProcess()) {
382 0 : nsTArray<SpeechSynthesisParent*> ssplist;
383 0 : GetAllSpeechSynthActors(ssplist);
384 :
385 0 : for (uint32_t i = 0; i < ssplist.Length(); ++i)
386 0 : Unused << ssplist[i]->SendNotifyVoicesChanged();
387 : }
388 :
389 0 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
390 0 : if(NS_WARN_IF(!(obs))) {
391 0 : return NS_ERROR_NOT_AVAILABLE;
392 : }
393 :
394 0 : obs->NotifyObservers(nullptr, "synth-voices-changed", nullptr);
395 :
396 0 : return NS_OK;
397 : }
398 :
399 : NS_IMETHODIMP
400 0 : nsSynthVoiceRegistry::SetDefaultVoice(const nsAString& aUri,
401 : bool aIsDefault)
402 : {
403 0 : bool found = false;
404 0 : VoiceData* retval = mUriVoiceMap.GetWeak(aUri, &found);
405 0 : if(NS_WARN_IF(!(found))) {
406 0 : return NS_ERROR_NOT_AVAILABLE;
407 : }
408 :
409 0 : mDefaultVoices.RemoveElement(retval);
410 :
411 0 : LOG(LogLevel::Debug, ("nsSynthVoiceRegistry::SetDefaultVoice %s %s",
412 : NS_ConvertUTF16toUTF8(aUri).get(),
413 : aIsDefault ? "true" : "false"));
414 :
415 0 : if (aIsDefault) {
416 0 : mDefaultVoices.AppendElement(retval);
417 : }
418 :
419 0 : if (XRE_IsParentProcess()) {
420 0 : nsTArray<SpeechSynthesisParent*> ssplist;
421 0 : GetAllSpeechSynthActors(ssplist);
422 :
423 0 : for (uint32_t i = 0; i < ssplist.Length(); ++i) {
424 0 : Unused << ssplist[i]->SendSetDefaultVoice(nsString(aUri), aIsDefault);
425 : }
426 : }
427 :
428 0 : return NS_OK;
429 : }
430 :
431 : NS_IMETHODIMP
432 0 : nsSynthVoiceRegistry::GetVoiceCount(uint32_t* aRetval)
433 : {
434 0 : *aRetval = mVoices.Length();
435 :
436 0 : return NS_OK;
437 : }
438 :
439 : NS_IMETHODIMP
440 0 : nsSynthVoiceRegistry::GetVoice(uint32_t aIndex, nsAString& aRetval)
441 : {
442 0 : if(NS_WARN_IF(!(aIndex < mVoices.Length()))) {
443 0 : return NS_ERROR_INVALID_ARG;
444 : }
445 :
446 0 : aRetval = mVoices[aIndex]->mUri;
447 :
448 0 : return NS_OK;
449 : }
450 :
451 : NS_IMETHODIMP
452 0 : nsSynthVoiceRegistry::IsDefaultVoice(const nsAString& aUri, bool* aRetval)
453 : {
454 : bool found;
455 0 : VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found);
456 0 : if(NS_WARN_IF(!(found))) {
457 0 : return NS_ERROR_NOT_AVAILABLE;
458 : }
459 :
460 0 : for (int32_t i = mDefaultVoices.Length(); i > 0; ) {
461 0 : VoiceData* defaultVoice = mDefaultVoices[--i];
462 :
463 0 : if (voice->mLang.Equals(defaultVoice->mLang)) {
464 0 : *aRetval = voice == defaultVoice;
465 0 : return NS_OK;
466 : }
467 : }
468 :
469 0 : *aRetval = false;
470 0 : return NS_OK;
471 : }
472 :
473 : NS_IMETHODIMP
474 0 : nsSynthVoiceRegistry::IsLocalVoice(const nsAString& aUri, bool* aRetval)
475 : {
476 : bool found;
477 0 : VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found);
478 0 : if(NS_WARN_IF(!(found))) {
479 0 : return NS_ERROR_NOT_AVAILABLE;
480 : }
481 :
482 0 : *aRetval = voice->mIsLocal;
483 0 : return NS_OK;
484 : }
485 :
486 : NS_IMETHODIMP
487 0 : nsSynthVoiceRegistry::GetVoiceLang(const nsAString& aUri, nsAString& aRetval)
488 : {
489 : bool found;
490 0 : VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found);
491 0 : if(NS_WARN_IF(!(found))) {
492 0 : return NS_ERROR_NOT_AVAILABLE;
493 : }
494 :
495 0 : aRetval = voice->mLang;
496 0 : return NS_OK;
497 : }
498 :
499 : NS_IMETHODIMP
500 0 : nsSynthVoiceRegistry::GetVoiceName(const nsAString& aUri, nsAString& aRetval)
501 : {
502 : bool found;
503 0 : VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found);
504 0 : if(NS_WARN_IF(!(found))) {
505 0 : return NS_ERROR_NOT_AVAILABLE;
506 : }
507 :
508 0 : aRetval = voice->mName;
509 0 : return NS_OK;
510 : }
511 :
512 : nsresult
513 0 : nsSynthVoiceRegistry::AddVoiceImpl(nsISpeechService* aService,
514 : const nsAString& aUri,
515 : const nsAString& aName,
516 : const nsAString& aLang,
517 : bool aLocalService,
518 : bool aQueuesUtterances)
519 : {
520 0 : bool found = false;
521 0 : mUriVoiceMap.GetWeak(aUri, &found);
522 0 : if(NS_WARN_IF(found)) {
523 0 : return NS_ERROR_INVALID_ARG;
524 : }
525 :
526 : RefPtr<VoiceData> voice = new VoiceData(aService, aUri, aName, aLang,
527 0 : aLocalService, aQueuesUtterances);
528 :
529 0 : mVoices.AppendElement(voice);
530 0 : mUriVoiceMap.Put(aUri, voice);
531 0 : mUseGlobalQueue |= aQueuesUtterances;
532 :
533 0 : nsTArray<SpeechSynthesisParent*> ssplist;
534 0 : GetAllSpeechSynthActors(ssplist);
535 :
536 0 : if (!ssplist.IsEmpty()) {
537 0 : mozilla::dom::RemoteVoice ssvoice(nsString(aUri),
538 0 : nsString(aName),
539 0 : nsString(aLang),
540 : aLocalService,
541 0 : aQueuesUtterances);
542 :
543 0 : for (uint32_t i = 0; i < ssplist.Length(); ++i) {
544 0 : Unused << ssplist[i]->SendVoiceAdded(ssvoice);
545 : }
546 : }
547 :
548 0 : return NS_OK;
549 : }
550 :
551 : bool
552 0 : nsSynthVoiceRegistry::FindVoiceByLang(const nsAString& aLang,
553 : VoiceData** aRetval)
554 : {
555 0 : nsAString::const_iterator dashPos, start, end;
556 0 : aLang.BeginReading(start);
557 0 : aLang.EndReading(end);
558 :
559 : while (true) {
560 0 : nsAutoString langPrefix(Substring(start, end));
561 :
562 0 : for (int32_t i = mDefaultVoices.Length(); i > 0; ) {
563 0 : VoiceData* voice = mDefaultVoices[--i];
564 :
565 0 : if (StringBeginsWith(voice->mLang, langPrefix)) {
566 0 : *aRetval = voice;
567 0 : return true;
568 : }
569 : }
570 :
571 0 : for (int32_t i = mVoices.Length(); i > 0; ) {
572 0 : VoiceData* voice = mVoices[--i];
573 :
574 0 : if (StringBeginsWith(voice->mLang, langPrefix)) {
575 0 : *aRetval = voice;
576 0 : return true;
577 : }
578 : }
579 :
580 0 : dashPos = end;
581 0 : end = start;
582 :
583 0 : if (!RFindInReadable(NS_LITERAL_STRING("-"), end, dashPos)) {
584 0 : break;
585 : }
586 0 : }
587 :
588 0 : return false;
589 : }
590 :
591 : VoiceData*
592 0 : nsSynthVoiceRegistry::FindBestMatch(const nsAString& aUri,
593 : const nsAString& aLang)
594 : {
595 0 : if (mVoices.IsEmpty()) {
596 0 : return nullptr;
597 : }
598 :
599 0 : bool found = false;
600 0 : VoiceData* retval = mUriVoiceMap.GetWeak(aUri, &found);
601 :
602 0 : if (found) {
603 0 : LOG(LogLevel::Debug, ("nsSynthVoiceRegistry::FindBestMatch - Matched URI"));
604 0 : return retval;
605 : }
606 :
607 : // Try finding a match for given voice.
608 0 : if (!aLang.IsVoid() && !aLang.IsEmpty()) {
609 0 : if (FindVoiceByLang(aLang, &retval)) {
610 0 : LOG(LogLevel::Debug,
611 : ("nsSynthVoiceRegistry::FindBestMatch - Matched language (%s ~= %s)",
612 : NS_ConvertUTF16toUTF8(aLang).get(),
613 : NS_ConvertUTF16toUTF8(retval->mLang).get()));
614 :
615 0 : return retval;
616 : }
617 : }
618 :
619 : // Try UI language.
620 : nsresult rv;
621 0 : nsCOMPtr<nsILocaleService> localeService = do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv);
622 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
623 0 : return nullptr;
624 : }
625 :
626 0 : nsAutoString uiLang;
627 0 : rv = localeService->GetLocaleComponentForUserAgent(uiLang);
628 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
629 0 : return nullptr;
630 : }
631 :
632 0 : if (FindVoiceByLang(uiLang, &retval)) {
633 0 : LOG(LogLevel::Debug,
634 : ("nsSynthVoiceRegistry::FindBestMatch - Matched UI language (%s ~= %s)",
635 : NS_ConvertUTF16toUTF8(uiLang).get(),
636 : NS_ConvertUTF16toUTF8(retval->mLang).get()));
637 :
638 0 : return retval;
639 : }
640 :
641 : // Try en-US, the language of locale "C"
642 0 : if (FindVoiceByLang(NS_LITERAL_STRING("en-US"), &retval)) {
643 0 : LOG(LogLevel::Debug,
644 : ("nsSynthVoiceRegistry::FindBestMatch - Matched C locale language (en-US ~= %s)",
645 : NS_ConvertUTF16toUTF8(retval->mLang).get()));
646 :
647 0 : return retval;
648 : }
649 :
650 : // The top default voice is better than nothing...
651 0 : if (!mDefaultVoices.IsEmpty()) {
652 0 : return mDefaultVoices.LastElement();
653 : }
654 :
655 0 : return nullptr;
656 : }
657 :
658 : already_AddRefed<nsSpeechTask>
659 0 : nsSynthVoiceRegistry::SpeakUtterance(SpeechSynthesisUtterance& aUtterance,
660 : const nsAString& aDocLang)
661 : {
662 0 : nsString lang = nsString(aUtterance.mLang.IsEmpty() ? aDocLang : aUtterance.mLang);
663 0 : nsAutoString uri;
664 :
665 0 : if (aUtterance.mVoice) {
666 0 : aUtterance.mVoice->GetVoiceURI(uri);
667 : }
668 :
669 : // Get current audio volume to apply speech call
670 0 : float volume = aUtterance.Volume();
671 0 : RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
672 0 : if (service) {
673 0 : if (nsCOMPtr<nsPIDOMWindowInner> topWindow = aUtterance.GetOwner()) {
674 : // TODO : use audio channel agent, open new bug to fix it.
675 0 : uint32_t channel = static_cast<uint32_t>(AudioChannelService::GetDefaultAudioChannel());
676 0 : AudioPlaybackConfig config = service->GetMediaConfig(topWindow->GetOuterWindow(),
677 0 : channel);
678 0 : volume = config.mMuted ? 0.0f : config.mVolume * volume;
679 : }
680 : }
681 :
682 0 : RefPtr<nsSpeechTask> task;
683 0 : if (XRE_IsContentProcess()) {
684 0 : task = new SpeechTaskChild(&aUtterance);
685 : SpeechSynthesisRequestChild* actor =
686 0 : new SpeechSynthesisRequestChild(static_cast<SpeechTaskChild*>(task.get()));
687 0 : mSpeechSynthChild->SendPSpeechSynthesisRequestConstructor(actor,
688 : aUtterance.mText,
689 : lang,
690 : uri,
691 : volume,
692 0 : aUtterance.Rate(),
693 0 : aUtterance.Pitch());
694 : } else {
695 0 : task = new nsSpeechTask(&aUtterance);
696 0 : Speak(aUtterance.mText, lang, uri,
697 0 : volume, aUtterance.Rate(), aUtterance.Pitch(), task);
698 : }
699 :
700 0 : return task.forget();
701 : }
702 :
703 : void
704 0 : nsSynthVoiceRegistry::Speak(const nsAString& aText,
705 : const nsAString& aLang,
706 : const nsAString& aUri,
707 : const float& aVolume,
708 : const float& aRate,
709 : const float& aPitch,
710 : nsSpeechTask* aTask)
711 : {
712 0 : MOZ_ASSERT(XRE_IsParentProcess());
713 :
714 0 : VoiceData* voice = FindBestMatch(aUri, aLang);
715 :
716 0 : if (!voice) {
717 0 : NS_WARNING("No voices found.");
718 0 : aTask->DispatchError(0, 0);
719 0 : return;
720 : }
721 :
722 0 : aTask->SetChosenVoiceURI(voice->mUri);
723 :
724 0 : if (mUseGlobalQueue || MediaPrefs::WebSpeechForceGlobal()) {
725 0 : LOG(LogLevel::Debug,
726 : ("nsSynthVoiceRegistry::Speak queueing text='%s' lang='%s' uri='%s' rate=%f pitch=%f",
727 : NS_ConvertUTF16toUTF8(aText).get(), NS_ConvertUTF16toUTF8(aLang).get(),
728 : NS_ConvertUTF16toUTF8(aUri).get(), aRate, aPitch));
729 : RefPtr<GlobalQueueItem> item = new GlobalQueueItem(voice, aTask, aText,
730 0 : aVolume, aRate, aPitch);
731 0 : mGlobalQueue.AppendElement(item);
732 :
733 0 : if (mGlobalQueue.Length() == 1) {
734 0 : SpeakImpl(item->mVoice, item->mTask, item->mText, item->mVolume, item->mRate,
735 0 : item->mPitch);
736 : }
737 : } else {
738 0 : SpeakImpl(voice, aTask, aText, aVolume, aRate, aPitch);
739 : }
740 : }
741 :
742 : void
743 0 : nsSynthVoiceRegistry::SpeakNext()
744 : {
745 0 : MOZ_ASSERT(XRE_IsParentProcess());
746 :
747 0 : LOG(LogLevel::Debug,
748 : ("nsSynthVoiceRegistry::SpeakNext %d", mGlobalQueue.IsEmpty()));
749 :
750 0 : SetIsSpeaking(false);
751 :
752 0 : if (mGlobalQueue.IsEmpty()) {
753 0 : return;
754 : }
755 :
756 0 : mGlobalQueue.RemoveElementAt(0);
757 :
758 0 : while (!mGlobalQueue.IsEmpty()) {
759 0 : RefPtr<GlobalQueueItem> item = mGlobalQueue.ElementAt(0);
760 0 : if (item->mTask->IsPreCanceled()) {
761 0 : mGlobalQueue.RemoveElementAt(0);
762 0 : continue;
763 : }
764 0 : if (!item->mTask->IsPrePaused()) {
765 0 : SpeakImpl(item->mVoice, item->mTask, item->mText, item->mVolume,
766 0 : item->mRate, item->mPitch);
767 : }
768 0 : break;
769 : }
770 : }
771 :
772 : void
773 0 : nsSynthVoiceRegistry::ResumeQueue()
774 : {
775 0 : MOZ_ASSERT(XRE_IsParentProcess());
776 0 : LOG(LogLevel::Debug,
777 : ("nsSynthVoiceRegistry::ResumeQueue %d", mGlobalQueue.IsEmpty()));
778 :
779 0 : if (mGlobalQueue.IsEmpty()) {
780 0 : return;
781 : }
782 :
783 0 : RefPtr<GlobalQueueItem> item = mGlobalQueue.ElementAt(0);
784 0 : if (!item->mTask->IsPrePaused()) {
785 0 : SpeakImpl(item->mVoice, item->mTask, item->mText, item->mVolume,
786 0 : item->mRate, item->mPitch);
787 : }
788 : }
789 :
790 : bool
791 0 : nsSynthVoiceRegistry::IsSpeaking()
792 : {
793 0 : return mIsSpeaking;
794 : }
795 :
796 : void
797 0 : nsSynthVoiceRegistry::SetIsSpeaking(bool aIsSpeaking)
798 : {
799 0 : MOZ_ASSERT(XRE_IsParentProcess());
800 :
801 : // Only set to 'true' if global queue is enabled.
802 0 : mIsSpeaking =
803 0 : aIsSpeaking && (mUseGlobalQueue || MediaPrefs::WebSpeechForceGlobal());
804 :
805 0 : nsTArray<SpeechSynthesisParent*> ssplist;
806 0 : GetAllSpeechSynthActors(ssplist);
807 0 : for (uint32_t i = 0; i < ssplist.Length(); ++i) {
808 0 : Unused << ssplist[i]->SendIsSpeakingChanged(aIsSpeaking);
809 : }
810 0 : }
811 :
812 : void
813 0 : nsSynthVoiceRegistry::SpeakImpl(VoiceData* aVoice,
814 : nsSpeechTask* aTask,
815 : const nsAString& aText,
816 : const float& aVolume,
817 : const float& aRate,
818 : const float& aPitch)
819 : {
820 0 : LOG(LogLevel::Debug,
821 : ("nsSynthVoiceRegistry::SpeakImpl queueing text='%s' uri='%s' rate=%f pitch=%f",
822 : NS_ConvertUTF16toUTF8(aText).get(), NS_ConvertUTF16toUTF8(aVoice->mUri).get(),
823 : aRate, aPitch));
824 :
825 : SpeechServiceType serviceType;
826 :
827 0 : DebugOnly<nsresult> rv = aVoice->mService->GetServiceType(&serviceType);
828 0 : NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to get speech service type");
829 :
830 0 : if (serviceType == nsISpeechService::SERVICETYPE_INDIRECT_AUDIO) {
831 0 : aTask->InitIndirectAudio();
832 : } else {
833 0 : aTask->InitDirectAudio();
834 : }
835 :
836 0 : if (NS_FAILED(aVoice->mService->Speak(aText, aVoice->mUri, aVolume, aRate,
837 : aPitch, aTask))) {
838 0 : if (serviceType == nsISpeechService::SERVICETYPE_INDIRECT_AUDIO) {
839 0 : aTask->DispatchError(0, 0);
840 : }
841 : // XXX When using direct audio, no way to dispatch error
842 : }
843 0 : }
844 :
845 : } // namespace dom
846 : } // namespace mozilla
|