| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 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. | 9 * Trivial container class for session information. |
| 10 * @param {!hotword.constants.SessionSource} source Source of the hotword | 10 * @param {!hotword.constants.SessionSource} source Source of the hotword |
| 11 * session. | 11 * session. |
| 12 * @param {!function()} triggerCb Callback invoked when the hotword has | 12 * @param {!function()} triggerCb Callback invoked when the hotword has |
| 13 * triggered. | 13 * triggered. |
| 14 * @param {!function()} startedCb Callback invoked when the session has | 14 * @param {!function()} startedCb Callback invoked when the session has |
| 15 * been started successfully. | 15 * been started successfully. |
| 16 * @param {function()=} opt_modelSavedCb Callback invoked when the speaker | 16 * @param {function()=} opt_modelSavedCb Callback invoked when the speaker |
| 17 * model has been saved successfully. | 17 * model has been saved successfully. |
| 18 * @constructor | 18 * @constructor |
| 19 * @struct | 19 * @struct |
| 20 * @private | 20 * @private |
| 21 */ | 21 */ |
| 22 function Session_(source, triggerCb, startedCb, opt_modelSavedCb) { | 22 function Session_(source, triggerCb, startedCb, opt_modelSavedCb) { |
| 23 /** | 23 /** |
| 24 * Source of the hotword session request. | 24 * Source of the hotword session request. |
| 25 * @private {!hotword.constants.SessionSource} | 25 * @private {!hotword.constants.SessionSource} |
| 26 */ | 26 */ |
| 27 this.source_ = source; | 27 this.source_ = source; |
| 28 | 28 |
| 29 /** | 29 /** |
| 30 * Callback invoked when the hotword has triggered. | 30 * Callback invoked when the hotword has triggered. |
| 31 * @private {!function()} | 31 * @private {!function()} |
| 32 */ | 32 */ |
| 33 this.triggerCb_ = triggerCb; | 33 this.triggerCb_ = triggerCb; |
| 34 | 34 |
| 35 /** | 35 /** |
| 36 * Callback invoked when the session has been started successfully. | 36 * Callback invoked when the session has been started successfully. |
| 37 * @private {?function()} | 37 * @private {?function()} |
| 38 */ | 38 */ |
| 39 this.startedCb_ = startedCb; | 39 this.startedCb_ = startedCb; |
| 40 | 40 |
| 41 /** | 41 /** |
| 42 * Callback invoked when the session has been started successfully. | 42 * Callback invoked when the session has been started successfully. |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 86 * Event that fires when the hotwording status has changed. | 86 * Event that fires when the hotwording status has changed. |
| 87 * @type {!ChromeEvent} | 87 * @type {!ChromeEvent} |
| 88 */ | 88 */ |
| 89 this.onStatusChanged = new chrome.Event(); | 89 this.onStatusChanged = new chrome.Event(); |
| 90 | 90 |
| 91 /** | 91 /** |
| 92 * Hotword trigger audio notification... a.k.a The Chime (tm). | 92 * Hotword trigger audio notification... a.k.a The Chime (tm). |
| 93 * @private {!HTMLAudioElement} | 93 * @private {!HTMLAudioElement} |
| 94 */ | 94 */ |
| 95 this.chime_ = | 95 this.chime_ = |
| 96 /** @type {!HTMLAudioElement} */(document.createElement('audio')); | 96 /** @type {!HTMLAudioElement} */ (document.createElement('audio')); |
| 97 | 97 |
| 98 /** | 98 /** |
| 99 * Chrome event listeners. Saved so that they can be de-registered when | 99 * Chrome event listeners. Saved so that they can be de-registered when |
| 100 * hotwording is disabled. | 100 * hotwording is disabled. |
| 101 * @private | 101 * @private |
| 102 */ | 102 */ |
| 103 this.idleStateChangedListener_ = this.handleIdleStateChanged_.bind(this); | 103 this.idleStateChangedListener_ = this.handleIdleStateChanged_.bind(this); |
| 104 this.startupListener_ = this.handleStartup_.bind(this); | 104 this.startupListener_ = this.handleStartup_.bind(this); |
| 105 | 105 |
| 106 /** | 106 /** |
| (...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 209 * hotwordPrivate.onEnabledChanged() event. | 209 * hotwordPrivate.onEnabledChanged() event. |
| 210 */ | 210 */ |
| 211 updateStatus: function() { | 211 updateStatus: function() { |
| 212 chrome.hotwordPrivate.getStatus(this.handleStatus_.bind(this)); | 212 chrome.hotwordPrivate.getStatus(this.handleStatus_.bind(this)); |
| 213 }, | 213 }, |
| 214 | 214 |
| 215 /** | 215 /** |
| 216 * @return {boolean} True if google.com/NTP/launcher hotwording is enabled. | 216 * @return {boolean} True if google.com/NTP/launcher hotwording is enabled. |
| 217 */ | 217 */ |
| 218 isSometimesOnEnabled: function() { | 218 isSometimesOnEnabled: function() { |
| 219 assert(this.hotwordStatus_, | 219 assert( |
| 220 'No hotwording status (isSometimesOnEnabled)'); | 220 this.hotwordStatus_, 'No hotwording status (isSometimesOnEnabled)'); |
| 221 // Although the two settings are supposed to be mutually exclusive, it's | 221 // Although the two settings are supposed to be mutually exclusive, it's |
| 222 // possible for both to be set. In that case, always-on takes precedence. | 222 // possible for both to be set. In that case, always-on takes precedence. |
| 223 return this.hotwordStatus_.enabled && | 223 return this.hotwordStatus_.enabled && |
| 224 !this.hotwordStatus_.alwaysOnEnabled; | 224 !this.hotwordStatus_.alwaysOnEnabled; |
| 225 }, | 225 }, |
| 226 | 226 |
| 227 /** | 227 /** |
| 228 * @return {boolean} True if always-on hotwording is enabled. | 228 * @return {boolean} True if always-on hotwording is enabled. |
| 229 */ | 229 */ |
| 230 isAlwaysOnEnabled: function() { | 230 isAlwaysOnEnabled: function() { |
| (...skipping 25 matching lines...) Expand all Loading... |
| 256 }, | 256 }, |
| 257 | 257 |
| 258 /** | 258 /** |
| 259 * Updates state based on the current status. | 259 * Updates state based on the current status. |
| 260 * @private | 260 * @private |
| 261 */ | 261 */ |
| 262 updateStateFromStatus_: function() { | 262 updateStateFromStatus_: function() { |
| 263 if (!this.hotwordStatus_) | 263 if (!this.hotwordStatus_) |
| 264 return; | 264 return; |
| 265 | 265 |
| 266 if (this.hotwordStatus_.enabled || | 266 if (this.hotwordStatus_.enabled || this.hotwordStatus_.alwaysOnEnabled || |
| 267 this.hotwordStatus_.alwaysOnEnabled || | |
| 268 this.hotwordStatus_.trainingEnabled) { | 267 this.hotwordStatus_.trainingEnabled) { |
| 269 // Detect changes to audio logging and kill the detector if that setting | 268 // Detect changes to audio logging and kill the detector if that setting |
| 270 // has changed. | 269 // has changed. |
| 271 if (this.hotwordStatus_.audioLoggingEnabled != this.loggingEnabled_) | 270 if (this.hotwordStatus_.audioLoggingEnabled != this.loggingEnabled_) |
| 272 this.shutdownDetector_(); | 271 this.shutdownDetector_(); |
| 273 this.loggingEnabled_ = this.hotwordStatus_.audioLoggingEnabled; | 272 this.loggingEnabled_ = this.hotwordStatus_.audioLoggingEnabled; |
| 274 | 273 |
| 275 // If the training state has changed, we need to first shut down the | 274 // If the training state has changed, we need to first shut down the |
| 276 // detector so that we can restart in a different mode. | 275 // detector so that we can restart in a different mode. |
| 277 if (this.hotwordStatus_.trainingEnabled != this.trainingEnabled_) | 276 if (this.hotwordStatus_.trainingEnabled != this.trainingEnabled_) |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 315 // Last attempt to start detector resulted in an error. | 314 // Last attempt to start detector resulted in an error. |
| 316 if (this.state_ == State_.ERROR) { | 315 if (this.state_ == State_.ERROR) { |
| 317 // TODO(amistry): Do some error rate tracking here and disable the | 316 // TODO(amistry): Do some error rate tracking here and disable the |
| 318 // extension if we error too often. | 317 // extension if we error too often. |
| 319 } | 318 } |
| 320 | 319 |
| 321 if (!this.pluginManager_) { | 320 if (!this.pluginManager_) { |
| 322 this.state_ = State_.STARTING; | 321 this.state_ = State_.STARTING; |
| 323 var isHotwordStream = this.isAlwaysOnEnabled() && | 322 var isHotwordStream = this.isAlwaysOnEnabled() && |
| 324 this.hotwordStatus_.hotwordHardwareAvailable; | 323 this.hotwordStatus_.hotwordHardwareAvailable; |
| 325 this.pluginManager_ = new hotword.NaClManager(this.loggingEnabled_, | 324 this.pluginManager_ = |
| 326 isHotwordStream); | 325 new hotword.NaClManager(this.loggingEnabled_, isHotwordStream); |
| 327 this.pluginManager_.addEventListener(hotword.constants.Event.READY, | 326 this.pluginManager_.addEventListener( |
| 328 this.onReady_.bind(this)); | 327 hotword.constants.Event.READY, this.onReady_.bind(this)); |
| 329 this.pluginManager_.addEventListener(hotword.constants.Event.ERROR, | 328 this.pluginManager_.addEventListener( |
| 330 this.onError_.bind(this)); | 329 hotword.constants.Event.ERROR, this.onError_.bind(this)); |
| 331 this.pluginManager_.addEventListener(hotword.constants.Event.TRIGGER, | 330 this.pluginManager_.addEventListener( |
| 332 this.onTrigger_.bind(this)); | 331 hotword.constants.Event.TRIGGER, this.onTrigger_.bind(this)); |
| 333 this.pluginManager_.addEventListener(hotword.constants.Event.TIMEOUT, | 332 this.pluginManager_.addEventListener( |
| 334 this.onTimeout_.bind(this)); | 333 hotword.constants.Event.TIMEOUT, this.onTimeout_.bind(this)); |
| 335 this.pluginManager_.addEventListener( | 334 this.pluginManager_.addEventListener( |
| 336 hotword.constants.Event.SPEAKER_MODEL_SAVED, | 335 hotword.constants.Event.SPEAKER_MODEL_SAVED, |
| 337 this.onSpeakerModelSaved_.bind(this)); | 336 this.onSpeakerModelSaved_.bind(this)); |
| 338 chrome.runtime.getPlatformInfo(function(platform) { | 337 chrome.runtime.getPlatformInfo(function(platform) { |
| 339 var naclArch = platform.nacl_arch; | 338 var naclArch = platform.nacl_arch; |
| 340 | 339 |
| 341 // googDucking set to false so that audio output level from other tabs | 340 // googDucking set to false so that audio output level from other tabs |
| 342 // is not affected when hotword is enabled. https://crbug.com/357773 | 341 // is not affected when hotword is enabled. https://crbug.com/357773 |
| 343 // content/common/media/media_stream_options.cc | 342 // content/common/media/media_stream_options.cc |
| 344 // When always-on is enabled, request the hotword stream. | 343 // When always-on is enabled, request the hotword stream. |
| 345 // Optional because we allow power users to bypass the hardware | 344 // Optional because we allow power users to bypass the hardware |
| 346 // detection via a flag, and hence the hotword stream may not be | 345 // detection via a flag, and hence the hotword stream may not be |
| 347 // available. | 346 // available. |
| 348 var constraints = /** @type {googMediaStreamConstraints} */ | 347 var constraints = /** @type {googMediaStreamConstraints} */ |
| 349 ({audio: {optional: [ | 348 ({ |
| 350 { googDucking: false }, | 349 audio: { |
| 351 { googHotword: this.isAlwaysOnEnabled() } | 350 optional: [ |
| 352 ]}}); | 351 {googDucking: false}, |
| 352 {googHotword: this.isAlwaysOnEnabled()} |
| 353 ] |
| 354 } |
| 355 }); |
| 353 navigator.webkitGetUserMedia( | 356 navigator.webkitGetUserMedia( |
| 354 /** @type {MediaStreamConstraints} */ (constraints), | 357 /** @type {MediaStreamConstraints} */ (constraints), |
| 355 function(stream) { | 358 function(stream) { |
| 356 hotword.metrics.recordEnum( | 359 hotword.metrics.recordEnum( |
| 357 hotword.constants.UmaMetrics.MEDIA_STREAM_RESULT, | 360 hotword.constants.UmaMetrics.MEDIA_STREAM_RESULT, |
| 358 hotword.constants.UmaMediaStreamOpenResult.SUCCESS, | 361 hotword.constants.UmaMediaStreamOpenResult.SUCCESS, |
| 359 hotword.constants.UmaMediaStreamOpenResult.MAX); | 362 hotword.constants.UmaMediaStreamOpenResult.MAX); |
| 360 // The detector could have been shut down before the stream | 363 // The detector could have been shut down before the stream |
| 361 // finishes opening. | 364 // finishes opening. |
| 362 if (this.pluginManager_ == null) { | 365 if (this.pluginManager_ == null) { |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 445 shutdownDetector_: function() { | 448 shutdownDetector_: function() { |
| 446 this.state_ = State_.STOPPED; | 449 this.state_ = State_.STOPPED; |
| 447 this.shutdownPluginManager_(); | 450 this.shutdownPluginManager_(); |
| 448 }, | 451 }, |
| 449 | 452 |
| 450 /** | 453 /** |
| 451 * Finalizes the speaker model. Assumes the plugin has been loaded and | 454 * Finalizes the speaker model. Assumes the plugin has been loaded and |
| 452 * started. | 455 * started. |
| 453 */ | 456 */ |
| 454 finalizeSpeakerModel: function() { | 457 finalizeSpeakerModel: function() { |
| 455 assert(this.pluginManager_, | 458 assert( |
| 456 'Cannot finalize speaker model: No NaCl plugin loaded'); | 459 this.pluginManager_, |
| 460 'Cannot finalize speaker model: No NaCl plugin loaded'); |
| 457 if (this.state_ != State_.RUNNING) { | 461 if (this.state_ != State_.RUNNING) { |
| 458 hotword.debug('Cannot finalize speaker model: NaCl plugin not started'); | 462 hotword.debug('Cannot finalize speaker model: NaCl plugin not started'); |
| 459 return; | 463 return; |
| 460 } | 464 } |
| 461 this.pluginManager_.finalizeSpeakerModel(); | 465 this.pluginManager_.finalizeSpeakerModel(); |
| 462 }, | 466 }, |
| 463 | 467 |
| 464 /** | 468 /** |
| 465 * Handle the hotword plugin being ready to start. | 469 * Handle the hotword plugin being ready to start. |
| 466 * @private | 470 * @private |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 570 * @param {!hotword.constants.SessionSource} source Source of the hotword | 574 * @param {!hotword.constants.SessionSource} source Source of the hotword |
| 571 * session request. | 575 * session request. |
| 572 * @param {!function()} startedCb Callback invoked when the session has | 576 * @param {!function()} startedCb Callback invoked when the session has |
| 573 * been started successfully. | 577 * been started successfully. |
| 574 * @param {!function()} triggerCb Callback invoked when the hotword has | 578 * @param {!function()} triggerCb Callback invoked when the hotword has |
| 575 * @param {function()=} modelSavedCb Callback invoked when the speaker model | 579 * @param {function()=} modelSavedCb Callback invoked when the speaker model |
| 576 * has been saved. | 580 * has been saved. |
| 577 * @param {hotword.constants.RecognizerStartMode=} opt_mode The mode to | 581 * @param {hotword.constants.RecognizerStartMode=} opt_mode The mode to |
| 578 * start the recognizer in. | 582 * start the recognizer in. |
| 579 */ | 583 */ |
| 580 startSession: function(source, startedCb, triggerCb, | 584 startSession: function( |
| 581 opt_modelSavedCb, opt_mode) { | 585 source, startedCb, triggerCb, opt_modelSavedCb, opt_mode) { |
| 582 if (this.isTrainingEnabled() && opt_mode) { | 586 if (this.isTrainingEnabled() && opt_mode) { |
| 583 this.startMode_ = opt_mode; | 587 this.startMode_ = opt_mode; |
| 584 } else { | 588 } else { |
| 585 this.startMode_ = hotword.constants.RecognizerStartMode.NORMAL; | 589 this.startMode_ = hotword.constants.RecognizerStartMode.NORMAL; |
| 586 } | 590 } |
| 587 hotword.debug('Starting session for source: ' + source); | 591 hotword.debug('Starting session for source: ' + source); |
| 588 this.removeSession_(source); | 592 this.removeSession_(source); |
| 589 this.sessions_.push(new Session_(source, triggerCb, startedCb, | 593 this.sessions_.push( |
| 590 opt_modelSavedCb)); | 594 new Session_(source, triggerCb, startedCb, opt_modelSavedCb)); |
| 591 this.updateStateFromStatus_(); | 595 this.updateStateFromStatus_(); |
| 592 }, | 596 }, |
| 593 | 597 |
| 594 /** | 598 /** |
| 595 * Stops a hotwording session. | 599 * Stops a hotwording session. |
| 596 * @param {!hotword.constants.SessionSource} source Source of the hotword | 600 * @param {!hotword.constants.SessionSource} source Source of the hotword |
| 597 * session request. | 601 * session request. |
| 598 */ | 602 */ |
| 599 stopSession: function(source) { | 603 stopSession: function(source) { |
| 600 hotword.debug('Stopping session for source: ' + source); | 604 hotword.debug('Stopping session for source: ' + source); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 626 /** | 630 /** |
| 627 * Handles a chrome.runtime.onStartup event. | 631 * Handles a chrome.runtime.onStartup event. |
| 628 * @private | 632 * @private |
| 629 */ | 633 */ |
| 630 handleStartup_: function() { | 634 handleStartup_: function() { |
| 631 // Nothing specific needs to be done here. This function exists solely to | 635 // Nothing specific needs to be done here. This function exists solely to |
| 632 // be registered on the startup event. | 636 // be registered on the startup event. |
| 633 } | 637 } |
| 634 }; | 638 }; |
| 635 | 639 |
| 636 return { | 640 return {StateManager: StateManager}; |
| 637 StateManager: StateManager | |
| 638 }; | |
| 639 }); | 641 }); |
| OLD | NEW |