| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * @fileoverview | 6 * @fileoverview |
| 7 * This is a component extension that implements a text-to-speech (TTS) | 7 * This is a component extension that implements a text-to-speech (TTS) |
| 8 * engine powered by Google's speech synthesis API. | 8 * engine powered by Google's speech synthesis API. |
| 9 * | 9 * |
| 10 * This is an "event page", so it's not loaded when the API isn't being used, | 10 * This is an "event page", so it's not loaded when the API isn't being used, |
| (...skipping 12 matching lines...) Expand all Loading... |
| 23 function TtsExtension() {} | 23 function TtsExtension() {} |
| 24 | 24 |
| 25 TtsExtension.prototype = { | 25 TtsExtension.prototype = { |
| 26 /** | 26 /** |
| 27 * The url prefix of the speech server, including static query | 27 * The url prefix of the speech server, including static query |
| 28 * parameters that don't change. | 28 * parameters that don't change. |
| 29 * @type {string} | 29 * @type {string} |
| 30 * @const | 30 * @const |
| 31 * @private | 31 * @private |
| 32 */ | 32 */ |
| 33 SPEECH_SERVER_URL_: | 33 SPEECH_SERVER_URL_: 'https://www.google.com/speech-api/v2/synthesize?' + |
| 34 'https://www.google.com/speech-api/v2/synthesize?' + | |
| 35 'enc=mpeg&client=chromium', | 34 'enc=mpeg&client=chromium', |
| 36 | 35 |
| 37 /** | 36 /** |
| 38 * A mapping from language and gender to voice name, hardcoded for now | 37 * A mapping from language and gender to voice name, hardcoded for now |
| 39 * until the speech synthesis server capabilities response provides this. | 38 * until the speech synthesis server capabilities response provides this. |
| 40 * The key of this map is of the form '<lang>-<gender>'. | 39 * The key of this map is of the form '<lang>-<gender>'. |
| 41 * @type {Object<string>} | 40 * @type {Object<string>} |
| 42 * @private | 41 * @private |
| 43 */ | 42 */ |
| 44 LANG_AND_GENDER_TO_VOICE_NAME_: { | 43 LANG_AND_GENDER_TO_VOICE_NAME_: { |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 140 lang = navigator.language; | 139 lang = navigator.language; |
| 141 | 140 |
| 142 // Look up the specific voice name for this language and gender. | 141 // Look up the specific voice name for this language and gender. |
| 143 // If it's not in the map, it doesn't matter - the language will | 142 // If it's not in the map, it doesn't matter - the language will |
| 144 // be used directly. This is only used for languages where more | 143 // be used directly. This is only used for languages where more |
| 145 // than one gender is actually available. | 144 // than one gender is actually available. |
| 146 var key = lang.toLowerCase() + '-' + gender; | 145 var key = lang.toLowerCase() + '-' + gender; |
| 147 var voiceName = this.LANG_AND_GENDER_TO_VOICE_NAME_[key]; | 146 var voiceName = this.LANG_AND_GENDER_TO_VOICE_NAME_[key]; |
| 148 | 147 |
| 149 var url = this.SPEECH_SERVER_URL_; | 148 var url = this.SPEECH_SERVER_URL_; |
| 150 chrome.systemPrivate.getApiKey((function(key) { | 149 chrome.systemPrivate.getApiKey( |
| 151 url += '&key=' + key; | 150 (function(key) { |
| 152 url += '&text=' + encodeURIComponent(utterance); | 151 url += '&key=' + key; |
| 153 url += '&lang=' + lang.toLowerCase(); | 152 url += '&text=' + encodeURIComponent(utterance); |
| 153 url += '&lang=' + lang.toLowerCase(); |
| 154 | 154 |
| 155 if (voiceName) | 155 if (voiceName) |
| 156 url += '&name=' + voiceName; | 156 url += '&name=' + voiceName; |
| 157 | 157 |
| 158 if (options.rate) { | 158 if (options.rate) { |
| 159 // Input rate is between 0.1 and 10.0 with a default of 1.0. | 159 // Input rate is between 0.1 and 10.0 with a default of 1.0. |
| 160 // Output speed is between 0.0 and 1.0 with a default of 0.5. | 160 // Output speed is between 0.0 and 1.0 with a default of 0.5. |
| 161 url += '&speed=' + (options.rate / 2.0); | 161 url += '&speed=' + (options.rate / 2.0); |
| 162 } | 162 } |
| 163 | 163 |
| 164 if (options.pitch) { | 164 if (options.pitch) { |
| 165 // Input pitch is between 0.0 and 2.0 with a default of 1.0. | 165 // Input pitch is between 0.0 and 2.0 with a default of 1.0. |
| 166 // Output pitch is between 0.0 and 1.0 with a default of 0.5. | 166 // Output pitch is between 0.0 and 1.0 with a default of 0.5. |
| 167 url += '&pitch=' + (options.pitch / 2.0); | 167 url += '&pitch=' + (options.pitch / 2.0); |
| 168 } | 168 } |
| 169 | 169 |
| 170 // This begins loading the audio but does not play it. | 170 // This begins loading the audio but does not play it. |
| 171 // When enough of the audio has loaded to begin playback, | 171 // When enough of the audio has loaded to begin playback, |
| 172 // the 'canplaythrough' handler will call this.onStart_, | 172 // the 'canplaythrough' handler will call this.onStart_, |
| 173 // which sends a start event to the ttsEngine callback and | 173 // which sends a start event to the ttsEngine callback and |
| 174 // then begins playing audio. | 174 // then begins playing audio. |
| 175 this.audioElement_.src = url; | 175 this.audioElement_.src = url; |
| 176 }).bind(this)); | 176 }).bind(this)); |
| 177 } catch (err) { | 177 } catch (err) { |
| 178 console.error(String(err)); | 178 console.error(String(err)); |
| 179 callback({ | 179 callback({'type': 'error', 'errorMessage': String(err)}); |
| 180 'type': 'error', | |
| 181 'errorMessage': String(err) | |
| 182 }); | |
| 183 this.currentUtterance_ = null; | 180 this.currentUtterance_ = null; |
| 184 } | 181 } |
| 185 }, | 182 }, |
| 186 | 183 |
| 187 /** | 184 /** |
| 188 * Handler for the chrome.ttsEngine.onStop interface. | 185 * Handler for the chrome.ttsEngine.onStop interface. |
| 189 * Called either when the ttsEngine API requests us to stop, or when | 186 * Called either when the ttsEngine API requests us to stop, or when |
| 190 * we reach the end of the audio stream. Pause the audio element to | 187 * we reach the end of the audio stream. Pause the audio element to |
| 191 * silence it, and send a callback to the ttsEngine API to let it know | 188 * silence it, and send a callback to the ttsEngine API to let it know |
| 192 * that we've completed. Note that the ttsEngine API manages callback | 189 * that we've completed. Note that the ttsEngine API manages callback |
| (...skipping 20 matching lines...) Expand all Loading... |
| 213 * then begin playing the audio element. | 210 * then begin playing the audio element. |
| 214 * @private | 211 * @private |
| 215 */ | 212 */ |
| 216 onStart_: function() { | 213 onStart_: function() { |
| 217 if (this.currentUtterance_) { | 214 if (this.currentUtterance_) { |
| 218 if (this.currentUtterance_.options.volume !== undefined) { | 215 if (this.currentUtterance_.options.volume !== undefined) { |
| 219 // Both APIs use the same range for volume, between 0.0 and 1.0. | 216 // Both APIs use the same range for volume, between 0.0 and 1.0. |
| 220 this.audioElement_.volume = this.currentUtterance_.options.volume; | 217 this.audioElement_.volume = this.currentUtterance_.options.volume; |
| 221 } | 218 } |
| 222 this.audioElement_.play(); | 219 this.audioElement_.play(); |
| 223 this.currentUtterance_.callback({ | 220 this.currentUtterance_.callback({'type': 'start', 'charIndex': 0}); |
| 224 'type': 'start', | |
| 225 'charIndex': 0 | |
| 226 }); | |
| 227 } | 221 } |
| 228 }, | 222 }, |
| 229 | 223 |
| 230 /** | 224 /** |
| 231 * Handler for the chrome.ttsEngine.onPause interface. | 225 * Handler for the chrome.ttsEngine.onPause interface. |
| 232 * Pauses audio if we're in the middle of an utterance. | 226 * Pauses audio if we're in the middle of an utterance. |
| 233 * @private | 227 * @private |
| 234 */ | 228 */ |
| 235 onPause_: function() { | 229 onPause_: function() { |
| 236 if (this.currentUtterance_) { | 230 if (this.currentUtterance_) { |
| 237 this.audioElement_.pause(); | 231 this.audioElement_.pause(); |
| 238 } | 232 } |
| 239 }, | 233 }, |
| 240 | 234 |
| 241 /** | 235 /** |
| 242 * Handler for the chrome.ttsEngine.onPause interface. | 236 * Handler for the chrome.ttsEngine.onPause interface. |
| 243 * Resumes audio if we're in the middle of an utterance. | 237 * Resumes audio if we're in the middle of an utterance. |
| 244 * @private | 238 * @private |
| 245 */ | 239 */ |
| 246 onResume_: function() { | 240 onResume_: function() { |
| 247 if (this.currentUtterance_) { | 241 if (this.currentUtterance_) { |
| 248 this.audioElement_.play(); | 242 this.audioElement_.play(); |
| 249 } | 243 } |
| 250 } | 244 } |
| 251 | 245 |
| 252 }; | 246 }; |
| 253 | 247 |
| 254 (new TtsExtension()).init(); | 248 (new TtsExtension()).init(); |
| OLD | NEW |