• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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