blob: 6a025e0548a1b2cad873d5f16aac776f2512c9dc [file] [log] [blame]
// Copyright 2013 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.
/**
* @fileoverview The manager of offline hotword speech recognizer plugin.
*/
cr.define('speech', function() {
'use strict';
/** The timeout milliseconds to load the model file. */
var MODEL_LOAD_TIMEOUT = 2000;
/**
* The type of the plugin state.
** @enum {number}
*/
var PluginState = {
UNINITIALIZED: 0,
LOADED: 1,
READY: 2,
RECOGNIZING: 3
};
/**
* The command names of the plugin.
* @enum {string}
*/
var pluginCommands = {
SET_SAMPLING_RATE: 'h',
SET_CONFIG: 'm',
START_RECOGNIZING: 'r',
STOP_RECOGNIZING: 's'
};
/**
* The regexp pattern of the hotword recognition result.
*/
var recognitionPattern = /^(HotwordFiredEvent:|hotword)/;
/**
* @constructor
*/
function PluginManager(prefix, onReady, onRecognized, onError) {
this.state = PluginState.UNINITIALIZED;
this.onReady_ = onReady;
this.onRecognized_ = onRecognized;
this.onError_ = onError;
this.samplingRate_ = null;
this.config_ = null;
this.modelLoadTimeoutId_ = null;
var recognizer = $('recognizer');
if (!recognizer) {
recognizer = document.createElement('EMBED');
recognizer.id = 'recognizer';
recognizer.type = 'application/x-nacl';
recognizer.src = 'chrome://app-list/hotword_' + prefix + '.nmf';
recognizer.width = '1';
recognizer.height = '1';
document.body.appendChild(recognizer);
}
recognizer.addEventListener('error', onError);
recognizer.addEventListener('message', this.onMessage_.bind(this));
recognizer.addEventListener('load', this.onLoad_.bind(this));
};
/**
* The event handler of the plugin status.
*
* @param {Event} messageEvent the event object from the plugin.
* @private
*/
PluginManager.prototype.onMessage_ = function(messageEvent) {
if (messageEvent.data == 'audio') {
var wasNotReady = this.state < PluginState.READY;
this.state = PluginState.RECOGNIZING;
if (wasNotReady) {
window.clearTimeout(this.modelLoadTimeoutId_);
this.modelLoadTimeoutId_ = null;
this.onReady_(this);
}
} else if (messageEvent.data == 'stopped' &&
this.state == PluginState.RECOGNIZING) {
this.state = PluginState.READY;
} else if (recognitionPattern.exec(messageEvent.data)) {
this.onRecognized_();
}
};
/**
* The event handler when the plugin is loaded.
*
* @private
*/
PluginManager.prototype.onLoad_ = function() {
if (this.state == PluginState.UNINITIALIZED) {
this.state = PluginState.LOADED;
if (this.samplingRate_ && this.config_)
this.initialize_(this.samplingRate_, this.config_);
// Sets the timeout for initialization in case that NaCl module failed to
// respond during the initialization.
this.modelLoadTimeoutId_ = window.setTimeout(
this.onError_, MODEL_LOAD_TIMEOUT);
}
};
/**
* Sends the initialization messages to the plugin. This method is private.
* The initialization will happen from onLoad_ or scheduleInitialize.
*
* @param {number} samplingRate the sampling rate the plugin accepts.
* @param {string} config the url of the config file.
* @private
*/
PluginManager.prototype.initialize_ = function(samplingRate, config) {
$('recognizer').postMessage(
pluginCommands.SET_SAMPLING_RATE + samplingRate);
$('recognizer').postMessage(pluginCommands.SET_CONFIG + config);
};
/**
* Initializes the plugin with the specified parameter, or schedules the
* initialization if the plugin is not ready.
*
* @param {number} samplingRate the sampling rate the plugin accepts.
* @param {string} config the url of the config file.
*/
PluginManager.prototype.scheduleInitialize = function(samplingRate, config) {
if (this.state == PluginState.UNINITIALIZED) {
this.samplingRate_ = samplingRate;
this.config_ = config;
} else {
this.initialize_(samplingRate, config);
}
};
/**
* Asks the plugin to start recognizing the hotword.
*/
PluginManager.prototype.startRecognizer = function() {
if (this.state == PluginState.READY)
$('recognizer').postMessage(pluginCommands.START_RECOGNIZING);
};
/**
* Asks the plugin to stop recognizing the hotword.
*/
PluginManager.prototype.stopRecognizer = function() {
if (this.state == PluginState.RECOGNIZING)
$('recognizer').postMessage(pluginCommands.STOP_RECOGNIZING);
};
/**
* Sends the actual audio wave data.
*
* @param {cr.event.Event} event The event for the audio data.
*/
PluginManager.prototype.sendAudioData = function(event) {
if (this.state == PluginState.RECOGNIZING)
$('recognizer').postMessage(event.data.buffer);
};
return {
PluginManager: PluginManager,
PluginState: PluginState,
};
});