{"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.\r\n// Licensed under the MIT license.\r\n\r\nimport { ReplayableAudioNode } from \"../common.browser/Exports\";\r\nimport { ConnectionOpenResponse } from \"../common/ConnectionOpenResponse\";\r\nimport {\r\n    ArgumentNullError,\r\n    ConnectionClosedEvent,\r\n    ConnectionEvent,\r\n    ConnectionState,\r\n    createNoDashGuid,\r\n    EventSource,\r\n    IAudioSource,\r\n    IAudioStreamNode,\r\n    IConnection,\r\n    IDisposable,\r\n    IStreamChunk,\r\n    MessageType,\r\n    ServiceEvent,\r\n    Timeout\r\n} from \"../common/Exports\";\r\nimport { AudioStreamFormatImpl } from \"../sdk/Audio/AudioStreamFormat\";\r\nimport {\r\n    CancellationErrorCode,\r\n    CancellationReason,\r\n    PropertyId,\r\n    RecognitionEventArgs,\r\n    Recognizer,\r\n    SessionEventArgs,\r\n    SpeechRecognitionResult,\r\n} from \"../sdk/Exports\";\r\nimport { Callback } from \"../sdk/Transcription/IConversation\";\r\nimport {\r\n    AgentConfig,\r\n    DynamicGrammarBuilder,\r\n    ISpeechConfigAudioDevice,\r\n    RecognitionMode,\r\n    RequestSession,\r\n    SpeechContext,\r\n    SpeechDetected,\r\n    type\r\n} from \"./Exports\";\r\nimport {\r\n    AuthInfo,\r\n    IAuthentication,\r\n} from \"./IAuthentication\";\r\nimport { IConnectionFactory } from \"./IConnectionFactory\";\r\nimport { RecognizerConfig } from \"./RecognizerConfig\";\r\nimport { SpeechConnectionMessage } from \"./SpeechConnectionMessage.Internal\";\r\n\r\nexport abstract class ServiceRecognizerBase implements IDisposable {\r\n    private privAuthentication: IAuthentication;\r\n    private privConnectionFactory: IConnectionFactory;\r\n\r\n    // A promise for a configured connection.\r\n    // Do not consume directly, call fetchConnection instead.\r\n    private privConnectionConfigurationPromise: Promise<IConnection>;\r\n\r\n    // A promise for a connection, but one that has not had the speech context sent yet.\r\n    // Do not consume directly, call fetchConnection instead.\r\n    private privConnectionPromise: Promise<IConnection>;\r\n    private privAuthFetchEventId: string;\r\n    private privIsDisposed: boolean;\r\n    private privMustReportEndOfStream: boolean;\r\n    private privConnectionEvents: EventSource<ConnectionEvent>;\r\n    private privServiceEvents: EventSource<ServiceEvent>;\r\n    private privDynamicGrammar: DynamicGrammarBuilder;\r\n    private privAgentConfig: AgentConfig;\r\n    private privServiceHasSentMessage: boolean;\r\n    private privActivityTemplate: string;\r\n    private privSetTimeout: (cb: () => void, delay: number) => number = setTimeout;\r\n    private privAudioSource: IAudioSource;\r\n    private privIsLiveAudio: boolean = false;\r\n    protected privSpeechContext: SpeechContext;\r\n    protected privRequestSession: RequestSession;\r\n    protected privConnectionId: string;\r\n    protected privRecognizerConfig: RecognizerConfig;\r\n    protected privRecognizer: Recognizer;\r\n    protected privSuccessCallback: (e: SpeechRecognitionResult) => void;\r\n    protected privErrorCallback: (e: string) => void;\r\n\r\n    public constructor(\r\n        authentication: IAuthentication,\r\n        connectionFactory: IConnectionFactory,\r\n        audioSource: IAudioSource,\r\n        recognizerConfig: RecognizerConfig,\r\n        recognizer: Recognizer) {\r\n\r\n        if (!authentication) {\r\n            throw new ArgumentNullError(\"authentication\");\r\n        }\r\n\r\n        if (!connectionFactory) {\r\n            throw new ArgumentNullError(\"connectionFactory\");\r\n        }\r\n\r\n        if (!audioSource) {\r\n            throw new ArgumentNullError(\"audioSource\");\r\n        }\r\n\r\n        if (!recognizerConfig) {\r\n            throw new ArgumentNullError(\"recognizerConfig\");\r\n        }\r\n\r\n        this.privMustReportEndOfStream = false;\r\n        this.privAuthentication = authentication;\r\n        this.privConnectionFactory = connectionFactory;\r\n        this.privAudioSource = audioSource;\r\n        this.privRecognizerConfig = recognizerConfig;\r\n        this.privIsDisposed = false;\r\n        this.privRecognizer = recognizer;\r\n        this.privRequestSession = new RequestSession(this.privAudioSource.id());\r\n        this.privConnectionEvents = new EventSource<ConnectionEvent>();\r\n        this.privServiceEvents = new EventSource<ServiceEvent>();\r\n        this.privDynamicGrammar = new DynamicGrammarBuilder();\r\n        this.privSpeechContext = new SpeechContext(this.privDynamicGrammar);\r\n        this.privAgentConfig = new AgentConfig();\r\n        if (typeof (Blob) !== \"undefined\" && typeof (Worker) !== \"undefined\") {\r\n            this.privSetTimeout = Timeout.setTimeout;\r\n        }\r\n\r\n        this.connectionEvents.attach(async (connectionEvent: ConnectionEvent): Promise<void> => {\r\n            if (connectionEvent.name === \"ConnectionClosedEvent\") {\r\n                const connectionClosedEvent = connectionEvent as ConnectionClosedEvent;\r\n                if (connectionClosedEvent.statusCode === 1003 ||\r\n                    connectionClosedEvent.statusCode === 1007 ||\r\n                    connectionClosedEvent.statusCode === 1002 ||\r\n                    connectionClosedEvent.statusCode === 4000 ||\r\n                    this.privRequestSession.numConnectionAttempts > this.privRecognizerConfig.maxRetryCount\r\n                ) {\r\n                    await this.cancelRecognitionLocal(CancellationReason.Error,\r\n                        connectionClosedEvent.statusCode === 1007 ? CancellationErrorCode.BadRequestParameters : CancellationErrorCode.ConnectionFailure,\r\n                        connectionClosedEvent.reason + \" websocket error code: \" + connectionClosedEvent.statusCode);\r\n                }\r\n            }\r\n        });\r\n    }\r\n\r\n    public get audioSource(): IAudioSource {\r\n        return this.privAudioSource;\r\n    }\r\n\r\n    public get speechContext(): SpeechContext {\r\n        return this.privSpeechContext;\r\n    }\r\n\r\n    public get dynamicGrammar(): DynamicGrammarBuilder {\r\n        return this.privDynamicGrammar;\r\n    }\r\n\r\n    public get agentConfig(): AgentConfig {\r\n        return this.privAgentConfig;\r\n    }\r\n\r\n    public set conversationTranslatorToken(token: string) {\r\n        this.privRecognizerConfig.parameters.setProperty(PropertyId.ConversationTranslator_Token, token);\r\n    }\r\n\r\n    public set authentication(auth: IAuthentication) {\r\n        this.privAuthentication = this.authentication;\r\n    }\r\n\r\n    public isDisposed(): boolean {\r\n        return this.privIsDisposed;\r\n    }\r\n\r\n    public async dispose(reason?: string): Promise<void> {\r\n        this.privIsDisposed = true;\r\n        if (this.privConnectionConfigurationPromise) {\r\n            try {\r\n                const connection: IConnection = await this.privConnectionConfigurationPromise;\r\n                await connection.dispose(reason);\r\n            } catch (error) {\r\n                // The connection is in a bad state. But we're trying to kill it, so...\r\n                return;\r\n            }\r\n        }\r\n    }\r\n\r\n    public get connectionEvents(): EventSource<ConnectionEvent> {\r\n        return this.privConnectionEvents;\r\n    }\r\n\r\n    public get serviceEvents(): EventSource<ServiceEvent> {\r\n        return this.privServiceEvents;\r\n    }\r\n\r\n    public get recognitionMode(): RecognitionMode {\r\n        return this.privRecognizerConfig.recognitionMode;\r\n    }\r\n\r\n    protected recognizeOverride: (recoMode: RecognitionMode, sc: (e: SpeechRecognitionResult) => void, ec: (e: string) => void) => any = undefined;\r\n\r\n    public async recognize(\r\n        recoMode: RecognitionMode,\r\n        successCallback: (e: SpeechRecognitionResult) => void,\r\n        errorCallBack: (e: string) => void,\r\n    ): Promise<void> {\r\n\r\n        if (this.recognizeOverride !== undefined) {\r\n            return this.recognizeOverride(recoMode, successCallback, errorCallBack);\r\n        }\r\n        // Clear the existing configuration promise to force a re-transmission of config and context.\r\n        this.privConnectionConfigurationPromise = null;\r\n        this.privRecognizerConfig.recognitionMode = recoMode;\r\n\r\n        this.privSuccessCallback = successCallback;\r\n        this.privErrorCallback = errorCallBack;\r\n\r\n        this.privRequestSession.startNewRecognition();\r\n        this.privRequestSession.listenForServiceTelemetry(this.privAudioSource.events);\r\n\r\n        // Start the connection to the service. The promise this will create is stored and will be used by configureConnection().\r\n        const conPromise: Promise<IConnection> = this.connectImpl();\r\n        let audioNode: ReplayableAudioNode;\r\n\r\n        try {\r\n            const audioStreamNode: IAudioStreamNode = await this.audioSource.attach(this.privRequestSession.audioNodeId);\r\n            const format: AudioStreamFormatImpl = await this.audioSource.format;\r\n            const deviceInfo: ISpeechConfigAudioDevice = await this.audioSource.deviceInfo;\r\n            this.privIsLiveAudio = deviceInfo.type && deviceInfo.type === type.Microphones;\r\n\r\n            audioNode = new ReplayableAudioNode(audioStreamNode, format.avgBytesPerSec);\r\n            await this.privRequestSession.onAudioSourceAttachCompleted(audioNode, false);\r\n            this.privRecognizerConfig.SpeechServiceConfig.Context.audio = { source: deviceInfo };\r\n\r\n        } catch (error) {\r\n            await this.privRequestSession.onStopRecognizing();\r\n            throw error;\r\n        }\r\n\r\n        try {\r\n            await conPromise;\r\n        } catch (error) {\r\n            await this.cancelRecognitionLocal(CancellationReason.Error, CancellationErrorCode.ConnectionFailure, error);\r\n            return;\r\n        }\r\n\r\n        const sessionStartEventArgs: SessionEventArgs = new SessionEventArgs(this.privRequestSession.sessionId);\r\n\r\n        if (!!this.privRecognizer.sessionStarted) {\r\n            this.privRecognizer.sessionStarted(this.privRecognizer, sessionStartEventArgs);\r\n        }\r\n\r\n        const messageRetrievalPromise = this.receiveMessage();\r\n        const audioSendPromise = this.sendAudio(audioNode);\r\n\r\n        audioSendPromise.catch(async (error: string) => {\r\n            await this.cancelRecognitionLocal(CancellationReason.Error, CancellationErrorCode.RuntimeError, error);\r\n        });\r\n\r\n        return;\r\n    }\r\n\r\n    public async stopRecognizing(): Promise<void> {\r\n        if (this.privRequestSession.isRecognizing) {\r\n            try {\r\n                await this.audioSource.turnOff();\r\n                await this.sendFinalAudio();\r\n                await this.privRequestSession.onStopRecognizing();\r\n                await this.privRequestSession.turnCompletionPromise;\r\n            } finally {\r\n                await this.privRequestSession.dispose();\r\n            }\r\n        }\r\n        return;\r\n    }\r\n\r\n    public async connect(): Promise<void> {\r\n        await this.connectImpl();\r\n        return Promise.resolve();\r\n    }\r\n\r\n    public connectAsync(cb?: Callback, err?: Callback): void {\r\n        this.connectImpl().then((connection: IConnection): void => {\r\n            try {\r\n                if (!!cb) {\r\n                    cb();\r\n                }\r\n            } catch (e) {\r\n                if (!!err) {\r\n                    err(e);\r\n                }\r\n            }\r\n        }, (reason: any): void => {\r\n            try {\r\n                if (!!err) {\r\n                    err(reason);\r\n                }\r\n                /* tslint:disable:no-empty */\r\n            } catch (error) {\r\n            }\r\n        });\r\n    }\r\n\r\n    protected disconnectOverride: () => Promise<void> = undefined;\r\n\r\n    public async disconnect(): Promise<void> {\r\n        await this.cancelRecognitionLocal(CancellationReason.Error,\r\n            CancellationErrorCode.NoError,\r\n            \"Disconnecting\");\r\n\r\n        if (this.disconnectOverride !== undefined) {\r\n            await this.disconnectOverride();\r\n        }\r\n\r\n        try {\r\n            await (await this.privConnectionPromise).dispose();\r\n        } catch (error) {\r\n\r\n        }\r\n\r\n        this.privConnectionPromise = null;\r\n    }\r\n\r\n    // Called when telemetry data is sent to the service.\r\n    // Used for testing Telemetry capture.\r\n    public static telemetryData: (json: string) => void;\r\n    public static telemetryDataEnabled: boolean = true;\r\n\r\n    public sendMessage(message: string): void { }\r\n\r\n    public async sendNetworkMessage(path: string, payload: string | ArrayBuffer): Promise<void> {\r\n        const type: MessageType = typeof payload === \"string\" ? MessageType.Text : MessageType.Binary;\r\n        const contentType: string = typeof payload === \"string\" ? \"application/json\" : \"\";\r\n\r\n        const connection: IConnection = await this.fetchConnection();\r\n        return connection.send(new SpeechConnectionMessage(type, path, this.privRequestSession.requestId, contentType, payload));\r\n    }\r\n\r\n    public set activityTemplate(messagePayload: string) { this.privActivityTemplate = messagePayload; }\r\n    public get activityTemplate(): string { return this.privActivityTemplate; }\r\n\r\n    protected abstract processTypeSpecificMessages(\r\n        connectionMessage: SpeechConnectionMessage,\r\n        successCallback?: (e: SpeechRecognitionResult) => void,\r\n        errorCallBack?: (e: string) => void): Promise<boolean>;\r\n\r\n    protected async sendTelemetryData(): Promise<void> {\r\n        const telemetryData = this.privRequestSession.getTelemetry();\r\n        if (ServiceRecognizerBase.telemetryDataEnabled !== true ||\r\n            this.privIsDisposed ||\r\n            null === telemetryData) {\r\n            return;\r\n        }\r\n\r\n        if (!!ServiceRecognizerBase.telemetryData) {\r\n            try {\r\n                ServiceRecognizerBase.telemetryData(telemetryData);\r\n                /* tslint:disable:no-empty */\r\n            } catch { }\r\n        }\r\n\r\n        const connection: IConnection = await this.fetchConnection();\r\n        await connection.send(new SpeechConnectionMessage(\r\n            MessageType.Text,\r\n            \"telemetry\",\r\n            this.privRequestSession.requestId,\r\n            \"application/json\",\r\n            telemetryData));\r\n    }\r\n\r\n    // Cancels recognition.\r\n    protected abstract cancelRecognition(\r\n        sessionId: string,\r\n        requestId: string,\r\n        cancellationReason: CancellationReason,\r\n        errorCode: CancellationErrorCode,\r\n        error: string): void;\r\n\r\n    // Cancels recognition.\r\n    protected async cancelRecognitionLocal(\r\n        cancellationReason: CancellationReason,\r\n        errorCode: CancellationErrorCode,\r\n        error: string): Promise<void> {\r\n\r\n        if (!!this.privRequestSession.isRecognizing) {\r\n            await this.privRequestSession.onStopRecognizing();\r\n\r\n            this.cancelRecognition(\r\n                this.privRequestSession.sessionId,\r\n                this.privRequestSession.requestId,\r\n                cancellationReason,\r\n                errorCode,\r\n                error);\r\n        }\r\n    }\r\n\r\n    protected receiveMessageOverride: () => Promise<void> = undefined;\r\n\r\n    protected async receiveMessage(): Promise<void> {\r\n        try {\r\n            if (this.privIsDisposed) {\r\n                // We're done.\r\n                return;\r\n            }\r\n\r\n            let connection = await this.fetchConnection();\r\n            const message = await connection.read();\r\n\r\n            if (this.receiveMessageOverride !== undefined) {\r\n                return this.receiveMessageOverride();\r\n            }\r\n\r\n            // indicates we are draining the queue and it came with no message;\r\n            if (!message) {\r\n                if (!this.privRequestSession.isRecognizing) {\r\n                    return;\r\n                } else {\r\n                    return this.receiveMessage();\r\n                }\r\n            }\r\n\r\n            this.privServiceHasSentMessage = true;\r\n            const connectionMessage = SpeechConnectionMessage.fromConnectionMessage(message);\r\n\r\n            if (connectionMessage.requestId.toLowerCase() === this.privRequestSession.requestId.toLowerCase()) {\r\n                switch (connectionMessage.path.toLowerCase()) {\r\n                    case \"turn.start\":\r\n                        this.privMustReportEndOfStream = true;\r\n                        this.privRequestSession.onServiceTurnStartResponse();\r\n                        break;\r\n\r\n                    case \"speech.startdetected\":\r\n                        const speechStartDetected: SpeechDetected = SpeechDetected.fromJSON(connectionMessage.textBody);\r\n                        const speechStartEventArgs = new RecognitionEventArgs(speechStartDetected.Offset, this.privRequestSession.sessionId);\r\n                        if (!!this.privRecognizer.speechStartDetected) {\r\n                            this.privRecognizer.speechStartDetected(this.privRecognizer, speechStartEventArgs);\r\n                        }\r\n                        break;\r\n\r\n                    case \"speech.enddetected\":\r\n                        let json: string;\r\n                        if (connectionMessage.textBody.length > 0) {\r\n                            json = connectionMessage.textBody;\r\n                        } else {\r\n                            // If the request was empty, the JSON returned is empty.\r\n                            json = \"{ Offset: 0 }\";\r\n                        }\r\n                        const speechStopDetected: SpeechDetected = SpeechDetected.fromJSON(json);\r\n                        // Only shrink the buffers for continuous recognition.\r\n                        // For single shot, the speech.phrase message will come after the speech.end and it should own buffer shrink.\r\n                        if (this.privRecognizerConfig.isContinuousRecognition) {\r\n                            this.privRequestSession.onServiceRecognized(speechStopDetected.Offset + this.privRequestSession.currentTurnAudioOffset);\r\n                        }\r\n                        const speechStopEventArgs = new RecognitionEventArgs(speechStopDetected.Offset + this.privRequestSession.currentTurnAudioOffset, this.privRequestSession.sessionId);\r\n                        if (!!this.privRecognizer.speechEndDetected) {\r\n                            this.privRecognizer.speechEndDetected(this.privRecognizer, speechStopEventArgs);\r\n                        }\r\n                        break;\r\n\r\n                    case \"turn.end\":\r\n                        await this.sendTelemetryData();\r\n                        if (this.privRequestSession.isSpeechEnded && this.privMustReportEndOfStream) {\r\n                            this.privMustReportEndOfStream = false;\r\n                            await this.cancelRecognitionLocal(CancellationReason.EndOfStream, CancellationErrorCode.NoError, undefined);\r\n                        }\r\n                        const sessionStopEventArgs: SessionEventArgs = new SessionEventArgs(this.privRequestSession.sessionId);\r\n                        await this.privRequestSession.onServiceTurnEndResponse(this.privRecognizerConfig.isContinuousRecognition);\r\n                        if (!this.privRecognizerConfig.isContinuousRecognition || this.privRequestSession.isSpeechEnded || !this.privRequestSession.isRecognizing) {\r\n                            if (!!this.privRecognizer.sessionStopped) {\r\n                                this.privRecognizer.sessionStopped(this.privRecognizer, sessionStopEventArgs);\r\n                            }\r\n                            return;\r\n                        } else {\r\n                            connection = await this.fetchConnection();\r\n                            await this.sendPrePayloadJSON(connection);\r\n                        }\r\n                        break;\r\n\r\n                    default:\r\n                        if (!await this.processTypeSpecificMessages(connectionMessage)) {\r\n                            // here are some messages that the derived class has not processed, dispatch them to connect class\r\n                            if (!!this.privServiceEvents) {\r\n                                this.serviceEvents.onEvent(new ServiceEvent(connectionMessage.path.toLowerCase(), connectionMessage.textBody));\r\n                            }\r\n                        }\r\n                }\r\n            }\r\n            return this.receiveMessage();\r\n        } catch (error) {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    protected sendSpeechContext = (connection: IConnection): Promise<void> => {\r\n        const speechContextJson = this.speechContext.toJSON();\r\n        this.privRequestSession.onSpeechContext();\r\n\r\n        if (speechContextJson) {\r\n            return connection.send(new SpeechConnectionMessage(\r\n                MessageType.Text,\r\n                \"speech.context\",\r\n                this.privRequestSession.requestId,\r\n                \"application/json\",\r\n                speechContextJson));\r\n        }\r\n        return;\r\n    }\r\n\r\n    protected sendPrePayloadJSONOverride: (connection: IConnection) => Promise<void> = undefined;\r\n\r\n    // Encapsulated for derived service recognizers that need to send additional JSON\r\n    protected async sendPrePayloadJSON(connection: IConnection): Promise<void> {\r\n        if (this.sendPrePayloadJSONOverride !== undefined) {\r\n            return this.sendPrePayloadJSONOverride(connection);\r\n        }\r\n\r\n        await this.sendSpeechContext(connection);\r\n        await this.sendWaveHeader(connection);\r\n        return;\r\n    }\r\n\r\n    protected async sendWaveHeader(connection: IConnection): Promise<void> {\r\n        const format: AudioStreamFormatImpl = await this.audioSource.format;\r\n        // this.writeBufferToConsole(format.header);\r\n        return connection.send(new SpeechConnectionMessage(\r\n            MessageType.Binary,\r\n            \"audio\",\r\n            this.privRequestSession.requestId,\r\n            \"audio/x-wav\",\r\n            format.header\r\n        ));\r\n    }\r\n\r\n    protected postConnectImplOverride: (connection: Promise<IConnection>) => Promise<IConnection> = undefined;\r\n\r\n    // Establishes a websocket connection to the end point.\r\n    protected connectImpl(): Promise<IConnection> {\r\n        if (this.privConnectionPromise) {\r\n            return this.privConnectionPromise.then((connection: IConnection): Promise<IConnection> => {\r\n                if (connection.state() === ConnectionState.Disconnected) {\r\n                    this.privConnectionId = null;\r\n                    this.privConnectionPromise = null;\r\n                    this.privServiceHasSentMessage = false;\r\n                    return this.connectImpl();\r\n                }\r\n                return this.privConnectionPromise;\r\n            }, (error: string): Promise<IConnection> => {\r\n                this.privConnectionId = null;\r\n                this.privConnectionPromise = null;\r\n                this.privServiceHasSentMessage = false;\r\n                return this.connectImpl();\r\n            });\r\n        }\r\n\r\n        this.privConnectionPromise = this.retryableConnect();\r\n\r\n        // Attach an empty handler to allow the promise to run in the background while\r\n        // other startup events happen. It'll eventually be awaited on.\r\n        this.privConnectionPromise.catch(() => { });\r\n\r\n        if (this.postConnectImplOverride !== undefined) {\r\n            return this.postConnectImplOverride(this.privConnectionPromise);\r\n        }\r\n\r\n        return this.privConnectionPromise;\r\n    }\r\n\r\n    protected configConnectionOverride: (connection: IConnection) => Promise<IConnection> = undefined;\r\n\r\n    protected sendSpeechServiceConfig = (connection: IConnection, requestSession: RequestSession, SpeechServiceConfigJson: string): Promise<void> => {\r\n        // filter out anything that is not required for the service to work.\r\n        if (ServiceRecognizerBase.telemetryDataEnabled !== true) {\r\n            const withTelemetry = JSON.parse(SpeechServiceConfigJson);\r\n\r\n            const replacement: any = {\r\n                context: {\r\n                    system: withTelemetry.context.system,\r\n                },\r\n            };\r\n\r\n            SpeechServiceConfigJson = JSON.stringify(replacement);\r\n        }\r\n\r\n        if (this.privRecognizerConfig.parameters.getProperty(\"TranscriptionService_SingleChannel\", \"false\").toLowerCase() === \"true\") {\r\n            const json: { context: any } = JSON.parse(SpeechServiceConfigJson);\r\n            json.context.DisableReferenceChannel = \"True\";\r\n            json.context.MicSpec = \"1_0_0\";\r\n            SpeechServiceConfigJson = JSON.stringify(json);\r\n        }\r\n\r\n        if (SpeechServiceConfigJson) {\r\n            return connection.send(new SpeechConnectionMessage(\r\n                MessageType.Text,\r\n                \"speech.config\",\r\n                requestSession.requestId,\r\n                \"application/json\",\r\n                SpeechServiceConfigJson));\r\n        }\r\n\r\n        return;\r\n    }\r\n\r\n    protected async fetchConnection(): Promise<IConnection> {\r\n        if (this.privConnectionConfigurationPromise) {\r\n            return this.privConnectionConfigurationPromise.then((connection: IConnection): Promise<IConnection> => {\r\n                if (connection.state() === ConnectionState.Disconnected) {\r\n                    this.privConnectionId = null;\r\n                    this.privConnectionConfigurationPromise = null;\r\n                    this.privServiceHasSentMessage = false;\r\n                    return this.fetchConnection();\r\n                }\r\n                return this.privConnectionConfigurationPromise;\r\n            }, (error: string): Promise<IConnection> => {\r\n                this.privConnectionId = null;\r\n                this.privConnectionConfigurationPromise = null;\r\n                this.privServiceHasSentMessage = false;\r\n                return this.fetchConnection();\r\n            });\r\n        }\r\n\r\n        this.privConnectionConfigurationPromise = this.configureConnection();\r\n        return await this.privConnectionConfigurationPromise;\r\n    }\r\n\r\n    protected async sendAudio(audioStreamNode: IAudioStreamNode): Promise<void> {\r\n        const audioFormat: AudioStreamFormatImpl = await this.audioSource.format;\r\n\r\n        // The time we last sent data to the service.\r\n        let nextSendTime: number = Date.now();\r\n\r\n        // Max amount to send before we start to throttle\r\n        const fastLaneSizeMs: string = this.privRecognizerConfig.parameters.getProperty(\"SPEECH-TransmitLengthBeforThrottleMs\", \"5000\");\r\n        const maxSendUnthrottledBytes: number = audioFormat.avgBytesPerSec / 1000 * parseInt(fastLaneSizeMs, 10);\r\n        const startRecogNumber: number = this.privRequestSession.recogNumber;\r\n\r\n        const readAndUploadCycle = async (): Promise<void> => {\r\n            // If speech is done, stop sending audio.\r\n            if (!this.privIsDisposed &&\r\n                !this.privRequestSession.isSpeechEnded &&\r\n                this.privRequestSession.isRecognizing &&\r\n                this.privRequestSession.recogNumber === startRecogNumber) {\r\n\r\n                const connection: IConnection = await this.fetchConnection();\r\n                const audioStreamChunk: IStreamChunk<ArrayBuffer> = await audioStreamNode.read();\r\n                // we have a new audio chunk to upload.\r\n                if (this.privRequestSession.isSpeechEnded) {\r\n                    // If service already recognized audio end then don't send any more audio\r\n                    return;\r\n                }\r\n\r\n                let payload: ArrayBuffer;\r\n                let sendDelay: number;\r\n\r\n                if (!audioStreamChunk || audioStreamChunk.isEnd) {\r\n                    payload = null;\r\n                    sendDelay = 0;\r\n                } else {\r\n                    payload = audioStreamChunk.buffer;\r\n\r\n                    this.privRequestSession.onAudioSent(payload.byteLength);\r\n\r\n                    if (maxSendUnthrottledBytes >= this.privRequestSession.bytesSent) {\r\n                        sendDelay = 0;\r\n                    } else {\r\n                        sendDelay = Math.max(0, nextSendTime - Date.now());\r\n                    }\r\n                }\r\n\r\n                if (0 !== sendDelay) {\r\n                    await this.delay(sendDelay);\r\n                }\r\n\r\n                if (payload !== null) {\r\n                    nextSendTime = Date.now() + (payload.byteLength * 1000 / (audioFormat.avgBytesPerSec * 2));\r\n                }\r\n\r\n                // Are we still alive?\r\n                if (!this.privIsDisposed &&\r\n                    !this.privRequestSession.isSpeechEnded &&\r\n                    this.privRequestSession.isRecognizing &&\r\n                    this.privRequestSession.recogNumber === startRecogNumber) {\r\n                    connection.send(\r\n                        new SpeechConnectionMessage(MessageType.Binary, \"audio\", this.privRequestSession.requestId, null, payload)\r\n                    ).catch(() => {\r\n                        this.privRequestSession.onServiceTurnEndResponse(this.privRecognizerConfig.isContinuousRecognition).catch(() => { });\r\n                    });\r\n\r\n                    if (!audioStreamChunk?.isEnd) {\r\n                        // this.writeBufferToConsole(payload);\r\n                        // Regardless of success or failure, schedule the next upload.\r\n                        // If the underlying connection was broken, the next cycle will\r\n                        // get a new connection and re-transmit missing audio automatically.\r\n                        return readAndUploadCycle();\r\n                    } else {\r\n                        // the audio stream has been closed, no need to schedule next\r\n                        // read-upload cycle.\r\n                        if (!this.privIsLiveAudio) {\r\n                            this.privRequestSession.onSpeechEnded();\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        };\r\n\r\n        return readAndUploadCycle();\r\n    }\r\n\r\n    private async retryableConnect(): Promise<IConnection> {\r\n        let isUnAuthorized: boolean = false;\r\n\r\n        this.privAuthFetchEventId = createNoDashGuid();\r\n        const sessionId: string = this.privRequestSession.sessionId;\r\n        this.privConnectionId = (sessionId !== undefined) ? sessionId : createNoDashGuid();\r\n\r\n        this.privRequestSession.onPreConnectionStart(this.privAuthFetchEventId, this.privConnectionId);\r\n        let lastStatusCode: number = 0;\r\n        let lastReason: string = \"\";\r\n\r\n        while (this.privRequestSession.numConnectionAttempts <= this.privRecognizerConfig.maxRetryCount) {\r\n\r\n            // Get the auth information for the connection. This is a bit of overkill for the current API surface, but leaving the plumbing in place to be able to raise a developer-customer\r\n            // facing event when a connection fails to let them try and provide new auth information.\r\n            const authPromise = isUnAuthorized ? this.privAuthentication.fetchOnExpiry(this.privAuthFetchEventId) : this.privAuthentication.fetch(this.privAuthFetchEventId);\r\n            const auth: AuthInfo = await authPromise;\r\n\r\n            await this.privRequestSession.onAuthCompleted(false);\r\n\r\n            // Create the connection\r\n            const connection: IConnection = this.privConnectionFactory.create(this.privRecognizerConfig, auth, this.privConnectionId);\r\n            // Attach the telemetry handlers.\r\n            this.privRequestSession.listenForServiceTelemetry(connection.events);\r\n\r\n            // Attach to the underlying event. No need to hold onto the detach pointers as in the event the connection goes away,\r\n            // it'll stop sending events.\r\n            connection.events.attach((event: ConnectionEvent) => {\r\n                this.connectionEvents.onEvent(event);\r\n            });\r\n\r\n            const response: ConnectionOpenResponse = await connection.open();\r\n            // 200 == everything is fine.\r\n            if (response.statusCode === 200) {\r\n                await this.privRequestSession.onConnectionEstablishCompleted(response.statusCode);\r\n                return Promise.resolve(connection);\r\n            } else if (response.statusCode === 1006) {\r\n                isUnAuthorized = true;\r\n            }\r\n\r\n            lastStatusCode = response.statusCode;\r\n            lastReason = response.reason;\r\n\r\n            this.privRequestSession.onRetryConnection();\r\n        }\r\n\r\n        await this.privRequestSession.onConnectionEstablishCompleted(lastStatusCode, lastReason);\r\n        return Promise.reject(`Unable to contact server. StatusCode: ${lastStatusCode}, ${this.privRecognizerConfig.parameters.getProperty(PropertyId.SpeechServiceConnection_Endpoint)} Reason: ${lastReason}`);\r\n    }\r\n\r\n    private delay(delayMs: number): Promise<void> {\r\n        return new Promise((resolve: () => void, reject: (error: string) => void) => {\r\n            this.privSetTimeout(resolve, delayMs);\r\n        });\r\n    }\r\n\r\n    private writeBufferToConsole(buffer: ArrayBuffer): void {\r\n        let out: string = \"Buffer Size: \";\r\n        if (null === buffer) {\r\n            out += \"null\";\r\n        } else {\r\n            const readView: Uint8Array = new Uint8Array(buffer);\r\n            out += buffer.byteLength + \"\\r\\n\";\r\n            for (let i: number = 0; i < buffer.byteLength; i++) {\r\n                out += readView[i].toString(16).padStart(2, \"0\") + \" \";\r\n            }\r\n        }\r\n        // tslint:disable-next-line:no-console\r\n        console.info(out);\r\n    }\r\n\r\n    private async sendFinalAudio(): Promise<void> {\r\n        const connection: IConnection = await this.fetchConnection();\r\n        await connection.send(new SpeechConnectionMessage(MessageType.Binary, \"audio\", this.privRequestSession.requestId, null, null));\r\n        return;\r\n    }\r\n\r\n    // Takes an established websocket connection to the endpoint and sends speech configuration information.\r\n    private async configureConnection(): Promise<IConnection> {\r\n        const connection: IConnection = await this.connectImpl();\r\n        if (this.configConnectionOverride !== undefined) {\r\n            return this.configConnectionOverride(connection);\r\n        }\r\n        await this.sendSpeechServiceConfig(connection, this.privRequestSession, this.privRecognizerConfig.SpeechServiceConfig.serialize());\r\n        await this.sendPrePayloadJSON(connection);\r\n        return connection;\r\n    }\r\n}\r\n"]}