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 "nsSpeechTask.h" michael@0: #include "prlog.h" michael@0: michael@0: #include "mozilla/dom/ContentChild.h" michael@0: #include "mozilla/dom/Element.h" michael@0: michael@0: #include "mozilla/dom/SpeechSynthesisBinding.h" michael@0: #include "SpeechSynthesis.h" michael@0: #include "nsSynthVoiceRegistry.h" michael@0: #include "nsIDocument.h" michael@0: michael@0: #undef LOG michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* michael@0: GetSpeechSynthLog() michael@0: { michael@0: static PRLogModuleInfo* sLog = nullptr; michael@0: michael@0: if (!sLog) { michael@0: sLog = PR_NewLogModule("SpeechSynthesis"); michael@0: } michael@0: michael@0: return sLog; michael@0: } 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 mozilla { michael@0: namespace dom { michael@0: michael@0: static PLDHashOperator michael@0: TraverseCachedVoices(const nsAString& aKey, SpeechSynthesisVoice* aEntry, void* aData) michael@0: { michael@0: nsCycleCollectionTraversalCallback* cb = static_cast(aData); michael@0: cb->NoteXPCOMChild(aEntry); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(SpeechSynthesis) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SpeechSynthesis) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mCurrentTask) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechQueue) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER michael@0: tmp->mVoiceCache.Clear(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SpeechSynthesis) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCurrentTask) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechQueue) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: tmp->mVoiceCache.EnumerateRead(TraverseCachedVoices, &cb); michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(SpeechSynthesis) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SpeechSynthesis) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(SpeechSynthesis) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(SpeechSynthesis) michael@0: michael@0: SpeechSynthesis::SpeechSynthesis(nsPIDOMWindow* aParent) michael@0: : mParent(aParent) michael@0: { michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: SpeechSynthesis::~SpeechSynthesis() michael@0: { michael@0: } michael@0: michael@0: JSObject* michael@0: SpeechSynthesis::WrapObject(JSContext* aCx) michael@0: { michael@0: return SpeechSynthesisBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: nsIDOMWindow* michael@0: SpeechSynthesis::GetParentObject() const michael@0: { michael@0: return mParent; michael@0: } michael@0: michael@0: bool michael@0: SpeechSynthesis::Pending() const michael@0: { michael@0: switch (mSpeechQueue.Length()) { michael@0: case 0: michael@0: return false; michael@0: michael@0: case 1: michael@0: return mSpeechQueue.ElementAt(0)->GetState() == SpeechSynthesisUtterance::STATE_PENDING; michael@0: michael@0: default: michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: SpeechSynthesis::Speaking() const michael@0: { michael@0: if (mSpeechQueue.IsEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: return mSpeechQueue.ElementAt(0)->GetState() == SpeechSynthesisUtterance::STATE_SPEAKING; michael@0: } michael@0: michael@0: bool michael@0: SpeechSynthesis::Paused() const michael@0: { michael@0: if (mSpeechQueue.IsEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: return mSpeechQueue.ElementAt(0)->IsPaused(); michael@0: } michael@0: michael@0: void michael@0: SpeechSynthesis::Speak(SpeechSynthesisUtterance& aUtterance) michael@0: { michael@0: if (aUtterance.mState != SpeechSynthesisUtterance::STATE_NONE) { michael@0: // XXX: Should probably raise an error michael@0: return; michael@0: } michael@0: michael@0: mSpeechQueue.AppendElement(&aUtterance); michael@0: aUtterance.mState = SpeechSynthesisUtterance::STATE_PENDING; michael@0: michael@0: if (mSpeechQueue.Length() == 1) { michael@0: AdvanceQueue(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SpeechSynthesis::AdvanceQueue() michael@0: { michael@0: LOG(PR_LOG_DEBUG, michael@0: ("SpeechSynthesis::AdvanceQueue length=%d", mSpeechQueue.Length())); michael@0: michael@0: if (mSpeechQueue.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr utterance = mSpeechQueue.ElementAt(0); michael@0: michael@0: nsAutoString docLang; michael@0: nsCOMPtr win = do_QueryInterface(mParent); michael@0: nsIDocument* doc = win->GetExtantDoc(); michael@0: michael@0: if (doc) { michael@0: Element* elm = doc->GetHtmlElement(); michael@0: michael@0: if (elm) { michael@0: elm->GetLang(docLang); michael@0: } michael@0: } michael@0: michael@0: mCurrentTask = michael@0: nsSynthVoiceRegistry::GetInstance()->SpeakUtterance(*utterance, docLang); michael@0: michael@0: if (mCurrentTask) { michael@0: mCurrentTask->SetSpeechSynthesis(this); michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: void michael@0: SpeechSynthesis::Cancel() michael@0: { michael@0: mSpeechQueue.Clear(); michael@0: michael@0: if (mCurrentTask) { michael@0: mCurrentTask->Cancel(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SpeechSynthesis::Pause() michael@0: { michael@0: if (mCurrentTask) { michael@0: mCurrentTask->Pause(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SpeechSynthesis::Resume() michael@0: { michael@0: if (mCurrentTask) { michael@0: mCurrentTask->Resume(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SpeechSynthesis::OnEnd(const nsSpeechTask* aTask) michael@0: { michael@0: MOZ_ASSERT(mCurrentTask == aTask); michael@0: michael@0: if (!mSpeechQueue.IsEmpty()) { michael@0: mSpeechQueue.RemoveElementAt(0); michael@0: } michael@0: michael@0: mCurrentTask = nullptr; michael@0: AdvanceQueue(); michael@0: } michael@0: michael@0: void michael@0: SpeechSynthesis::GetVoices(nsTArray< nsRefPtr >& aResult) michael@0: { michael@0: aResult.Clear(); michael@0: uint32_t voiceCount = 0; michael@0: michael@0: nsresult rv = nsSynthVoiceRegistry::GetInstance()->GetVoiceCount(&voiceCount); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: for (uint32_t i = 0; i < voiceCount; i++) { michael@0: nsAutoString uri; michael@0: rv = nsSynthVoiceRegistry::GetInstance()->GetVoice(i, uri); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to retrieve voice from registry"); michael@0: continue; michael@0: } michael@0: michael@0: SpeechSynthesisVoice* voice = mVoiceCache.GetWeak(uri); michael@0: michael@0: if (!voice) { michael@0: voice = new SpeechSynthesisVoice(this, uri); michael@0: } michael@0: michael@0: aResult.AppendElement(voice); michael@0: } michael@0: michael@0: mVoiceCache.Clear(); michael@0: michael@0: for (uint32_t i = 0; i < aResult.Length(); i++) { michael@0: SpeechSynthesisVoice* voice = aResult[i]; michael@0: mVoiceCache.Put(voice->mUri, voice); michael@0: } michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla