1// Copyright 2013 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. 4 5/** 6 * @fileoverview The manager of offline hotword speech recognizer plugin. 7 */ 8 9cr.define('speech', function() { 10 'use strict'; 11 12 /** The timeout milliseconds to load the model file. */ 13 var MODEL_LOAD_TIMEOUT = 2000; 14 15 /** 16 * The type of the plugin state. 17 ** @enum {number} 18 */ 19 var PluginState = { 20 UNINITIALIZED: 0, 21 LOADED: 1, 22 READY: 2, 23 RECOGNIZING: 3 24 }; 25 26 /** 27 * The command names of the plugin. 28 * @enum {string} 29 */ 30 var pluginCommands = { 31 SET_SAMPLING_RATE: 'h', 32 SET_CONFIG: 'm', 33 START_RECOGNIZING: 'r', 34 STOP_RECOGNIZING: 's' 35 }; 36 37 /** 38 * The regexp pattern of the hotword recognition result. 39 */ 40 var recognitionPattern = /^(HotwordFiredEvent:|hotword)/; 41 42 /** 43 * @constructor 44 */ 45 function PluginManager(prefix, onReady, onRecognized, onError) { 46 this.state = PluginState.UNINITIALIZED; 47 this.onReady_ = onReady; 48 this.onRecognized_ = onRecognized; 49 this.onError_ = onError; 50 this.samplingRate_ = null; 51 this.config_ = null; 52 this.modelLoadTimeoutId_ = null; 53 var recognizer = $('recognizer'); 54 if (!recognizer) { 55 recognizer = document.createElement('EMBED'); 56 recognizer.id = 'recognizer'; 57 recognizer.type = 'application/x-nacl'; 58 recognizer.src = 'chrome://app-list/hotword_' + prefix + '.nmf'; 59 recognizer.width = '1'; 60 recognizer.height = '1'; 61 document.body.appendChild(recognizer); 62 } 63 recognizer.addEventListener('error', onError); 64 recognizer.addEventListener('message', this.onMessage_.bind(this)); 65 recognizer.addEventListener('load', this.onLoad_.bind(this)); 66 }; 67 68 /** 69 * The event handler of the plugin status. 70 * 71 * @param {Event} messageEvent the event object from the plugin. 72 * @private 73 */ 74 PluginManager.prototype.onMessage_ = function(messageEvent) { 75 if (messageEvent.data == 'audio') { 76 var wasNotReady = this.state < PluginState.READY; 77 this.state = PluginState.RECOGNIZING; 78 if (wasNotReady) { 79 window.clearTimeout(this.modelLoadTimeoutId_); 80 this.modelLoadTimeoutId_ = null; 81 this.onReady_(this); 82 } 83 } else if (messageEvent.data == 'stopped' && 84 this.state == PluginState.RECOGNIZING) { 85 this.state = PluginState.READY; 86 } else if (recognitionPattern.exec(messageEvent.data)) { 87 this.onRecognized_(); 88 } 89 }; 90 91 /** 92 * The event handler when the plugin is loaded. 93 * 94 * @private 95 */ 96 PluginManager.prototype.onLoad_ = function() { 97 if (this.state == PluginState.UNINITIALIZED) { 98 this.state = PluginState.LOADED; 99 if (this.samplingRate_ && this.config_) 100 this.initialize_(this.samplingRate_, this.config_); 101 // Sets the timeout for initialization in case that NaCl module failed to 102 // respond during the initialization. 103 this.modelLoadTimeoutId_ = window.setTimeout( 104 this.onError_, MODEL_LOAD_TIMEOUT); 105 } 106 }; 107 108 /** 109 * Sends the initialization messages to the plugin. This method is private. 110 * The initialization will happen from onLoad_ or scheduleInitialize. 111 * 112 * @param {number} samplingRate the sampling rate the plugin accepts. 113 * @param {string} config the url of the config file. 114 * @private 115 */ 116 PluginManager.prototype.initialize_ = function(samplingRate, config) { 117 $('recognizer').postMessage( 118 pluginCommands.SET_SAMPLING_RATE + samplingRate); 119 $('recognizer').postMessage(pluginCommands.SET_CONFIG + config); 120 }; 121 122 /** 123 * Initializes the plugin with the specified parameter, or schedules the 124 * initialization if the plugin is not ready. 125 * 126 * @param {number} samplingRate the sampling rate the plugin accepts. 127 * @param {string} config the url of the config file. 128 */ 129 PluginManager.prototype.scheduleInitialize = function(samplingRate, config) { 130 if (this.state == PluginState.UNINITIALIZED) { 131 this.samplingRate_ = samplingRate; 132 this.config_ = config; 133 } else { 134 this.initialize_(samplingRate, config); 135 } 136 }; 137 138 /** 139 * Asks the plugin to start recognizing the hotword. 140 */ 141 PluginManager.prototype.startRecognizer = function() { 142 if (this.state == PluginState.READY) 143 $('recognizer').postMessage(pluginCommands.START_RECOGNIZING); 144 }; 145 146 /** 147 * Asks the plugin to stop recognizing the hotword. 148 */ 149 PluginManager.prototype.stopRecognizer = function() { 150 if (this.state == PluginState.RECOGNIZING) 151 $('recognizer').postMessage(pluginCommands.STOP_RECOGNIZING); 152 }; 153 154 /** 155 * Sends the actual audio wave data. 156 * 157 * @param {cr.event.Event} event The event for the audio data. 158 */ 159 PluginManager.prototype.sendAudioData = function(event) { 160 if (this.state == PluginState.RECOGNIZING) 161 $('recognizer').postMessage(event.data.buffer); 162 }; 163 164 return { 165 PluginManager: PluginManager, 166 PluginState: PluginState, 167 }; 168}); 169