| 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 hotword.debug('New hotword status', status); | 131 hotword.debug('New hotword status', status); |
| 90 this.hotwordStatus_ = status; | 132 this.hotwordStatus_ = status; |
| 91 this.updateStateFromStatus_(); | 133 this.updateStateFromStatus_(); |
| 134 |
| 135 this.onStatusChanged.dispatch(); |
| 92 }, | 136 }, |
| 93 | 137 |
| 94 /** | 138 /** |
| 95 * Updates state based on the current status. | 139 * Updates state based on the current status. |
| 96 * @private | 140 * @private |
| 97 */ | 141 */ |
| 98 updateStateFromStatus_: function() { | 142 updateStateFromStatus_: function() { |
| 99 if (!this.hotwordStatus_) | 143 if (!this.hotwordStatus_) |
| 100 return; | 144 return; |
| 101 | 145 |
| 102 if (this.hotwordStatus_.enabled) { | 146 if (this.hotwordStatus_.enabled) { |
| 103 // Start the detector if there's a session, and shut it down if there | 147 // Start the detector if there's a session, and shut it down if there |
| 104 // isn't. | 148 // 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 | 149 // NOTE(amistry): With always-on, we want a different behaviour with |
| 111 // sessions since the detector should always be running. The exception | 150 // sessions since the detector should always be running. The exception |
| 112 // being when the user triggers by saying 'Ok Google'. In that case, the | 151 // being when the user triggers by saying 'Ok Google'. In that case, the |
| 113 // detector stops, so starting/stopping the launcher session should | 152 // detector stops, so starting/stopping the launcher session should |
| 114 // restart the detector. | 153 // restart the detector. |
| 115 if (this.sessionSource_) | 154 if (this.sessions_.length) |
| 116 this.startDetector_(); | 155 this.startDetector_(); |
| 117 else | 156 else |
| 118 this.shutdownDetector_(); | 157 this.shutdownDetector_(); |
| 119 } else { | 158 } else { |
| 120 // Not enabled. Shut down if running. | 159 // Not enabled. Shut down if running. |
| 121 this.shutdownDetector_(); | 160 this.shutdownDetector_(); |
| 122 } | 161 } |
| 123 }, | 162 }, |
| 124 | 163 |
| 125 /** | 164 /** |
| (...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 | 212 * Start the recognizer plugin. Assumes the plugin has been loaded and is |
| 174 * ready to start. | 213 * ready to start. |
| 175 * @private | 214 * @private |
| 176 */ | 215 */ |
| 177 startRecognizer_: function() { | 216 startRecognizer_: function() { |
| 178 assert(this.pluginManager_, 'No NaCl plugin loaded'); | 217 assert(this.pluginManager_, 'No NaCl plugin loaded'); |
| 179 if (this.state_ != State_.RUNNING) { | 218 if (this.state_ != State_.RUNNING) { |
| 180 this.state_ = State_.RUNNING; | 219 this.state_ = State_.RUNNING; |
| 181 this.pluginManager_.startRecognizer(); | 220 this.pluginManager_.startRecognizer(); |
| 182 } | 221 } |
| 183 if (this.sessionStartedCb_) { | 222 for (var i = 0; i < this.sessions_.length; i++) { |
| 184 this.sessionStartedCb_(); | 223 var session = this.sessions_[i]; |
| 185 this.sessionStartedCb_ = null; | 224 if (session.startedCb) { |
| 225 session.startedCb(); |
| 226 session.startedCb = null; |
| 227 } |
| 186 } | 228 } |
| 187 }, | 229 }, |
| 188 | 230 |
| 189 /** | 231 /** |
| 190 * Shuts down and removes the plugin manager, if it exists. | 232 * Shuts down and removes the plugin manager, if it exists. |
| 191 * @private | 233 * @private |
| 192 */ | 234 */ |
| 193 shutdownPluginManager_: function() { | 235 shutdownPluginManager_: function() { |
| 194 if (this.pluginManager_) { | 236 if (this.pluginManager_) { |
| 195 this.pluginManager_.shutdown(); | 237 this.pluginManager_.shutdown(); |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 236 */ | 278 */ |
| 237 onTrigger_: function() { | 279 onTrigger_: function() { |
| 238 hotword.debug('Hotword triggered!'); | 280 hotword.debug('Hotword triggered!'); |
| 239 assert(this.pluginManager_, 'No NaCl plugin loaded on trigger'); | 281 assert(this.pluginManager_, 'No NaCl plugin loaded on trigger'); |
| 240 // Detector implicitly stops when the hotword is detected. | 282 // Detector implicitly stops when the hotword is detected. |
| 241 this.state_ = State_.STOPPED; | 283 this.state_ = State_.STOPPED; |
| 242 | 284 |
| 243 // Play the chime. | 285 // Play the chime. |
| 244 this.chime_.play(); | 286 this.chime_.play(); |
| 245 | 287 |
| 246 chrome.hotwordPrivate.notifyHotwordRecognition('search', function() {}); | 288 // Implicitly clear the top session. A session needs to be started in |
| 247 | 289 // order to restart the detector. |
| 248 // Implicitly clear the session. A session needs to be started in order to | 290 if (this.sessions_.length) { |
| 249 // restart the detector. | 291 var session = this.sessions_.pop(); |
| 250 this.sessionSource_ = null; | 292 if (session.triggerCb) |
| 251 this.sessionStartedCb_ = null; | 293 session.triggerCb(); |
| 294 } |
| 252 }, | 295 }, |
| 253 | 296 |
| 254 /** | 297 /** |
| 298 * Remove a hotwording session from the given source. |
| 299 * @param {!hotword.constants.SessionSource} source Source of the hotword |
| 300 * session request. |
| 301 * @private |
| 302 */ |
| 303 removeSession_: function(source) { |
| 304 for (var i = 0; i < this.sessions_.length; i++) { |
| 305 if (this.sessions_[i].source == source) { |
| 306 this.sessions_.splice(i, 1); |
| 307 break; |
| 308 } |
| 309 } |
| 310 }, |
| 311 |
| 312 /** |
| 255 * Start a hotwording session. | 313 * Start a hotwording session. |
| 256 * @param {!hotword.constants.SessionSource} source Source of the hotword | 314 * @param {!hotword.constants.SessionSource} source Source of the hotword |
| 257 * session request. | 315 * session request. |
| 258 * @param {!function()} startedCb Callback invoked when the session has | 316 * @param {!function()} startedCb Callback invoked when the session has |
| 259 * been started successfully. | 317 * been started successfully. |
| 318 * @param {!function()} triggerCb Callback invoked when the hotword has |
| 319 * triggered. |
| 260 */ | 320 */ |
| 261 startSession: function(source, startedCb) { | 321 startSession: function(source, startedCb, triggerCb) { |
| 262 hotword.debug('Starting session for source: ' + source); | 322 hotword.debug('Starting session for source: ' + source); |
| 263 this.sessionSource_ = source; | 323 this.removeSession_(source); |
| 264 this.sessionStartedCb_ = startedCb; | 324 this.sessions_.push(new Session_(source, triggerCb, startedCb)); |
| 265 this.updateStateFromStatus_(); | 325 this.updateStateFromStatus_(); |
| 266 }, | 326 }, |
| 267 | 327 |
| 268 /** | 328 /** |
| 269 * Stops a hotwording session. | 329 * Stops a hotwording session. |
| 270 * @param {!hotword.constants.SessionSource} source Source of the hotword | 330 * @param {!hotword.constants.SessionSource} source Source of the hotword |
| 271 * session request. | 331 * session request. |
| 272 */ | 332 */ |
| 273 stopSession: function(source) { | 333 stopSession: function(source) { |
| 274 hotword.debug('Stopping session for source: ' + source); | 334 hotword.debug('Stopping session for source: ' + source); |
| 275 this.sessionSource_ = null; | 335 this.removeSession_(source); |
| 276 this.sessionStartedCb_ = null; | |
| 277 this.updateStateFromStatus_(); | 336 this.updateStateFromStatus_(); |
| 278 } | 337 } |
| 279 }; | 338 }; |
| 280 | 339 |
| 281 return { | 340 return { |
| 282 StateManager: StateManager | 341 StateManager: StateManager |
| 283 }; | 342 }; |
| 284 }); | 343 }); |
| OLD | NEW |