Home | History | Annotate | Download | only in extensions
      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