summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-07 05:25:00 +0000
committerdmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-07 05:25:00 +0000
commitc63f2b7fe4fe2977f858a8e36d5f48db17eff2e7 (patch)
tree3d04e487b74ab61f09d2454850104d8e3b125996
parent3768ee10e91e95f4ff90bbe8177ffa407725d955 (diff)
downloadchromium_src-c63f2b7fe4fe2977f858a8e36d5f48db17eff2e7.zip
chromium_src-c63f2b7fe4fe2977f858a8e36d5f48db17eff2e7.tar.gz
chromium_src-c63f2b7fe4fe2977f858a8e36d5f48db17eff2e7.tar.bz2
Extend TTS extension API to support richer events returned from the engine
to the client. Previously we just had a completed event; this adds start, word boundary, sentence boundary, and marker boundary. In addition, interrupted and canceled, which were previously errors, now become events. Mac and Windows implementations extended to support as many of these events as possible. BUG=67713 BUG=70198 BUG=75106 BUG=83404 TEST=Updates all TTS API tests to be event-based, and adds new tests. Review URL: http://codereview.chromium.org/6792014 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@91665 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--base/mac/cocoa_protocols.h3
-rw-r--r--chrome/browser/chromeos/cros/cros_mock.cc10
-rw-r--r--chrome/browser/extensions/extension_function_dispatcher.cc6
-rw-r--r--chrome/browser/extensions/extension_tts_api.cc456
-rw-r--r--chrome/browser/extensions/extension_tts_api.h214
-rw-r--r--chrome/browser/extensions/extension_tts_api_chromeos.cc114
-rw-r--r--chrome/browser/extensions/extension_tts_api_constants.cc49
-rw-r--r--chrome/browser/extensions/extension_tts_api_constants.h54
-rw-r--r--chrome/browser/extensions/extension_tts_api_controller.cc312
-rw-r--r--chrome/browser/extensions/extension_tts_api_controller.h256
-rw-r--r--chrome/browser/extensions/extension_tts_api_linux.cc21
-rw-r--r--chrome/browser/extensions/extension_tts_api_mac.mm144
-rw-r--r--chrome/browser/extensions/extension_tts_api_platform.cc19
-rw-r--r--chrome/browser/extensions/extension_tts_api_platform.h59
-rw-r--r--chrome/browser/extensions/extension_tts_api_util.cc17
-rw-r--r--chrome/browser/extensions/extension_tts_api_util.h23
-rw-r--r--chrome/browser/extensions/extension_tts_api_win.cc149
-rw-r--r--chrome/browser/extensions/extension_tts_apitest.cc263
-rw-r--r--chrome/browser/extensions/extension_tts_engine_api.cc260
-rw-r--r--chrome/browser/extensions/extension_tts_engine_api.h45
-rw-r--r--chrome/browser/extensions/extensions_quota_service_unittest.cc3
-rw-r--r--chrome/chrome_browser.gypi10
-rw-r--r--chrome/common/extensions/api/extension_api.json194
-rw-r--r--chrome/common/extensions/docs/experimental.html1
-rw-r--r--chrome/common/extensions/docs/experimental.tts.html1285
-rw-r--r--chrome/common/extensions/docs/experimental.ttsEngine.html1468
-rw-r--r--chrome/common/extensions/docs/static/experimental.tts.html233
-rw-r--r--chrome/common/extensions/docs/static/experimental.ttsEngine.html152
-rw-r--r--chrome/common/extensions/extension.cc42
-rw-r--r--chrome/common/extensions/extension.h3
-rw-r--r--chrome/common/extensions/extension_constants.cc27
-rw-r--r--chrome/common/extensions/extension_constants.h14
-rw-r--r--chrome/common/extensions/extension_manifests_unittest.cc29
-rw-r--r--chrome/renderer/extensions/extension_process_bindings.cc9
-rw-r--r--chrome/renderer/resources/extension_process_bindings.js52
-rw-r--r--chrome/renderer/resources/renderer_extension_bindings.js1
-rw-r--r--chrome/test/data/extensions/api_test/tts/chromeos/test.js42
-rw-r--r--chrome/test/data/extensions/api_test/tts/enqueue/test.js48
-rw-r--r--chrome/test/data/extensions/api_test/tts/interrupt/test.js47
-rw-r--r--chrome/test/data/extensions/api_test/tts/provide/test.js113
-rw-r--r--chrome/test/data/extensions/api_test/tts/queue_interrupt/test.js66
-rw-r--r--chrome/test/data/extensions/api_test/tts/speak_error/test.js51
-rw-r--r--chrome/test/data/extensions/api_test/tts/speak_once/test.js20
-rw-r--r--chrome/test/data/extensions/api_test/tts/word_callbacks/manifest.json7
-rw-r--r--chrome/test/data/extensions/api_test/tts/word_callbacks/test.html (renamed from chrome/test/data/extensions/api_test/tts/provide/test.html)0
-rw-r--r--chrome/test/data/extensions/api_test/tts/word_callbacks/test.js40
-rw-r--r--chrome/test/data/extensions/api_test/tts_engine/engine_error/manifest.json (renamed from chrome/test/data/extensions/api_test/tts/provide/manifest.json)12
-rw-r--r--chrome/test/data/extensions/api_test/tts_engine/engine_error/test.html1
-rw-r--r--chrome/test/data/extensions/api_test/tts_engine/engine_error/test.js62
-rw-r--r--chrome/test/data/extensions/api_test/tts_engine/engine_word_callbacks/manifest.json16
-rw-r--r--chrome/test/data/extensions/api_test/tts_engine/engine_word_callbacks/test.html1
-rw-r--r--chrome/test/data/extensions/api_test/tts_engine/engine_word_callbacks/test.js66
-rw-r--r--chrome/test/data/extensions/api_test/tts_engine/register_engine/manifest.json22
-rw-r--r--chrome/test/data/extensions/api_test/tts_engine/register_engine/test.html1
-rw-r--r--chrome/test/data/extensions/api_test/tts_engine/register_engine/test.js170
-rw-r--r--chrome/test/data/extensions/manifest_tests/tts_engine_invalid_1.json (renamed from chrome/test/data/extensions/manifest_tests/tts_provider_invalid_1.json)2
-rw-r--r--chrome/test/data/extensions/manifest_tests/tts_engine_invalid_2.json (renamed from chrome/test/data/extensions/manifest_tests/tts_provider_invalid_2.json)2
-rw-r--r--chrome/test/data/extensions/manifest_tests/tts_engine_invalid_3.json (renamed from chrome/test/data/extensions/manifest_tests/tts_provider_invalid_3.json)2
-rw-r--r--chrome/test/data/extensions/manifest_tests/tts_engine_invalid_4.json11
-rw-r--r--chrome/test/data/extensions/manifest_tests/tts_engine_invalid_5.json (renamed from chrome/test/data/extensions/manifest_tests/tts_provider_invalid_5.json)4
-rw-r--r--chrome/test/data/extensions/manifest_tests/tts_engine_invalid_6.json (renamed from chrome/test/data/extensions/manifest_tests/tts_provider_invalid_6.json)4
-rw-r--r--chrome/test/data/extensions/manifest_tests/tts_engine_invalid_7.json (renamed from chrome/test/data/extensions/manifest_tests/tts_provider_invalid_7.json)2
-rw-r--r--chrome/test/data/extensions/manifest_tests/tts_engine_invalid_8.json (renamed from chrome/test/data/extensions/manifest_tests/tts_provider_invalid_4.json)4
-rw-r--r--chrome/test/data/extensions/manifest_tests/tts_engine_invalid_9.json11
-rw-r--r--chrome/test/data/extensions/manifest_tests/tts_engine_valid.json (renamed from chrome/test/data/extensions/manifest_tests/tts_provider_valid.json)7
65 files changed, 5308 insertions, 1523 deletions
diff --git a/base/mac/cocoa_protocols.h b/base/mac/cocoa_protocols.h
index 9482d51..0269c58 100644
--- a/base/mac/cocoa_protocols.h
+++ b/base/mac/cocoa_protocols.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -34,6 +34,7 @@ DEFINE_EMPTY_PROTOCOL(NSMenuDelegate)
DEFINE_EMPTY_PROTOCOL(NSOpenSavePanelDelegate)
DEFINE_EMPTY_PROTOCOL(NSOutlineViewDataSource)
DEFINE_EMPTY_PROTOCOL(NSOutlineViewDelegate)
+DEFINE_EMPTY_PROTOCOL(NSSpeechSynthesizerDelegate)
DEFINE_EMPTY_PROTOCOL(NSTableViewDataSource)
DEFINE_EMPTY_PROTOCOL(NSTableViewDelegate)
DEFINE_EMPTY_PROTOCOL(NSTextFieldDelegate)
diff --git a/chrome/browser/chromeos/cros/cros_mock.cc b/chrome/browser/chromeos/cros/cros_mock.cc
index 49f79da..5baa999 100644
--- a/chrome/browser/chromeos/cros/cros_mock.cc
+++ b/chrome/browser/chromeos/cros/cros_mock.cc
@@ -287,15 +287,21 @@ void CrosMock::SetSpeechSynthesisLibraryExpectations() {
EXPECT_CALL(*mock_speech_synthesis_library_, StopSpeaking())
.WillOnce(Return(true))
.RetiresOnSaturation();
+ EXPECT_CALL(*mock_speech_synthesis_library_, SetSpeakProperties(_))
+ .WillOnce(Return(true))
+ .RetiresOnSaturation();
EXPECT_CALL(*mock_speech_synthesis_library_, Speak(_))
.WillOnce(Return(true))
.RetiresOnSaturation();
EXPECT_CALL(*mock_speech_synthesis_library_, IsSpeaking())
- .Times(AnyNumber())
- .WillRepeatedly(Return(true));
+ .WillOnce(Return(true))
+ .RetiresOnSaturation();
EXPECT_CALL(*mock_speech_synthesis_library_, StopSpeaking())
.WillOnce(Return(true))
.RetiresOnSaturation();
+ EXPECT_CALL(*mock_speech_synthesis_library_, SetSpeakProperties(_))
+ .WillOnce(Return(true))
+ .RetiresOnSaturation();
EXPECT_CALL(*mock_speech_synthesis_library_, Speak(_))
.WillOnce(Return(true))
.RetiresOnSaturation();
diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc
index 7ad884a..15fa40e 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.cc
+++ b/chrome/browser/extensions/extension_function_dispatcher.cc
@@ -40,6 +40,7 @@
#include "chrome/browser/extensions/extension_tabs_module.h"
#include "chrome/browser/extensions/extension_test_api.h"
#include "chrome/browser/extensions/extension_tts_api.h"
+#include "chrome/browser/extensions/extension_tts_engine_api.h"
#include "chrome/browser/extensions/extension_web_socket_proxy_private_api.h"
#include "chrome/browser/extensions/extension_web_ui.h"
#include "chrome/browser/extensions/extension_webrequest_api.h"
@@ -249,10 +250,11 @@ void FactoryRegistry::ResetFunctions() {
RegisterFunction<SetAccessibilityEnabledFunction>();
// Text-to-speech.
+ RegisterFunction<ExtensionTtsEngineSendTtsEventFunction>();
+ RegisterFunction<ExtensionTtsGetVoicesFunction>();
+ RegisterFunction<ExtensionTtsIsSpeakingFunction>();
RegisterFunction<ExtensionTtsSpeakFunction>();
RegisterFunction<ExtensionTtsStopSpeakingFunction>();
- RegisterFunction<ExtensionTtsIsSpeakingFunction>();
- RegisterFunction<ExtensionTtsSpeakCompletedFunction>();
// Context Menus.
RegisterFunction<CreateContextMenuFunction>();
diff --git a/chrome/browser/extensions/extension_tts_api.cc b/chrome/browser/extensions/extension_tts_api.cc
index 4781751..916665a 100644
--- a/chrome/browser/extensions/extension_tts_api.cc
+++ b/chrome/browser/extensions/extension_tts_api.cc
@@ -2,366 +2,159 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "chrome/browser/extensions/extension_tts_api.h"
+
#include <string>
-#include <vector>
-#include "base/float_util.h"
-#include "base/json/json_writer.h"
-#include "base/message_loop.h"
#include "base/values.h"
-#include "chrome/browser/extensions/extension_event_router.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/extension_tts_api.h"
+#include "chrome/browser/extensions/extension_tts_api_constants.h"
+#include "chrome/browser/extensions/extension_tts_api_controller.h"
#include "chrome/browser/profiles/profile.h"
+#include "ui/base/l10n/l10n_util.h"
-namespace util = extension_tts_api_util;
-
-namespace {
-const char kSpeechInterruptedError[] = "Utterance interrupted.";
-const char kSpeechRemovedFromQueueError[] = "Utterance removed from queue.";
-const int kSpeechCheckDelayIntervalMs = 100;
-};
-
-namespace events {
-const char kOnSpeak[] = "experimental.tts.onSpeak";
-const char kOnStop[] = "experimental.tts.onStop";
-}; // namespace events
-
-//
-// ExtensionTtsPlatformImpl
-//
+namespace constants = extension_tts_api_constants;
-std::string ExtensionTtsPlatformImpl::error() {
- return error_;
-}
-
-void ExtensionTtsPlatformImpl::clear_error() {
- error_ = std::string();
-}
-
-void ExtensionTtsPlatformImpl::set_error(const std::string& error) {
- error_ = error;
-}
-
-//
-// Utterance
-//
-
-// static
-int Utterance::next_utterance_id_ = 0;
-
-Utterance::Utterance(Profile* profile,
- const std::string& text,
- DictionaryValue* options,
- Task* completion_task)
- : profile_(profile),
- id_(next_utterance_id_++),
- text_(text),
- rate_(-1.0),
- pitch_(-1.0),
- volume_(-1.0),
- can_enqueue_(false),
- completion_task_(completion_task) {
- if (!options) {
- // Use all default options.
- options_.reset(new DictionaryValue());
- return;
+bool ExtensionTtsSpeakFunction::RunImpl() {
+ std::string text;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &text));
+ if (text.size() > 32768) {
+ error_ = constants::kErrorUtteranceTooLong;
+ return false;
}
- options_.reset(options->DeepCopy());
-
- if (options->HasKey(util::kVoiceNameKey))
- options->GetString(util::kVoiceNameKey, &voice_name_);
-
- if (options->HasKey(util::kLocaleKey))
- options->GetString(util::kLocaleKey, &locale_);
-
- if (options->HasKey(util::kGenderKey))
- options->GetString(util::kGenderKey, &gender_);
-
- if (options->GetDouble(util::kRateKey, &rate_)) {
- if (!base::IsFinite(rate_) || rate_ < 0.0 || rate_ > 1.0)
- rate_ = -1.0;
+ scoped_ptr<DictionaryValue> options;
+ if (args_->GetSize() >= 2) {
+ DictionaryValue* temp_options = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &temp_options));
+ options.reset(temp_options->DeepCopy());
}
- if (options->GetDouble(util::kPitchKey, &pitch_)) {
- if (!base::IsFinite(pitch_) || pitch_ < 0.0 || pitch_ > 1.0)
- pitch_ = -1.0;
+ std::string voice_name;
+ if (options->HasKey(constants::kVoiceNameKey)) {
+ EXTENSION_FUNCTION_VALIDATE(
+ options->GetString(constants::kVoiceNameKey, &voice_name));
}
- if (options->GetDouble(util::kVolumeKey, &volume_)) {
- if (!base::IsFinite(volume_) || volume_ < 0.0 || volume_ > 1.0)
- volume_ = -1.0;
+ std::string lang;
+ if (options->HasKey(constants::kLangKey))
+ EXTENSION_FUNCTION_VALIDATE(options->GetString(constants::kLangKey, &lang));
+ if (!lang.empty() && !l10n_util::IsValidLocaleSyntax(lang)) {
+ error_ = constants::kErrorInvalidLang;
+ return false;
}
- if (options->HasKey(util::kEnqueueKey))
- options->GetBoolean(util::kEnqueueKey, &can_enqueue_);
-}
-
-Utterance::~Utterance() {
- DCHECK_EQ(completion_task_, static_cast<Task *>(NULL));
-}
-
-void Utterance::FinishAndDestroy() {
- completion_task_->Run();
- completion_task_ = NULL;
- delete this;
-}
-
-//
-// ExtensionTtsController
-//
-
-// static
-ExtensionTtsController* ExtensionTtsController::GetInstance() {
- return Singleton<ExtensionTtsController>::get();
-}
-
-ExtensionTtsController::ExtensionTtsController()
- : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
- current_utterance_(NULL),
- platform_impl_(NULL) {
-}
-
-ExtensionTtsController::~ExtensionTtsController() {
- FinishCurrentUtterance();
- ClearUtteranceQueue();
-}
-
-void ExtensionTtsController::SpeakOrEnqueue(Utterance* utterance) {
- if (IsSpeaking() && utterance->can_enqueue()) {
- utterance_queue_.push(utterance);
- } else {
- Stop();
- SpeakNow(utterance);
+ std::string gender;
+ if (options->HasKey(constants::kGenderKey))
+ EXTENSION_FUNCTION_VALIDATE(
+ options->GetString(constants::kGenderKey, &gender));
+ if (!gender.empty() &&
+ gender != constants::kGenderFemale &&
+ gender != constants::kGenderMale) {
+ error_ = constants::kErrorInvalidGender;
+ return false;
}
-}
-
-std::string ExtensionTtsController::GetMatchingExtensionId(
- Utterance* utterance) {
- ExtensionService* service = utterance->profile()->GetExtensionService();
- DCHECK(service);
- ExtensionEventRouter* event_router =
- utterance->profile()->GetExtensionEventRouter();
- DCHECK(event_router);
- const ExtensionList* extensions = service->extensions();
- ExtensionList::const_iterator iter;
- for (iter = extensions->begin(); iter != extensions->end(); ++iter) {
- const Extension* extension = *iter;
-
- if (!event_router->ExtensionHasEventListener(
- extension->id(), events::kOnSpeak) ||
- !event_router->ExtensionHasEventListener(
- extension->id(), events::kOnStop)) {
- continue;
+ double rate = 1.0;
+ if (options->HasKey(constants::kRateKey)) {
+ EXTENSION_FUNCTION_VALIDATE(
+ options->GetDouble(constants::kRateKey, &rate));
+ if (rate < 0.1 || rate > 10.0) {
+ error_ = constants::kErrorInvalidRate;
+ return false;
}
-
- const std::vector<Extension::TtsVoice>& tts_voices =
- extension->tts_voices();
- for (size_t i = 0; i < tts_voices.size(); ++i) {
- const Extension::TtsVoice& voice = tts_voices[i];
- if (!voice.voice_name.empty() &&
- !utterance->voice_name().empty() &&
- voice.voice_name != utterance->voice_name()) {
- continue;
- }
- if (!voice.locale.empty() &&
- !utterance->locale().empty() &&
- voice.locale != utterance->locale()) {
- continue;
- }
- if (!voice.gender.empty() &&
- !utterance->gender().empty() &&
- voice.gender != utterance->gender()) {
- continue;
- }
-
- return extension->id();
- }
- }
-
- return std::string();
-}
-
-void ExtensionTtsController::SpeakNow(Utterance* utterance) {
- std::string extension_id = GetMatchingExtensionId(utterance);
- if (!extension_id.empty()) {
- current_utterance_ = utterance;
- utterance->set_extension_id(extension_id);
-
- ListValue args;
- args.Set(0, Value::CreateStringValue(utterance->text()));
-
- // Pass through all options to the speech engine, except for
- // "enqueue", which the speech engine doesn't need to handle.
- DictionaryValue* options = static_cast<DictionaryValue*>(
- utterance->options()->DeepCopy());
- if (options->HasKey(util::kEnqueueKey))
- options->Remove(util::kEnqueueKey, NULL);
-
- args.Set(1, options);
- args.Set(2, Value::CreateIntegerValue(utterance->id()));
- std::string json_args;
- base::JSONWriter::Write(&args, false, &json_args);
-
- utterance->profile()->GetExtensionEventRouter()->DispatchEventToExtension(
- extension_id,
- events::kOnSpeak,
- json_args,
- utterance->profile(),
- GURL());
-
- return;
}
- GetPlatformImpl()->clear_error();
- bool success = GetPlatformImpl()->Speak(
- utterance->text(),
- utterance->locale(),
- utterance->gender(),
- utterance->rate(),
- utterance->pitch(),
- utterance->volume());
- if (!success) {
- utterance->set_error(GetPlatformImpl()->error());
- utterance->FinishAndDestroy();
- return;
+ double pitch = 1.0;
+ if (options->HasKey(constants::kPitchKey)) {
+ EXTENSION_FUNCTION_VALIDATE(
+ options->GetDouble(constants::kPitchKey, &pitch));
+ if (pitch < 0.0 || pitch > 2.0) {
+ error_ = constants::kErrorInvalidPitch;
+ return false;
+ }
}
- current_utterance_ = utterance;
-
- // Check to see if it's still speaking; finish the utterance if not and
- // start polling if so. Checking immediately helps to avoid flaky unit
- // tests by forcing them to set expectations for IsSpeaking.
- CheckSpeechStatus();
-}
-void ExtensionTtsController::Stop() {
- if (current_utterance_ && !current_utterance_->extension_id().empty()) {
- current_utterance_->profile()->GetExtensionEventRouter()->
- DispatchEventToExtension(
- current_utterance_->extension_id(),
- events::kOnStop,
- "[]",
- current_utterance_->profile(),
- GURL());
- } else {
- GetPlatformImpl()->clear_error();
- GetPlatformImpl()->StopSpeaking();
+ double volume = 1.0;
+ if (options->HasKey(constants::kVolumeKey)) {
+ EXTENSION_FUNCTION_VALIDATE(
+ options->GetDouble(constants::kVolumeKey, &volume));
+ if (volume < 0.0 || volume > 1.0) {
+ error_ = constants::kErrorInvalidVolume;
+ return false;
+ }
}
- if (current_utterance_)
- current_utterance_->set_error(kSpeechInterruptedError);
- FinishCurrentUtterance();
- ClearUtteranceQueue();
-}
-
-void ExtensionTtsController::OnSpeechFinished(
- int request_id, const std::string& error_message) {
- // We may sometimes receive completion callbacks "late", after we've
- // already finished the utterance (for example because another utterance
- // interrupted or we got a call to Stop). It's also possible that a buggy
- // extension has called this more than once. In either case it's safe to
- // just ignore this call.
- if (!current_utterance_ || request_id != current_utterance_->id())
- return;
-
- current_utterance_->set_error(error_message);
- FinishCurrentUtterance();
- SpeakNextUtterance();
-}
-
-bool ExtensionTtsController::IsSpeaking() const {
- return current_utterance_ != NULL;
-}
-
-void ExtensionTtsController::FinishCurrentUtterance() {
- if (current_utterance_) {
- current_utterance_->FinishAndDestroy();
- current_utterance_ = NULL;
+ bool can_enqueue = false;
+ if (options->HasKey(constants::kEnqueueKey)) {
+ EXTENSION_FUNCTION_VALIDATE(
+ options->GetBoolean(constants::kEnqueueKey, &can_enqueue));
}
-}
-void ExtensionTtsController::SpeakNextUtterance() {
- // Start speaking the next utterance in the queue. Keep trying in case
- // one fails but there are still more in the queue to try.
- while (!utterance_queue_.empty() && !current_utterance_) {
- Utterance* utterance = utterance_queue_.front();
- utterance_queue_.pop();
- SpeakNow(utterance);
- }
-}
-
-void ExtensionTtsController::ClearUtteranceQueue() {
- while (!utterance_queue_.empty()) {
- Utterance* utterance = utterance_queue_.front();
- utterance_queue_.pop();
- utterance->set_error(kSpeechRemovedFromQueueError);
- utterance->FinishAndDestroy();
+ std::set<std::string> required_event_types;
+ if (options->HasKey(constants::kRequiredEventTypesKey)) {
+ ListValue* list;
+ EXTENSION_FUNCTION_VALIDATE(
+ options->GetList(constants::kRequiredEventTypesKey, &list));
+ for (size_t i = 0; i < list->GetSize(); i++) {
+ std::string event_type;
+ if (!list->GetString(i, &event_type))
+ required_event_types.insert(event_type);
+ }
}
-}
-
-void ExtensionTtsController::CheckSpeechStatus() {
- if (!current_utterance_)
- return;
- if (!current_utterance_->extension_id().empty())
- return;
-
- if (GetPlatformImpl()->IsSpeaking() == false) {
- FinishCurrentUtterance();
- SpeakNextUtterance();
+ std::set<std::string> desired_event_types;
+ if (options->HasKey(constants::kDesiredEventTypesKey)) {
+ ListValue* list;
+ EXTENSION_FUNCTION_VALIDATE(
+ options->GetList(constants::kDesiredEventTypesKey, &list));
+ for (size_t i = 0; i < list->GetSize(); i++) {
+ std::string event_type;
+ if (!list->GetString(i, &event_type))
+ desired_event_types.insert(event_type);
+ }
}
- // If we're still speaking something (either the prevoius utterance or
- // a new utterance), keep calling this method after another delay.
- // TODO(dmazzoni): get rid of this as soon as all platform implementations
- // provide completion callbacks rather than only supporting polling.
- if (current_utterance_ && current_utterance_->extension_id().empty()) {
- MessageLoop::current()->PostDelayedTask(
- FROM_HERE, method_factory_.NewRunnableMethod(
- &ExtensionTtsController::CheckSpeechStatus),
- kSpeechCheckDelayIntervalMs);
+ std::string voice_extension_id;
+ if (options->HasKey(constants::kExtensionIdKey)) {
+ EXTENSION_FUNCTION_VALIDATE(
+ options->GetString(constants::kExtensionIdKey, &voice_extension_id));
}
-}
-
-void ExtensionTtsController::SetPlatformImpl(
- ExtensionTtsPlatformImpl* platform_impl) {
- platform_impl_ = platform_impl;
-}
-ExtensionTtsPlatformImpl* ExtensionTtsController::GetPlatformImpl() {
- if (!platform_impl_)
- platform_impl_ = ExtensionTtsPlatformImpl::GetInstance();
- return platform_impl_;
-}
-
-//
-// Extension API functions
-//
-
-bool ExtensionTtsSpeakFunction::RunImpl() {
- std::string text;
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &text));
- DictionaryValue* options = NULL;
- if (args_->GetSize() >= 2)
- EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &options));
- Task* completion_task = NewRunnableMethod(
- this, &ExtensionTtsSpeakFunction::SpeechFinished);
- utterance_ = new Utterance(profile(), text, options, completion_task);
-
- AddRef(); // Balanced in SpeechFinished().
- ExtensionTtsController::GetInstance()->SpeakOrEnqueue(utterance_);
+ int src_id = -1;
+ EXTENSION_FUNCTION_VALIDATE(
+ options->GetInteger(constants::kSrcIdKey, &src_id));
+
+ // If we got this far, the arguments were all in the valid format, so
+ // send the success response to the callback now - this ensures that
+ // the callback response always arrives before events, which makes
+ // the behavior more predictable and easier to write unit tests for too.
+ SendResponse(true);
+
+ UtteranceContinuousParameters continuous_params;
+ continuous_params.rate = rate;
+ continuous_params.pitch = pitch;
+ continuous_params.volume = volume;
+
+ Utterance* utterance = new Utterance(profile());
+ utterance->set_text(text);
+ utterance->set_voice_name(voice_name);
+ utterance->set_src_extension_id(extension_id());
+ utterance->set_src_id(src_id);
+ utterance->set_src_url(source_url());
+ utterance->set_lang(lang);
+ utterance->set_gender(gender);
+ utterance->set_continuous_parameters(continuous_params);
+ utterance->set_can_enqueue(can_enqueue);
+ utterance->set_required_event_types(required_event_types);
+ utterance->set_desired_event_types(desired_event_types);
+ utterance->set_extension_id(voice_extension_id);
+ utterance->set_options(options.get());
+
+ ExtensionTtsController* controller = ExtensionTtsController::GetInstance();
+ controller->SpeakOrEnqueue(utterance);
return true;
}
-void ExtensionTtsSpeakFunction::SpeechFinished() {
- error_ = utterance_->error();
- bool success = error_.empty();
- SendResponse(success);
- Release(); // Balanced in RunImpl().
-}
-
bool ExtensionTtsStopSpeakingFunction::RunImpl() {
ExtensionTtsController::GetInstance()->Stop();
return true;
@@ -373,14 +166,7 @@ bool ExtensionTtsIsSpeakingFunction::RunImpl() {
return true;
}
-bool ExtensionTtsSpeakCompletedFunction::RunImpl() {
- int request_id;
- std::string error_message;
- EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &request_id));
- if (args_->GetSize() >= 2)
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &error_message));
- ExtensionTtsController::GetInstance()->OnSpeechFinished(
- request_id, error_message);
-
+bool ExtensionTtsGetVoicesFunction::RunImpl() {
+ result_.reset(ExtensionTtsController::GetInstance()->GetVoices(profile()));
return true;
}
diff --git a/chrome/browser/extensions/extension_tts_api.h b/chrome/browser/extensions/extension_tts_api.h
index 88e8ba8..28329a2 100644
--- a/chrome/browser/extensions/extension_tts_api.h
+++ b/chrome/browser/extensions/extension_tts_api.h
@@ -5,232 +5,38 @@
#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_H_
#define CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_H_
-#include <queue>
#include <string>
-#include "base/memory/singleton.h"
-#include "base/task.h"
#include "chrome/browser/extensions/extension_function.h"
-#include "chrome/browser/extensions/extension_tts_api_util.h"
+#include "chrome/browser/extensions/extension_tts_api_controller.h"
-// Abstract class that defines the native platform TTS interface.
-class ExtensionTtsPlatformImpl {
- public:
- static ExtensionTtsPlatformImpl* GetInstance();
-
- // Speak the given utterance with the given parameters if possible,
- // and return true on success. Utterance will always be nonempty.
- // If the user does not specify the other values, then locale and gender
- // will be empty strings, and rate, pitch, and volume will be -1.0.
- //
- // The ExtensionTtsController will only try to speak one utterance at
- // a time. If it wants to interrupt speech, it will always call Stop
- // before speaking again, otherwise it will wait until IsSpeaking
- // returns false before calling Speak again.
- virtual bool Speak(
- const std::string& utterance,
- const std::string& locale,
- const std::string& gender,
- double rate,
- double pitch,
- double volume) = 0;
-
- // Stop speaking immediately and return true on success.
- virtual bool StopSpeaking() = 0;
-
- // Return true if the synthesis engine is currently speaking.
- virtual bool IsSpeaking() = 0;
-
- virtual std::string error();
- virtual void clear_error();
- virtual void set_error(const std::string& error);
-
- protected:
- ExtensionTtsPlatformImpl() {}
- virtual ~ExtensionTtsPlatformImpl() {}
-
- std::string error_;
-
- DISALLOW_COPY_AND_ASSIGN(ExtensionTtsPlatformImpl);
-};
-
-// One speech utterance.
-class Utterance {
- public:
- // Construct an utterance given a profile, the text to speak,
- // the options passed to tts.speak, and a completion task to call
- // when the utterance is done speaking.
- Utterance(Profile* profile,
- const std::string& text,
- DictionaryValue* options,
- Task* completion_task);
- ~Utterance();
-
- // Calls the completion task and then destroys itself.
- void FinishAndDestroy();
-
- void set_error(const std::string& error) { error_ = error; }
- void set_extension_id(const std::string& extension_id) {
- extension_id_ = extension_id;
- }
-
- // Accessors
- Profile* profile() { return profile_; }
- const std::string& extension_id() { return extension_id_; }
- int id() { return id_; }
- const std::string& text() { return text_; }
- const Value* options() { return options_.get(); }
- const std::string& voice_name() { return voice_name_; }
- const std::string& locale() { return locale_; }
- const std::string& gender() { return gender_; }
- double rate() { return rate_; }
- double pitch() { return pitch_; }
- double volume() { return volume_; }
- bool can_enqueue() { return can_enqueue_; }
- const std::string& error() { return error_; }
-
- private:
- // The profile that initiated this utterance.
- Profile* profile_;
-
- // The extension ID of the extension providing TTS for this utterance, or
- // empty if native TTS is being used.
- std::string extension_id_;
-
- // The unique ID of this utterance, used to associate callback functions
- // with utterances.
- int id_;
-
- // The id of the next utterance, so we can associate requests with
- // responses.
- static int next_utterance_id_;
-
- // The text to speak.
- std::string text_;
-
- // The full options arg passed to tts.speak, which may include fields
- // other than the ones we explicitly parse, below.
- scoped_ptr<Value> options_;
-
- // The parsed options.
- std::string voice_name_;
- std::string locale_;
- std::string gender_;
- double rate_;
- double pitch_;
- double volume_;
- bool can_enqueue_;
-
- // The error string to pass to the completion task. Will be empty if
- // no error occurred.
- std::string error_;
-
- // The method to call when this utterance has completed speaking.
- Task* completion_task_;
-};
-
-// Singleton class that manages text-to-speech.
-class ExtensionTtsController {
- public:
- // Get the single instance of this class.
- static ExtensionTtsController* GetInstance();
-
- // Returns true if we're currently speaking an utterance.
- bool IsSpeaking() const;
-
- // Speak the given utterance. If the utterance's can_enqueue flag is true
- // and another utterance is in progress, adds it to the end of the queue.
- // Otherwise, interrupts any current utterance and speaks this one
- // immediately.
- void SpeakOrEnqueue(Utterance* utterance);
-
- // Stop all utterances and flush the queue.
- void Stop();
-
- // Called when an extension finishes speaking an utterance.
- void OnSpeechFinished(int request_id, const std::string& error_message);
-
- // For unit testing.
- void SetPlatformImpl(ExtensionTtsPlatformImpl* platform_impl);
-
- private:
- ExtensionTtsController();
- virtual ~ExtensionTtsController();
-
- // Get the platform TTS implementation (or injected mock).
- ExtensionTtsPlatformImpl* GetPlatformImpl();
-
- // Start speaking the given utterance. Will either take ownership of
- // |utterance| or delete it if there's an error.
- void SpeakNow(Utterance* utterance);
-
- // Called periodically when speech is ongoing. Checks to see if the
- // underlying platform speech system has finished the current utterance,
- // and if so finishes it and pops the next utterance off the queue.
- void CheckSpeechStatus();
-
- // Clear the utterance queue.
- void ClearUtteranceQueue();
-
- // Finalize and delete the current utterance.
- void FinishCurrentUtterance();
-
- // Start speaking the next utterance in the queue.
- void SpeakNextUtterance();
-
- // Return the id string of the first extension with tts_voices in its
- // manifest that matches the speech parameters of this utterance,
- // or the empty string if none is found.
- std::string GetMatchingExtensionId(Utterance* utterance);
-
- ScopedRunnableMethodFactory<ExtensionTtsController> method_factory_;
- friend struct DefaultSingletonTraits<ExtensionTtsController>;
-
- // The current utterance being spoken.
- Utterance* current_utterance_;
-
- // A queue of utterances to speak after the current one finishes.
- std::queue<Utterance*> utterance_queue_;
-
- // A pointer to the platform implementation of text-to-speech, for
- // dependency injection.
- ExtensionTtsPlatformImpl* platform_impl_;
-
- DISALLOW_COPY_AND_ASSIGN(ExtensionTtsController);
-};
-
-//
-// Extension API function definitions
-//
-
-class ExtensionTtsSpeakFunction : public AsyncExtensionFunction {
+class ExtensionTtsSpeakFunction
+ : public AsyncExtensionFunction {
private:
virtual ~ExtensionTtsSpeakFunction() {}
- virtual bool RunImpl();
- void SpeechFinished();
- Utterance* utterance_;
+ virtual bool RunImpl() OVERRIDE;
DECLARE_EXTENSION_FUNCTION_NAME("experimental.tts.speak")
};
class ExtensionTtsStopSpeakingFunction : public SyncExtensionFunction {
private:
virtual ~ExtensionTtsStopSpeakingFunction() {}
- virtual bool RunImpl();
+ virtual bool RunImpl() OVERRIDE;
DECLARE_EXTENSION_FUNCTION_NAME("experimental.tts.stop")
};
class ExtensionTtsIsSpeakingFunction : public SyncExtensionFunction {
private:
virtual ~ExtensionTtsIsSpeakingFunction() {}
- virtual bool RunImpl();
+ virtual bool RunImpl() OVERRIDE;
DECLARE_EXTENSION_FUNCTION_NAME("experimental.tts.isSpeaking")
};
-class ExtensionTtsSpeakCompletedFunction : public SyncExtensionFunction {
+class ExtensionTtsGetVoicesFunction : public SyncExtensionFunction {
private:
- virtual ~ExtensionTtsSpeakCompletedFunction() {}
- virtual bool RunImpl();
- DECLARE_EXTENSION_FUNCTION_NAME("experimental.tts.speakCompleted")
+ virtual ~ExtensionTtsGetVoicesFunction() {}
+ virtual bool RunImpl() OVERRIDE;
+ DECLARE_EXTENSION_FUNCTION_NAME("experimental.tts.getVoices")
};
#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_H_
diff --git a/chrome/browser/extensions/extension_tts_api_chromeos.cc b/chrome/browser/extensions/extension_tts_api_chromeos.cc
index 403a4a7..9e27333 100644
--- a/chrome/browser/extensions/extension_tts_api_chromeos.cc
+++ b/chrome/browser/extensions/extension_tts_api_chromeos.cc
@@ -2,46 +2,56 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "chrome/browser/extensions/extension_tts_api.h"
-
#include "base/memory/singleton.h"
+#include "base/message_loop.h"
#include "base/string_number_conversions.h"
+#include "base/task.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/cros/speech_synthesis_library.h"
-
-namespace util = extension_tts_api_util;
+#include "chrome/browser/extensions/extension_tts_api_controller.h"
+#include "chrome/browser/extensions/extension_tts_api_platform.h"
using base::DoubleToString;
namespace {
const char kCrosLibraryNotLoadedError[] = "Cros shared library not loaded.";
+const int kSpeechCheckDelayIntervalMs = 100;
};
class ExtensionTtsPlatformImplChromeOs : public ExtensionTtsPlatformImpl {
public:
+ virtual bool PlatformImplAvailable() {
+ return true;
+ }
+
virtual bool Speak(
+ int utterance_id,
const std::string& utterance,
- const std::string& locale,
- const std::string& gender,
- double rate,
- double pitch,
- double volume);
+ const std::string& lang,
+ const UtteranceContinuousParameters& params);
virtual bool StopSpeaking();
- virtual bool IsSpeaking();
+ virtual bool SendsEvent(TtsEventType event_type);
// Get the single instance of this class.
static ExtensionTtsPlatformImplChromeOs* GetInstance();
private:
- ExtensionTtsPlatformImplChromeOs() {}
+ ExtensionTtsPlatformImplChromeOs()
+ : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {}
virtual ~ExtensionTtsPlatformImplChromeOs() {}
+ void PollUntilSpeechFinishes(int utterance_id);
+
void AppendSpeakOption(std::string key,
std::string value,
std::string* options);
+ int utterance_id_;
+ int utterance_length_;
+ ScopedRunnableMethodFactory<ExtensionTtsPlatformImplChromeOs> method_factory_;
+
friend struct DefaultSingletonTraits<ExtensionTtsPlatformImplChromeOs>;
DISALLOW_COPY_AND_ASSIGN(ExtensionTtsPlatformImplChromeOs);
@@ -53,54 +63,48 @@ ExtensionTtsPlatformImpl* ExtensionTtsPlatformImpl::GetInstance() {
}
bool ExtensionTtsPlatformImplChromeOs::Speak(
+ int utterance_id,
const std::string& utterance,
- const std::string& locale,
- const std::string& gender,
- double rate,
- double pitch,
- double volume) {
+ const std::string& lang,
+ const UtteranceContinuousParameters& params) {
chromeos::CrosLibrary* cros_library = chromeos::CrosLibrary::Get();
if (!cros_library->EnsureLoaded()) {
set_error(kCrosLibraryNotLoadedError);
return false;
}
+ utterance_id_ = utterance_id;
+ utterance_length_ = utterance.size();
+
std::string options;
- if (!locale.empty()) {
+ if (!lang.empty()) {
AppendSpeakOption(
chromeos::SpeechSynthesisLibrary::kSpeechPropertyLocale,
- locale,
+ lang,
&options);
}
- if (!gender.empty()) {
- AppendSpeakOption(
- chromeos::SpeechSynthesisLibrary::kSpeechPropertyGender,
- gender,
- &options);
- }
-
- if (rate >= 0.0) {
+ if (params.rate >= 0.0) {
AppendSpeakOption(
chromeos::SpeechSynthesisLibrary::kSpeechPropertyRate,
- DoubleToString(rate * 5),
+ DoubleToString(1.5 + params.rate * 2.5),
&options);
}
- if (pitch >= 0.0) {
+ if (params.pitch >= 0.0) {
// The TTS service allows a range of 0 to 2 for speech pitch.
AppendSpeakOption(
chromeos::SpeechSynthesisLibrary::kSpeechPropertyPitch,
- DoubleToString(pitch * 2),
+ DoubleToString(params.pitch),
&options);
}
- if (volume >= 0.0) {
+ if (params.volume >= 0.0) {
// The TTS service allows a range of 0 to 5 for speech volume.
AppendSpeakOption(
chromeos::SpeechSynthesisLibrary::kSpeechPropertyVolume,
- DoubleToString(volume * 5),
+ DoubleToString(params.volume * 5),
&options);
}
@@ -109,7 +113,16 @@ bool ExtensionTtsPlatformImplChromeOs::Speak(
options.c_str());
}
- return cros_library->GetSpeechSynthesisLibrary()->Speak(utterance.c_str());
+ bool result =
+ cros_library->GetSpeechSynthesisLibrary()->Speak(utterance.c_str());
+
+ if (result) {
+ ExtensionTtsController* controller = ExtensionTtsController::GetInstance();
+ controller->OnTtsEvent(utterance_id_, TTS_EVENT_START, 0, std::string());
+ PollUntilSpeechFinishes(utterance_id_);
+ }
+
+ return result;
}
bool ExtensionTtsPlatformImplChromeOs::StopSpeaking() {
@@ -122,14 +135,39 @@ bool ExtensionTtsPlatformImplChromeOs::StopSpeaking() {
return false;
}
-bool ExtensionTtsPlatformImplChromeOs::IsSpeaking() {
- if (chromeos::CrosLibrary::Get()->EnsureLoaded()) {
- return chromeos::CrosLibrary::Get()->GetSpeechSynthesisLibrary()->
- IsSpeaking();
+bool ExtensionTtsPlatformImplChromeOs::SendsEvent(TtsEventType event_type) {
+ return (event_type == TTS_EVENT_START ||
+ event_type == TTS_EVENT_END ||
+ event_type == TTS_EVENT_ERROR);
+}
+
+void ExtensionTtsPlatformImplChromeOs::PollUntilSpeechFinishes(
+ int utterance_id) {
+ if (utterance_id != utterance_id_) {
+ // This utterance must have been interrupted or cancelled.
+ return;
}
- set_error(kCrosLibraryNotLoadedError);
- return false;
+ chromeos::CrosLibrary* cros_library = chromeos::CrosLibrary::Get();
+ ExtensionTtsController* controller = ExtensionTtsController::GetInstance();
+
+ if (!cros_library->EnsureLoaded()) {
+ controller->OnTtsEvent(
+ utterance_id_, TTS_EVENT_ERROR, 0, kCrosLibraryNotLoadedError);
+ return;
+ }
+
+ if (!cros_library->GetSpeechSynthesisLibrary()->IsSpeaking()) {
+ controller->OnTtsEvent(
+ utterance_id_, TTS_EVENT_END, utterance_length_, std::string());
+ return;
+ }
+
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, method_factory_.NewRunnableMethod(
+ &ExtensionTtsPlatformImplChromeOs::PollUntilSpeechFinishes,
+ utterance_id),
+ kSpeechCheckDelayIntervalMs);
}
void ExtensionTtsPlatformImplChromeOs::AppendSpeakOption(
diff --git a/chrome/browser/extensions/extension_tts_api_constants.cc b/chrome/browser/extensions/extension_tts_api_constants.cc
new file mode 100644
index 0000000..dc0ec95
--- /dev/null
+++ b/chrome/browser/extensions/extension_tts_api_constants.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/extension_tts_api_constants.h"
+
+namespace extension_tts_api_constants {
+
+const char kVoiceNameKey[] = "voiceName";
+const char kLangKey[] = "lang";
+const char kGenderKey[] = "gender";
+const char kRateKey[] = "rate";
+const char kPitchKey[] = "pitch";
+const char kVolumeKey[] = "volume";
+const char kEnqueueKey[] = "enqueue";
+const char kEventTypeKey[] = "type";
+const char kEventTypesKey[] = "eventTypes";
+const char kCharIndexKey[] = "charIndex";
+const char kErrorMessageKey[] = "errorMessage";
+const char kRequiredEventTypesKey[] = "requiredEventTypes";
+const char kDesiredEventTypesKey[] = "desiredEventTypes";
+const char kExtensionIdKey[] = "extensionId";
+const char kSrcIdKey[] = "srcId";
+const char kIsFinalEventKey[] = "isFinalEvent";
+
+const char kGenderFemale[] = "female";
+const char kGenderMale[] = "male";
+
+const char kEventTypeStart[] = "start";
+const char kEventTypeEnd[] = "end";
+const char kEventTypeWord[] = "word";
+const char kEventTypeSentence[] = "sentence";
+const char kEventTypeMarker[] = "marker";
+const char kEventTypeInterrupted[] = "interrupted";
+const char kEventTypeCancelled[] = "cancelled";
+const char kEventTypeError[] = "error";
+
+const char kNativeVoiceName[] = "native";
+
+const char kErrorUndeclaredEventType[] =
+ "Cannot send an event type that is not declared in the extension manifest.";
+const char kErrorUtteranceTooLong[] = "Utterance length is too long.";
+const char kErrorInvalidLang[] = "Invalid lang.";
+const char kErrorInvalidGender[] = "Invalid gender.";
+const char kErrorInvalidRate[] = "Invalid rate.";
+const char kErrorInvalidPitch[] = "Invalid pitch.";
+const char kErrorInvalidVolume[] = "Invalid volume.";
+
+} // namespace extension_tts_api_constants.
diff --git a/chrome/browser/extensions/extension_tts_api_constants.h b/chrome/browser/extensions/extension_tts_api_constants.h
new file mode 100644
index 0000000..6a66777
--- /dev/null
+++ b/chrome/browser/extensions/extension_tts_api_constants.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_CONSTANTS_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_CONSTANTS_H_
+
+#include <string>
+
+#include "base/values.h"
+
+namespace extension_tts_api_constants {
+
+extern const char kVoiceNameKey[];
+extern const char kLangKey[];
+extern const char kGenderKey[];
+extern const char kRateKey[];
+extern const char kPitchKey[];
+extern const char kVolumeKey[];
+extern const char kEnqueueKey[];
+extern const char kEventTypeKey[];
+extern const char kEventTypesKey[];
+extern const char kCharIndexKey[];
+extern const char kErrorMessageKey[];
+extern const char kRequiredEventTypesKey[];
+extern const char kDesiredEventTypesKey[];
+extern const char kExtensionIdKey[];
+extern const char kSrcIdKey[];
+extern const char kIsFinalEventKey[];
+
+extern const char kGenderFemale[];
+extern const char kGenderMale[];
+
+extern const char kEventTypeStart[];
+extern const char kEventTypeEnd[];
+extern const char kEventTypeWord[];
+extern const char kEventTypeSentence[];
+extern const char kEventTypeMarker[];
+extern const char kEventTypeInterrupted[];
+extern const char kEventTypeCancelled[];
+extern const char kEventTypeError[];
+
+extern const char kNativeVoiceName[];
+
+extern const char kErrorUndeclaredEventType[];
+extern const char kErrorUtteranceTooLong[];
+extern const char kErrorInvalidLang[];
+extern const char kErrorInvalidGender[];
+extern const char kErrorInvalidRate[];
+extern const char kErrorInvalidPitch[];
+extern const char kErrorInvalidVolume[];
+
+} // namespace extension_tts_api_constants.
+#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_CONSTANTS_H_
diff --git a/chrome/browser/extensions/extension_tts_api_controller.cc b/chrome/browser/extensions/extension_tts_api_controller.cc
new file mode 100644
index 0000000..1d0b3fc
--- /dev/null
+++ b/chrome/browser/extensions/extension_tts_api_controller.cc
@@ -0,0 +1,312 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/extension_tts_api_controller.h"
+
+#include <string>
+#include <vector>
+
+#include "base/float_util.h"
+#include "base/json/json_writer.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/extension_event_router.h"
+#include "chrome/browser/extensions/extension_tts_api.h"
+#include "chrome/browser/extensions/extension_tts_api_constants.h"
+#include "chrome/browser/extensions/extension_tts_api_platform.h"
+#include "chrome/browser/extensions/extension_tts_engine_api.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/extensions/extension.h"
+
+namespace constants = extension_tts_api_constants;
+
+namespace events {
+const char kOnEvent[] = "experimental.ttsEngine.onEvent";
+}; // namespace events
+
+std::string TtsEventTypeToString(TtsEventType event_type) {
+ switch (event_type) {
+ case TTS_EVENT_START:
+ return constants::kEventTypeStart;
+ case TTS_EVENT_END:
+ return constants::kEventTypeEnd;
+ case TTS_EVENT_WORD:
+ return constants::kEventTypeWord;
+ case TTS_EVENT_SENTENCE:
+ return constants::kEventTypeSentence;
+ case TTS_EVENT_MARKER:
+ return constants::kEventTypeMarker;
+ case TTS_EVENT_INTERRUPTED:
+ return constants::kEventTypeInterrupted;
+ case TTS_EVENT_CANCELLED:
+ return constants::kEventTypeCancelled;
+ case TTS_EVENT_ERROR:
+ return constants::kEventTypeError;
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+
+//
+// UtteranceContinuousParameters
+//
+
+
+UtteranceContinuousParameters::UtteranceContinuousParameters()
+ : rate(-1),
+ pitch(-1),
+ volume(-1) {}
+
+
+//
+// Utterance
+//
+
+// static
+int Utterance::next_utterance_id_ = 0;
+
+Utterance::Utterance(Profile* profile)
+ : profile_(profile),
+ id_(next_utterance_id_++),
+ can_enqueue_(false),
+ char_index_(0),
+ finished_(false) {
+ options_.reset(new DictionaryValue());
+}
+
+Utterance::~Utterance() {
+ DCHECK(finished_);
+}
+
+void Utterance::OnTtsEvent(TtsEventType event_type,
+ int char_index,
+ const std::string& error_message) {
+ std::string event_type_string = TtsEventTypeToString(event_type);
+ if (char_index >= 0)
+ char_index_ = char_index;
+ if (event_type == TTS_EVENT_END ||
+ event_type == TTS_EVENT_INTERRUPTED ||
+ event_type == TTS_EVENT_CANCELLED ||
+ event_type == TTS_EVENT_ERROR) {
+ finished_ = true;
+ }
+ if (desired_event_types_.size() > 0 &&
+ desired_event_types_.find(event_type_string) ==
+ desired_event_types_.end()) {
+ return;
+ }
+
+ ListValue args;
+ DictionaryValue* event = new DictionaryValue();
+ event->SetInteger(constants::kCharIndexKey, char_index);
+ event->SetString(constants::kEventTypeKey, event_type_string);
+ if (event_type == TTS_EVENT_ERROR) {
+ event->SetString(constants::kErrorMessageKey, error_message);
+ }
+ event->SetInteger(constants::kSrcIdKey, src_id_);
+ event->SetBoolean(constants::kIsFinalEventKey, finished_);
+ args.Set(0, event);
+ std::string json_args;
+ base::JSONWriter::Write(&args, false, &json_args);
+
+ profile_->GetExtensionEventRouter()->DispatchEventToExtension(
+ src_extension_id_,
+ events::kOnEvent,
+ json_args,
+ profile_,
+ src_url_);
+}
+
+void Utterance::Finish() {
+ finished_ = true;
+}
+
+void Utterance::set_options(const Value* options) {
+ options_.reset(options->DeepCopy());
+}
+
+//
+// ExtensionTtsController
+//
+
+// static
+ExtensionTtsController* ExtensionTtsController::GetInstance() {
+ return Singleton<ExtensionTtsController>::get();
+}
+
+ExtensionTtsController::ExtensionTtsController()
+ : current_utterance_(NULL),
+ platform_impl_(NULL) {
+}
+
+ExtensionTtsController::~ExtensionTtsController() {
+ FinishCurrentUtterance();
+ ClearUtteranceQueue();
+}
+
+void ExtensionTtsController::SpeakOrEnqueue(Utterance* utterance) {
+ if (IsSpeaking() && utterance->can_enqueue()) {
+ utterance_queue_.push(utterance);
+ } else {
+ Stop();
+ SpeakNow(utterance);
+ }
+}
+
+void ExtensionTtsController::SpeakNow(Utterance* utterance) {
+ const Extension* extension;
+ size_t voice_index;
+ if (GetMatchingExtensionVoice(utterance, &extension, &voice_index)) {
+ current_utterance_ = utterance;
+ utterance->set_extension_id(extension->id());
+
+ ExtensionTtsEngineSpeak(utterance, extension, voice_index);
+
+ const std::set<std::string> event_types =
+ extension->tts_voices()[voice_index].event_types;
+ bool sends_end_event =
+ (event_types.find(constants::kEventTypeEnd) != event_types.end());
+ if (!sends_end_event) {
+ utterance->Finish();
+ delete utterance;
+ current_utterance_ = NULL;
+ SpeakNextUtterance();
+ }
+ return;
+ }
+
+ GetPlatformImpl()->clear_error();
+ bool success = GetPlatformImpl()->Speak(
+ utterance->id(),
+ utterance->text(),
+ utterance->lang(),
+ utterance->continuous_parameters());
+ if (!success) {
+ utterance->OnTtsEvent(TTS_EVENT_ERROR, -1, GetPlatformImpl()->error());
+ delete utterance;
+ return;
+ }
+ current_utterance_ = utterance;
+}
+
+void ExtensionTtsController::Stop() {
+ if (current_utterance_ && !current_utterance_->extension_id().empty()) {
+ ExtensionTtsEngineStop(current_utterance_);
+ } else {
+ GetPlatformImpl()->clear_error();
+ GetPlatformImpl()->StopSpeaking();
+ }
+
+ if (current_utterance_)
+ current_utterance_->OnTtsEvent(TTS_EVENT_INTERRUPTED, -1, std::string());
+ FinishCurrentUtterance();
+ ClearUtteranceQueue();
+}
+
+void ExtensionTtsController::OnTtsEvent(int utterance_id,
+ TtsEventType event_type,
+ int char_index,
+ const std::string& error_message) {
+ // We may sometimes receive completion callbacks "late", after we've
+ // already finished the utterance (for example because another utterance
+ // interrupted or we got a call to Stop). This is normal and we can
+ // safely just ignore these events.
+ if (!current_utterance_ || utterance_id != current_utterance_->id())
+ return;
+
+ current_utterance_->OnTtsEvent(event_type, char_index, error_message);
+ if (current_utterance_->finished()) {
+ FinishCurrentUtterance();
+ SpeakNextUtterance();
+ }
+}
+
+ListValue* ExtensionTtsController::GetVoices(Profile* profile) {
+ ListValue* result_voices = new ListValue();
+ if (platform_impl_ && platform_impl_->PlatformImplAvailable()) {
+ DictionaryValue* result_voice = new DictionaryValue();
+ result_voice->SetString(
+ constants::kVoiceNameKey, constants::kNativeVoiceName);
+ if (!platform_impl_->gender().empty())
+ result_voice->SetString(constants::kGenderKey, platform_impl_->gender());
+ ListValue* event_types = new ListValue();
+
+ // All platforms must send end events, and cancelled and interrupted
+ // events are generated from the controller.
+ DCHECK(platform_impl_->SendsEvent(TTS_EVENT_END));
+ event_types->Append(Value::CreateStringValue(constants::kEventTypeEnd));
+ event_types->Append(Value::CreateStringValue(
+ constants::kEventTypeCancelled));
+ event_types->Append(Value::CreateStringValue(
+ constants::kEventTypeInterrupted));
+
+ if (platform_impl_->SendsEvent(TTS_EVENT_START))
+ event_types->Append(Value::CreateStringValue(constants::kEventTypeStart));
+ if (platform_impl_->SendsEvent(TTS_EVENT_WORD))
+ event_types->Append(Value::CreateStringValue(constants::kEventTypeWord));
+ if (platform_impl_->SendsEvent(TTS_EVENT_SENTENCE))
+ event_types->Append(Value::CreateStringValue(
+ constants::kEventTypeSentence));
+ if (platform_impl_->SendsEvent(TTS_EVENT_MARKER))
+ event_types->Append(Value::CreateStringValue(
+ constants::kEventTypeMarker));
+ if (platform_impl_->SendsEvent(TTS_EVENT_ERROR))
+ event_types->Append(Value::CreateStringValue(
+ constants::kEventTypeError));
+ result_voice->Set(constants::kEventTypesKey, event_types);
+ result_voices->Append(result_voice);
+ }
+
+ GetExtensionVoices(profile, result_voices);
+
+ return result_voices;
+}
+
+bool ExtensionTtsController::IsSpeaking() const {
+ return current_utterance_ != NULL;
+}
+
+void ExtensionTtsController::FinishCurrentUtterance() {
+ if (current_utterance_) {
+ if (!current_utterance_->finished())
+ current_utterance_->OnTtsEvent(TTS_EVENT_INTERRUPTED, -1, std::string());
+ delete current_utterance_;
+ current_utterance_ = NULL;
+ }
+}
+
+void ExtensionTtsController::SpeakNextUtterance() {
+ // Start speaking the next utterance in the queue. Keep trying in case
+ // one fails but there are still more in the queue to try.
+ while (!utterance_queue_.empty() && !current_utterance_) {
+ Utterance* utterance = utterance_queue_.front();
+ utterance_queue_.pop();
+ SpeakNow(utterance);
+ }
+}
+
+void ExtensionTtsController::ClearUtteranceQueue() {
+ while (!utterance_queue_.empty()) {
+ Utterance* utterance = utterance_queue_.front();
+ utterance_queue_.pop();
+ utterance->OnTtsEvent(TTS_EVENT_CANCELLED, -1, std::string());
+ delete utterance;
+ }
+}
+
+void ExtensionTtsController::SetPlatformImpl(
+ ExtensionTtsPlatformImpl* platform_impl) {
+ platform_impl_ = platform_impl;
+}
+
+int ExtensionTtsController::QueueSize() {
+ return static_cast<int>(utterance_queue_.size());
+}
+
+ExtensionTtsPlatformImpl* ExtensionTtsController::GetPlatformImpl() {
+ if (!platform_impl_)
+ platform_impl_ = ExtensionTtsPlatformImpl::GetInstance();
+ return platform_impl_;
+}
diff --git a/chrome/browser/extensions/extension_tts_api_controller.h b/chrome/browser/extensions/extension_tts_api_controller.h
new file mode 100644
index 0000000..8ffe065
--- /dev/null
+++ b/chrome/browser/extensions/extension_tts_api_controller.h
@@ -0,0 +1,256 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_CONTROLLER_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_CONTROLLER_H_
+
+#include <queue>
+#include <set>
+#include <string>
+
+#include "base/memory/singleton.h"
+#include "base/scoped_ptr.h"
+#include "googleurl/src/gurl.h"
+
+class DictionaryValue;
+class Extension;
+class ExtensionTtsPlatformImpl;
+class ListValue;
+class Profile;
+class Value;
+
+
+// Events sent back from the TTS engine indicating the progress.
+enum TtsEventType {
+ TTS_EVENT_START,
+ TTS_EVENT_END,
+ TTS_EVENT_WORD,
+ TTS_EVENT_SENTENCE,
+ TTS_EVENT_MARKER,
+ TTS_EVENT_INTERRUPTED,
+ TTS_EVENT_CANCELLED,
+ TTS_EVENT_ERROR
+};
+
+
+// The continuous parameters that apply to a given utterance.
+struct UtteranceContinuousParameters {
+ UtteranceContinuousParameters();
+
+ double rate;
+ double pitch;
+ double volume;
+};
+
+
+// One speech utterance.
+class Utterance {
+ public:
+ // Construct an utterance given a profile and a completion task to call
+ // when the utterance is done speaking. Before speaking this utterance,
+ // its other parameters like text, rate, pitch, etc. should all be set.
+ explicit Utterance(Profile* profile);
+ ~Utterance();
+
+ // Sends an event to the delegate. If the event type is TTS_EVENT_END
+ // or TTS_EVENT_ERROR, deletes the utterance. If |char_index| is -1,
+ // uses the last good value.
+ void OnTtsEvent(TtsEventType event_type,
+ int char_index,
+ const std::string& error_message);
+
+ // Finish an utterance without sending an event to the delegate.
+ void Finish();
+
+ // Getters and setters for the text to speak and other speech options.
+ void set_text(const std::string& text) { text_ = text; }
+ const std::string& text() const { return text_; }
+
+ void set_options(const Value* options);
+ const Value* options() const { return options_.get(); }
+
+ void set_src_extension_id(const std::string& src_extension_id) {
+ src_extension_id_ = src_extension_id;
+ }
+ const std::string& src_extension_id() { return src_extension_id_; }
+
+ void set_src_id(int src_id) { src_id_ = src_id; }
+ int src_id() { return src_id_; }
+
+ void set_src_url(const GURL& src_url) { src_url_ = src_url; }
+ const GURL& src_url() { return src_url_; }
+
+ void set_voice_name(const std::string& voice_name) {
+ voice_name_ = voice_name;
+ }
+ const std::string& voice_name() const { return voice_name_; }
+
+ void set_lang(const std::string& lang) {
+ lang_ = lang;
+ }
+ const std::string& lang() const { return lang_; }
+
+ void set_gender(const std::string& gender) {
+ gender_ = gender;
+ }
+ const std::string& gender() const { return gender_; }
+
+ void set_continuous_parameters(const UtteranceContinuousParameters& params) {
+ continuous_parameters_ = params;
+ }
+ const UtteranceContinuousParameters& continuous_parameters() {
+ return continuous_parameters_;
+ }
+
+ void set_can_enqueue(bool can_enqueue) { can_enqueue_ = can_enqueue; }
+ bool can_enqueue() const { return can_enqueue_; }
+
+ void set_required_event_types(const std::set<std::string>& types) {
+ required_event_types_ = types;
+ }
+ const std::set<std::string>& required_event_types() const {
+ return required_event_types_;
+ }
+
+ void set_desired_event_types(const std::set<std::string>& types) {
+ desired_event_types_ = types;
+ }
+ const std::set<std::string>& desired_event_types() const {
+ return desired_event_types_;
+ }
+
+ const std::string& extension_id() const { return extension_id_; }
+ void set_extension_id(const std::string& extension_id) {
+ extension_id_ = extension_id;
+ }
+
+ // Getters and setters for internal state.
+ Profile* profile() const { return profile_; }
+ int id() const { return id_; }
+ bool finished() const { return finished_; }
+
+ private:
+ // The profile that initiated this utterance.
+ Profile* profile_;
+
+ // The extension ID of the extension providing TTS for this utterance, or
+ // empty if native TTS is being used.
+ std::string extension_id_;
+
+ // The unique ID of this utterance, used to associate callback functions
+ // with utterances.
+ int id_;
+
+ // The id of the next utterance, so we can associate requests with
+ // responses.
+ static int next_utterance_id_;
+
+ // The text to speak.
+ std::string text_;
+
+ // The full options arg passed to tts.speak, which may include fields
+ // other than the ones we explicitly parse, below.
+ scoped_ptr<Value> options_;
+
+ // The extension ID of the extension that called speak() and should
+ // receive events.
+ std::string src_extension_id_;
+
+ // The source extension's ID of this utterance, so that it can associate
+ // events with the appropriate callback.
+ int src_id_;
+
+ // The URL of the page where the source extension called speak.
+ GURL src_url_;
+
+ // The parsed options.
+ std::string voice_name_;
+ std::string lang_;
+ std::string gender_;
+ UtteranceContinuousParameters continuous_parameters_;
+ bool can_enqueue_;
+ std::set<std::string> required_event_types_;
+ std::set<std::string> desired_event_types_;
+
+ // The index of the current char being spoken.
+ int char_index_;
+
+ // True if this utterance received an event indicating it's done.
+ bool finished_;
+};
+
+
+// Singleton class that manages text-to-speech for the TTS and TTS engine
+// extension APIs, maintaining a queue of pending utterances and keeping
+// track of all state.
+class ExtensionTtsController {
+ public:
+ // Get the single instance of this class.
+ static ExtensionTtsController* GetInstance();
+
+ // Returns true if we're currently speaking an utterance.
+ bool IsSpeaking() const;
+
+ // Speak the given utterance. If the utterance's can_enqueue flag is true
+ // and another utterance is in progress, adds it to the end of the queue.
+ // Otherwise, interrupts any current utterance and speaks this one
+ // immediately.
+ void SpeakOrEnqueue(Utterance* utterance);
+
+ // Stop all utterances and flush the queue.
+ void Stop();
+
+ // Handle events received from the speech engine. Events are forwarded to
+ // the callback function, and in addition, completion and error events
+ // trigger finishing the current utterance and starting the next one, if
+ // any.
+ void OnTtsEvent(int utterance_id,
+ TtsEventType event_type,
+ int char_index,
+ const std::string& error_message);
+
+ // Return a list of all available voices, including the native voice,
+ // if supported, and all voices registered by extensions.
+ ListValue* GetVoices(Profile* profile);
+
+ // For unit testing.
+ void SetPlatformImpl(ExtensionTtsPlatformImpl* platform_impl);
+ int QueueSize();
+
+ private:
+ ExtensionTtsController();
+ virtual ~ExtensionTtsController();
+
+ // Get the platform TTS implementation (or injected mock).
+ ExtensionTtsPlatformImpl* GetPlatformImpl();
+
+ // Start speaking the given utterance. Will either take ownership of
+ // |utterance| or delete it if there's an error. Returns true on success.
+ void SpeakNow(Utterance* utterance);
+
+ // Clear the utterance queue.
+ void ClearUtteranceQueue();
+
+ // Finalize and delete the current utterance.
+ void FinishCurrentUtterance();
+
+ // Start speaking the next utterance in the queue.
+ void SpeakNextUtterance();
+
+ friend struct DefaultSingletonTraits<ExtensionTtsController>;
+
+ // The current utterance being spoken.
+ Utterance* current_utterance_;
+
+ // A queue of utterances to speak after the current one finishes.
+ std::queue<Utterance*> utterance_queue_;
+
+ // A pointer to the platform implementation of text-to-speech, for
+ // dependency injection.
+ ExtensionTtsPlatformImpl* platform_impl_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionTtsController);
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_CONTROLLER_H_
diff --git a/chrome/browser/extensions/extension_tts_api_linux.cc b/chrome/browser/extensions/extension_tts_api_linux.cc
index d008526..07e6c61 100644
--- a/chrome/browser/extensions/extension_tts_api_linux.cc
+++ b/chrome/browser/extensions/extension_tts_api_linux.cc
@@ -2,11 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "chrome/browser/extensions/extension_tts_api.h"
-
#include "base/memory/singleton.h"
-
-namespace util = extension_tts_api_util;
+#include "chrome/browser/extensions/extension_tts_api_platform.h"
namespace {
const char kNotSupportedError[] =
@@ -15,13 +12,15 @@ const char kNotSupportedError[] =
class ExtensionTtsPlatformImplLinux : public ExtensionTtsPlatformImpl {
public:
+ virtual bool PlatformImplAvailable() {
+ return false;
+ }
+
virtual bool Speak(
+ int utterance_id,
const std::string& utterance,
- const std::string& language,
- const std::string& gender,
- double rate,
- double pitch,
- double volume) {
+ const std::string& lang,
+ const UtteranceContinuousParameters& params) {
error_ = kNotSupportedError;
return false;
}
@@ -36,6 +35,10 @@ class ExtensionTtsPlatformImplLinux : public ExtensionTtsPlatformImpl {
return false;
}
+ virtual bool SendsEvent(TtsEventType event_type) {
+ return false;
+ }
+
// Get the single instance of this class.
static ExtensionTtsPlatformImplLinux* GetInstance() {
return Singleton<ExtensionTtsPlatformImplLinux>::get();
diff --git a/chrome/browser/extensions/extension_tts_api_mac.mm b/chrome/browser/extensions/extension_tts_api_mac.mm
index dde3df9..1a774983 100644
--- a/chrome/browser/extensions/extension_tts_api_mac.mm
+++ b/chrome/browser/extensions/extension_tts_api_mac.mm
@@ -2,40 +2,67 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "extension_tts_api.h"
-
#include <string>
+#include "base/mac/cocoa_protocols.h"
+#include "base/memory/scoped_nsobject.h"
#include "base/memory/singleton.h"
+#include "base/sys_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_function.h"
+#include "chrome/browser/extensions/extension_tts_api_constants.h"
+#include "chrome/browser/extensions/extension_tts_api_controller.h"
+#include "chrome/browser/extensions/extension_tts_api_platform.h"
#import <Cocoa/Cocoa.h>
-namespace util = extension_tts_api_util;
+class ExtensionTtsPlatformImplMac;
+
+@interface ChromeTtsDelegate : NSObject <NSSpeechSynthesizerDelegate> {
+ @private
+ ExtensionTtsPlatformImplMac* ttsImplMac_; // weak.
+}
+
+- (id)initWithPlatformImplMac:(ExtensionTtsPlatformImplMac*)ttsImplMac;
+
+@end
class ExtensionTtsPlatformImplMac : public ExtensionTtsPlatformImpl {
public:
+ virtual bool PlatformImplAvailable() {
+ return true;
+ }
+
virtual bool Speak(
+ int utterance_id,
const std::string& utterance,
- const std::string& language,
- const std::string& gender,
- double rate,
- double pitch,
- double volume);
+ const std::string& lang,
+ const UtteranceContinuousParameters& params);
virtual bool StopSpeaking();
virtual bool IsSpeaking();
+ virtual bool SendsEvent(TtsEventType event_type);
+
+ // Called by ChromeTtsDelegate when we get a callback from the
+ // native speech engine.
+ void OnSpeechEvent(TtsEventType event_type,
+ int char_index,
+ const std::string& error_message);
+
// Get the single instance of this class.
static ExtensionTtsPlatformImplMac* GetInstance();
private:
ExtensionTtsPlatformImplMac();
- virtual ~ExtensionTtsPlatformImplMac() {}
+ virtual ~ExtensionTtsPlatformImplMac();
- NSSpeechSynthesizer* speech_synthesizer_;
+ scoped_nsobject<NSSpeechSynthesizer> speech_synthesizer_;
+ scoped_nsobject<ChromeTtsDelegate> delegate_;
+ int utterance_id_;
+ std::string utterance_;
+ bool sent_start_event_;
friend struct DefaultSingletonTraits<ExtensionTtsPlatformImplMac>;
@@ -48,32 +75,35 @@ ExtensionTtsPlatformImpl* ExtensionTtsPlatformImpl::GetInstance() {
}
bool ExtensionTtsPlatformImplMac::Speak(
+ int utterance_id,
const std::string& utterance,
- const std::string& language,
- const std::string& gender,
- double rate,
- double pitch,
- double volume) {
- // NSSpeechSynthesizer equivalents for kGenderKey and kLanguageNameKey do
- // not exist and thus are not supported.
-
- if (rate >= 0.0) {
- // The TTS api defines rate via words per minute.
+ const std::string& lang,
+ const UtteranceContinuousParameters& params) {
+ utterance_id_ = utterance_id;
+ sent_start_event_ = false;
+
+ // TODO: convert SSML to SAPI xml. http://crbug.com/88072
+ utterance_ = utterance;
+
+ // TODO: support languages other than the default: crbug.com/88059
+
+ if (params.rate >= 0.0) {
+ // The TTS api defines rate via words per minute. Let 200 be the default.
[speech_synthesizer_
- setObject:[NSNumber numberWithInt:rate * 400]
+ setObject:[NSNumber numberWithInt:params.rate * 200]
forProperty:NSSpeechRateProperty error:nil];
}
- if (pitch >= 0.0) {
+ if (params.pitch >= 0.0) {
// The TTS api allows an approximate range of 30 to 65 for speech pitch.
[speech_synthesizer_
- setObject: [NSNumber numberWithInt:(pitch * 35 + 30)]
+ setObject: [NSNumber numberWithInt:(params.pitch * 17 + 30)]
forProperty:NSSpeechPitchBaseProperty error:nil];
}
- if (volume >= 0.0) {
+ if (params.volume >= 0.0) {
[speech_synthesizer_
- setObject: [NSNumber numberWithFloat:volume]
+ setObject: [NSNumber numberWithFloat:params.volume]
forProperty:NSSpeechVolumeProperty error:nil];
}
@@ -90,11 +120,73 @@ bool ExtensionTtsPlatformImplMac::IsSpeaking() {
return [speech_synthesizer_ isSpeaking];
}
+bool ExtensionTtsPlatformImplMac::SendsEvent(TtsEventType event_type) {
+ return (event_type == TTS_EVENT_START ||
+ event_type == TTS_EVENT_END ||
+ event_type == TTS_EVENT_WORD ||
+ event_type == TTS_EVENT_ERROR);
+}
+
+void ExtensionTtsPlatformImplMac::OnSpeechEvent(
+ TtsEventType event_type,
+ int char_index,
+ const std::string& error_message) {
+ if (event_type == TTS_EVENT_END)
+ char_index = utterance_.size();
+ ExtensionTtsController* controller = ExtensionTtsController::GetInstance();
+ if (event_type == TTS_EVENT_WORD && !sent_start_event_) {
+ controller->OnTtsEvent(
+ utterance_id_, TTS_EVENT_START, 0, "");
+ sent_start_event_ = true;
+ }
+ controller->OnTtsEvent(
+ utterance_id_, event_type, char_index, error_message);
+}
+
ExtensionTtsPlatformImplMac::ExtensionTtsPlatformImplMac() {
- speech_synthesizer_ = [[NSSpeechSynthesizer alloc] init];
+ utterance_id_ = -1;
+ sent_start_event_ = true;
+ speech_synthesizer_.reset([[NSSpeechSynthesizer alloc] init]);
+
+ delegate_.reset([[ChromeTtsDelegate alloc] initWithPlatformImplMac:this]);
+ [speech_synthesizer_ setDelegate:delegate_];
+}
+
+ExtensionTtsPlatformImplMac::~ExtensionTtsPlatformImplMac() {
+ [speech_synthesizer_ setDelegate:nil];
}
// static
ExtensionTtsPlatformImplMac* ExtensionTtsPlatformImplMac::GetInstance() {
return Singleton<ExtensionTtsPlatformImplMac>::get();
}
+
+@implementation ChromeTtsDelegate
+
+- (id)initWithPlatformImplMac:(ExtensionTtsPlatformImplMac*)ttsImplMac {
+ if ((self = [super init])) {
+ ttsImplMac_ = ttsImplMac;
+ }
+ return self;
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer*)sender
+ didFinishSpeaking:(BOOL)finished_speaking {
+ ttsImplMac_->OnSpeechEvent(TTS_EVENT_END, 0, "");
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer*)sender
+ willSpeakWord:(NSRange)character_range
+ ofString:(NSString*)string {
+ ttsImplMac_->OnSpeechEvent(TTS_EVENT_WORD, character_range.location, "");
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer*)sender
+ didEncounterErrorAtIndex:(NSUInteger)character_index
+ ofString:(NSString*)string
+ message:(NSString*)message {
+ std::string message_utf8 = base::SysNSStringToUTF8(message);
+ ttsImplMac_->OnSpeechEvent(TTS_EVENT_ERROR, character_index, message_utf8);
+}
+
+@end
diff --git a/chrome/browser/extensions/extension_tts_api_platform.cc b/chrome/browser/extensions/extension_tts_api_platform.cc
new file mode 100644
index 0000000..03578e3
--- /dev/null
+++ b/chrome/browser/extensions/extension_tts_api_platform.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/extension_tts_api_platform.h"
+
+#include <string>
+
+std::string ExtensionTtsPlatformImpl::error() {
+ return error_;
+}
+
+void ExtensionTtsPlatformImpl::clear_error() {
+ error_ = std::string();
+}
+
+void ExtensionTtsPlatformImpl::set_error(const std::string& error) {
+ error_ = error;
+}
diff --git a/chrome/browser/extensions/extension_tts_api_platform.h b/chrome/browser/extensions/extension_tts_api_platform.h
new file mode 100644
index 0000000..f26422f
--- /dev/null
+++ b/chrome/browser/extensions/extension_tts_api_platform.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_PLATFORM_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_PLATFORM_H_
+
+#include <string>
+
+#include "chrome/browser/extensions/extension_tts_api_controller.h"
+
+// Abstract class that defines the native platform TTS interface,
+// subclassed by specific implementations on Win, Mac, etc.
+class ExtensionTtsPlatformImpl {
+ public:
+ static ExtensionTtsPlatformImpl* GetInstance();
+
+ // Returns true if this platform implementation is supported and available.
+ virtual bool PlatformImplAvailable() = 0;
+
+ // Speak the given utterance with the given parameters if possible,
+ // and return true on success. Utterance will always be nonempty.
+ // If rate, pitch, or volume are -1.0, they will be ignored.
+ //
+ // The ExtensionTtsController will only try to speak one utterance at
+ // a time. If it wants to interrupt speech, it will always call Stop
+ // before speaking again.
+ virtual bool Speak(
+ int utterance_id,
+ const std::string& utterance,
+ const std::string& lang,
+ const UtteranceContinuousParameters& params) = 0;
+
+ // Stop speaking immediately and return true on success.
+ virtual bool StopSpeaking() = 0;
+
+ // Return true if this platform implementation will fire the given event.
+ // All platform implementations must fire the TTS_EVENT_END event at a
+ // minimum.
+ virtual bool SendsEvent(TtsEventType event_type) = 0;
+
+ // Return the gender of the voice, should be either "male" or "female"
+ // if known, otherwise the empty string.
+ virtual std::string gender() { return std::string(); }
+
+ virtual std::string error();
+ virtual void clear_error();
+ virtual void set_error(const std::string& error);
+
+ protected:
+ ExtensionTtsPlatformImpl() {}
+ virtual ~ExtensionTtsPlatformImpl() {}
+
+ std::string error_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionTtsPlatformImpl);
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_PLATFORM_H_
diff --git a/chrome/browser/extensions/extension_tts_api_util.cc b/chrome/browser/extensions/extension_tts_api_util.cc
deleted file mode 100644
index 8884966..0000000
--- a/chrome/browser/extensions/extension_tts_api_util.cc
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/extensions/extension_tts_api_util.h"
-
-namespace extension_tts_api_util {
-
-const char kVoiceNameKey[] = "voiceName";
-const char kLocaleKey[] = "locale";
-const char kGenderKey[] = "gender";
-const char kRateKey[] = "rate";
-const char kPitchKey[] = "pitch";
-const char kVolumeKey[] = "volume";
-const char kEnqueueKey[] = "enqueue";
-
-} // namespace extension_tts_api_util.
diff --git a/chrome/browser/extensions/extension_tts_api_util.h b/chrome/browser/extensions/extension_tts_api_util.h
deleted file mode 100644
index 4ca782c..0000000
--- a/chrome/browser/extensions/extension_tts_api_util.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_UTIL_H_
-#define CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_UTIL_H_
-
-#include <string>
-
-#include "base/values.h"
-
-namespace extension_tts_api_util {
-
-extern const char kVoiceNameKey[];
-extern const char kLocaleKey[];
-extern const char kGenderKey[];
-extern const char kRateKey[];
-extern const char kPitchKey[];
-extern const char kVolumeKey[];
-extern const char kEnqueueKey[];
-
-} // namespace extension_tts_api_util.
-#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_UTIL_H_
diff --git a/chrome/browser/extensions/extension_tts_api_win.cc b/chrome/browser/extensions/extension_tts_api_win.cc
index 2b7b54c..7fe146f 100644
--- a/chrome/browser/extensions/extension_tts_api_win.cc
+++ b/chrome/browser/extensions/extension_tts_api_win.cc
@@ -2,10 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "chrome/browser/extensions/extension_tts_api.h"
-
-#include <atlbase.h>
-#include <atlcom.h>
+#include <math.h>
#include <sapi.h>
#include "base/memory/singleton.h"
@@ -13,33 +10,48 @@
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "base/win/scoped_comptr.h"
-
-namespace util = extension_tts_api_util;
+#include "chrome/browser/extensions/extension_tts_api_controller.h"
+#include "chrome/browser/extensions/extension_tts_api_platform.h"
class ExtensionTtsPlatformImplWin : public ExtensionTtsPlatformImpl {
public:
+ virtual bool PlatformImplAvailable() {
+ return true;
+ }
+
virtual bool Speak(
+ int utterance_id,
const std::string& utterance,
- const std::string& language,
- const std::string& gender,
- double rate,
- double pitch,
- double volume);
+ const std::string& lang,
+ const UtteranceContinuousParameters& params);
virtual bool StopSpeaking();
virtual bool IsSpeaking();
+ virtual bool SendsEvent(TtsEventType event_type);
+
// Get the single instance of this class.
static ExtensionTtsPlatformImplWin* GetInstance();
+ static void __stdcall SpeechEventCallback(WPARAM w_param, LPARAM l_param);
+
private:
ExtensionTtsPlatformImplWin();
virtual ~ExtensionTtsPlatformImplWin() {}
+ void OnSpeechEvent();
+
base::win::ScopedComPtr<ISpVoice> speech_synthesizer_;
bool paused_;
+ // These apply to the current utterance only.
+ std::wstring utterance_;
+ int utterance_id_;
+ int prefix_len_;
+ ULONG stream_number_;
+ int char_position_;
+
friend struct DefaultSingletonTraits<ExtensionTtsPlatformImplWin>;
DISALLOW_COPY_AND_ASSIGN(ExtensionTtsPlatformImplWin);
@@ -51,48 +63,61 @@ ExtensionTtsPlatformImpl* ExtensionTtsPlatformImpl::GetInstance() {
}
bool ExtensionTtsPlatformImplWin::Speak(
+ int utterance_id,
const std::string& src_utterance,
- const std::string& language,
- const std::string& gender,
- double rate,
- double pitch,
- double volume) {
- std::wstring utterance = UTF8ToUTF16(src_utterance);
+ const std::string& lang,
+ const UtteranceContinuousParameters& params) {
+ std::wstring prefix;
+ std::wstring suffix;
if (!speech_synthesizer_)
return false;
- // Speech API equivalents for kGenderKey and kLanguageNameKey do not
- // exist and thus are not supported.
+ // TODO(dmazzoni): support languages other than the default: crbug.com/88059
- if (rate >= 0.0) {
- // The TTS api allows a range of -10 to 10 for speech rate.
- speech_synthesizer_->SetRate(static_cast<int32>(rate * 20 - 10));
+ if (params.rate >= 0.0) {
+ // Map our multiplicative range of 0.1x to 10.0x onto Microsoft's
+ // linear range of -10 to 10:
+ // 0.1 -> -10
+ // 1.0 -> 0
+ // 10.0 -> 10
+ speech_synthesizer_->SetRate(static_cast<int32>(10 * log10(params.rate)));
}
- if (pitch >= 0.0) {
+ if (params.pitch >= 0.0) {
// The TTS api allows a range of -10 to 10 for speech pitch.
// TODO(dtseng): cleanup if we ever use any other properties that
// require xml.
std::wstring pitch_value =
- base::IntToString16(static_cast<int>(pitch * 20 - 10));
- utterance = L"<pitch absmiddle=\"" + pitch_value + L"\">" +
- utterance + L"</pitch>";
+ base::IntToString16(static_cast<int>(params.pitch * 10 - 10));
+ prefix = L"<pitch absmiddle=\"" + pitch_value + L"\">";
+ suffix = L"</pitch>";
}
- if (volume >= 0.0) {
+ if (params.volume >= 0.0) {
// The TTS api allows a range of 0 to 100 for speech volume.
- speech_synthesizer_->SetVolume(static_cast<uint16>(volume * 100));
+ speech_synthesizer_->SetVolume(static_cast<uint16>(params.volume * 100));
}
if (paused_) {
speech_synthesizer_->Resume();
paused_ = false;
}
- speech_synthesizer_->Speak(
- utterance.c_str(), SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
- return true;
+ // TODO(dmazzoni): convert SSML to SAPI xml. http://crbug.com/88072
+
+ utterance_ = UTF8ToWide(src_utterance);
+ utterance_id_ = utterance_id;
+ char_position_ = 0;
+ std::wstring merged_utterance = prefix + utterance_ + suffix;
+ prefix_len_ = prefix.size();
+
+
+ HRESULT result = speech_synthesizer_->Speak(
+ merged_utterance.c_str(),
+ SPF_ASYNC | SPF_PURGEBEFORESPEAK,
+ &stream_number_);
+ return (result == S_OK);
}
bool ExtensionTtsPlatformImplWin::StopSpeaking() {
@@ -117,6 +142,51 @@ bool ExtensionTtsPlatformImplWin::IsSpeaking() {
return false;
}
+bool ExtensionTtsPlatformImplWin::SendsEvent(TtsEventType event_type) {
+ return (event_type == TTS_EVENT_START ||
+ event_type == TTS_EVENT_END ||
+ event_type == TTS_EVENT_MARKER ||
+ event_type == TTS_EVENT_WORD ||
+ event_type == TTS_EVENT_SENTENCE);
+}
+
+void ExtensionTtsPlatformImplWin::OnSpeechEvent() {
+ ExtensionTtsController* controller = ExtensionTtsController::GetInstance();
+ SPEVENT event;
+ while (S_OK == speech_synthesizer_->GetEvents(1, &event, NULL)) {
+ if (event.ulStreamNum != stream_number_)
+ continue;
+
+ switch (event.eEventId) {
+ case SPEI_START_INPUT_STREAM:
+ controller->OnTtsEvent(
+ utterance_id_, TTS_EVENT_START, 0, std::string());
+ break;
+ case SPEI_END_INPUT_STREAM:
+ char_position_ = utterance_.size();
+ controller->OnTtsEvent(
+ utterance_id_, TTS_EVENT_END, char_position_, std::string());
+ break;
+ case SPEI_TTS_BOOKMARK:
+ controller->OnTtsEvent(
+ utterance_id_, TTS_EVENT_MARKER, char_position_, std::string());
+ break;
+ case SPEI_WORD_BOUNDARY:
+ char_position_ = static_cast<ULONG>(event.lParam) - prefix_len_;
+ controller->OnTtsEvent(
+ utterance_id_, TTS_EVENT_WORD, char_position_,
+ std::string());
+ break;
+ case SPEI_SENTENCE_BOUNDARY:
+ char_position_ = static_cast<ULONG>(event.lParam) - prefix_len_;
+ controller->OnTtsEvent(
+ utterance_id_, TTS_EVENT_SENTENCE, char_position_,
+ std::string());
+ break;
+ }
+ }
+}
+
ExtensionTtsPlatformImplWin::ExtensionTtsPlatformImplWin()
: speech_synthesizer_(NULL),
paused_(false) {
@@ -126,9 +196,26 @@ ExtensionTtsPlatformImplWin::ExtensionTtsPlatformImplWin()
CLSCTX_SERVER,
IID_ISpVoice,
reinterpret_cast<void**>(&speech_synthesizer_));
+ if (speech_synthesizer_) {
+ ULONGLONG event_mask =
+ SPFEI(SPEI_START_INPUT_STREAM) |
+ SPFEI(SPEI_TTS_BOOKMARK) |
+ SPFEI(SPEI_WORD_BOUNDARY) |
+ SPFEI(SPEI_SENTENCE_BOUNDARY) |
+ SPFEI(SPEI_END_INPUT_STREAM);
+ speech_synthesizer_->SetInterest(event_mask, event_mask);
+ speech_synthesizer_->SetNotifyCallbackFunction(
+ ExtensionTtsPlatformImplWin::SpeechEventCallback, 0, 0);
+ }
}
// static
ExtensionTtsPlatformImplWin* ExtensionTtsPlatformImplWin::GetInstance() {
return Singleton<ExtensionTtsPlatformImplWin>::get();
}
+
+// static
+void ExtensionTtsPlatformImplWin::SpeechEventCallback(
+ WPARAM w_param, LPARAM l_param) {
+ GetInstance()->OnSpeechEvent();
+}
diff --git a/chrome/browser/extensions/extension_tts_apitest.cc b/chrome/browser/extensions/extension_tts_apitest.cc
index 560235d..e1e385f 100644
--- a/chrome/browser/extensions/extension_tts_apitest.cc
+++ b/chrome/browser/extensions/extension_tts_apitest.cc
@@ -1,9 +1,13 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+
#include "base/command_line.h"
+#include "base/task.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_tts_api.h"
+#include "chrome/browser/extensions/extension_tts_api_controller.h"
+#include "chrome/browser/extensions/extension_tts_api_platform.h"
#include "chrome/common/chrome_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -27,19 +31,90 @@ using ::testing::_;
class MockExtensionTtsPlatformImpl : public ExtensionTtsPlatformImpl {
public:
- MOCK_METHOD6(Speak,
- bool(const std::string& utterance,
- const std::string& locale,
- const std::string& gender,
- double rate,
- double pitch,
- double volume));
+ MockExtensionTtsPlatformImpl()
+ : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {}
+
+ virtual bool PlatformImplAvailable() {
+ return true;
+ }
+
+ virtual bool SendsEvent(TtsEventType event_type) {
+ return (event_type == TTS_EVENT_END ||
+ event_type == TTS_EVENT_WORD);
+ }
+
+
+ MOCK_METHOD4(Speak,
+ bool(int utterance_id,
+ const std::string& utterance,
+ const std::string& lang,
+ const UtteranceContinuousParameters& params));
MOCK_METHOD0(StopSpeaking, bool(void));
- MOCK_METHOD0(IsSpeaking, bool(void));
void SetErrorToEpicFail() {
set_error("epic fail");
}
+
+ void SendEndEvent(int utterance_id,
+ const std::string& utterance,
+ const std::string& lang,
+ const UtteranceContinuousParameters& params) {
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, method_factory_.NewRunnableMethod(
+ &MockExtensionTtsPlatformImpl::SendEvent,
+ false, utterance_id, TTS_EVENT_END, utterance.size(),
+ std::string()),
+ 0);
+ }
+
+ void SendEndEventWhenQueueNotEmpty(
+ int utterance_id,
+ const std::string& utterance,
+ const std::string& lang,
+ const UtteranceContinuousParameters& params) {
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, method_factory_.NewRunnableMethod(
+ &MockExtensionTtsPlatformImpl::SendEvent,
+ true, utterance_id, TTS_EVENT_END, utterance.size(), std::string()),
+ 0);
+ }
+
+ void SendWordEvents(int utterance_id,
+ const std::string& utterance,
+ const std::string& lang,
+ const UtteranceContinuousParameters& params) {
+ for (int i = 0; i < static_cast<int>(utterance.size()); i++) {
+ if (i == 0 || utterance[i - 1] == ' ') {
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, method_factory_.NewRunnableMethod(
+ &MockExtensionTtsPlatformImpl::SendEvent,
+ false, utterance_id, TTS_EVENT_WORD, i,
+ std::string()),
+ 0);
+ }
+ }
+ }
+
+ void SendEvent(bool wait_for_non_empty_queue,
+ int utterance_id,
+ TtsEventType event_type,
+ int char_index,
+ const std::string& message) {
+ ExtensionTtsController* controller = ExtensionTtsController::GetInstance();
+ if (wait_for_non_empty_queue && controller->QueueSize() == 0) {
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, method_factory_.NewRunnableMethod(
+ &MockExtensionTtsPlatformImpl::SendEvent,
+ true, utterance_id, event_type, char_index, message),
+ 100);
+ return;
+ }
+
+ controller->OnTtsEvent(utterance_id, event_type, char_index, message);
+ }
+
+ private:
+ ScopedRunnableMethodFactory<MockExtensionTtsPlatformImpl> method_factory_;
};
class TtsApiTest : public ExtensionApiTest {
@@ -63,49 +138,29 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakFinishesImmediately) {
InSequence s;
EXPECT_CALL(mock_platform_impl_, StopSpeaking())
.WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, Speak(_, _, _, _, _, _))
- .WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, IsSpeaking())
- .WillOnce(Return(false));
- ASSERT_TRUE(RunExtensionTest("tts/speak_once")) << message_;
-}
-
-IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakKeepsSpeakingTwice) {
- InSequence s;
- EXPECT_CALL(mock_platform_impl_, StopSpeaking())
- .WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, Speak(_, _, _, _, _, _))
- .WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, IsSpeaking())
- .WillOnce(Return(true))
- .WillOnce(Return(true))
- .WillOnce(Return(false));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, _, _, _))
+ .WillOnce(DoAll(
+ Invoke(&mock_platform_impl_,
+ &MockExtensionTtsPlatformImpl::SendEndEvent),
+ Return(true)));
ASSERT_TRUE(RunExtensionTest("tts/speak_once")) << message_;
}
IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakInterrupt) {
- // One utterances starts speaking, and then a second interrupts.
+ // One utterance starts speaking, and then a second interrupts.
InSequence s;
EXPECT_CALL(mock_platform_impl_, StopSpeaking())
.WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, Speak("text 1", _, _, _, _, _))
+ EXPECT_CALL(mock_platform_impl_, Speak(_, "text 1", _, _))
.WillOnce(Return(true));
-
- // Ensure that the first utterance keeps going until it's interrupted.
- EXPECT_CALL(mock_platform_impl_, IsSpeaking())
- .Times(AnyNumber())
- .WillRepeatedly(Return(true));
-
- // Expect the second utterance and allow it to continue for two calls to
- // IsSpeaking and then finish successfully.
+ // Expect the second utterance and allow it to finish.
EXPECT_CALL(mock_platform_impl_, StopSpeaking())
.WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, Speak("text 2", _, _, _, _, _))
- .WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, IsSpeaking())
- .WillOnce(Return(true))
- .WillOnce(Return(true))
- .WillOnce(Return(false));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, "text 2", _, _))
+ .WillOnce(DoAll(
+ Invoke(&mock_platform_impl_,
+ &MockExtensionTtsPlatformImpl::SendEndEvent),
+ Return(true)));
ASSERT_TRUE(RunExtensionTest("tts/interrupt")) << message_;
}
@@ -115,24 +170,18 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakQueueInterrupt) {
InSequence s;
EXPECT_CALL(mock_platform_impl_, StopSpeaking())
.WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, Speak("text 1", _, _, _, _, _))
+ EXPECT_CALL(mock_platform_impl_, Speak(_, "text 1", _, _))
.WillOnce(Return(true));
-
- // Ensure that the first utterance keeps going until it's interrupted.
- EXPECT_CALL(mock_platform_impl_, IsSpeaking())
- .Times(AnyNumber())
- .WillRepeatedly(Return(true));
-
- // Expect the third utterance and allow it to continue for two calls to
- // IsSpeaking and then finish successfully.
+ // Don't expect the second utterance, because it's queued up and the
+ // first never finishes.
+ // Expect the third utterance and allow it to finish successfully.
EXPECT_CALL(mock_platform_impl_, StopSpeaking())
.WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, Speak("text 3", _, _, _, _, _))
- .WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, IsSpeaking())
- .WillOnce(Return(true))
- .WillOnce(Return(true))
- .WillOnce(Return(false));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, "text 3", _, _))
+ .WillOnce(DoAll(
+ Invoke(&mock_platform_impl_,
+ &MockExtensionTtsPlatformImpl::SendEndEvent),
+ Return(true)));
ASSERT_TRUE(RunExtensionTest("tts/queue_interrupt")) << message_;
}
@@ -140,18 +189,16 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakEnqueue) {
InSequence s;
EXPECT_CALL(mock_platform_impl_, StopSpeaking())
.WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, Speak("text 1", _, _, _, _, _))
- .WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, IsSpeaking())
- .WillOnce(Return(true))
- .WillOnce(Return(true))
- .WillOnce(Return(false));
- EXPECT_CALL(mock_platform_impl_, Speak("text 2", _, _, _, _, _))
- .WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, IsSpeaking())
- .WillOnce(Return(true))
- .WillOnce(Return(true))
- .WillOnce(Return(false));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, "text 1", _, _))
+ .WillOnce(DoAll(
+ Invoke(&mock_platform_impl_,
+ &MockExtensionTtsPlatformImpl::SendEndEventWhenQueueNotEmpty),
+ Return(true)));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, "text 2", _, _))
+ .WillOnce(DoAll(
+ Invoke(&mock_platform_impl_,
+ &MockExtensionTtsPlatformImpl::SendEndEvent),
+ Return(true)));
ASSERT_TRUE(RunExtensionTest("tts/enqueue")) << message_;
}
@@ -159,13 +206,7 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakError) {
InSequence s;
EXPECT_CALL(mock_platform_impl_, StopSpeaking())
.WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, Speak(_, _, _, _, _, _))
- .WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, IsSpeaking())
- .WillOnce(Return(false));
- EXPECT_CALL(mock_platform_impl_, StopSpeaking())
- .WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, Speak(_, _, _, _, _, _))
+ EXPECT_CALL(mock_platform_impl_, Speak(_, "first try", _, _))
.WillOnce(DoAll(
InvokeWithoutArgs(
CreateFunctor(&mock_platform_impl_,
@@ -173,36 +214,66 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakError) {
Return(false)));
EXPECT_CALL(mock_platform_impl_, StopSpeaking())
.WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, Speak(_, _, _, _, _, _))
- .WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, IsSpeaking())
- .WillOnce(Return(false));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, "second try", _, _))
+ .WillOnce(DoAll(
+ Invoke(&mock_platform_impl_,
+ &MockExtensionTtsPlatformImpl::SendEndEvent),
+ Return(true)));
ASSERT_TRUE(RunExtensionTest("tts/speak_error")) << message_;
}
-#if defined(OS_WIN)
-// Flakily fails on Windows: http://crbug.com/70198
-#define MAYBE_Provide FLAKY_Provide
-#else
-#define MAYBE_Provide Provide
-#endif
-IN_PROC_BROWSER_TEST_F(TtsApiTest, MAYBE_Provide) {
+IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformWordCallbacks) {
+ InSequence s;
+ EXPECT_CALL(mock_platform_impl_, StopSpeaking())
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, "one two three", _, _))
+ .WillOnce(DoAll(
+ Invoke(&mock_platform_impl_,
+ &MockExtensionTtsPlatformImpl::SendWordEvents),
+ Invoke(&mock_platform_impl_,
+ &MockExtensionTtsPlatformImpl::SendEndEvent),
+ Return(true)));
+ ASSERT_TRUE(RunExtensionTest("tts/word_callbacks")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(TtsApiTest, RegisterEngine) {
EXPECT_CALL(mock_platform_impl_, StopSpeaking())
.WillRepeatedly(Return(true));
- EXPECT_CALL(mock_platform_impl_, IsSpeaking())
- .WillRepeatedly(Return(false));
{
InSequence s;
- EXPECT_CALL(mock_platform_impl_, Speak("native speech", _, _, _, _, _))
- .WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, Speak("native speech 2", _, _, _, _, _))
- .WillOnce(Return(true));
- EXPECT_CALL(mock_platform_impl_, Speak("native speech 3", _, _, _, _, _))
- .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, "native speech", _, _))
+ .WillOnce(DoAll(
+ Invoke(&mock_platform_impl_,
+ &MockExtensionTtsPlatformImpl::SendEndEvent),
+ Return(true)));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, "native speech 2", _, _))
+ .WillOnce(DoAll(
+ Invoke(&mock_platform_impl_,
+ &MockExtensionTtsPlatformImpl::SendEndEvent),
+ Return(true)));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, "native speech 3", _, _))
+ .WillOnce(DoAll(
+ Invoke(&mock_platform_impl_,
+ &MockExtensionTtsPlatformImpl::SendEndEvent),
+ Return(true)));
}
- ASSERT_TRUE(RunExtensionTest("tts/provide")) << message_;
+ ASSERT_TRUE(RunExtensionTest("tts_engine/register_engine")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(TtsApiTest, EngineError) {
+ EXPECT_CALL(mock_platform_impl_, StopSpeaking())
+ .WillRepeatedly(Return(true));
+
+ ASSERT_TRUE(RunExtensionTest("tts_engine/engine_error")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(TtsApiTest, EngineWordCallbacks) {
+ EXPECT_CALL(mock_platform_impl_, StopSpeaking())
+ .WillRepeatedly(Return(true));
+
+ ASSERT_TRUE(RunExtensionTest("tts_engine/engine_word_callbacks")) << message_;
}
#if defined(OS_CHROMEOS)
diff --git a/chrome/browser/extensions/extension_tts_engine_api.cc b/chrome/browser/extensions/extension_tts_engine_api.cc
new file mode 100644
index 0000000..d69420f
--- /dev/null
+++ b/chrome/browser/extensions/extension_tts_engine_api.cc
@@ -0,0 +1,260 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/extension_tts_engine_api.h"
+
+#include <string>
+
+#include "base/json/json_writer.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/extension_event_router.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_tts_api_constants.h"
+#include "chrome/browser/extensions/extension_tts_api_controller.h"
+#include "chrome/common/extensions/extension.h"
+
+namespace constants = extension_tts_api_constants;
+
+namespace events {
+const char kOnSpeak[] = "experimental.ttsEngine.onSpeak";
+const char kOnStop[] = "experimental.ttsEngine.onStop";
+}; // namespace events
+
+void GetExtensionVoices(Profile* profile, ListValue* result_voices) {
+ ExtensionService* service = profile->GetExtensionService();
+ DCHECK(service);
+ ExtensionEventRouter* event_router = profile->GetExtensionEventRouter();
+ DCHECK(event_router);
+
+ const ExtensionList* extensions = service->extensions();
+ ExtensionList::const_iterator iter;
+ for (iter = extensions->begin(); iter != extensions->end(); ++iter) {
+ const Extension* extension = *iter;
+
+ if (!event_router->ExtensionHasEventListener(
+ extension->id(), events::kOnSpeak) ||
+ !event_router->ExtensionHasEventListener(
+ extension->id(), events::kOnStop)) {
+ continue;
+ }
+
+ const std::vector<Extension::TtsVoice>& tts_voices =
+ extension->tts_voices();
+ for (size_t i = 0; i < tts_voices.size(); ++i) {
+ const Extension::TtsVoice& voice = tts_voices[i];
+ DictionaryValue* result_voice = new DictionaryValue();
+ if (!voice.voice_name.empty())
+ result_voice->SetString(constants::kVoiceNameKey, voice.voice_name);
+ if (!voice.lang.empty())
+ result_voice->SetString(constants::kLangKey, voice.lang);
+ if (!voice.gender.empty())
+ result_voice->SetString(constants::kGenderKey, voice.gender);
+ result_voice->SetString(constants::kExtensionIdKey, extension->id());
+
+ ListValue* event_types = new ListValue();
+ for (std::set<std::string>::const_iterator iter =
+ voice.event_types.begin();
+ iter != voice.event_types.end();
+ ++iter) {
+ event_types->Append(Value::CreateStringValue(*iter));
+ }
+ // If the extension sends end events, the controller will handle
+ // queueing and send interrupted and cancelled events.
+ if (voice.event_types.find(constants::kEventTypeEnd) !=
+ voice.event_types.end()) {
+ event_types->Append(
+ Value::CreateStringValue(constants::kEventTypeCancelled));
+ event_types->Append(Value::CreateStringValue(
+ constants::kEventTypeInterrupted));
+ }
+
+ result_voice->Set(constants::kEventTypesKey, event_types);
+ result_voices->Append(result_voice);
+ }
+ }
+}
+
+bool GetMatchingExtensionVoice(
+ Utterance* utterance,
+ const Extension** matching_extension,
+ size_t* voice_index) {
+ ExtensionService* service = utterance->profile()->GetExtensionService();
+ DCHECK(service);
+ ExtensionEventRouter* event_router =
+ utterance->profile()->GetExtensionEventRouter();
+ DCHECK(event_router);
+
+ *matching_extension = NULL;
+ *voice_index = -1;
+ const ExtensionList* extensions = service->extensions();
+ ExtensionList::const_iterator iter;
+ for (iter = extensions->begin(); iter != extensions->end(); ++iter) {
+ const Extension* extension = *iter;
+
+ if (!event_router->ExtensionHasEventListener(
+ extension->id(), events::kOnSpeak) ||
+ !event_router->ExtensionHasEventListener(
+ extension->id(), events::kOnStop)) {
+ continue;
+ }
+
+ if (!utterance->extension_id().empty() &&
+ utterance->extension_id() != extension->id()) {
+ continue;
+ }
+
+ const std::vector<Extension::TtsVoice>& tts_voices =
+ extension->tts_voices();
+ for (size_t i = 0; i < tts_voices.size(); ++i) {
+ const Extension::TtsVoice& voice = tts_voices[i];
+ if (!voice.voice_name.empty() &&
+ !utterance->voice_name().empty() &&
+ voice.voice_name != utterance->voice_name()) {
+ continue;
+ }
+ if (!voice.lang.empty() &&
+ !utterance->lang().empty() &&
+ voice.lang != utterance->lang()) {
+ continue;
+ }
+ if (!voice.gender.empty() &&
+ !utterance->gender().empty() &&
+ voice.gender != utterance->gender()) {
+ continue;
+ }
+ if (utterance->required_event_types().size() > 0) {
+ bool has_all_required_event_types = true;
+ for (std::set<std::string>::const_iterator iter =
+ utterance->required_event_types().begin();
+ iter != utterance->required_event_types().end();
+ ++iter) {
+ if (voice.event_types.find(*iter) == voice.event_types.end()) {
+ has_all_required_event_types = false;
+ break;
+ }
+ }
+ if (!has_all_required_event_types)
+ continue;
+ }
+
+ *matching_extension = extension;
+ *voice_index = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void ExtensionTtsEngineSpeak(Utterance* utterance,
+ const Extension* extension,
+ size_t voice_index) {
+ // See if the engine supports the "end" event; if so, we can keep the
+ // utterance around and track it. If not, we're finished with this
+ // utterance now.
+ const std::set<std::string> event_types =
+ extension->tts_voices()[voice_index].event_types;
+ bool sends_end_event =
+ (event_types.find(constants::kEventTypeEnd) != event_types.end());
+
+ ListValue args;
+ args.Set(0, Value::CreateStringValue(utterance->text()));
+
+ // Pass through most options to the speech engine, but remove some
+ // that are handled internally.
+ DictionaryValue* options = static_cast<DictionaryValue*>(
+ utterance->options()->DeepCopy());
+ if (options->HasKey(constants::kRequiredEventTypesKey))
+ options->Remove(constants::kRequiredEventTypesKey, NULL);
+ if (options->HasKey(constants::kDesiredEventTypesKey))
+ options->Remove(constants::kDesiredEventTypesKey, NULL);
+ if (sends_end_event && options->HasKey(constants::kEnqueueKey))
+ options->Remove(constants::kEnqueueKey, NULL);
+ if (options->HasKey(constants::kSrcIdKey))
+ options->Remove(constants::kSrcIdKey, NULL);
+ if (options->HasKey(constants::kIsFinalEventKey))
+ options->Remove(constants::kIsFinalEventKey, NULL);
+
+ args.Set(1, options);
+ args.Set(2, Value::CreateIntegerValue(utterance->id()));
+ std::string json_args;
+ base::JSONWriter::Write(&args, false, &json_args);
+
+ utterance->profile()->GetExtensionEventRouter()->DispatchEventToExtension(
+ extension->id(),
+ events::kOnSpeak,
+ json_args,
+ utterance->profile(),
+ GURL());
+}
+
+void ExtensionTtsEngineStop(Utterance* utterance) {
+ utterance->profile()->GetExtensionEventRouter()->DispatchEventToExtension(
+ utterance->extension_id(),
+ events::kOnStop,
+ "[]",
+ utterance->profile(),
+ GURL());
+}
+
+bool ExtensionTtsEngineSendTtsEventFunction::RunImpl() {
+ int utterance_id;
+ std::string error_message;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &utterance_id));
+
+ DictionaryValue* event;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &event));
+
+ std::string event_type;
+ EXTENSION_FUNCTION_VALIDATE(
+ event->GetString(constants::kEventTypeKey, &event_type));
+
+ int char_index = 0;
+ if (event->HasKey(constants::kCharIndexKey)) {
+ EXTENSION_FUNCTION_VALIDATE(
+ event->GetInteger(constants::kCharIndexKey, &char_index));
+ }
+
+ // Make sure the extension has included this event type in its manifest.
+ bool event_type_allowed = false;
+ const Extension* extension = GetExtension();
+ for (size_t i = 0; i < extension->tts_voices().size(); i++) {
+ const Extension::TtsVoice& voice = extension->tts_voices()[i];
+ if (voice.event_types.find(event_type) != voice.event_types.end()) {
+ event_type_allowed = true;
+ break;
+ }
+ }
+ if (!event_type_allowed) {
+ error_ = constants::kErrorUndeclaredEventType;
+ return false;
+ }
+
+ ExtensionTtsController* controller = ExtensionTtsController::GetInstance();
+ if (event_type == constants::kEventTypeStart) {
+ controller->OnTtsEvent(
+ utterance_id, TTS_EVENT_START, char_index, std::string());
+ } else if (event_type == constants::kEventTypeEnd) {
+ controller->OnTtsEvent(
+ utterance_id, TTS_EVENT_END, char_index, std::string());
+ } else if (event_type == constants::kEventTypeWord) {
+ controller->OnTtsEvent(
+ utterance_id, TTS_EVENT_WORD, char_index, std::string());
+ } else if (event_type == constants::kEventTypeSentence) {
+ controller->OnTtsEvent(
+ utterance_id, TTS_EVENT_SENTENCE, char_index, std::string());
+ } else if (event_type == constants::kEventTypeMarker) {
+ controller->OnTtsEvent(
+ utterance_id, TTS_EVENT_MARKER, char_index, std::string());
+ } else if (event_type == constants::kEventTypeError) {
+ std::string error_message;
+ event->GetString(constants::kErrorMessageKey, &error_message);
+ controller->OnTtsEvent(
+ utterance_id, TTS_EVENT_ERROR, char_index, error_message);
+ } else {
+ EXTENSION_FUNCTION_VALIDATE(false);
+ }
+
+ return true;
+}
diff --git a/chrome/browser/extensions/extension_tts_engine_api.h b/chrome/browser/extensions/extension_tts_engine_api.h
new file mode 100644
index 0000000..7c14937
--- /dev/null
+++ b/chrome/browser/extensions/extension_tts_engine_api.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_ENGINE_API_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_ENGINE_API_H_
+
+#include "base/memory/singleton.h"
+#include "chrome/browser/extensions/extension_function.h"
+
+class Extension;
+class Utterance;
+
+// Return a list of all available voices registered by extensions.
+void GetExtensionVoices(Profile* profile, ListValue* result_voices);
+
+// Find the first extension with a tts_voices in its
+// manifest that matches the speech parameters of this utterance.
+// If found, store a pointer to the extension in |matching_extension| and
+// the index of the voice within the extension in |voice_index| and
+// return true.
+bool GetMatchingExtensionVoice(Utterance* utterance,
+ const Extension** matching_extension,
+ size_t* voice_index);
+
+// Speak the given utterance by sending an event to the given TTS engine
+// extension voice.
+void ExtensionTtsEngineSpeak(Utterance* utterance,
+ const Extension* extension,
+ size_t voice_index);
+
+// Stop speaking the given utterance by sending an event to the extension
+// associated with this utterance.
+void ExtensionTtsEngineStop(Utterance* utterance);
+
+// Hidden/internal extension function used to allow TTS engine extensions
+// to send events back to the client that's calling tts.speak().
+class ExtensionTtsEngineSendTtsEventFunction : public SyncExtensionFunction {
+ private:
+ virtual ~ExtensionTtsEngineSendTtsEventFunction() {}
+ virtual bool RunImpl() OVERRIDE;
+ DECLARE_EXTENSION_FUNCTION_NAME("experimental.ttsEngine.sendTtsEvent")
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_ENGINE_API_H_
diff --git a/chrome/browser/extensions/extensions_quota_service_unittest.cc b/chrome/browser/extensions/extensions_quota_service_unittest.cc
index 39cd64e3..feb7da2 100644
--- a/chrome/browser/extensions/extensions_quota_service_unittest.cc
+++ b/chrome/browser/extensions/extensions_quota_service_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -59,6 +59,7 @@ class MockFunction : public ExtensionFunction {
virtual void Destruct() const { delete this; }
virtual bool RunImpl() { return true; }
virtual void SendResponse(bool) { }
+ virtual void SendNonFinalResponse() { }
virtual void HandleBadMessage() { }
};
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 4f7f31b..dfe8fb4 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1038,11 +1038,17 @@
'browser/extensions/extension_tts_api.cc',
'browser/extensions/extension_tts_api.h',
'browser/extensions/extension_tts_api_chromeos.cc',
+ 'browser/extensions/extension_tts_api_constants.cc',
+ 'browser/extensions/extension_tts_api_constants.h',
+ 'browser/extensions/extension_tts_api_controller.cc',
+ 'browser/extensions/extension_tts_api_controller.h',
'browser/extensions/extension_tts_api_linux.cc',
'browser/extensions/extension_tts_api_mac.mm',
- 'browser/extensions/extension_tts_api_util.cc',
- 'browser/extensions/extension_tts_api_util.h',
+ 'browser/extensions/extension_tts_api_platform.cc',
+ 'browser/extensions/extension_tts_api_platform.h',
'browser/extensions/extension_tts_api_win.cc',
+ 'browser/extensions/extension_tts_engine_api.cc',
+ 'browser/extensions/extension_tts_engine_api.h',
'browser/extensions/extension_uninstall_dialog.cc',
'browser/extensions/extension_uninstall_dialog.h',
'browser/extensions/extension_updater.cc',
diff --git a/chrome/common/extensions/api/extension_api.json b/chrome/common/extensions/api/extension_api.json
index ac3510e..6458211 100644
--- a/chrome/common/extensions/api/extension_api.json
+++ b/chrome/common/extensions/api/extension_api.json
@@ -508,6 +508,76 @@
},
{
"namespace": "experimental.tts",
+ "types": [
+ {
+ "id": "TtsEvent",
+ "type": "object",
+ "description": "An event from the TTS engine to communicate the status of an utterance.",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["start", "end", "word", "sentence", "marker", "interrupted", "cancelled", "error"],
+ "description": "The message can be 'start' when this utterance is begun to be spoken, 'word' when a word boundary is reached, 'sentence' when a sentence boundary is reached, 'marker' when an SSML mark element is reached, 'end' when the end of the utterance is reached, 'interrupted' when the utterance is stopped or interrupted before reaching the end, 'cancelled' when it's removed from the queue before ever being synthesized, and 'error' when any other error occurs. Clients will always receive 'end', 'cancelled', 'interrupted', or 'error', and other events will depend on the engine."
+ },
+ "charIndex": {
+ "type": "number",
+ "optional": true,
+ "description": "The index of the current character in the utterance."
+ },
+ "errorMessage": {
+ "type": "string",
+ "description": "The error message, if the message is 'error'.",
+ "optional": true
+ },
+ "srcId": {
+ "type": "number",
+ "description": "An ID unique to the calling function's context so that events can get routed back to the correct tts.speak call.",
+ "nodoc": true,
+ "optional": true
+ },
+ "isFinalEvent": {
+ "type": "boolean",
+ "description": "True if this is the final event that will be sent to this handler.",
+ "nodoc": true,
+ "optional": true
+ }
+ }
+ },
+ {
+ "id": "TtsVoice",
+ "type": "object",
+ "description": "A description of a voice available for speech synthesis.",
+ "properties": {
+ "voiceName": {
+ "type": "string",
+ "optional": true,
+ "description": "The name of the voice."
+ },
+ "lang": {
+ "type": "string",
+ "optional": true,
+ "description": "The language that this voice supports, in the form &lt;language&gt;-&lt;region&gt;. Examples: 'en', 'en-US', 'en-GB', 'zh-CN', etc."
+ },
+ "gender": {
+ "type": "string",
+ "optional": true,
+ "description": "This voice's gender.",
+ "enum": ["male", "female"]
+ },
+ "extensionId": {
+ "type": "string",
+ "optional": true,
+ "description": "The ID of the extension providing this voice."
+ },
+ "eventTypes": {
+ "type": "array",
+ "items": {"type": "string"},
+ "optional": true,
+ "description": "All of the callback event types that this voice is capable of sending."
+ }
+ }
+ }
+ ],
"functions": [
{
"name": "speak",
@@ -517,7 +587,7 @@
{
"type": "string",
"name": "utterance",
- "description": "The text to speak. May include SSML markup."
+ "description": "The text to speak, either plaintext or a complete well-formed SSML document. Speech engines that do not support SSML will strip away the tags and speak the text. The maximum length of the text is 32,768 characters."
},
{
"type": "object",
@@ -535,10 +605,15 @@
"optional": true,
"description": "The name of the voice to use for synthesis. If empty, uses any available voice."
},
- "locale": {
+ "extensionId": {
+ "type": "string",
+ "optional": true,
+ "description": "The specific extension ID of the speech engine to use, if known."
+ },
+ "lang": {
"type": "string",
"optional": true,
- "description": "The language and optional region code that specify the language and dialect to be used for synthesis, in the form &lt;language&gt;-&lt;region&gt;. Examples: 'en', 'en-US', 'en-GB', 'zh-CN', etc."
+ "description": "The language to be used for synthesis, in the form &lt;language&gt;-&lt;region&gt;. Examples: 'en', 'en-US', 'en-GB', 'zh-CN', etc."
},
"gender": {
"type": "string",
@@ -549,16 +624,16 @@
"rate": {
"type": "number",
"optional": true,
- "minimum": 0,
- "maximum": 1,
- "description": "Speaking speed between 0 and 1 inclusive, with 0 being slowest and 1 being fastest, with a default of 0.5."
+ "minimum": 0.1,
+ "maximum": 10,
+ "description": "Speaking rate relative to the default rate for this voice. 1.0 is the default rate, normally around 180 to 220 words per minute, 2.0 would be twice as fast, and 0.5 would be half as fast. Values below 0.1 or above 10.0 are strictly disallowed, but many voices will constrain the minimum and maximum rates further - i.e. a particular voice may not actually speak faster than 3 times normal even if you specify a value larger than 3.0."
},
"pitch": {
"type": "number",
"optional": true,
"minimum": 0,
- "maximum": 1,
- "description": "Speaking pitch between 0 and 1 inclusive, with 0 being lowest and 1 being highest, with a default of 0.5."
+ "maximum": 2,
+ "description": "Speaking pitch between 0 and 2 inclusive, with 0 being lowest and 1 being highest, with 1.0 being the default pitch of this particular voice."
},
"volume": {
"type": "number",
@@ -566,6 +641,30 @@
"minimum": 0,
"maximum": 1,
"description": "Speaking volume between 0 and 1 inclusive, with 0 being lowest and 1 being highest, with a default of 1.0."
+ },
+ "requiredEventTypes": {
+ "type": "array",
+ "items": {"type": "string"},
+ "optional": true,
+ "description": "The TTS event types the voice must support. If missing, this criteria will not be used to filter voices."
+ },
+ "desiredEventTypes": {
+ "type": "array",
+ "items": {"type": "string"},
+ "optional": true,
+ "description": "The TTS event types that should be sent. If missing, all event types will be sent."
+ },
+ "onevent": {
+ "type": "function",
+ "optional": true,
+ "description": "This function is called with events that occur in the process of speaking the utterance.",
+ "parameters": [
+ {
+ "name": "event",
+ "$ref": "TtsEvent",
+ "description": "The update event from the text-to-speech engine indicating the status of this utterance."
+ }
+ ]
}
}
},
@@ -573,7 +672,7 @@
"type": "function",
"name": "callback",
"optional": true,
- "description": "This function is called when speaking is finished.",
+ "description": "Called right away, before speech finishes. Check chrome.extension.lastError to make sure there were no errors. Use options.onevent to get more detailed feedback.",
"parameters": []
}
]
@@ -604,19 +703,44 @@
]
},
{
- "name": "speakCompleted",
+ "name": "getVoices",
+ "type": "function",
+ "description": "Get an array of all available voices.",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "type": "array",
+ "name": "voices",
+ "items": { "$ref": "TtsVoice" },
+ "description": "Array of $ref:TtsVoice objects representing the available voices for speech synthesis."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "namespace": "experimental.ttsEngine",
+ "functions": [
+ {
+ "name": "sendTtsEvent",
"nodoc": true,
"type": "function",
- "description": "A callback passed to the onSpeak event.",
+ "description": "Route a TTS event from a speech engine to a client.",
"parameters": [
{
"type": "integer",
"name": "requestId"
},
{
- "type": "string",
- "optional": "true",
- "name": "errorMessage"
+ "name": "event",
+ "$ref": "TtsEvent",
+ "description": "The update event from the text-to-speech engine indicating the status of this utterance."
}
]
}
@@ -630,7 +754,7 @@
{
"type": "string",
"name": "utterance",
- "description": "The text to speak. This may include SSML, so if your engine does not support SSML, you should strip out all XML markup and synthesize only the underlying text content."
+ "description": "The text to speak. This may include SSML, so if your engine does not support SSML, you should strip out all XML markup and synthesize only the underlying text content. This is guaranteed to be no more than 32,768 characters. If this engine does not support speaking that many characters at a time, the utterance should be split into smaller chunks and queued internally without returning an error."
},
{
"type": "object",
@@ -642,10 +766,10 @@
"optional": true,
"description": "The name of the voice to use for synthesis."
},
- "locale": {
+ "lang": {
"type": "string",
"optional": true,
- "description": "The language and region code that specify the language and dialect to be used for synthesis, in the form <language>-<region>, e.g. en-US, en-GB, fr-CA, zh-CN, etc."
+ "description": "The language to be used for synthesis, in the form <language>-<region>, e.g. en-US, en-GB, fr-CA, zh-CN, etc."
},
"gender": {
"type": "string",
@@ -656,36 +780,35 @@
"rate": {
"type": "number",
"optional": true,
- "minimum": 0,
- "maximum": 1,
- "description": "Speaking speed between 0 and 1 inclusive, with 0 being slowest and 1 being fastest."
+ "minimum": 0.1,
+ "maximum": 10.0,
+ "description": "Speaking rate relative to the default rate for this voice. 1.0 is the default rate, normally around 180 to 220 words per minute, 2.0 would be twice as fast, and 0.5 would be half as fast. This value is guaranteed to be between 0.1 and 10.0, inclusive. When a voice does not support this full range of rates, the actual rate should be clipped to the range that is supported without returning an error."
},
"pitch": {
"type": "number",
"optional": true,
"minimum": 0,
- "maximum": 1,
- "description": "Speaking pitch between 0 and 1 inclusive, with 0 being lowest and 1 being highest."
+ "maximum": 2,
+ "description": "Speaking pitch between 0 and 2 inclusive, with 0 being lowest and 1 being highest, with 1.0 being the default pitch of this particular voice."
},
"volume": {
"type": "number",
"optional": true,
"minimum": 0,
"maximum": 1,
- "description": "Speaking volume between 0 and 1 inclusive, with 0 being lowest and 1 being highest."
+ "description": "Speaking volume between 0 and 1 inclusive, with 0 being lowest and 1 being highest, with a default of 1.0."
}
}
},
{
+ "name": "sendTtsEvent",
"type": "function",
- "name": "callback",
- "description": "You must call this function when speaking is finished.",
+ "description": "Call this function with events that occur in the process of speaking the utterance.",
"parameters": [
{
- "type": "string",
- "name": "error",
- "optional": true,
- "description": "Error message, which will be returned to the caller in chrome.extension.lastError."
+ "name": "event",
+ "$ref": "TtsEvent",
+ "description": "The event from the text-to-speech engine indicating the status of this utterance."
}
]
}
@@ -695,6 +818,19 @@
"name": "onStop",
"type": "function",
"description": "Fired when a call is made to tts.stop and this extension may be in the middle of speaking. If an extension receives a call to onStop and speech is already stopped, it should do nothing (not raise an error)."
+ },
+ {
+ "name": "onEvent",
+ "type": "function",
+ "nodoc": true,
+ "parameters": [
+ {
+ "name": "event",
+ "$ref": "TtsEvent",
+ "description": "The event from the text-to-speech engine indicating the status of this utterance."
+ }
+ ],
+ "description": "Used to pass events back to the function calling speak()."
}
]
},
diff --git a/chrome/common/extensions/docs/experimental.html b/chrome/common/extensions/docs/experimental.html
index 5ac04c5..cd536c5 100644
--- a/chrome/common/extensions/docs/experimental.html
+++ b/chrome/common/extensions/docs/experimental.html
@@ -331,6 +331,7 @@ on the following experimental APIs:
<a href="experimental.processes.html">experimental.processes</a></li><li>
<a href="experimental.sidebar.html">experimental.sidebar</a></li><li>
<a href="experimental.tts.html">experimental.tts</a></li><li>
+ <a href="experimental.ttsEngine.html">experimental.ttsEngine</a></li><li>
<a href="experimental.webNavigation.html">experimental.webNavigation</a></li><li>
<a href="experimental.webRequest.html">experimental.webRequest</a></li>
</ul>
diff --git a/chrome/common/extensions/docs/experimental.tts.html b/chrome/common/extensions/docs/experimental.tts.html
index fb487cd..e3fb2b0 100644
--- a/chrome/common/extensions/docs/experimental.tts.html
+++ b/chrome/common/extensions/docs/experimental.tts.html
@@ -272,6 +272,13 @@
</li>
</ol>
</li><li>
+ <a href="#events">Listening to events</a>
+ <ol>
+ <li style="display: none; ">
+ <a>h3Name</a>
+ </li>
+ </ol>
+ </li><li>
<a href="#ssml">SSML markup</a>
<ol>
<li style="display: none; ">
@@ -279,7 +286,7 @@
</li>
</ol>
</li><li>
- <a href="#provider">Implementing a speech provider</a>
+ <a href="#choosing_voice">Choosing a voice</a>
<ol>
<li style="display: none; ">
<a>h3Name</a>
@@ -301,31 +308,31 @@
<a href="#global-methods">Methods</a>
<ol>
<li>
+ <a href="#method-getVoices">getVoices</a>
+ </li><li>
<a href="#method-isSpeaking">isSpeaking</a>
</li><li>
<a href="#method-speak">speak</a>
- </li><li style="display: none; ">
- <a href="#method-anchor">methodName</a>
</li><li>
<a href="#method-stop">stop</a>
</li>
</ol>
</li>
- <li>
- <a href="#global-events">Events</a>
+ <li style="display: none; ">
+ <a>Events</a>
<ol>
<li>
- <a href="#event-onSpeak">onSpeak</a>
- </li><li>
- <a href="#event-onStop">onStop</a>
+ <a href="#event-anchor">eventName</a>
</li>
</ol>
</li>
- <li style="display: none; ">
+ <li>
<a href="#types">Types</a>
<ol>
<li>
- <a href="#id-anchor">id</a>
+ <a href="#type-TtsEvent">TtsEvent</a>
+ </li><li>
+ <a href="#type-TtsVoice">TtsVoice</a>
</li>
</ol>
</li>
@@ -343,8 +350,10 @@
<!-- STATIC CONTENT PLACEHOLDER -->
<div id="static"><p id="classSummary">
Use the <code>chrome.experimental.tts</code> module to play synthesized
-text-to-speech (TTS) from your extension or packaged app, or to register
-as a speech provider for other extensions and packaged apps that want to speak.
+text-to-speech (TTS) from your extension or packaged app.
+See also the related
+<a href="experimental.ttsEngine.html">experimental.ttsEngine</a>
+module which allows an extension to implement a speech engine.
</p>
<p class="note"><b>Give us feedback:</b> If you have suggestions,
@@ -362,7 +371,7 @@ group.</p>
5), Mac OS X, and Chrome OS, using speech synthesis capabilities
provided by the operating system. On all platforms, the user can
install extensions that register themselves as alternative speech
-synthesis providers.</p>
+engines.</p>
<h2 id="generating_speech">Generating speech</h2>
@@ -371,122 +380,163 @@ packaged app to speak. For example:</p>
<pre>chrome.experimental.tts.speak('Hello, world.');</pre>
+<p>To stop speaking immediately, just call <code>stop()</code>:
+
+</p><pre>chrome.experimental.tts.stop();</pre>
+
<p>You can provide options that control various properties of the speech,
such as its rate, pitch, and more. For example:</p>
-<pre>chrome.experimental.tts.speak('Hello, world.', {'rate': 0.8});</pre>
+<pre>chrome.experimental.tts.speak('Hello, world.', {'rate': 2.0});</pre>
-<p>It's also a good idea to specify the locale so that a synthesizer
+<p>It's also a good idea to specify the language so that a synthesizer
supporting that language (and regional dialect, if applicable) is chosen.</p>
<pre>chrome.experimental.tts.speak(
- 'Hello, world.',
- {
- 'locale': 'en-US',
- 'rate': 0.8
- });</pre>
+ 'Hello, world.', {'lang': 'en-US', 'rate': 2.0});</pre>
-<p>Not all speech engines will support all options.</p>
+<p>By default, each call to <code>speak()</code> will interrupt any
+ongoing speech and speak immediately. To determine if a call would be
+interrupting anything, you can call <code>isSpeaking()</code>, or
+you can use the <code>enqueue</code> option to cause this utterance to
+be added to a queue of utterances that will be spoken when the current
+utterance has finished.
-<p>You can also pass a callback function that will be called when the
-speech has finished. For example, suppose we have an image on our page
-displaying a picture of a face with a closed mouth. We could open the mouth
-while speaking, and close it when done.</p>
-
-<pre>faceImage.src = 'open_mouth.png';
+</p><pre>chrome.experimental.tts.speak(
+ 'Speak this first.');
chrome.experimental.tts.speak(
- 'Hello, world.', null, function() {
- faceImage.src = 'closed_mouth.png';
- });
+ 'Speak this next, when the first sentence is done.', {'enqueue': true});
</pre>
-<p>To stop speaking immediately, just call <code>stop()</code>. Call
-<code>isSpeaking()</code> to find out if a TTS engine is currently speaking.</p>
+<p>A complete description of all options can be found in the
+<a href="#method-speak">speak() method documentation</a> below.
+Not all speech engines will support all options.</p>
+
+<p>To catch errors and make sure you're calling <code>speak()</code>
+correctly, pass a callback function that takes no arguments. Inside
+the callback, check
+<a href="extension.html#property-lastError">chrome.extension.lastError</a>
+to see if there were any errors.</p>
+
+<pre>chrome.experimental.tts.speak(
+ utterance,
+ options,
+ function() {
+ if (chrome.extension.lastError) {
+ console.log('Error: ' + chrome.extension.lastError.message);
+ }
+ });</pre>
-<p>You can check to see if an error occurred by checking
-<code>chrome.extension.lastError</code> inside the callback function.</p>
+<p>The callback returns right away, before the speech engine has started
+generating speech. The purpose of the callback is to alert you to syntax
+errors in your use of the TTS API, not all possible errors that might occur
+in the process of synthesizing and outputting speech. To catch these errors
+too, you need to use an event listener, described below.
-<h2 id="ssml">SSML markup</h2>
+</p><h2 id="events">Listening to events</h2>
+
+<p>To get more real-time information about the status of synthesized speech,
+pass an event listener in the options to <code>speak()</code>, like this:</p>
+
+<pre>chrome.experimental.tts.speak(
+ utterance,
+ {
+ 'onevent': function(event) {
+ console.log('Event ' + event.type ' at position ' + event.charIndex);
+ if (event.type == 'error') {
+ console.log('Error: ' + event.errorMessage);
+ }
+ }
+ },
+ callback);</pre>
+
+<p>Each event includes an event type, the character index of the current
+speech relative to the utterance, and for error events, an optional
+error message. The event types are:</p>
+
+<ul>
+ <li><code>'start'</code>: the engine has started speaking the utterance.
+ </li><li><code>'word'</code>: a word boundary was reached. Use
+ <code>event.charIndex</code> to determine the current speech
+ position.
+ </li><li><code>'sentence'</code>: a sentence boundary was reached. Use
+ <code>event.charIndex</code> to determine the current speech
+ position.
+ </li><li><code>'marker'</code>: an SSML marker was reached. Use
+ <code>event.charIndex</code> to determine the current speech
+ position.
+ </li><li><code>'end'</code>: the engine has finished speaking the utterance.
+ </li><li><code>'interrupted'</code>: this utterance was interrupted by another
+ call to <code>speak()</code> or <code>stop()</code> and did not
+ finish.
+ </li><li><code>'cancelled'</code>: this utterance was queued, but then
+ cancelled by another call to <code>speak()</code> or
+ <code>stop()</code> and never began to speak at all.
+ </li><li><code>'error'</code>: An engine-specific error occurred and
+ this utterance cannot be spoken.
+ Check <code>event.errorMessage</code> for details.
+</li></ul>
+
+<p>Four of the event types, <code>'end'</code>, <code>'interrupted'</code>,
+<code>'cancelled'</code>, and <code>'error'</code>, are <i>final</i>. After
+one of those events is received, this utterance will no longer speak and
+no new events from this utterance will be received.</p>
+
+<p>Some TTS engines may not support all event types, and some may not even
+support any events at all. To require that the speech engine used sends
+the events you're interested in, you can pass a list of event types in
+the <code>requiredEventTypes</code> member of the options object, or use
+<code>getVoices</code> to choose a voice that has the events you need.
+Both are documented below.
+
+</p><h2 id="ssml">SSML markup</h2>
<p>Utterances used in this API may include markup using the
<a href="http://www.w3.org/TR/speech-synthesis">Speech Synthesis Markup
-Language (SSML)</a>. For example:
+Language (SSML)</a>. If you use SSML, the first argument to
+<code>speak()</code> should be a complete SSML document with an XML
+header and a top-level <code>&lt;speak&gt;</code> tag, not a document
+fragment.
+
+For example:
-</p><pre>chrome.experimental.tts.speak('The &lt;emphasis&gt;second&lt;/emphasis&gt; word of this sentence was emphasized.');</pre>
+</p><pre>chrome.experimental.tts.speak(
+ '&lt;?xml version="1.0"?&gt;' +
+ '&lt;speak&gt;' +
+ ' The &lt;emphasis&gt;second&lt;/emphasis&gt; ' +
+ ' word of this sentence was emphasized.' +
+ '&lt;/speak&gt;');</pre>
<p>Not all speech engines will support all SSML tags, and some may not support
-SSML at all, but all engines are expected to ignore any SSML they don't
+SSML at all, but all engines are required to ignore any SSML they don't
support and still speak the underlying text.</p>
-<h2 id="provider">Implementing a speech provider</h2>
-
-<p>An extension can register itself as a speech provider. By doing so, it
-can intercept some or all calls to functions such as
-<code>speak()</code> and <code>stop()</code> and provide an alternate
-implementation. Extensions are free to use any available web technology
-to provide speech, including streaming audio from a server, HTML5 audio,
-Native Client, or Flash. An extension could even do something different
-with the utterances, like display closed captions in a pop-up window or
-send them as log messages to a remote server.</p>
-
-<p>To provide TTS, an extension must first declare all voices it provides
-in the extension manifest, like this:</p>
-
-<pre>{
- "name": "My TTS Provider",
- "version": "1.0",
- <b>"permissions": ["experimental"]
- "tts": {
- "voices": [
- {
- "voiceName": "Alice",
- "locale": "en-US",
- "gender": "female"
- },
- {
- "voiceName": "Pat",
- "locale": "en-US"
+<h2 id="choosing_voice">Choosing a voice</h2>
+
+<p>By default, Chrome will choose the most appropriate voice for each
+utterance you want to speak, based on the language and gender. On most
+Windows, Mac OS X, and Chrome OS systems, speech synthesis provided by
+the operating system should be able to speak any text in at least one
+language. Some users may have a variety of voices available, though,
+from their operating system and from speech engines implemented by other
+Chrome extensions. In those cases, you can implement custom code to choose
+the appropriate voice, or present the user with a list of choices.</p>
+
+<p>To get a list of all voices, call <code>getVoices()</code> and pass it
+a function that receives an array of <code>TtsVoice</code> objects as its
+argument:</p>
+
+<pre>chrome.experimental.tts.getVoices(
+ function(voices) {
+ for (var i = 0; i &lt; voices.length; i++) {
+ console.log('Voice ' + i + ':');
+ console.log(' name: ' + voices[i].voiceName);
+ console.log(' lang: ' + voices[i].lang);
+ console.log(' gender: ' + voices[i].gender);
+ console.log(' extension id: ' + voices[i].extensionId);
+ console.log(' event types: ' + voices[i].eventTypes);
}
- ]
- },</b>
- "background_page": "background.html",
-}</pre>
-
-<p>An extension can specify any number of voices. The three
-parameters—<code>voiceName</code>, <code>locale</code>,
-and <code>gender</code>—are all optional. If they are all unspecified,
-the extension will handle all speech from all clients. If any of them
-are specified, they can be used to filter speech requests. For
-example, if a voice only supports French, it should set the locale to
-'fr' (or something more specific like 'fr-FR') so that only utterances
-in that locale are routed to that extension.</p>
-
-<p>To handle speech calls, the extension should register listeners
-for <code>onSpeak</code> and <code>onStop</code>, like this:</p>
-
-<pre>var speakListener = function(utterance, options, callback) {
- ...
- callback();
-};
-var stopListener = function() {
- ...
-};
-chrome.experimental.tts.onSpeak.addListener(speakListener);
-chrome.experimental.tts.onStop.addListener(stopListener);</pre>
-
-<p class="warning"><b>Important:</b> Don't forget to call the callback
-function from your speak listener!</p>
-
-<p>If an extension does not register listeners for both
-<code>onSpeak</code> and <code>onStop</code>, it will not intercept any
-speech calls, regardless of what is in the manifest.
-
-</p><p>The decision of whether or not to send a given speech request to an
-extension is based solely on whether the extension supports the given voice
-parameters in its manifest and has registered listeners
-for <code>onSpeak</code> and <code>onStop</code>. In other words,
-there's no way for an extension to receive a speech request and
-dynamically decide whether to handle it or not.</p>
+ });</pre>
</div>
<!-- API PAGE -->
@@ -519,6 +569,213 @@ dynamically decide whether to handle it or not.</p>
<!-- iterates over all functions -->
<div class="apiItem">
+ <a name="method-getVoices"></a> <!-- method-anchor -->
+ <h4>getVoices</h4>
+
+ <div class="summary"><span style="display: none; ">void</span>
+ <!-- Note: intentionally longer 80 columns -->
+ <span>chrome.experimental.tts.getVoices</span>(<span class="optional"><span style="display: none; ">, </span><span>function</span>
+ <var><span>callback</span></var></span>)</div>
+
+ <div class="description">
+ <p class="todo" style="display: none; ">Undocumented.</p>
+ <p>Get an array of all available voices.</p>
+
+ <!-- PARAMETERS -->
+ <h4>Parameters</h4>
+ <dl>
+ <div>
+ <div>
+ <dt>
+ <var>callback</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>function</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo">
+ Undocumented.
+ </dd>
+ <dd style="display: none; ">
+ Description of this parameter from the json schema.
+ </dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div>
+ </dl>
+
+ <!-- RETURNS -->
+ <h4 style="display: none; ">Returns</h4>
+ <dl>
+ <div style="display: none; ">
+ <div>
+ </div>
+ </div>
+ </dl>
+
+ <!-- CALLBACK -->
+ <div>
+ <div>
+ <h4>Callback function</h4>
+ <p style="display: none; ">
+ The callback <em>parameter</em> should specify a function
+ that looks like this:
+ </p>
+ <p>
+ If you specify the <em>callback</em> parameter, it should
+ specify a function that looks like this:
+ </p>
+
+ <!-- Note: intentionally longer 80 columns -->
+ <pre>function(<span>array of TtsVoice voices</span>) <span class="subdued">{...}</span>;</pre>
+ <dl>
+ <div>
+ <div>
+ <dt>
+ <var>voices</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional" style="display: none; ">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span>
+ array of <span><span>
+ <span>
+ <a href="experimental.tts.html#type-TtsVoice">TtsVoice</a>
+ </span>
+ <span style="display: none; ">
+ <span>
+ array of <span><span></span></span>
+ </span>
+ <span>paramType</span>
+ <span></span>
+ </span>
+ </span></span>
+ </span>
+ <span style="display: none; ">paramType</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>Array of <a href="experimental.tts.html#type-TtsVoice">TtsVoice</a> objects representing the available voices for speech synthesis.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div>
+ </dl>
+ </div>
+ </div>
+
+ <!-- MIN_VERSION -->
+ <p style="display: none; ">
+ This function was added in version <b><span></span></b>.
+ If you require this function, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </p>
+ </div> <!-- /description -->
+
+ </div><div class="apiItem">
<a name="method-isSpeaking"></a> <!-- method-anchor -->
<h4>isSpeaking</h4>
@@ -763,7 +1020,7 @@ dynamically decide whether to handle it or not.</p>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>The text to speak. May include SSML markup.</dd>
+ <dd>The text to speak, either plaintext or a complete well-formed SSML document. Speech engines that do not support SSML will strip away the tags and speak the text. The maximum length of the text is 32,768 characters.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
@@ -984,7 +1241,7 @@ dynamically decide whether to handle it or not.</p>
</div><div>
<div>
<dt>
- <var>locale</var>
+ <var>extensionId</var>
<em>
<!-- TYPE -->
@@ -1012,7 +1269,75 @@ dynamically decide whether to handle it or not.</p>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>The language and optional region code that specify the language and dialect to be used for synthesis, in the form &lt;language&gt;-&lt;region&gt;. Examples: 'en', 'en-US', 'en-GB', 'zh-CN', etc.</dd>
+ <dd>The specific extension ID of the speech engine to use, if known.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div><div>
+ <div>
+ <dt>
+ <var>lang</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>string</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>The language to be used for synthesis, in the form &lt;language&gt;-&lt;region&gt;. Examples: 'en', 'en-US', 'en-GB', 'zh-CN', etc.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
@@ -1148,7 +1473,7 @@ dynamically decide whether to handle it or not.</p>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>Speaking speed between 0 and 1 inclusive, with 0 being slowest and 1 being fastest, with a default of 0.5.</dd>
+ <dd>Speaking rate relative to the default rate for this voice. 1.0 is the default rate, normally around 180 to 220 words per minute, 2.0 would be twice as fast, and 0.5 would be half as fast. Values below 0.1 or above 10.0 are strictly disallowed, but many voices will constrain the minimum and maximum rates further - i.e. a particular voice may not actually speak faster than 3 times normal even if you specify a value larger than 3.0.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
@@ -1216,7 +1541,7 @@ dynamically decide whether to handle it or not.</p>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>Speaking pitch between 0 and 1 inclusive, with 0 being lowest and 1 being highest, with a default of 0.5.</dd>
+ <dd>Speaking pitch between 0 and 2 inclusive, with 0 being lowest and 1 being highest, with 1.0 being the default pitch of this particular voice.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
@@ -1321,6 +1646,65 @@ dynamically decide whether to handle it or not.</p>
</dd>
</div>
+ </div><div>
+ <div>
+ <dt>
+ <var>requiredEventTypes</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span>
+ array of <span><span>
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>string</span>
+ <span style="display: none; "></span>
+ </span>
+ </span></span>
+ </span>
+ <span style="display: none; ">paramType</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>The TTS event types the voice must support. If missing, this criteria will not be used to filter voices.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
</div>
</dl>
</dd>
@@ -1341,10 +1725,10 @@ dynamically decide whether to handle it or not.</p>
</dd>
</div>
- </div><div>
- <div>
+ </div><div>
+ <div>
<dt>
- <var>callback</var>
+ <var>desiredEventTypes</var>
<em>
<!-- TYPE -->
@@ -1357,10 +1741,21 @@ dynamically decide whether to handle it or not.</p>
<a> Type</a>
</span>
<span>
+ <span>
+ array of <span><span>
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
<span style="display: none; ">
array of <span><span></span></span>
</span>
- <span>function</span>
+ <span>string</span>
+ <span style="display: none; "></span>
+ </span>
+ </span></span>
+ </span>
+ <span style="display: none; ">paramType</span>
<span style="display: none; "></span>
</span>
</span>
@@ -1372,7 +1767,7 @@ dynamically decide whether to handle it or not.</p>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>This function is called when speaking is finished.</dd>
+ <dd>The TTS event types that should be sent. If missing, all event types will be sent.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
@@ -1409,79 +1804,242 @@ dynamically decide whether to handle it or not.</p>
</dd>
</div>
- </div>
- </dl>
+ </div><div>
+ <div>
+ <dt>
+ <var>onevent</var>
+ <em>
- <!-- RETURNS -->
- <h4 style="display: none; ">Returns</h4>
- <dl>
- <div style="display: none; ">
- <div>
- </div>
- </div>
- </dl>
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>function</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
- <!-- CALLBACK -->
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>This function is called with events that occur in the process of speaking the utterance.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
<div>
- <div>
- <h4>Callback function</h4>
- <p style="display: none; ">
- The callback <em>parameter</em> should specify a function
- that looks like this:
- </p>
- <p>
- If you specify the <em>callback</em> parameter, it should
- specify a function that looks like this:
- </p>
+ </div>
+ </div>
+ </dl>
+ </dd>
- <!-- Note: intentionally longer 80 columns -->
- <pre>function(<span></span>) <span class="subdued">{...}</span>;</pre>
- <dl>
- <div style="display: none; ">
- <div>
- </div>
- </div>
- </dl>
- </div>
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd>
+ <div>
+ <h5>Parameters</h5>
+ <dl>
+ <div>
+ <div>
+ <dt>
+ <var>event</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional" style="display: none; ">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span>
+ <a href="experimental.tts.html#type-TtsEvent">TtsEvent</a>
+ </span>
+ <span style="display: none; ">
+ <span>
+ array of <span><span></span></span>
+ </span>
+ <span>paramType</span>
+ <span></span>
+ </span>
+ </span>
+ )
</div>
- <!-- MIN_VERSION -->
- <p style="display: none; ">
- This function was added in version <b><span></span></b>.
- If you require this function, the manifest key
- <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
- can ensure that your extension won't be run in an earlier browser version.
- </p>
- </div> <!-- /description -->
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>The update event from the text-to-speech engine indicating the status of this utterance.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
- </div><div class="apiItem" style="display: none; ">
- <a></a> <!-- method-anchor -->
- <h4>method name</h4>
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
- <div class="summary"><span>void</span>
- <!-- Note: intentionally longer 80 columns -->
- <span>chrome.module.methodName</span>(<span><span>, </span><span></span>
- <var><span></span></var></span>)</div>
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
- <div class="description">
- <p class="todo">Undocumented.</p>
- <p>
- A description from the json schema def of the function goes here.
- </p>
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
- <!-- PARAMETERS -->
- <h4>Parameters</h4>
- <dl>
- <div>
+ </div>
+ </div>
+ </dl>
+ </div>
+ </dd>
+
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div><div>
<div>
- </div>
+ <dt>
+ <var>callback</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>function</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>Called right away, before speech finishes. Check chrome.extension.lastError to make sure there were no errors. Use options.onevent to get more detailed feedback.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
</div>
</dl>
<!-- RETURNS -->
- <h4>Returns</h4>
+ <h4 style="display: none; ">Returns</h4>
<dl>
- <div>
+ <div style="display: none; ">
<div>
</div>
</div>
@@ -1491,7 +2049,7 @@ dynamically decide whether to handle it or not.</p>
<div>
<div>
<h4>Callback function</h4>
- <p>
+ <p style="display: none; ">
The callback <em>parameter</em> should specify a function
that looks like this:
</p>
@@ -1501,9 +2059,9 @@ dynamically decide whether to handle it or not.</p>
</p>
<!-- Note: intentionally longer 80 columns -->
- <pre>function(<span>Type param1, Type param2</span>) <span class="subdued">{...}</span>;</pre>
+ <pre>function(<span></span>) <span class="subdued">{...}</span>;</pre>
<dl>
- <div>
+ <div style="display: none; ">
<div>
</div>
</div>
@@ -1512,7 +2070,7 @@ dynamically decide whether to handle it or not.</p>
</div>
<!-- MIN_VERSION -->
- <p>
+ <p style="display: none; ">
This function was added in version <b><span></span></b>.
If you require this function, the manifest key
<a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
@@ -1589,22 +2147,24 @@ dynamically decide whether to handle it or not.</p>
</div> <!-- /apiGroup -->
<!-- EVENTS -->
- <div id="eventsTemplate" class="apiGroup">
- <a name="global-events"></a>
+ <div id="eventsTemplate" class="apiGroup" style="display: none; ">
+ <a></a>
<h3>Events</h3>
<!-- iterates over all events -->
<div class="apiItem">
- <a name="event-onSpeak"></a>
- <h4>onSpeak</h4>
+ <a></a>
+ <h4>event name</h4>
<div class="summary">
<!-- Note: intentionally longer 80 columns -->
- <span class="subdued">chrome.experimental.tts.</span><span>onSpeak</span><span class="subdued">.addListener</span>(function(<span>string utterance, object options, function callback</span>) <span class="subdued">{...}</span><span></span>));
+ <span class="subdued">chrome.bookmarks</span><span>onEvent</span><span class="subdued">.addListener</span>(function(<span>Type param1, Type param2</span>) <span class="subdued">{...}</span><span>, Type opt_param1, Type opt_param2</span>));
</div>
<div class="description">
- <p class="todo" style="display: none; ">Undocumented.</p>
- <p>Called when the user makes a call to tts.speak and the options matches one of the tts_voices from this extension's manifest.</p>
+ <p class="todo">Undocumented.</p>
+ <p>
+ A description from the json schema def of the event goes here.
+ </p>
<!-- LISTENER PARAMETERS -->
<div>
@@ -1612,8 +2172,49 @@ dynamically decide whether to handle it or not.</p>
<dl>
<div>
<div>
+ </div>
+ </div>
+ </dl>
+ </div>
+
+ <!-- EXTRA PARAMETERS -->
+ <div>
+ <h4>Extra parameters to addListener</h4>
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </div>
+
+ <!-- LISTENER RETURN VALUE -->
+ <h4>Listener returns</h4>
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+
+ </div> <!-- /description -->
+ </div> <!-- /apiItem -->
+
+ </div> <!-- /apiGroup -->
+
+ <!-- TYPES -->
+ <div class="apiGroup">
+ <a name="types"></a>
+ <h3 id="types">Types</h3>
+
+ <!-- iterates over all types -->
+ <div class="apiItem">
+ <a name="type-TtsEvent"></a>
+ <h4>TtsEvent</h4>
+
+ <div>
<dt>
- <var>utterance</var>
+ <var style="display: none; ">paramName</var>
<em>
<!-- TYPE -->
@@ -1629,7 +2230,7 @@ dynamically decide whether to handle it or not.</p>
<span style="display: none; ">
array of <span><span></span></span>
</span>
- <span>string</span>
+ <span>object</span>
<span style="display: none; "></span>
</span>
</span>
@@ -1641,7 +2242,7 @@ dynamically decide whether to handle it or not.</p>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>The text to speak. This may include SSML, so if your engine does not support SSML, you should strip out all XML markup and synthesize only the underlying text content.</dd>
+ <dd>An event from the TTS engine to communicate the status of an utterance.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
@@ -1653,42 +2254,19 @@ dynamically decide whether to handle it or not.</p>
</dd>
<!-- OBJECT PROPERTIES -->
- <dd style="display: none; ">
+ <dd>
<dl>
<div>
<div>
- </div>
- </div>
- </dl>
- </dd>
-
- <!-- OBJECT METHODS -->
- <dd style="display: none; ">
- <div></div>
- </dd>
-
- <!-- OBJECT EVENT FIELDS -->
- <dd style="display: none; ">
- <div></div>
- </dd>
-
- <!-- FUNCTION PARAMETERS -->
- <dd style="display: none; ">
- <div></div>
- </dd>
-
- </div>
- </div><div>
- <div>
<dt>
- <var>options</var>
+ <var>type</var>
<em>
<!-- TYPE -->
<div style="display:inline">
(
<span class="optional" style="display: none; ">optional</span>
- <span class="enum" style="display: none; ">enumerated</span>
+ <span class="enum">enumerated</span>
<span id="typeTemplate">
<span style="display: none; ">
<a> Type</a>
@@ -1697,8 +2275,8 @@ dynamically decide whether to handle it or not.</p>
<span style="display: none; ">
array of <span><span></span></span>
</span>
- <span>object</span>
- <span style="display: none; "></span>
+ <span>string</span>
+ <span>["start", "end", "word", "sentence", "marker", "interrupted", "cancelled", "error"]</span>
</span>
</span>
)
@@ -1709,7 +2287,7 @@ dynamically decide whether to handle it or not.</p>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>The speak options.</dd>
+ <dd>The message can be 'start' when this utterance is begun to be spoken, 'word' when a word boundary is reached, 'sentence' when a sentence boundary is reached, 'marker' when an SSML mark element is reached, 'end' when the end of the utterance is reached, 'interrupted' when the utterance is stopped or interrupted before reaching the end, 'cancelled' when it's removed from the queue before ever being synthesized, and 'error' when any other error occurs. Clients will always receive 'end', 'cancelled', 'interrupted', or 'error', and other events will depend on the engine.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
@@ -1721,12 +2299,35 @@ dynamically decide whether to handle it or not.</p>
</dd>
<!-- OBJECT PROPERTIES -->
- <dd>
+ <dd style="display: none; ">
<dl>
<div>
<div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div><div>
+ <div>
<dt>
- <var>voiceName</var>
+ <var>charIndex</var>
<em>
<!-- TYPE -->
@@ -1742,7 +2343,7 @@ dynamically decide whether to handle it or not.</p>
<span style="display: none; ">
array of <span><span></span></span>
</span>
- <span>string</span>
+ <span>number</span>
<span style="display: none; "></span>
</span>
</span>
@@ -1754,7 +2355,7 @@ dynamically decide whether to handle it or not.</p>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>The name of the voice to use for synthesis.</dd>
+ <dd>The index of the current character in the utterance.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
@@ -1794,7 +2395,7 @@ dynamically decide whether to handle it or not.</p>
</div><div>
<div>
<dt>
- <var>locale</var>
+ <var>errorMessage</var>
<em>
<!-- TYPE -->
@@ -1822,7 +2423,7 @@ dynamically decide whether to handle it or not.</p>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>The language and region code that specify the language and dialect to be used for synthesis, in the form <language>-<region>, e.g. en-US, en-GB, fr-CA, zh-CN, etc.</region></language></dd>
+ <dd>The error message, if the message is 'error'.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
@@ -1859,17 +2460,86 @@ dynamically decide whether to handle it or not.</p>
</dd>
</div>
- </div><div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+
+ </div><div class="apiItem">
+ <a name="type-TtsVoice"></a>
+ <h4>TtsVoice</h4>
+
+ <div>
+ <dt>
+ <var style="display: none; ">paramName</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional" style="display: none; ">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>object</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>A description of a voice available for speech synthesis.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd>
+ <dl>
+ <div>
<div>
<dt>
- <var>gender</var>
+ <var>voiceName</var>
<em>
<!-- TYPE -->
<div style="display:inline">
(
<span class="optional">optional</span>
- <span class="enum">enumerated</span>
+ <span class="enum" style="display: none; ">enumerated</span>
<span id="typeTemplate">
<span style="display: none; ">
<a> Type</a>
@@ -1879,7 +2549,7 @@ dynamically decide whether to handle it or not.</p>
array of <span><span></span></span>
</span>
<span>string</span>
- <span>["male", "female"]</span>
+ <span style="display: none; "></span>
</span>
</span>
)
@@ -1890,7 +2560,7 @@ dynamically decide whether to handle it or not.</p>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>Gender of voice for synthesized speech.</dd>
+ <dd>The name of the voice.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
@@ -1930,7 +2600,7 @@ dynamically decide whether to handle it or not.</p>
</div><div>
<div>
<dt>
- <var>rate</var>
+ <var>lang</var>
<em>
<!-- TYPE -->
@@ -1946,7 +2616,7 @@ dynamically decide whether to handle it or not.</p>
<span style="display: none; ">
array of <span><span></span></span>
</span>
- <span>number</span>
+ <span>string</span>
<span style="display: none; "></span>
</span>
</span>
@@ -1958,7 +2628,7 @@ dynamically decide whether to handle it or not.</p>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>Speaking speed between 0 and 1 inclusive, with 0 being slowest and 1 being fastest.</dd>
+ <dd>The language that this voice supports, in the form &lt;language&gt;-&lt;region&gt;. Examples: 'en', 'en-US', 'en-GB', 'zh-CN', etc.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
@@ -1998,14 +2668,14 @@ dynamically decide whether to handle it or not.</p>
</div><div>
<div>
<dt>
- <var>pitch</var>
+ <var>gender</var>
<em>
<!-- TYPE -->
<div style="display:inline">
(
<span class="optional">optional</span>
- <span class="enum" style="display: none; ">enumerated</span>
+ <span class="enum">enumerated</span>
<span id="typeTemplate">
<span style="display: none; ">
<a> Type</a>
@@ -2014,8 +2684,8 @@ dynamically decide whether to handle it or not.</p>
<span style="display: none; ">
array of <span><span></span></span>
</span>
- <span>number</span>
- <span style="display: none; "></span>
+ <span>string</span>
+ <span>["male", "female"]</span>
</span>
</span>
)
@@ -2026,7 +2696,7 @@ dynamically decide whether to handle it or not.</p>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>Speaking pitch between 0 and 1 inclusive, with 0 being lowest and 1 being highest.</dd>
+ <dd>This voice's gender.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
@@ -2066,7 +2736,7 @@ dynamically decide whether to handle it or not.</p>
</div><div>
<div>
<dt>
- <var>volume</var>
+ <var>extensionId</var>
<em>
<!-- TYPE -->
@@ -2082,7 +2752,7 @@ dynamically decide whether to handle it or not.</p>
<span style="display: none; ">
array of <span><span></span></span>
</span>
- <span>number</span>
+ <span>string</span>
<span style="display: none; "></span>
</span>
</span>
@@ -2094,7 +2764,7 @@ dynamically decide whether to handle it or not.</p>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>Speaking volume between 0 and 1 inclusive, with 0 being lowest and 1 being highest.</dd>
+ <dd>The ID of the extension providing this voice.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
@@ -2131,46 +2801,37 @@ dynamically decide whether to handle it or not.</p>
</dd>
</div>
- </div>
- </dl>
- </dd>
-
- <!-- OBJECT METHODS -->
- <dd style="display: none; ">
- <div></div>
- </dd>
-
- <!-- OBJECT EVENT FIELDS -->
- <dd style="display: none; ">
- <div></div>
- </dd>
-
- <!-- FUNCTION PARAMETERS -->
- <dd style="display: none; ">
- <div></div>
- </dd>
-
- </div>
- </div><div>
- <div>
+ </div><div>
+ <div>
<dt>
- <var>callback</var>
+ <var>eventTypes</var>
<em>
<!-- TYPE -->
<div style="display:inline">
(
- <span class="optional" style="display: none; ">optional</span>
+ <span class="optional">optional</span>
<span class="enum" style="display: none; ">enumerated</span>
<span id="typeTemplate">
<span style="display: none; ">
<a> Type</a>
</span>
<span>
+ <span>
+ array of <span><span>
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
<span style="display: none; ">
array of <span><span></span></span>
</span>
- <span>function</span>
+ <span>string</span>
+ <span style="display: none; "></span>
+ </span>
+ </span></span>
+ </span>
+ <span style="display: none; ">paramType</span>
<span style="display: none; "></span>
</span>
</span>
@@ -2182,7 +2843,7 @@ dynamically decide whether to handle it or not.</p>
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>You must call this function when speaking is finished.</dd>
+ <dd>All of the callback event types that this voice is capable of sending.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
@@ -2219,92 +2880,26 @@ dynamically decide whether to handle it or not.</p>
</dd>
</div>
- </div>
- </dl>
- </div>
-
- <!-- EXTRA PARAMETERS -->
- <div style="display: none; ">
- <h4>Extra parameters to addListener</h4>
- <dl>
- <div>
- <div>
- </div>
- </div>
- </dl>
- </div>
-
- <!-- LISTENER RETURN VALUE -->
- <h4 style="display: none; ">Listener returns</h4>
- <dl>
- <div style="display: none; ">
- <div>
- </div>
- </div>
- </dl>
-
- </div> <!-- /description -->
- </div><div class="apiItem">
- <a name="event-onStop"></a>
- <h4>onStop</h4>
-
- <div class="summary">
- <!-- Note: intentionally longer 80 columns -->
- <span class="subdued">chrome.experimental.tts.</span><span>onStop</span><span class="subdued">.addListener</span>(function(<span></span>) <span class="subdued">{...}</span><span></span>));
</div>
+ </dl>
+ </dd>
- <div class="description">
- <p class="todo" style="display: none; ">Undocumented.</p>
- <p>Fired when a call is made to tts.stop and this extension may be in the middle of speaking. If an extension receives a call to onStop and speech is already stopped, it should do nothing (not raise an error).</p>
-
- <!-- LISTENER PARAMETERS -->
- <div style="display: none; ">
- <h4>Listener parameters</h4>
- <dl>
- <div>
- <div>
- </div>
- </div>
- </dl>
- </div>
-
- <!-- EXTRA PARAMETERS -->
- <div style="display: none; ">
- <h4>Extra parameters to addListener</h4>
- <dl>
- <div>
- <div>
- </div>
- </div>
- </dl>
- </div>
-
- <!-- LISTENER RETURN VALUE -->
- <h4 style="display: none; ">Listener returns</h4>
- <dl>
- <div style="display: none; ">
- <div>
- </div>
- </div>
- </dl>
-
- </div> <!-- /description -->
- </div> <!-- /apiItem -->
-
- </div> <!-- /apiGroup -->
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
- <!-- TYPES -->
- <div class="apiGroup" style="display: none; ">
- <a name="types"></a>
- <h3 id="types">Types</h3>
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
- <!-- iterates over all types -->
- <div class="apiItem">
- <a></a>
- <h4>type name</h4>
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
- <div>
- </div>
+ </div>
</div> <!-- /apiItem -->
diff --git a/chrome/common/extensions/docs/experimental.ttsEngine.html b/chrome/common/extensions/docs/experimental.ttsEngine.html
new file mode 100644
index 0000000..d92354f
--- /dev/null
+++ b/chrome/common/extensions/docs/experimental.ttsEngine.html
@@ -0,0 +1,1468 @@
+<!DOCTYPE html><!-- This page is a placeholder for generated extensions api doc. Note:
+ 1) The <head> information in this page is significant, should be uniform
+ across api docs and should be edited only with knowledge of the
+ templating mechanism.
+ 3) All <body>.innerHTML is genereated as an rendering step. If viewed in a
+ browser, it will be re-generated from the template, json schema and
+ authored overview content.
+ 4) The <body>.innerHTML is also generated by an offline step so that this
+ page may easily be indexed by search engines.
+--><html xmlns="http://www.w3.org/1999/xhtml"><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link href="css/ApiRefStyles.css" rel="stylesheet" type="text/css">
+ <link href="css/print.css" rel="stylesheet" type="text/css" media="print">
+ <script type="text/javascript" src="../../../third_party/jstemplate/jstemplate_compiled.js">
+ </script>
+ <script type="text/javascript" src="js/api_page_generator.js"></script>
+ <script type="text/javascript" src="js/bootstrap.js"></script>
+ <script type="text/javascript" src="js/sidebar.js"></script>
+ <title>chrome.experimental.ttsEngine - Google Chrome Extensions - Google Code</title></head>
+ <body> <div id="gc-container" class="labs">
+ <div id="devModeWarning">
+ You are viewing extension docs in chrome via the 'file:' scheme: are you expecting to see local changes when you refresh? You'll need run chrome with --allow-file-access-from-files.
+ </div>
+ <!-- SUBTEMPLATES: DO NOT MOVE FROM THIS LOCATION -->
+ <!-- In particular, sub-templates that recurse, must be used by allowing
+ jstemplate to make a copy of the template in this section which
+ are not operated on by way of the jsskip="true" -->
+ <div style="display:none">
+
+ <!-- VALUE -->
+ <div id="valueTemplate">
+ <dt>
+ <var>paramName</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional">optional</span>
+ <span class="enum">enumerated</span>
+ <span id="typeTemplate">
+ <span>
+ <a> Type</a>
+ </span>
+ <span>
+ <span>
+ array of <span><span></span></span>
+ </span>
+ <span>paramType</span>
+ <span></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo">
+ Undocumented.
+ </dd>
+ <dd>
+ Description of this parameter from the json schema.
+ </dd>
+ <dd>
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd>
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd>
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd>
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd>
+ <div></div>
+ </dd>
+
+ </div> <!-- /VALUE -->
+
+ <div id="functionParametersTemplate">
+ <h5>Parameters</h5>
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </div>
+ </div> <!-- /SUBTEMPLATES -->
+
+ <a id="top"></a>
+ <div id="skipto">
+ <a href="#gc-pagecontent">Skip to page content</a>
+ <a href="#gc-toc">Skip to main navigation</a>
+ </div>
+ <!-- API HEADER -->
+ <table id="header" width="100%" cellspacing="0" border="0">
+ <tbody><tr>
+ <td valign="middle"><a href="http://code.google.com/"><img src="images/code_labs_logo.gif" height="43" width="161" alt="Google Code Labs" style="border:0; margin:0;"></a></td>
+ <td valign="middle" width="100%" style="padding-left:0.6em;">
+ <form action="http://www.google.com/cse" id="cse" style="margin-top:0.5em">
+ <div id="gsc-search-box">
+ <input type="hidden" name="cx" value="002967670403910741006:61_cvzfqtno">
+ <input type="hidden" name="ie" value="UTF-8">
+ <input type="text" name="q" value="" size="55">
+ <input class="gsc-search-button" type="submit" name="sa" value="Search">
+ <br>
+ <span class="greytext">e.g. "page action" or "tabs"</span>
+ </div>
+ </form>
+
+ <script type="text/javascript" src="http://www.google.com/jsapi"></script>
+ <script type="text/javascript">google.load("elements", "1", {packages: "transliteration"});</script>
+ <script type="text/javascript" src="http://www.google.com/coop/cse/t13n?form=cse&amp;t13n_langs=en"></script>
+ <script type="text/javascript" src="http://www.google.com/coop/cse/brand?form=cse&amp;lang=en"></script>
+ </td>
+ </tr>
+ </tbody></table>
+
+ <div id="codesiteContent" class="">
+
+ <a id="gc-topnav-anchor"></a>
+ <div id="gc-topnav">
+ <h1>Google Chrome Extensions (<a href="http://code.google.com/labs/">Labs</a>)</h1>
+ <ul id="home" class="gc-topnav-tabs">
+ <li id="home_link">
+ <a href="index.html" title="Google Chrome Extensions home page">Home</a>
+ </li>
+ <li id="docs_link">
+ <a href="docs.html" title="Official Google Chrome Extensions documentation">Docs</a>
+ </li>
+ <li id="faq_link">
+ <a href="faq.html" title="Answers to frequently asked questions about Google Chrome Extensions">FAQ</a>
+ </li>
+ <li id="samples_link">
+ <a href="samples.html" title="Sample extensions (with source code)">Samples</a>
+ </li>
+ <li id="group_link">
+ <a href="http://groups.google.com/a/chromium.org/group/chromium-extensions" title="Google Chrome Extensions developer forum">Group</a>
+ </li>
+ </ul>
+ </div> <!-- end gc-topnav -->
+
+ <div class="g-section g-tpl-170">
+ <!-- SIDENAV -->
+ <div class="g-unit g-first" id="gc-toc">
+ <ul>
+ <li><a href="getstarted.html">Getting Started</a></li>
+ <li><a href="overview.html">Overview</a></li>
+ <li><a href="whats_new.html">What's New?</a></li>
+ <li><h2><a href="devguide.html">Developer's Guide</a></h2>
+ <ul>
+ <li>Browser UI
+ <ul>
+ <li><a href="browserAction.html">Browser Actions</a></li>
+ <li><a href="contextMenus.html">Context Menus</a></li>
+ <li><a href="notifications.html">Desktop Notifications</a></li>
+ <li><a href="omnibox.html">Omnibox</a></li>
+ <li><a href="options.html">Options Pages</a></li>
+ <li><a href="override.html">Override Pages</a></li>
+ <li><a href="pageAction.html">Page Actions</a></li>
+ </ul>
+ </li>
+ <li>Browser Interaction
+ <ul>
+ <li><a href="bookmarks.html">Bookmarks</a></li>
+ <li><a href="cookies.html">Cookies</a></li>
+ <li><a href="events.html">Events</a></li>
+ <li><a href="history.html">History</a></li>
+ <li><a href="management.html">Management</a></li>
+ <li><a href="tabs.html">Tabs</a></li>
+ <li><a href="windows.html">Windows</a></li>
+ </ul>
+ </li>
+ <li>Implementation
+ <ul>
+ <li><a href="a11y.html">Accessibility</a></li>
+ <li><a href="background_pages.html">Background Pages</a></li>
+ <li><a href="content_scripts.html">Content Scripts</a></li>
+ <li><a href="xhr.html">Cross-Origin XHR</a></li>
+ <li><a href="idle.html">Idle</a></li>
+ <li><a href="i18n.html">Internationalization</a></li>
+ <li><a href="messaging.html">Message Passing</a></li>
+ <li><a href="npapi.html">NPAPI Plugins</a></li>
+ </ul>
+ </li>
+ <li>Finishing
+ <ul>
+ <li><a href="hosting.html">Hosting</a></li>
+ <li><a href="external_extensions.html">Other Deployment Options</a></li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ <li><h2><a href="apps.html">Packaged Apps</a></h2></li>
+ <li><h2><a href="tutorials.html">Tutorials</a></h2>
+ <ul>
+ <li><a href="tut_debugging.html">Debugging</a></li>
+ <li><a href="tut_analytics.html">Google Analytics</a></li>
+ <li><a href="tut_oauth.html">OAuth</a></li>
+ </ul>
+ </li>
+ <li><h2>Reference</h2>
+ <ul>
+ <li>Formats
+ <ul>
+ <li><a href="manifest.html">Manifest Files</a></li>
+ <li><a href="match_patterns.html">Match Patterns</a></li>
+ </ul>
+ </li>
+ <li><a href="permission_warnings.html">Permission Warnings</a></li>
+ <li><a href="api_index.html">chrome.* APIs</a></li>
+ <li><a href="api_other.html">Other APIs</a></li>
+ </ul>
+ </li>
+ <li><h2><a href="samples.html">Samples</a></h2></li>
+ <div class="line"> </div>
+ <li><h2>More</h2>
+ <ul>
+ <li><a href="http://code.google.com/chrome/webstore/docs/index.html">Chrome Web Store</a></li>
+ <li><a href="http://code.google.com/chrome/apps/docs/developers_guide.html">Hosted Apps</a></li>
+ <li><a href="themes.html">Themes</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ <script>
+ initToggles();
+ </script>
+
+ <div class="g-unit" id="gc-pagecontent">
+ <div id="pageTitle">
+ <h1 class="page_title">chrome.experimental.ttsEngine</h1>
+ </div>
+ <!-- TABLE OF CONTENTS -->
+ <div id="toc">
+ <h2>Contents</h2>
+ <ol>
+ <li>
+ <a href="#overview">Overview</a>
+ <ol>
+ <li style="display: none; ">
+ <a>h3Name</a>
+ </li>
+ </ol>
+ </li><li>
+ <a href="#manifest">Manifest</a>
+ <ol>
+ <li style="display: none; ">
+ <a>h3Name</a>
+ </li>
+ </ol>
+ </li><li>
+ <a href="#handling_speech_events">Handling Speech Events</a>
+ <ol>
+ <li style="display: none; ">
+ <a>h3Name</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="#apiReference">API reference: chrome.experimental.ttsEngine</a>
+ <ol>
+ <li style="display: none; ">
+ <a href="#properties">Properties</a>
+ <ol>
+ <li>
+ <a href="#property-anchor">propertyName</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="#global-methods">Methods</a>
+ <ol>
+ <li style="display: none; ">
+ <a href="#method-anchor">methodName</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="#global-events">Events</a>
+ <ol>
+ <li style="display: none; ">
+ <a href="#event-anchor">eventName</a>
+ </li><li>
+ <a href="#event-onSpeak">onSpeak</a>
+ </li><li>
+ <a href="#event-onStop">onStop</a>
+ </li>
+ </ol>
+ </li>
+ <li style="display: none; ">
+ <a href="#types">Types</a>
+ <ol>
+ <li>
+ <a href="#id-anchor">id</a>
+ </li>
+ </ol>
+ </li>
+ </ol>
+ </li>
+ </ol>
+ </div>
+ <!-- /TABLE OF CONTENTS -->
+
+ <!-- Standard content lead-in for experimental API pages -->
+ <p id="classSummary">
+ For information on how to use experimental APIs, see the <a href="experimental.html">chrome.experimental.* APIs</a> page.
+ </p>
+
+ <!-- STATIC CONTENT PLACEHOLDER -->
+ <div id="static"><p id="classSummary">
+Use the <code>chrome.experimental.ttsEngine</code> module to
+implement a text-to-speech (TTS) engine using an extension. If your
+extension registers using this API, it will receive events containing
+the intended utterance and other parameters when any extension or packaged
+app uses the
+<a href="experimental.tts.html">experimental.tts</a>
+module to generate speech. Your extension can then use any available
+web technology to synthesize and output the speech, and send events back
+to the calling function to report the status.
+</p>
+
+<p class="note"><b>Give us feedback:</b> If you have suggestions,
+especially changes that should be made before stabilizing the first
+version of this API, please send your ideas to the
+<a href="http://groups.google.com/a/chromium.org/group/chromium-extensions">chromium-extensions</a>
+group.</p>
+
+<h2 id="overview">Overview</h2>
+
+<p>To enable this experimental API, visit
+<b>chrome://flags</b> and enable <b>Experimental Extension APIs</b>.
+
+</p><p>An extension can register itself as a speech engine. By doing so, it
+can intercept some or all calls to functions such as
+<a href="experimental.tts.html#method-speak"><code>speak()</code></a> and
+<a href="experimental.tts.html#method-stop"><code>stop()</code></a>
+and provide an alternate implementation.
+Extensions are free to use any available web technology
+to provide speech, including streaming audio from a server, HTML5 audio,
+Native Client, or Flash. An extension could even do something different
+with the utterances, like display closed captions in a pop-up window or
+send them as log messages to a remote server.</p>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>To implement a TTS engine, an extension must first declare all voices
+it provides in the extension manifest, like this:</p>
+
+<pre>{
+ "name": "My TTS Engine",
+ "version": "1.0",
+ <b>"permissions": ["experimental"],
+ "tts_engine": {
+ "voices": [
+ {
+ "voice_name": "Alice",
+ "lang": "en-US",
+ "gender": "female",
+ "event_types": ["start", "marker", "end"]
+ },
+ {
+ "voice_name": "Pat",
+ "lang": "en-US",
+ "event_types": ["end"]
+ }
+ ]
+ },</b>
+ "background_page": "background.html",
+}</pre>
+
+<p>An extension can specify any number of voices.</p>
+
+<p>The <code>voice_name</code> parameter is required. The name should be
+descriptive enough that it identifies the name of the voice and the
+engine used. In the unlikely event that two extensions register voices
+with the same name, a client can manually specify the extension id it
+wants to do the synthesis.</p>
+
+<p>The <code>gender</code> parameter is optional. If your voice corresponds
+to a male or female voice, you can use this parameter to help clients
+choose the most appropriate voice for their application.</p>
+
+<p>The <code>lang</code> parameter is optional, but highly recommended.
+Almost always, a voice can synthesize speech in just a single language.
+When an engine supports more than one language, it can easily register a
+separate voice for each language. Under rare circumstances where a single
+voice can handle more than one language, it's easiest to just list two
+separate voices and handle them using the same logic internally. However,
+if you want to create a voice that will handle utterances in any language,
+leave out the <code>lang</code> parameter from your extension's manifest.</p>
+
+<p>Finally, the <code>event_types</code> parameter is required if the engine can
+send events to update the client on the progress of speech synthesis.
+At a minimum, supporting the <code>'end'</code> event type to indicate
+when speech is finished is highly recommend, otherwise it's impossible
+for Chrome to schedule queued utterances.</p>
+
+<p class="note">If your TTS engine does not support the <code>'end'</code>
+event type, Chrome will pass the <code>enqueue</code> option to
+onSpeak, so that your engine can implement its own queuing. However, this is
+discouraged because it means that users cannot queue utterances that get
+sent to different speech engines.</p>
+
+<p>The possible event types you can send correspond to the event types that
+the <code>speak()</code> method receives:</p>
+
+<ul>
+ <li><code>'start'</code>: the engine has started speaking the utterance.
+ </li><li><code>'word'</code>: a word boundary was reached. Use
+ <code>event.charIndex</code> to determine the current speech
+ position.
+ </li><li><code>'sentence'</code>: a sentence boundary was reached. Use
+ <code>event.charIndex</code> to determine the current speech
+ position.
+ </li><li><code>'marker'</code>: an SSML marker was reached. Use
+ <code>event.charIndex</code> to determine the current speech
+ position.
+ </li><li><code>'end'</code>: the engine has finished speaking the utterance.
+ </li><li><code>'error'</code>: An engine-specific error occurred and
+ this utterance cannot be spoken.
+ Pass more information in <code>event.errorMessage</code>.
+</li></ul>
+
+<p>The <code>'interrupted'</code> and <code>'cancelled'</code> events are
+not sent by the speech engine; they are generated automatically by Chrome.</p>
+
+<p>The information about your extensions's voices from your manifest
+will be returned to any client that calls <code>getVoices</code>, assuming
+you've also registered speech event listeners as described below.</p>
+
+<h2 id="handling_speech_events">Handling Speech Events</h2>
+
+<p>To generate speech at the request of clients, your extension must
+register listeners for both <code>onSpeak</code> and <code>onStop</code>,
+like this:</p>
+
+<pre>var speakListener = function(utterance, options, sendTtsEvent) {
+ sendTtsEvent({'event_type': 'start', 'charIndex': 0})
+
+ // (start speaking)
+
+ sendTtsEvent({'event_type': 'end', 'charIndex': utterance.length})
+};
+
+var stopListener = function() {
+ // (stop all speech)
+};
+
+chrome.experimental.ttsEngine.onSpeak.addListener(speakListener);
+chrome.experimental.ttsEngine.onStop.addListener(stopListener);</pre>
+
+<p class="warning">If an extension does not register listeners for both
+<code>onSpeak</code> and <code>onStop</code>, it will not intercept any
+speech calls, regardless of what is in the manifest.</p>
+
+<p>The decision of whether or not to send a given speech request to an
+extension is based solely on whether the extension supports the given voice
+parameters in its manifest and has registered listeners
+for <code>onSpeak</code> and <code>onStop</code>. In other words,
+there's no way for an extension to receive a speech request and
+dynamically decide whether to handle it or not.</p>
+</div>
+
+ <!-- API PAGE -->
+ <div class="apiPage">
+ <a name="apiReference"></a>
+ <h2>API reference: chrome.experimental.ttsEngine</h2>
+
+ <!-- PROPERTIES -->
+ <div class="apiGroup" style="display: none; ">
+ <a name="properties"></a>
+ <h3 id="properties">Properties</h3>
+
+ <div>
+ <a></a>
+ <h4>getLastError</h4>
+ <div class="summary">
+ <!-- Note: intentionally longer 80 columns -->
+ <span>chrome.extension</span><span>lastError</span>
+ </div>
+ <div>
+ </div>
+ </div>
+
+ </div> <!-- /apiGroup -->
+
+ <!-- METHODS -->
+ <div id="methodsTemplate" class="apiGroup">
+ <a name="global-methods"></a>
+ <h3>Methods</h3>
+
+ <!-- iterates over all functions -->
+ <div class="apiItem" style="display: none; ">
+ <a></a> <!-- method-anchor -->
+ <h4>method name</h4>
+
+ <div class="summary"><span>void</span>
+ <!-- Note: intentionally longer 80 columns -->
+ <span>chrome.module.methodName</span>(<span><span>, </span><span></span>
+ <var><span></span></var></span>)</div>
+
+ <div class="description">
+ <p class="todo">Undocumented.</p>
+ <p>
+ A description from the json schema def of the function goes here.
+ </p>
+
+ <!-- PARAMETERS -->
+ <h4>Parameters</h4>
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+
+ <!-- RETURNS -->
+ <h4>Returns</h4>
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+
+ <!-- CALLBACK -->
+ <div>
+ <div>
+ <h4>Callback function</h4>
+ <p>
+ The callback <em>parameter</em> should specify a function
+ that looks like this:
+ </p>
+ <p>
+ If you specify the <em>callback</em> parameter, it should
+ specify a function that looks like this:
+ </p>
+
+ <!-- Note: intentionally longer 80 columns -->
+ <pre>function(<span>Type param1, Type param2</span>) <span class="subdued">{...}</span>;</pre>
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </div>
+ </div>
+
+ <!-- MIN_VERSION -->
+ <p>
+ This function was added in version <b><span></span></b>.
+ If you require this function, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </p>
+ </div> <!-- /description -->
+
+ </div> <!-- /apiItem -->
+
+ </div> <!-- /apiGroup -->
+
+ <!-- EVENTS -->
+ <div id="eventsTemplate" class="apiGroup">
+ <a name="global-events"></a>
+ <h3>Events</h3>
+ <!-- iterates over all events -->
+ <div class="apiItem" style="display: none; ">
+ <a></a>
+ <h4>event name</h4>
+
+ <div class="summary">
+ <!-- Note: intentionally longer 80 columns -->
+ <span class="subdued">chrome.bookmarks</span><span>onEvent</span><span class="subdued">.addListener</span>(function(<span>Type param1, Type param2</span>) <span class="subdued">{...}</span><span>, Type opt_param1, Type opt_param2</span>));
+ </div>
+
+ <div class="description">
+ <p class="todo">Undocumented.</p>
+ <p>
+ A description from the json schema def of the event goes here.
+ </p>
+
+ <!-- LISTENER PARAMETERS -->
+ <div>
+ <h4>Listener parameters</h4>
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </div>
+
+ <!-- EXTRA PARAMETERS -->
+ <div>
+ <h4>Extra parameters to addListener</h4>
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </div>
+
+ <!-- LISTENER RETURN VALUE -->
+ <h4>Listener returns</h4>
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+
+ </div> <!-- /description -->
+ </div><div class="apiItem">
+ <a name="event-onSpeak"></a>
+ <h4>onSpeak</h4>
+
+ <div class="summary">
+ <!-- Note: intentionally longer 80 columns -->
+ <span class="subdued">chrome.experimental.ttsEngine.</span><span>onSpeak</span><span class="subdued">.addListener</span>(function(<span>string utterance, object options, function sendTtsEvent</span>) <span class="subdued">{...}</span><span></span>));
+ </div>
+
+ <div class="description">
+ <p class="todo" style="display: none; ">Undocumented.</p>
+ <p>Called when the user makes a call to tts.speak and the options matches one of the tts_voices from this extension's manifest.</p>
+
+ <!-- LISTENER PARAMETERS -->
+ <div>
+ <h4>Listener parameters</h4>
+ <dl>
+ <div>
+ <div>
+ <dt>
+ <var>utterance</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional" style="display: none; ">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>string</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>The text to speak. This may include SSML, so if your engine does not support SSML, you should strip out all XML markup and synthesize only the underlying text content. This is guaranteed to be no more than 32,768 characters. If this engine does not support speaking that many characters at a time, the utterance should be split into smaller chunks and queued internally without returning an error.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div><div>
+ <div>
+ <dt>
+ <var>options</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional" style="display: none; ">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>object</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>The speak options.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd>
+ <dl>
+ <div>
+ <div>
+ <dt>
+ <var>voiceName</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>string</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>The name of the voice to use for synthesis.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div><div>
+ <div>
+ <dt>
+ <var>lang</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>string</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>The language to be used for synthesis, in the form <language>-<region>, e.g. en-US, en-GB, fr-CA, zh-CN, etc.</region></language></dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div><div>
+ <div>
+ <dt>
+ <var>gender</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional">optional</span>
+ <span class="enum">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>string</span>
+ <span>["male", "female"]</span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>Gender of voice for synthesized speech.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div><div>
+ <div>
+ <dt>
+ <var>rate</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>number</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>Speaking rate relative to the default rate for this voice. 1.0 is the default rate, normally around 180 to 220 words per minute, 2.0 would be twice as fast, and 0.5 would be half as fast. This value is guaranteed to be between 0.1 and 10.0, inclusive. When a voice does not support this full range of rates, the actual rate should be clipped to the range that is supported without returning an error.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div><div>
+ <div>
+ <dt>
+ <var>pitch</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>number</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>Speaking pitch between 0 and 2 inclusive, with 0 being lowest and 1 being highest, with 1.0 being the default pitch of this particular voice.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div><div>
+ <div>
+ <dt>
+ <var>volume</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>number</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>Speaking volume between 0 and 1 inclusive, with 0 being lowest and 1 being highest, with a default of 1.0.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div><div>
+ <div>
+ <dt>
+ <var>sendTtsEvent</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional" style="display: none; ">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span style="display: none; ">
+ <a> Type</a>
+ </span>
+ <span>
+ <span style="display: none; ">
+ array of <span><span></span></span>
+ </span>
+ <span>function</span>
+ <span style="display: none; "></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>Call this function with events that occur in the process of speaking the utterance.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd>
+ <div>
+ <h5>Parameters</h5>
+ <dl>
+ <div>
+ <div>
+ <dt>
+ <var>event</var>
+ <em>
+
+ <!-- TYPE -->
+ <div style="display:inline">
+ (
+ <span class="optional" style="display: none; ">optional</span>
+ <span class="enum" style="display: none; ">enumerated</span>
+ <span id="typeTemplate">
+ <span>
+ <a href="experimental.tts.html#type-TtsEvent">TtsEvent</a>
+ </span>
+ <span style="display: none; ">
+ <span>
+ array of <span><span></span></span>
+ </span>
+ <span>paramType</span>
+ <span></span>
+ </span>
+ </span>
+ )
+ </div>
+
+ </em>
+ </dt>
+ <dd class="todo" style="display: none; ">
+ Undocumented.
+ </dd>
+ <dd>The event from the text-to-speech engine indicating the status of this utterance.</dd>
+ <dd style="display: none; ">
+ This parameter was added in version
+ <b><span></span></b>.
+ You must omit this parameter in earlier versions,
+ and you may omit it in any version. If you require this
+ parameter, the manifest key
+ <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a>
+ can ensure that your extension won't be run in an earlier browser version.
+ </dd>
+
+ <!-- OBJECT PROPERTIES -->
+ <dd style="display: none; ">
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </dd>
+
+ <!-- OBJECT METHODS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- OBJECT EVENT FIELDS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ <!-- FUNCTION PARAMETERS -->
+ <dd style="display: none; ">
+ <div></div>
+ </dd>
+
+ </div>
+ </div>
+ </dl>
+ </div>
+ </dd>
+
+ </div>
+ </div>
+ </dl>
+ </div>
+
+ <!-- EXTRA PARAMETERS -->
+ <div style="display: none; ">
+ <h4>Extra parameters to addListener</h4>
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </div>
+
+ <!-- LISTENER RETURN VALUE -->
+ <h4 style="display: none; ">Listener returns</h4>
+ <dl>
+ <div style="display: none; ">
+ <div>
+ </div>
+ </div>
+ </dl>
+
+ </div> <!-- /description -->
+ </div><div class="apiItem">
+ <a name="event-onStop"></a>
+ <h4>onStop</h4>
+
+ <div class="summary">
+ <!-- Note: intentionally longer 80 columns -->
+ <span class="subdued">chrome.experimental.ttsEngine.</span><span>onStop</span><span class="subdued">.addListener</span>(function(<span></span>) <span class="subdued">{...}</span><span></span>));
+ </div>
+
+ <div class="description">
+ <p class="todo" style="display: none; ">Undocumented.</p>
+ <p>Fired when a call is made to tts.stop and this extension may be in the middle of speaking. If an extension receives a call to onStop and speech is already stopped, it should do nothing (not raise an error).</p>
+
+ <!-- LISTENER PARAMETERS -->
+ <div style="display: none; ">
+ <h4>Listener parameters</h4>
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </div>
+
+ <!-- EXTRA PARAMETERS -->
+ <div style="display: none; ">
+ <h4>Extra parameters to addListener</h4>
+ <dl>
+ <div>
+ <div>
+ </div>
+ </div>
+ </dl>
+ </div>
+
+ <!-- LISTENER RETURN VALUE -->
+ <h4 style="display: none; ">Listener returns</h4>
+ <dl>
+ <div style="display: none; ">
+ <div>
+ </div>
+ </div>
+ </dl>
+
+ </div> <!-- /description -->
+ </div> <!-- /apiItem -->
+
+ </div> <!-- /apiGroup -->
+
+ <!-- TYPES -->
+ <div class="apiGroup" style="display: none; ">
+ <a name="types"></a>
+ <h3 id="types">Types</h3>
+
+ <!-- iterates over all types -->
+ <div class="apiItem">
+ <a></a>
+ <h4>type name</h4>
+
+ <div>
+ </div>
+
+ </div> <!-- /apiItem -->
+
+ </div> <!-- /apiGroup -->
+
+ </div> <!-- /apiPage -->
+ </div> <!-- /gc-pagecontent -->
+ </div> <!-- /g-section -->
+ </div> <!-- /codesiteContent -->
+ <div id="gc-footer" --="">
+ <div class="text">
+ <p>
+ Except as otherwise <a href="http://code.google.com/policies.html#restrictions">noted</a>,
+ the content of this page is licensed under the <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">Creative Commons
+ Attribution 3.0 License</a>, and code samples are licensed under the
+ <a rel="license" href="http://code.google.com/google_bsd_license.html">BSD License</a>.
+ </p>
+ <p>
+ ©2011 Google
+ </p>
+
+<!-- begin analytics -->
+<script src="http://www.google-analytics.com/urchin.js" type="text/javascript"></script>
+<script src="http://www.google-analytics.com/ga.js" type="text/javascript"></script>
+
+<script type="text/javascript">
+ // chrome doc tracking
+ try {
+ var engdocs = _gat._getTracker("YT-10763712-2");
+ engdocs._trackPageview();
+ } catch(err) {}
+
+ // code.google.com site-wide tracking
+ try {
+ _uacct="UA-18071-1";
+ _uanchor=1;
+ _uff=0;
+ urchinTracker();
+ }
+ catch(e) {/* urchinTracker not available. */}
+</script>
+<!-- end analytics -->
+ </div>
+ </div> <!-- /gc-footer -->
+ </div> <!-- /gc-container -->
+</body></html>
diff --git a/chrome/common/extensions/docs/static/experimental.tts.html b/chrome/common/extensions/docs/static/experimental.tts.html
index fb2a772..31cc987 100644
--- a/chrome/common/extensions/docs/static/experimental.tts.html
+++ b/chrome/common/extensions/docs/static/experimental.tts.html
@@ -1,7 +1,9 @@
<p id="classSummary">
Use the <code>chrome.experimental.tts</code> module to play synthesized
-text-to-speech (TTS) from your extension or packaged app, or to register
-as a speech provider for other extensions and packaged apps that want to speak.
+text-to-speech (TTS) from your extension or packaged app.
+See also the related
+<a href="experimental.ttsEngine.html">experimental.ttsEngine</a>
+module which allows an extension to implement a speech engine.
</p>
<p class="note"><b>Give us feedback:</b> If you have suggestions,
@@ -19,7 +21,7 @@ group.</p>
5), Mac OS X, and Chrome OS, using speech synthesis capabilities
provided by the operating system. On all platforms, the user can
install extensions that register themselves as alternative speech
-synthesis providers.</p>
+engines.</p>
<h2 id="generating_speech">Generating speech</h2>
@@ -28,119 +30,160 @@ packaged app to speak. For example:</p>
<pre>chrome.experimental.tts.speak('Hello, world.');</pre>
+<p>To stop speaking immediately, just call <code>stop()</code>:
+
+<pre>chrome.experimental.tts.stop();</pre>
+
<p>You can provide options that control various properties of the speech,
such as its rate, pitch, and more. For example:</p>
-<pre>chrome.experimental.tts.speak('Hello, world.', {'rate': 0.8});</pre>
+<pre>chrome.experimental.tts.speak('Hello, world.', {'rate': 2.0});</pre>
-<p>It's also a good idea to specify the locale so that a synthesizer
+<p>It's also a good idea to specify the language so that a synthesizer
supporting that language (and regional dialect, if applicable) is chosen.</p>
<pre>chrome.experimental.tts.speak(
- 'Hello, world.',
- {
- 'locale': 'en-US',
- 'rate': 0.8
- });</pre>
-
-<p>Not all speech engines will support all options.</p>
+ 'Hello, world.', {'lang': 'en-US', 'rate': 2.0});</pre>
-<p>You can also pass a callback function that will be called when the
-speech has finished. For example, suppose we have an image on our page
-displaying a picture of a face with a closed mouth. We could open the mouth
-while speaking, and close it when done.</p>
+<p>By default, each call to <code>speak()</code> will interrupt any
+ongoing speech and speak immediately. To determine if a call would be
+interrupting anything, you can call <code>isSpeaking()</code>, or
+you can use the <code>enqueue</code> option to cause this utterance to
+be added to a queue of utterances that will be spoken when the current
+utterance has finished.
-<pre>faceImage.src = 'open_mouth.png';
+<pre>chrome.experimental.tts.speak(
+ 'Speak this first.');
chrome.experimental.tts.speak(
- 'Hello, world.', null, function() {
- faceImage.src = 'closed_mouth.png';
- });
+ 'Speak this next, when the first sentence is done.', {'enqueue': true});
</pre>
-<p>To stop speaking immediately, just call <code>stop()</code>. Call
-<code>isSpeaking()</code> to find out if a TTS engine is currently speaking.</p>
+<p>A complete description of all options can be found in the
+<a href="#method-speak">speak() method documentation</a> below.
+Not all speech engines will support all options.</p>
+
+<p>To catch errors and make sure you're calling <code>speak()</code>
+correctly, pass a callback function that takes no arguments. Inside
+the callback, check
+<a href="extension.html#property-lastError">chrome.extension.lastError</a>
+to see if there were any errors.</p>
+
+<pre>chrome.experimental.tts.speak(
+ utterance,
+ options,
+ function() {
+ if (chrome.extension.lastError) {
+ console.log('Error: ' + chrome.extension.lastError.message);
+ }
+ });</pre>
+
+<p>The callback returns right away, before the speech engine has started
+generating speech. The purpose of the callback is to alert you to syntax
+errors in your use of the TTS API, not all possible errors that might occur
+in the process of synthesizing and outputting speech. To catch these errors
+too, you need to use an event listener, described below.
+
+<h2 id="events">Listening to events</h2>
-<p>You can check to see if an error occurred by checking
-<code>chrome.extension.lastError</code> inside the callback function.</p>
+<p>To get more real-time information about the status of synthesized speech,
+pass an event listener in the options to <code>speak()</code>, like this:</p>
+
+<pre>chrome.experimental.tts.speak(
+ utterance,
+ {
+ 'onevent': function(event) {
+ console.log('Event ' + event.type ' at position ' + event.charIndex);
+ if (event.type == 'error') {
+ console.log('Error: ' + event.errorMessage);
+ }
+ }
+ },
+ callback);</pre>
+
+<p>Each event includes an event type, the character index of the current
+speech relative to the utterance, and for error events, an optional
+error message. The event types are:</p>
+
+<ul>
+ <li><code>'start'</code>: the engine has started speaking the utterance.
+ <li><code>'word'</code>: a word boundary was reached. Use
+ <code>event.charIndex</code> to determine the current speech
+ position.
+ <li><code>'sentence'</code>: a sentence boundary was reached. Use
+ <code>event.charIndex</code> to determine the current speech
+ position.
+ <li><code>'marker'</code>: an SSML marker was reached. Use
+ <code>event.charIndex</code> to determine the current speech
+ position.
+ <li><code>'end'</code>: the engine has finished speaking the utterance.
+ <li><code>'interrupted'</code>: this utterance was interrupted by another
+ call to <code>speak()</code> or <code>stop()</code> and did not
+ finish.
+ <li><code>'cancelled'</code>: this utterance was queued, but then
+ cancelled by another call to <code>speak()</code> or
+ <code>stop()</code> and never began to speak at all.
+ <li><code>'error'</code>: An engine-specific error occurred and
+ this utterance cannot be spoken.
+ Check <code>event.errorMessage</code> for details.
+</ul>
+
+<p>Four of the event types, <code>'end'</code>, <code>'interrupted'</code>,
+<code>'cancelled'</code>, and <code>'error'</code>, are <i>final</i>. After
+one of those events is received, this utterance will no longer speak and
+no new events from this utterance will be received.</p>
+
+<p>Some TTS engines may not support all event types, and some may not even
+support any events at all. To require that the speech engine used sends
+the events you're interested in, you can pass a list of event types in
+the <code>requiredEventTypes</code> member of the options object, or use
+<code>getVoices</code> to choose a voice that has the events you need.
+Both are documented below.
<h2 id="ssml">SSML markup</h2>
<p>Utterances used in this API may include markup using the
<a href="http://www.w3.org/TR/speech-synthesis">Speech Synthesis Markup
-Language (SSML)</a>. For example:
+Language (SSML)</a>. If you use SSML, the first argument to
+<code>speak()</code> should be a complete SSML document with an XML
+header and a top-level <code>&lt;speak&gt;</code> tag, not a document
+fragment.
-<pre>chrome.experimental.tts.speak('The &lt;emphasis&gt;second&lt;/emphasis&gt; word of this sentence was emphasized.');</pre>
+For example:
+
+<pre>chrome.experimental.tts.speak(
+ '&lt;?xml version="1.0"?&gt;' +
+ '&lt;speak&gt;' +
+ ' The &lt;emphasis&gt;second&lt;/emphasis&gt; ' +
+ ' word of this sentence was emphasized.' +
+ '&lt;/speak&gt;');</pre>
<p>Not all speech engines will support all SSML tags, and some may not support
-SSML at all, but all engines are expected to ignore any SSML they don't
+SSML at all, but all engines are required to ignore any SSML they don't
support and still speak the underlying text.</p>
-<h2 id="provider">Implementing a speech provider</h2>
-
-<p>An extension can register itself as a speech provider. By doing so, it
-can intercept some or all calls to functions such as
-<code>speak()</code> and <code>stop()</code> and provide an alternate
-implementation. Extensions are free to use any available web technology
-to provide speech, including streaming audio from a server, HTML5 audio,
-Native Client, or Flash. An extension could even do something different
-with the utterances, like display closed captions in a pop-up window or
-send them as log messages to a remote server.</p>
-
-<p>To provide TTS, an extension must first declare all voices it provides
-in the extension manifest, like this:</p>
-
-<pre>{
- "name": "My TTS Provider",
- "version": "1.0",
- <b>"permissions": ["experimental"]
- "tts": {
- "voices": [
- {
- "voiceName": "Alice",
- "locale": "en-US",
- "gender": "female"
- },
- {
- "voiceName": "Pat",
- "locale": "en-US"
+<h2 id="choosing_voice">Choosing a voice</h2>
+
+<p>By default, Chrome will choose the most appropriate voice for each
+utterance you want to speak, based on the language and gender. On most
+Windows, Mac OS X, and Chrome OS systems, speech synthesis provided by
+the operating system should be able to speak any text in at least one
+language. Some users may have a variety of voices available, though,
+from their operating system and from speech engines implemented by other
+Chrome extensions. In those cases, you can implement custom code to choose
+the appropriate voice, or present the user with a list of choices.</p>
+
+<p>To get a list of all voices, call <code>getVoices()</code> and pass it
+a function that receives an array of <code>TtsVoice</code> objects as its
+argument:</p>
+
+<pre>chrome.experimental.tts.getVoices(
+ function(voices) {
+ for (var i = 0; i < voices.length; i++) {
+ console.log('Voice ' + i + ':');
+ console.log(' name: ' + voices[i].voiceName);
+ console.log(' lang: ' + voices[i].lang);
+ console.log(' gender: ' + voices[i].gender);
+ console.log(' extension id: ' + voices[i].extensionId);
+ console.log(' event types: ' + voices[i].eventTypes);
}
- ]
- },</b>
- "background_page": "background.html",
-}</pre>
-
-<p>An extension can specify any number of voices. The three
-parameters&mdash;<code>voiceName</code>, <code>locale</code>,
-and <code>gender</code>&mdash;are all optional. If they are all unspecified,
-the extension will handle all speech from all clients. If any of them
-are specified, they can be used to filter speech requests. For
-example, if a voice only supports French, it should set the locale to
-'fr' (or something more specific like 'fr-FR') so that only utterances
-in that locale are routed to that extension.</p>
-
-<p>To handle speech calls, the extension should register listeners
-for <code>onSpeak</code> and <code>onStop</code>, like this:</p>
-
-<pre>var speakListener = function(utterance, options, callback) {
- ...
- callback();
-};
-var stopListener = function() {
- ...
-};
-chrome.experimental.tts.onSpeak.addListener(speakListener);
-chrome.experimental.tts.onStop.addListener(stopListener);</pre>
-
-<p class="warning"><b>Important:</b> Don't forget to call the callback
-function from your speak listener!</p>
-
-<p>If an extension does not register listeners for both
-<code>onSpeak</code> and <code>onStop</code>, it will not intercept any
-speech calls, regardless of what is in the manifest.
-
-<p>The decision of whether or not to send a given speech request to an
-extension is based solely on whether the extension supports the given voice
-parameters in its manifest and has registered listeners
-for <code>onSpeak</code> and <code>onStop</code>. In other words,
-there's no way for an extension to receive a speech request and
-dynamically decide whether to handle it or not.</p>
+ });</pre>
diff --git a/chrome/common/extensions/docs/static/experimental.ttsEngine.html b/chrome/common/extensions/docs/static/experimental.ttsEngine.html
new file mode 100644
index 0000000..d7c3387
--- /dev/null
+++ b/chrome/common/extensions/docs/static/experimental.ttsEngine.html
@@ -0,0 +1,152 @@
+<p id="classSummary">
+Use the <code>chrome.experimental.ttsEngine</code> module to
+implement a text-to-speech (TTS) engine using an extension. If your
+extension registers using this API, it will receive events containing
+the intended utterance and other parameters when any extension or packaged
+app uses the
+<a href="experimental.tts.html">experimental.tts</a>
+module to generate speech. Your extension can then use any available
+web technology to synthesize and output the speech, and send events back
+to the calling function to report the status.
+</p>
+
+<p class="note"><b>Give us feedback:</b> If you have suggestions,
+especially changes that should be made before stabilizing the first
+version of this API, please send your ideas to the
+<a href="http://groups.google.com/a/chromium.org/group/chromium-extensions">chromium-extensions</a>
+group.</p>
+
+<h2 id="overview">Overview</h2>
+
+<p>To enable this experimental API, visit
+<b>chrome://flags</b> and enable <b>Experimental Extension APIs</b>.
+
+<p>An extension can register itself as a speech engine. By doing so, it
+can intercept some or all calls to functions such as
+<a href="experimental.tts.html#method-speak"><code>speak()</code></a> and
+<a href="experimental.tts.html#method-stop"><code>stop()</code></a>
+and provide an alternate implementation.
+Extensions are free to use any available web technology
+to provide speech, including streaming audio from a server, HTML5 audio,
+Native Client, or Flash. An extension could even do something different
+with the utterances, like display closed captions in a pop-up window or
+send them as log messages to a remote server.</p>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>To implement a TTS engine, an extension must first declare all voices
+it provides in the extension manifest, like this:</p>
+
+<pre>{
+ "name": "My TTS Engine",
+ "version": "1.0",
+ <b>"permissions": ["experimental"],
+ "tts_engine": {
+ "voices": [
+ {
+ "voice_name": "Alice",
+ "lang": "en-US",
+ "gender": "female",
+ "event_types": ["start", "marker", "end"]
+ },
+ {
+ "voice_name": "Pat",
+ "lang": "en-US",
+ "event_types": ["end"]
+ }
+ ]
+ },</b>
+ "background_page": "background.html",
+}</pre>
+
+<p>An extension can specify any number of voices.</p>
+
+<p>The <code>voice_name</code> parameter is required. The name should be
+descriptive enough that it identifies the name of the voice and the
+engine used. In the unlikely event that two extensions register voices
+with the same name, a client can manually specify the extension id it
+wants to do the synthesis.</p>
+
+<p>The <code>gender</code> parameter is optional. If your voice corresponds
+to a male or female voice, you can use this parameter to help clients
+choose the most appropriate voice for their application.</p>
+
+<p>The <code>lang</code> parameter is optional, but highly recommended.
+Almost always, a voice can synthesize speech in just a single language.
+When an engine supports more than one language, it can easily register a
+separate voice for each language. Under rare circumstances where a single
+voice can handle more than one language, it's easiest to just list two
+separate voices and handle them using the same logic internally. However,
+if you want to create a voice that will handle utterances in any language,
+leave out the <code>lang</code> parameter from your extension's manifest.</p>
+
+<p>Finally, the <code>event_types</code> parameter is required if the engine can
+send events to update the client on the progress of speech synthesis.
+At a minimum, supporting the <code>'end'</code> event type to indicate
+when speech is finished is highly recommend, otherwise it's impossible
+for Chrome to schedule queued utterances.</p>
+
+<p class="note">If your TTS engine does not support the <code>'end'</code>
+event type, Chrome will pass the <code>enqueue</code> option to
+onSpeak, so that your engine can implement its own queuing. However, this is
+discouraged because it means that users cannot queue utterances that get
+sent to different speech engines.</p>
+
+<p>The possible event types you can send correspond to the event types that
+the <code>speak()</code> method receives:</p>
+
+<ul>
+ <li><code>'start'</code>: the engine has started speaking the utterance.
+ <li><code>'word'</code>: a word boundary was reached. Use
+ <code>event.charIndex</code> to determine the current speech
+ position.
+ <li><code>'sentence'</code>: a sentence boundary was reached. Use
+ <code>event.charIndex</code> to determine the current speech
+ position.
+ <li><code>'marker'</code>: an SSML marker was reached. Use
+ <code>event.charIndex</code> to determine the current speech
+ position.
+ <li><code>'end'</code>: the engine has finished speaking the utterance.
+ <li><code>'error'</code>: An engine-specific error occurred and
+ this utterance cannot be spoken.
+ Pass more information in <code>event.errorMessage</code>.
+</ul>
+
+<p>The <code>'interrupted'</code> and <code>'cancelled'</code> events are
+not sent by the speech engine; they are generated automatically by Chrome.</p>
+
+<p>The information about your extensions's voices from your manifest
+will be returned to any client that calls <code>getVoices</code>, assuming
+you've also registered speech event listeners as described below.</p>
+
+<h2 id="handling_speech_events">Handling Speech Events</h2>
+
+<p>To generate speech at the request of clients, your extension must
+register listeners for both <code>onSpeak</code> and <code>onStop</code>,
+like this:</p>
+
+<pre>var speakListener = function(utterance, options, sendTtsEvent) {
+ sendTtsEvent({'event_type': 'start', 'charIndex': 0})
+
+ // (start speaking)
+
+ sendTtsEvent({'event_type': 'end', 'charIndex': utterance.length})
+};
+
+var stopListener = function() {
+ // (stop all speech)
+};
+
+chrome.experimental.ttsEngine.onSpeak.addListener(speakListener);
+chrome.experimental.ttsEngine.onStop.addListener(stopListener);</pre>
+
+<p class="warning">If an extension does not register listeners for both
+<code>onSpeak</code> and <code>onStop</code>, it will not intercept any
+speech calls, regardless of what is in the manifest.</p>
+
+<p>The decision of whether or not to send a given speech request to an
+extension is based solely on whether the extension supports the given voice
+parameters in its manifest and has registered listeners
+for <code>onSpeak</code> and <code>onStop</code>. In other words,
+there's no way for an extension to receive a speech request and
+dynamically decide whether to handle it or not.</p>
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc
index 606ba76..d3fd98f 100644
--- a/chrome/common/extensions/extension.cc
+++ b/chrome/common/extensions/extension.cc
@@ -2245,9 +2245,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags,
}
// Initialize text-to-speech voices (optional).
- if (source.HasKey(keys::kTts)) {
+ if (source.HasKey(keys::kTtsEngine)) {
DictionaryValue* tts_dict = NULL;
- if (!source.GetDictionary(keys::kTts, &tts_dict)) {
+ if (!source.GetDictionary(keys::kTtsEngine, &tts_dict)) {
*error = errors::kInvalidTts;
return false;
}
@@ -2274,11 +2274,11 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags,
return false;
}
}
- if (one_tts_voice->HasKey(keys::kTtsVoicesLocale)) {
+ if (one_tts_voice->HasKey(keys::kTtsVoicesLang)) {
if (!one_tts_voice->GetString(
- keys::kTtsVoicesLocale, &voice_data.locale) ||
- !l10n_util::IsValidLocaleSyntax(voice_data.locale)) {
- *error = errors::kInvalidTtsVoicesLocale;
+ keys::kTtsVoicesLang, &voice_data.lang) ||
+ !l10n_util::IsValidLocaleSyntax(voice_data.lang)) {
+ *error = errors::kInvalidTtsVoicesLang;
return false;
}
}
@@ -2291,6 +2291,36 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags,
return false;
}
}
+ if (one_tts_voice->HasKey(keys::kTtsVoicesEventTypes)) {
+ ListValue* event_types_list;
+ if (!one_tts_voice->GetList(
+ keys::kTtsVoicesEventTypes, &event_types_list)) {
+ *error = errors::kInvalidTtsVoicesEventTypes;
+ return false;
+ }
+ for (size_t i = 0; i < event_types_list->GetSize(); i++) {
+ std::string event_type;
+ if (!event_types_list->GetString(i, &event_type)) {
+ *error = errors::kInvalidTtsVoicesEventTypes;
+ return false;
+ }
+ if (event_type != keys::kTtsVoicesEventTypeEnd &&
+ event_type != keys::kTtsVoicesEventTypeError &&
+ event_type != keys::kTtsVoicesEventTypeMarker &&
+ event_type != keys::kTtsVoicesEventTypeSentence &&
+ event_type != keys::kTtsVoicesEventTypeStart &&
+ event_type != keys::kTtsVoicesEventTypeWord) {
+ *error = errors::kInvalidTtsVoicesEventTypes;
+ return false;
+ }
+ if (voice_data.event_types.find(event_type) !=
+ voice_data.event_types.end()) {
+ *error = errors::kInvalidTtsVoicesEventTypes;
+ return false;
+ }
+ voice_data.event_types.insert(event_type);
+ }
+ }
tts_voices_.push_back(voice_data);
}
diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h
index 95e1be0..505bac6 100644
--- a/chrome/common/extensions/extension.h
+++ b/chrome/common/extensions/extension.h
@@ -139,8 +139,9 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
struct TtsVoice {
std::string voice_name;
- std::string locale;
+ std::string lang;
std::string gender;
+ std::set<std::string> event_types;
};
enum InitFromValueFlags {
diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc
index 4761639..0218761 100644
--- a/chrome/common/extensions/extension_constants.cc
+++ b/chrome/common/extensions/extension_constants.cc
@@ -81,13 +81,20 @@ const char* kThemeImages = "images";
const char* kThemeTints = "tints";
const char* kToolstripPath = "path";
const char* kToolstrips = "toolstrips";
-const char* kTts = "tts";
+const char* kTtsEngine = "tts_engine";
const char* kTtsGenderFemale = "female";
const char* kTtsGenderMale = "male";
const char* kTtsVoices = "voices";
+const char* kTtsVoicesEventTypeEnd = "end";
+const char* kTtsVoicesEventTypeError = "error";
+const char* kTtsVoicesEventTypeMarker = "marker";
+const char* kTtsVoicesEventTypeSentence = "sentence";
+const char* kTtsVoicesEventTypeStart = "start";
+const char* kTtsVoicesEventTypeWord = "word";
+const char* kTtsVoicesEventTypes = "event_types";
const char* kTtsVoicesGender = "gender";
-const char* kTtsVoicesLocale = "locale";
-const char* kTtsVoicesVoiceName = "voiceName";
+const char* kTtsVoicesLang = "lang";
+const char* kTtsVoicesVoiceName = "voice_name";
const char* kType = "type";
const char* kUpdateURL = "update_url";
const char* kVersion = "version";
@@ -326,15 +333,17 @@ const char* kInvalidToolstrip =
const char* kInvalidToolstrips =
"Invalid value for 'toolstrips'.";
const char* kInvalidTts =
- "Invalid value for 'tts'.";
+ "Invalid value for 'tts_engine'.";
const char* kInvalidTtsVoices =
- "Invalid value for 'tts.voices'.";
+ "Invalid value for 'tts_engine.voices'.";
+const char* kInvalidTtsVoicesEventTypes =
+ "Invalid value for 'tts_engine.voices[*].event_types'.";
const char* kInvalidTtsVoicesGender =
- "Invalid value for 'tts.voices[*].gender'.";
-const char* kInvalidTtsVoicesLocale =
- "Invalid value for 'tts.voices[*].locale'.";
+ "Invalid value for 'tts_engine.voices[*].gender'.";
+const char* kInvalidTtsVoicesLang =
+ "Invalid value for 'tts_engine.voices[*].lang'.";
const char* kInvalidTtsVoicesVoiceName =
- "Invalid value for 'tts.voices[*].voiceName'.";
+ "Invalid value for 'tts_engine.voices[*].voice_name'.";
const char* kInvalidUpdateURL =
"Invalid value for update url: '[*]'.";
const char* kInvalidURLPatternError =
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
index 1fdd433..b9fd273 100644
--- a/chrome/common/extensions/extension_constants.h
+++ b/chrome/common/extensions/extension_constants.h
@@ -86,12 +86,19 @@ namespace extension_manifest_keys {
extern const char* kThemeTints;
extern const char* kToolstripPath;
extern const char* kToolstrips;
- extern const char* kTts;
+ extern const char* kTtsEngine;
extern const char* kTtsGenderFemale;
extern const char* kTtsGenderMale;
extern const char* kTtsVoices;
+ extern const char* kTtsVoicesEventTypeEnd;
+ extern const char* kTtsVoicesEventTypeError;
+ extern const char* kTtsVoicesEventTypeMarker;
+ extern const char* kTtsVoicesEventTypeSentence;
+ extern const char* kTtsVoicesEventTypeStart;
+ extern const char* kTtsVoicesEventTypeWord;
+ extern const char* kTtsVoicesEventTypes;
extern const char* kTtsVoicesGender;
- extern const char* kTtsVoicesLocale;
+ extern const char* kTtsVoicesLang;
extern const char* kTtsVoicesVoiceName;
extern const char* kType;
extern const char* kUpdateURL;
@@ -222,8 +229,9 @@ namespace extension_manifest_errors {
extern const char* kInvalidToolstrips;
extern const char* kInvalidTts;
extern const char* kInvalidTtsVoices;
+ extern const char* kInvalidTtsVoicesEventTypes;
extern const char* kInvalidTtsVoicesGender;
- extern const char* kInvalidTtsVoicesLocale;
+ extern const char* kInvalidTtsVoicesLang;
extern const char* kInvalidTtsVoicesVoiceName;
extern const char* kInvalidUpdateURL;
extern const char* kInvalidURLPatternError;
diff --git a/chrome/common/extensions/extension_manifests_unittest.cc b/chrome/common/extensions/extension_manifests_unittest.cc
index 7a7122a..1184d52 100644
--- a/chrome/common/extensions/extension_manifests_unittest.cc
+++ b/chrome/common/extensions/extension_manifests_unittest.cc
@@ -706,29 +706,34 @@ TEST_F(ExtensionManifestTest, DefaultLocale) {
EXPECT_EQ("de-AT", extension->default_locale());
}
-TEST_F(ExtensionManifestTest, TtsProvider) {
- LoadAndExpectError("tts_provider_invalid_1.json",
+TEST_F(ExtensionManifestTest, TtsEngine) {
+ LoadAndExpectError("tts_engine_invalid_1.json",
extension_manifest_errors::kInvalidTts);
- LoadAndExpectError("tts_provider_invalid_2.json",
+ LoadAndExpectError("tts_engine_invalid_2.json",
extension_manifest_errors::kInvalidTtsVoices);
- LoadAndExpectError("tts_provider_invalid_3.json",
+ LoadAndExpectError("tts_engine_invalid_3.json",
extension_manifest_errors::kInvalidTtsVoices);
- LoadAndExpectError("tts_provider_invalid_4.json",
+ LoadAndExpectError("tts_engine_invalid_4.json",
extension_manifest_errors::kInvalidTtsVoicesVoiceName);
- LoadAndExpectError("tts_provider_invalid_5.json",
- extension_manifest_errors::kInvalidTtsVoicesLocale);
- LoadAndExpectError("tts_provider_invalid_6.json",
- extension_manifest_errors::kInvalidTtsVoicesLocale);
- LoadAndExpectError("tts_provider_invalid_7.json",
+ LoadAndExpectError("tts_engine_invalid_5.json",
+ extension_manifest_errors::kInvalidTtsVoicesLang);
+ LoadAndExpectError("tts_engine_invalid_6.json",
+ extension_manifest_errors::kInvalidTtsVoicesLang);
+ LoadAndExpectError("tts_engine_invalid_7.json",
extension_manifest_errors::kInvalidTtsVoicesGender);
+ LoadAndExpectError("tts_engine_invalid_8.json",
+ extension_manifest_errors::kInvalidTtsVoicesEventTypes);
+ LoadAndExpectError("tts_engine_invalid_9.json",
+ extension_manifest_errors::kInvalidTtsVoicesEventTypes);
scoped_refptr<Extension> extension(
- LoadAndExpectSuccess("tts_provider_valid.json"));
+ LoadAndExpectSuccess("tts_engine_valid.json"));
ASSERT_EQ(1u, extension->tts_voices().size());
EXPECT_EQ("name", extension->tts_voices()[0].voice_name);
- EXPECT_EQ("en-US", extension->tts_voices()[0].locale);
+ EXPECT_EQ("en-US", extension->tts_voices()[0].lang);
EXPECT_EQ("female", extension->tts_voices()[0].gender);
+ EXPECT_EQ(3U, extension->tts_voices()[0].event_types.size());
}
TEST_F(ExtensionManifestTest, ForbidPortsInPermissions) {
diff --git a/chrome/renderer/extensions/extension_process_bindings.cc b/chrome/renderer/extensions/extension_process_bindings.cc
index 18a4a08..b518c9b 100644
--- a/chrome/renderer/extensions/extension_process_bindings.cc
+++ b/chrome/renderer/extensions/extension_process_bindings.cc
@@ -162,6 +162,8 @@ class ExtensionImpl : public ExtensionBase {
return v8::FunctionTemplate::New(OpenChannelToTab);
} else if (name->Equals(v8::String::New("GetNextContextMenuId"))) {
return v8::FunctionTemplate::New(GetNextContextMenuId);
+ } else if (name->Equals(v8::String::New("GetNextTtsEventId"))) {
+ return v8::FunctionTemplate::New(GetNextTtsEventId);
} else if (name->Equals(v8::String::New("GetCurrentPageActions"))) {
return v8::FunctionTemplate::New(GetCurrentPageActions,
v8::External::New(this));
@@ -375,6 +377,13 @@ class ExtensionImpl : public ExtensionBase {
return v8::Integer::New(next_context_menu_id++);
}
+ static v8::Handle<v8::Value> GetNextTtsEventId(const v8::Arguments& args) {
+ // Note: this works because the TTS API only works in the
+ // extension process, not content scripts.
+ static int next_tts_event_id = 1;
+ return v8::Integer::New(next_tts_event_id++);
+ }
+
static v8::Handle<v8::Value> GetCurrentPageActions(
const v8::Arguments& args) {
ExtensionImpl* v8_extension = GetFromArguments<ExtensionImpl>(args);
diff --git a/chrome/renderer/resources/extension_process_bindings.js b/chrome/renderer/resources/extension_process_bindings.js
index cce5d38..64814c9 100644
--- a/chrome/renderer/resources/extension_process_bindings.js
+++ b/chrome/renderer/resources/extension_process_bindings.js
@@ -14,6 +14,7 @@ var chrome = chrome || {};
native function GetChromeHidden();
native function GetNextRequestId();
native function GetNextContextMenuId();
+ native function GetNextTtsEventId();
native function OpenChannelToTab();
native function GetRenderViewId();
native function SetIconCommon();
@@ -529,16 +530,35 @@ var chrome = chrome || {};
}
function setupTtsEvents() {
- chrome.experimental.tts.onSpeak.dispatch =
+ chromeHidden.tts = {};
+ chromeHidden.tts.handlers = {};
+ chrome.experimental.ttsEngine.onSpeak.dispatch =
function(text, options, requestId) {
- var callback = function(errorMessage) {
- if (errorMessage)
- chrome.experimental.tts.speakCompleted(requestId, errorMessage);
- else
- chrome.experimental.tts.speakCompleted(requestId);
- };
- chrome.Event.prototype.dispatch.apply(this, [text, options, callback]);
- };
+ var sendTtsEvent = function(event) {
+ chrome.experimental.ttsEngine.sendTtsEvent(requestId, event);
+ };
+ chrome.Event.prototype.dispatch.apply(
+ this, [text, options, sendTtsEvent]);
+ };
+ try {
+ chrome.experimental.ttsEngine.onEvent.addListener(
+ function(event) {
+ var eventHandler = chromeHidden.tts.handlers[event.srcId];
+ if (eventHandler) {
+ eventHandler({
+ type: event.type,
+ charIndex: event.charIndex,
+ errorMessage: event.errorMessage
+ });
+ if (event.isFinalEvent) {
+ delete chromeHidden.tts.handlers[event.srcId];
+ }
+ }
+ });
+ } catch (e) {
+ // This extension doesn't have permission to access TTS, so we
+ // can safely ignore this.
+ }
}
// Get the platform from navigator.appVersion.
@@ -988,6 +1008,17 @@ var chrome = chrome || {};
return [requestId, suggestions];
};
+ apiFunctions["experimental.tts.speak"].handleRequest = function() {
+ var args = arguments;
+ if (args.length > 1 && args[1].onevent) {
+ var id = GetNextTtsEventId();
+ args[1].srcId = id;
+ chromeHidden.tts.handlers[id] = args[1].onevent;
+ }
+ sendRequest(this.name, args, this.definition.parameters);
+ return id;
+ };
+
if (chrome.test) {
chrome.test.getApiDefinitions = GetExtensionAPIDefinition;
}
@@ -1007,4 +1038,7 @@ var chrome = chrome || {};
if (!chrome.experimental.tts)
chrome.experimental.tts = {};
+
+ if (!chrome.experimental.ttsEngine)
+ chrome.experimental.ttsEngine = {};
})();
diff --git a/chrome/renderer/resources/renderer_extension_bindings.js b/chrome/renderer/resources/renderer_extension_bindings.js
index 074d948..777f4fa 100644
--- a/chrome/renderer/resources/renderer_extension_bindings.js
+++ b/chrome/renderer/resources/renderer_extension_bindings.js
@@ -314,6 +314,7 @@ var chrome = chrome || {};
"experimental.rlz",
"experimental.sidebar",
"experimental.tts",
+ "experimental.ttsEngine",
"experimental.webNavigation",
"experimental.webRequest",
"fileBrowserHandler",
diff --git a/chrome/test/data/extensions/api_test/tts/chromeos/test.js b/chrome/test/data/extensions/api_test/tts/chromeos/test.js
index ece6f97..6859f21 100644
--- a/chrome/test/data/extensions/api_test/tts/chromeos/test.js
+++ b/chrome/test/data/extensions/api_test/tts/chromeos/test.js
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -8,21 +8,33 @@
chrome.test.runTests([
function testChromeOsSpeech() {
var callbacks = 0;
- chrome.experimental.tts.speak('text 1', {}, function() {
- chrome.test.assertEq('Utterance interrupted.',
- chrome.extension.lastError.message);
- callbacks++;
- });
- chrome.experimental.tts.speak('text 2', {}, function() {
- chrome.test.assertNoLastError();
- callbacks++;
- if (callbacks == 2) {
+ chrome.experimental.tts.speak(
+ 'text 1',
+ {
+ 'onevent': function(event) {
+ callbacks++;
+ chrome.test.assertEq('interrupted', event.type);
+ }
+ },
+ function() {
chrome.test.assertNoLastError();
- chrome.test.succeed();
- } else {
- chrome.test.fail();
- }
- });
+ });
+ chrome.experimental.tts.speak(
+ 'text 2',
+ {
+ 'onevent': function(event) {
+ chrome.test.assertEq('end', event.type);
+ callbacks++;
+ if (callbacks == 2) {
+ chrome.test.succeed();
+ } else {
+ chrome.test.fail();
+ }
+ }
+ },
+ function() {
+ chrome.test.assertNoLastError();
+ });
}
]);
diff --git a/chrome/test/data/extensions/api_test/tts/enqueue/test.js b/chrome/test/data/extensions/api_test/tts/enqueue/test.js
index a903ab3..e006ee9 100644
--- a/chrome/test/data/extensions/api_test/tts/enqueue/test.js
+++ b/chrome/test/data/extensions/api_test/tts/enqueue/test.js
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -6,20 +6,38 @@
// browser_tests.exe --gtest_filter="TtsApiTest.*"
chrome.test.runTests([
- function testAllSpeakCallbackFunctionsAreCalled() {
+ function testEnqueue() {
var callbacks = 0;
- chrome.experimental.tts.speak('text 1', {'enqueue': true}, function() {
- chrome.test.assertNoLastError();
- callbacks++;
- });
- chrome.experimental.tts.speak('text 2', {'enqueue': true}, function() {
- chrome.test.assertNoLastError();
- callbacks++;
- if (callbacks == 2) {
- chrome.test.succeed();
- } else {
- chrome.test.fail();
- }
- });
+ chrome.experimental.tts.speak(
+ 'text 1',
+ {
+ 'enqueue': true,
+ 'onevent': function(event) {
+ chrome.test.assertEq('end', event.type);
+ callbacks++;
+ }
+ },
+ function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ });
+ chrome.experimental.tts.speak(
+ 'text 2',
+ {
+ 'enqueue': true,
+ 'onevent': function(event) {
+ chrome.test.assertEq('end', event.type);
+ callbacks++;
+ if (callbacks == 4) {
+ chrome.test.succeed();
+ } else {
+ chrome.test.fail();
+ }
+ }
+ },
+ function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ });
}
]);
diff --git a/chrome/test/data/extensions/api_test/tts/interrupt/test.js b/chrome/test/data/extensions/api_test/tts/interrupt/test.js
index 390d68f..65e000c 100644
--- a/chrome/test/data/extensions/api_test/tts/interrupt/test.js
+++ b/chrome/test/data/extensions/api_test/tts/interrupt/test.js
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -6,21 +6,36 @@
// browser_tests.exe --gtest_filter="TtsApiTest.*"
chrome.test.runTests([
- function testAllSpeakCallbackFunctionsAreCalled() {
+ function testInterrupt() {
var callbacks = 0;
- chrome.experimental.tts.speak('text 1', {'enqueue': false}, function() {
- chrome.test.assertEq('Utterance interrupted.',
- chrome.extension.lastError.message);
- callbacks++;
- });
- chrome.experimental.tts.speak('text 2', {'enqueue': false}, function() {
- chrome.test.assertNoLastError();
- callbacks++;
- if (callbacks == 2) {
- chrome.test.succeed();
- } else {
- chrome.test.fail();
- }
- });
+ chrome.experimental.tts.speak(
+ 'text 1',
+ {
+ 'enqueue': false,
+ 'onevent': function(event) {
+ chrome.test.assertEq('interrupted', event.type);
+ callbacks++;
+ }
+ },
+ function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ });
+ chrome.experimental.tts.speak(
+ 'text 2',
+ {
+ 'enqueue': false,
+ 'onevent': function(event) {
+ chrome.test.assertEq('end', event.type);
+ callbacks++;
+ if (callbacks == 4) {
+ chrome.test.succeed();
+ }
+ }
+ },
+ function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ });
}
]);
diff --git a/chrome/test/data/extensions/api_test/tts/provide/test.js b/chrome/test/data/extensions/api_test/tts/provide/test.js
deleted file mode 100644
index bc5e16a..0000000
--- a/chrome/test/data/extensions/api_test/tts/provide/test.js
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// TTS api test for Chrome on ChromeOS.
-// browser_tests.exe --gtest_filter="TtsApiTest.*"
-
-if (!chrome.tts) {
- chrome.tts = chrome.experimental.tts;
-}
-
-chrome.test.runTests([
- function testNoListeners() {
- // This call should go to native speech because we haven't registered
- // any listeners.
- chrome.tts.speak('native speech', {}, function() {
- chrome.test.assertNoLastError();
- chrome.test.succeed();
- });
- },
- function testTtsProvider() {
- // Register listeners for speech functions, enabling this extension
- // to be a TTS provider.
- var speakListener = function(utterance, options, callback) {
- chrome.test.assertNoLastError();
- chrome.test.assertEq('extension speech', utterance);
- callback();
- };
- var stopListener = function() {};
- chrome.tts.onSpeak.addListener(speakListener);
- chrome.tts.onStop.addListener(stopListener);
-
- // This call should go to our own speech provider.
- chrome.tts.speak('extension speech', {}, function() {
- chrome.test.assertNoLastError();
- chrome.tts.onSpeak.removeListener(speakListener);
- chrome.tts.onStop.removeListener(stopListener);
- chrome.test.succeed();
- });
- },
- function testVoiceMatching() {
- // Count the number of times our callback functions have been called.
- var callbacks = 0;
- // Count the number of times our TTS provider has been called.
- var speakListenerCalls = 0;
-
- // Register listeners for speech functions.
- var speakListener = function(utterance, options, callback) {
- speakListenerCalls++;
- callback();
- };
- var stopListener = function() {};
- chrome.tts.onSpeak.addListener(speakListener);
- chrome.tts.onStop.addListener(stopListener);
-
- // These don't match the voices in the manifest, so they should
- // go to native speech. The gmock assertions in TtsApiTest::Provide
- // enforce that the native TTS handlers are called.
- chrome.tts.speak('native speech 2',
- {'voiceName': 'George', 'enqueue': true}, function() {
- chrome.test.assertNoLastError();
- callbacks++;
- });
- chrome.tts.speak('native speech 3',
- {'locale': 'fr-FR', 'enqueue': true},
- function() {
- chrome.test.assertNoLastError();
- callbacks++;
- });
-
- // These do match the voices in the manifest, so they should go to our
- // own TTS provider.
- chrome.tts.speak('extension speech 2',
- {'voiceName': 'Alice', 'enqueue': true},
- function() {
- chrome.test.assertNoLastError();
- callbacks++;
- chrome.test.succeed();
- });
- chrome.tts.speak('extension speech 3',
- {'voiceName': 'Pat', 'gender': 'male', 'enqueue': true},
- function() {
- chrome.test.assertNoLastError();
- callbacks++;
- chrome.tts.onSpeak.removeListener(speakListener);
- chrome.tts.onStop.removeListener(stopListener);
- if (callbacks == 4 && speakListenerCalls == 2) {
- chrome.test.succeed();
- }
- });
- },
- function testTtsProviderError() {
- // Register listeners for speech functions, but have speak return an
- // error when it's used.
- var speakListener = function(utterance, options, callback) {
- chrome.test.assertEq('extension speech 4', utterance);
- callback('extension tts error');
- };
- var stopListener = function() {};
- chrome.tts.onSpeak.addListener(speakListener);
- chrome.tts.onStop.addListener(stopListener);
-
- // This should go to our own TTS provider, and we can check that we
- // get the error message.
- chrome.tts.speak('extension speech 4', {}, function() {
- chrome.test.assertEq('extension tts error',
- chrome.extension.lastError.message);
- chrome.tts.onSpeak.removeListener(speakListener);
- chrome.tts.onStop.removeListener(stopListener);
- chrome.test.succeed();
- });
- }
-]);
diff --git a/chrome/test/data/extensions/api_test/tts/queue_interrupt/test.js b/chrome/test/data/extensions/api_test/tts/queue_interrupt/test.js
index e2317b2..c21f9f7 100644
--- a/chrome/test/data/extensions/api_test/tts/queue_interrupt/test.js
+++ b/chrome/test/data/extensions/api_test/tts/queue_interrupt/test.js
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -6,30 +6,54 @@
// browser_tests.exe --gtest_filter="TtsApiTest.*"
chrome.test.runTests([
- function testAllSpeakCallbackFunctionsAreCalled() {
+ function testQueueInterrupt() {
// In this test, two utterances are queued, and then a third
// interrupts. The first gets interrupted, the second never gets spoken
// at all. The test expectations in extension_tts_apitest.cc ensure that
// the first call to tts.speak keeps going until it's interrupted.
var callbacks = 0;
- chrome.experimental.tts.speak('text 1', {'enqueue': true}, function() {
- chrome.test.assertEq('Utterance interrupted.',
- chrome.extension.lastError.message);
- callbacks++;
- });
- chrome.experimental.tts.speak('text 2', {'enqueue': true}, function() {
- chrome.test.assertEq('Utterance removed from queue.',
- chrome.extension.lastError.message);
- callbacks++;
- });
- chrome.experimental.tts.speak('text 3', {'enqueue': false}, function() {
- chrome.test.assertNoLastError();
- callbacks++;
- if (callbacks == 3) {
- chrome.test.succeed();
- } else {
- chrome.test.fail();
- }
- });
+ chrome.experimental.tts.speak(
+ 'text 1',
+ {
+ 'enqueue': true,
+ 'onevent': function(event) {
+ chrome.test.assertEq('interrupted', event.type);
+ callbacks++;
+ }
+ },
+ function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ });
+ chrome.experimental.tts.speak(
+ 'text 2',
+ {
+ 'enqueue': true,
+ 'onevent': function(event) {
+ chrome.test.assertEq('cancelled', event.type);
+ callbacks++;
+ }
+ }, function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ });
+ chrome.experimental.tts.speak(
+ 'text 3',
+ {
+ 'enqueue': false,
+ 'onevent': function(event) {
+ chrome.test.assertEq('end', event.type);
+ callbacks++;
+ if (callbacks == 6) {
+ chrome.test.succeed();
+ } else {
+ chrome.test.fail();
+ }
+ }
+ },
+ function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ });
}
]);
diff --git a/chrome/test/data/extensions/api_test/tts/speak_error/test.js b/chrome/test/data/extensions/api_test/tts/speak_error/test.js
index 637ee46..f592d97 100644
--- a/chrome/test/data/extensions/api_test/tts/speak_error/test.js
+++ b/chrome/test/data/extensions/api_test/tts/speak_error/test.js
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -6,24 +6,37 @@
// browser_tests.exe --gtest_filter="TtsApiTest.*"
chrome.test.runTests([
- function testSpeakCallbackFunctionIsCalled() {
+ function testSpeakError() {
var callbacks = 0;
- chrome.experimental.tts.speak('first try', {'enqueue': true}, function() {
- chrome.test.assertNoLastError();
- callbacks++;
- });
- chrome.experimental.tts.speak('second try', {'enqueue': true}, function() {
- chrome.test.assertEq('epic fail', chrome.extension.lastError.message);
- callbacks++;
- });
- chrome.experimental.tts.speak('third try', {'enqueue': true}, function() {
- chrome.test.assertNoLastError();
- callbacks++;
- if (callbacks == 3) {
- chrome.test.succeed();
- } else {
- chrome.test.fail();
- }
- });
+ chrome.experimental.tts.speak(
+ 'first try',
+ {
+ 'enqueue': true,
+ 'onevent': function(event) {
+ chrome.test.assertEq('error', event.type);
+ chrome.test.assertEq('epic fail', event.errorMessage);
+ callbacks++;
+ }
+ },
+ function() {
+ chrome.test.assertNoLastError();
+ });
+ chrome.experimental.tts.speak(
+ 'second try',
+ {
+ 'enqueue': true,
+ 'onevent': function(event) {
+ chrome.test.assertEq('end', event.type);
+ callbacks++;
+ if (callbacks == 2) {
+ chrome.test.succeed();
+ } else {
+ chrome.test.fail();
+ }
+ }
+ },
+ function() {
+ chrome.test.assertNoLastError();
+ });
}
]);
diff --git a/chrome/test/data/extensions/api_test/tts/speak_once/test.js b/chrome/test/data/extensions/api_test/tts/speak_once/test.js
index 33fc8f5..588f998 100644
--- a/chrome/test/data/extensions/api_test/tts/speak_once/test.js
+++ b/chrome/test/data/extensions/api_test/tts/speak_once/test.js
@@ -1,15 +1,21 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// TTS api test for Chrome on ChromeOS.
// browser_tests.exe --gtest_filter="TtsApiTest.*"
chrome.test.runTests([
- function testSpeakCallbackFunctionIsCalled() {
- chrome.experimental.tts.speak('hello world', {}, function() {
- chrome.test.assertNoLastError();
- chrome.test.succeed();
- });
+ function testSpeakOnce() {
+ function eventListener(event) {
+ chrome.test.assertEq('end', event.type);
+ chrome.test.assertEq(11, event.charIndex);
+ chrome.test.succeed();
+ }
+ chrome.experimental.tts.speak(
+ 'hello world',
+ {'onevent': eventListener},
+ function() {
+ chrome.test.assertNoLastError();
+ });
}
]);
diff --git a/chrome/test/data/extensions/api_test/tts/word_callbacks/manifest.json b/chrome/test/data/extensions/api_test/tts/word_callbacks/manifest.json
new file mode 100644
index 0000000..a43bc3d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/word_callbacks/manifest.json
@@ -0,0 +1,7 @@
+{
+ "name": "chrome.experimental.tts",
+ "version": "0.1",
+ "description": "browser test for chrome.experimental.tts API",
+ "background_page": "test.html",
+ "permissions": ["experimental"]
+}
diff --git a/chrome/test/data/extensions/api_test/tts/provide/test.html b/chrome/test/data/extensions/api_test/tts/word_callbacks/test.html
index 46f4d74..46f4d74 100644
--- a/chrome/test/data/extensions/api_test/tts/provide/test.html
+++ b/chrome/test/data/extensions/api_test/tts/word_callbacks/test.html
diff --git a/chrome/test/data/extensions/api_test/tts/word_callbacks/test.js b/chrome/test/data/extensions/api_test/tts/word_callbacks/test.js
new file mode 100644
index 0000000..232a552
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/word_callbacks/test.js
@@ -0,0 +1,40 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TTS api test for Chrome on ChromeOS.
+// browser_tests.exe --gtest_filter="TtsApiTest.*"
+
+chrome.test.runTests([
+ function testWordCallbacks() {
+ var callbacks = 0;
+ chrome.experimental.tts.speak(
+ 'one two three',
+ {
+ 'onevent': function(event) {
+ callbacks++;
+ switch(callbacks) {
+ case 1:
+ chrome.test.assertEq('word', event.type);
+ chrome.test.assertEq(0, event.charIndex);
+ break;
+ case 2:
+ chrome.test.assertEq('word', event.type);
+ chrome.test.assertEq(4, event.charIndex);
+ break;
+ case 3:
+ chrome.test.assertEq('word', event.type);
+ chrome.test.assertEq(8, event.charIndex);
+ break;
+ case 4:
+ chrome.test.assertEq('end', event.type);
+ chrome.test.assertEq(13, event.charIndex);
+ chrome.test.succeed();
+ break;
+ default:
+ chrome.test.fail();
+ }
+ }
+ });
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/tts/provide/manifest.json b/chrome/test/data/extensions/api_test/tts_engine/engine_error/manifest.json
index 93bc98d..39ac616 100644
--- a/chrome/test/data/extensions/api_test/tts/provide/manifest.json
+++ b/chrome/test/data/extensions/api_test/tts_engine/engine_error/manifest.json
@@ -3,16 +3,12 @@
"version": "0.1",
"description": "browser test for chrome.experimental.tts API",
"background_page": "test.html",
- "tts": {
+ "tts_engine": {
"voices": [
{
- "voiceName": "Alice",
- "locale": "en-US",
- "gender": "female"
- },
- {
- "voiceName": "Pat",
- "locale": "en-US"
+ "voice_name": "Zach",
+ "lang": "en-US",
+ "event_types": [ "end", "error" ]
}
]
},
diff --git a/chrome/test/data/extensions/api_test/tts_engine/engine_error/test.html b/chrome/test/data/extensions/api_test/tts_engine/engine_error/test.html
new file mode 100644
index 0000000..46f4d74
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts_engine/engine_error/test.html
@@ -0,0 +1 @@
+<script src="test.js"></script>
diff --git a/chrome/test/data/extensions/api_test/tts_engine/engine_error/test.js b/chrome/test/data/extensions/api_test/tts_engine/engine_error/test.js
new file mode 100644
index 0000000..d21156f
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts_engine/engine_error/test.js
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TTS api test for Chrome on ChromeOS.
+// browser_tests.exe --gtest_filter="TtsApiTest.*"
+
+if (!chrome.tts) {
+ chrome.tts = chrome.experimental.tts;
+}
+
+if (!chrome.ttsEngine) {
+ chrome.ttsEngine = chrome.experimental.ttsEngine;
+}
+
+chrome.test.runTests([
+ function testTtsEngineError() {
+ // Register listeners for speech functions, but have speak return an
+ // error when it's used.
+ var speakListener = function(utterance, options, sendTtsEvent) {
+ chrome.test.assertEq('extension speech', utterance);
+
+ try {
+ // This should fail because 'foo' isn't a valid event type.
+ sendTtsEvent({'type': 'foo'});
+ chrome.test.fail();
+ } catch (e) {
+ }
+
+ // This won't actually send an event, and an error will be logged
+ // to the console, because we haven't registered the 'word'
+ // event type in our manifest.
+ sendTtsEvent({'type': 'word'});
+
+ // This will succeed.
+ sendTtsEvent({
+ 'type': 'error',
+ 'charIndex': 0,
+ 'errorMessage': 'extension tts error'});
+ };
+ var stopListener = function() {};
+ chrome.ttsEngine.onSpeak.addListener(speakListener);
+ chrome.ttsEngine.onStop.addListener(stopListener);
+
+ // This should go to our own TTS engine, and we can check that we
+ // get the error message.
+ chrome.tts.speak(
+ 'extension speech',
+ {
+ 'onevent': function(event) {
+ chrome.test.assertEq('error', event.type);
+ chrome.test.assertEq('extension tts error', event.errorMessage);
+ chrome.ttsEngine.onSpeak.removeListener(speakListener);
+ chrome.ttsEngine.onStop.removeListener(stopListener);
+ chrome.test.succeed();
+ }
+ },
+ function() {
+ chrome.test.assertNoLastError();
+ });
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/tts_engine/engine_word_callbacks/manifest.json b/chrome/test/data/extensions/api_test/tts_engine/engine_word_callbacks/manifest.json
new file mode 100644
index 0000000..7d28357
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts_engine/engine_word_callbacks/manifest.json
@@ -0,0 +1,16 @@
+{
+ "name": "chrome.experimental.tts",
+ "version": "0.1",
+ "description": "browser test for chrome.experimental.tts API",
+ "background_page": "test.html",
+ "tts_engine": {
+ "voices": [
+ {
+ "voice_name": "WordCallbackVoice",
+ "lang": "en-US",
+ "event_types": [ "end", "word" ]
+ }
+ ]
+ },
+ "permissions": ["experimental"]
+}
diff --git a/chrome/test/data/extensions/api_test/tts_engine/engine_word_callbacks/test.html b/chrome/test/data/extensions/api_test/tts_engine/engine_word_callbacks/test.html
new file mode 100644
index 0000000..46f4d74
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts_engine/engine_word_callbacks/test.html
@@ -0,0 +1 @@
+<script src="test.js"></script>
diff --git a/chrome/test/data/extensions/api_test/tts_engine/engine_word_callbacks/test.js b/chrome/test/data/extensions/api_test/tts_engine/engine_word_callbacks/test.js
new file mode 100644
index 0000000..9a304ba
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts_engine/engine_word_callbacks/test.js
@@ -0,0 +1,66 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TTS api test for Chrome on ChromeOS.
+// browser_tests.exe --gtest_filter="TtsApiTest.*"
+
+if (!chrome.tts) {
+ chrome.tts = chrome.experimental.tts;
+}
+
+if (!chrome.ttsEngine) {
+ chrome.ttsEngine = chrome.experimental.ttsEngine;
+}
+
+chrome.test.runTests([
+ function testWordCallbacks() {
+ // Register listeners for speech functions, enabling this extension
+ // to be a TTS engine.
+ var speakListener = function(utterance, options, sendTtsEvent) {
+ chrome.test.assertNoLastError();
+ chrome.test.assertEq('alpha beta gamma', utterance);
+ sendTtsEvent({'type': 'word', 'charIndex': 0});
+ sendTtsEvent({'type': 'word', 'charIndex': 6});
+ sendTtsEvent({'type': 'word', 'charIndex': 11});
+ sendTtsEvent({'type': 'end', 'charIndex': utterance.length});
+ };
+ var stopListener = function() {};
+ chrome.ttsEngine.onSpeak.addListener(speakListener);
+ chrome.ttsEngine.onStop.addListener(stopListener);
+
+ var callbacks = 0;
+ chrome.tts.speak(
+ 'alpha beta gamma',
+ {
+ 'onevent': function(event) {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ switch(callbacks) {
+ case 1:
+ chrome.test.assertEq('word', event.type);
+ chrome.test.assertEq(0, event.charIndex);
+ break;
+ case 2:
+ chrome.test.assertEq('word', event.type);
+ chrome.test.assertEq(6, event.charIndex);
+ break;
+ case 3:
+ chrome.test.assertEq('word', event.type);
+ chrome.test.assertEq(11, event.charIndex);
+ break;
+ case 4:
+ chrome.test.assertEq('end', event.type);
+ chrome.test.assertEq(16, event.charIndex);
+ chrome.test.succeed();
+ break;
+ default:
+ chrome.test.fail();
+ }
+ }
+ },
+ function() {
+ chrome.test.assertNoLastError();
+ });
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/tts_engine/register_engine/manifest.json b/chrome/test/data/extensions/api_test/tts_engine/register_engine/manifest.json
new file mode 100644
index 0000000..77b5f03
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts_engine/register_engine/manifest.json
@@ -0,0 +1,22 @@
+{
+ "name": "chrome.experimental.tts",
+ "version": "0.1",
+ "description": "browser test for chrome.experimental.tts API",
+ "background_page": "test.html",
+ "tts_engine": {
+ "voices": [
+ {
+ "voice_name": "Alice",
+ "lang": "en-US",
+ "gender": "female",
+ "event_types": [ "end" ]
+ },
+ {
+ "voice_name": "Pat",
+ "lang": "en-US",
+ "event_types": [ "end" ]
+ }
+ ]
+ },
+ "permissions": ["experimental"]
+}
diff --git a/chrome/test/data/extensions/api_test/tts_engine/register_engine/test.html b/chrome/test/data/extensions/api_test/tts_engine/register_engine/test.html
new file mode 100644
index 0000000..46f4d74
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts_engine/register_engine/test.html
@@ -0,0 +1 @@
+<script src="test.js"></script>
diff --git a/chrome/test/data/extensions/api_test/tts_engine/register_engine/test.js b/chrome/test/data/extensions/api_test/tts_engine/register_engine/test.js
new file mode 100644
index 0000000..ba9dc14
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts_engine/register_engine/test.js
@@ -0,0 +1,170 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TTS api test for Chrome on ChromeOS.
+// browser_tests.exe --gtest_filter="TtsApiTest.*"
+
+if (!chrome.tts) {
+ chrome.tts = chrome.experimental.tts;
+}
+
+if (!chrome.ttsEngine) {
+ chrome.ttsEngine = chrome.experimental.ttsEngine;
+}
+
+chrome.test.runTests([
+ function testNoListeners() {
+ // This call should go to native speech because we haven't registered
+ // any listeners.
+ chrome.tts.speak(
+ 'native speech',
+ {
+ 'onevent': function(event) {
+ if (event.type == 'end') {
+ chrome.test.succeed();
+ }
+ }
+ }, function() {
+ chrome.test.assertNoLastError();
+ });
+ },
+ function testTtsEngine() {
+ var calledOurEngine = false;
+
+ // Register listeners for speech functions, enabling this extension
+ // to be a TTS engine.
+ var speakListener = function(utterance, options, sendTtsEvent) {
+ chrome.test.assertNoLastError();
+ chrome.test.assertEq('extension speech', utterance);
+ calledOurEngine = true;
+ sendTtsEvent({'type': 'end', 'charIndex': utterance.length});
+ };
+ var stopListener = function() {};
+ chrome.ttsEngine.onSpeak.addListener(speakListener);
+ chrome.ttsEngine.onStop.addListener(stopListener);
+
+ // This call should go to our own speech engine.
+ chrome.tts.speak(
+ 'extension speech',
+ {
+ 'onevent': function(event) {
+ if (event.type == 'end') {
+ chrome.test.assertEq(true, calledOurEngine);
+ chrome.ttsEngine.onSpeak.removeListener(speakListener);
+ chrome.ttsEngine.onStop.removeListener(stopListener);
+ chrome.test.succeed();
+ }
+ }
+ },
+ function() {
+ chrome.test.assertNoLastError();
+ });
+ },
+ function testVoiceMatching() {
+ // Count the number of times our callback functions have been called.
+ var callbacks = 0;
+ // Count the number of times our TTS engine has been called.
+ var speakListenerCalls = 0;
+
+ // Register listeners for speech functions.
+ var speakListener = function(utterance, options, sendTtsEvent) {
+ speakListenerCalls++;
+ sendTtsEvent({'type': 'end', 'charIndex': utterance.length});
+ };
+ var stopListener = function() {};
+ chrome.ttsEngine.onSpeak.addListener(speakListener);
+ chrome.ttsEngine.onStop.addListener(stopListener);
+
+ // These don't match the voices in the manifest, so they should
+ // go to native speech. The gmock assertions in TtsApiTest::RegisterEngine
+ // enforce that the native TTS handlers are called.
+ chrome.tts.speak(
+ 'native speech 2',
+ {
+ 'voiceName': 'George',
+ 'enqueue': true,
+ 'onevent': function(event) {
+ if (event.type == 'end') {
+ callbacks++;
+ }
+ }
+ }, function() {
+ chrome.test.assertNoLastError();
+ });
+ chrome.tts.speak(
+ 'native speech 3',
+ {
+ 'lang': 'fr-FR',
+ 'enqueue': true,
+ 'onevent': function(event) {
+ if (event.type == 'end') {
+ callbacks++;
+ }
+ }
+ }, function() {
+ chrome.test.assertNoLastError();
+ });
+
+ // These do match the voices in the manifest, so they should go to our
+ // own TTS engine.
+ chrome.tts.speak(
+ 'extension speech 2',
+ {
+ 'voiceName': 'Alice',
+ 'enqueue': true,
+ 'onevent': function(event) {
+ if (event.type == 'end') {
+ callbacks++;
+ }
+ }
+ }, function() {
+ chrome.test.assertNoLastError();
+ });
+ chrome.tts.speak(
+ 'extension speech 3',
+ {
+ 'voiceName': 'Pat',
+ 'gender': 'male',
+ 'enqueue': true,
+ 'onevent': function(event) {
+ if (event.type == 'end') {
+ callbacks++;
+ chrome.ttsEngine.onSpeak.removeListener(speakListener);
+ chrome.ttsEngine.onStop.removeListener(stopListener);
+ if (callbacks == 4 && speakListenerCalls == 2) {
+ chrome.test.succeed();
+ }
+ }
+ }
+ }, function() {
+ chrome.test.assertNoLastError();
+ });
+ },
+ function testGetVoices() {
+ // We have to register listeners, or the voices provided
+ // by this extension won't be returned.
+ var speakListener = function(utterance, options, sendTtsEvent) {
+ chrome.test.assertNoLastError();
+ chrome.test.assertEq('extension speech', utterance);
+ sendTtsEvent({'type': 'end', 'charIndex': utterance.length});
+ };
+ var stopListener = function() {};
+ chrome.ttsEngine.onSpeak.addListener(speakListener);
+ chrome.ttsEngine.onStop.addListener(stopListener);
+
+ chrome.tts.getVoices(function(voices) {
+ chrome.test.assertEq(3, voices.length);
+
+ chrome.test.assertEq('native', voices[0].voiceName);
+
+ chrome.test.assertEq('Alice', voices[1].voiceName);
+ chrome.test.assertEq('en-US', voices[1].lang);
+ chrome.test.assertEq('female', voices[1].gender);
+
+ chrome.test.assertEq('Pat', voices[2].voiceName);
+ chrome.test.assertEq('en-US', voices[2].lang);
+ chrome.test.succeed();
+ });
+ }
+]);
diff --git a/chrome/test/data/extensions/manifest_tests/tts_provider_invalid_1.json b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_1.json
index 90e849e..91f1c28 100644
--- a/chrome/test/data/extensions/manifest_tests/tts_provider_invalid_1.json
+++ b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_1.json
@@ -1,5 +1,5 @@
{
"name": "test",
"version": "1",
- "tts": "shouldBeADict"
+ "tts_engine": "shouldBeADict"
}
diff --git a/chrome/test/data/extensions/manifest_tests/tts_provider_invalid_2.json b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_2.json
index 261f5d3..5f44e62 100644
--- a/chrome/test/data/extensions/manifest_tests/tts_provider_invalid_2.json
+++ b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_2.json
@@ -1,7 +1,7 @@
{
"name": "test",
"version": "1",
- "tts": {
+ "tts_engine": {
"voices": "shouldBeAnArray"
}
}
diff --git a/chrome/test/data/extensions/manifest_tests/tts_provider_invalid_3.json b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_3.json
index b7443df..109804c 100644
--- a/chrome/test/data/extensions/manifest_tests/tts_provider_invalid_3.json
+++ b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_3.json
@@ -1,7 +1,7 @@
{
"name": "test",
"version": "1",
- "tts": {
+ "tts_engine": {
"voices": [
"shouldBeADict"
]
diff --git a/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_4.json b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_4.json
new file mode 100644
index 0000000..340b676
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_4.json
@@ -0,0 +1,11 @@
+{
+ "name": "test",
+ "version": "1",
+ "tts_engine": {
+ "voices": [
+ {
+ "voice_name": [ "Shouldn't be in an array" ]
+ }
+ ]
+ }
+}
diff --git a/chrome/test/data/extensions/manifest_tests/tts_provider_invalid_5.json b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_5.json
index 6297ab1..bb4025b 100644
--- a/chrome/test/data/extensions/manifest_tests/tts_provider_invalid_5.json
+++ b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_5.json
@@ -1,10 +1,10 @@
{
"name": "test",
"version": "1",
- "tts": {
+ "tts_engine": {
"voices": [
{
- "locale": ""
+ "lang": ""
}
]
}
diff --git a/chrome/test/data/extensions/manifest_tests/tts_provider_invalid_6.json b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_6.json
index ff45401..8e73a325 100644
--- a/chrome/test/data/extensions/manifest_tests/tts_provider_invalid_6.json
+++ b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_6.json
@@ -1,10 +1,10 @@
{
"name": "test",
"version": "1",
- "tts": {
+ "tts_engine": {
"voices": [
{
- "locale": "German"
+ "lang": "German"
}
]
}
diff --git a/chrome/test/data/extensions/manifest_tests/tts_provider_invalid_7.json b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_7.json
index cba13d6..defd0f3 100644
--- a/chrome/test/data/extensions/manifest_tests/tts_provider_invalid_7.json
+++ b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_7.json
@@ -1,7 +1,7 @@
{
"name": "test",
"version": "1",
- "tts": {
+ "tts_engine": {
"voices": [
{
"gender": "alien"
diff --git a/chrome/test/data/extensions/manifest_tests/tts_provider_invalid_4.json b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_8.json
index da41c61..635f484 100644
--- a/chrome/test/data/extensions/manifest_tests/tts_provider_invalid_4.json
+++ b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_8.json
@@ -1,10 +1,10 @@
{
"name": "test",
"version": "1",
- "tts": {
+ "tts_engine": {
"voices": [
{
- "voiceName": [ "Shouldn't be in an array" ]
+ "event_types": "shouldBeAnArray"
}
]
}
diff --git a/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_9.json b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_9.json
new file mode 100644
index 0000000..9679e45
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/tts_engine_invalid_9.json
@@ -0,0 +1,11 @@
+{
+ "name": "test",
+ "version": "1",
+ "tts_engine": {
+ "voices": [
+ {
+ "event_types": ["on vacation"]
+ }
+ ]
+ }
+}
diff --git a/chrome/test/data/extensions/manifest_tests/tts_provider_valid.json b/chrome/test/data/extensions/manifest_tests/tts_engine_valid.json
index 8e15ad1..9b9e9ad 100644
--- a/chrome/test/data/extensions/manifest_tests/tts_provider_valid.json
+++ b/chrome/test/data/extensions/manifest_tests/tts_engine_valid.json
@@ -1,12 +1,13 @@
{
"name": "test",
"version": "1",
- "tts": {
+ "tts_engine": {
"voices": [
{
- "voiceName": "name",
- "locale": "en-US",
+ "voice_name": "name",
+ "lang": "en-US",
"gender": "female",
+ "event_types": ["start", "end", "marker"],
"other_key": "other_value"
}
]