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