Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / app_list / speech_manager.js
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 });