Move Webstore URL concepts to //extensions and out
[chromium-blink-merge.git] / chrome / browser / resources / hotword / state_manager.js
blob2727cbcd8af7cf76ee799f1d8790d071e2cc9d3a
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
3 // found in the LICENSE file.
5 cr.define('hotword', function() {
6 'use strict';
8 /**
9 * Class to manage hotwording state. Starts/stops the hotword detector based
10 * on user settings, session requests, and any other factors that play into
11 * whether or not hotwording should be running.
12 * @constructor
13 * @struct
15 function StateManager() {
16 /**
17 * Current state.
18 * @private {hotword.StateManager.State_}
20 this.state_ = State_.STOPPED;
22 /**
23 * Current hotwording status.
24 * @private {?chrome.hotwordPrivate.StatusDetails}
26 this.hotwordStatus_ = null;
28 /**
29 * NaCl plugin manager.
30 * @private {?hotword.NaClManager}
32 this.pluginManager_ = null;
34 /**
35 * Source of the current hotword session.
36 * @private {?hotword.constants.SessionSource}
38 this.sessionSource_ = null;
40 /**
41 * Callback to run when the hotword detector has successfully started.
42 * @private {!function()}
44 this.sessionStartedCb_ = null;
46 // Get the initial status.
47 chrome.hotwordPrivate.getStatus(this.handleStatus_.bind(this));
50 /**
51 * @enum {number}
52 * @private
54 StateManager.State_ = {
55 STOPPED: 0,
56 STARTING: 1,
57 RUNNING: 2,
58 ERROR: 3,
60 var State_ = StateManager.State_;
62 StateManager.prototype = {
63 /**
64 * Request status details update. Intended to be called from the
65 * hotwordPrivate.onEnabledChanged() event.
67 updateStatus: function() {
68 chrome.hotwordPrivate.getStatus(this.handleStatus_.bind(this));
71 /**
72 * Callback for hotwordPrivate.getStatus() function.
73 * @param {chrome.hotwordPrivate.StatusDetails} status Current hotword
74 * status.
75 * @private
77 handleStatus_: function(status) {
78 this.hotwordStatus_ = status;
79 this.updateStateFromStatus_();
82 /**
83 * Updates state based on the current status.
84 * @private
86 updateStateFromStatus_: function() {
87 if (!this.hotwordStatus_)
88 return;
90 if (this.hotwordStatus_.enabled) {
91 // Start the detector if there's a session, and shut it down if there
92 // isn't.
93 // TODO(amistry): Support stacking sessions. This can happen when the
94 // user opens google.com or the NTP, then opens the launcher. Opening
95 // google.com will create one session, and opening the launcher will
96 // create the second. Closing the launcher should re-activate the
97 // google.com session.
98 // NOTE(amistry): With always-on, we want a different behaviour with
99 // sessions since the detector should always be running. The exception
100 // being when the user triggers by saying 'Ok Google'. In that case, the
101 // detector stops, so starting/stopping the launcher session should
102 // restart the detector.
103 if (this.sessionSource_)
104 this.startDetector_();
105 else
106 this.shutdownDetector_();
107 } else {
108 // Not enabled. Shut down if running.
109 this.shutdownDetector_();
114 * Starts the hotword detector.
115 * @private
117 startDetector_: function() {
118 // Last attempt to start detector resulted in an error.
119 if (this.state_ == State_.ERROR) {
120 // TODO(amistry): Do some error rate tracking here and disable the
121 // extension if we error too often.
124 if (!this.pluginManager_) {
125 this.state_ = State_.STARTING;
126 this.pluginManager_ = new hotword.NaClManager();
127 this.pluginManager_.addEventListener(hotword.constants.Event.READY,
128 this.onReady_.bind(this));
129 this.pluginManager_.addEventListener(hotword.constants.Event.ERROR,
130 this.onError_.bind(this));
131 this.pluginManager_.addEventListener(hotword.constants.Event.TRIGGER,
132 this.onTrigger_.bind(this));
133 chrome.runtime.getPlatformInfo(function(platform) {
134 var naclArch = platform.nacl_arch;
136 // googDucking set to false so that audio output level from other tabs
137 // is not affected when hotword is enabled. https://crbug.com/357773
138 // content/common/media/media_stream_options.cc
139 var constraints = /** @type {googMediaStreamConstraints} */
140 ({audio: {optional: [{googDucking: false}]}});
141 navigator.webkitGetUserMedia(
142 /** @type {MediaStreamConstraints} */ (constraints),
143 function(stream) {
144 if (!this.pluginManager_.initialize(naclArch, stream)) {
145 this.state_ = State_.ERROR;
146 this.shutdownPluginManager_();
148 }.bind(this),
149 function(error) {
150 this.state_ = State_.ERROR;
151 this.pluginManager_ = null;
152 }.bind(this));
153 }.bind(this));
154 } else if (this.state_ != State_.STARTING) {
155 // Don't try to start a starting detector.
156 this.startRecognizer_();
161 * Start the recognizer plugin. Assumes the plugin has been loaded and is
162 * ready to start.
163 * @private
165 startRecognizer_: function() {
166 assert(this.pluginManager_);
167 if (this.state_ != State_.RUNNING) {
168 this.state_ = State_.RUNNING;
169 this.pluginManager_.startRecognizer();
171 if (this.sessionStartedCb_) {
172 this.sessionStartedCb_();
173 this.sessionStartedCb_ = null;
178 * Shuts down and removes the plugin manager, if it exists.
179 * @private
181 shutdownPluginManager_: function() {
182 if (this.pluginManager_) {
183 this.pluginManager_.shutdown();
184 this.pluginManager_ = null;
189 * Shuts down the hotword detector.
190 * @private
192 shutdownDetector_: function() {
193 this.state_ = State_.STOPPED;
194 this.shutdownPluginManager_();
198 * Handle the hotword plugin being ready to start.
199 * @private
201 onReady_: function() {
202 if (this.state_ != State_.STARTING) {
203 // At this point, we should not be in the RUNNING state. Doing so would
204 // imply the hotword detector was started without being ready.
205 assert(this.state_ != State_.RUNNING);
206 this.shutdownPluginManager_();
207 return;
209 this.startRecognizer_();
213 * Handle an error from the hotword plugin.
214 * @private
216 onError_: function() {
217 this.state_ = State_.ERROR;
218 this.shutdownPluginManager_();
222 * Handle hotword triggering.
223 * @private
225 onTrigger_: function() {
226 assert(this.pluginManager_);
227 // Detector implicitly stops when the hotword is detected.
228 this.state_ = State_.STOPPED;
230 chrome.hotwordPrivate.notifyHotwordRecognition('search', function() {});
232 // Implicitly clear the session. A session needs to be started in order to
233 // restart the detector.
234 this.sessionSource_ = null;
235 this.sessionStartedCb_ = null;
239 * Start a hotwording session.
240 * @param {!hotword.constants.SessionSource} source Source of the hotword
241 * session request.
242 * @param {!function()} startedCb Callback invoked when the session has
243 * been started successfully.
245 startSession: function(source, startedCb) {
246 this.sessionSource_ = source;
247 this.sessionStartedCb_ = startedCb;
248 this.updateStateFromStatus_();
252 * Stops a hotwording session.
253 * @param {!hotword.constants.SessionSource} source Source of the hotword
254 * session request.
256 stopSession: function(source) {
257 this.sessionSource_ = null;
258 this.sessionStartedCb_ = null;
259 this.updateStateFromStatus_();
263 return {
264 StateManager: StateManager