UNPKG

@hahnpro/ms-speech-sdk

Version:
1 lines 10.9 kB
{"version":3,"sources":["src/common.browser/FileAudioSource.ts"],"names":[],"mappings":";AAGA,OAAO,EAEH,wBAAwB,EAE3B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAEH,gBAAgB,EAYhB,WAAW,EACX,YAAY,EACZ,gBAAgB,EAInB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAqB,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAE7F,qBAAa,eAAgB,YAAW,YAAY;IAEhD,OAAO,CAAC,sBAAsB,CAAiC;IAE/D,OAAO,CAAC,WAAW,CAA8C;IAEjE,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO,CAAC,UAAU,CAAgC;IAElD,OAAO,CAAC,UAAU,CAAgB;IAElC,OAAO,CAAC,YAAY,CAAS;IAE7B,OAAO,CAAC,aAAa,CAAc;gBAEhB,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM;IAcjF,IAAW,MAAM,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAElD;IAEM,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAYvB,EAAE,IAAI,MAAM;IAIN,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkB5D,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAQjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAc/B,IAAW,MAAM,IAAI,WAAW,CAAC,gBAAgB,CAAC,CAEjD;IAED,IAAW,UAAU,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAmBzD;IAED,OAAO,CAAC,UAAU;YAyDJ,MAAM;IAkDpB,OAAO,CAAC,OAAO;CAIlB","file":"FileAudioSource.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT license.\n\nimport {\n connectivity,\n ISpeechConfigAudioDevice,\n type,\n} from \"../common.speech/Exports.js\";\nimport {\n AudioSourceErrorEvent,\n AudioSourceEvent,\n AudioSourceInitializingEvent,\n AudioSourceOffEvent,\n AudioSourceReadyEvent,\n AudioStreamNodeAttachedEvent,\n AudioStreamNodeAttachingEvent,\n AudioStreamNodeDetachedEvent,\n AudioStreamNodeErrorEvent,\n ChunkedArrayBufferStream,\n createNoDashGuid,\n Deferred,\n Events,\n EventSource,\n IAudioSource,\n IAudioStreamNode,\n IStreamChunk,\n IStringDictionary,\n Stream,\n} from \"../common/Exports.js\";\nimport { AudioStreamFormat, AudioStreamFormatImpl } from \"../sdk/Audio/AudioStreamFormat.js\";\n\nexport class FileAudioSource implements IAudioSource {\n\n private privAudioFormatPromise: Promise<AudioStreamFormatImpl>;\n\n private privStreams: IStringDictionary<Stream<ArrayBuffer>> = {};\n\n private privId: string;\n\n private privEvents: EventSource<AudioSourceEvent>;\n\n private privSource: Blob | Buffer;\n\n private privFilename: string;\n\n private privHeaderEnd: number = 44;\n\n public constructor(file: File | Buffer, filename?: string, audioSourceId?: string) {\n this.privId = audioSourceId ? audioSourceId : createNoDashGuid();\n this.privEvents = new EventSource<AudioSourceEvent>();\n this.privSource = file;\n if (typeof window !== \"undefined\" && typeof Blob !== \"undefined\" && this.privSource instanceof Blob) {\n this.privFilename = (file as File).name;\n } else {\n this.privFilename = filename || \"unknown.wav\";\n }\n\n // Read the header.\n this.privAudioFormatPromise = this.readHeader();\n }\n\n public get format(): Promise<AudioStreamFormatImpl> {\n return this.privAudioFormatPromise;\n }\n\n public turnOn(): Promise<void> {\n if (this.privFilename.lastIndexOf(\".wav\") !== this.privFilename.length - 4) {\n const errorMsg = this.privFilename + \" is not supported. Only WAVE files are allowed at the moment.\";\n this.onEvent(new AudioSourceErrorEvent(errorMsg, \"\"));\n return Promise.reject(errorMsg);\n }\n\n this.onEvent(new AudioSourceInitializingEvent(this.privId)); // no stream id\n this.onEvent(new AudioSourceReadyEvent(this.privId));\n return;\n }\n\n public id(): string {\n return this.privId;\n }\n\n public async attach(audioNodeId: string): Promise<IAudioStreamNode> {\n this.onEvent(new AudioStreamNodeAttachingEvent(this.privId, audioNodeId));\n\n const stream: Stream<ArrayBuffer> = await this.upload(audioNodeId);\n\n this.onEvent(new AudioStreamNodeAttachedEvent(this.privId, audioNodeId));\n return Promise.resolve({\n detach: async (): Promise<void> => {\n stream.readEnded();\n delete this.privStreams[audioNodeId];\n this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId));\n await this.turnOff();\n },\n id: (): string => audioNodeId,\n read: (): Promise<IStreamChunk<ArrayBuffer>> => stream.read(),\n });\n }\n\n public detach(audioNodeId: string): void {\n if (audioNodeId && this.privStreams[audioNodeId]) {\n this.privStreams[audioNodeId].close();\n delete this.privStreams[audioNodeId];\n this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId));\n }\n }\n\n public turnOff(): Promise<void> {\n for (const streamId in this.privStreams) {\n if (streamId) {\n const stream = this.privStreams[streamId];\n if (stream && !stream.isClosed) {\n stream.close();\n }\n }\n }\n\n this.onEvent(new AudioSourceOffEvent(this.privId)); // no stream now\n return Promise.resolve();\n }\n\n public get events(): EventSource<AudioSourceEvent> {\n return this.privEvents;\n }\n\n public get deviceInfo(): Promise<ISpeechConfigAudioDevice> {\n return this.privAudioFormatPromise.then<ISpeechConfigAudioDevice>((result: AudioStreamFormatImpl): Promise<{\n bitspersample: number;\n channelcount: number;\n connectivity: connectivity.Unknown;\n manufacturer: string;\n model: string;\n samplerate: number;\n type: type.File;\n }> => ( Promise.resolve({\n bitspersample: result.bitsPerSample,\n channelcount: result.channels,\n connectivity: connectivity.Unknown,\n manufacturer: \"Speech SDK\",\n model: \"File\",\n samplerate: result.samplesPerSec,\n type: type.File,\n })\n ));\n }\n\n private readHeader(): Promise<AudioStreamFormatImpl> {\n // Read the wave header.\n const maxHeaderSize: number = 4296;\n const header: Blob | Buffer = this.privSource.slice(0, maxHeaderSize);\n\n const headerResult: Deferred<AudioStreamFormatImpl> = new Deferred<AudioStreamFormatImpl>();\n\n const processHeader = (header: ArrayBuffer): void => {\n const view: DataView = new DataView(header);\n\n const getWord = (index: number): string => String.fromCharCode(view.getUint8(index), view.getUint8(index + 1), view.getUint8(index + 2), view.getUint8(index + 3));\n\n // RIFF 4 bytes.\n if (\"RIFF\" !== getWord(0)) {\n headerResult.reject(\"Invalid WAV header in file, RIFF was not found\");\n return;\n }\n\n // length, 4 bytes\n // RIFF Type & fmt 8 bytes\n if (\"WAVE\" !== getWord(8) || \"fmt \" !== getWord(12)) {\n headerResult.reject(\"Invalid WAV header in file, WAVEfmt was not found\");\n return;\n }\n\n const formatSize: number = view.getInt32(16, true);\n const channelCount: number = view.getUint16(22, true);\n const sampleRate: number = view.getUint32(24, true);\n const bitsPerSample: number = view.getUint16(34, true);\n // Confirm if header is 44 bytes long.\n let pos: number = 36 + Math.max(formatSize - 16, 0);\n for (; getWord(pos) !== \"data\"; pos += 2) {\n if (pos > maxHeaderSize - 8) {\n headerResult.reject(\"Invalid WAV header in file, data block was not found\");\n return;\n }\n }\n this.privHeaderEnd = pos + 8;\n headerResult.resolve(AudioStreamFormat.getWaveFormatPCM(sampleRate, bitsPerSample, channelCount) as AudioStreamFormatImpl);\n };\n\n if (typeof window !== \"undefined\" && typeof Blob !== \"undefined\" && header instanceof Blob) {\n const reader: FileReader = new FileReader();\n\n reader.onload = (event: Event): void => {\n const header: ArrayBuffer = (event.target as FileReader).result as ArrayBuffer;\n processHeader(header);\n };\n\n reader.readAsArrayBuffer(header);\n } else {\n const h: Buffer = header as Buffer;\n processHeader(h.buffer.slice(h.byteOffset, h.byteOffset + h.byteLength));\n }\n return headerResult.promise;\n }\n\n private async upload(audioNodeId: string): Promise<Stream<ArrayBuffer>> {\n const onerror = (error: string): void => {\n const errorMsg = `Error occurred while processing '${this.privFilename}'. ${error}`;\n this.onEvent(new AudioStreamNodeErrorEvent(this.privId, audioNodeId, errorMsg));\n throw new Error(errorMsg);\n };\n\n try {\n await this.turnOn();\n\n const format: AudioStreamFormatImpl = await this.privAudioFormatPromise;\n const stream = new ChunkedArrayBufferStream(format.avgBytesPerSec / 10, audioNodeId);\n\n this.privStreams[audioNodeId] = stream;\n const chunk: Blob | Buffer = this.privSource.slice(this.privHeaderEnd);\n\n const processFile = (buff: ArrayBuffer): void => {\n if (stream.isClosed) {\n return; // output stream was closed (somebody called TurnOff). We're done here.\n }\n\n stream.writeStreamChunk({\n buffer: buff,\n isEnd: false,\n timeReceived: Date.now(),\n });\n stream.close();\n };\n\n if (typeof window !== \"undefined\" && typeof Blob !== \"undefined\" && chunk instanceof Blob) {\n const reader: FileReader = new FileReader();\n reader.onerror = (ev: ProgressEvent<FileReader>): void => onerror(ev.toString());\n\n reader.onload = (event: Event): void => {\n const fileBuffer: ArrayBuffer = (event.target as FileReader).result as ArrayBuffer;\n processFile(fileBuffer);\n };\n\n reader.readAsArrayBuffer(chunk);\n } else {\n const c: Buffer = chunk as Buffer;\n processFile(c.buffer.slice(c.byteOffset, c.byteOffset + c.byteLength));\n }\n\n return stream;\n } catch (e) {\n onerror(e as string);\n }\n }\n\n private onEvent(event: AudioSourceEvent): void {\n this.privEvents.onEvent(event);\n Events.instance.onEvent(event);\n }\n}\n"]}