1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <math.h>
6 #include <objbase.h>
7 #include <sapi.h>
8 #include <stdint.h>
9 #include <wrl/client.h>
10 #include <wrl/implements.h>
11
12 #include <algorithm>
13
14 #include "base/bind.h"
15 #include "base/macros.h"
16 #include "base/no_destructor.h"
17 #include "base/sequenced_task_runner.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_piece.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/synchronization/lock.h"
22 #include "base/task/task_traits.h"
23 #include "base/task/thread_pool.h"
24 #include "base/task_runner.h"
25 #include "base/thread_annotations.h"
26 #include "base/threading/sequence_bound.h"
27 #include "base/values.h"
28 #include "base/win/scoped_co_mem.h"
29 #include "base/win/sphelper.h"
30 #include "content/browser/speech/tts_platform_impl.h"
31 #include "content/public/browser/browser_task_traits.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "content/public/browser/tts_controller.h"
34
35 namespace content {
36
37 namespace {
38
39 class TtsPlatformImplWin;
40 class TtsPlatformImplBackgroundWorker;
41
42 constexpr int kInvalidUtteranceId = -1;
43
44 // ISpObjectToken key and value names.
45 const wchar_t kAttributesKey[] = L"Attributes";
46 const wchar_t kLanguageValue[] = L"Language";
47
48 // This COM interface is receiving the TTS events on the ISpVoice asynchronous
49 // worker thread and is emitting a notification task
50 // TtsPlatformImplBackgroundWorker::SendTtsEvent(...) on the worker sequence.
51 class TtsEventSink
52 : public Microsoft::WRL::RuntimeClass<
53 Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
54 ISpNotifySink> {
55 public:
TtsEventSink(TtsPlatformImplBackgroundWorker * worker,scoped_refptr<base::TaskRunner> worker_task_runner)56 TtsEventSink(TtsPlatformImplBackgroundWorker* worker,
57 scoped_refptr<base::TaskRunner> worker_task_runner)
58 : worker_(worker), worker_task_runner_(std::move(worker_task_runner)) {}
59
60 // ISpNotifySink:
61 IFACEMETHODIMP Notify(void) override;
62
GetUtteranceId()63 int GetUtteranceId() {
64 base::AutoLock lock(lock_);
65 return utterance_id_;
66 }
67
SetUtteranceId(int utterance_id)68 void SetUtteranceId(int utterance_id) {
69 base::AutoLock lock(lock_);
70 utterance_id_ = utterance_id;
71 }
72
73 private:
74 // |worker_| is leaky and must never deleted because TtsEventSink posts
75 // asynchronous tasks to it.
76 TtsPlatformImplBackgroundWorker* worker_;
77 scoped_refptr<base::TaskRunner> worker_task_runner_;
78
79 base::Lock lock_;
80 int utterance_id_ GUARDED_BY(lock_);
81 };
82
83 class TtsPlatformImplBackgroundWorker {
84 public:
TtsPlatformImplBackgroundWorker(scoped_refptr<base::TaskRunner> task_runner)85 explicit TtsPlatformImplBackgroundWorker(
86 scoped_refptr<base::TaskRunner> task_runner)
87 : tts_event_sink_(
88 Microsoft::WRL::Make<TtsEventSink>(this, std::move(task_runner))) {}
89 TtsPlatformImplBackgroundWorker(const TtsPlatformImplBackgroundWorker&) =
90 delete;
91 TtsPlatformImplBackgroundWorker& operator=(
92 const TtsPlatformImplBackgroundWorker&) = delete;
93 ~TtsPlatformImplBackgroundWorker() = default;
94
95 void Initialize();
96
97 void ProcessSpeech(int utterance_id,
98 const std::string& lang,
99 const VoiceData& voice,
100 const UtteranceContinuousParameters& params,
101 base::OnceCallback<void(bool)> on_speak_finished,
102 const std::string& parsed_utterance);
103
104 void StopSpeaking(bool paused);
105 void Pause();
106 void Resume();
107 void Shutdown();
108
109 // This function is called after being notified by the speech synthetizer that
110 // there are TTS notifications are available and should be they should be
111 // processed.
112 void OnSpeechEvent(int utterance_id);
113
114 // Send an TTS event notification to the TTS controller.
115 void SendTtsEvent(int utterance_id,
116 TtsEventType event_type,
117 int char_index,
118 int length = -1);
119
120 private:
121 void GetVoices(std::vector<VoiceData>* voices);
122
123 void SetVoiceFromName(const std::string& name);
124
125 // These apply to the current utterance only that is currently being processed
126 // on the worker thread. TTS events are dispatched by TtsEventSink to this
127 // class and update the current speaking state of the utterance.
128 std::string last_voice_name_;
129 ULONG stream_number_ = 0u;
130 int utterance_id_ = kInvalidUtteranceId;
131 size_t utterance_char_position_ = 0u;
132 size_t utterance_prefix_length_ = 0u;
133 size_t utterance_length_ = 0u;
134
135 // The COM class ISpVoice lives within the COM MTA apartment (worker pool).
136 // This interface can not be called on the UI thread since UI thread is
137 // COM STA.
138 Microsoft::WRL::ComPtr<ISpVoice> speech_synthesizer_;
139 Microsoft::WRL::ComPtr<TtsEventSink> tts_event_sink_;
140 };
141
142 class TtsPlatformImplWin : public TtsPlatformImpl {
143 public:
144 TtsPlatformImplWin(const TtsPlatformImplWin&) = delete;
145 TtsPlatformImplWin& operator=(const TtsPlatformImplWin&) = delete;
146
PlatformImplSupported()147 bool PlatformImplSupported() override { return true; }
148 bool PlatformImplInitialized() override;
149
150 void Speak(int utterance_id,
151 const std::string& utterance,
152 const std::string& lang,
153 const VoiceData& voice,
154 const UtteranceContinuousParameters& params,
155 base::OnceCallback<void(bool)> on_speak_finished) override;
156
157 bool StopSpeaking() override;
158
159 void Pause() override;
160
161 void Resume() override;
162
163 bool IsSpeaking() override;
164
165 void GetVoices(std::vector<VoiceData>* out_voices) override;
166
167 void Shutdown() override;
168
169 void OnInitializeComplete(bool success, std::vector<VoiceData> voices);
170 void OnSpeakScheduled(base::OnceCallback<void(bool)> on_speak_finished,
171 bool success);
172 void OnSpeakFinished(int utterance_id);
173
174 // Get the single instance of this class.
175 static TtsPlatformImplWin* GetInstance();
176
177 private:
178 friend base::NoDestructor<TtsPlatformImplWin>;
179 TtsPlatformImplWin();
180
181 void ProcessSpeech(int utterance_id,
182 const std::string& lang,
183 const VoiceData& voice,
184 const UtteranceContinuousParameters& params,
185 base::OnceCallback<void(bool)> on_speak_finished,
186 const std::string& parsed_utterance);
187
188 void FinishCurrentUtterance();
189
190 // These variables hold the platform state.
191 bool paused_ = false;
192 bool is_speaking_ = false;
193 int utterance_id_ = kInvalidUtteranceId;
194 bool platform_initialized_ = false;
195 std::vector<VoiceData> voices_;
196
197 // Hold the state and the code of the background implementation.
198 scoped_refptr<base::SequencedTaskRunner> worker_task_runner_;
199 base::SequenceBound<TtsPlatformImplBackgroundWorker> worker_;
200 };
201
Notify()202 HRESULT TtsEventSink::Notify() {
203 worker_task_runner_->PostTask(
204 FROM_HERE, base::BindOnce(&TtsPlatformImplBackgroundWorker::OnSpeechEvent,
205 base::Unretained(worker_), GetUtteranceId()));
206 return S_OK;
207 }
208
209 //
210 // TtsPlatformImplBackgroundWorker
211 //
212
Initialize()213 void TtsPlatformImplBackgroundWorker::Initialize() {
214 bool success = false;
215 std::vector<VoiceData> voices;
216
217 ::CoCreateInstance(CLSID_SpVoice, nullptr, CLSCTX_ALL,
218 IID_PPV_ARGS(&speech_synthesizer_));
219 if (speech_synthesizer_.Get()) {
220 ULONGLONG event_mask =
221 SPFEI(SPEI_START_INPUT_STREAM) | SPFEI(SPEI_TTS_BOOKMARK) |
222 SPFEI(SPEI_WORD_BOUNDARY) | SPFEI(SPEI_SENTENCE_BOUNDARY) |
223 SPFEI(SPEI_END_INPUT_STREAM);
224 speech_synthesizer_->SetInterest(event_mask, event_mask);
225 speech_synthesizer_->SetNotifySink(tts_event_sink_.Get());
226
227 GetVoices(&voices);
228
229 success = true;
230 }
231
232 GetUIThreadTaskRunner({})->PostTask(
233 FROM_HERE,
234 base::BindOnce(&TtsPlatformImplWin::OnInitializeComplete,
235 base::Unretained(TtsPlatformImplWin::GetInstance()),
236 success, std::move(voices)));
237 }
238
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)239 void TtsPlatformImplBackgroundWorker::ProcessSpeech(
240 int utterance_id,
241 const std::string& lang,
242 const VoiceData& voice,
243 const UtteranceContinuousParameters& params,
244 base::OnceCallback<void(bool)> on_speak_finished,
245 const std::string& parsed_utterance) {
246 DCHECK(speech_synthesizer_.Get());
247
248 SetVoiceFromName(voice.name);
249
250 if (params.rate >= 0.0) {
251 // Map our multiplicative range of 0.1x to 10.0x onto Microsoft's
252 // linear range of -10 to 10:
253 // 0.1 -> -10
254 // 1.0 -> 0
255 // 10.0 -> 10
256 speech_synthesizer_->SetRate(static_cast<int32_t>(10 * log10(params.rate)));
257 }
258
259 std::wstring prefix;
260 std::wstring suffix;
261 if (params.pitch >= 0.0) {
262 // The TTS api allows a range of -10 to 10 for speech pitch:
263 // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms720500(v%3Dvs.85)
264 // Note that the API requires an integer value, so be sure to cast the pitch
265 // value to an int before calling NumberToString16. TODO(dtseng): cleanup if
266 // we ever use any other properties that require xml.
267 double adjusted_pitch =
268 std::max<double>(-10, std::min<double>(params.pitch * 10 - 10, 10));
269 std::wstring adjusted_pitch_string =
270 base::NumberToString16(static_cast<int>(adjusted_pitch));
271 prefix = L"<pitch absmiddle=\"" + adjusted_pitch_string + L"\">";
272 suffix = L"</pitch>";
273 }
274
275 if (params.volume >= 0.0) {
276 // The TTS api allows a range of 0 to 100 for speech volume.
277 speech_synthesizer_->SetVolume(static_cast<uint16_t>(params.volume * 100));
278 }
279
280 // TODO(dmazzoni): convert SSML to SAPI xml. http://crbug.com/88072
281
282 std::wstring utterance = base::UTF8ToWide(parsed_utterance);
283 std::wstring merged_utterance = prefix + utterance + suffix;
284
285 utterance_id_ = utterance_id;
286 utterance_char_position_ = 0;
287 utterance_length_ = utterance.size();
288 utterance_prefix_length_ = prefix.size();
289
290 tts_event_sink_->SetUtteranceId(utterance_id);
291
292 HRESULT result = speech_synthesizer_->Speak(merged_utterance.c_str(),
293 SPF_ASYNC, &stream_number_);
294 bool success = (result == S_OK);
295 GetUIThreadTaskRunner({})->PostTask(
296 FROM_HERE, base::BindOnce(std::move(on_speak_finished), success));
297 }
298
FinishCurrentUtterance()299 void TtsPlatformImplWin::FinishCurrentUtterance() {
300 if (paused_)
301 Resume();
302
303 DCHECK(is_speaking_);
304 DCHECK_NE(utterance_id_, kInvalidUtteranceId);
305 is_speaking_ = false;
306 utterance_id_ = kInvalidUtteranceId;
307 }
308
StopSpeaking(bool paused)309 void TtsPlatformImplBackgroundWorker::StopSpeaking(bool paused) {
310 if (speech_synthesizer_.Get()) {
311 // Block notifications from the current utterance.
312 tts_event_sink_->SetUtteranceId(kInvalidUtteranceId);
313 utterance_id_ = kInvalidUtteranceId;
314
315 // Stop speech by speaking nullptr with the purge flag.
316 speech_synthesizer_->Speak(nullptr, SPF_PURGEBEFORESPEAK, nullptr);
317
318 // Ensures the synthesizer is not paused after a stop.
319 if (paused)
320 speech_synthesizer_->Resume();
321 }
322 }
323
Pause()324 void TtsPlatformImplBackgroundWorker::Pause() {
325 if (speech_synthesizer_.Get()) {
326 speech_synthesizer_->Pause();
327 SendTtsEvent(utterance_id_, TTS_EVENT_PAUSE, utterance_char_position_);
328 }
329 }
330
Resume()331 void TtsPlatformImplBackgroundWorker::Resume() {
332 if (speech_synthesizer_.Get()) {
333 speech_synthesizer_->Resume();
334 SendTtsEvent(utterance_id_, TTS_EVENT_RESUME, utterance_char_position_);
335 }
336 }
337
Shutdown()338 void TtsPlatformImplBackgroundWorker::Shutdown() {
339 if (speech_synthesizer_)
340 speech_synthesizer_->SetNotifySink(nullptr);
341 if (tts_event_sink_) {
342 tts_event_sink_->SetUtteranceId(kInvalidUtteranceId);
343 utterance_id_ = kInvalidUtteranceId;
344 }
345
346 tts_event_sink_ = nullptr;
347 speech_synthesizer_ = nullptr;
348 }
349
OnSpeechEvent(int utterance_id)350 void TtsPlatformImplBackgroundWorker::OnSpeechEvent(int utterance_id) {
351 if (!speech_synthesizer_.Get())
352 return;
353
354 SPEVENT event;
355 while (S_OK == speech_synthesizer_->GetEvents(1, &event, nullptr)) {
356 // Ignore notifications that are not related to the current utterance.
357 if (event.ulStreamNum != stream_number_ ||
358 utterance_id_ == kInvalidUtteranceId || utterance_id != utterance_id_) {
359 continue;
360 }
361
362 switch (event.eEventId) {
363 case SPEI_START_INPUT_STREAM:
364 utterance_char_position_ = 0;
365 SendTtsEvent(utterance_id_, TTS_EVENT_START, utterance_char_position_);
366 break;
367 case SPEI_END_INPUT_STREAM:
368 GetUIThreadTaskRunner({})->PostTask(
369 FROM_HERE,
370 base::BindOnce(&TtsPlatformImplWin::OnSpeakFinished,
371 base::Unretained(TtsPlatformImplWin::GetInstance()),
372 utterance_id_));
373
374 utterance_char_position_ = utterance_length_;
375 SendTtsEvent(utterance_id_, TTS_EVENT_END, utterance_char_position_);
376 break;
377 case SPEI_TTS_BOOKMARK:
378 SendTtsEvent(utterance_id_, TTS_EVENT_MARKER, utterance_char_position_);
379 break;
380 case SPEI_WORD_BOUNDARY:
381 utterance_char_position_ =
382 static_cast<size_t>(event.lParam) - utterance_prefix_length_;
383 SendTtsEvent(utterance_id_, TTS_EVENT_WORD, utterance_char_position_,
384 static_cast<ULONG>(event.wParam));
385
386 break;
387 case SPEI_SENTENCE_BOUNDARY:
388 utterance_char_position_ =
389 static_cast<size_t>(event.lParam) - utterance_prefix_length_;
390 SendTtsEvent(utterance_id_, TTS_EVENT_SENTENCE,
391 utterance_char_position_);
392 break;
393 default:
394 break;
395 }
396 }
397 }
398
SendTtsEvent(int utterance_id,TtsEventType event_type,int char_index,int length)399 void TtsPlatformImplBackgroundWorker::SendTtsEvent(int utterance_id,
400 TtsEventType event_type,
401 int char_index,
402 int length) {
403 GetUIThreadTaskRunner({})->PostTask(
404 FROM_HERE, base::BindOnce(&TtsController::OnTtsEvent,
405 base::Unretained(TtsController::GetInstance()),
406 utterance_id, event_type, char_index, length,
407 std::string()));
408 }
409
GetVoices(std::vector<VoiceData> * out_voices)410 void TtsPlatformImplBackgroundWorker::GetVoices(
411 std::vector<VoiceData>* out_voices) {
412 if (!speech_synthesizer_.Get())
413 return;
414
415 Microsoft::WRL::ComPtr<IEnumSpObjectTokens> voice_tokens;
416 unsigned long voice_count;
417 if (S_OK != SpEnumTokens(SPCAT_VOICES, NULL, NULL, &voice_tokens))
418 return;
419 if (S_OK != voice_tokens->GetCount(&voice_count))
420 return;
421
422 for (unsigned i = 0; i < voice_count; i++) {
423 VoiceData voice;
424
425 Microsoft::WRL::ComPtr<ISpObjectToken> voice_token;
426 if (S_OK != voice_tokens->Next(1, &voice_token, NULL))
427 return;
428
429 base::win::ScopedCoMem<WCHAR> description;
430 if (S_OK != SpGetDescription(voice_token.Get(), &description))
431 continue;
432 voice.name = base::WideToUTF8(description.get());
433
434 Microsoft::WRL::ComPtr<ISpDataKey> attributes;
435 if (S_OK != voice_token->OpenKey(kAttributesKey, &attributes))
436 continue;
437
438 base::win::ScopedCoMem<WCHAR> language;
439 if (S_OK == attributes->GetStringValue(kLanguageValue, &language)) {
440 int lcid_value;
441 base::HexStringToInt(base::WideToUTF8(language.get()), &lcid_value);
442 LCID lcid = MAKELCID(lcid_value, SORT_DEFAULT);
443 WCHAR locale_name[LOCALE_NAME_MAX_LENGTH] = {0};
444 LCIDToLocaleName(lcid, locale_name, LOCALE_NAME_MAX_LENGTH, 0);
445 voice.lang = base::WideToUTF8(locale_name);
446 }
447
448 voice.native = true;
449 voice.events.insert(TTS_EVENT_START);
450 voice.events.insert(TTS_EVENT_END);
451 voice.events.insert(TTS_EVENT_MARKER);
452 voice.events.insert(TTS_EVENT_WORD);
453 voice.events.insert(TTS_EVENT_SENTENCE);
454 voice.events.insert(TTS_EVENT_PAUSE);
455 voice.events.insert(TTS_EVENT_RESUME);
456 out_voices->push_back(voice);
457 }
458 }
459
SetVoiceFromName(const std::string & name)460 void TtsPlatformImplBackgroundWorker::SetVoiceFromName(
461 const std::string& name) {
462 if (name.empty() || name == last_voice_name_)
463 return;
464
465 last_voice_name_ = name;
466
467 Microsoft::WRL::ComPtr<IEnumSpObjectTokens> voice_tokens;
468 unsigned long voice_count;
469 if (S_OK != SpEnumTokens(SPCAT_VOICES, NULL, NULL, &voice_tokens))
470 return;
471 if (S_OK != voice_tokens->GetCount(&voice_count))
472 return;
473
474 for (unsigned i = 0; i < voice_count; i++) {
475 Microsoft::WRL::ComPtr<ISpObjectToken> voice_token;
476 if (S_OK != voice_tokens->Next(1, &voice_token, NULL))
477 return;
478
479 base::win::ScopedCoMem<WCHAR> description;
480 if (S_OK != SpGetDescription(voice_token.Get(), &description))
481 continue;
482 if (name == base::WideToUTF8(description.get())) {
483 speech_synthesizer_->SetVoice(voice_token.Get());
484 break;
485 }
486 }
487 }
488
489 //
490 // TtsPlatformImplWin
491 //
492
PlatformImplInitialized()493 bool TtsPlatformImplWin::PlatformImplInitialized() {
494 DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
495 return platform_initialized_;
496 }
497
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)498 void TtsPlatformImplWin::Speak(
499 int utterance_id,
500 const std::string& utterance,
501 const std::string& lang,
502 const VoiceData& voice,
503 const UtteranceContinuousParameters& params,
504 base::OnceCallback<void(bool)> on_speak_finished) {
505 DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
506 DCHECK(platform_initialized_);
507
508 // Do not emit utterance if the platform is not ready.
509 if (paused_ || is_speaking_) {
510 std::move(on_speak_finished).Run(false);
511 return;
512 }
513
514 // Flag that a utterance is getting emitted. The |is_speaking_| flag will be
515 // set back to false when the utterance will be fully spoken, stopped or if
516 // the voice synthetizer was not able to emit it.
517 is_speaking_ = true;
518 utterance_id_ = utterance_id;
519
520 // Parse SSML and process speech.
521 TtsController::GetInstance()->StripSSML(
522 utterance,
523 base::BindOnce(&TtsPlatformImplWin::ProcessSpeech, base::Unretained(this),
524 utterance_id, lang, voice, params,
525 base::BindOnce(&TtsPlatformImplWin::OnSpeakScheduled,
526 base::Unretained(this),
527 std::move(on_speak_finished))));
528 }
529
StopSpeaking()530 bool TtsPlatformImplWin::StopSpeaking() {
531 DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
532
533 worker_.Post(FROM_HERE, &TtsPlatformImplBackgroundWorker::StopSpeaking,
534 paused_);
535 paused_ = false;
536
537 is_speaking_ = false;
538 utterance_id_ = kInvalidUtteranceId;
539
540 return true;
541 }
542
Pause()543 void TtsPlatformImplWin::Pause() {
544 DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
545 DCHECK(platform_initialized_);
546
547 if (paused_ || !is_speaking_)
548 return;
549 worker_.Post(FROM_HERE, &TtsPlatformImplBackgroundWorker::Pause);
550 paused_ = true;
551 }
552
Resume()553 void TtsPlatformImplWin::Resume() {
554 DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
555 DCHECK(platform_initialized_);
556
557 if (!paused_)
558 return;
559
560 worker_.Post(FROM_HERE, &TtsPlatformImplBackgroundWorker::Resume);
561 paused_ = false;
562 }
563
IsSpeaking()564 bool TtsPlatformImplWin::IsSpeaking() {
565 DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
566 DCHECK(platform_initialized_);
567 return is_speaking_ && !paused_;
568 }
569
GetVoices(std::vector<VoiceData> * out_voices)570 void TtsPlatformImplWin::GetVoices(std::vector<VoiceData>* out_voices) {
571 DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
572 DCHECK(platform_initialized_);
573 out_voices->insert(out_voices->end(), voices_.begin(), voices_.end());
574 }
575
Shutdown()576 void TtsPlatformImplWin::Shutdown() {
577 // This is required to ensures the object is released before the COM is
578 // uninitialized. Otherwise, this is causing shutdown hangs.
579 worker_.Post(FROM_HERE, &TtsPlatformImplBackgroundWorker::Shutdown);
580 }
581
OnInitializeComplete(bool success,std::vector<VoiceData> voices)582 void TtsPlatformImplWin::OnInitializeComplete(bool success,
583 std::vector<VoiceData> voices) {
584 DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
585
586 if (success)
587 voices_ = std::move(voices);
588
589 platform_initialized_ = true;
590 TtsController::GetInstance()->VoicesChanged();
591 }
592
OnSpeakScheduled(base::OnceCallback<void (bool)> on_speak_finished,bool success)593 void TtsPlatformImplWin::OnSpeakScheduled(
594 base::OnceCallback<void(bool)> on_speak_finished,
595 bool success) {
596 DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
597 DCHECK(is_speaking_);
598
599 // If the utterance was not able to be emitted, stop the speaking. There
600 // won't be any asynchronous TTS event to confirm the end of the speech.
601 if (!success)
602 FinishCurrentUtterance();
603
604 // Pass the results to our caller.
605 std::move(on_speak_finished).Run(success);
606 }
607
OnSpeakFinished(int utterance_id)608 void TtsPlatformImplWin::OnSpeakFinished(int utterance_id) {
609 DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
610 if (utterance_id != utterance_id_)
611 return;
612
613 FinishCurrentUtterance();
614 }
615
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)616 void TtsPlatformImplWin::ProcessSpeech(
617 int utterance_id,
618 const std::string& lang,
619 const VoiceData& voice,
620 const UtteranceContinuousParameters& params,
621 base::OnceCallback<void(bool)> on_speak_finished,
622 const std::string& parsed_utterance) {
623 DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
624
625 worker_.Post(FROM_HERE, &TtsPlatformImplBackgroundWorker::ProcessSpeech,
626 utterance_id, lang, voice, params, std::move(on_speak_finished),
627 parsed_utterance);
628 }
629
TtsPlatformImplWin()630 TtsPlatformImplWin::TtsPlatformImplWin()
631 : worker_task_runner_(
632 base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})),
633 worker_(worker_task_runner_, worker_task_runner_) {
634 DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
635 worker_.Post(FROM_HERE, &TtsPlatformImplBackgroundWorker::Initialize);
636 }
637
638 // static
GetInstance()639 TtsPlatformImplWin* TtsPlatformImplWin::GetInstance() {
640 static base::NoDestructor<TtsPlatformImplWin> tts_platform;
641 return tts_platform.get();
642 }
643
644 } // namespace
645
646 // static
GetInstance()647 TtsPlatformImpl* TtsPlatformImpl::GetInstance() {
648 return TtsPlatformImplWin::GetInstance();
649 }
650
651 } // namespace content
652