OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 cr.define('hotword', function() { | 5 cr.define('hotword', function() { |
6 'use strict'; | 6 'use strict'; |
7 | 7 |
8 /** | 8 /** |
| 9 * Trivial container class for session information. |
| 10 * @param {!hotword.constants.SessionSource} source Source of the hotword |
| 11 * session. |
| 12 * @param {!function()} triggerCb Callback invoked when the hotword has |
| 13 * triggered. |
| 14 * @param {!function()} startedCb Callback invoked when the session has |
| 15 * been started successfully. |
| 16 * @constructor |
| 17 * @struct |
| 18 * @private |
| 19 */ |
| 20 function Session_(source, triggerCb, startedCb) { |
| 21 /** |
| 22 * Source of the hotword session request. |
| 23 * @private {!hotword.constants.SessionSource} |
| 24 */ |
| 25 this.source_ = source; |
| 26 |
| 27 /** |
| 28 * Callback invoked when the hotword has triggered. |
| 29 * @private {!function()} |
| 30 */ |
| 31 this.triggerCb_ = triggerCb; |
| 32 |
| 33 /** |
| 34 * Callback invoked when the session has been started successfully. |
| 35 * @private {?function()} |
| 36 */ |
| 37 this.startedCb_ = startedCb; |
| 38 } |
| 39 |
| 40 /** |
9 * Class to manage hotwording state. Starts/stops the hotword detector based | 41 * Class to manage hotwording state. Starts/stops the hotword detector based |
10 * on user settings, session requests, and any other factors that play into | 42 * on user settings, session requests, and any other factors that play into |
11 * whether or not hotwording should be running. | 43 * whether or not hotwording should be running. |
12 * @constructor | 44 * @constructor |
13 * @struct | 45 * @struct |
14 */ | 46 */ |
15 function StateManager() { | 47 function StateManager() { |
16 /** | 48 /** |
17 * Current state. | 49 * Current state. |
18 * @private {hotword.StateManager.State_} | 50 * @private {hotword.StateManager.State_} |
19 */ | 51 */ |
20 this.state_ = State_.STOPPED; | 52 this.state_ = State_.STOPPED; |
21 | 53 |
22 /** | 54 /** |
23 * Current hotwording status. | 55 * Current hotwording status. |
24 * @private {?chrome.hotwordPrivate.StatusDetails} | 56 * @private {?chrome.hotwordPrivate.StatusDetails} |
25 */ | 57 */ |
26 this.hotwordStatus_ = null; | 58 this.hotwordStatus_ = null; |
27 | 59 |
28 /** | 60 /** |
29 * NaCl plugin manager. | 61 * NaCl plugin manager. |
30 * @private {?hotword.NaClManager} | 62 * @private {?hotword.NaClManager} |
31 */ | 63 */ |
32 this.pluginManager_ = null; | 64 this.pluginManager_ = null; |
33 | 65 |
34 /** | 66 /** |
35 * Source of the current hotword session. | 67 * Currently active hotwording sessions. |
36 * @private {?hotword.constants.SessionSource} | 68 * @private {!Array.<hotword.Session_>} |
37 */ | 69 */ |
38 this.sessionSource_ = null; | 70 this.sessions_ = []; |
39 | 71 |
40 /** | 72 /** |
41 * Callback to run when the hotword detector has successfully started. | 73 * Event that fires when the hotwording status has changed. |
42 * @private {!function()} | 74 * @type {!chrome.Event} |
43 */ | 75 */ |
44 this.sessionStartedCb_ = null; | 76 this.onStatusChanged = new chrome.Event(); |
45 | 77 |
46 /** | 78 /** |
47 * Hotword trigger audio notification... a.k.a The Chime (tm). | 79 * Hotword trigger audio notification... a.k.a The Chime (tm). |
48 * @private {!Audio} | 80 * @private {!Audio} |
49 */ | 81 */ |
50 this.chime_ = document.createElement('audio'); | 82 this.chime_ = document.createElement('audio'); |
51 | 83 |
52 // Get the initial status. | 84 // Get the initial status. |
53 chrome.hotwordPrivate.getStatus(this.handleStatus_.bind(this)); | 85 chrome.hotwordPrivate.getStatus(this.handleStatus_.bind(this)); |
54 | 86 |
(...skipping 18 matching lines...) Expand all Loading... |
73 StateManager.prototype = { | 105 StateManager.prototype = { |
74 /** | 106 /** |
75 * Request status details update. Intended to be called from the | 107 * Request status details update. Intended to be called from the |
76 * hotwordPrivate.onEnabledChanged() event. | 108 * hotwordPrivate.onEnabledChanged() event. |
77 */ | 109 */ |
78 updateStatus: function() { | 110 updateStatus: function() { |
79 chrome.hotwordPrivate.getStatus(this.handleStatus_.bind(this)); | 111 chrome.hotwordPrivate.getStatus(this.handleStatus_.bind(this)); |
80 }, | 112 }, |
81 | 113 |
82 /** | 114 /** |
| 115 * @return {boolean} True if hotwording is enabled. |
| 116 */ |
| 117 isEnabled: function() { |
| 118 assert(this.hotwordStatus_); |
| 119 return this.hotwordStatus_.enabled; |
| 120 }, |
| 121 |
| 122 /** |
| 123 * @return {boolean} True if always-on hotwording is enabled. |
| 124 */ |
| 125 isAlwaysOnEnabled: function() { |
| 126 assert(this.hotwordStatus_); |
| 127 return this.hotwordStatus_.enabled && |
| 128 this.hotwordStatus_.alwaysOnEnabled; |
| 129 }, |
| 130 |
| 131 /** |
83 * Callback for hotwordPrivate.getStatus() function. | 132 * Callback for hotwordPrivate.getStatus() function. |
84 * @param {chrome.hotwordPrivate.StatusDetails} status Current hotword | 133 * @param {chrome.hotwordPrivate.StatusDetails} status Current hotword |
85 * status. | 134 * status. |
86 * @private | 135 * @private |
87 */ | 136 */ |
88 handleStatus_: function(status) { | 137 handleStatus_: function(status) { |
89 hotword.debug('New hotword status', status); | 138 hotword.debug('New hotword status', status); |
90 this.hotwordStatus_ = status; | 139 this.hotwordStatus_ = status; |
91 this.updateStateFromStatus_(); | 140 this.updateStateFromStatus_(); |
| 141 |
| 142 this.onStatusChanged.dispatch(); |
92 }, | 143 }, |
93 | 144 |
94 /** | 145 /** |
95 * Updates state based on the current status. | 146 * Updates state based on the current status. |
96 * @private | 147 * @private |
97 */ | 148 */ |
98 updateStateFromStatus_: function() { | 149 updateStateFromStatus_: function() { |
99 if (!this.hotwordStatus_) | 150 if (!this.hotwordStatus_) |
100 return; | 151 return; |
101 | 152 |
102 if (this.hotwordStatus_.enabled) { | 153 if (this.hotwordStatus_.enabled) { |
103 // Start the detector if there's a session, and shut it down if there | 154 // Start the detector if there's a session, and shut it down if there |
104 // isn't. | 155 // isn't. |
105 // TODO(amistry): Support stacking sessions. This can happen when the | |
106 // user opens google.com or the NTP, then opens the launcher. Opening | |
107 // google.com will create one session, and opening the launcher will | |
108 // create the second. Closing the launcher should re-activate the | |
109 // google.com session. | |
110 // NOTE(amistry): With always-on, we want a different behaviour with | 156 // NOTE(amistry): With always-on, we want a different behaviour with |
111 // sessions since the detector should always be running. The exception | 157 // sessions since the detector should always be running. The exception |
112 // being when the user triggers by saying 'Ok Google'. In that case, the | 158 // being when the user triggers by saying 'Ok Google'. In that case, the |
113 // detector stops, so starting/stopping the launcher session should | 159 // detector stops, so starting/stopping the launcher session should |
114 // restart the detector. | 160 // restart the detector. |
115 if (this.sessionSource_) | 161 if (this.sessions_.length) |
116 this.startDetector_(); | 162 this.startDetector_(); |
117 else | 163 else |
118 this.shutdownDetector_(); | 164 this.shutdownDetector_(); |
119 } else { | 165 } else { |
120 // Not enabled. Shut down if running. | 166 // Not enabled. Shut down if running. |
121 this.shutdownDetector_(); | 167 this.shutdownDetector_(); |
122 } | 168 } |
123 }, | 169 }, |
124 | 170 |
125 /** | 171 /** |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
173 * Start the recognizer plugin. Assumes the plugin has been loaded and is | 219 * Start the recognizer plugin. Assumes the plugin has been loaded and is |
174 * ready to start. | 220 * ready to start. |
175 * @private | 221 * @private |
176 */ | 222 */ |
177 startRecognizer_: function() { | 223 startRecognizer_: function() { |
178 assert(this.pluginManager_, 'No NaCl plugin loaded'); | 224 assert(this.pluginManager_, 'No NaCl plugin loaded'); |
179 if (this.state_ != State_.RUNNING) { | 225 if (this.state_ != State_.RUNNING) { |
180 this.state_ = State_.RUNNING; | 226 this.state_ = State_.RUNNING; |
181 this.pluginManager_.startRecognizer(); | 227 this.pluginManager_.startRecognizer(); |
182 } | 228 } |
183 if (this.sessionStartedCb_) { | 229 for (var i = 0; i < this.sessions_.length; i++) { |
184 this.sessionStartedCb_(); | 230 var session = this.sessions_[i]; |
185 this.sessionStartedCb_ = null; | 231 if (session.startedCb_) { |
| 232 session.startedCb_(); |
| 233 session.startedCb_ = null; |
| 234 } |
186 } | 235 } |
187 }, | 236 }, |
188 | 237 |
189 /** | 238 /** |
190 * Shuts down and removes the plugin manager, if it exists. | 239 * Shuts down and removes the plugin manager, if it exists. |
191 * @private | 240 * @private |
192 */ | 241 */ |
193 shutdownPluginManager_: function() { | 242 shutdownPluginManager_: function() { |
194 if (this.pluginManager_) { | 243 if (this.pluginManager_) { |
195 this.pluginManager_.shutdown(); | 244 this.pluginManager_.shutdown(); |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
236 */ | 285 */ |
237 onTrigger_: function() { | 286 onTrigger_: function() { |
238 hotword.debug('Hotword triggered!'); | 287 hotword.debug('Hotword triggered!'); |
239 assert(this.pluginManager_, 'No NaCl plugin loaded on trigger'); | 288 assert(this.pluginManager_, 'No NaCl plugin loaded on trigger'); |
240 // Detector implicitly stops when the hotword is detected. | 289 // Detector implicitly stops when the hotword is detected. |
241 this.state_ = State_.STOPPED; | 290 this.state_ = State_.STOPPED; |
242 | 291 |
243 // Play the chime. | 292 // Play the chime. |
244 this.chime_.play(); | 293 this.chime_.play(); |
245 | 294 |
246 chrome.hotwordPrivate.notifyHotwordRecognition('search', function() {}); | 295 // Implicitly clear the top session. A session needs to be started in |
247 | 296 // order to restart the detector. |
248 // Implicitly clear the session. A session needs to be started in order to | 297 if (this.sessions_.length) { |
249 // restart the detector. | 298 var session = this.sessions_.pop(); |
250 this.sessionSource_ = null; | 299 if (session.triggerCb_) |
251 this.sessionStartedCb_ = null; | 300 session.triggerCb_(); |
| 301 } |
252 }, | 302 }, |
253 | 303 |
254 /** | 304 /** |
| 305 * Remove a hotwording session from the given source. |
| 306 * @param {!hotword.constants.SessionSource} source Source of the hotword |
| 307 * session request. |
| 308 * @private |
| 309 */ |
| 310 removeSession_: function(source) { |
| 311 for (var i = 0; i < this.sessions_.length; i++) { |
| 312 if (this.sessions_[i].source_ == source) { |
| 313 this.sessions_.splice(i, 1); |
| 314 break; |
| 315 } |
| 316 } |
| 317 }, |
| 318 |
| 319 /** |
255 * Start a hotwording session. | 320 * Start a hotwording session. |
256 * @param {!hotword.constants.SessionSource} source Source of the hotword | 321 * @param {!hotword.constants.SessionSource} source Source of the hotword |
257 * session request. | 322 * session request. |
258 * @param {!function()} startedCb Callback invoked when the session has | 323 * @param {!function()} startedCb Callback invoked when the session has |
259 * been started successfully. | 324 * been started successfully. |
| 325 * @param {!function()} triggerCb Callback invoked when the hotword has |
| 326 * triggered. |
260 */ | 327 */ |
261 startSession: function(source, startedCb) { | 328 startSession: function(source, startedCb, triggerCb) { |
262 hotword.debug('Starting session for source: ' + source); | 329 hotword.debug('Starting session for source: ' + source); |
263 this.sessionSource_ = source; | 330 this.removeSession_(source); |
264 this.sessionStartedCb_ = startedCb; | 331 this.sessions_.push(new Session_(source, triggerCb, startedCb)); |
265 this.updateStateFromStatus_(); | 332 this.updateStateFromStatus_(); |
266 }, | 333 }, |
267 | 334 |
268 /** | 335 /** |
269 * Stops a hotwording session. | 336 * Stops a hotwording session. |
270 * @param {!hotword.constants.SessionSource} source Source of the hotword | 337 * @param {!hotword.constants.SessionSource} source Source of the hotword |
271 * session request. | 338 * session request. |
272 */ | 339 */ |
273 stopSession: function(source) { | 340 stopSession: function(source) { |
274 hotword.debug('Stopping session for source: ' + source); | 341 hotword.debug('Stopping session for source: ' + source); |
275 this.sessionSource_ = null; | 342 this.removeSession_(source); |
276 this.sessionStartedCb_ = null; | |
277 this.updateStateFromStatus_(); | 343 this.updateStateFromStatus_(); |
278 } | 344 } |
279 }; | 345 }; |
280 | 346 |
281 return { | 347 return { |
282 StateManager: StateManager | 348 StateManager: StateManager |
283 }; | 349 }; |
284 }); | 350 }); |
OLD | NEW |