1 // Copyright 2019 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 "content/browser/speech/speech_synthesis_impl.h"
6
7 namespace content {
8 namespace {
9
10 // The lifetime of instances of this class is manually bound to the lifetime of
11 // the associated TtsUtterance. See OnTtsEvent.
12 class EventThunk : public UtteranceEventDelegate {
13 public:
EventThunk(mojo::PendingRemote<blink::mojom::SpeechSynthesisClient> client)14 explicit EventThunk(
15 mojo::PendingRemote<blink::mojom::SpeechSynthesisClient> client)
16 : client_(std::move(client)) {}
17 ~EventThunk() override = default;
18
19 // UtteranceEventDelegate methods:
OnTtsEvent(TtsUtterance * utterance,TtsEventType event_type,int char_index,int char_length,const std::string & error_message)20 void OnTtsEvent(TtsUtterance* utterance,
21 TtsEventType event_type,
22 int char_index,
23 int char_length,
24 const std::string& error_message) override {
25 // These values are unsigned in the web speech API, so -1 cannot be used as
26 // a sentinel value. Use 0 instead to match web standards.
27 char_index = std::max(char_index, 0);
28 char_length = std::max(char_length, 0);
29
30 switch (event_type) {
31 case TTS_EVENT_START:
32 client_->OnStartedSpeaking();
33 break;
34 case TTS_EVENT_END:
35 case TTS_EVENT_INTERRUPTED:
36 case TTS_EVENT_CANCELLED:
37 // The web platform API does not differentiate these events.
38 client_->OnFinishedSpeaking();
39 break;
40 case TTS_EVENT_WORD:
41 client_->OnEncounteredWordBoundary(char_index, char_length);
42 break;
43 case TTS_EVENT_SENTENCE:
44 client_->OnEncounteredSentenceBoundary(char_index, 0);
45 break;
46 case TTS_EVENT_MARKER:
47 // The web platform API does not support this event.
48 break;
49 case TTS_EVENT_ERROR:
50 // The web platform API does not support error text.
51 client_->OnEncounteredSpeakingError();
52 break;
53 case TTS_EVENT_PAUSE:
54 client_->OnPausedSpeaking();
55 break;
56 case TTS_EVENT_RESUME:
57 client_->OnResumedSpeaking();
58 break;
59 }
60
61 if (utterance->IsFinished())
62 delete this;
63 }
64
65 private:
66 mojo::Remote<blink::mojom::SpeechSynthesisClient> client_;
67 };
68
SendVoiceListToObserver(blink::mojom::SpeechSynthesisVoiceListObserver * observer,const std::vector<VoiceData> & voices)69 void SendVoiceListToObserver(
70 blink::mojom::SpeechSynthesisVoiceListObserver* observer,
71 const std::vector<VoiceData>& voices) {
72 std::vector<blink::mojom::SpeechSynthesisVoicePtr> out_voices;
73 out_voices.resize(voices.size());
74 for (size_t i = 0; i < voices.size(); ++i) {
75 blink::mojom::SpeechSynthesisVoicePtr& out_voice = out_voices[i];
76 out_voice = blink::mojom::SpeechSynthesisVoice::New();
77 out_voice->voice_uri = voices[i].name;
78 out_voice->name = voices[i].name;
79 out_voice->lang = voices[i].lang;
80 out_voice->is_local_service = !voices[i].remote;
81 out_voice->is_default = (i == 0);
82 }
83 observer->OnSetVoiceList(std::move(out_voices));
84 }
85
86 } // namespace
87
SpeechSynthesisImpl(BrowserContext * browser_context)88 SpeechSynthesisImpl::SpeechSynthesisImpl(BrowserContext* browser_context)
89 : browser_context_(browser_context) {
90 DCHECK(browser_context_);
91 TtsController::GetInstance()->AddVoicesChangedDelegate(this);
92 }
93
~SpeechSynthesisImpl()94 SpeechSynthesisImpl::~SpeechSynthesisImpl() {
95 TtsController::GetInstance()->RemoveVoicesChangedDelegate(this);
96
97 // NOTE: Some EventThunk instances may outlive this class, and that's okay.
98 // They have their lifetime bound to their associated TtsUtterance instance,
99 // and the TtsController manages the lifetime of those.
100 }
101
AddReceiver(mojo::PendingReceiver<blink::mojom::SpeechSynthesis> receiver)102 void SpeechSynthesisImpl::AddReceiver(
103 mojo::PendingReceiver<blink::mojom::SpeechSynthesis> receiver) {
104 receiver_set_.Add(this, std::move(receiver));
105 }
106
AddVoiceListObserver(mojo::PendingRemote<blink::mojom::SpeechSynthesisVoiceListObserver> pending_observer)107 void SpeechSynthesisImpl::AddVoiceListObserver(
108 mojo::PendingRemote<blink::mojom::SpeechSynthesisVoiceListObserver>
109 pending_observer) {
110 mojo::Remote<blink::mojom::SpeechSynthesisVoiceListObserver> observer(
111 std::move(pending_observer));
112
113 std::vector<VoiceData> voices;
114 TtsController::GetInstance()->GetVoices(browser_context_, &voices);
115 SendVoiceListToObserver(observer.get(), voices);
116
117 observer_set_.Add(std::move(observer));
118 }
119
Speak(blink::mojom::SpeechSynthesisUtterancePtr utterance,mojo::PendingRemote<blink::mojom::SpeechSynthesisClient> client)120 void SpeechSynthesisImpl::Speak(
121 blink::mojom::SpeechSynthesisUtterancePtr utterance,
122 mojo::PendingRemote<blink::mojom::SpeechSynthesisClient> client) {
123 std::unique_ptr<TtsUtterance> tts_utterance(
124 TtsUtterance::Create((browser_context_)));
125 tts_utterance->SetText(utterance->text);
126 tts_utterance->SetLang(utterance->lang);
127 tts_utterance->SetVoiceName(utterance->voice);
128 tts_utterance->SetCanEnqueue(true);
129 tts_utterance->SetContinuousParameters(utterance->rate, utterance->pitch,
130 utterance->volume);
131
132 // See comments on EventThunk about how lifetime of this instance is managed.
133 tts_utterance->SetEventDelegate(new EventThunk(std::move(client)));
134
135 TtsController::GetInstance()->SpeakOrEnqueue(std::move(tts_utterance));
136 }
137
Pause()138 void SpeechSynthesisImpl::Pause() {
139 TtsController::GetInstance()->Pause();
140 }
141
Resume()142 void SpeechSynthesisImpl::Resume() {
143 TtsController::GetInstance()->Resume();
144 }
145
Cancel()146 void SpeechSynthesisImpl::Cancel() {
147 TtsController::GetInstance()->Stop();
148 }
149
OnVoicesChanged()150 void SpeechSynthesisImpl::OnVoicesChanged() {
151 std::vector<VoiceData> voices;
152 TtsController::GetInstance()->GetVoices(browser_context_, &voices);
153 for (auto& observer : observer_set_)
154 SendVoiceListToObserver(observer.get(), voices);
155 }
156
157 } // namespace content
158