{"version":3,"sources":["src/common.browser/FileAudioSource.ts"],"names":[],"mappings":";AAGA,OAAO,EAEH,wBAAwB,EAE3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEH,gBAAgB,EAYhB,WAAW,EACX,YAAY,EACZ,gBAAgB,EAEhB,OAAO,EAGV,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAqB,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAE1F,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,QAAQ,CAAO;gBAEJ,IAAI,EAAE,IAAI,EAAE,aAAa,CAAC,EAAE,MAAM;IASrD,IAAW,MAAM,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAElD;IAED,IAAW,IAAI,IAAI,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAExC;IAEM,MAAM,yBAcZ;IAEM,EAAE,eAER;IAEM,MAAM,qDAqBZ;IAEM,MAAM,gCAMZ;IAEM,OAAO,yBAYb;IAED,IAAW,MAAM,IAAI,WAAW,CAAC,gBAAgB,CAAC,CAEjD;IAED,IAAW,UAAU,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAYzD;IAED,OAAO,CAAC,UAAU,CA2CjB;IAED,OAAO,CAAC,MAAM,CAwCb;IAED,OAAO,CAAC,OAAO,CAGd;CACJ","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\";\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    IStringDictionary,\n    Promise,\n    PromiseHelper,\n    Stream,\n} from \"../common/Exports\";\nimport { AudioStreamFormat, AudioStreamFormatImpl } from \"../sdk/Audio/AudioStreamFormat\";\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 privFile: File;\n\n    public constructor(file: File, audioSourceId?: string) {\n        this.privId = audioSourceId ? audioSourceId : createNoDashGuid();\n        this.privEvents = new EventSource<AudioSourceEvent>();\n        this.privFile = file;\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 get blob(): Promise<Blob | Buffer> {\n        return PromiseHelper.fromResult(this.privFile);\n    }\n\n    public turnOn = (): Promise<boolean> => {\n        if (typeof FileReader === \"undefined\") {\n            const errorMsg = \"Browser does not support FileReader.\";\n            this.onEvent(new AudioSourceErrorEvent(errorMsg, \"\")); // initialization error - no streamid at this point\n            return PromiseHelper.fromError<boolean>(errorMsg);\n        } else if (this.privFile.name.lastIndexOf(\".wav\") !== this.privFile.name.length - 4) {\n            const errorMsg = this.privFile.name + \" is not supported. Only WAVE files are allowed at the moment.\";\n            this.onEvent(new AudioSourceErrorEvent(errorMsg, \"\"));\n            return PromiseHelper.fromError<boolean>(errorMsg);\n        }\n\n        this.onEvent(new AudioSourceInitializingEvent(this.privId)); // no stream id\n        this.onEvent(new AudioSourceReadyEvent(this.privId));\n        return PromiseHelper.fromResult(true);\n    }\n\n    public id = (): string => {\n        return this.privId;\n    }\n\n    public attach = (audioNodeId: string): Promise<IAudioStreamNode> => {\n        this.onEvent(new AudioStreamNodeAttachingEvent(this.privId, audioNodeId));\n\n        return this.upload(audioNodeId).onSuccessContinueWith<IAudioStreamNode>(\n            (stream: Stream<ArrayBuffer>) => {\n                this.onEvent(new AudioStreamNodeAttachedEvent(this.privId, audioNodeId));\n                return {\n                    detach: () => {\n                        stream.readEnded();\n                        delete this.privStreams[audioNodeId];\n                        this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId));\n                        this.turnOff();\n                    },\n                    id: () => {\n                        return audioNodeId;\n                    },\n                    read: () => {\n                        return stream.read();\n                    },\n                };\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<boolean> => {\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 PromiseHelper.fromResult(true);\n    }\n\n    public get events(): EventSource<AudioSourceEvent> {\n        return this.privEvents;\n    }\n\n    public get deviceInfo(): Promise<ISpeechConfigAudioDevice> {\n        return this.privAudioFormatPromise.onSuccessContinueWithPromise<ISpeechConfigAudioDevice>((result: AudioStreamFormatImpl) => {\n            return PromiseHelper.fromResult({\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 header: Blob = this.privFile.slice(0, 44);\n        const headerReader: FileReader = new FileReader();\n\n        const headerResult: Deferred<AudioStreamFormatImpl> = new Deferred<AudioStreamFormatImpl>();\n\n        const processHeader = (event: Event): void => {\n            const header: ArrayBuffer = (event.target as FileReader).result as ArrayBuffer;\n\n            const view: DataView = new DataView(header);\n\n            // RIFF 4 bytes.\n            const riff: string = String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3));\n            if (\"RIFF\" !== riff) {\n                headerResult.reject(\"Invalid WAV header in file, RIFF was not found\");\n            }\n\n            // length, 4 bytes\n            // RIFF Type & fmt 8 bytes\n            const type: string = String.fromCharCode(\n                view.getUint8(8),\n                view.getUint8(9),\n                view.getUint8(10),\n                view.getUint8(11),\n                view.getUint8(12),\n                view.getUint8(13),\n                view.getUint8(14));\n            if (\"WAVEfmt\" !== type) {\n                headerResult.reject(\"Invalid WAV header in file, WAVEfmt was not found\");\n            }\n\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\n            headerResult.resolve(AudioStreamFormat.getWaveFormatPCM(sampleRate, bitsPerSample, channelCount) as AudioStreamFormatImpl);\n\n        };\n\n        headerReader.onload = processHeader;\n        headerReader.readAsArrayBuffer(header);\n        return headerResult.promise();\n    }\n\n    private upload = (audioNodeId: string): Promise<Stream<ArrayBuffer>> => {\n        return this.turnOn()\n            .onSuccessContinueWithPromise<Stream<ArrayBuffer>>((_: boolean) => {\n                return this.privAudioFormatPromise.onSuccessContinueWith<Stream<ArrayBuffer>>((format: AudioStreamFormatImpl) => {\n                    const fileStream: ChunkedArrayBufferStream = new ChunkedArrayBufferStream(3200);\n\n                    const reader: FileReader = new FileReader();\n\n                    const stream = new ChunkedArrayBufferStream(format.avgBytesPerSec / 10, audioNodeId);\n\n                    this.privStreams[audioNodeId] = stream;\n\n                    const processFile = (event: Event): 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: reader.result as ArrayBuffer,\n                            isEnd: false,\n                            timeReceived: Date.now(),\n                        });\n                        stream.close();\n                    };\n\n                    reader.onload = processFile;\n\n                    reader.onerror = (event: ProgressEvent) => {\n                        const errorMsg = `Error occurred while processing '${this.privFile.name}'. ${event}`;\n                        this.onEvent(new AudioStreamNodeErrorEvent(this.privId, audioNodeId, errorMsg));\n                        throw new Error(errorMsg);\n                    };\n\n                    const chunk = this.privFile.slice(44);\n                    reader.readAsArrayBuffer(chunk);\n\n                    return stream;\n                });\n            });\n\n    }\n\n    private onEvent = (event: AudioSourceEvent): void => {\n        this.privEvents.onEvent(event);\n        Events.instance.onEvent(event);\n    }\n}\n"]}