@voice-ping/cognitive-services-speech
Version:
VoicePing Cognitive Services Speech SDK for JavaScript forked from Microsoft
170 lines (168 loc) • 7.01 kB
JavaScript
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { SynthesisAdapterBase } from "../../common.speech/Exports";
import { createNoDashGuid } from "../../common/Exports";
import { AudioFormatTag } from "./AudioOutputFormat";
import { PullAudioOutputStreamImpl } from "./AudioOutputStream";
const MediaDurationPlaceholderSeconds = 60 * 30;
const AudioFormatToMimeType = {
[AudioFormatTag.PCM]: "audio/wav",
[AudioFormatTag.MP3]: "audio/mpeg",
[AudioFormatTag.Opus]: "audio/ogg",
};
/**
* Represents the speaker playback audio destination, which only works in browser.
* Note: the SDK will try to use <a href="https://www.w3.org/TR/media-source/">Media Source Extensions</a> to play audio.
* Mp3 format has better supports on Microsoft Edge, Chrome and Safari (desktop), so, it's better to specify mp3 format for playback.
* @class SpeakerAudioDestination
* Updated in version 1.12.1
*/
export class SpeakerAudioDestination {
constructor(audioDestinationId) {
this.privPlaybackStarted = false;
this.privAppendingToBuffer = false;
this.privMediaSourceOpened = false;
this.privBytesReceived = 0;
this.privId = audioDestinationId ? audioDestinationId : createNoDashGuid();
this.privIsPaused = false;
this.privIsClosed = false;
}
id() {
return this.privId;
}
write(buffer) {
if (this.privAudioBuffer !== undefined) {
this.privAudioBuffer.push(buffer);
this.updateSourceBuffer();
}
else if (this.privAudioOutputStream !== undefined) {
this.privAudioOutputStream.write(buffer);
this.privBytesReceived += buffer.byteLength;
}
}
close() {
this.privIsClosed = true;
if (this.privSourceBuffer !== undefined) {
this.handleSourceBufferUpdateEnd();
}
else if (this.privAudioOutputStream !== undefined) {
let receivedAudio = new ArrayBuffer(this.privBytesReceived);
this.privAudioOutputStream.read(receivedAudio);
if (this.privFormat.hasHeader) {
receivedAudio = SynthesisAdapterBase.addHeader(receivedAudio, this.privFormat);
}
const audioBlob = new Blob([receivedAudio], { type: AudioFormatToMimeType[this.privFormat.formatTag] });
this.privAudio.src = window.URL.createObjectURL(audioBlob);
this.notifyPlayback();
}
}
set format(format) {
if (typeof (AudioContext) !== "undefined" || typeof (window.webkitAudioContext) !== "undefined") {
this.privFormat = format;
const mimeType = AudioFormatToMimeType[this.privFormat.formatTag];
if (mimeType === undefined) {
// tslint:disable-next-line:no-console
console.warn(`Unknown mimeType for format ${AudioFormatTag[this.privFormat.formatTag]}.`);
}
else if (typeof (MediaSource) !== "undefined" && MediaSource.isTypeSupported(mimeType)) {
this.privAudio = new Audio();
this.privAudioBuffer = [];
this.privMediaSource = new MediaSource();
this.privAudio.src = URL.createObjectURL(this.privMediaSource);
this.privAudio.load();
this.privMediaSource.onsourceopen = (event) => {
this.privMediaSourceOpened = true;
this.privMediaSource.duration = MediaDurationPlaceholderSeconds;
this.privSourceBuffer = this.privMediaSource.addSourceBuffer(mimeType);
this.privSourceBuffer.onupdate = (_) => {
this.updateSourceBuffer();
};
this.privSourceBuffer.onupdateend = (_) => {
this.handleSourceBufferUpdateEnd();
};
this.privSourceBuffer.onupdatestart = (_) => {
this.privAppendingToBuffer = false;
};
};
this.updateSourceBuffer();
}
else {
// tslint:disable-next-line:no-console
console.warn(`Format ${AudioFormatTag[this.privFormat.formatTag]} could not be played by MSE, streaming playback is not enabled.`);
this.privAudioOutputStream = new PullAudioOutputStreamImpl();
this.privAudioOutputStream.format = this.privFormat;
this.privAudio = new Audio();
}
}
}
get isClosed() {
return this.privIsClosed;
}
get currentTime() {
if (this.privAudio !== undefined) {
return this.privAudio.currentTime;
}
return -1;
}
pause() {
if (!this.privIsPaused && this.privAudio !== undefined) {
this.privAudio.pause();
this.privIsPaused = true;
}
}
resume() {
if (this.privIsPaused && this.privAudio !== undefined) {
this.privAudio.play();
this.privIsPaused = false;
}
}
get internalAudio() {
return this.privAudio;
}
updateSourceBuffer() {
if (this.privAudioBuffer !== undefined && (this.privAudioBuffer.length > 0) && this.sourceBufferAvailable()) {
this.privAppendingToBuffer = true;
const binary = this.privAudioBuffer.shift();
try {
this.privSourceBuffer.appendBuffer(binary);
}
catch (error) {
this.privAudioBuffer.unshift(binary);
// tslint:disable-next-line:no-console
console.log("buffer filled, pausing addition of binaries until space is made");
return;
}
this.notifyPlayback();
}
else if (this.canEndStream()) {
this.handleSourceBufferUpdateEnd();
}
}
handleSourceBufferUpdateEnd() {
if (this.canEndStream() && this.sourceBufferAvailable()) {
this.privMediaSource.endOfStream();
this.notifyPlayback();
}
}
notifyPlayback() {
if (!this.privPlaybackStarted && this.privAudio !== undefined) {
this.privAudio.onended = () => {
if (!!this.onAudioEnd) {
this.onAudioEnd(this);
}
};
if (!this.privIsPaused) {
this.privAudio.play();
}
this.privPlaybackStarted = true;
}
}
canEndStream() {
return (this.isClosed && this.privSourceBuffer !== undefined && (this.privAudioBuffer.length === 0)
&& this.privMediaSourceOpened && !this.privAppendingToBuffer && this.privMediaSource.readyState === "open");
}
sourceBufferAvailable() {
return (this.privSourceBuffer !== undefined && !this.privSourceBuffer.updating);
}
}
//# sourceMappingURL=SpeakerAudioDestination.js.map