• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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