| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 * @fileoverview The class to Manage both offline / online speech recognition. | |
| 7 */ | |
| 8 | |
| 9 <include src="plugin_manager.js"> | |
| 10 <include src="audio_manager.js"> | |
| 11 <include src="speech_recognition_manager.js"> | |
| 12 | |
| 13 cr.define('speech', function() { | |
| 14 'use strict'; | |
| 15 | |
| 16 /** | |
| 17 * The state of speech recognition. | |
| 18 * | |
| 19 * @enum {string} | |
| 20 */ | |
| 21 var SpeechState = { | |
| 22 READY: 'READY', | |
| 23 HOTWORD_RECOGNIZING: 'HOTWORD_RECOGNIZING', | |
| 24 RECOGNIZING: 'RECOGNIZING', | |
| 25 IN_SPEECH: 'IN_SPEECH', | |
| 26 STOPPING: 'STOPPING', | |
| 27 NETWORK_ERROR: 'NETWORK_ERROR' | |
| 28 }; | |
| 29 | |
| 30 /** | |
| 31 * The time to show the network error message in seconds. | |
| 32 * | |
| 33 * @const {number} | |
| 34 */ | |
| 35 var SPEECH_ERROR_TIMEOUT = 3; | |
| 36 | |
| 37 /** | |
| 38 * Checks the prefix for the hotword module based on the language. This is | |
| 39 * fragile if the file structure has changed. | |
| 40 */ | |
| 41 function getHotwordPrefix() { | |
| 42 var prefix = navigator.language.toLowerCase(); | |
| 43 if (prefix == 'en-gb') | |
| 44 return prefix; | |
| 45 var hyphen = prefix.indexOf('-'); | |
| 46 if (hyphen >= 0) | |
| 47 prefix = prefix.substr(0, hyphen); | |
| 48 if (prefix == 'en') | |
| 49 prefix = ''; | |
| 50 return prefix; | |
| 51 } | |
| 52 | |
| 53 /** | |
| 54 * @constructor | |
| 55 */ | |
| 56 function SpeechManager() { | |
| 57 this.audioManager_ = new speech.AudioManager(); | |
| 58 this.audioManager_.addEventListener('audio', this.onAudioLevel_.bind(this)); | |
| 59 this.speechRecognitionManager_ = new speech.SpeechRecognitionManager(this); | |
| 60 this.errorTimeoutId_ = null; | |
| 61 } | |
| 62 | |
| 63 /** | |
| 64 * Updates the state. | |
| 65 * | |
| 66 * @param {SpeechState} newState The new state. | |
| 67 * @private | |
| 68 */ | |
| 69 SpeechManager.prototype.setState_ = function(newState) { | |
| 70 if (this.state == newState) | |
| 71 return; | |
| 72 | |
| 73 this.state = newState; | |
| 74 chrome.send('setSpeechRecognitionState', [this.state]); | |
| 75 }; | |
| 76 | |
| 77 /** | |
| 78 * Called with the mean audio level when audio data arrives. | |
| 79 * | |
| 80 * @param {cr.event.Event} event The event object for the audio data. | |
| 81 * @private | |
| 82 */ | |
| 83 SpeechManager.prototype.onAudioLevel_ = function(event) { | |
| 84 var data = event.data; | |
| 85 var level = 0; | |
| 86 for (var i = 0; i < data.length; ++i) | |
| 87 level += Math.abs(data[i]); | |
| 88 level /= data.length; | |
| 89 chrome.send('speechSoundLevel', [level]); | |
| 90 }; | |
| 91 | |
| 92 /** | |
| 93 * Called when the hotword recognizer is ready. | |
| 94 * | |
| 95 * @param {PluginManager} pluginManager The hotword plugin manager which gets | |
| 96 * ready. | |
| 97 * @private | |
| 98 */ | |
| 99 SpeechManager.prototype.onHotwordRecognizerReady_ = function(pluginManager) { | |
| 100 this.pluginManager_ = pluginManager; | |
| 101 this.audioManager_.addEventListener( | |
| 102 'audio', pluginManager.sendAudioData.bind(pluginManager)); | |
| 103 this.pluginManager_.startRecognizer(); | |
| 104 this.audioManager_.start(); | |
| 105 this.setState_(SpeechState.HOTWORD_RECOGNIZING); | |
| 106 }; | |
| 107 | |
| 108 /** | |
| 109 * Called when an error happens for loading the hotword recognizer. | |
| 110 * | |
| 111 * @private | |
| 112 */ | |
| 113 SpeechManager.prototype.onHotwordRecognizerLoadError_ = function() { | |
| 114 this.setHotwordEnabled(false); | |
| 115 this.setState_(SpeechState.READY); | |
| 116 }; | |
| 117 | |
| 118 /** | |
| 119 * Called when the hotword is recognized. | |
| 120 * | |
| 121 * @private | |
| 122 */ | |
| 123 SpeechManager.prototype.onHotwordRecognized_ = function() { | |
| 124 if (this.state != SpeechState.HOTWORD_RECOGNIZING) | |
| 125 return; | |
| 126 this.pluginManager_.stopRecognizer(); | |
| 127 this.speechRecognitionManager_.start(); | |
| 128 }; | |
| 129 | |
| 130 /** | |
| 131 * Called when the speech recognition has happened. | |
| 132 * | |
| 133 * @param {string} result The speech recognition result. | |
| 134 * @param {boolean} isFinal Whether the result is final or not. | |
| 135 */ | |
| 136 SpeechManager.prototype.onSpeechRecognized = function(result, isFinal) { | |
| 137 chrome.send('speechResult', [result, isFinal]); | |
| 138 if (isFinal) | |
| 139 this.speechRecognitionManager_.stop(); | |
| 140 }; | |
| 141 | |
| 142 /** | |
| 143 * Called when the speech recognition has started. | |
| 144 */ | |
| 145 SpeechManager.prototype.onSpeechRecognitionStarted = function() { | |
| 146 this.setState_(SpeechState.RECOGNIZING); | |
| 147 }; | |
| 148 | |
| 149 /** | |
| 150 * Called when the speech recognition has ended. | |
| 151 */ | |
| 152 SpeechManager.prototype.onSpeechRecognitionEnded = function() { | |
| 153 // Do not handle the speech recognition ends if it ends due to an error | |
| 154 // because an error message should be shown for a while. | |
| 155 // See onSpeechRecognitionError. | |
| 156 if (this.state == SpeechState.NETWORK_ERROR) | |
| 157 return; | |
| 158 | |
| 159 // Restarts the hotword recognition. | |
| 160 if (this.state != SpeechState.STOPPING && this.pluginManager_) { | |
| 161 this.pluginManager_.startRecognizer(); | |
| 162 this.audioManager_.start(); | |
| 163 this.setState_(SpeechState.HOTWORD_RECOGNIZING); | |
| 164 } else { | |
| 165 this.audioManager_.stop(); | |
| 166 this.setState_(SpeechState.READY); | |
| 167 } | |
| 168 }; | |
| 169 | |
| 170 /** | |
| 171 * Called when a speech has started. | |
| 172 */ | |
| 173 SpeechManager.prototype.onSpeechStarted = function() { | |
| 174 if (this.state == SpeechState.RECOGNIZING) | |
| 175 this.setState_(SpeechState.IN_SPEECH); | |
| 176 }; | |
| 177 | |
| 178 /** | |
| 179 * Called when a speech has ended. | |
| 180 */ | |
| 181 SpeechManager.prototype.onSpeechEnded = function() { | |
| 182 if (this.state == SpeechState.IN_SPEECH) | |
| 183 this.setState_(SpeechState.RECOGNIZING); | |
| 184 }; | |
| 185 | |
| 186 /** | |
| 187 * Called when the speech manager should recover from the error state. | |
| 188 * | |
| 189 * @private | |
| 190 */ | |
| 191 SpeechManager.prototype.onSpeechRecognitionErrorTimeout_ = function() { | |
| 192 this.errorTimeoutId_ = null; | |
| 193 this.setState_(SpeechState.READY); | |
| 194 this.onSpeechRecognitionEnded(); | |
| 195 }; | |
| 196 | |
| 197 /** | |
| 198 * Called when an error happened during the speech recognition. | |
| 199 * | |
| 200 * @param {SpeechRecognitionError} e The error object. | |
| 201 */ | |
| 202 SpeechManager.prototype.onSpeechRecognitionError = function(e) { | |
| 203 if (e.error == 'network') { | |
| 204 this.setState_(SpeechState.NETWORK_ERROR); | |
| 205 this.errorTimeoutId_ = window.setTimeout( | |
| 206 this.onSpeechRecognitionErrorTimeout_.bind(this), | |
| 207 SPEECH_ERROR_TIMEOUT * 1000); | |
| 208 } else { | |
| 209 if (this.state != SpeechState.STOPPING) | |
| 210 this.setState_(SpeechState.READY); | |
| 211 } | |
| 212 }; | |
| 213 | |
| 214 /** | |
| 215 * Changes the availability of the hotword plugin. | |
| 216 * | |
| 217 * @param {boolean} enabled Whether enabled or not. | |
| 218 */ | |
| 219 SpeechManager.prototype.setHotwordEnabled = function(enabled) { | |
| 220 var recognizer = $('recognizer'); | |
| 221 if (enabled) { | |
| 222 if (recognizer) | |
| 223 return; | |
| 224 if (!this.naclArch) | |
| 225 return; | |
| 226 | |
| 227 var prefix = getHotwordPrefix(); | |
| 228 var pluginManager = new speech.PluginManager( | |
| 229 prefix, | |
| 230 this.onHotwordRecognizerReady_.bind(this), | |
| 231 this.onHotwordRecognized_.bind(this), | |
| 232 this.onHotwordRecognizerLoadError_.bind(this)); | |
| 233 var modelUrl = 'chrome://app-list/_platform_specific/' + this.naclArch + | |
| 234 '_' + prefix + '/hotword.data'; | |
| 235 pluginManager.scheduleInitialize(this.audioManager_.sampleRate, modelUrl); | |
| 236 } else { | |
| 237 if (!recognizer) | |
| 238 return; | |
| 239 document.body.removeChild(recognizer); | |
| 240 this.pluginManager_ = null; | |
| 241 if (this.state == SpeechState.HOTWORD_RECOGNIZING) { | |
| 242 this.audioManager_.stop(); | |
| 243 this.setState_(SpeechState.READY); | |
| 244 } | |
| 245 } | |
| 246 }; | |
| 247 | |
| 248 /** | |
| 249 * Sets the NaCl architecture for the hotword module. | |
| 250 * | |
| 251 * @param {string} arch The architecture. | |
| 252 */ | |
| 253 SpeechManager.prototype.setNaclArch = function(arch) { | |
| 254 this.naclArch = arch; | |
| 255 }; | |
| 256 | |
| 257 /** | |
| 258 * Called when the app-list bubble is shown. | |
| 259 * | |
| 260 * @param {boolean} hotwordEnabled Whether the hotword is enabled or not. | |
| 261 */ | |
| 262 SpeechManager.prototype.onShown = function(hotwordEnabled) { | |
| 263 this.setHotwordEnabled(hotwordEnabled); | |
| 264 | |
| 265 // No one sets the state if the content is initialized on shown but hotword | |
| 266 // is not enabled. Sets the state in such case. | |
| 267 if (!this.state && !hotwordEnabled) | |
| 268 this.setState_(SpeechState.READY); | |
| 269 }; | |
| 270 | |
| 271 /** | |
| 272 * Called when the app-list bubble is hidden. | |
| 273 */ | |
| 274 SpeechManager.prototype.onHidden = function() { | |
| 275 this.setHotwordEnabled(false); | |
| 276 | |
| 277 // SpeechRecognition is asynchronous. | |
| 278 this.audioManager_.stop(); | |
| 279 if (this.state == SpeechState.RECOGNIZING || | |
| 280 this.state == SpeechState.IN_SPEECH) { | |
| 281 this.setState_(SpeechState.STOPPING); | |
| 282 this.speechRecognitionManager_.stop(); | |
| 283 } else { | |
| 284 this.setState_(SpeechState.READY); | |
| 285 } | |
| 286 }; | |
| 287 | |
| 288 /** | |
| 289 * Toggles the current state of speech recognition. | |
| 290 */ | |
| 291 SpeechManager.prototype.toggleSpeechRecognition = function() { | |
| 292 if (this.state == SpeechState.NETWORK_ERROR) { | |
| 293 if (this.errorTimeoutId_) | |
| 294 window.clearTimeout(this.errorTimeoutId_); | |
| 295 this.onSpeechRecognitionErrorTimeout_(); | |
| 296 } else if (this.state == SpeechState.RECOGNIZING || | |
| 297 this.state == SpeechState.IN_SPEECH) { | |
| 298 this.audioManager_.stop(); | |
| 299 this.speechRecognitionManager_.stop(); | |
| 300 } else { | |
| 301 if (this.pluginManager_) | |
| 302 this.pluginManager_.stopRecognizer(); | |
| 303 if (this.audioManager_.state == speech.AudioState.STOPPED) | |
| 304 this.audioManager_.start(); | |
| 305 this.speechRecognitionManager_.start(); | |
| 306 } | |
| 307 }; | |
| 308 | |
| 309 return { | |
| 310 SpeechManager: SpeechManager | |
| 311 }; | |
| 312 }); | |
| OLD | NEW |