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 34 std::string ExtensionTtsPlatformImpl::error() { 35 return error_; 36 } 37 38 void ExtensionTtsPlatformImpl::clear_error() { 39 error_ = std::string(); 40 } 41 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 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 101 Utterance::~Utterance() { 102 DCHECK_EQ(completion_task_, static_cast<Task *>(NULL)); 103 } 104 105 void Utterance::FinishAndDestroy() { 106 completion_task_->Run(); 107 completion_task_ = NULL; 108 delete this; 109 } 110 111 // 112 // ExtensionTtsController 113 // 114 115 // static 116 ExtensionTtsController* ExtensionTtsController::GetInstance() { 117 return Singleton<ExtensionTtsController>::get(); 118 } 119 120 ExtensionTtsController::ExtensionTtsController() 121 : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), 122 current_utterance_(NULL), 123 platform_impl_(NULL) { 124 } 125 126 ExtensionTtsController::~ExtensionTtsController() { 127 FinishCurrentUtterance(); 128 ClearUtteranceQueue(); 129 } 130 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 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 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 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 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 274 bool ExtensionTtsController::IsSpeaking() const { 275 return current_utterance_ != NULL; 276 } 277 278 void ExtensionTtsController::FinishCurrentUtterance() { 279 if (current_utterance_) { 280 current_utterance_->FinishAndDestroy(); 281 current_utterance_ = NULL; 282 } 283 } 284 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 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 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 328 void ExtensionTtsController::SetPlatformImpl( 329 ExtensionTtsPlatformImpl* platform_impl) { 330 platform_impl_ = platform_impl; 331 } 332 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 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 358 void ExtensionTtsSpeakFunction::SpeechFinished() { 359 error_ = utterance_->error(); 360 bool success = error_.empty(); 361 SendResponse(success); 362 Release(); // Balanced in RunImpl(). 363 } 364 365 bool ExtensionTtsStopSpeakingFunction::RunImpl() { 366 ExtensionTtsController::GetInstance()->Stop(); 367 return true; 368 } 369 370 bool ExtensionTtsIsSpeakingFunction::RunImpl() { 371 result_.reset(Value::CreateBooleanValue( 372 ExtensionTtsController::GetInstance()->IsSpeaking())); 373 return true; 374 } 375 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