blob: 31c9812452aae5783232f9eac4ba4c7b5ac6e452 [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 class to Manage both offline / online speech recognition.
*/
<include src="plugin_manager.js"/>
<include src="audio_manager.js"/>
<include src="speech_recognition_manager.js"/>
cr.define('speech', function() {
'use strict';
/**
* The state of speech recognition.
*
* @enum {string}
*/
var SpeechState = {
READY: 'READY',
HOTWORD_RECOGNIZING: 'HOTWORD_RECOGNIZING',
RECOGNIZING: 'RECOGNIZING',
IN_SPEECH: 'IN_SPEECH',
STOPPING: 'STOPPING',
NETWORK_ERROR: 'NETWORK_ERROR'
};
/**
* The time to show the network error message in seconds.
*
* @const {number}
*/
var SPEECH_ERROR_TIMEOUT = 3;
/**
* Checks the prefix for the hotword module based on the language. This is
* fragile if the file structure has changed.
*/
function getHotwordPrefix() {
var prefix = navigator.language.toLowerCase();
if (prefix == 'en-gb')
return prefix;
var hyphen = prefix.indexOf('-');
if (hyphen >= 0)
prefix = prefix.substr(0, hyphen);
if (prefix == 'en')
prefix = '';
return prefix;
}
/**
* @constructor
*/
function SpeechManager() {
this.audioManager_ = new speech.AudioManager();
this.audioManager_.addEventListener('audio', this.onAudioLevel_.bind(this));
this.speechRecognitionManager_ = new speech.SpeechRecognitionManager(this);
this.errorTimeoutId_ = null;
}
/**
* Updates the state.
*
* @param {SpeechState} newState The new state.
* @private
*/
SpeechManager.prototype.setState_ = function(newState) {
if (this.state == newState)
return;
this.state = newState;
chrome.send('setSpeechRecognitionState', [this.state]);
};
/**
* Called with the mean audio level when audio data arrives.
*
* @param {cr.event.Event} event The event object for the audio data.
* @private
*/
SpeechManager.prototype.onAudioLevel_ = function(event) {
var data = event.data;
var level = 0;
for (var i = 0; i < data.length; ++i)
level += Math.abs(data[i]);
level /= data.length;
chrome.send('speechSoundLevel', [level]);
};
/**
* Called when the hotword recognizer is ready.
*
* @param {PluginManager} pluginManager The hotword plugin manager which gets
* ready.
* @private
*/
SpeechManager.prototype.onHotwordRecognizerReady_ = function(pluginManager) {
this.pluginManager_ = pluginManager;
this.audioManager_.addEventListener(
'audio', pluginManager.sendAudioData.bind(pluginManager));
this.pluginManager_.startRecognizer();
this.audioManager_.start();
this.setState_(SpeechState.HOTWORD_RECOGNIZING);
};
/**
* Called when an error happens for loading the hotword recognizer.
*
* @private
*/
SpeechManager.prototype.onHotwordRecognizerLoadError_ = function() {
this.setHotwordEnabled(false);
this.setState_(SpeechState.READY);
};
/**
* Called when the hotword is recognized.
*
* @private
*/
SpeechManager.prototype.onHotwordRecognized_ = function() {
if (this.state != SpeechState.HOTWORD_RECOGNIZING)
return;
this.pluginManager_.stopRecognizer();
this.speechRecognitionManager_.start();
};
/**
* Called when the speech recognition has happened.
*
* @param {string} result The speech recognition result.
* @param {boolean} isFinal Whether the result is final or not.
*/
SpeechManager.prototype.onSpeechRecognized = function(result, isFinal) {
chrome.send('speechResult', [result, isFinal]);
if (isFinal)
this.speechRecognitionManager_.stop();
};
/**
* Called when the speech recognition has started.
*/
SpeechManager.prototype.onSpeechRecognitionStarted = function() {
this.setState_(SpeechState.RECOGNIZING);
};
/**
* Called when the speech recognition has ended.
*/
SpeechManager.prototype.onSpeechRecognitionEnded = function() {
// Do not handle the speech recognition ends if it ends due to an error
// because an error message should be shown for a while.
// See onSpeechRecognitionError.
if (this.state == SpeechState.NETWORK_ERROR)
return;
// Restarts the hotword recognition.
if (this.state != SpeechState.STOPPING && this.pluginManager_) {
this.pluginManager_.startRecognizer();
this.audioManager_.start();
this.setState_(SpeechState.HOTWORD_RECOGNIZING);
} else {
this.audioManager_.stop();
this.setState_(SpeechState.READY);
}
};
/**
* Called when a speech has started.
*/
SpeechManager.prototype.onSpeechStarted = function() {
if (this.state == SpeechState.RECOGNIZING)
this.setState_(SpeechState.IN_SPEECH);
};
/**
* Called when a speech has ended.
*/
SpeechManager.prototype.onSpeechEnded = function() {
if (this.state == SpeechState.IN_SPEECH)
this.setState_(SpeechState.RECOGNIZING);
};
/**
* Called when the speech manager should recover from the error state.
*
* @private
*/
SpeechManager.prototype.onSpeechRecognitionErrorTimeout_ = function() {
this.errorTimeoutId_ = null;
this.setState_(SpeechState.READY);
this.onSpeechRecognitionEnded();
};
/**
* Called when an error happened during the speech recognition.
*
* @param {SpeechRecognitionError} e The error object.
*/
SpeechManager.prototype.onSpeechRecognitionError = function(e) {
if (e.error == 'network') {
this.setState_(SpeechState.NETWORK_ERROR);
this.errorTimeoutId_ = window.setTimeout(
this.onSpeechRecognitionErrorTimeout_.bind(this),
SPEECH_ERROR_TIMEOUT * 1000);
} else {
if (this.state != SpeechState.STOPPING)
this.setState_(SpeechState.READY);
}
};
/**
* Changes the availability of the hotword plugin.
*
* @param {boolean} enabled Whether enabled or not.
*/
SpeechManager.prototype.setHotwordEnabled = function(enabled) {
var recognizer = $('recognizer');
if (enabled) {
if (recognizer)
return;
if (!this.naclArch)
return;
var prefix = getHotwordPrefix();
var pluginManager = new speech.PluginManager(
prefix,
this.onHotwordRecognizerReady_.bind(this),
this.onHotwordRecognized_.bind(this),
this.onHotwordRecognizerLoadError_.bind(this));
var modelUrl = 'chrome://app-list/_platform_specific/' + this.naclArch +
'_' + prefix + '/hotword.data';
pluginManager.scheduleInitialize(this.audioManager_.sampleRate, modelUrl);
} else {
if (!recognizer)
return;
document.body.removeChild(recognizer);
this.pluginManager_ = null;
if (this.state == SpeechState.HOTWORD_RECOGNIZING) {
this.audioManager_.stop();
this.setState_(SpeechState.READY);
}
}
};
/**
* Sets the NaCl architecture for the hotword module.
*
* @param {string} arch The architecture.
*/
SpeechManager.prototype.setNaclArch = function(arch) {
this.naclArch = arch;
};
/**
* Called when the app-list bubble is shown.
*
* @param {boolean} hotwordEnabled Whether the hotword is enabled or not.
*/
SpeechManager.prototype.onShown = function(hotwordEnabled) {
this.setHotwordEnabled(hotwordEnabled);
// No one sets the state if the content is initialized on shown but hotword
// is not enabled. Sets the state in such case.
if (!this.state && !hotwordEnabled)
this.setState_(SpeechState.READY);
};
/**
* Called when the app-list bubble is hidden.
*/
SpeechManager.prototype.onHidden = function() {
this.setHotwordEnabled(false);
// SpeechRecognition is asynchronous.
this.audioManager_.stop();
if (this.state == SpeechState.RECOGNIZING ||
this.state == SpeechState.IN_SPEECH) {
this.setState_(SpeechState.STOPPING);
this.speechRecognitionManager_.stop();
} else {
this.setState_(SpeechState.READY);
}
};
/**
* Toggles the current state of speech recognition.
*/
SpeechManager.prototype.toggleSpeechRecognition = function() {
if (this.state == SpeechState.NETWORK_ERROR) {
if (this.errorTimeoutId_)
window.clearTimeout(this.errorTimeoutId_);
this.onSpeechRecognitionErrorTimeout_();
} else if (this.state == SpeechState.RECOGNIZING ||
this.state == SpeechState.IN_SPEECH) {
this.audioManager_.stop();
this.speechRecognitionManager_.stop();
} else {
if (this.pluginManager_)
this.pluginManager_.stopRecognizer();
if (this.audioManager_.state == speech.AudioState.STOPPED)
this.audioManager_.start();
this.speechRecognitionManager_.start();
}
};
return {
SpeechManager: SpeechManager
};
});