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.
6 * @fileoverview The class to Manage both offline / online speech recognition.
9 <include src="plugin_manager.js">
10 <include src="audio_manager.js">
11 <include src="speech_recognition_manager.js">
13 cr.define('speech', function() {
17 * The state of speech recognition.
23 HOTWORD_RECOGNIZING: 'HOTWORD_RECOGNIZING',
24 RECOGNIZING: 'RECOGNIZING',
25 IN_SPEECH: 'IN_SPEECH',
27 NETWORK_ERROR: 'NETWORK_ERROR'
31 * The time to show the network error message in seconds.
35 var SPEECH_ERROR_TIMEOUT = 3;
38 * Checks the prefix for the hotword module based on the language. This is
39 * fragile if the file structure has changed.
41 function getHotwordPrefix() {
42 var prefix = navigator.language.toLowerCase();
43 if (prefix == 'en-gb')
45 var hyphen = prefix.indexOf('-');
47 prefix = prefix.substr(0, hyphen);
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;
66 * @param {SpeechState} newState The new state.
69 SpeechManager.prototype.setState_ = function(newState) {
70 if (this.state == newState)
73 this.state = newState;
74 chrome.send('setSpeechRecognitionState', [this.state]);
78 * Called with the mean audio level when audio data arrives.
80 * @param {cr.event.Event} event The event object for the audio data.
83 SpeechManager.prototype.onAudioLevel_ = function(event) {
84 var data = event.data;
86 for (var i = 0; i < data.length; ++i)
87 level += Math.abs(data[i]);
89 chrome.send('speechSoundLevel', [level]);
93 * Called when the hotword recognizer is ready.
95 * @param {PluginManager} pluginManager The hotword plugin manager which gets
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);
109 * Called when an error happens for loading the hotword recognizer.
113 SpeechManager.prototype.onHotwordRecognizerLoadError_ = function() {
114 this.setHotwordEnabled(false);
115 this.setState_(SpeechState.READY);
119 * Called when the hotword is recognized.
123 SpeechManager.prototype.onHotwordRecognized_ = function() {
124 if (this.state != SpeechState.HOTWORD_RECOGNIZING)
126 this.pluginManager_.stopRecognizer();
127 this.speechRecognitionManager_.start();
131 * Called when the speech recognition has happened.
133 * @param {string} result The speech recognition result.
134 * @param {boolean} isFinal Whether the result is final or not.
136 SpeechManager.prototype.onSpeechRecognized = function(result, isFinal) {
137 chrome.send('speechResult', [result, isFinal]);
139 this.speechRecognitionManager_.stop();
143 * Called when the speech recognition has started.
145 SpeechManager.prototype.onSpeechRecognitionStarted = function() {
146 this.setState_(SpeechState.RECOGNIZING);
150 * Called when the speech recognition has ended.
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)
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);
165 this.audioManager_.stop();
166 this.setState_(SpeechState.READY);
171 * Called when a speech has started.
173 SpeechManager.prototype.onSpeechStarted = function() {
174 if (this.state == SpeechState.RECOGNIZING)
175 this.setState_(SpeechState.IN_SPEECH);
179 * Called when a speech has ended.
181 SpeechManager.prototype.onSpeechEnded = function() {
182 if (this.state == SpeechState.IN_SPEECH)
183 this.setState_(SpeechState.RECOGNIZING);
187 * Called when the speech manager should recover from the error state.
191 SpeechManager.prototype.onSpeechRecognitionErrorTimeout_ = function() {
192 this.errorTimeoutId_ = null;
193 this.setState_(SpeechState.READY);
194 this.onSpeechRecognitionEnded();
198 * Called when an error happened during the speech recognition.
200 * @param {SpeechRecognitionError} e The error object.
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);
209 if (this.state != SpeechState.STOPPING)
210 this.setState_(SpeechState.READY);
215 * Changes the availability of the hotword plugin.
217 * @param {boolean} enabled Whether enabled or not.
219 SpeechManager.prototype.setHotwordEnabled = function(enabled) {
220 var recognizer = $('recognizer');
227 var prefix = getHotwordPrefix();
228 var pluginManager = new speech.PluginManager(
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);
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);
249 * Sets the NaCl architecture for the hotword module.
251 * @param {string} arch The architecture.
253 SpeechManager.prototype.setNaclArch = function(arch) {
254 this.naclArch = arch;
258 * Called when the app-list bubble is shown.
260 * @param {boolean} hotwordEnabled Whether the hotword is enabled or not.
262 SpeechManager.prototype.onShown = function(hotwordEnabled) {
263 this.setHotwordEnabled(hotwordEnabled);
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);
272 * Called when the app-list bubble is hidden.
274 SpeechManager.prototype.onHidden = function() {
275 this.setHotwordEnabled(false);
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();
284 this.setState_(SpeechState.READY);
289 * Toggles the current state of speech recognition.
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();
301 if (this.pluginManager_)
302 this.pluginManager_.stopRecognizer();
303 if (this.audioManager_.state == speech.AudioState.STOPPED)
304 this.audioManager_.start();
305 this.speechRecognitionManager_.start();
310 SpeechManager: SpeechManager