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() {
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.
15 function StateManager() {
18 * @private {hotword.StateManager.State_}
20 this.state_
= State_
.STOPPED
;
23 * Current hotwording status.
24 * @private {?chrome.hotwordPrivate.StatusDetails}
26 this.hotwordStatus_
= null;
29 * NaCl plugin manager.
30 * @private {?hotword.NaClManager}
32 this.pluginManager_
= null;
35 * Source of the current hotword session.
36 * @private {?hotword.constants.SessionSource}
38 this.sessionSource_
= null;
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));
54 StateManager
.State_
= {
60 var State_
= StateManager
.State_
;
62 StateManager
.prototype = {
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));
72 * Callback for hotwordPrivate.getStatus() function.
73 * @param {chrome.hotwordPrivate.StatusDetails} status Current hotword
77 handleStatus_: function(status
) {
78 this.hotwordStatus_
= status
;
79 this.updateStateFromStatus_();
83 * Updates state based on the current status.
86 updateStateFromStatus_: function() {
87 if (!this.hotwordStatus_
)
90 if (this.hotwordStatus_
.enabled
) {
91 // Start the detector if there's a session, and shut it down if there
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_();
106 this.shutdownDetector_();
108 // Not enabled. Shut down if running.
109 this.shutdownDetector_();
114 * Starts the hotword detector.
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
),
144 if (!this.pluginManager_
.initialize(naclArch
, stream
)) {
145 this.state_
= State_
.ERROR
;
146 this.shutdownPluginManager_();
150 this.state_
= State_
.ERROR
;
151 this.pluginManager_
= null;
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
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.
181 shutdownPluginManager_: function() {
182 if (this.pluginManager_
) {
183 this.pluginManager_
.shutdown();
184 this.pluginManager_
= null;
189 * Shuts down the hotword detector.
192 shutdownDetector_: function() {
193 this.state_
= State_
.STOPPED
;
194 this.shutdownPluginManager_();
198 * Handle the hotword plugin being ready to start.
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_();
209 this.startRecognizer_();
213 * Handle an error from the hotword plugin.
216 onError_: function() {
217 this.state_
= State_
.ERROR
;
218 this.shutdownPluginManager_();
222 * Handle hotword triggering.
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
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
256 stopSession: function(source
) {
257 this.sessionSource_
= null;
258 this.sessionStartedCb_
= null;
259 this.updateStateFromStatus_();
264 StateManager
: StateManager