Index: chrome/browser/resources/hotword/state_manager.js |
diff --git a/chrome/browser/resources/hotword/state_manager.js b/chrome/browser/resources/hotword/state_manager.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4b2ae1316614d86cdb14e0b62cfc9bb18e0a2586 |
--- /dev/null |
+++ b/chrome/browser/resources/hotword/state_manager.js |
@@ -0,0 +1,195 @@ |
+// Copyright (c) 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+cr.define('hotword', function() { |
+ 'use strict'; |
+ |
+ /** |
+ * Class to manage hotwording state. Starts/stops the hotword detector based |
+ * on user settings, session requests, and any other factors that play into |
+ * whether or not hotwording should be running. |
+ * @constructor |
+ * @struct |
+ */ |
+ function StateManager() { |
+ /** |
+ * Current state. |
+ * @private {hotword.StateManager.State_} |
+ */ |
+ this.state_ = State_.STOPPED; |
+ |
+ /** |
+ * Current hotwording status. |
+ * @private {?chrome.hotwordPrivate.StatusDetails} |
+ */ |
+ this.hotwordStatus_ = null; |
+ |
+ /** |
+ * NaCl plugin manager. |
+ * @private {?hotword.NaClManager} |
+ */ |
+ this.pluginManager_ = null; |
+ |
+ // Get the initial status. |
+ chrome.hotwordPrivate.getStatus(this.handleStatus_.bind(this)); |
+ } |
+ |
+ /** |
+ * @enum {number} |
+ * @private |
+ */ |
+ StateManager.State_ = { |
+ STOPPED: 0, |
+ STARTING: 1, |
+ RUNNING: 2, |
+ ERROR: 3, |
+ }; |
+ var State_ = StateManager.State_; |
+ |
+ StateManager.prototype = { |
+ /** |
+ * Request status details update. Intended to be called from the |
+ * hotwordPrivate.onEnabledChanged() event. |
+ */ |
+ updateStatus: function() { |
+ chrome.hotwordPrivate.getStatus(this.handleStatus_.bind(this)); |
+ }, |
+ |
+ /** |
+ * Callback for hotwordPrivate.getStatus() function. |
+ * @param {chrome.hotwordPrivate.StatusDetails} status Current hotword |
+ * status. |
+ * @private |
+ */ |
+ handleStatus_: function(status) { |
+ this.hotwordStatus_ = status; |
+ this.updateStateFromStatus_(); |
+ }, |
+ |
+ /** |
+ * Updates state based on the current status. |
+ * @private |
+ */ |
+ updateStateFromStatus_: function() { |
+ if (this.hotwordStatus_.enabled) { |
+ // Hotwording is enabled. |
+ // TODO(amistry): Have a separate alwaysOnEnabled flag. For now, treat |
+ // "enabled" as "always on enabled". |
+ this.startDetector_(); |
+ } else { |
+ // Not enabled. Shut down if running. |
+ this.shutdownDetector_(); |
+ } |
+ }, |
+ |
+ /** |
+ * Starts the hotword detector. |
+ * @private |
+ */ |
+ startDetector_: function() { |
+ // Last attempt to start detector resulted in an error. |
+ if (this.state_ == State_.ERROR) { |
+ // TODO(amistry): Do some error rate tracking here and disable the |
+ // extension if we error too often. |
+ } |
+ |
+ if (!this.pluginManager_) { |
+ this.state_ = State_.STARTING; |
+ this.pluginManager_ = new hotword.NaClManager(); |
+ this.pluginManager_.addEventListener(hotword.constants.Event.READY, |
+ this.onReady_.bind(this)); |
+ this.pluginManager_.addEventListener(hotword.constants.Event.ERROR, |
+ this.onError_.bind(this)); |
+ this.pluginManager_.addEventListener(hotword.constants.Event.TRIGGER, |
+ this.onTrigger_.bind(this)); |
+ chrome.runtime.getPlatformInfo(function(platform) { |
+ var naclArch = platform.nacl_arch; |
+ |
+ // googDucking set to false so that audio output level from other tabs |
+ // is not affected when hotword is enabled. https://crbug.com/357773 |
+ // content/common/media/media_stream_options.cc |
+ var constraints = /** @type {googMediaStreamConstraints} */ |
+ ({audio: {optional: [{googDucking: false}]}}); |
+ navigator.webkitGetUserMedia( |
+ /** @type {MediaStreamConstraints} */ (constraints), |
+ function(stream) { |
+ if (!this.pluginManager_.initialize(naclArch, stream)) { |
+ this.state_ = State_.ERROR; |
+ this.shutdownPluginManager_(); |
+ } |
+ }.bind(this), |
+ function(error) { |
+ this.state_ = State_.ERROR; |
+ this.pluginManager_ = null; |
+ }.bind(this)); |
+ }.bind(this)); |
+ } else if (this.state_ != State_.STARTING) { |
+ // Don't try to start a starting detector. |
+ this.state_ = State_.RUNNING; |
+ this.pluginManager_.startRecognizer(); |
+ } |
+ }, |
+ |
+ /** |
+ * Shuts down and removes the plugin manager, if it exists. |
+ * @private |
+ */ |
+ shutdownPluginManager_: function() { |
+ if (this.pluginManager_) { |
+ this.pluginManager_.shutdown(); |
+ this.pluginManager_ = null; |
+ } |
+ }, |
+ |
+ /** |
+ * Shuts down the hotword detector. |
+ * @private |
+ */ |
+ shutdownDetector_: function() { |
+ this.state_ = State_.STOPPED; |
+ this.shutdownPluginManager_(); |
+ }, |
+ |
+ /** |
+ * Handle the hotword plugin being ready to start. |
+ * @private |
+ */ |
+ onReady_: function() { |
+ if (this.state_ != State_.STARTING) { |
+ // At this point, we should not be in the RUNNING state. Doing so would |
+ // imply the hotword detector was started without being ready. |
+ assert(this.state_ != State_.RUNNING); |
+ this.shutdownPluginManager_(); |
+ return; |
+ } |
+ this.state_ = State_.RUNNING; |
+ this.pluginManager_.startRecognizer(); |
+ }, |
+ |
+ /** |
+ * Handle an error from the hotword plugin. |
+ * @private |
+ */ |
+ onError_: function() { |
+ this.state_ = State_.ERROR; |
+ this.shutdownPluginManager_(); |
+ }, |
+ |
+ /** |
+ * Handle hotword triggering. |
+ * @private |
+ */ |
+ onTrigger_: function() { |
+ assert(this.pluginManager_); |
+ // Detector implicitly stops when the hotword is detected. |
+ this.state_ = State_.STOPPED; |
+ |
+ chrome.hotwordPrivate.notifyHotwordRecognition('search', function() {}); |
+ } |
+ }; |
+ |
+ return { |
+ StateManager: StateManager |
+ }; |
+}); |