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 |