extension_tts_api.cc revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
1// Copyright (c) 2010 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
17namespace util = extension_tts_api_util;
18
19namespace {
20const char kSpeechInterruptedError[] = "Utterance interrupted.";
21const char kSpeechRemovedFromQueueError[] = "Utterance removed from queue.";
22const int kSpeechCheckDelayIntervalMs = 100;
23};
24
25namespace events {
26const char kOnSpeak[] = "experimental.tts.onSpeak";
27const char kOnStop[] = "experimental.tts.onStop";
28};  // namespace events
29
30//
31// ExtensionTtsPlatformImpl
32//
33
34std::string ExtensionTtsPlatformImpl::error() {
35  return error_;
36}
37
38void ExtensionTtsPlatformImpl::clear_error() {
39  error_ = std::string();
40}
41
42void ExtensionTtsPlatformImpl::set_error(const std::string& error) {
43  error_ = error;
44}
45
46//
47// Utterance
48//
49
50// static
51int Utterance::next_utterance_id_ = 0;
52
53Utterance::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
101Utterance::~Utterance() {
102  DCHECK_EQ(completion_task_, static_cast<Task *>(NULL));
103}
104
105void Utterance::FinishAndDestroy() {
106  completion_task_->Run();
107  completion_task_ = NULL;
108  delete this;
109}
110
111//
112// ExtensionTtsController
113//
114
115// static
116ExtensionTtsController* ExtensionTtsController::GetInstance() {
117  return Singleton<ExtensionTtsController>::get();
118}
119
120ExtensionTtsController::ExtensionTtsController()
121    : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
122      current_utterance_(NULL),
123      platform_impl_(NULL) {
124}
125
126ExtensionTtsController::~ExtensionTtsController() {
127  FinishCurrentUtterance();
128  ClearUtteranceQueue();
129}
130
131void 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
140std::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
187void 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
239void 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
259void ExtensionTtsController::OnSpeechFinished(
260    int request_id, 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
274bool ExtensionTtsController::IsSpeaking() const {
275  return current_utterance_ != NULL;
276}
277
278void ExtensionTtsController::FinishCurrentUtterance() {
279  if (current_utterance_) {
280    current_utterance_->FinishAndDestroy();
281    current_utterance_ = NULL;
282  }
283}
284
285void 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
295void 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
304void 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
328void ExtensionTtsController::SetPlatformImpl(
329    ExtensionTtsPlatformImpl* platform_impl) {
330  platform_impl_ = platform_impl;
331}
332
333ExtensionTtsPlatformImpl* ExtensionTtsController::GetPlatformImpl() {
334  if (!platform_impl_)
335    platform_impl_ = ExtensionTtsPlatformImpl::GetInstance();
336  return platform_impl_;
337}
338
339//
340// Extension API functions
341//
342
343bool 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
358void ExtensionTtsSpeakFunction::SpeechFinished() {
359  error_ = utterance_->error();
360  bool success = error_.empty();
361  SendResponse(success);
362  Release();  // Balanced in RunImpl().
363}
364
365bool ExtensionTtsStopSpeakingFunction::RunImpl() {
366  ExtensionTtsController::GetInstance()->Stop();
367  return true;
368}
369
370bool ExtensionTtsIsSpeakingFunction::RunImpl() {
371  result_.reset(Value::CreateBooleanValue(
372      ExtensionTtsController::GetInstance()->IsSpeaking()));
373  return true;
374}
375
376bool 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