web-speech-profanity
Version:
Web Speech API adapter to use Cognitive Services Speech Services for both speech-to-text and text-to-speech service.
116 lines (92 loc) • 3.26 kB
JavaScript
import DOMEventEmitter from '../Util/DOMEventEmitter';
import EventAsPromise from 'event-as-promise';
import fetchSpeechData from './fetchSpeechData';
import subscribeEvent from './subscribeEvent';
function asyncDecodeAudioData(audioContext, arrayBuffer) {
return new Promise((resolve, reject) => {
const promise = audioContext.decodeAudioData(arrayBuffer, resolve, reject);
// Newer implementation of "decodeAudioData" will return a Promise
promise && typeof promise.then === 'function' && resolve(promise);
});
}
function playDecoded(audioContext, audioBuffer, source) {
return new Promise(async (resolve, reject) => {
const audioContextClosed = new EventAsPromise();
const sourceEnded = new EventAsPromise();
const unsubscribe = subscribeEvent(audioContext, 'statechange', ({ target: { state } }) => state === 'closed' && audioContextClosed.eventListener());
try {
source.buffer = audioBuffer;
// "ended" may not fire if the underlying AudioContext is closed prematurely
source.onended = sourceEnded.eventListener;
source.connect(audioContext.destination);
source.start(0);
await Promise.race([
audioContextClosed.upcoming(),
sourceEnded.upcoming()
]);
resolve();
} catch (err) {
reject(err);
} finally {
unsubscribe();
}
});
}
export default class extends DOMEventEmitter {
constructor(text) {
super(['boundary', 'end', 'error', 'mark', 'pause', 'resume', 'start']);
this._lang = null;
this._pitch = 1;
this._rate = 1;
this._voice = null;
this._volume = 1;
this.text = text;
this.onboundary = null;
this.onend = null;
this.onerror = null;
this.onmark = null;
this.onpause = null;
this.onresume = null;
this.onstart = null;
}
get lang() { return this._lang; }
set lang(value) { this._lang = value; }
get pitch() { return this._pitch; }
set pitch(value) { this._pitch = value; }
get rate() { return this._rate; }
set rate(value) { this._rate = value; }
get voice() { return this._voice; }
set voice(value) { this._voice = value; }
get volume() { return this._volume; }
set volume(value) { this._volume = value; }
async preload() {
this.arrayBufferPromise = fetchSpeechData({
authorizationToken: this.authorizationToken,
lang: this.lang || window.navigator.language,
outputFormat: this.outputFormat,
pitch: this.pitch,
rate: this.rate,
text: this.text,
voice: this.voice && this.voice.voiceURI,
volume: this.volume
});
await this.arrayBufferPromise;
}
async play(audioContext) {
try {
// HACK: iOS requires bufferSourceNode to be constructed before decoding data
const source = audioContext.createBufferSource();
const audioBuffer = await asyncDecodeAudioData(audioContext, await this.arrayBufferPromise);
this.emit('start');
this._playingSource = source;
await playDecoded(audioContext, audioBuffer, source);
this._playingSource = null;
this.emit('end');
} catch (error) {
this.emit('error', { error, type: 'error' });
}
}
stop() {
this._playingSource && this._playingSource.stop();
}
}