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