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