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