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/. */
7 #include "nsISpeechService.h"
8 #include "nsServiceManagerUtils.h"
9 #include "nsCategoryManagerUtils.h"
11 #include "SpeechSynthesisUtterance.h"
12 #include "SpeechSynthesisVoice.h"
13 #include "nsContentUtils.h"
14 #include "nsSynthVoiceRegistry.h"
15 #include "nsSpeechTask.h"
16 #include "AudioChannelService.h"
19 #include "mozilla/ClearOnShutdown.h"
20 #include "mozilla/dom/ContentChild.h"
21 #include "mozilla/dom/ContentParent.h"
22 #include "mozilla/dom/Document.h"
23 #include "mozilla/intl/LocaleService.h"
24 #include "mozilla/StaticPrefs_media.h"
25 #include "mozilla/StaticPtr.h"
26 #include "mozilla/Unused.h"
28 #include "SpeechSynthesisChild.h"
29 #include "SpeechSynthesisParent.h"
31 using mozilla::intl::LocaleService
;
34 extern mozilla::LogModule
* GetSpeechSynthLog();
35 #define LOG(type, msg) MOZ_LOG(GetSpeechSynthLog(), type, msg)
39 void GetAllSpeechSynthActors(
40 nsTArray
<mozilla::dom::SpeechSynthesisParent
*>& aActors
) {
41 MOZ_ASSERT(NS_IsMainThread());
42 MOZ_ASSERT(aActors
.IsEmpty());
44 AutoTArray
<mozilla::dom::ContentParent
*, 20> contentActors
;
45 mozilla::dom::ContentParent::GetAll(contentActors
);
47 for (uint32_t contentIndex
= 0; contentIndex
< contentActors
.Length();
49 MOZ_ASSERT(contentActors
[contentIndex
]);
51 AutoTArray
<mozilla::dom::PSpeechSynthesisParent
*, 5> speechsynthActors
;
52 contentActors
[contentIndex
]->ManagedPSpeechSynthesisParent(
55 for (uint32_t speechsynthIndex
= 0;
56 speechsynthIndex
< speechsynthActors
.Length(); ++speechsynthIndex
) {
57 MOZ_ASSERT(speechsynthActors
[speechsynthIndex
]);
59 mozilla::dom::SpeechSynthesisParent
* actor
=
60 static_cast<mozilla::dom::SpeechSynthesisParent
*>(
61 speechsynthActors
[speechsynthIndex
]);
62 aActors
.AppendElement(actor
);
69 namespace mozilla::dom
{
73 class VoiceData final
{
75 // Private destructor, to discourage deletion outside of Release():
76 ~VoiceData() = default;
79 VoiceData(nsISpeechService
* aService
, const nsAString
& aUri
,
80 const nsAString
& aName
, const nsAString
& aLang
, bool aIsLocal
,
81 bool aQueuesUtterances
)
87 mIsQueued(aQueuesUtterances
) {}
89 NS_INLINE_DECL_REFCOUNTING(VoiceData
)
91 nsCOMPtr
<nsISpeechService
> mService
;
106 class GlobalQueueItem final
{
108 // Private destructor, to discourage deletion outside of Release():
109 ~GlobalQueueItem() = default;
112 GlobalQueueItem(VoiceData
* aVoice
, nsSpeechTask
* aTask
,
113 const nsAString
& aText
, const float& aVolume
,
114 const float& aRate
, const float& aPitch
)
123 NS_INLINE_DECL_REFCOUNTING(GlobalQueueItem
)
125 RefPtr
<VoiceData
> mVoice
;
127 RefPtr
<nsSpeechTask
> mTask
;
140 // nsSynthVoiceRegistry
142 static StaticRefPtr
<nsSynthVoiceRegistry
> gSynthVoiceRegistry
;
144 NS_IMPL_ISUPPORTS(nsSynthVoiceRegistry
, nsISynthVoiceRegistry
)
146 nsSynthVoiceRegistry::nsSynthVoiceRegistry()
147 : mSpeechSynthChild(nullptr), mUseGlobalQueue(false), mIsSpeaking(false) {
148 if (XRE_IsContentProcess()) {
149 RefPtr
<SpeechSynthesisChild
> actor
= new SpeechSynthesisChild();
150 if (ContentChild::GetSingleton()->SendPSpeechSynthesisConstructor(actor
)) {
151 mSpeechSynthChild
= actor
;
156 nsSynthVoiceRegistry::~nsSynthVoiceRegistry() {
157 LOG(LogLevel::Debug
, ("~nsSynthVoiceRegistry"));
159 mUriVoiceMap
.Clear();
162 nsSynthVoiceRegistry
* nsSynthVoiceRegistry::GetInstance() {
163 MOZ_ASSERT(NS_IsMainThread());
165 if (!gSynthVoiceRegistry
) {
166 gSynthVoiceRegistry
= new nsSynthVoiceRegistry();
167 ClearOnShutdown(&gSynthVoiceRegistry
);
168 if (XRE_IsParentProcess()) {
169 // Start up all speech synth services.
170 NS_CreateServicesFromCategory(NS_SPEECH_SYNTH_STARTED
, nullptr,
171 NS_SPEECH_SYNTH_STARTED
);
175 return gSynthVoiceRegistry
;
178 already_AddRefed
<nsSynthVoiceRegistry
>
179 nsSynthVoiceRegistry::GetInstanceForService() {
180 RefPtr
<nsSynthVoiceRegistry
> registry
= GetInstance();
182 return registry
.forget();
185 bool nsSynthVoiceRegistry::SendInitialVoicesAndState(
186 SpeechSynthesisParent
* aParent
) {
187 MOZ_ASSERT(XRE_IsParentProcess());
189 nsTArray
<RemoteVoice
> voices
;
190 nsTArray
<nsString
> defaults
;
192 for (uint32_t i
= 0; i
< mVoices
.Length(); ++i
) {
193 RefPtr
<VoiceData
> voice
= mVoices
[i
];
195 voices
.AppendElement(RemoteVoice(voice
->mUri
, voice
->mName
, voice
->mLang
,
196 voice
->mIsLocal
, voice
->mIsQueued
));
199 for (uint32_t i
= 0; i
< mDefaultVoices
.Length(); ++i
) {
200 defaults
.AppendElement(mDefaultVoices
[i
]->mUri
);
203 return aParent
->SendInitialVoicesAndState(voices
, defaults
, IsSpeaking());
206 void nsSynthVoiceRegistry::RecvInitialVoicesAndState(
207 const nsTArray
<RemoteVoice
>& aVoices
, const nsTArray
<nsString
>& aDefaults
,
208 const bool& aIsSpeaking
) {
209 // We really should have a local instance since this is a directed response to
211 MOZ_ASSERT(gSynthVoiceRegistry
);
213 for (uint32_t i
= 0; i
< aVoices
.Length(); ++i
) {
214 RemoteVoice voice
= aVoices
[i
];
215 gSynthVoiceRegistry
->AddVoiceImpl(nullptr, voice
.voiceURI(), voice
.name(),
216 voice
.lang(), voice
.localService(),
220 for (uint32_t i
= 0; i
< aDefaults
.Length(); ++i
) {
221 gSynthVoiceRegistry
->SetDefaultVoice(aDefaults
[i
], true);
224 gSynthVoiceRegistry
->mIsSpeaking
= aIsSpeaking
;
226 if (aVoices
.Length()) {
227 gSynthVoiceRegistry
->NotifyVoicesChanged();
231 void nsSynthVoiceRegistry::RecvRemoveVoice(const nsAString
& aUri
) {
232 // If we dont have a local instance of the registry yet, we will recieve
233 // current voices at contruction time.
234 if (!gSynthVoiceRegistry
) {
238 gSynthVoiceRegistry
->RemoveVoice(nullptr, aUri
);
241 void nsSynthVoiceRegistry::RecvAddVoice(const RemoteVoice
& aVoice
) {
242 // If we dont have a local instance of the registry yet, we will recieve
243 // current voices at contruction time.
244 if (!gSynthVoiceRegistry
) {
248 gSynthVoiceRegistry
->AddVoiceImpl(nullptr, aVoice
.voiceURI(), aVoice
.name(),
249 aVoice
.lang(), aVoice
.localService(),
253 void nsSynthVoiceRegistry::RecvSetDefaultVoice(const nsAString
& aUri
,
255 // If we dont have a local instance of the registry yet, we will recieve
256 // current voices at contruction time.
257 if (!gSynthVoiceRegistry
) {
261 gSynthVoiceRegistry
->SetDefaultVoice(aUri
, aIsDefault
);
264 void nsSynthVoiceRegistry::RecvIsSpeakingChanged(bool aIsSpeaking
) {
265 // If we dont have a local instance of the registry yet, we will get the
266 // speaking state on construction.
267 if (!gSynthVoiceRegistry
) {
271 gSynthVoiceRegistry
->mIsSpeaking
= aIsSpeaking
;
274 void nsSynthVoiceRegistry::RecvNotifyVoicesChanged() {
275 // If we dont have a local instance of the registry yet, we don't care.
276 if (!gSynthVoiceRegistry
) {
280 gSynthVoiceRegistry
->NotifyVoicesChanged();
283 void nsSynthVoiceRegistry::RecvNotifyVoicesError(const nsAString
& aError
) {
284 // If we dont have a local instance of the registry yet, we don't care.
285 if (!gSynthVoiceRegistry
) {
289 gSynthVoiceRegistry
->NotifyVoicesError(aError
);
293 nsSynthVoiceRegistry::AddVoice(nsISpeechService
* aService
,
294 const nsAString
& aUri
, const nsAString
& aName
,
295 const nsAString
& aLang
, bool aLocalService
,
296 bool aQueuesUtterances
) {
298 ("nsSynthVoiceRegistry::AddVoice uri='%s' name='%s' lang='%s' local=%s "
300 NS_ConvertUTF16toUTF8(aUri
).get(), NS_ConvertUTF16toUTF8(aName
).get(),
301 NS_ConvertUTF16toUTF8(aLang
).get(), aLocalService
? "true" : "false",
302 aQueuesUtterances
? "true" : "false"));
304 if (NS_WARN_IF(XRE_IsContentProcess())) {
305 return NS_ERROR_NOT_AVAILABLE
;
308 return AddVoiceImpl(aService
, aUri
, aName
, aLang
, aLocalService
,
313 nsSynthVoiceRegistry::RemoveVoice(nsISpeechService
* aService
,
314 const nsAString
& aUri
) {
315 LOG(LogLevel::Debug
, ("nsSynthVoiceRegistry::RemoveVoice uri='%s' (%s)",
316 NS_ConvertUTF16toUTF8(aUri
).get(),
317 (XRE_IsContentProcess()) ? "child" : "parent"));
320 VoiceData
* retval
= mUriVoiceMap
.GetWeak(aUri
, &found
);
322 if (NS_WARN_IF(!(found
))) {
323 return NS_ERROR_NOT_AVAILABLE
;
325 if (NS_WARN_IF(!(aService
== retval
->mService
))) {
326 return NS_ERROR_INVALID_ARG
;
329 mVoices
.RemoveElement(retval
);
330 mDefaultVoices
.RemoveElement(retval
);
331 mUriVoiceMap
.Remove(aUri
);
333 if (retval
->mIsQueued
&&
334 !StaticPrefs::media_webspeech_synth_force_global_queue()) {
335 // Check if this is the last queued voice, and disable the global queue if
338 for (uint32_t i
= 0; i
< mVoices
.Length(); i
++) {
339 VoiceData
* voice
= mVoices
[i
];
340 if (voice
->mIsQueued
) {
346 mUseGlobalQueue
= false;
350 nsTArray
<SpeechSynthesisParent
*> ssplist
;
351 GetAllSpeechSynthActors(ssplist
);
353 for (uint32_t i
= 0; i
< ssplist
.Length(); ++i
)
354 Unused
<< ssplist
[i
]->SendVoiceRemoved(aUri
);
360 nsSynthVoiceRegistry::NotifyVoicesChanged() {
361 if (XRE_IsParentProcess()) {
362 nsTArray
<SpeechSynthesisParent
*> ssplist
;
363 GetAllSpeechSynthActors(ssplist
);
365 for (uint32_t i
= 0; i
< ssplist
.Length(); ++i
)
366 Unused
<< ssplist
[i
]->SendNotifyVoicesChanged();
369 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
370 if (NS_WARN_IF(!(obs
))) {
371 return NS_ERROR_NOT_AVAILABLE
;
374 obs
->NotifyObservers(nullptr, "synth-voices-changed", nullptr);
380 nsSynthVoiceRegistry::NotifyVoicesError(const nsAString
& aError
) {
381 if (XRE_IsParentProcess()) {
382 nsTArray
<SpeechSynthesisParent
*> ssplist
;
383 GetAllSpeechSynthActors(ssplist
);
385 for (uint32_t i
= 0; i
< ssplist
.Length(); ++i
) {
386 Unused
<< ssplist
[i
]->SendNotifyVoicesError(aError
);
390 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
391 if (NS_WARN_IF(!(obs
))) {
392 return NS_ERROR_NOT_AVAILABLE
;
395 obs
->NotifyObservers(nullptr, "synth-voices-error", aError
.BeginReading());
401 nsSynthVoiceRegistry::SetDefaultVoice(const nsAString
& aUri
, bool aIsDefault
) {
403 VoiceData
* retval
= mUriVoiceMap
.GetWeak(aUri
, &found
);
404 if (NS_WARN_IF(!(found
))) {
405 return NS_ERROR_NOT_AVAILABLE
;
408 mDefaultVoices
.RemoveElement(retval
);
411 ("nsSynthVoiceRegistry::SetDefaultVoice %s %s",
412 NS_ConvertUTF16toUTF8(aUri
).get(), aIsDefault
? "true" : "false"));
415 mDefaultVoices
.AppendElement(retval
);
418 if (XRE_IsParentProcess()) {
419 nsTArray
<SpeechSynthesisParent
*> ssplist
;
420 GetAllSpeechSynthActors(ssplist
);
422 for (uint32_t i
= 0; i
< ssplist
.Length(); ++i
) {
423 Unused
<< ssplist
[i
]->SendSetDefaultVoice(aUri
, aIsDefault
);
431 nsSynthVoiceRegistry::GetVoiceCount(uint32_t* aRetval
) {
432 *aRetval
= mVoices
.Length();
438 nsSynthVoiceRegistry::GetVoice(uint32_t aIndex
, nsAString
& aRetval
) {
439 if (NS_WARN_IF(!(aIndex
< mVoices
.Length()))) {
440 return NS_ERROR_INVALID_ARG
;
443 aRetval
= mVoices
[aIndex
]->mUri
;
449 nsSynthVoiceRegistry::IsDefaultVoice(const nsAString
& aUri
, bool* aRetval
) {
451 VoiceData
* voice
= mUriVoiceMap
.GetWeak(aUri
, &found
);
452 if (NS_WARN_IF(!(found
))) {
453 return NS_ERROR_NOT_AVAILABLE
;
456 for (int32_t i
= mDefaultVoices
.Length(); i
> 0;) {
457 VoiceData
* defaultVoice
= mDefaultVoices
[--i
];
459 if (voice
->mLang
.Equals(defaultVoice
->mLang
)) {
460 *aRetval
= voice
== defaultVoice
;
470 nsSynthVoiceRegistry::IsLocalVoice(const nsAString
& aUri
, bool* aRetval
) {
472 VoiceData
* voice
= mUriVoiceMap
.GetWeak(aUri
, &found
);
473 if (NS_WARN_IF(!(found
))) {
474 return NS_ERROR_NOT_AVAILABLE
;
477 *aRetval
= voice
->mIsLocal
;
482 nsSynthVoiceRegistry::GetVoiceLang(const nsAString
& aUri
, nsAString
& aRetval
) {
484 VoiceData
* voice
= mUriVoiceMap
.GetWeak(aUri
, &found
);
485 if (NS_WARN_IF(!(found
))) {
486 return NS_ERROR_NOT_AVAILABLE
;
489 aRetval
= voice
->mLang
;
494 nsSynthVoiceRegistry::GetVoiceName(const nsAString
& aUri
, nsAString
& aRetval
) {
496 VoiceData
* voice
= mUriVoiceMap
.GetWeak(aUri
, &found
);
497 if (NS_WARN_IF(!(found
))) {
498 return NS_ERROR_NOT_AVAILABLE
;
501 aRetval
= voice
->mName
;
505 nsresult
nsSynthVoiceRegistry::AddVoiceImpl(
506 nsISpeechService
* aService
, const nsAString
& aUri
, const nsAString
& aName
,
507 const nsAString
& aLang
, bool aLocalService
, bool aQueuesUtterances
) {
508 const bool found
= mUriVoiceMap
.Contains(aUri
);
509 if (NS_WARN_IF(found
)) {
510 return NS_ERROR_INVALID_ARG
;
513 RefPtr
<VoiceData
> voice
= new VoiceData(aService
, aUri
, aName
, aLang
,
514 aLocalService
, aQueuesUtterances
);
516 mVoices
.AppendElement(voice
);
517 mUriVoiceMap
.InsertOrUpdate(aUri
, std::move(voice
));
518 mUseGlobalQueue
|= aQueuesUtterances
;
520 nsTArray
<SpeechSynthesisParent
*> ssplist
;
521 GetAllSpeechSynthActors(ssplist
);
523 if (!ssplist
.IsEmpty()) {
524 mozilla::dom::RemoteVoice
ssvoice(nsString(aUri
), nsString(aName
),
525 nsString(aLang
), aLocalService
,
528 for (uint32_t i
= 0; i
< ssplist
.Length(); ++i
) {
529 Unused
<< ssplist
[i
]->SendVoiceAdded(ssvoice
);
536 bool nsSynthVoiceRegistry::FindVoiceByLang(const nsAString
& aLang
,
537 VoiceData
** aRetval
) {
538 nsAString::const_iterator dashPos
, start
, end
;
539 aLang
.BeginReading(start
);
540 aLang
.EndReading(end
);
543 nsAutoString
langPrefix(Substring(start
, end
));
545 for (int32_t i
= mDefaultVoices
.Length(); i
> 0;) {
546 VoiceData
* voice
= mDefaultVoices
[--i
];
548 if (StringBeginsWith(voice
->mLang
, langPrefix
)) {
554 for (int32_t i
= mVoices
.Length(); i
> 0;) {
555 VoiceData
* voice
= mVoices
[--i
];
557 if (StringBeginsWith(voice
->mLang
, langPrefix
)) {
566 if (!RFindInReadable(u
"-"_ns
, end
, dashPos
)) {
574 VoiceData
* nsSynthVoiceRegistry::FindBestMatch(const nsAString
& aUri
,
575 const nsAString
& aLang
) {
576 if (mVoices
.IsEmpty()) {
581 VoiceData
* retval
= mUriVoiceMap
.GetWeak(aUri
, &found
);
584 LOG(LogLevel::Debug
, ("nsSynthVoiceRegistry::FindBestMatch - Matched URI"));
588 // Try finding a match for given voice.
589 if (!aLang
.IsVoid() && !aLang
.IsEmpty()) {
590 if (FindVoiceByLang(aLang
, &retval
)) {
592 ("nsSynthVoiceRegistry::FindBestMatch - Matched language (%s ~= %s)",
593 NS_ConvertUTF16toUTF8(aLang
).get(),
594 NS_ConvertUTF16toUTF8(retval
->mLang
).get()));
601 nsAutoCString uiLang
;
602 LocaleService::GetInstance()->GetAppLocaleAsBCP47(uiLang
);
604 if (FindVoiceByLang(NS_ConvertASCIItoUTF16(uiLang
), &retval
)) {
606 ("nsSynthVoiceRegistry::FindBestMatch - Matched UI language (%s ~= %s)",
607 uiLang
.get(), NS_ConvertUTF16toUTF8(retval
->mLang
).get()));
612 // Try en-US, the language of locale "C"
613 if (FindVoiceByLang(u
"en-US"_ns
, &retval
)) {
614 LOG(LogLevel::Debug
, ("nsSynthVoiceRegistry::FindBestMatch - Matched C "
615 "locale language (en-US ~= %s)",
616 NS_ConvertUTF16toUTF8(retval
->mLang
).get()));
621 // The top default voice is better than nothing...
622 if (!mDefaultVoices
.IsEmpty()) {
623 return mDefaultVoices
.LastElement();
629 already_AddRefed
<nsSpeechTask
> nsSynthVoiceRegistry::SpeakUtterance(
630 SpeechSynthesisUtterance
& aUtterance
, const nsAString
& aDocLang
) {
632 nsString(aUtterance
.mLang
.IsEmpty() ? aDocLang
: aUtterance
.mLang
);
635 if (aUtterance
.mVoice
) {
636 aUtterance
.mVoice
->GetVoiceURI(uri
);
639 // Get current audio volume to apply speech call
640 float volume
= aUtterance
.Volume();
641 RefPtr
<AudioChannelService
> service
= AudioChannelService::GetOrCreate();
643 if (nsCOMPtr
<nsPIDOMWindowInner
> topWindow
= aUtterance
.GetOwnerWindow()) {
644 // TODO : use audio channel agent, open new bug to fix it.
645 AudioPlaybackConfig config
=
646 service
->GetMediaConfig(topWindow
->GetOuterWindow());
647 volume
= config
.mMuted
? 0.0f
: config
.mVolume
* volume
;
651 RefPtr
<nsSpeechTask
> task
;
652 if (XRE_IsContentProcess()) {
653 task
= new SpeechTaskChild(&aUtterance
,
654 aUtterance
.ShouldResistFingerprinting());
655 SpeechSynthesisRequestChild
* actor
= new SpeechSynthesisRequestChild(
656 static_cast<SpeechTaskChild
*>(task
.get()));
657 mSpeechSynthChild
->SendPSpeechSynthesisRequestConstructor(
658 actor
, aUtterance
.mText
, lang
, uri
, volume
, aUtterance
.Rate(),
659 aUtterance
.Pitch(), aUtterance
.ShouldResistFingerprinting());
662 new nsSpeechTask(&aUtterance
, aUtterance
.ShouldResistFingerprinting());
663 Speak(aUtterance
.mText
, lang
, uri
, volume
, aUtterance
.Rate(),
664 aUtterance
.Pitch(), task
);
667 return task
.forget();
670 void nsSynthVoiceRegistry::Speak(const nsAString
& aText
, const nsAString
& aLang
,
671 const nsAString
& aUri
, const float& aVolume
,
672 const float& aRate
, const float& aPitch
,
673 nsSpeechTask
* aTask
) {
674 MOZ_ASSERT(XRE_IsParentProcess());
676 if (aTask
->ShouldResistFingerprinting()) {
677 aTask
->ForceError(0, 0);
681 VoiceData
* voice
= FindBestMatch(aUri
, aLang
);
684 NS_WARNING("No voices found.");
685 aTask
->ForceError(0, 0);
689 aTask
->SetChosenVoiceURI(voice
->mUri
);
691 if (mUseGlobalQueue
||
692 StaticPrefs::media_webspeech_synth_force_global_queue()) {
694 ("nsSynthVoiceRegistry::Speak queueing text='%s' lang='%s' uri='%s' "
696 NS_ConvertUTF16toUTF8(aText
).get(), NS_ConvertUTF16toUTF8(aLang
).get(),
697 NS_ConvertUTF16toUTF8(aUri
).get(), aRate
, aPitch
));
698 RefPtr
<GlobalQueueItem
> item
=
699 new GlobalQueueItem(voice
, aTask
, aText
, aVolume
, aRate
, aPitch
);
700 mGlobalQueue
.AppendElement(item
);
702 if (mGlobalQueue
.Length() == 1) {
703 SpeakImpl(item
->mVoice
, item
->mTask
, item
->mText
, item
->mVolume
,
704 item
->mRate
, item
->mPitch
);
707 SpeakImpl(voice
, aTask
, aText
, aVolume
, aRate
, aPitch
);
711 void nsSynthVoiceRegistry::SpeakNext() {
712 MOZ_ASSERT(XRE_IsParentProcess());
715 ("nsSynthVoiceRegistry::SpeakNext %d", mGlobalQueue
.IsEmpty()));
717 SetIsSpeaking(false);
719 if (mGlobalQueue
.IsEmpty()) {
723 mGlobalQueue
.RemoveElementAt(0);
725 while (!mGlobalQueue
.IsEmpty()) {
726 RefPtr
<GlobalQueueItem
> item
= mGlobalQueue
.ElementAt(0);
727 if (item
->mTask
->IsPreCanceled()) {
728 mGlobalQueue
.RemoveElementAt(0);
731 if (!item
->mTask
->IsPrePaused()) {
732 SpeakImpl(item
->mVoice
, item
->mTask
, item
->mText
, item
->mVolume
,
733 item
->mRate
, item
->mPitch
);
739 void nsSynthVoiceRegistry::ResumeQueue() {
740 MOZ_ASSERT(XRE_IsParentProcess());
742 ("nsSynthVoiceRegistry::ResumeQueue %d", mGlobalQueue
.IsEmpty()));
744 if (mGlobalQueue
.IsEmpty()) {
748 RefPtr
<GlobalQueueItem
> item
= mGlobalQueue
.ElementAt(0);
749 if (!item
->mTask
->IsPrePaused()) {
750 SpeakImpl(item
->mVoice
, item
->mTask
, item
->mText
, item
->mVolume
,
751 item
->mRate
, item
->mPitch
);
755 bool nsSynthVoiceRegistry::IsSpeaking() { return mIsSpeaking
; }
757 void nsSynthVoiceRegistry::SetIsSpeaking(bool aIsSpeaking
) {
758 MOZ_ASSERT(XRE_IsParentProcess());
760 // Only set to 'true' if global queue is enabled.
762 aIsSpeaking
&& (mUseGlobalQueue
||
763 StaticPrefs::media_webspeech_synth_force_global_queue());
765 nsTArray
<SpeechSynthesisParent
*> ssplist
;
766 GetAllSpeechSynthActors(ssplist
);
767 for (uint32_t i
= 0; i
< ssplist
.Length(); ++i
) {
768 Unused
<< ssplist
[i
]->SendIsSpeakingChanged(aIsSpeaking
);
772 void nsSynthVoiceRegistry::SpeakImpl(VoiceData
* aVoice
, nsSpeechTask
* aTask
,
773 const nsAString
& aText
,
774 const float& aVolume
, const float& aRate
,
775 const float& aPitch
) {
777 ("nsSynthVoiceRegistry::SpeakImpl queueing text='%s' uri='%s' rate=%f "
779 NS_ConvertUTF16toUTF8(aText
).get(),
780 NS_ConvertUTF16toUTF8(aVoice
->mUri
).get(), aRate
, aPitch
));
784 if (NS_FAILED(aVoice
->mService
->Speak(aText
, aVoice
->mUri
, aVolume
, aRate
,
786 aTask
->DispatchError(0, 0);
790 } // namespace mozilla::dom