Bug 1945643 - Update to mozilla-nimbus-schemas 2025.1.1 r=chumphreys
[gecko.git] / dom / media / webspeech / synth / nsSynthVoiceRegistry.cpp
blobe5a1353d6b19db6f5daa181423a3ded1fa6630af
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"
18 #include "nsString.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;
33 #undef LOG
34 extern mozilla::LogModule* GetSpeechSynthLog();
35 #define LOG(type, msg) MOZ_LOG(GetSpeechSynthLog(), type, msg)
37 namespace {
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();
48 ++contentIndex) {
49 MOZ_ASSERT(contentActors[contentIndex]);
51 AutoTArray<mozilla::dom::PSpeechSynthesisParent*, 5> speechsynthActors;
52 contentActors[contentIndex]->ManagedPSpeechSynthesisParent(
53 speechsynthActors);
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);
67 } // namespace
69 namespace mozilla::dom {
71 // VoiceData
73 class VoiceData final {
74 private:
75 // Private destructor, to discourage deletion outside of Release():
76 ~VoiceData() = default;
78 public:
79 VoiceData(nsISpeechService* aService, const nsAString& aUri,
80 const nsAString& aName, const nsAString& aLang, bool aIsLocal,
81 bool aQueuesUtterances)
82 : mService(aService),
83 mUri(aUri),
84 mName(aName),
85 mLang(aLang),
86 mIsLocal(aIsLocal),
87 mIsQueued(aQueuesUtterances) {}
89 NS_INLINE_DECL_REFCOUNTING(VoiceData)
91 nsCOMPtr<nsISpeechService> mService;
93 nsString mUri;
95 nsString mName;
97 nsString mLang;
99 bool mIsLocal;
101 bool mIsQueued;
104 // GlobalQueueItem
106 class GlobalQueueItem final {
107 private:
108 // Private destructor, to discourage deletion outside of Release():
109 ~GlobalQueueItem() = default;
111 public:
112 GlobalQueueItem(VoiceData* aVoice, nsSpeechTask* aTask,
113 const nsAString& aText, const float& aVolume,
114 const float& aRate, const float& aPitch)
115 : mVoice(aVoice),
116 mTask(aTask),
117 mText(aText),
118 mVolume(aVolume),
119 mRate(aRate),
120 mPitch(aPitch),
121 mIsLocal(false) {}
123 NS_INLINE_DECL_REFCOUNTING(GlobalQueueItem)
125 RefPtr<VoiceData> mVoice;
127 RefPtr<nsSpeechTask> mTask;
129 nsString mText;
131 float mVolume;
133 float mRate;
135 float mPitch;
137 bool mIsLocal;
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
210 // an Init() call.
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(),
217 voice.queued());
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) {
235 return;
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) {
245 return;
248 gSynthVoiceRegistry->AddVoiceImpl(nullptr, aVoice.voiceURI(), aVoice.name(),
249 aVoice.lang(), aVoice.localService(),
250 aVoice.queued());
253 void nsSynthVoiceRegistry::RecvSetDefaultVoice(const nsAString& aUri,
254 bool aIsDefault) {
255 // If we dont have a local instance of the registry yet, we will recieve
256 // current voices at contruction time.
257 if (!gSynthVoiceRegistry) {
258 return;
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) {
268 return;
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) {
277 return;
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) {
286 return;
289 gSynthVoiceRegistry->NotifyVoicesError(aError);
292 NS_IMETHODIMP
293 nsSynthVoiceRegistry::AddVoice(nsISpeechService* aService,
294 const nsAString& aUri, const nsAString& aName,
295 const nsAString& aLang, bool aLocalService,
296 bool aQueuesUtterances) {
297 LOG(LogLevel::Debug,
298 ("nsSynthVoiceRegistry::AddVoice uri='%s' name='%s' lang='%s' local=%s "
299 "queued=%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,
309 aQueuesUtterances);
312 NS_IMETHODIMP
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"));
319 bool found = false;
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
336 // it is.
337 bool queued = false;
338 for (uint32_t i = 0; i < mVoices.Length(); i++) {
339 VoiceData* voice = mVoices[i];
340 if (voice->mIsQueued) {
341 queued = true;
342 break;
345 if (!queued) {
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);
356 return NS_OK;
359 NS_IMETHODIMP
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);
376 return NS_OK;
379 NS_IMETHODIMP
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());
397 return NS_OK;
400 NS_IMETHODIMP
401 nsSynthVoiceRegistry::SetDefaultVoice(const nsAString& aUri, bool aIsDefault) {
402 bool found = false;
403 VoiceData* retval = mUriVoiceMap.GetWeak(aUri, &found);
404 if (NS_WARN_IF(!(found))) {
405 return NS_ERROR_NOT_AVAILABLE;
408 mDefaultVoices.RemoveElement(retval);
410 LOG(LogLevel::Debug,
411 ("nsSynthVoiceRegistry::SetDefaultVoice %s %s",
412 NS_ConvertUTF16toUTF8(aUri).get(), aIsDefault ? "true" : "false"));
414 if (aIsDefault) {
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);
427 return NS_OK;
430 NS_IMETHODIMP
431 nsSynthVoiceRegistry::GetVoiceCount(uint32_t* aRetval) {
432 *aRetval = mVoices.Length();
434 return NS_OK;
437 NS_IMETHODIMP
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;
445 return NS_OK;
448 NS_IMETHODIMP
449 nsSynthVoiceRegistry::IsDefaultVoice(const nsAString& aUri, bool* aRetval) {
450 bool found;
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;
461 return NS_OK;
465 *aRetval = false;
466 return NS_OK;
469 NS_IMETHODIMP
470 nsSynthVoiceRegistry::IsLocalVoice(const nsAString& aUri, bool* aRetval) {
471 bool found;
472 VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found);
473 if (NS_WARN_IF(!(found))) {
474 return NS_ERROR_NOT_AVAILABLE;
477 *aRetval = voice->mIsLocal;
478 return NS_OK;
481 NS_IMETHODIMP
482 nsSynthVoiceRegistry::GetVoiceLang(const nsAString& aUri, nsAString& aRetval) {
483 bool found;
484 VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found);
485 if (NS_WARN_IF(!(found))) {
486 return NS_ERROR_NOT_AVAILABLE;
489 aRetval = voice->mLang;
490 return NS_OK;
493 NS_IMETHODIMP
494 nsSynthVoiceRegistry::GetVoiceName(const nsAString& aUri, nsAString& aRetval) {
495 bool found;
496 VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found);
497 if (NS_WARN_IF(!(found))) {
498 return NS_ERROR_NOT_AVAILABLE;
501 aRetval = voice->mName;
502 return NS_OK;
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,
526 aQueuesUtterances);
528 for (uint32_t i = 0; i < ssplist.Length(); ++i) {
529 Unused << ssplist[i]->SendVoiceAdded(ssvoice);
533 return NS_OK;
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);
542 while (true) {
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)) {
549 *aRetval = voice;
550 return true;
554 for (int32_t i = mVoices.Length(); i > 0;) {
555 VoiceData* voice = mVoices[--i];
557 if (StringBeginsWith(voice->mLang, langPrefix)) {
558 *aRetval = voice;
559 return true;
563 dashPos = end;
564 end = start;
566 if (!RFindInReadable(u"-"_ns, end, dashPos)) {
567 break;
571 return false;
574 VoiceData* nsSynthVoiceRegistry::FindBestMatch(const nsAString& aUri,
575 const nsAString& aLang) {
576 if (mVoices.IsEmpty()) {
577 return nullptr;
580 bool found = false;
581 VoiceData* retval = mUriVoiceMap.GetWeak(aUri, &found);
583 if (found) {
584 LOG(LogLevel::Debug, ("nsSynthVoiceRegistry::FindBestMatch - Matched URI"));
585 return retval;
588 // Try finding a match for given voice.
589 if (!aLang.IsVoid() && !aLang.IsEmpty()) {
590 if (FindVoiceByLang(aLang, &retval)) {
591 LOG(LogLevel::Debug,
592 ("nsSynthVoiceRegistry::FindBestMatch - Matched language (%s ~= %s)",
593 NS_ConvertUTF16toUTF8(aLang).get(),
594 NS_ConvertUTF16toUTF8(retval->mLang).get()));
596 return retval;
600 // Try UI language.
601 nsAutoCString uiLang;
602 LocaleService::GetInstance()->GetAppLocaleAsBCP47(uiLang);
604 if (FindVoiceByLang(NS_ConvertASCIItoUTF16(uiLang), &retval)) {
605 LOG(LogLevel::Debug,
606 ("nsSynthVoiceRegistry::FindBestMatch - Matched UI language (%s ~= %s)",
607 uiLang.get(), NS_ConvertUTF16toUTF8(retval->mLang).get()));
609 return retval;
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()));
618 return retval;
621 // The top default voice is better than nothing...
622 if (!mDefaultVoices.IsEmpty()) {
623 return mDefaultVoices.LastElement();
626 return nullptr;
629 already_AddRefed<nsSpeechTask> nsSynthVoiceRegistry::SpeakUtterance(
630 SpeechSynthesisUtterance& aUtterance, const nsAString& aDocLang) {
631 nsString lang =
632 nsString(aUtterance.mLang.IsEmpty() ? aDocLang : aUtterance.mLang);
633 nsAutoString uri;
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();
642 if (service) {
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());
660 } else {
661 task =
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);
678 return;
681 VoiceData* voice = FindBestMatch(aUri, aLang);
683 if (!voice) {
684 NS_WARNING("No voices found.");
685 aTask->ForceError(0, 0);
686 return;
689 aTask->SetChosenVoiceURI(voice->mUri);
691 if (mUseGlobalQueue ||
692 StaticPrefs::media_webspeech_synth_force_global_queue()) {
693 LOG(LogLevel::Debug,
694 ("nsSynthVoiceRegistry::Speak queueing text='%s' lang='%s' uri='%s' "
695 "rate=%f pitch=%f",
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);
706 } else {
707 SpeakImpl(voice, aTask, aText, aVolume, aRate, aPitch);
711 void nsSynthVoiceRegistry::SpeakNext() {
712 MOZ_ASSERT(XRE_IsParentProcess());
714 LOG(LogLevel::Debug,
715 ("nsSynthVoiceRegistry::SpeakNext %d", mGlobalQueue.IsEmpty()));
717 SetIsSpeaking(false);
719 if (mGlobalQueue.IsEmpty()) {
720 return;
723 mGlobalQueue.RemoveElementAt(0);
725 while (!mGlobalQueue.IsEmpty()) {
726 RefPtr<GlobalQueueItem> item = mGlobalQueue.ElementAt(0);
727 if (item->mTask->IsPreCanceled()) {
728 mGlobalQueue.RemoveElementAt(0);
729 continue;
731 if (!item->mTask->IsPrePaused()) {
732 SpeakImpl(item->mVoice, item->mTask, item->mText, item->mVolume,
733 item->mRate, item->mPitch);
735 break;
739 void nsSynthVoiceRegistry::ResumeQueue() {
740 MOZ_ASSERT(XRE_IsParentProcess());
741 LOG(LogLevel::Debug,
742 ("nsSynthVoiceRegistry::ResumeQueue %d", mGlobalQueue.IsEmpty()));
744 if (mGlobalQueue.IsEmpty()) {
745 return;
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.
761 mIsSpeaking =
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) {
776 LOG(LogLevel::Debug,
777 ("nsSynthVoiceRegistry::SpeakImpl queueing text='%s' uri='%s' rate=%f "
778 "pitch=%f",
779 NS_ConvertUTF16toUTF8(aText).get(),
780 NS_ConvertUTF16toUTF8(aVoice->mUri).get(), aRate, aPitch));
782 aTask->Init();
784 if (NS_FAILED(aVoice->mService->Speak(aText, aVoice->mUri, aVolume, aRate,
785 aPitch, aTask))) {
786 aTask->DispatchError(0, 0);
790 } // namespace mozilla::dom