{"version":3,"sources":["src/common.speech/ServiceRecognizerBase.ts"],"names":[],"mappings":"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","file":"ServiceRecognizerBase.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT license.\n\nimport { ReplayableAudioNode } from \"../common.browser/Exports\";\nimport {\n    ArgumentNullError,\n    ConnectionClosedEvent,\n    ConnectionEvent,\n    ConnectionMessage,\n    ConnectionOpenResponse,\n    ConnectionState,\n    createGuid,\n    createNoDashGuid,\n    Deferred,\n    EventSource,\n    IAudioSource,\n    IAudioStreamNode,\n    IConnection,\n    IDisposable,\n    IStreamChunk,\n    MessageType,\n    Promise,\n    PromiseHelper,\n    PromiseResult,\n    ServiceEvent,\n    Timeout\n} from \"../common/Exports\";\nimport { AudioStreamFormatImpl } from \"../sdk/Audio/AudioStreamFormat\";\nimport {\n    CancellationErrorCode,\n    CancellationReason,\n    PropertyId,\n    RecognitionEventArgs,\n    Recognizer,\n    SessionEventArgs,\n    SpeechRecognitionResult,\n    SpeechRecognitionResultCustom,\n} from \"../sdk/Exports\";\nimport { Callback } from \"../sdk/Transcription/IConversation\";\nimport {\n    AgentConfig,\n    DynamicGrammarBuilder,\n    ISpeechConfigAudio,\n    ISpeechConfigAudioDevice,\n    RecognitionMode,\n    RequestSession,\n    SpeechContext,\n    SpeechDetected,\n} from \"./Exports\";\nimport {\n    AuthInfo,\n    IAuthentication,\n} from \"./IAuthentication\";\nimport { IConnectionFactory } from \"./IConnectionFactory\";\nimport { RecognizerConfig } from \"./RecognizerConfig\";\nimport { SpeechConnectionMessage } from \"./SpeechConnectionMessage.Internal\";\n\nexport abstract class ServiceRecognizerBase implements IDisposable {\n    private privAuthentication: IAuthentication;\n    private privConnectionFactory: IConnectionFactory;\n    private privAudioSource: IAudioSource;\n\n    // A promise for a configured connection.\n    // Do not consume directly, call fetchConnection instead.\n    private privConnectionConfigurationPromise: Promise<IConnection>;\n\n    // A promise for a connection, but one that has not had the speech context sent yet.\n    // Do not consume directly, call fetchConnection instead.\n    private privConnectionPromise: Promise<IConnection>;\n    private privAuthFetchEventId: string;\n    private privIsDisposed: boolean;\n    private privMustReportEndOfStream: boolean;\n    private privConnectionEvents: EventSource<ConnectionEvent>;\n    private privServiceEvents: EventSource<ServiceEvent>;\n    private privDynamicGrammar: DynamicGrammarBuilder;\n    private privAgentConfig: AgentConfig;\n    private privServiceHasSentMessage: boolean;\n    private privActivityTemplate: string;\n    private privSetTimeout: (cb: () => void, delay: number) => number = setTimeout;\n    protected privSpeechContext: SpeechContext;\n    protected privRequestSession: RequestSession;\n    protected privConnectionId: string;\n    protected privRecognizerConfig: RecognizerConfig;\n    protected privRecognizer: Recognizer;\n    protected privSuccessCallback: (e: SpeechRecognitionResult | SpeechRecognitionResultCustom) => void;\n    protected privErrorCallback: (e: string) => void;\n\n    public constructor(\n        authentication: IAuthentication,\n        connectionFactory: IConnectionFactory,\n        audioSource: IAudioSource,\n        recognizerConfig: RecognizerConfig,\n        recognizer: Recognizer) {\n\n        if (!authentication) {\n            throw new ArgumentNullError(\"authentication\");\n        }\n\n        if (!connectionFactory) {\n            throw new ArgumentNullError(\"connectionFactory\");\n        }\n\n        if (!audioSource) {\n            throw new ArgumentNullError(\"audioSource\");\n        }\n\n        if (!recognizerConfig) {\n            throw new ArgumentNullError(\"recognizerConfig\");\n        }\n\n        this.privMustReportEndOfStream = false;\n        this.privAuthentication = authentication;\n        this.privConnectionFactory = connectionFactory;\n        this.privAudioSource = audioSource;\n        this.privRecognizerConfig = recognizerConfig;\n        this.privIsDisposed = false;\n        this.privRecognizer = recognizer;\n        this.privRequestSession = new RequestSession(this.privAudioSource.id());\n        this.privConnectionEvents = new EventSource<ConnectionEvent>();\n        this.privServiceEvents = new EventSource<ServiceEvent>();\n        this.privDynamicGrammar = new DynamicGrammarBuilder();\n        this.privSpeechContext = new SpeechContext(this.privDynamicGrammar);\n        this.privAgentConfig = new AgentConfig();\n        if (typeof (Blob) !== \"undefined\" && typeof (Worker) !== \"undefined\") {\n            this.privSetTimeout = Timeout.setTimeout;\n        }\n    }\n\n    public get audioSource(): IAudioSource {\n        return this.privAudioSource;\n    }\n\n    public get speechContext(): SpeechContext {\n        return this.privSpeechContext;\n    }\n\n    public get dynamicGrammar(): DynamicGrammarBuilder {\n        return this.privDynamicGrammar;\n    }\n\n    public get agentConfig(): AgentConfig {\n        return this.privAgentConfig;\n    }\n\n    public set conversationTranslatorToken(token: string) {\n        this.privRecognizerConfig.parameters.setProperty(PropertyId.ConversationTranslator_Token, token);\n    }\n\n    public isDisposed(): boolean {\n        return this.privIsDisposed;\n    }\n\n    public dispose(reason?: string): void {\n        this.privIsDisposed = true;\n        if (this.privConnectionConfigurationPromise) {\n            this.privConnectionConfigurationPromise.onSuccessContinueWith((connection: IConnection) => {\n                connection.dispose(reason);\n            });\n        }\n    }\n\n    public get connectionEvents(): EventSource<ConnectionEvent> {\n        return this.privConnectionEvents;\n    }\n\n    public get serviceEvents(): EventSource<ServiceEvent> {\n        return this.privServiceEvents;\n    }\n\n    public get recognitionMode(): RecognitionMode {\n        return this.privRecognizerConfig.recognitionMode;\n    }\n\n    protected recognizeOverride: (recoMode: RecognitionMode, sc: (e: SpeechRecognitionResult) => void, ec: (e: string) => void) => any = undefined;\n\n    public recognize(\n        recoMode: RecognitionMode,\n        successCallback: (e: SpeechRecognitionResult) => void,\n        errorCallBack: (e: string) => void,\n    ): Promise<boolean> {\n\n        if (this.recognizeOverride !== undefined) {\n            return this.recognizeOverride(recoMode, successCallback, errorCallBack);\n        }\n\n        // Clear the existing configuration promise to force a re-transmission of config and context.\n        this.privConnectionConfigurationPromise = null;\n        this.privRecognizerConfig.recognitionMode = recoMode;\n\n        this.privSuccessCallback = successCallback;\n        this.privErrorCallback = errorCallBack;\n\n        this.privRequestSession.startNewRecognition();\n        this.privRequestSession.listenForServiceTelemetry(this.privAudioSource.events);\n\n        // Start the connection to the service. The promise this will create is stored and will be used by configureConnection().\n        this.connectImpl();\n\n        return this.audioSource\n            .attach(this.privRequestSession.audioNodeId)\n            .onSuccessContinueWithPromise<boolean>((result: IAudioStreamNode) => {\n                let audioNode: ReplayableAudioNode;\n\n                return this.audioSource.format.onSuccessContinueWithPromise((format: AudioStreamFormatImpl) => {\n                    audioNode = new ReplayableAudioNode(result, format.avgBytesPerSec);\n                    this.privRequestSession.onAudioSourceAttachCompleted(audioNode, false);\n\n                    return this.audioSource.deviceInfo.onSuccessContinueWithPromise<boolean>((deviceInfo: ISpeechConfigAudioDevice): Promise<boolean> => {\n                        this.privRecognizerConfig.SpeechServiceConfig.Context.audio = { source: deviceInfo };\n\n                        return this.configureConnection()\n                            .continueWithPromise<boolean>((result: PromiseResult<IConnection>): Promise<boolean> => {\n                                if (result.isError) {\n                                    this.cancelRecognitionLocal(CancellationReason.Error, CancellationErrorCode.ConnectionFailure, result.error);\n                                    return PromiseHelper.fromError(result.error);\n                                }\n\n                                const sessionStartEventArgs: SessionEventArgs = new SessionEventArgs(this.privRequestSession.sessionId);\n\n                                if (!!this.privRecognizer.sessionStarted) {\n                                    this.privRecognizer.sessionStarted(this.privRecognizer, sessionStartEventArgs);\n                                }\n\n                                const messageRetrievalPromise = this.receiveMessage();\n                                const audioSendPromise = this.sendAudio(audioNode);\n\n                                /* tslint:disable:no-empty */\n                                audioSendPromise.on((_: boolean) => { }, (error: string) => {\n                                    this.cancelRecognitionLocal(CancellationReason.Error, CancellationErrorCode.RuntimeError, error);\n                                });\n                                return PromiseHelper.fromResult(true);\n                            });\n                    });\n                });\n            });\n    }\n\n    public stopRecognizing(): Promise<boolean> {\n        if (this.privRequestSession.isRecognizing) {\n            this.audioSource.turnOff();\n            return this.sendFinalAudio().onSuccessContinueWithPromise((_: boolean) => {\n                this.privRequestSession.onStopRecognizing();\n                return this.privRequestSession.turnCompletionPromise.onSuccessContinueWith((_: boolean) => {\n                    this.privRequestSession.onStopRecognizing();\n                    this.privRequestSession.dispose();\n                    return true;\n                });\n            });\n        }\n\n        return PromiseHelper.fromResult(true);\n    }\n\n    public connect(): void {\n        this.connectImpl().result();\n    }\n\n    public connectAsync(cb?: Callback, err?: Callback): void {\n        this.connectImpl().continueWith((promiseResult: PromiseResult<IConnection>) => {\n            try {\n                if (promiseResult.isError) {\n                    if (!!err) {\n                        err(promiseResult.error);\n                    }\n                } else if (promiseResult.isCompleted) {\n                    if (!!cb) {\n                        cb();\n                    }\n                }\n            } catch (e) {\n                if (!!err) {\n                    err(e);\n                }\n            }\n        });\n    }\n\n    protected disconnectOverride: () => any = undefined;\n\n    public disconnect(): void {\n        if (this.disconnectOverride !== undefined) {\n            this.disconnectOverride();\n            return;\n        }\n\n        this.cancelRecognitionLocal(CancellationReason.Error,\n            CancellationErrorCode.NoError,\n            \"Disconnecting\");\n\n        if (this.privConnectionPromise.result().isCompleted) {\n            if (!this.privConnectionPromise.result().isError) {\n                this.privConnectionPromise.result().result.dispose();\n                this.privConnectionPromise = null;\n            }\n        } else {\n            this.privConnectionPromise.onSuccessContinueWith((connection: IConnection) => {\n                connection.dispose();\n            });\n        }\n    }\n\n    public disconnectAsync(cb?: Callback, err?: Callback): void {\n        try {\n            if (this.disconnectOverride !== undefined) {\n                this.disconnectOverride();\n                if (!!cb) {\n                    cb();\n                }\n                return;\n            }\n\n            this.cancelRecognitionLocal(CancellationReason.Error,\n                CancellationErrorCode.NoError,\n                \"Disconnecting\");\n\n            this.privConnectionPromise.continueWith((result: PromiseResult<IConnection>): void => {\n                try {\n                    if (result.isError) {\n                        if (!!err) {\n                            err(result.error);\n                        }\n                    } else if (result.isCompleted) {\n                        result.result.dispose();\n\n                        if (!!cb) {\n                            cb();\n                        }\n                    }\n                } catch (e) {\n                    if (!!err) {\n                        err(e);\n                    }\n                }\n            });\n        } catch (e) {\n            if (!!err) {\n                err(e);\n            }\n        }\n    }\n\n    // Called when telemetry data is sent to the service.\n    // Used for testing Telemetry capture.\n    public static telemetryData: (json: string) => void;\n    public static telemetryDataEnabled: boolean = true;\n\n    public sendMessage(message: string): void { }\n\n    public sendNetworkMessage(path: string, payload: string | ArrayBuffer, success?: () => void, err?: (error: string) => void): void {\n        const type: MessageType = typeof payload === \"string\" ? MessageType.Text : MessageType.Binary;\n        const contentType: string = typeof payload === \"string\" ? \"application/json\" : \"\";\n\n        this.fetchConnection().on((connection: IConnection) => {\n            connection.send(new SpeechConnectionMessage(type, path, this.privRequestSession.requestId, contentType, payload)).on(() => {\n                if (!!success) {\n                    success();\n                }\n            }, (error: string) => {\n                if (!!err) {\n                    err(error);\n                }\n            });\n        }, (error: string) => {\n            if (!!err) {\n                err(error);\n            }\n        });\n    }\n\n    public set activityTemplate(messagePayload: string) { this.privActivityTemplate = messagePayload; }\n    public get activityTemplate(): string { return this.privActivityTemplate; }\n\n    protected abstract processTypeSpecificMessages(\n        connectionMessage: SpeechConnectionMessage,\n        successCallback?: (e: SpeechRecognitionResult) => void,\n        errorCallBack?: (e: string) => void): boolean;\n\n    protected sendTelemetryData = () => {\n        const telemetryData = this.privRequestSession.getTelemetry();\n        if (ServiceRecognizerBase.telemetryDataEnabled !== true ||\n            this.privIsDisposed ||\n            null === telemetryData) {\n            return PromiseHelper.fromResult(true);\n        }\n\n        if (!!ServiceRecognizerBase.telemetryData) {\n            try {\n                ServiceRecognizerBase.telemetryData(telemetryData);\n                /* tslint:disable:no-empty */\n            } catch { }\n        }\n\n        return this.fetchConnection().onSuccessContinueWith((connection: IConnection): Promise<boolean> => {\n            return connection.send(new SpeechConnectionMessage(\n                MessageType.Text,\n                \"telemetry\",\n                this.privRequestSession.requestId,\n                \"application/json\",\n                telemetryData));\n        });\n    }\n\n    // Cancels recognition.\n    protected abstract cancelRecognition(\n        sessionId: string,\n        requestId: string,\n        cancellationReason: CancellationReason,\n        errorCode: CancellationErrorCode,\n        error: string): void;\n\n    // Cancels recognition.\n    protected cancelRecognitionLocal(\n        cancellationReason: CancellationReason,\n        errorCode: CancellationErrorCode,\n        error: string): void {\n\n        if (!!this.privRequestSession.isRecognizing) {\n            this.privRequestSession.onStopRecognizing();\n\n            this.cancelRecognition(\n                this.privRequestSession.sessionId,\n                this.privRequestSession.requestId,\n                cancellationReason,\n                errorCode,\n                error);\n        }\n    }\n\n    protected receiveMessageOverride: () => any = undefined;\n\n    protected receiveMessage = (): Promise<IConnection> => {\n        return this.fetchConnection().on((connection: IConnection): Promise<IConnection> => {\n            return connection.read()\n                .onSuccessContinueWithPromise((message: ConnectionMessage) => {\n\n                    if (this.receiveMessageOverride !== undefined) {\n                        return this.receiveMessageOverride();\n                    }\n                    if (this.privIsDisposed) {\n                        // We're done.\n                        return PromiseHelper.fromResult(undefined);\n                    }\n\n                    // indicates we are draining the queue and it came with no message;\n                    if (!message) {\n                        if (!this.privRequestSession.isRecognizing) {\n                            return PromiseHelper.fromResult(true);\n                        } else {\n                            return this.receiveMessage();\n                        }\n                    }\n\n                    this.privServiceHasSentMessage = true;\n\n                    const connectionMessage = SpeechConnectionMessage.fromConnectionMessage(message);\n\n                    if (connectionMessage.requestId.toLowerCase() === this.privRequestSession.requestId.toLowerCase()) {\n                        switch (connectionMessage.path.toLowerCase()) {\n                            case \"turn.start\":\n                                this.privMustReportEndOfStream = true;\n                                this.privRequestSession.onServiceTurnStartResponse();\n                                break;\n                            case \"speech.startdetected\":\n                                const speechStartDetected: SpeechDetected = SpeechDetected.fromJSON(connectionMessage.textBody);\n\n                                const speechStartEventArgs = new RecognitionEventArgs(speechStartDetected.Offset, this.privRequestSession.sessionId);\n\n                                if (!!this.privRecognizer.speechStartDetected) {\n                                    this.privRecognizer.speechStartDetected(this.privRecognizer, speechStartEventArgs);\n                                }\n\n                                break;\n                            case \"speech.enddetected\":\n\n                                let json: string;\n\n                                if (connectionMessage.textBody.length > 0) {\n                                    json = connectionMessage.textBody;\n                                } else {\n                                    // If the request was empty, the JSON returned is empty.\n                                    json = \"{ Offset: 0 }\";\n                                }\n\n                                const speechStopDetected: SpeechDetected = SpeechDetected.fromJSON(json);\n\n                                // Only shrink the buffers for continuous recognition.\n                                // For single shot, the speech.phrase message will come after the speech.end and it should own buffer shrink.\n                                if (this.privRecognizerConfig.isContinuousRecognition) {\n                                    this.privRequestSession.onServiceRecognized(speechStopDetected.Offset + this.privRequestSession.currentTurnAudioOffset);\n                                }\n\n                                const speechStopEventArgs = new RecognitionEventArgs(speechStopDetected.Offset + this.privRequestSession.currentTurnAudioOffset, this.privRequestSession.sessionId);\n\n                                if (!!this.privRecognizer.speechEndDetected) {\n                                    this.privRecognizer.speechEndDetected(this.privRecognizer, speechStopEventArgs);\n                                }\n                                break;\n                            case \"turn.end\":\n                                this.sendTelemetryData();\n\n                                if (this.privRequestSession.isSpeechEnded && this.privMustReportEndOfStream) {\n                                    this.privMustReportEndOfStream = false;\n                                    this.cancelRecognitionLocal(CancellationReason.EndOfStream, CancellationErrorCode.NoError, undefined);\n                                }\n\n                                const sessionStopEventArgs: SessionEventArgs = new SessionEventArgs(this.privRequestSession.sessionId);\n                                this.privRequestSession.onServiceTurnEndResponse(this.privRecognizerConfig.isContinuousRecognition);\n\n                                if (!this.privRecognizerConfig.isContinuousRecognition || this.privRequestSession.isSpeechEnded || !this.privRequestSession.isRecognizing) {\n                                    if (!!this.privRecognizer.sessionStopped) {\n                                        this.privRecognizer.sessionStopped(this.privRecognizer, sessionStopEventArgs);\n                                    }\n\n                                    return PromiseHelper.fromResult(true);\n                                } else {\n                                    this.fetchConnection().onSuccessContinueWith((connection: IConnection) => {\n                                        this.sendSpeechContext(connection);\n                                        this.sendWaveHeader(connection);\n                                    });\n                                }\n                                break;\n\n                            default:\n\n                                if (!this.processTypeSpecificMessages(connectionMessage)) {\n                                    // here are some messages that the derived class has not processed, dispatch them to connect class\n                                    if (!!this.privServiceEvents) {\n                                        this.serviceEvents.onEvent(new ServiceEvent(connectionMessage.path.toLowerCase(), connectionMessage.textBody));\n                                    }\n                                }\n\n                        }\n                    }\n\n                    return this.receiveMessage();\n                });\n        }, (error: string) => {\n        });\n    }\n\n    protected sendSpeechContext = (connection: IConnection): Promise<boolean> => {\n        const speechContextJson = this.speechContext.toJSON();\n\n        if (speechContextJson) {\n            return connection.send(new SpeechConnectionMessage(\n                MessageType.Text,\n                \"speech.context\",\n                this.privRequestSession.requestId,\n                \"application/json\",\n                speechContextJson));\n        }\n        return PromiseHelper.fromResult(true);\n    }\n\n    protected sendWaveHeader(connection: IConnection): Promise<boolean> {\n        return this.audioSource.format.onSuccessContinueWithPromise((format: AudioStreamFormatImpl) => {\n            // this.writeBufferToConsole(format.header);\n            return connection.send(new SpeechConnectionMessage(\n                MessageType.Binary,\n                \"audio\",\n                this.privRequestSession.requestId,\n                \"audio/x-wav\",\n                format.header\n            ));\n        });\n    }\n\n    protected connectImplOverride: (isUnAuthorized: boolean) => any = undefined;\n\n    // Establishes a websocket connection to the end point.\n    protected connectImpl(isUnAuthorized: boolean = false): Promise<IConnection> {\n\n        if (this.connectImplOverride !== undefined) {\n            return this.connectImplOverride(isUnAuthorized);\n        }\n\n        if (this.privConnectionPromise) {\n            if (this.privConnectionPromise.result().isCompleted &&\n                (this.privConnectionPromise.result().isError\n                    || this.privConnectionPromise.result().result.state() === ConnectionState.Disconnected) &&\n                this.privServiceHasSentMessage === true) {\n                this.privConnectionId = null;\n                this.privConnectionPromise = null;\n                this.privServiceHasSentMessage = false;\n                return this.connectImpl();\n            } else {\n                return this.privConnectionPromise;\n            }\n        }\n\n        this.privAuthFetchEventId = createNoDashGuid();\n        this.privConnectionId = createNoDashGuid();\n\n        this.privRequestSession.onPreConnectionStart(this.privAuthFetchEventId, this.privConnectionId);\n\n        const authPromise = isUnAuthorized ? this.privAuthentication.fetchOnExpiry(this.privAuthFetchEventId) : this.privAuthentication.fetch(this.privAuthFetchEventId);\n\n        this.privConnectionPromise = authPromise\n            .continueWithPromise((result: PromiseResult<AuthInfo>) => {\n                if (result.isError) {\n                    this.privRequestSession.onAuthCompleted(true, result.error);\n                    throw new Error(result.error);\n                } else {\n                    this.privRequestSession.onAuthCompleted(false);\n                }\n\n                const connection: IConnection = this.privConnectionFactory.create(this.privRecognizerConfig, result.result, this.privConnectionId);\n\n                this.privRequestSession.listenForServiceTelemetry(connection.events);\n\n                // Attach to the underlying event. No need to hold onto the detach pointers as in the event the connection goes away,\n                // it'll stop sending events.\n                connection.events.attach((event: ConnectionEvent) => {\n                    this.connectionEvents.onEvent(event);\n                });\n\n                return connection.open().onSuccessContinueWithPromise((response: ConnectionOpenResponse): Promise<IConnection> => {\n                    if (response.statusCode === 200) {\n                        this.privRequestSession.onPreConnectionStart(this.privAuthFetchEventId, this.privConnectionId);\n                        this.privRequestSession.onConnectionEstablishCompleted(response.statusCode);\n\n                        return PromiseHelper.fromResult<IConnection>(connection);\n                    } else if (response.statusCode === 403 && !isUnAuthorized) {\n                        return this.connectImpl(true);\n                    } else {\n                        this.privRequestSession.onConnectionEstablishCompleted(response.statusCode, response.reason);\n                        return PromiseHelper.fromError<IConnection>(`Unable to contact server. StatusCode: ${response.statusCode}, ${this.privRecognizerConfig.parameters.getProperty(PropertyId.SpeechServiceConnection_Endpoint)} Reason: ${response.reason}`);\n                    }\n                });\n            });\n\n        return this.privConnectionPromise;\n    }\n\n    protected configConnectionOverride: () => any = undefined;\n\n    protected fetchConnectionOverride: () => any = undefined;\n\n    protected sendSpeechServiceConfig = (connection: IConnection, requestSession: RequestSession, SpeechServiceConfigJson: string): Promise<boolean> => {\n        // filter out anything that is not required for the service to work.\n        if (ServiceRecognizerBase.telemetryDataEnabled !== true) {\n            const withTelemetry = JSON.parse(SpeechServiceConfigJson);\n\n            const replacement: any = {\n                context: {\n                    system: withTelemetry.context.system,\n                },\n            };\n\n            SpeechServiceConfigJson = JSON.stringify(replacement);\n        }\n\n        if (SpeechServiceConfigJson) { // && this.privConnectionId !== this.privSpeechServiceConfigConnectionId) {\n            //  this.privSpeechServiceConfigConnectionId = this.privConnectionId;\n            return connection.send(new SpeechConnectionMessage(\n                MessageType.Text,\n                \"speech.config\",\n                requestSession.requestId,\n                \"application/json\",\n                SpeechServiceConfigJson));\n        }\n\n        return PromiseHelper.fromResult(true);\n    }\n\n    protected sendAudio = (\n        audioStreamNode: IAudioStreamNode): Promise<boolean> => {\n        return this.audioSource.format.onSuccessContinueWithPromise((audioFormat: AudioStreamFormatImpl) => {\n            // NOTE: Home-baked promises crash ios safari during the invocation\n            // of the error callback chain (looks like the recursion is way too deep, and\n            // it blows up the stack). The following construct is a stop-gap that does not\n            // bubble the error up the callback chain and hence circumvents this problem.\n            // TODO: rewrite with ES6 promises.\n            const deferred = new Deferred<boolean>();\n\n            // The time we last sent data to the service.\n            let nextSendTime: number = Date.now();\n\n            // Max amount to send before we start to throttle\n            const fastLaneSizeMs: string = this.privRecognizerConfig.parameters.getProperty(\"SPEECH-TransmitLengthBeforThrottleMs\", \"5000\");\n            const maxSendUnthrottledBytes: number = audioFormat.avgBytesPerSec / 1000 * parseInt(fastLaneSizeMs, 10);\n            const startRecogNumber: number = this.privRequestSession.recogNumber;\n\n            const readAndUploadCycle = () => {\n\n                // If speech is done, stop sending audio.\n                if (!this.privIsDisposed &&\n                    !this.privRequestSession.isSpeechEnded &&\n                    this.privRequestSession.isRecognizing &&\n                    this.privRequestSession.recogNumber === startRecogNumber) {\n                    this.fetchConnection().on((connection: IConnection) => {\n                        audioStreamNode.read().on(\n                            (audioStreamChunk: IStreamChunk<ArrayBuffer>) => {\n                                // we have a new audio chunk to upload.\n                                if (this.privRequestSession.isSpeechEnded) {\n                                    // If service already recognized audio end then don't send any more audio\n                                    deferred.resolve(true);\n                                    return;\n                                }\n\n                                let payload: ArrayBuffer;\n                                let sendDelay: number;\n\n                                if (!audioStreamChunk || audioStreamChunk.isEnd) {\n                                    payload = null;\n                                    sendDelay = 0;\n                                } else {\n                                    payload = audioStreamChunk.buffer;\n                                    this.privRequestSession.onAudioSent(payload.byteLength);\n\n                                    if (maxSendUnthrottledBytes >= this.privRequestSession.bytesSent) {\n                                        sendDelay = 0;\n                                    } else {\n                                        sendDelay = Math.max(0, nextSendTime - Date.now());\n                                    }\n                                }\n\n                                // Are we ready to send, or need we delay more?\n                                this.privSetTimeout(() => {\n                                    if (payload !== null) {\n                                        nextSendTime = Date.now() + (payload.byteLength * 1000 / (audioFormat.avgBytesPerSec * 2));\n                                    }\n\n                                    const uploaded: Promise<boolean> = connection.send(\n                                        new SpeechConnectionMessage(\n                                            MessageType.Binary, \"audio\", this.privRequestSession.requestId, null, payload));\n\n                                    if (!audioStreamChunk?.isEnd) {\n                                        uploaded.continueWith((_: PromiseResult<boolean>) => {\n                                            // this.writeBufferToConsole(payload);\n                                            // Regardless of success or failure, schedule the next upload.\n                                            // If the underlying connection was broken, the next cycle will\n                                            // get a new connection and re-transmit missing audio automatically.\n                                            readAndUploadCycle();\n                                        });\n                                    } else {\n                                        // the audio stream has been closed, no need to schedule next\n                                        // read-upload cycle.\n                                        this.privRequestSession.onSpeechEnded();\n                                        deferred.resolve(true);\n                                    }\n                                }, sendDelay);\n                            },\n                            (error: string) => {\n                                if (this.privRequestSession.isSpeechEnded) {\n                                    // For whatever reason, Reject is used to remove queue subscribers inside\n                                    // the Queue.DrainAndDispose invoked from DetachAudioNode down below, which\n                                    // means that sometimes things can be rejected in normal circumstances, without\n                                    // any errors.\n                                    deferred.resolve(true); // TODO: remove the argument, it's is completely meaningless.\n                                } else {\n                                    // Only reject, if there was a proper error.\n                                    deferred.reject(error);\n                                }\n                            });\n                    }, (error: string) => {\n                        deferred.reject(error);\n                    });\n                }\n            };\n\n            readAndUploadCycle();\n\n            return deferred.promise();\n        });\n    }\n\n    private writeBufferToConsole(buffer: ArrayBuffer): void {\n        let out: string = \"Buffer Size: \";\n        if (null === buffer) {\n            out += \"null\";\n        } else {\n            const readView: Uint8Array = new Uint8Array(buffer);\n            out += buffer.byteLength + \"\\r\\n\";\n            for (let i: number = 0; i < buffer.byteLength; i++) {\n                out += readView[i].toString(16).padStart(2, \"0\") + \" \";\n            }\n        }\n        // tslint:disable-next-line:no-console\n        console.info(out);\n    }\n\n    private sendFinalAudio(): Promise<boolean> {\n        const deferred = new Deferred<boolean>();\n\n        this.fetchConnection().on((connection: IConnection) => {\n            connection.send(new SpeechConnectionMessage(MessageType.Binary, \"audio\", this.privRequestSession.requestId, null, null)).on((_: boolean) => {\n                deferred.resolve(true);\n            }, (error: string) => {\n                deferred.reject(error);\n            });\n        }, (error: string) => {\n            deferred.reject(error);\n        });\n\n        return deferred.promise();\n    }\n\n    private fetchConnection = (): Promise<IConnection> => {\n        if (this.fetchConnectionOverride !== undefined) {\n            return this.fetchConnectionOverride();\n        }\n\n        return this.configureConnection();\n    }\n\n    // Takes an established websocket connection to the endpoint and sends speech configuration information.\n    private configureConnection(): Promise<IConnection> {\n        if (this.configConnectionOverride !== undefined) {\n            return this.configConnectionOverride();\n        }\n\n        if (this.privConnectionConfigurationPromise) {\n            if (this.privConnectionConfigurationPromise.result().isCompleted &&\n                (this.privConnectionConfigurationPromise.result().isError\n                    || this.privConnectionConfigurationPromise.result().result.state() === ConnectionState.Disconnected)) {\n\n                this.privConnectionConfigurationPromise = null;\n                return this.configureConnection();\n            } else {\n                return this.privConnectionConfigurationPromise;\n            }\n        }\n\n        this.privConnectionConfigurationPromise = this.connectImpl().onSuccessContinueWithPromise((connection: IConnection): Promise<IConnection> => {\n            return this.sendSpeechServiceConfig(connection, this.privRequestSession, this.privRecognizerConfig.SpeechServiceConfig.serialize())\n                .onSuccessContinueWithPromise((_: boolean) => {\n                    return this.sendSpeechContext(connection).onSuccessContinueWithPromise((_: boolean) => {\n                        return this.sendWaveHeader(connection).onSuccessContinueWith((_: boolean) => {\n                            return connection;\n                        });\n                    });\n                });\n        });\n\n        return this.privConnectionConfigurationPromise;\n    }\n}\n"]}