diff options
author | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-01 16:44:57 +0000 |
---|---|---|
committer | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-01 16:44:57 +0000 |
commit | 78127e620e70c4bdf030ebd8e54598c9182b0e4e (patch) | |
tree | ac0ccc1631ea68e135b246c8d8995d7d24951eab | |
parent | 29ccd8e9c7a8f16e2174fdcf9f0bafe10a74db98 (diff) | |
download | chromium_src-78127e620e70c4bdf030ebd8e54598c9182b0e4e.zip chromium_src-78127e620e70c4bdf030ebd8e54598c9182b0e4e.tar.gz chromium_src-78127e620e70c4bdf030ebd8e54598c9182b0e4e.tar.bz2 |
Implement Google network speech synthesis.
See bug for context. Implements a component extension
that provides speech synthesis using Google's speech
synthesis API.
Adds a "remote" flag to the TTS and TTS Engine APIs
so that it's possible for clients to distinguish
between local and remote speech engines.
Adds a new private extension API to expose Google's
API key, needed to make the request.
BUG=308250
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=232242
Review URL: https://codereview.chromium.org/27034009
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@232412 0039d316-1c4b-4281-b951-d872f2087c98
25 files changed, 507 insertions, 46 deletions
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd index db93304..687a4d0 100644 --- a/chrome/browser/browser_resources.grd +++ b/chrome/browser/browser_resources.grd @@ -149,6 +149,7 @@ <include name="IDR_INSTANT_JS" file="resources\instant\instant.js" flattenhtml="true" type="BINDATA" /> <include name="IDR_NET_EXPORT_HTML" file="resources\net_export\net_export.html" flattenhtml="true" type="BINDATA" /> <include name="IDR_NET_EXPORT_JS" file="resources\net_export\net_export.js" flattenhtml="true" type="BINDATA" /> + <include name="IDR_NETWORK_SPEECH_SYNTHESIS_MANIFEST" file="resources\network_speech_synthesis\manifest.json" type="BINDATA" /> <if expr="not is_android"> <include name="IDR_PERFORMANCE_MONITOR_CHART_CSS" file="resources\performance_monitor\chart.css" flattenhtml="true" type="BINDATA" /> <include name="IDR_PERFORMANCE_MONITOR_CHART_JS" file="resources\performance_monitor\chart.js" type="BINDATA" /> diff --git a/chrome/browser/extensions/api/system_private/system_private_api.cc b/chrome/browser/extensions/api/system_private/system_private_api.cc index 20e3c37..0ef4103 100644 --- a/chrome/browser/extensions/api/system_private/system_private_api.cc +++ b/chrome/browser/extensions/api/system_private/system_private_api.cc @@ -11,6 +11,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/common/extensions/api/system_private.h" #include "chrome/common/pref_names.h" +#include "google_apis/google_api_keys.h" #if defined(OS_CHROMEOS) #include "chromeos/dbus/dbus_thread_manager.h" @@ -132,6 +133,11 @@ bool SystemPrivateGetUpdateStatusFunction::RunImpl() { return true; } +bool SystemPrivateGetApiKeyFunction::RunImpl() { + SetResult(new base::StringValue(google_apis::GetAPIKey())); + return true; +} + void DispatchVolumeChangedEvent(double volume, bool is_volume_muted) { base::DictionaryValue* dict = new base::DictionaryValue(); dict->SetDouble(kVolumeKey, volume); diff --git a/chrome/browser/extensions/api/system_private/system_private_api.h b/chrome/browser/extensions/api/system_private/system_private_api.h index 33f91ba..ddb820b 100644 --- a/chrome/browser/extensions/api/system_private/system_private_api.h +++ b/chrome/browser/extensions/api/system_private/system_private_api.h @@ -39,6 +39,18 @@ class SystemPrivateGetUpdateStatusFunction virtual bool RunImpl() OVERRIDE; }; +// API function which returns the Google API key. +class SystemPrivateGetApiKeyFunction : public SyncExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("systemPrivate.getApiKey", SYSTEMPRIVATE_GETAPIKEY) + + protected: + virtual ~SystemPrivateGetApiKeyFunction() {} + + // ExtensionFunction: + virtual bool RunImpl() OVERRIDE; +}; + // Dispatches systemPrivate.onBrightnessChanged event for extensions. void DispatchBrightnessChangedEvent(int brightness, bool user_initiated); diff --git a/chrome/browser/extensions/component_loader.cc b/chrome/browser/extensions/component_loader.cc index 07ea15e..c023be6 100644 --- a/chrome/browser/extensions/component_loader.cc +++ b/chrome/browser/extensions/component_loader.cc @@ -316,6 +316,11 @@ void ComponentLoader::AddBookmarksExtensions() { #endif } +void ComponentLoader::AddNetworkSpeechSynthesisExtension() { + Add(IDR_NETWORK_SPEECH_SYNTHESIS_MANIFEST, + base::FilePath(FILE_PATH_LITERAL("network_speech_synthesis"))); +} + void ComponentLoader::AddWithName(int manifest_resource_id, const base::FilePath& root_directory, const std::string& name) { @@ -536,6 +541,10 @@ void ComponentLoader::AddDefaultComponentExtensionsWithBackgroundPages( base::FilePath(FILE_PATH_LITERAL("google_now"))); } #endif + +#if defined(GOOGLE_CHROME_BUILD) + AddNetworkSpeechSynthesisExtension(); +#endif // defined(GOOGLE_CHROME_BUILD) } void ComponentLoader::UnloadComponent(ComponentExtensionInfo* component) { diff --git a/chrome/browser/extensions/component_loader.h b/chrome/browser/extensions/component_loader.h index 8b1a961..1dd2e8d 100644 --- a/chrome/browser/extensions/component_loader.h +++ b/chrome/browser/extensions/component_loader.h @@ -115,6 +115,7 @@ class ComponentLoader { void AddHangoutServicesExtension(); void AddImageLoaderExtension(); void AddBookmarksExtensions(); + void AddNetworkSpeechSynthesisExtension(); void AddWithName(int manifest_resource_id, const base::FilePath& root_directory, @@ -135,6 +136,8 @@ class ComponentLoader { typedef std::vector<ComponentExtensionInfo> RegisteredComponentExtensions; RegisteredComponentExtensions component_extensions_; + FRIEND_TEST_ALL_PREFIXES(TtsApiTest, NetworkSpeechEngine); + DISALLOW_COPY_AND_ASSIGN(ComponentLoader); }; diff --git a/chrome/browser/extensions/extension_function_histogram_value.h b/chrome/browser/extensions/extension_function_histogram_value.h index 14d357d..42e5ccd 100644 --- a/chrome/browser/extensions/extension_function_histogram_value.h +++ b/chrome/browser/extensions/extension_function_histogram_value.h @@ -682,6 +682,7 @@ enum HistogramValue { APP_CURRENTWINDOWINTERNAL_SETMINHEIGHT, APP_CURRENTWINDOWINTERNAL_SETMAXWIDTH, APP_CURRENTWINDOWINTERNAL_SETMAXHEIGHT, + SYSTEMPRIVATE_GETAPIKEY, ENUM_BOUNDARY // Last entry: Add new entries above. }; diff --git a/chrome/browser/resources/component_extension_resources.grd b/chrome/browser/resources/component_extension_resources.grd index f1a9202..5107a7d 100644 --- a/chrome/browser/resources/component_extension_resources.grd +++ b/chrome/browser/resources/component_extension_resources.grd @@ -21,6 +21,8 @@ </if> </structures> <includes> + <include name="IDR_NETWORK_SPEECH_SYNTHESIS_JS" file="network_speech_synthesis/tts_extension.js" type="BINDATA" /> + <include name="IDR_BOOKMARK_MANAGER_BOOKMARK_MANAGER_SEARCH" file="bookmark_manager/images/bookmark_manager_search.png" type="BINDATA" /> <include name="IDR_BOOKMARK_MANAGER_BOOKMARK_MANAGER_SEARCH_RTL" file="bookmark_manager/images/bookmark_manager_search_rtl.png" type="BINDATA" /> <include name="IDR_BOOKMARK_MANAGER_BOOKMARK_MAIN_JS" file="bookmark_manager/js/main.js" type="BINDATA" /> diff --git a/chrome/browser/resources/network_speech_synthesis/manifest.json b/chrome/browser/resources/network_speech_synthesis/manifest.json new file mode 100644 index 0000000..9019ee6 --- /dev/null +++ b/chrome/browser/resources/network_speech_synthesis/manifest.json @@ -0,0 +1,90 @@ +{ + "background": { + "scripts": [ "tts_extension.js" ], + "persistent": false + }, + "description": "Component extension providing speech via the Google network text-to-speech service.", + "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8GSbNUMGygqQTNDMFGIjZNcwXsHLzkNkHjWbuY37PbNdSDZ4VqlVjzbWqODSe+MjELdv5Keb51IdytnoGYXBMyqKmWpUrg+RnKvQ5ibWr4MW9pyIceOIdp9GrzC1WZGgTmZismYR3AjaIpufZ7xDdQQv+XrghPWCkdVqLN+qZDA1HU+DURznkMICiDDSH2sU0egm9UbWfS218bZqzKeQDiC3OnTPlaxcbJtKUuupIm5knjze3Wo9Ae9poTDMzKgchg0VlFCv3uqox+wlD8sjXBoyBCCK9HpImdVAF1a7jpdgiUHpPeV/26oYzM9/grltwNR3bzECQgSpyXp0eyoegwIDAQAB", + "manifest_version": 2, + "name": "Google Network Speech", + "permissions": [ + "systemPrivate", + "ttsEngine", + "https://www.google.com/" + ], + "tts_engine": { + "voices": [ + { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "en-US", + "voice_name": "Google US English", + "remote": true + }, + { + "event_types": [ "start", "end", "error" ], + "gender": "male", + "lang": "en-GB", + "voice_name": "Google UK English Male", + "remote": true + }, + { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "en-GB", + "voice_name": "Google UK English Female", + "remote": true + }, + { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "es-ES", + "voice_name": "Google Español", + "remote": true + }, + { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "fr-FR", + "voice_name": "Google Français", + "remote": true + }, + { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "it-IT", + "voice_name": "Google Italiano", + "remote": true + }, + { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "de-DE", + "voice_name": "Google Deutsch", + "remote": true + }, + { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "ja-JP", + "voice_name": "Google 日本人", + "remote": true + }, + { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "ko-KR", + "voice_name": "Google 한국의", + "remote": true + }, + { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "zh-CN", + "voice_name": "Google 中国的", + "remote": true + } + ] + }, + "version": "1.0" +} diff --git a/chrome/browser/resources/network_speech_synthesis/tts_extension.js b/chrome/browser/resources/network_speech_synthesis/tts_extension.js new file mode 100644 index 0000000..b2a3e71 --- /dev/null +++ b/chrome/browser/resources/network_speech_synthesis/tts_extension.js @@ -0,0 +1,251 @@ +// Copyright 2013 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. + +/** + * @fileoverview + * This is a component extension that implements a text-to-speech (TTS) + * engine powered by Google's speech synthesis API. + * + * This is an "event page", so it's not loaded when the API isn't being used, + * and doesn't waste resources. When a web page or web app makes a speech + * request and the parameters match one of the voices in this extension's + * manifest, it makes a request to Google's API using Chrome's private key + * and plays the resulting speech using HTML5 audio. + */ + +/** + * The main class for this extension. Adds listeners to + * chrome.ttsEngine.onSpeak and chrome.ttsEngine.onStop and implements + * them using Google's speech synthesis API. + * @constructor + */ +function TtsExtension() {} + +TtsExtension.prototype = { + /** + * The url prefix of the speech server, including static query + * parameters that don't change. + * @type {string} + * @const + * @private + */ + SPEECH_SERVER_URL_: + 'https://www.google.com/speech-api/v2/synthesize?' + + 'enc=mpeg&client=chromium', + + /** + * A mapping from language and gender to voice name, hardcoded for now + * until the speech synthesis server capabilities response provides this. + * The key of this map is of the form '<lang>-<gender>'. + * @type {Object.<string, string>} + * @private + */ + LANG_AND_GENDER_TO_VOICE_NAME_: { + 'en-gb-male': 'rjs', + 'en-gb-female': 'fis', + }, + + /** + * The arguments passed to the onSpeak event handler for the utterance + * that's currently being spoken. Should be null when no object is + * pending. + * + * @type {?{utterance: string, options: Object, callback: Function}} + * @private + */ + currentUtterance_: null, + + /** + * The HTML5 audio element we use for playing the sound served by the + * speech server. + * @type {HTMLAudioElement} + * @private + */ + audioElement_: null, + + /** + * A mapping from voice name to language and gender, derived from the + * manifest file. This is used in case the speech synthesis request + * specifies a voice name but doesn't specify a language code or gender. + * @type {Object.<string, {lang: string, gender: string}>} + * @private + */ + voiceNameToLangAndGender_: {}, + + /** + * This is the main function called to initialize this extension. + * Initializes data structures and adds event listeners. + */ + init: function() { + // Get voices from manifest. + var voices = chrome.app.getDetails().tts_engine.voices; + for (var i = 0; i < voices.length; i++) { + this.voiceNameToLangAndGender_[voices[i].voice_name] = { + lang: voices[i].lang, + gender: voices[i].gender + }; + } + + // Initialize the audio element and event listeners on it. + this.audioElement_ = document.createElement('audio'); + document.body.appendChild(this.audioElement_); + this.audioElement_.addEventListener( + 'ended', this.onStop_.bind(this), false); + this.audioElement_.addEventListener( + 'canplaythrough', this.onStart_.bind(this), false); + + // Install event listeners for the ttsEngine API. + chrome.ttsEngine.onSpeak.addListener(this.onSpeak_.bind(this)); + chrome.ttsEngine.onStop.addListener(this.onStop_.bind(this)); + chrome.ttsEngine.onPause.addListener(this.onPause_.bind(this)); + chrome.ttsEngine.onResume.addListener(this.onResume_.bind(this)); + }, + + /** + * Handler for the chrome.ttsEngine.onSpeak interface. + * Gets Chrome's Google API key and then uses it to generate a request + * url for the requested speech utterance. Sets that url as the source + * of the HTML5 audio element. + * @param {string} utterance The text to be spoken. + * @param {Object} options Options to control the speech, as defined + * in the Chrome ttsEngine extension API. + * @private + */ + onSpeak_: function(utterance, options, callback) { + // Truncate the utterance if it's too long. Both Chrome's tts + // extension api and the web speech api specify 32k as the + // maximum limit for an utterance. + if (utterance.length > 32768) + utterance = utterance.substr(0, 32768); + + try { + // First, stop any pending audio. + this.onStop_(); + + this.currentUtterance_ = { + utterance: utterance, + options: options, + callback: callback + }; + + var lang = options.lang; + var gender = options.gender; + if (options.voiceName) { + lang = this.voiceNameToLangAndGender_[options.voiceName].lang; + gender = this.voiceNameToLangAndGender_[options.voiceName].gender; + } + + // Look up the specific voice name for this language and gender. + // If it's not in the map, it doesn't matter - the language will + // be used directly. This is only used for languages where more + // than one gender is actually available. + var key = lang.toLowerCase() + '-' + gender; + var voiceName = this.LANG_AND_GENDER_TO_VOICE_NAME_[key]; + + var url = this.SPEECH_SERVER_URL_; + chrome.systemPrivate.getApiKey((function(key) { + url += '&key=' + key; + url += '&text=' + escape(utterance); + url += '&lang=' + lang.toLowerCase(); + + if (voiceName) + url += '&name=' + voiceName; + + if (options.rate) { + // Input rate is between 0.1 and 10.0 with a default of 1.0. + // Output speed is between 0.0 and 1.0 with a default of 0.5. + url += '&speed=' + (options.rate / 2.0); + } + + if (options.pitch) { + // Input pitch is between 0.0 and 2.0 with a default of 1.0. + // Output pitch is between 0.0 and 1.0 with a default of 0.5. + url += '&pitch=' + (options.pitch / 2.0); + } + + // This begins loading the audio but does not play it. + // When enough of the audio has loaded to begin playback, + // the 'canplaythrough' handler will call this.onStart_, + // which sends a start event to the ttsEngine callback and + // then begins playing audio. + this.audioElement_.src = url; + }).bind(this)); + } catch (err) { + console.error(String(err)); + callback({ + 'type': 'error', + 'errorMessage': String(err) + }); + this.currentUtterance_ = null; + } + }, + + /** + * Handler for the chrome.ttsEngine.onStop interface. + * Called either when the ttsEngine API requests us to stop, or when + * we reach the end of the audio stream. Pause the audio element to + * silence it, and send a callback to the ttsEngine API to let it know + * that we've completed. Note that the ttsEngine API manages callback + * messages and will automatically replace the 'end' event with a + * more specific callback like 'interrupted' when sending it to the + * TTS client. + * @private + */ + onStop_: function() { + if (this.currentUtterance_) { + this.audioElement_.pause(); + this.currentUtterance_.callback({ + 'type': 'end', + 'charIndex': this.currentUtterance_.utterance.length + }); + } + this.currentUtterance_ = null; + }, + + /** + * Handler for the canplaythrough event on the audio element. + * Called when the audio element has buffered enough audio to begin + * playback. Send the 'start' event to the ttsEngine callback and + * then begin playing the audio element. + * @private + */ + onStart_: function() { + if (this.currentUtterance_) { + if (this.currentUtterance_.options.volume !== undefined) { + // Both APIs use the same range for volume, between 0.0 and 1.0. + this.audioElement_.volume = this.currentUtterance_.options.volume; + } + this.audioElement_.play(); + this.currentUtterance_.callback({ + 'type': 'start', + 'charIndex': 0 + }); + } + }, + + /** + * Handler for the chrome.ttsEngine.onPause interface. + * Pauses audio if we're in the middle of an utterance. + * @private + */ + onPause_: function() { + if (this.currentUtterance_) { + this.audioElement_.pause(); + } + }, + + /** + * Handler for the chrome.ttsEngine.onPause interface. + * Resumes audio if we're in the middle of an utterance. + * @private + */ + onResume_: function() { + if (this.currentUtterance_) { + this.audioElement_.play(); + } + } + +}; + +(new TtsExtension()).init(); diff --git a/chrome/browser/speech/extension_api/tts_engine_extension_api.cc b/chrome/browser/speech/extension_api/tts_engine_extension_api.cc index ba5e65b..b6908760 100644 --- a/chrome/browser/speech/extension_api/tts_engine_extension_api.cc +++ b/chrome/browser/speech/extension_api/tts_engine_extension_api.cc @@ -91,6 +91,7 @@ void GetExtensionVoices(Profile* profile, std::vector<VoiceData>* out_voices) { result_voice.native = false; result_voice.name = voice.voice_name; result_voice.lang = voice.lang; + result_voice.remote = voice.remote; result_voice.extension_id = extension->id(); if (voice.gender == constants::kGenderMale) result_voice.gender = TTS_GENDER_MALE; diff --git a/chrome/browser/speech/extension_api/tts_extension_api.cc b/chrome/browser/speech/extension_api/tts_extension_api.cc index 6523b4e..14c5641 100644 --- a/chrome/browser/speech/extension_api/tts_extension_api.cc +++ b/chrome/browser/speech/extension_api/tts_extension_api.cc @@ -307,6 +307,7 @@ bool TtsGetVoicesFunction::RunImpl() { const VoiceData& voice = voices[i]; DictionaryValue* result_voice = new DictionaryValue(); result_voice->SetString(constants::kVoiceNameKey, voice.name); + result_voice->SetBoolean(constants::kRemoteKey, voice.remote); if (!voice.lang.empty()) result_voice->SetString(constants::kLangKey, voice.lang); if (voice.gender == TTS_GENDER_MALE) diff --git a/chrome/browser/speech/extension_api/tts_extension_api_constants.cc b/chrome/browser/speech/extension_api/tts_extension_api_constants.cc index 7b8c45b..f1853dc 100644 --- a/chrome/browser/speech/extension_api/tts_extension_api_constants.cc +++ b/chrome/browser/speech/extension_api/tts_extension_api_constants.cc @@ -6,48 +6,49 @@ namespace tts_extension_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 kCharIndexKey[] = "charIndex"; +const char kDesiredEventTypesKey[] = "desiredEventTypes"; const char kEnqueueKey[] = "enqueue"; +const char kErrorMessageKey[] = "errorMessage"; 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 kGenderKey[] = "gender"; const char kIsFinalEventKey[] = "isFinalEvent"; +const char kLangKey[] = "lang"; const char kOnEventKey[] = "onEvent"; +const char kPitchKey[] = "pitch"; +const char kRateKey[] = "rate"; +const char kRemoteKey[] = "remote"; +const char kRequiredEventTypesKey[] = "requiredEventTypes"; +const char kSrcIdKey[] = "srcId"; +const char kVoiceNameKey[] = "voiceName"; +const char kVolumeKey[] = "volume"; 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 kEventTypeEnd[] = "end"; const char kEventTypeError[] = "error"; +const char kEventTypeInterrupted[] = "interrupted"; +const char kEventTypeMarker[] = "marker"; const char kEventTypePause[] = "pause"; const char kEventTypeResume[] = "resume"; +const char kEventTypeSentence[] = "sentence"; +const char kEventTypeStart[] = "start"; +const char kEventTypeWord[] = "word"; -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 kErrorInvalidLang[] = "Invalid lang."; const char kErrorInvalidPitch[] = "Invalid pitch."; +const char kErrorInvalidRate[] = "Invalid rate."; const char kErrorInvalidVolume[] = "Invalid volume."; const char kErrorMissingPauseOrResume[] = "A TTS engine extension should either listen for both onPause and onResume " "events, or neither."; +const char kErrorUndeclaredEventType[] = + "Cannot send an event type that is not declared in the extension manifest."; +const char kErrorUtteranceTooLong[] = "Utterance length is too long."; } // namespace tts_extension_api_constants. diff --git a/chrome/browser/speech/extension_api/tts_extension_api_constants.h b/chrome/browser/speech/extension_api/tts_extension_api_constants.h index 0809473..bec1749 100644 --- a/chrome/browser/speech/extension_api/tts_extension_api_constants.h +++ b/chrome/browser/speech/extension_api/tts_extension_api_constants.h @@ -11,46 +11,47 @@ namespace tts_extension_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 kCharIndexKey[]; +extern const char kDesiredEventTypesKey[]; extern const char kEnqueueKey[]; +extern const char kErrorMessageKey[]; 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 kGenderKey[]; extern const char kIsFinalEventKey[]; +extern const char kLangKey[]; extern const char kOnEventKey[]; +extern const char kPitchKey[]; +extern const char kRateKey[]; +extern const char kRemoteKey[]; +extern const char kRequiredEventTypesKey[]; +extern const char kSrcIdKey[]; +extern const char kVoiceNameKey[]; +extern const char kVolumeKey[]; 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 kEventTypeEnd[]; extern const char kEventTypeError[]; +extern const char kEventTypeInterrupted[]; +extern const char kEventTypeMarker[]; extern const char kEventTypePause[]; extern const char kEventTypeResume[]; +extern const char kEventTypeSentence[]; +extern const char kEventTypeStart[]; +extern const char kEventTypeWord[]; -extern const char kErrorUndeclaredEventType[]; -extern const char kErrorUtteranceTooLong[]; -extern const char kErrorInvalidLang[]; extern const char kErrorInvalidGender[]; -extern const char kErrorInvalidRate[]; +extern const char kErrorInvalidLang[]; extern const char kErrorInvalidPitch[]; +extern const char kErrorInvalidRate[]; extern const char kErrorInvalidVolume[]; extern const char kErrorMissingPauseOrResume[]; +extern const char kErrorUndeclaredEventType[]; +extern const char kErrorUtteranceTooLong[]; } // namespace tts_extension_api_constants. #endif // CHROME_BROWSER_SPEECH_EXTENSION_API_TTS_EXTENSION_API_CONSTANTS_H_ diff --git a/chrome/browser/speech/extension_api/tts_extension_apitest.cc b/chrome/browser/speech/extension_api/tts_extension_apitest.cc index 63cc83c..b9e7a9a 100644 --- a/chrome/browser/speech/extension_api/tts_extension_apitest.cc +++ b/chrome/browser/speech/extension_api/tts_extension_apitest.cc @@ -6,7 +6,10 @@ #include "base/command_line.h" #include "base/memory/weak_ptr.h" #include "base/message_loop/message_loop.h" +#include "chrome/browser/extensions/component_loader.h" #include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/speech/extension_api/tts_extension_api.h" #include "chrome/browser/speech/tts_controller.h" #include "chrome/browser/speech/tts_platform.h" @@ -32,6 +35,8 @@ namespace { int g_saved_utterance_id; } +namespace extensions { + class MockTtsPlatformImpl : public TtsPlatformImpl { public: MockTtsPlatformImpl() @@ -375,7 +380,16 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, LangMatching) { ASSERT_TRUE(RunExtensionTest("tts_engine/lang_matching")) << message_; } +IN_PROC_BROWSER_TEST_F(TtsApiTest, NetworkSpeechEngine) { + ExtensionService* service = extensions::ExtensionSystem::Get( + profile())->extension_service(); + service->component_loader()->AddNetworkSpeechSynthesisExtension(); + ASSERT_TRUE(RunExtensionTest("tts_engine/network_speech_engine")) << message_; +} + // http://crbug.com/122474 IN_PROC_BROWSER_TEST_F(TtsApiTest, EngineApi) { ASSERT_TRUE(RunExtensionTest("tts_engine/engine_api")) << message_; } + +} // namespace extensions diff --git a/chrome/browser/speech/tts_controller.cc b/chrome/browser/speech/tts_controller.cc index a0d376f..34f72a4 100644 --- a/chrome/browser/speech/tts_controller.cc +++ b/chrome/browser/speech/tts_controller.cc @@ -57,6 +57,7 @@ UtteranceContinuousParameters::UtteranceContinuousParameters() VoiceData::VoiceData() : gender(TTS_GENDER_NONE), + remote(false), native(false) {} VoiceData::~VoiceData() {} @@ -419,4 +420,3 @@ void TtsController::RemoveVoicesChangedDelegate( VoicesChangedDelegate* delegate) { voices_changed_delegates_.erase(delegate); } - diff --git a/chrome/browser/speech/tts_controller.h b/chrome/browser/speech/tts_controller.h index 18cbcd9..6081518 100644 --- a/chrome/browser/speech/tts_controller.h +++ b/chrome/browser/speech/tts_controller.h @@ -66,6 +66,10 @@ struct VoiceData { std::string extension_id; std::set<TtsEventType> events; + // If true, the synthesis engine is a remote network resource. + // It may be higher latency and may incur bandwidth costs. + bool remote; + // If true, this is implemented by this platform's subclass of // TtsPlatformImpl. If false, this is implemented by an extension. bool native; diff --git a/chrome/browser/speech/tts_message_filter.cc b/chrome/browser/speech/tts_message_filter.cc index 714c51f..3c397cd 100644 --- a/chrome/browser/speech/tts_message_filter.cc +++ b/chrome/browser/speech/tts_message_filter.cc @@ -67,7 +67,7 @@ void TtsMessageFilter::OnInitializeVoiceList() { out_voice.voice_uri = voices[i].name; out_voice.name = voices[i].name; out_voice.lang = voices[i].lang; - out_voice.local_service = true; + out_voice.local_service = !voices[i].remote; out_voice.is_default = (i == 0); } Send(new TtsMsg_SetVoiceList(out_voices)); diff --git a/chrome/common/extensions/api/speech/tts_engine_manifest_handler.cc b/chrome/common/extensions/api/speech/tts_engine_manifest_handler.cc index bed4937..e18c594 100644 --- a/chrome/common/extensions/api/speech/tts_engine_manifest_handler.cc +++ b/chrome/common/extensions/api/speech/tts_engine_manifest_handler.cc @@ -29,7 +29,8 @@ struct TtsVoices : public Extension::ManifestData { } // namespace -TtsVoice::TtsVoice() {} +TtsVoice::TtsVoice() : remote(false) {} + TtsVoice::~TtsVoice() {} // static @@ -95,6 +96,13 @@ bool TtsEngineManifestHandler::Parse(Extension* extension, string16* error) { return false; } } + if (one_tts_voice->HasKey(keys::kTtsVoicesRemote)) { + if (!one_tts_voice->GetBoolean( + keys::kTtsVoicesRemote, &voice_data.remote)) { + *error = ASCIIToUTF16(errors::kInvalidTtsVoicesRemote); + return false; + } + } if (one_tts_voice->HasKey(keys::kTtsVoicesEventTypes)) { const base::ListValue* event_types_list; if (!one_tts_voice->GetList( diff --git a/chrome/common/extensions/api/speech/tts_engine_manifest_handler.h b/chrome/common/extensions/api/speech/tts_engine_manifest_handler.h index 71d8e05..13b194b 100644 --- a/chrome/common/extensions/api/speech/tts_engine_manifest_handler.h +++ b/chrome/common/extensions/api/speech/tts_engine_manifest_handler.h @@ -21,6 +21,7 @@ struct TtsVoice { std::string voice_name; std::string lang; std::string gender; + bool remote; std::set<std::string> event_types; static const std::vector<TtsVoice>* GetTtsVoices(const Extension* extension); diff --git a/chrome/common/extensions/api/system_private.json b/chrome/common/extensions/api/system_private.json index 7d95450..ea9ae38 100644 --- a/chrome/common/extensions/api/system_private.json +++ b/chrome/common/extensions/api/system_private.json @@ -86,6 +86,24 @@ ] } ] + }, + { + "name": "getApiKey", + "type": "function", + "description": "Gets Chrome's API key to use for requests to Google services.", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "key", + "type": "string", + "description": "The API key." + } + ] + } + ] } ], "events": [ diff --git a/chrome/common/extensions/api/tts.json b/chrome/common/extensions/api/tts.json index ae8771e..995968ca 100644 --- a/chrome/common/extensions/api/tts.json +++ b/chrome/common/extensions/api/tts.json @@ -62,6 +62,11 @@ "description": "This voice's gender.", "enum": ["male", "female"] }, + "remote": { + "type": "boolean", + "optional": true, + "description": "If true, the synthesis engine is a remote network resource. It may be higher latency and may incur bandwidth costs." + }, "extensionId": { "type": "string", "optional": true, diff --git a/chrome/test/data/extensions/api_test/tts_engine/network_speech_engine/manifest.json b/chrome/test/data/extensions/api_test/tts_engine/network_speech_engine/manifest.json new file mode 100644 index 0000000..14fec9e --- /dev/null +++ b/chrome/test/data/extensions/api_test/tts_engine/network_speech_engine/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "chrome.tts", + "version": "0.1", + "manifest_version": 2, + "description": "browser test for chrome.tts API", + "background": { + "scripts": [ "test.js" ] + }, + "permissions": ["tts"] +} diff --git a/chrome/test/data/extensions/api_test/tts_engine/network_speech_engine/test.js b/chrome/test/data/extensions/api_test/tts_engine/network_speech_engine/test.js new file mode 100644 index 0000000..72b7e34 --- /dev/null +++ b/chrome/test/data/extensions/api_test/tts_engine/network_speech_engine/test.js @@ -0,0 +1,17 @@ +// Copyright 2013 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. + +// browser_tests.exe --gtest_filter="TtsApiTest.*" + +chrome.test.runTests([ + function testNetworkSpeechVoices() { + chrome.tts.getVoices(function(voices) { + chrome.test.assertTrue(voices.length >= 6); + for (var i = 0; i < voices.length; i++) { + chrome.test.assertEq(true, voices[i].remote); + } + chrome.test.succeed(); + }); + } +]); diff --git a/extensions/common/manifest_constants.cc b/extensions/common/manifest_constants.cc index 962ff47..b15db19 100644 --- a/extensions/common/manifest_constants.cc +++ b/extensions/common/manifest_constants.cc @@ -146,6 +146,7 @@ const char kTtsVoicesEventTypeWord[] = "word"; const char kTtsVoicesEventTypes[] = "event_types"; const char kTtsVoicesGender[] = "gender"; const char kTtsVoicesLang[] = "lang"; +const char kTtsVoicesRemote[] = "remote"; const char kTtsVoicesVoiceName[] = "voice_name"; const char kType[] = "type"; const char kUpdateURL[] = "update_url"; @@ -573,6 +574,8 @@ const char kInvalidTtsVoicesGender[] = "Invalid value for 'tts_engine.voices[*].gender'."; const char kInvalidTtsVoicesLang[] = "Invalid value for 'tts_engine.voices[*].lang'."; +const char kInvalidTtsVoicesRemote[] = + "Invalid value for 'tts_engine.voices[*].remote'."; const char kInvalidTtsVoicesVoiceName[] = "Invalid value for 'tts_engine.voices[*].voice_name'."; const char kInvalidUpdateURL[] = diff --git a/extensions/common/manifest_constants.h b/extensions/common/manifest_constants.h index 9b3fe9b..f645477 100644 --- a/extensions/common/manifest_constants.h +++ b/extensions/common/manifest_constants.h @@ -154,6 +154,7 @@ extern const char kTtsVoicesEventTypeWord[]; extern const char kTtsVoicesEventTypes[]; extern const char kTtsVoicesGender[]; extern const char kTtsVoicesLang[]; +extern const char kTtsVoicesRemote[]; extern const char kTtsVoicesVoiceName[]; extern const char kType[]; extern const char kUpdateURL[]; @@ -398,6 +399,7 @@ extern const char kInvalidTtsVoices[]; extern const char kInvalidTtsVoicesEventTypes[]; extern const char kInvalidTtsVoicesGender[]; extern const char kInvalidTtsVoicesLang[]; +extern const char kInvalidTtsVoicesRemote[]; extern const char kInvalidTtsVoicesVoiceName[]; extern const char kInvalidUpdateURL[]; extern const char kInvalidURLPatternError[]; |