1 // Copyright (c) 2011 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 <string>
6 #include <vector>
7
8 #include "base/float_util.h"
9 #include "base/json/json_writer.h"
10 #include "base/message_loop.h"
11 #include "base/values.h"
12 #include "chrome/browser/extensions/extension_event_router.h"
13 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/extensions/extension_tts_api.h"
15 #include "chrome/browser/profiles/profile.h"
16
17 namespace util = extension_tts_api_util;
18
19 namespace {
20 const char kSpeechInterruptedError[] = "Utterance interrupted.";
21 const char kSpeechRemovedFromQueueError[] = "Utterance removed from queue.";
22 const int kSpeechCheckDelayIntervalMs = 100;
23 };
24
25 namespace events {
26 const char kOnSpeak[] = "experimental.tts.onSpeak";
27 const char kOnStop[] = "experimental.tts.onStop";
28 }; // namespace events
29
30 //
31 // ExtensionTtsPlatformImpl
32 //
33
error()34 std::string ExtensionTtsPlatformImpl::error() {
35 return error_;
36 }
37
clear_error()38 void ExtensionTtsPlatformImpl::clear_error() {
39 error_ = std::string();
40 }
41
set_error(const std::string & error)42 void ExtensionTtsPlatformImpl::set_error(const std::string& error) {
43 error_ = error;
44 }
45
46 //
47 // Utterance
48 //
49
50 // static
51 int Utterance::next_utterance_id_ = 0;
52
Utterance(Profile * profile,const std::string & text,DictionaryValue * options,Task * completion_task)53 Utterance::Utterance(Profile* profile,
54 const std::string& text,
55 DictionaryValue* options,
56 Task* completion_task)
57 : profile_(profile),
58 id_(next_utterance_id_++),
59 text_(text),
60 rate_(-1.0),
61 pitch_(-1.0),
62 volume_(-1.0),
63 can_enqueue_(false),
64 completion_task_(completion_task) {
65 if (!options) {
66 // Use all default options.
67 options_.reset(new DictionaryValue());
68 return;
69 }
70
71 options_.reset(options->DeepCopy());
72
73 if (options->HasKey(util::kVoiceNameKey))
74 options->GetString(util::kVoiceNameKey, &voice_name_);
75
76 if (options->HasKey(util::kLocaleKey))
77 options->GetString(util::kLocaleKey, &locale_);
78
79 if (options->HasKey(util::kGenderKey))
80 options->GetString(util::kGenderKey, &gender_);
81
82 if (util::ReadNumberByKey(options, util::kRateKey, &rate_)) {
83 if (!base::IsFinite(rate_) || rate_ < 0.0 || rate_ > 1.0)
84 rate_ = -1.0;
85 }
86
87 if (util::ReadNumberByKey(options, util::kPitchKey, &pitch_)) {
88 if (!base::IsFinite(pitch_) || pitch_ < 0.0 || pitch_ > 1.0)
89 pitch_ = -1.0;
90 }
91
92 if (util::ReadNumberByKey(options, util::kVolumeKey, &volume_)) {
93 if (!base::IsFinite(volume_) || volume_ < 0.0 || volume_ > 1.0)
94 volume_ = -1.0;
95 }
96
97 if (options->HasKey(util::kEnqueueKey))
98 options->GetBoolean(util::kEnqueueKey, &can_enqueue_);
99 }
100
~Utterance()101 Utterance::~Utterance() {
102 DCHECK_EQ(completion_task_, static_cast<Task *>(NULL));
103 }
104
FinishAndDestroy()105 void Utterance::FinishAndDestroy() {
106 completion_task_->Run();
107 completion_task_ = NULL;
108 delete this;
109 }
110
111 //
112 // ExtensionTtsController
113 //
114
115 // static
GetInstance()116 ExtensionTtsController* ExtensionTtsController::GetInstance() {
117 return Singleton<ExtensionTtsController>::get();
118 }
119
ExtensionTtsController()120 ExtensionTtsController::ExtensionTtsController()
121 : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
122 current_utterance_(NULL),
123 platform_impl_(NULL) {
124 }
125
~ExtensionTtsController()126 ExtensionTtsController::~ExtensionTtsController() {
127 FinishCurrentUtterance();
128 ClearUtteranceQueue();
129 }
130
SpeakOrEnqueue(Utterance * utterance)131 void ExtensionTtsController::SpeakOrEnqueue(Utterance* utterance) {
132 if (IsSpeaking() && utterance->can_enqueue()) {
133 utterance_queue_.push(utterance);
134 } else {
135 Stop();
136 SpeakNow(utterance);
137 }
138 }
139
GetMatchingExtensionId(Utterance * utterance)140 std::string ExtensionTtsController::GetMatchingExtensionId(
141 Utterance* utterance) {
142 ExtensionService* service = utterance->profile()->GetExtensionService();
143 DCHECK(service);
144 ExtensionEventRouter* event_router =
145 utterance->profile()->GetExtensionEventRouter();
146 DCHECK(event_router);
147
148 const ExtensionList* extensions = service->extensions();
149 ExtensionList::const_iterator iter;
150 for (iter = extensions->begin(); iter != extensions->end(); ++iter) {
151 const Extension* extension = *iter;
152
153 if (!event_router->ExtensionHasEventListener(
154 extension->id(), events::kOnSpeak) ||
155 !event_router->ExtensionHasEventListener(
156 extension->id(), events::kOnStop)) {
157 continue;
158 }
159
160 const std::vector<Extension::TtsVoice>& tts_voices =
161 extension->tts_voices();
162 for (size_t i = 0; i < tts_voices.size(); ++i) {
163 const Extension::TtsVoice& voice = tts_voices[i];
164 if (!voice.voice_name.empty() &&
165 !utterance->voice_name().empty() &&
166 voice.voice_name != utterance->voice_name()) {
167 continue;
168 }
169 if (!voice.locale.empty() &&
170 !utterance->locale().empty() &&
171 voice.locale != utterance->locale()) {
172 continue;
173 }
174 if (!voice.gender.empty() &&
175 !utterance->gender().empty() &&
176 voice.gender != utterance->gender()) {
177 continue;
178 }
179
180 return extension->id();
181 }
182 }
183
184 return std::string();
185 }
186
SpeakNow(Utterance * utterance)187 void ExtensionTtsController::SpeakNow(Utterance* utterance) {
188 std::string extension_id = GetMatchingExtensionId(utterance);
189 if (!extension_id.empty()) {
190 current_utterance_ = utterance;
191 utterance->set_extension_id(extension_id);
192
193 ListValue args;
194 args.Set(0, Value::CreateStringValue(utterance->text()));
195
196 // Pass through all options to the speech engine, except for
197 // "enqueue", which the speech engine doesn't need to handle.
198 DictionaryValue* options = static_cast<DictionaryValue*>(
199 utterance->options()->DeepCopy());
200 if (options->HasKey(util::kEnqueueKey))
201 options->Remove(util::kEnqueueKey, NULL);
202
203 args.Set(1, options);
204 args.Set(2, Value::CreateIntegerValue(utterance->id()));
205 std::string json_args;
206 base::JSONWriter::Write(&args, false, &json_args);
207
208 utterance->profile()->GetExtensionEventRouter()->DispatchEventToExtension(
209 extension_id,
210 events::kOnSpeak,
211 json_args,
212 utterance->profile(),
213 GURL());
214
215 return;
216 }
217
218 GetPlatformImpl()->clear_error();
219 bool success = GetPlatformImpl()->Speak(
220 utterance->text(),
221 utterance->locale(),
222 utterance->gender(),
223 utterance->rate(),
224 utterance->pitch(),
225 utterance->volume());
226 if (!success) {
227 utterance->set_error(GetPlatformImpl()->error());
228 utterance->FinishAndDestroy();
229 return;
230 }
231 current_utterance_ = utterance;
232
233 // Check to see if it's still speaking; finish the utterance if not and
234 // start polling if so. Checking immediately helps to avoid flaky unit
235 // tests by forcing them to set expectations for IsSpeaking.
236 CheckSpeechStatus();
237 }
238
Stop()239 void ExtensionTtsController::Stop() {
240 if (current_utterance_ && !current_utterance_->extension_id().empty()) {
241 current_utterance_->profile()->GetExtensionEventRouter()->
242 DispatchEventToExtension(
243 current_utterance_->extension_id(),
244 events::kOnStop,
245 "[]",
246 current_utterance_->profile(),
247 GURL());
248 } else {
249 GetPlatformImpl()->clear_error();
250 GetPlatformImpl()->StopSpeaking();
251 }
252
253 if (current_utterance_)
254 current_utterance_->set_error(kSpeechInterruptedError);
255 FinishCurrentUtterance();
256 ClearUtteranceQueue();
257 }
258
OnSpeechFinished(int request_id,const std::string & error_message)259 void ExtensionTtsController::OnSpeechFinished(
260 int request_id, const std::string& error_message) {
261 // We may sometimes receive completion callbacks "late", after we've
262 // already finished the utterance (for example because another utterance
263 // interrupted or we got a call to Stop). It's also possible that a buggy
264 // extension has called this more than once. In either case it's safe to
265 // just ignore this call.
266 if (!current_utterance_ || request_id != current_utterance_->id())
267 return;
268
269 current_utterance_->set_error(error_message);
270 FinishCurrentUtterance();
271 SpeakNextUtterance();
272 }
273
IsSpeaking() const274 bool ExtensionTtsController::IsSpeaking() const {
275 return current_utterance_ != NULL;
276 }
277
FinishCurrentUtterance()278 void ExtensionTtsController::FinishCurrentUtterance() {
279 if (current_utterance_) {
280 current_utterance_->FinishAndDestroy();
281 current_utterance_ = NULL;
282 }
283 }
284
SpeakNextUtterance()285 void ExtensionTtsController::SpeakNextUtterance() {
286 // Start speaking the next utterance in the queue. Keep trying in case
287 // one fails but there are still more in the queue to try.
288 while (!utterance_queue_.empty() && !current_utterance_) {
289 Utterance* utterance = utterance_queue_.front();
290 utterance_queue_.pop();
291 SpeakNow(utterance);
292 }
293 }
294
ClearUtteranceQueue()295 void ExtensionTtsController::ClearUtteranceQueue() {
296 while (!utterance_queue_.empty()) {
297 Utterance* utterance = utterance_queue_.front();
298 utterance_queue_.pop();
299 utterance->set_error(kSpeechRemovedFromQueueError);
300 utterance->FinishAndDestroy();
301 }
302 }
303
CheckSpeechStatus()304 void ExtensionTtsController::CheckSpeechStatus() {
305 if (!current_utterance_)
306 return;
307
308 if (!current_utterance_->extension_id().empty())
309 return;
310
311 if (GetPlatformImpl()->IsSpeaking() == false) {
312 FinishCurrentUtterance();
313 SpeakNextUtterance();
314 }
315
316 // If we're still speaking something (either the prevoius utterance or
317 // a new utterance), keep calling this method after another delay.
318 // TODO(dmazzoni): get rid of this as soon as all platform implementations
319 // provide completion callbacks rather than only supporting polling.
320 if (current_utterance_ && current_utterance_->extension_id().empty()) {
321 MessageLoop::current()->PostDelayedTask(
322 FROM_HERE, method_factory_.NewRunnableMethod(
323 &ExtensionTtsController::CheckSpeechStatus),
324 kSpeechCheckDelayIntervalMs);
325 }
326 }
327
SetPlatformImpl(ExtensionTtsPlatformImpl * platform_impl)328 void ExtensionTtsController::SetPlatformImpl(
329 ExtensionTtsPlatformImpl* platform_impl) {
330 platform_impl_ = platform_impl;
331 }
332
GetPlatformImpl()333 ExtensionTtsPlatformImpl* ExtensionTtsController::GetPlatformImpl() {
334 if (!platform_impl_)
335 platform_impl_ = ExtensionTtsPlatformImpl::GetInstance();
336 return platform_impl_;
337 }
338
339 //
340 // Extension API functions
341 //
342
RunImpl()343 bool ExtensionTtsSpeakFunction::RunImpl() {
344 std::string text;
345 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &text));
346 DictionaryValue* options = NULL;
347 if (args_->GetSize() >= 2)
348 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &options));
349 Task* completion_task = NewRunnableMethod(
350 this, &ExtensionTtsSpeakFunction::SpeechFinished);
351 utterance_ = new Utterance(profile(), text, options, completion_task);
352
353 AddRef(); // Balanced in SpeechFinished().
354 ExtensionTtsController::GetInstance()->SpeakOrEnqueue(utterance_);
355 return true;
356 }
357
SpeechFinished()358 void ExtensionTtsSpeakFunction::SpeechFinished() {
359 error_ = utterance_->error();
360 bool success = error_.empty();
361 SendResponse(success);
362 Release(); // Balanced in RunImpl().
363 }
364
RunImpl()365 bool ExtensionTtsStopSpeakingFunction::RunImpl() {
366 ExtensionTtsController::GetInstance()->Stop();
367 return true;
368 }
369
RunImpl()370 bool ExtensionTtsIsSpeakingFunction::RunImpl() {
371 result_.reset(Value::CreateBooleanValue(
372 ExtensionTtsController::GetInstance()->IsSpeaking()));
373 return true;
374 }
375
RunImpl()376 bool ExtensionTtsSpeakCompletedFunction::RunImpl() {
377 int request_id;
378 std::string error_message;
379 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &request_id));
380 if (args_->GetSize() >= 2)
381 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &error_message));
382 ExtensionTtsController::GetInstance()->OnSpeechFinished(
383 request_id, error_message);
384
385 return true;
386 }
387