michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsILocaleService.h" michael@0: #include "nsISpeechService.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: michael@0: #include "SpeechSynthesisUtterance.h" michael@0: #include "SpeechSynthesisVoice.h" michael@0: #include "nsSynthVoiceRegistry.h" michael@0: #include "nsSpeechTask.h" michael@0: michael@0: #include "nsString.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "mozilla/dom/ContentChild.h" michael@0: #include "mozilla/dom/ContentParent.h" michael@0: #include "mozilla/unused.h" michael@0: michael@0: #include "SpeechSynthesisChild.h" michael@0: #include "SpeechSynthesisParent.h" michael@0: michael@0: #undef LOG michael@0: #ifdef PR_LOGGING michael@0: extern PRLogModuleInfo* GetSpeechSynthLog(); michael@0: #define LOG(type, msg) PR_LOG(GetSpeechSynthLog(), type, msg) michael@0: #else michael@0: #define LOG(type, msg) michael@0: #endif michael@0: michael@0: namespace { michael@0: michael@0: void michael@0: GetAllSpeechSynthActors(InfallibleTArray& aActors) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(aActors.IsEmpty()); michael@0: michael@0: nsAutoTArray contentActors; michael@0: mozilla::dom::ContentParent::GetAll(contentActors); michael@0: michael@0: for (uint32_t contentIndex = 0; michael@0: contentIndex < contentActors.Length(); michael@0: ++contentIndex) { michael@0: MOZ_ASSERT(contentActors[contentIndex]); michael@0: michael@0: AutoInfallibleTArray speechsynthActors; michael@0: contentActors[contentIndex]->ManagedPSpeechSynthesisParent(speechsynthActors); michael@0: michael@0: for (uint32_t speechsynthIndex = 0; michael@0: speechsynthIndex < speechsynthActors.Length(); michael@0: ++speechsynthIndex) { michael@0: MOZ_ASSERT(speechsynthActors[speechsynthIndex]); michael@0: michael@0: mozilla::dom::SpeechSynthesisParent* actor = michael@0: static_cast(speechsynthActors[speechsynthIndex]); michael@0: aActors.AppendElement(actor); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: // VoiceData michael@0: michael@0: class VoiceData MOZ_FINAL michael@0: { michael@0: private: michael@0: // Private destructor, to discourage deletion outside of Release(): michael@0: ~VoiceData() {} michael@0: michael@0: public: michael@0: VoiceData(nsISpeechService* aService, const nsAString& aUri, michael@0: const nsAString& aName, const nsAString& aLang, bool aIsLocal) michael@0: : mService(aService) michael@0: , mUri(aUri) michael@0: , mName(aName) michael@0: , mLang(aLang) michael@0: , mIsLocal(aIsLocal) {} michael@0: michael@0: NS_INLINE_DECL_REFCOUNTING(VoiceData) michael@0: michael@0: nsCOMPtr mService; michael@0: michael@0: nsString mUri; michael@0: michael@0: nsString mName; michael@0: michael@0: nsString mLang; michael@0: michael@0: bool mIsLocal; michael@0: }; michael@0: michael@0: // nsSynthVoiceRegistry michael@0: michael@0: static StaticRefPtr gSynthVoiceRegistry; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsSynthVoiceRegistry, nsISynthVoiceRegistry) michael@0: michael@0: nsSynthVoiceRegistry::nsSynthVoiceRegistry() michael@0: : mSpeechSynthChild(nullptr) michael@0: { michael@0: if (XRE_GetProcessType() == GeckoProcessType_Content) { michael@0: michael@0: mSpeechSynthChild = new SpeechSynthesisChild(); michael@0: ContentChild::GetSingleton()->SendPSpeechSynthesisConstructor(mSpeechSynthChild); michael@0: michael@0: InfallibleTArray voices; michael@0: InfallibleTArray defaults; michael@0: michael@0: mSpeechSynthChild->SendReadVoiceList(&voices, &defaults); michael@0: michael@0: for (uint32_t i = 0; i < voices.Length(); ++i) { michael@0: RemoteVoice voice = voices[i]; michael@0: AddVoiceImpl(nullptr, voice.voiceURI(), michael@0: voice.name(), voice.lang(), michael@0: voice.localService()); michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < defaults.Length(); ++i) { michael@0: SetDefaultVoice(defaults[i], true); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsSynthVoiceRegistry::~nsSynthVoiceRegistry() michael@0: { michael@0: LOG(PR_LOG_DEBUG, ("~nsSynthVoiceRegistry")); michael@0: michael@0: // mSpeechSynthChild's lifecycle is managed by the Content protocol. michael@0: mSpeechSynthChild = nullptr; michael@0: michael@0: mUriVoiceMap.Clear(); michael@0: } michael@0: michael@0: nsSynthVoiceRegistry* michael@0: nsSynthVoiceRegistry::GetInstance() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (!gSynthVoiceRegistry) { michael@0: gSynthVoiceRegistry = new nsSynthVoiceRegistry(); michael@0: } michael@0: michael@0: return gSynthVoiceRegistry; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsSynthVoiceRegistry::GetInstanceForService() michael@0: { michael@0: nsRefPtr registry = GetInstance(); michael@0: michael@0: return registry.forget(); michael@0: } michael@0: michael@0: void michael@0: nsSynthVoiceRegistry::Shutdown() michael@0: { michael@0: LOG(PR_LOG_DEBUG, ("[%s] nsSynthVoiceRegistry::Shutdown()", michael@0: (XRE_GetProcessType() == GeckoProcessType_Content) ? "Content" : "Default")); michael@0: gSynthVoiceRegistry = nullptr; michael@0: } michael@0: michael@0: void michael@0: nsSynthVoiceRegistry::SendVoices(InfallibleTArray* aVoices, michael@0: InfallibleTArray* aDefaults) michael@0: { michael@0: for (uint32_t i=0; i < mVoices.Length(); ++i) { michael@0: nsRefPtr voice = mVoices[i]; michael@0: michael@0: aVoices->AppendElement(RemoteVoice(voice->mUri, voice->mName, voice->mLang, michael@0: voice->mIsLocal)); michael@0: } michael@0: michael@0: for (uint32_t i=0; i < mDefaultVoices.Length(); ++i) { michael@0: aDefaults->AppendElement(mDefaultVoices[i]->mUri); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSynthVoiceRegistry::RecvRemoveVoice(const nsAString& aUri) michael@0: { michael@0: // If we dont have a local instance of the registry yet, we will recieve current michael@0: // voices at contruction time. michael@0: if(!gSynthVoiceRegistry) { michael@0: return; michael@0: } michael@0: michael@0: gSynthVoiceRegistry->RemoveVoice(nullptr, aUri); michael@0: } michael@0: michael@0: void michael@0: nsSynthVoiceRegistry::RecvAddVoice(const RemoteVoice& aVoice) michael@0: { michael@0: // If we dont have a local instance of the registry yet, we will recieve current michael@0: // voices at contruction time. michael@0: if(!gSynthVoiceRegistry) { michael@0: return; michael@0: } michael@0: michael@0: gSynthVoiceRegistry->AddVoiceImpl(nullptr, aVoice.voiceURI(), michael@0: aVoice.name(), aVoice.lang(), michael@0: aVoice.localService()); michael@0: } michael@0: michael@0: void michael@0: nsSynthVoiceRegistry::RecvSetDefaultVoice(const nsAString& aUri, bool aIsDefault) michael@0: { michael@0: // If we dont have a local instance of the registry yet, we will recieve current michael@0: // voices at contruction time. michael@0: if(!gSynthVoiceRegistry) { michael@0: return; michael@0: } michael@0: michael@0: gSynthVoiceRegistry->SetDefaultVoice(aUri, aIsDefault); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSynthVoiceRegistry::AddVoice(nsISpeechService* aService, michael@0: const nsAString& aUri, michael@0: const nsAString& aName, michael@0: const nsAString& aLang, michael@0: bool aLocalService) michael@0: { michael@0: LOG(PR_LOG_DEBUG, michael@0: ("nsSynthVoiceRegistry::AddVoice uri='%s' name='%s' lang='%s' local=%s", michael@0: NS_ConvertUTF16toUTF8(aUri).get(), NS_ConvertUTF16toUTF8(aName).get(), michael@0: NS_ConvertUTF16toUTF8(aLang).get(), michael@0: aLocalService ? "true" : "false")); michael@0: michael@0: NS_ENSURE_FALSE(XRE_GetProcessType() == GeckoProcessType_Content, michael@0: NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return AddVoiceImpl(aService, aUri, aName, aLang, michael@0: aLocalService); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSynthVoiceRegistry::RemoveVoice(nsISpeechService* aService, michael@0: const nsAString& aUri) michael@0: { michael@0: LOG(PR_LOG_DEBUG, michael@0: ("nsSynthVoiceRegistry::RemoveVoice uri='%s' (%s)", michael@0: NS_ConvertUTF16toUTF8(aUri).get(), michael@0: (XRE_GetProcessType() == GeckoProcessType_Content) ? "child" : "parent")); michael@0: michael@0: bool found = false; michael@0: VoiceData* retval = mUriVoiceMap.GetWeak(aUri, &found); michael@0: michael@0: NS_ENSURE_TRUE(found, NS_ERROR_NOT_AVAILABLE); michael@0: NS_ENSURE_TRUE(aService == retval->mService, NS_ERROR_INVALID_ARG); michael@0: michael@0: mVoices.RemoveElement(retval); michael@0: mDefaultVoices.RemoveElement(retval); michael@0: mUriVoiceMap.Remove(aUri); michael@0: michael@0: nsTArray ssplist; michael@0: GetAllSpeechSynthActors(ssplist); michael@0: michael@0: for (uint32_t i = 0; i < ssplist.Length(); ++i) michael@0: unused << ssplist[i]->SendVoiceRemoved(nsString(aUri)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSynthVoiceRegistry::SetDefaultVoice(const nsAString& aUri, michael@0: bool aIsDefault) michael@0: { michael@0: bool found = false; michael@0: VoiceData* retval = mUriVoiceMap.GetWeak(aUri, &found); michael@0: NS_ENSURE_TRUE(found, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: mDefaultVoices.RemoveElement(retval); michael@0: michael@0: LOG(PR_LOG_DEBUG, ("nsSynthVoiceRegistry::SetDefaultVoice %s %s", michael@0: NS_ConvertUTF16toUTF8(aUri).get(), michael@0: aIsDefault ? "true" : "false")); michael@0: michael@0: if (aIsDefault) { michael@0: mDefaultVoices.AppendElement(retval); michael@0: } michael@0: michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: nsTArray ssplist; michael@0: GetAllSpeechSynthActors(ssplist); michael@0: michael@0: for (uint32_t i = 0; i < ssplist.Length(); ++i) { michael@0: unused << ssplist[i]->SendSetDefaultVoice(nsString(aUri), aIsDefault); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSynthVoiceRegistry::GetVoiceCount(uint32_t* aRetval) michael@0: { michael@0: *aRetval = mVoices.Length(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSynthVoiceRegistry::GetVoice(uint32_t aIndex, nsAString& aRetval) michael@0: { michael@0: NS_ENSURE_TRUE(aIndex < mVoices.Length(), NS_ERROR_INVALID_ARG); michael@0: michael@0: aRetval = mVoices[aIndex]->mUri; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSynthVoiceRegistry::IsDefaultVoice(const nsAString& aUri, bool* aRetval) michael@0: { michael@0: bool found; michael@0: VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found); michael@0: NS_ENSURE_TRUE(found, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: for (int32_t i = mDefaultVoices.Length(); i > 0; ) { michael@0: VoiceData* defaultVoice = mDefaultVoices[--i]; michael@0: michael@0: if (voice->mLang.Equals(defaultVoice->mLang)) { michael@0: *aRetval = voice == defaultVoice; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: *aRetval = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSynthVoiceRegistry::IsLocalVoice(const nsAString& aUri, bool* aRetval) michael@0: { michael@0: bool found; michael@0: VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found); michael@0: NS_ENSURE_TRUE(found, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: *aRetval = voice->mIsLocal; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSynthVoiceRegistry::GetVoiceLang(const nsAString& aUri, nsAString& aRetval) michael@0: { michael@0: bool found; michael@0: VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found); michael@0: NS_ENSURE_TRUE(found, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: aRetval = voice->mLang; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSynthVoiceRegistry::GetVoiceName(const nsAString& aUri, nsAString& aRetval) michael@0: { michael@0: bool found; michael@0: VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found); michael@0: NS_ENSURE_TRUE(found, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: aRetval = voice->mName; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSynthVoiceRegistry::AddVoiceImpl(nsISpeechService* aService, michael@0: const nsAString& aUri, michael@0: const nsAString& aName, michael@0: const nsAString& aLang, michael@0: bool aLocalService) michael@0: { michael@0: bool found = false; michael@0: mUriVoiceMap.GetWeak(aUri, &found); michael@0: NS_ENSURE_FALSE(found, NS_ERROR_INVALID_ARG); michael@0: michael@0: nsRefPtr voice = new VoiceData(aService, aUri, aName, aLang, michael@0: aLocalService); michael@0: michael@0: mVoices.AppendElement(voice); michael@0: mUriVoiceMap.Put(aUri, voice); michael@0: michael@0: nsTArray ssplist; michael@0: GetAllSpeechSynthActors(ssplist); michael@0: michael@0: if (!ssplist.IsEmpty()) { michael@0: mozilla::dom::RemoteVoice ssvoice(nsString(aUri), michael@0: nsString(aName), michael@0: nsString(aLang), michael@0: aLocalService); michael@0: michael@0: for (uint32_t i = 0; i < ssplist.Length(); ++i) { michael@0: unused << ssplist[i]->SendVoiceAdded(ssvoice); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsSynthVoiceRegistry::FindVoiceByLang(const nsAString& aLang, michael@0: VoiceData** aRetval) michael@0: { michael@0: nsAString::const_iterator dashPos, start, end; michael@0: aLang.BeginReading(start); michael@0: aLang.EndReading(end); michael@0: michael@0: while (true) { michael@0: nsAutoString langPrefix(Substring(start, end)); michael@0: michael@0: for (int32_t i = mDefaultVoices.Length(); i > 0; ) { michael@0: VoiceData* voice = mDefaultVoices[--i]; michael@0: michael@0: if (StringBeginsWith(voice->mLang, langPrefix)) { michael@0: *aRetval = voice; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: for (int32_t i = mVoices.Length(); i > 0; ) { michael@0: VoiceData* voice = mVoices[--i]; michael@0: michael@0: if (StringBeginsWith(voice->mLang, langPrefix)) { michael@0: *aRetval = voice; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: dashPos = end; michael@0: end = start; michael@0: michael@0: if (!RFindInReadable(NS_LITERAL_STRING("-"), end, dashPos)) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: VoiceData* michael@0: nsSynthVoiceRegistry::FindBestMatch(const nsAString& aUri, michael@0: const nsAString& aLang) michael@0: { michael@0: if (mVoices.IsEmpty()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: bool found = false; michael@0: VoiceData* retval = mUriVoiceMap.GetWeak(aUri, &found); michael@0: michael@0: if (found) { michael@0: LOG(PR_LOG_DEBUG, ("nsSynthVoiceRegistry::FindBestMatch - Matched URI")); michael@0: return retval; michael@0: } michael@0: michael@0: // Try finding a match for given voice. michael@0: if (!aLang.IsVoid() && !aLang.IsEmpty()) { michael@0: if (FindVoiceByLang(aLang, &retval)) { michael@0: LOG(PR_LOG_DEBUG, michael@0: ("nsSynthVoiceRegistry::FindBestMatch - Matched language (%s ~= %s)", michael@0: NS_ConvertUTF16toUTF8(aLang).get(), michael@0: NS_ConvertUTF16toUTF8(retval->mLang).get())); michael@0: michael@0: return retval; michael@0: } michael@0: } michael@0: michael@0: // Try UI language. michael@0: nsresult rv; michael@0: nsCOMPtr localeService = do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: nsAutoString uiLang; michael@0: rv = localeService->GetLocaleComponentForUserAgent(uiLang); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: if (FindVoiceByLang(uiLang, &retval)) { michael@0: LOG(PR_LOG_DEBUG, michael@0: ("nsSynthVoiceRegistry::FindBestMatch - Matched UI language (%s ~= %s)", michael@0: NS_ConvertUTF16toUTF8(uiLang).get(), michael@0: NS_ConvertUTF16toUTF8(retval->mLang).get())); michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: // Try en-US, the language of locale "C" michael@0: if (FindVoiceByLang(NS_LITERAL_STRING("en-US"), &retval)) { michael@0: LOG(PR_LOG_DEBUG, michael@0: ("nsSynthVoiceRegistry::FindBestMatch - Matched C locale language (en-US ~= %s)", michael@0: NS_ConvertUTF16toUTF8(retval->mLang).get())); michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: // The top default voice is better than nothing... michael@0: if (!mDefaultVoices.IsEmpty()) { michael@0: return mDefaultVoices.LastElement(); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsSynthVoiceRegistry::SpeakUtterance(SpeechSynthesisUtterance& aUtterance, michael@0: const nsAString& aDocLang) michael@0: { michael@0: nsString lang = nsString(aUtterance.mLang.IsEmpty() ? aDocLang : aUtterance.mLang); michael@0: nsAutoString uri; michael@0: michael@0: if (aUtterance.mVoice) { michael@0: aUtterance.mVoice->GetVoiceURI(uri); michael@0: } michael@0: michael@0: nsRefPtr task; michael@0: if (XRE_GetProcessType() == GeckoProcessType_Content) { michael@0: task = new SpeechTaskChild(&aUtterance); michael@0: SpeechSynthesisRequestChild* actor = michael@0: new SpeechSynthesisRequestChild(static_cast(task.get())); michael@0: mSpeechSynthChild->SendPSpeechSynthesisRequestConstructor(actor, michael@0: aUtterance.mText, michael@0: lang, michael@0: uri, michael@0: aUtterance.Volume(), michael@0: aUtterance.Rate(), michael@0: aUtterance.Pitch()); michael@0: } else { michael@0: task = new nsSpeechTask(&aUtterance); michael@0: Speak(aUtterance.mText, lang, uri, michael@0: aUtterance.Rate(), aUtterance.Pitch(), task); michael@0: } michael@0: michael@0: return task.forget(); michael@0: } michael@0: michael@0: void michael@0: nsSynthVoiceRegistry::Speak(const nsAString& aText, michael@0: const nsAString& aLang, michael@0: const nsAString& aUri, michael@0: const float& aRate, michael@0: const float& aPitch, michael@0: nsSpeechTask* aTask) michael@0: { michael@0: LOG(PR_LOG_DEBUG, michael@0: ("nsSynthVoiceRegistry::Speak text='%s' lang='%s' uri='%s' rate=%f pitch=%f", michael@0: NS_ConvertUTF16toUTF8(aText).get(), NS_ConvertUTF16toUTF8(aLang).get(), michael@0: NS_ConvertUTF16toUTF8(aUri).get(), aRate, aPitch)); michael@0: michael@0: VoiceData* voice = FindBestMatch(aUri, aLang); michael@0: michael@0: if (!voice) { michael@0: NS_WARNING("No voices found."); michael@0: aTask->DispatchError(0, 0); michael@0: return; michael@0: } michael@0: michael@0: LOG(PR_LOG_DEBUG, ("nsSynthVoiceRegistry::Speak - Using voice URI: %s", michael@0: NS_ConvertUTF16toUTF8(voice->mUri).get())); michael@0: michael@0: SpeechServiceType serviceType; michael@0: michael@0: DebugOnly rv = voice->mService->GetServiceType(&serviceType); michael@0: NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to get speech service type"); michael@0: michael@0: if (serviceType == nsISpeechService::SERVICETYPE_INDIRECT_AUDIO) { michael@0: aTask->SetIndirectAudio(true); michael@0: } michael@0: michael@0: voice->mService->Speak(aText, voice->mUri, aRate, aPitch, aTask); michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla