| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <objbase.h> |
| |
| #include <math.h> |
| #include <sapi.h> |
| #include <stdint.h> |
| #include <wrl/client.h> |
| #include <wrl/implements.h> |
| |
| #include <algorithm> |
| |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/synchronization/lock.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/task_runner.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "base/thread_annotations.h" |
| #include "base/threading/sequence_bound.h" |
| #include "base/values.h" |
| #include "base/win/scoped_co_mem.h" |
| #include "base/win/sphelper.h" |
| #include "content/browser/speech/tts_platform_impl.h" |
| #include "content/browser/speech/tts_win_utils.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/tts_controller.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| class TtsPlatformImplWin; |
| class TtsPlatformImplBackgroundWorker; |
| |
| constexpr int kInvalidUtteranceId = -1; |
| |
| // ISpObjectToken key and value names. |
| const wchar_t kAttributesKey[] = L"Attributes"; |
| const wchar_t kLanguageValue[] = L"Language"; |
| |
| // Original blog detailing how to use this registry. |
| // https://social.msdn.microsoft.com/Forums/en-US/8bbe761c-69c7-401c-8261-1442935c57c8/why-isnt-my-program-detecting-all-tts-voices |
| // Microsoft docs on how to view system registry keys. |
| // https://docs.microsoft.com/en-us/troubleshoot/windows-client/deployment/view-system-registry-with-64-bit-windows |
| const wchar_t* kSPCategoryOneCoreVoices = |
| L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech_OneCore\\Voices"; |
| |
| // This COM interface is receiving the TTS events on the ISpVoice asynchronous |
| // worker thread and is emitting a notification task |
| // TtsPlatformImplBackgroundWorker::SendTtsEvent(...) on the worker sequence. |
| class TtsEventSink |
| : public Microsoft::WRL::RuntimeClass< |
| Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, |
| ISpNotifySink> { |
| public: |
| TtsEventSink(TtsPlatformImplBackgroundWorker* worker, |
| scoped_refptr<base::TaskRunner> worker_task_runner) |
| : worker_(worker), worker_task_runner_(std::move(worker_task_runner)) {} |
| |
| // ISpNotifySink: |
| IFACEMETHODIMP Notify(void) override; |
| |
| int GetUtteranceId() { |
| base::AutoLock lock(lock_); |
| return utterance_id_; |
| } |
| |
| void SetUtteranceId(int utterance_id) { |
| base::AutoLock lock(lock_); |
| utterance_id_ = utterance_id; |
| } |
| |
| private: |
| // |worker_| is leaky and must never deleted because TtsEventSink posts |
| // asynchronous tasks to it. |
| raw_ptr<TtsPlatformImplBackgroundWorker> worker_; |
| scoped_refptr<base::TaskRunner> worker_task_runner_; |
| |
| base::Lock lock_; |
| int utterance_id_ GUARDED_BY(lock_); |
| }; |
| |
| class TtsPlatformImplBackgroundWorker { |
| public: |
| explicit TtsPlatformImplBackgroundWorker( |
| scoped_refptr<base::TaskRunner> task_runner) |
| : tts_event_sink_( |
| Microsoft::WRL::Make<TtsEventSink>(this, std::move(task_runner))) {} |
| TtsPlatformImplBackgroundWorker(const TtsPlatformImplBackgroundWorker&) = |
| delete; |
| TtsPlatformImplBackgroundWorker& operator=( |
| const TtsPlatformImplBackgroundWorker&) = delete; |
| ~TtsPlatformImplBackgroundWorker() = default; |
| |
| void Initialize(); |
| |
| void ProcessSpeech(int utterance_id, |
| const std::string& lang, |
| const VoiceData& voice, |
| const UtteranceContinuousParameters& params, |
| base::OnceCallback<void(bool)> on_speak_finished, |
| const std::string& parsed_utterance); |
| |
| void StopSpeaking(bool paused); |
| void Pause(); |
| void Resume(); |
| void Shutdown(); |
| |
| // This function is called after being notified by the speech synthetizer that |
| // there are TTS notifications are available and should be they should be |
| // processed. |
| void OnSpeechEvent(int utterance_id); |
| |
| // Send an TTS event notification to the TTS controller. |
| void SendTtsEvent(int utterance_id, |
| TtsEventType event_type, |
| int char_index, |
| int length = -1); |
| |
| private: |
| void GetVoices(std::vector<VoiceData>* voices); |
| |
| // Search the newer OneCore or the older SAPI locations for voice tokens. |
| // This ensures new voices are shown and that the method works on Windows 7. |
| bool GetVoiceTokens(Microsoft::WRL::ComPtr<IEnumSpObjectTokens>* out_tokens); |
| |
| void SetVoiceFromName(const std::string& name); |
| |
| // These apply to the current utterance only that is currently being processed |
| // on the worker thread. TTS events are dispatched by TtsEventSink to this |
| // class and update the current speaking state of the utterance. |
| std::string last_voice_name_; |
| ULONG stream_number_ = 0u; |
| int utterance_id_ = kInvalidUtteranceId; |
| size_t utterance_char_position_ = 0u; |
| size_t utterance_prefix_length_ = 0u; |
| size_t utterance_length_ = 0u; |
| |
| // The COM class ISpVoice lives within the COM MTA apartment (worker pool). |
| // This interface can not be called on the UI thread since UI thread is |
| // COM STA. |
| Microsoft::WRL::ComPtr<ISpVoice> speech_synthesizer_; |
| Microsoft::WRL::ComPtr<TtsEventSink> tts_event_sink_; |
| }; |
| |
| class TtsPlatformImplWin : public TtsPlatformImpl { |
| public: |
| TtsPlatformImplWin(const TtsPlatformImplWin&) = delete; |
| TtsPlatformImplWin& operator=(const TtsPlatformImplWin&) = delete; |
| |
| bool PlatformImplSupported() override { return true; } |
| bool PlatformImplInitialized() override; |
| |
| void Speak(int utterance_id, |
| const std::string& utterance, |
| const std::string& lang, |
| const VoiceData& voice, |
| const UtteranceContinuousParameters& params, |
| base::OnceCallback<void(bool)> on_speak_finished) override; |
| |
| bool StopSpeaking() override; |
| |
| void Pause() override; |
| |
| void Resume() override; |
| |
| bool IsSpeaking() override; |
| |
| void GetVoices(std::vector<VoiceData>* out_voices) override; |
| |
| void Shutdown() override; |
| |
| void OnInitializeComplete(bool success, std::vector<VoiceData> voices); |
| void OnSpeakScheduled(base::OnceCallback<void(bool)> on_speak_finished, |
| bool success); |
| void OnSpeakFinished(int utterance_id); |
| |
| // Get the single instance of this class. |
| static TtsPlatformImplWin* GetInstance(); |
| |
| private: |
| friend base::NoDestructor<TtsPlatformImplWin>; |
| TtsPlatformImplWin(); |
| |
| void ProcessSpeech(int utterance_id, |
| const std::string& lang, |
| const VoiceData& voice, |
| const UtteranceContinuousParameters& params, |
| base::OnceCallback<void(bool)> on_speak_finished, |
| const std::string& parsed_utterance); |
| |
| void FinishCurrentUtterance(); |
| |
| // These variables hold the platform state. |
| bool paused_ = false; |
| bool is_speaking_ = false; |
| int utterance_id_ = kInvalidUtteranceId; |
| bool platform_initialized_ = false; |
| std::vector<VoiceData> voices_; |
| |
| // Hold the state and the code of the background implementation. |
| scoped_refptr<base::SequencedTaskRunner> worker_task_runner_; |
| base::SequenceBound<TtsPlatformImplBackgroundWorker> worker_; |
| }; |
| |
| HRESULT TtsEventSink::Notify() { |
| worker_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&TtsPlatformImplBackgroundWorker::OnSpeechEvent, |
| base::Unretained(worker_), GetUtteranceId())); |
| return S_OK; |
| } |
| |
| // |
| // TtsPlatformImplBackgroundWorker |
| // |
| |
| void TtsPlatformImplBackgroundWorker::Initialize() { |
| bool success = false; |
| std::vector<VoiceData> voices; |
| |
| ::CoCreateInstance(CLSID_SpVoice, nullptr, CLSCTX_ALL, |
| IID_PPV_ARGS(&speech_synthesizer_)); |
| if (speech_synthesizer_.Get()) { |
| ULONGLONG event_mask = |
| SPFEI(SPEI_START_INPUT_STREAM) | SPFEI(SPEI_TTS_BOOKMARK) | |
| SPFEI(SPEI_WORD_BOUNDARY) | SPFEI(SPEI_SENTENCE_BOUNDARY) | |
| SPFEI(SPEI_END_INPUT_STREAM); |
| speech_synthesizer_->SetInterest(event_mask, event_mask); |
| speech_synthesizer_->SetNotifySink(tts_event_sink_.Get()); |
| |
| GetVoices(&voices); |
| |
| success = true; |
| } |
| |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&TtsPlatformImplWin::OnInitializeComplete, |
| base::Unretained(TtsPlatformImplWin::GetInstance()), |
| success, std::move(voices))); |
| } |
| |
| void TtsPlatformImplBackgroundWorker::ProcessSpeech( |
| int utterance_id, |
| const std::string& lang, |
| const VoiceData& voice, |
| const UtteranceContinuousParameters& params, |
| base::OnceCallback<void(bool)> on_speak_finished, |
| const std::string& parsed_utterance) { |
| DCHECK(speech_synthesizer_.Get()); |
| |
| SetVoiceFromName(voice.name); |
| |
| if (params.rate >= 0.0) { |
| // Map our multiplicative range of 0.1x to 10.0x onto Microsoft's |
| // linear range of -10 to 10: |
| // 0.1 -> -10 |
| // 1.0 -> 0 |
| // 10.0 -> 10 |
| speech_synthesizer_->SetRate(static_cast<int32_t>(10 * log10(params.rate))); |
| } |
| |
| std::wstring prefix; |
| std::wstring suffix; |
| if (params.pitch >= 0.0) { |
| // The TTS api allows a range of -10 to 10 for speech pitch: |
| // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms720500(v%3Dvs.85) |
| // Note that the API requires an integer value, so be sure to cast the pitch |
| // value to an int before calling NumberToWString. TODO(dtseng): cleanup if |
| // we ever use any other properties that require xml. |
| double adjusted_pitch = std::clamp<double>(params.pitch * 10 - 10, -10, 10); |
| std::wstring adjusted_pitch_string = |
| base::NumberToWString(static_cast<int>(adjusted_pitch)); |
| prefix = L"<pitch absmiddle=\"" + adjusted_pitch_string + L"\">"; |
| suffix = L"</pitch>"; |
| } |
| |
| if (params.volume >= 0.0) { |
| // The TTS api allows a range of 0 to 100 for speech volume. |
| speech_synthesizer_->SetVolume(static_cast<uint16_t>(params.volume * 100)); |
| } |
| |
| // TODO(dmazzoni): convert SSML to SAPI xml. http://crbug.com/88072 |
| |
| std::wstring utterance = base::UTF8ToWide(parsed_utterance); |
| RemoveXml(utterance); |
| std::wstring merged_utterance = prefix + utterance + suffix; |
| |
| utterance_id_ = utterance_id; |
| utterance_char_position_ = 0; |
| utterance_length_ = utterance.size(); |
| utterance_prefix_length_ = prefix.size(); |
| |
| tts_event_sink_->SetUtteranceId(utterance_id); |
| |
| HRESULT result = speech_synthesizer_->Speak(merged_utterance.c_str(), |
| SPF_ASYNC, &stream_number_); |
| bool success = (result == S_OK); |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(std::move(on_speak_finished), success)); |
| } |
| |
| void TtsPlatformImplWin::FinishCurrentUtterance() { |
| if (paused_) |
| Resume(); |
| |
| DCHECK(is_speaking_ || (utterance_id_ == kInvalidUtteranceId)); |
| is_speaking_ = false; |
| utterance_id_ = kInvalidUtteranceId; |
| } |
| |
| void TtsPlatformImplBackgroundWorker::StopSpeaking(bool paused) { |
| if (speech_synthesizer_.Get()) { |
| // Block notifications from the current utterance. |
| tts_event_sink_->SetUtteranceId(kInvalidUtteranceId); |
| utterance_id_ = kInvalidUtteranceId; |
| |
| // Stop speech by speaking nullptr with the purge flag. |
| speech_synthesizer_->Speak(nullptr, SPF_PURGEBEFORESPEAK, nullptr); |
| |
| // Ensures the synthesizer is not paused after a stop. |
| if (paused) |
| speech_synthesizer_->Resume(); |
| } |
| } |
| |
| void TtsPlatformImplBackgroundWorker::Pause() { |
| if (speech_synthesizer_.Get()) { |
| speech_synthesizer_->Pause(); |
| SendTtsEvent(utterance_id_, TTS_EVENT_PAUSE, utterance_char_position_); |
| } |
| } |
| |
| void TtsPlatformImplBackgroundWorker::Resume() { |
| if (speech_synthesizer_.Get()) { |
| speech_synthesizer_->Resume(); |
| SendTtsEvent(utterance_id_, TTS_EVENT_RESUME, utterance_char_position_); |
| } |
| } |
| |
| void TtsPlatformImplBackgroundWorker::Shutdown() { |
| if (speech_synthesizer_) |
| speech_synthesizer_->SetNotifySink(nullptr); |
| if (tts_event_sink_) { |
| tts_event_sink_->SetUtteranceId(kInvalidUtteranceId); |
| utterance_id_ = kInvalidUtteranceId; |
| } |
| |
| tts_event_sink_ = nullptr; |
| speech_synthesizer_ = nullptr; |
| } |
| |
| void TtsPlatformImplBackgroundWorker::OnSpeechEvent(int utterance_id) { |
| if (!speech_synthesizer_.Get()) |
| return; |
| |
| SPEVENT event; |
| while (S_OK == speech_synthesizer_->GetEvents(1, &event, nullptr)) { |
| // Ignore notifications that are not related to the current utterance. |
| if (event.ulStreamNum != stream_number_ || |
| utterance_id_ == kInvalidUtteranceId || utterance_id != utterance_id_) { |
| continue; |
| } |
| |
| switch (event.eEventId) { |
| case SPEI_START_INPUT_STREAM: |
| utterance_char_position_ = 0; |
| SendTtsEvent(utterance_id_, TTS_EVENT_START, utterance_char_position_); |
| break; |
| case SPEI_END_INPUT_STREAM: |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&TtsPlatformImplWin::OnSpeakFinished, |
| base::Unretained(TtsPlatformImplWin::GetInstance()), |
| utterance_id_)); |
| |
| utterance_char_position_ = utterance_length_; |
| SendTtsEvent(utterance_id_, TTS_EVENT_END, utterance_char_position_); |
| break; |
| case SPEI_TTS_BOOKMARK: |
| SendTtsEvent(utterance_id_, TTS_EVENT_MARKER, utterance_char_position_); |
| break; |
| case SPEI_WORD_BOUNDARY: |
| utterance_char_position_ = |
| static_cast<size_t>(event.lParam) - utterance_prefix_length_; |
| SendTtsEvent(utterance_id_, TTS_EVENT_WORD, utterance_char_position_, |
| static_cast<ULONG>(event.wParam)); |
| |
| break; |
| case SPEI_SENTENCE_BOUNDARY: |
| utterance_char_position_ = |
| static_cast<size_t>(event.lParam) - utterance_prefix_length_; |
| SendTtsEvent(utterance_id_, TTS_EVENT_SENTENCE, |
| utterance_char_position_); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| void TtsPlatformImplBackgroundWorker::SendTtsEvent(int utterance_id, |
| TtsEventType event_type, |
| int char_index, |
| int length) { |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&TtsController::OnTtsEvent, |
| base::Unretained(TtsController::GetInstance()), |
| utterance_id, event_type, char_index, length, |
| std::string())); |
| } |
| |
| void TtsPlatformImplBackgroundWorker::GetVoices( |
| std::vector<VoiceData>* out_voices) { |
| if (!speech_synthesizer_.Get()) |
| return; |
| |
| Microsoft::WRL::ComPtr<IEnumSpObjectTokens> voice_tokens; |
| unsigned long voice_count; |
| if (!this->GetVoiceTokens(&voice_tokens)) { |
| return; |
| } |
| if (S_OK != voice_tokens->GetCount(&voice_count)) |
| return; |
| |
| for (unsigned i = 0; i < voice_count; i++) { |
| VoiceData voice; |
| |
| Microsoft::WRL::ComPtr<ISpObjectToken> voice_token; |
| if (S_OK != voice_tokens->Next(1, &voice_token, NULL)) |
| return; |
| |
| base::win::ScopedCoMem<WCHAR> description; |
| if (S_OK != SpGetDescription(voice_token.Get(), &description)) |
| continue; |
| voice.name = base::WideToUTF8(description.get()); |
| |
| Microsoft::WRL::ComPtr<ISpDataKey> attributes; |
| if (S_OK != voice_token->OpenKey(kAttributesKey, &attributes)) |
| continue; |
| |
| base::win::ScopedCoMem<WCHAR> language; |
| if (S_OK == attributes->GetStringValue(kLanguageValue, &language)) { |
| int lcid_value; |
| base::HexStringToInt(base::WideToUTF8(language.get()), &lcid_value); |
| LCID lcid = MAKELCID(lcid_value, SORT_DEFAULT); |
| WCHAR locale_name[LOCALE_NAME_MAX_LENGTH] = {}; |
| LCIDToLocaleName(lcid, locale_name, LOCALE_NAME_MAX_LENGTH, 0); |
| voice.lang = base::WideToUTF8(locale_name); |
| } |
| |
| voice.native = true; |
| voice.events.insert(TTS_EVENT_START); |
| voice.events.insert(TTS_EVENT_END); |
| voice.events.insert(TTS_EVENT_MARKER); |
| voice.events.insert(TTS_EVENT_WORD); |
| voice.events.insert(TTS_EVENT_SENTENCE); |
| voice.events.insert(TTS_EVENT_PAUSE); |
| voice.events.insert(TTS_EVENT_RESUME); |
| out_voices->push_back(voice); |
| } |
| } |
| |
| void TtsPlatformImplBackgroundWorker::SetVoiceFromName( |
| const std::string& name) { |
| if (name.empty() || name == last_voice_name_) |
| return; |
| |
| last_voice_name_ = name; |
| |
| Microsoft::WRL::ComPtr<IEnumSpObjectTokens> voice_tokens; |
| unsigned long voice_count; |
| if (!this->GetVoiceTokens(&voice_tokens)) { |
| return; |
| } |
| if (S_OK != voice_tokens->GetCount(&voice_count)) |
| return; |
| |
| for (unsigned i = 0; i < voice_count; i++) { |
| Microsoft::WRL::ComPtr<ISpObjectToken> voice_token; |
| if (S_OK != voice_tokens->Next(1, &voice_token, NULL)) |
| return; |
| |
| base::win::ScopedCoMem<WCHAR> description; |
| if (S_OK != SpGetDescription(voice_token.Get(), &description)) |
| continue; |
| if (name == base::WideToUTF8(description.get())) { |
| speech_synthesizer_->SetVoice(voice_token.Get()); |
| break; |
| } |
| } |
| } |
| |
| bool TtsPlatformImplBackgroundWorker::GetVoiceTokens( |
| Microsoft::WRL::ComPtr<IEnumSpObjectTokens>* out_tokens) { |
| if (S_OK == |
| SpEnumTokens(kSPCategoryOneCoreVoices, NULL, NULL, &(*out_tokens))) { |
| } else if (S_OK != SpEnumTokens(SPCAT_VOICES, NULL, NULL, &(*out_tokens))) { |
| return false; |
| } |
| return true; |
| } |
| |
| // |
| // TtsPlatformImplWin |
| // |
| |
| bool TtsPlatformImplWin::PlatformImplInitialized() { |
| DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| return platform_initialized_; |
| } |
| |
| void TtsPlatformImplWin::Speak( |
| int utterance_id, |
| const std::string& utterance, |
| const std::string& lang, |
| const VoiceData& voice, |
| const UtteranceContinuousParameters& params, |
| base::OnceCallback<void(bool)> on_speak_finished) { |
| DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| DCHECK(platform_initialized_); |
| |
| // Do not emit utterance if the platform is not ready. |
| if (paused_ || is_speaking_) { |
| std::move(on_speak_finished).Run(false); |
| return; |
| } |
| |
| // Flag that a utterance is getting emitted. The |is_speaking_| flag will be |
| // set back to false when the utterance will be fully spoken, stopped or if |
| // the voice synthetizer was not able to emit it. |
| is_speaking_ = true; |
| utterance_id_ = utterance_id; |
| |
| // Parse SSML and process speech. |
| TtsController::GetInstance()->StripSSML( |
| utterance, |
| base::BindOnce(&TtsPlatformImplWin::ProcessSpeech, base::Unretained(this), |
| utterance_id, lang, voice, params, |
| base::BindOnce(&TtsPlatformImplWin::OnSpeakScheduled, |
| base::Unretained(this), |
| std::move(on_speak_finished)))); |
| } |
| |
| bool TtsPlatformImplWin::StopSpeaking() { |
| DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| worker_.AsyncCall(&TtsPlatformImplBackgroundWorker::StopSpeaking) |
| .WithArgs(paused_); |
| paused_ = false; |
| |
| is_speaking_ = false; |
| utterance_id_ = kInvalidUtteranceId; |
| |
| return true; |
| } |
| |
| void TtsPlatformImplWin::Pause() { |
| DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| DCHECK(platform_initialized_); |
| |
| if (paused_ || !is_speaking_) |
| return; |
| worker_.AsyncCall(&TtsPlatformImplBackgroundWorker::Pause); |
| paused_ = true; |
| } |
| |
| void TtsPlatformImplWin::Resume() { |
| DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| DCHECK(platform_initialized_); |
| |
| if (!paused_) |
| return; |
| |
| worker_.AsyncCall(&TtsPlatformImplBackgroundWorker::Resume); |
| paused_ = false; |
| } |
| |
| bool TtsPlatformImplWin::IsSpeaking() { |
| DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| DCHECK(platform_initialized_); |
| return is_speaking_ && !paused_; |
| } |
| |
| void TtsPlatformImplWin::GetVoices(std::vector<VoiceData>* out_voices) { |
| DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| DCHECK(platform_initialized_); |
| out_voices->insert(out_voices->end(), voices_.begin(), voices_.end()); |
| } |
| |
| void TtsPlatformImplWin::Shutdown() { |
| // This is required to ensures the object is released before the COM is |
| // uninitialized. Otherwise, this is causing shutdown hangs. |
| worker_.AsyncCall(&TtsPlatformImplBackgroundWorker::Shutdown); |
| } |
| |
| void TtsPlatformImplWin::OnInitializeComplete(bool success, |
| std::vector<VoiceData> voices) { |
| DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| if (success) |
| voices_ = std::move(voices); |
| |
| platform_initialized_ = true; |
| TtsController::GetInstance()->VoicesChanged(); |
| } |
| |
| void TtsPlatformImplWin::OnSpeakScheduled( |
| base::OnceCallback<void(bool)> on_speak_finished, |
| bool success) { |
| DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| DCHECK(is_speaking_ || (utterance_id_ == kInvalidUtteranceId)); |
| // If speech was stopped while we were processing the utterance (For example, |
| // in the case of a page navigation), then there is nothing left to do. Do not |
| // emit an asynchronous TTS event to confirm the end of speech. |
| if (!is_speaking_) { |
| return; |
| } |
| |
| // If the utterance was not able to be emitted, stop the speaking. There |
| // won't be any asynchronous TTS event to confirm the end of the speech. |
| if (!success) |
| FinishCurrentUtterance(); |
| |
| // Pass the results to our caller. |
| std::move(on_speak_finished).Run(success); |
| } |
| |
| void TtsPlatformImplWin::OnSpeakFinished(int utterance_id) { |
| DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| if (utterance_id != utterance_id_) |
| return; |
| |
| FinishCurrentUtterance(); |
| } |
| |
| void TtsPlatformImplWin::ProcessSpeech( |
| int utterance_id, |
| const std::string& lang, |
| const VoiceData& voice, |
| const UtteranceContinuousParameters& params, |
| base::OnceCallback<void(bool)> on_speak_finished, |
| const std::string& parsed_utterance) { |
| DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| worker_.AsyncCall(&TtsPlatformImplBackgroundWorker::ProcessSpeech) |
| .WithArgs(utterance_id, lang, voice, params, std::move(on_speak_finished), |
| parsed_utterance); |
| } |
| |
| TtsPlatformImplWin::TtsPlatformImplWin() |
| : worker_task_runner_( |
| base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})), |
| worker_(worker_task_runner_, worker_task_runner_) { |
| DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| worker_.AsyncCall(&TtsPlatformImplBackgroundWorker::Initialize); |
| } |
| |
| // static |
| TtsPlatformImplWin* TtsPlatformImplWin::GetInstance() { |
| static base::NoDestructor<TtsPlatformImplWin> tts_platform; |
| return tts_platform.get(); |
| } |
| |
| } // namespace |
| |
| // static |
| TtsPlatformImpl* TtsPlatformImpl::GetInstance() { |
| return TtsPlatformImplWin::GetInstance(); |
| } |
| |
| } // namespace content |