blob: 14e3fd95d415f0f9d8b93bde0acc5b097c5bac1a [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS 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 Event listener for the Google text-to-speech extension.
*
* This class implements the Chrome TTS engine extnesion API and dispatches
* speech requests to an instance of TtsController, defined in
* tts_controller.js.
*/
'use strict';
/**
* @constructor
*
* Simple structure to hold a pending request, this is used when we
* get a request to speak before the engine is initialized.
*
* @param {string} utterance The utterance to speak.
* @param {object} options A map of strings to values, options like
* pitch, volume, etc.
* @param {callback} callback The callback to call to report progress,
* finalization, cancellation.
*/
var PendingRequest = function(utterance, options, callback) {
this.utterance = utterance;
this.options = options;
this.callback = callback;
};
/**
* @constructor
*/
var TtsMain = function() {
this.callback_ = null;
this.utteranceId_ = 0;
this.voice_ = null;
this.lang_ = '';
this.voiceName_ = '';
this.pendingSpeechRequest_ = null;
this.offset_ = 0;
};
/**
* Function called on startup.
*/
TtsMain.prototype.run = function() {
document.addEventListener('unload', this.unload, false);
this.currentController_ = new TtsController('lstm', this, navigator.language);
this.currentController_.updateVoices();
chrome.ttsEngine.onSpeak.addListener(this.onSpeak.bind(this));
chrome.ttsEngine.onStop.addListener(this.onStop.bind(this));
};
/**
* Called by one of the TTS controllers when it finishes its initialization.
*/
TtsMain.prototype.onInitialized = function() {
this.speakPendingRequest_();
};
/**
* Called by one of the TTS controllers when we have a message coming from
* the engine, to send to the client.
*
* @param {string} utteranceId The Identifier for the utterance this message
* is about.
* @param {object} response The message coming from the engine, to be sent
* to the callback function provided by the client.
*/
TtsMain.prototype.onResponse = function(utteranceId, response) {
if (utteranceId != this.utteranceId_ || this.callback_ == null) {
return;
}
console.log('onResponse type=' + response.type +
' utteranceId=' + utteranceId);
// We asked TTS to speak the original string trimmed of any whitespace
// at the beginning. This addresses part of b/70898596, but we must add
// back the number of trimmed whitespace characters to give the caller
// a charIndex into their original, untrimmed string.
response.charIndex += this.offset_;
this.callback_(response);
var type = response.type;
if (type == 'end' || type == 'interrupted' ||
type == 'cancelled' || type == 'error') {
this.callback_ = null;
}
};
/**
* Called by the client to stop speech.
*/
TtsMain.prototype.onStop = function() {
this.pendingSpeechRequest_ = null;
this.offset_ = 0;
this.callback_ = null;
this.currentController_.onStop();
};
/**
* Called by the client to start speech synthesis.
*
* @param {string} utterance The utterance to say.
* @param {object} options The options affecting the speech, like language,
* pitch, rate, etc.
* @param {function(object)} callback The function to receive messages from the
* engine.
*/
TtsMain.prototype.onSpeak = function(utterance, options, callback) {
console.log('Will speak: "' + utterance + '" lang="' + options.lang + '"');
this.currentController_.switchVoiceIfNeeded(options.voiceName, options.lang);
if (!this.currentController_.isInitialized()) {
this.currentController_.ensureInitialized();
console.log('No text-to-speech controller is initialized yet.');
if (this.pendingSpeechRequest_) {
// Chrome takes care of queueing. The extension only needs to handle one
// utterance at a time. If a new speak request is received that always
// means to interrupt / throw away the previous one.
var response = {type: 'cancelled', charIndex: 0};
var pendingCallback = this.pendingSpeechRequest_.callback;
pendingCallback(response);
this.pendingSpeechRequest_ = null;
this.offset_ = 0;
}
this.pendingSpeechRequest_ = new PendingRequest(utterance, options,
callback);
return;
}
this.currentController_.onStop();
this.utteranceId_++;
this.callback_ = callback;
console.log('SETTING CALLBACK, id=' + this.utteranceId_);
// We will ask the TTS engine to speak the utterance without any
// whitespace at the beginning, because it can cause the word
// callbacks to be incorrect. See b/70898596.
let original = utterance;
utterance = utterance.trimLeft();
// Keep track of the difference in length between the original
// string from the user and the string we are asking TTS to
// speak.
this.offset_ = original.length - utterance.length;
this.currentController_.onSpeak(utterance, options, this.utteranceId_);
};
/**
* If we were required to synthesize some speech before any engine was
* initialized, the last request was saved and this function is called
* to start synthesis when the first engine is ready.
* @private
*/
TtsMain.prototype.speakPendingRequest_ = function() {
if (!this.pendingSpeechRequest_)
return;
var utterance = this.pendingSpeechRequest_.utterance;
var options = this.pendingSpeechRequest_.options;
var callback = this.pendingSpeechRequest_.callback;
this.pendingSpeechRequest_ = null;
this.onSpeak(utterance, options, callback);
};
/**
* Method called when the window is closed to do the clean up
* @private
*/
TtsMain.prototype.unload_ = function() {
this.currentController_.unload();
};
var ttsMain = new TtsMain();
ttsMain.run();