{"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.js\";\nimport { ConnectionOpenResponse } from \"../common/ConnectionOpenResponse.js\";\nimport {\n    ArgumentNullError,\n    ConnectionClosedEvent,\n    ConnectionEvent,\n    ConnectionState,\n    createNoDashGuid,\n    EventSource,\n    IAudioSource,\n    IAudioStreamNode,\n    IConnection,\n    IDisposable,\n    IStreamChunk,\n    MessageType,\n    ServiceEvent,\n    Timeout\n} from \"../common/Exports.js\";\nimport { AudioStreamFormatImpl } from \"../sdk/Audio/AudioStreamFormat.js\";\nimport { SpeakerRecognitionModel } from \"../sdk/SpeakerRecognitionModel.js\";\nimport {\n    CancellationErrorCode,\n    CancellationReason,\n    PropertyId,\n    RecognitionEventArgs,\n    Recognizer,\n    SessionEventArgs,\n    SpeakerRecognitionResult,\n    SpeechRecognitionResult,\n    OutputFormat\n} from \"../sdk/Exports.js\";\nimport { Callback } from \"../sdk/Transcription/IConversation.js\";\nimport {\n    AgentConfig,\n    AutoDetectSourceLanguagesOpenRangeOptionName,\n    DynamicGrammarBuilder,\n    ISpeechConfigAudioDevice,\n    RequestSession,\n    SpeechContext,\n    SpeechDetected,\n    type,\n    OutputFormatPropertyName\n} from \"./Exports.js\";\nimport {\n    AuthInfo,\n    IAuthentication,\n} from \"./IAuthentication.js\";\nimport { IConnectionFactory } from \"./IConnectionFactory.js\";\nimport { RecognizerConfig } from \"./RecognizerConfig.js\";\nimport { SpeechConnectionMessage } from \"./SpeechConnectionMessage.Internal.js\";\nimport { Segmentation, SegmentationMode } from \"./ServiceMessages/PhraseDetection/Segmentation.js\";\nimport { CustomLanguageMappingEntry, PhraseDetectionContext, RecognitionMode } from \"./ServiceMessages/PhraseDetection/PhraseDetectionContext.js\";\nimport { NextAction as NextTranslationAction } from \"./ServiceMessages/Translation/OnSuccess.js\";\nimport { Mode } from \"./ServiceMessages/Translation/InterimResults.js\";\nimport { LanguageIdDetectionMode, LanguageIdDetectionPriority } from \"./ServiceMessages/LanguageId/LanguageIdContext.js\";\nimport { NextAction as NextLangaugeIdAction } from \"./ServiceMessages/LanguageId/OnSuccess.js\";\nimport { OnUnknownAction } from \"./ServiceMessages/LanguageId/OnUnknown.js\";\nimport { ResultType } from \"./ServiceMessages/PhraseOutput/InterimResults.js\";\nimport { PhraseResultOutputType } from \"./ServiceMessages/PhraseOutput/PhraseResults.js\";\nimport { NextAction as NextPhraseDetectionAction } from \"./ServiceMessages/PhraseDetection/OnSuccess.js\";\n\nexport abstract class ServiceRecognizerBase implements IDisposable {\n    private privAuthentication: IAuthentication;\n    private privConnectionFactory: IConnectionFactory;\n\n    // A promise for a configured connection.\n    // Do not consume directly, call fetchConnection instead.\n    private privConnectionConfigurationPromise: Promise<IConnection> = undefined;\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> = undefined;\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    private privAudioSource: IAudioSource;\n    private privIsLiveAudio: boolean = false;\n    private privAverageBytesPerMs: number = 0;\n    protected privSpeechContext: SpeechContext;\n    protected privRequestSession: RequestSession;\n    protected privConnectionId: string;\n    protected privDiarizationSessionId: string;\n    protected privRecognizerConfig: RecognizerConfig;\n    protected privRecognizer: Recognizer;\n    protected privSuccessCallback: (e: SpeechRecognitionResult) => void;\n    protected privErrorCallback: (e: string) => void;\n    protected privEnableSpeakerId: boolean = false;\n    protected privExpectContentAssessmentResponse: boolean = false;\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.privEnableSpeakerId = recognizerConfig.isSpeakerDiarizationEnabled;\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        const webWorkerLoadType: string = this.privRecognizerConfig.parameters.getProperty(PropertyId.WebWorkerLoadType, \"on\").toLowerCase();\n        if (webWorkerLoadType === \"on\" && typeof (Blob) !== \"undefined\" && typeof (Worker) !== \"undefined\") {\n            this.privSetTimeout = Timeout.setTimeout;\n        } else {\n            if (typeof window !== \"undefined\") {\n                // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n                this.privSetTimeout = window.setTimeout.bind(window);\n            }\n            if (typeof globalThis !== \"undefined\") {\n                // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n                this.privSetTimeout = globalThis.setTimeout.bind(globalThis);\n            }\n        }\n\n        this.connectionEvents.attach((connectionEvent: ConnectionEvent): void => {\n            if (connectionEvent.name === \"ConnectionClosedEvent\") {\n                const connectionClosedEvent = connectionEvent as ConnectionClosedEvent;\n                if (connectionClosedEvent.statusCode === 1003 ||\n                    connectionClosedEvent.statusCode === 1007 ||\n                    connectionClosedEvent.statusCode === 1002 ||\n                    connectionClosedEvent.statusCode === 4000 ||\n                    this.privRequestSession.numConnectionAttempts > this.privRecognizerConfig.maxRetryCount\n                ) {\n                    void this.cancelRecognitionLocal(CancellationReason.Error,\n                        connectionClosedEvent.statusCode === 1007 ? CancellationErrorCode.BadRequestParameters : CancellationErrorCode.ConnectionFailure,\n                        `${connectionClosedEvent.reason} websocket error code: ${connectionClosedEvent.statusCode}`);\n                }\n            }\n        });\n\n        if (this.privEnableSpeakerId) {\n            this.privDiarizationSessionId = createNoDashGuid();\n        }\n    }\n\n    protected setTranslationJson(): void {\n        const targetLanguages: string = this.privRecognizerConfig.parameters.getProperty(PropertyId.SpeechServiceConnection_TranslationToLanguages, undefined);\n        if (targetLanguages !== undefined) {\n            const languages = targetLanguages.split(\",\");\n            const translationVoice: string = this.privRecognizerConfig.parameters.getProperty(PropertyId.SpeechServiceConnection_TranslationVoice, undefined);\n            const categoryId: string = this.privRecognizerConfig.parameters.getProperty(PropertyId.SpeechServiceConnection_TranslationCategoryId, undefined);\n\n            const action = (translationVoice !== undefined) ? NextTranslationAction.Synthesize : NextTranslationAction.None;\n\n            this.privSpeechContext.getContext().translation = {\n                onPassthrough: { action },  // Add onPassthrough with same action\n                onSuccess: { action },\n                output: {\n                    includePassThroughResults: true,  // Explicitly set pass-through results\n                    interimResults: { mode: Mode.Always }\n                },\n                targetLanguages: languages,\n            };\n\n            // Add category if specified\n            if (categoryId !== undefined) {\n                this.privSpeechContext.getContext().translation.category = categoryId;\n            }\n\n            if (translationVoice !== undefined) {\n                const languageToVoiceMap: { [key: string]: string } = {};\n                for (const lang of languages) {\n                    languageToVoiceMap[lang] = translationVoice;\n                }\n                this.privSpeechContext.getContext().synthesis = {\n                    defaultVoices: languageToVoiceMap\n                };\n            }\n\n            // Configure phrase detection for translation\n            const phraseDetection = this.privSpeechContext.getContext().phraseDetection || {};\n            phraseDetection.onSuccess = { action: NextPhraseDetectionAction.Translate };\n            phraseDetection.onInterim = { action: NextPhraseDetectionAction.Translate };\n            this.privSpeechContext.getContext().phraseDetection = phraseDetection;\n        }\n    }\n\n    protected setSpeechSegmentationTimeoutJson(): void {\n        const speechSegmentationSilenceTimeoutMs: string = this.privRecognizerConfig.parameters.getProperty(PropertyId.Speech_SegmentationSilenceTimeoutMs, undefined);\n        const speechSegmentationMaximumTimeMs: string = this.privRecognizerConfig.parameters.getProperty(PropertyId.Speech_SegmentationMaximumTimeMs, undefined);\n        const speechSegmentationStrategy: string = this.privRecognizerConfig.parameters.getProperty(PropertyId.Speech_SegmentationStrategy, undefined);\n        const segmentation: Segmentation = {\n            mode: SegmentationMode.Normal,\n        };\n\n        let configuredSegment = false;\n\n        if (speechSegmentationStrategy !== undefined) {\n            configuredSegment = true;\n            let segMode: SegmentationMode = SegmentationMode.Normal;\n            switch (speechSegmentationStrategy.toLowerCase()) {\n                case \"default\":\n                    break;\n                case \"time\":\n                    segMode = SegmentationMode.Custom;\n                    break;\n                case \"semantic\":\n                    segMode = SegmentationMode.Semantic;\n                    break;\n            }\n\n            segmentation.mode = segMode;\n        }\n\n        if (speechSegmentationSilenceTimeoutMs !== undefined) {\n            configuredSegment = true;\n            const segmentationSilenceTimeoutMs: number = parseInt(speechSegmentationSilenceTimeoutMs, 10);\n            segmentation.mode = SegmentationMode.Custom;\n            segmentation.segmentationSilenceTimeoutMs = segmentationSilenceTimeoutMs;\n        }\n\n        if (speechSegmentationMaximumTimeMs !== undefined) {\n            configuredSegment = true;\n            const segmentationMaximumTimeMs: number = parseInt(speechSegmentationMaximumTimeMs, 10);\n            segmentation.mode = SegmentationMode.Custom;\n            segmentation.segmentationForcedTimeoutMs = segmentationMaximumTimeMs;\n        }\n\n        if (configuredSegment) {\n            const phraseDetection: PhraseDetectionContext = this.privSpeechContext.getContext().phraseDetection || {};\n            phraseDetection.mode = this.recognitionMode;\n\n            switch (this.recognitionMode) {\n                case RecognitionMode.Conversation:\n                    phraseDetection.conversation = phraseDetection.conversation ?? { segmentation: {} };\n                    phraseDetection.conversation.segmentation = segmentation;\n\n                    break;\n                case RecognitionMode.Interactive:\n                    phraseDetection.interactive = phraseDetection.interactive ?? { segmentation: {} };\n                    phraseDetection.interactive.segmentation = segmentation;\n                    break;\n                case RecognitionMode.Dictation:\n                    phraseDetection.dictation = phraseDetection.dictation ?? {};\n                    phraseDetection.dictation.segmentation = segmentation;\n\n                    break;\n            }\n            this.privSpeechContext.getContext().phraseDetection = phraseDetection;\n        }\n    }\n\n    protected setLanguageIdJson(): void {\n        const phraseDetection: PhraseDetectionContext = this.privSpeechContext.getContext().phraseDetection || {};\n        if (this.privRecognizerConfig.autoDetectSourceLanguages !== undefined) {\n            const sourceLanguages: string[] = this.privRecognizerConfig.autoDetectSourceLanguages.split(\",\");\n\n            if (sourceLanguages.length === 1 && sourceLanguages[0] === AutoDetectSourceLanguagesOpenRangeOptionName) {\n                sourceLanguages[0] = \"UND\";\n            }\n\n            let speechContextLidMode;\n            if (this.privRecognizerConfig.languageIdMode === \"Continuous\") {\n                speechContextLidMode = LanguageIdDetectionMode.DetectContinuous;\n            } else {// recognizerConfig.languageIdMode === \"AtStart\"\n                speechContextLidMode = LanguageIdDetectionMode.DetectAtAudioStart;\n            }\n\n            this.privSpeechContext.getContext().languageId = {\n                languages: sourceLanguages,\n                mode: speechContextLidMode,\n                onSuccess: { action: NextLangaugeIdAction.Recognize },\n                onUnknown: { action: OnUnknownAction.None },\n                priority: LanguageIdDetectionPriority.PrioritizeLatency\n            };\n            this.privSpeechContext.getContext().phraseOutput = {\n                interimResults: {\n                    resultType: ResultType.Auto\n                },\n                phraseResults: {\n                    resultType: PhraseResultOutputType.Always\n                }\n            };\n            const customModels: CustomLanguageMappingEntry[] = this.privRecognizerConfig.sourceLanguageModels;\n            if (customModels !== undefined) {\n                phraseDetection.customModels = customModels;\n                phraseDetection.onInterim = { action: NextPhraseDetectionAction.None };\n                phraseDetection.onSuccess = { action: NextPhraseDetectionAction.None };\n            }\n        }\n        // No longer setting translation-specific configuration here\n        // This is now handled in setTranslationJson and setupTranslationWithLanguageId methods\n\n        this.privSpeechContext.getContext().phraseDetection = phraseDetection;\n    }\n\n    protected setOutputDetailLevelJson(): void {\n        if (this.privEnableSpeakerId) {\n            const requestWordLevelTimestamps: string = this.privRecognizerConfig.parameters.getProperty(PropertyId.SpeechServiceResponse_RequestWordLevelTimestamps, \"false\").toLowerCase();\n            if (requestWordLevelTimestamps === \"true\") {\n                this.privSpeechContext.setWordLevelTimings();\n            } else {\n                const outputFormat: string = this.privRecognizerConfig.parameters.getProperty(OutputFormatPropertyName, OutputFormat[OutputFormat.Simple]).toLowerCase();\n                if (outputFormat === OutputFormat[OutputFormat.Detailed].toLocaleLowerCase()) {\n                    this.privSpeechContext.setDetailedOutputFormat();\n                }\n            }\n        }\n    }\n\n    public get isSpeakerDiarizationEnabled(): boolean {\n        return this.privEnableSpeakerId;\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 set voiceProfileType(type: string) {\n        this.privRecognizerConfig.parameters.setProperty(PropertyId.SpeechServiceConnection_SpeakerIdMode, type);\n    }\n\n    public set authentication(auth: IAuthentication) {\n        this.privAuthentication = auth;\n    }\n\n    public isDisposed(): boolean {\n        return this.privIsDisposed;\n    }\n\n    public async dispose(reason?: string): Promise<void> {\n        this.privIsDisposed = true;\n        if (this.privConnectionPromise !== undefined) {\n            try {\n                const connection: IConnection = await this.privConnectionPromise;\n                await connection.dispose(reason);\n            } catch (error) {\n                // The connection is in a bad state. But we're trying to kill it, so...\n                return;\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) => Promise<void> = undefined;\n\n    public recognizeSpeaker: (model: SpeakerRecognitionModel) => Promise<SpeakerRecognitionResult> = undefined;\n\n    public async recognize(\n        recoMode: RecognitionMode,\n        successCallback: (e: SpeechRecognitionResult) => void,\n        errorCallBack: (e: string) => void,\n    ): Promise<void> {\n\n        if (this.recognizeOverride !== undefined) {\n            await this.recognizeOverride(recoMode, successCallback, errorCallBack);\n            return;\n        }\n        // Clear the existing configuration promise to force a re-transmission of config and context.\n        this.privConnectionConfigurationPromise = undefined;\n        this.privRecognizerConfig.recognitionMode = recoMode;\n\n        if (this.privRecognizerConfig.recognitionEndpointVersion === \"2\") {\n            const phraseDetection: PhraseDetectionContext = this.privSpeechContext.getContext().phraseDetection || {};\n            phraseDetection.mode = recoMode;\n            this.privSpeechContext.getContext().phraseDetection = phraseDetection;\n        }\n\n        // Set language ID (if configured)\n        this.setLanguageIdJson();\n\n        // Then set translation (if configured)\n        this.setTranslationJson();\n\n        // Configure the integration between language ID and translation (if both are used)\n        if (this.privRecognizerConfig.autoDetectSourceLanguages !== undefined &&\n            this.privRecognizerConfig.parameters.getProperty(PropertyId.SpeechServiceConnection_TranslationToLanguages, undefined) !== undefined) {\n            this.setupTranslationWithLanguageId();\n        }\n\n        this.setSpeechSegmentationTimeoutJson();\n        this.setOutputDetailLevelJson();\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        const conPromise: Promise<IConnection> = this.connectImpl();\n        let audioNode: ReplayableAudioNode;\n\n        try {\n            const audioStreamNode: IAudioStreamNode = await this.audioSource.attach(this.privRequestSession.audioNodeId);\n            const format: AudioStreamFormatImpl = await this.audioSource.format;\n            const deviceInfo: ISpeechConfigAudioDevice = await this.audioSource.deviceInfo;\n            this.privIsLiveAudio = deviceInfo.type && deviceInfo.type === type.Microphones;\n\n            audioNode = new ReplayableAudioNode(audioStreamNode, format.avgBytesPerSec);\n            await this.privRequestSession.onAudioSourceAttachCompleted(audioNode, false);\n            this.privRecognizerConfig.SpeechServiceConfig.Context.audio = { source: deviceInfo };\n\n        } catch (error) {\n            await this.privRequestSession.onStopRecognizing();\n            throw error;\n        }\n\n        try {\n            await conPromise;\n        } catch (error) {\n            await this.cancelRecognitionLocal(CancellationReason.Error, CancellationErrorCode.ConnectionFailure, error as string);\n            return;\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        void this.receiveMessage();\n        const audioSendPromise = this.sendAudio(audioNode);\n\n        audioSendPromise.catch(async (error: string): Promise<void> => {\n            await this.cancelRecognitionLocal(CancellationReason.Error, CancellationErrorCode.RuntimeError, error);\n        });\n\n        return;\n    }\n\n    public async stopRecognizing(): Promise<void> {\n        if (this.privRequestSession.isRecognizing) {\n            try {\n                await this.audioSource.turnOff();\n                await this.sendFinalAudio();\n                await this.privRequestSession.onStopRecognizing();\n                await this.privRequestSession.turnCompletionPromise;\n            } finally {\n                await this.privRequestSession.dispose();\n            }\n        }\n        return;\n    }\n\n    public async connect(): Promise<void> {\n        await this.connectImpl();\n        return Promise.resolve();\n    }\n\n    public connectAsync(cb?: Callback, err?: Callback): void {\n        this.connectImpl().then((): void => {\n            try {\n                if (!!cb) {\n                    cb();\n                }\n            } catch (e) {\n                if (!!err) {\n                    err(e);\n                }\n            }\n        }, (reason: any): void => {\n            try {\n                if (!!err) {\n                    err(reason);\n                }\n                /* eslint-disable no-empty */\n            } catch (error) {\n            }\n        });\n    }\n\n    protected disconnectOverride: () => Promise<void> = undefined;\n\n    public async disconnect(): Promise<void> {\n        await this.cancelRecognitionLocal(CancellationReason.Error,\n            CancellationErrorCode.NoError,\n            \"Disconnecting\");\n\n        if (this.disconnectOverride !== undefined) {\n            await this.disconnectOverride();\n        }\n\n        if (this.privConnectionPromise !== undefined) {\n            try {\n                await (await this.privConnectionPromise).dispose();\n            } catch (error) {\n\n            }\n        }\n        this.privConnectionPromise = undefined;\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    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    public sendMessage(message: string): Promise<void> {\n        return;\n    }\n\n    public async sendNetworkMessage(path: string, payload: string | ArrayBuffer): Promise<void> {\n        const type: MessageType = typeof payload === \"string\" ? MessageType.Text : MessageType.Binary;\n        const contentType: string = typeof payload === \"string\" ? \"application/json\" : \"\";\n\n        const connection: IConnection = await this.fetchConnection();\n        return connection.send(new SpeechConnectionMessage(type, path, this.privRequestSession.requestId, contentType, payload));\n    }\n\n    public set activityTemplate(messagePayload: string) {\n        this.privActivityTemplate = messagePayload;\n    }\n\n    public get activityTemplate(): string {\n        return this.privActivityTemplate;\n    }\n\n    public set expectContentAssessmentResponse(value: boolean) {\n        this.privExpectContentAssessmentResponse = value;\n    }\n\n    protected abstract processTypeSpecificMessages(\n        connectionMessage: SpeechConnectionMessage,\n        successCallback?: (e: SpeechRecognitionResult) => void,\n        errorCallBack?: (e: string) => void): Promise<boolean>;\n\n    protected async sendTelemetryData(): Promise<void> {\n        const telemetryData = this.privRequestSession.getTelemetry();\n        if (ServiceRecognizerBase.telemetryDataEnabled !== true ||\n            this.privIsDisposed ||\n            null === telemetryData) {\n            return;\n        }\n\n        if (!!ServiceRecognizerBase.telemetryData) {\n            try {\n                ServiceRecognizerBase.telemetryData(telemetryData);\n                /* eslint-disable no-empty */\n            } catch { }\n        }\n\n        const connection: IConnection = await this.fetchConnection();\n        await connection.send(new SpeechConnectionMessage(\n            MessageType.Text,\n            \"telemetry\",\n            this.privRequestSession.requestId,\n            \"application/json\",\n            telemetryData));\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 async cancelRecognitionLocal(\n        cancellationReason: CancellationReason,\n        errorCode: CancellationErrorCode,\n        error: string): Promise<void> {\n\n        if (!!this.privRequestSession.isRecognizing) {\n            await 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: () => Promise<void> = undefined;\n\n    protected async receiveMessage(): Promise<void> {\n        try {\n            if (this.privIsDisposed) {\n                // We're done.\n                return;\n            }\n\n            let connection = await this.fetchConnection();\n            const message = await connection.read();\n\n            if (this.receiveMessageOverride !== undefined) {\n                return this.receiveMessageOverride();\n            }\n\n            // indicates we are draining the queue and it came with no message;\n            if (!message) {\n                return this.receiveMessage();\n            }\n\n            this.privServiceHasSentMessage = true;\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\n                    case \"speech.startdetected\":\n                        const speechStartDetected: SpeechDetected = SpeechDetected.fromJSON(connectionMessage.textBody, this.privRequestSession.currentTurnAudioOffset);\n                        const speechStartEventArgs = new RecognitionEventArgs(speechStartDetected.Offset, this.privRequestSession.sessionId);\n                        if (!!this.privRecognizer.speechStartDetected) {\n                            this.privRecognizer.speechStartDetected(this.privRecognizer, speechStartEventArgs);\n                        }\n                        break;\n\n                    case \"speech.enddetected\":\n                        let json: string;\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                        const speechStopDetected: SpeechDetected = SpeechDetected.fromJSON(json, this.privRequestSession.currentTurnAudioOffset);\n                        const speechStopEventArgs = new RecognitionEventArgs(speechStopDetected.Offset + this.privRequestSession.currentTurnAudioOffset, this.privRequestSession.sessionId);\n                        if (!!this.privRecognizer.speechEndDetected) {\n                            this.privRecognizer.speechEndDetected(this.privRecognizer, speechStopEventArgs);\n                        }\n                        break;\n\n                    case \"turn.end\":\n                        await this.sendTelemetryData();\n                        if (this.privRequestSession.isSpeechEnded && this.privMustReportEndOfStream) {\n                            this.privMustReportEndOfStream = false;\n                            await this.cancelRecognitionLocal(CancellationReason.EndOfStream, CancellationErrorCode.NoError, undefined);\n                        }\n                        const sessionStopEventArgs: SessionEventArgs = new SessionEventArgs(this.privRequestSession.sessionId);\n                        await this.privRequestSession.onServiceTurnEndResponse(this.privRecognizerConfig.isContinuousRecognition);\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                            return;\n                        } else {\n                            connection = await this.fetchConnection();\n                            await this.sendPrePayloadJSON(connection);\n                        }\n                        break;\n\n                    default:\n                        if (!await 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            return this.receiveMessage();\n        } catch (error) {\n            return null;\n        }\n    }\n\n    private updateSpeakerDiarizationAudioOffset(): void {\n        const bytesSent: number = this.privRequestSession.recognitionBytesSent;\n        const audioOffsetMs: number = this.privAverageBytesPerMs !== 0 ? bytesSent / this.privAverageBytesPerMs : 0;\n        this.privSpeechContext.setSpeakerDiarizationAudioOffsetMs(audioOffsetMs);\n    }\n\n    protected sendSpeechContext(connection: IConnection, generateNewRequestId: boolean): Promise<void> {\n        if (this.privEnableSpeakerId) {\n            this.updateSpeakerDiarizationAudioOffset();\n        }\n\n        const speechContextJson = this.speechContext.toJSON();\n        if (generateNewRequestId) {\n            this.privRequestSession.onSpeechContext();\n        }\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;\n    }\n\n    protected sendPrePayloadJSONOverride: (connection: IConnection) => Promise<void> = undefined;\n\n    protected setupTranslationWithLanguageId(): void {\n        const targetLanguages: string = this.privRecognizerConfig.parameters.getProperty(PropertyId.SpeechServiceConnection_TranslationToLanguages, undefined);\n        const hasLanguageId = this.privRecognizerConfig.autoDetectSourceLanguages !== undefined;\n\n        if (targetLanguages !== undefined && hasLanguageId) {\n            // Configure phraseOutput for translation + language ID scenario\n            this.privSpeechContext.getContext().phraseOutput = {\n                interimResults: {\n                    resultType: ResultType.None\n                },\n                phraseResults: {\n                    resultType: PhraseResultOutputType.None\n                }\n            };\n\n            // Handle custom language models and voice mapping\n            const translationContext = this.privSpeechContext.getContext().translation;\n            if (translationContext) {\n                const customModels = this.privRecognizerConfig.sourceLanguageModels;\n                if (customModels !== undefined && customModels.length > 0) {\n                    const phraseDetection = this.privSpeechContext.getContext().phraseDetection || {};\n                    phraseDetection.customModels = customModels;\n                    this.privSpeechContext.getContext().phraseDetection = phraseDetection;\n                }\n\n                const translationVoice = this.privRecognizerConfig.parameters.getProperty(\n                    PropertyId.SpeechServiceConnection_TranslationVoice, undefined);\n\n                if (translationVoice !== undefined) {\n                    // Update translation actions for synthesis\n                    translationContext.onSuccess = { action: NextTranslationAction.Synthesize };\n                    translationContext.onPassthrough = { action: NextTranslationAction.Synthesize };\n                }\n            }\n        }\n    }\n\n    protected noOp(): Promise<void> {\n        // operation not supported\n        return;\n    }\n\n    // Encapsulated for derived service recognizers that need to send additional JSON\n    protected async sendPrePayloadJSON(connection: IConnection, generateNewRequestId: boolean = true): Promise<void> {\n        if (this.sendPrePayloadJSONOverride !== undefined) {\n            return this.sendPrePayloadJSONOverride(connection);\n        }\n\n        await this.sendSpeechContext(connection, generateNewRequestId);\n        await this.sendWaveHeader(connection);\n        return;\n    }\n\n    protected async sendWaveHeader(connection: IConnection): Promise<void> {\n        const format: AudioStreamFormatImpl = await this.audioSource.format;\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    protected postConnectImplOverride: (connection: Promise<IConnection>) => Promise<IConnection> = undefined;\n\n    // Establishes a websocket connection to the end point.\n    protected connectImpl(): Promise<IConnection> {\n        if (this.privConnectionPromise !== undefined) {\n            return this.privConnectionPromise.then((connection: IConnection): Promise<IConnection> => {\n                if (connection.state() === ConnectionState.Disconnected) {\n                    this.privConnectionId = null;\n                    this.privConnectionPromise = undefined;\n                    this.privServiceHasSentMessage = false;\n                    return this.connectImpl();\n                }\n                return this.privConnectionPromise;\n            }, (): Promise<IConnection> => {\n                this.privConnectionId = null;\n                this.privConnectionPromise = undefined;\n                this.privServiceHasSentMessage = false;\n                return this.connectImpl();\n            });\n        }\n\n        this.privConnectionPromise = this.retryableConnect();\n\n        // Attach an empty handler to allow the promise to run in the background while\n        // other startup events happen. It'll eventually be awaited on.\n        // eslint-disable-next-line @typescript-eslint/no-empty-function\n        this.privConnectionPromise.catch((): void => { });\n\n        if (this.postConnectImplOverride !== undefined) {\n            return this.postConnectImplOverride(this.privConnectionPromise);\n        }\n\n        return this.privConnectionPromise;\n    }\n\n    protected configConnectionOverride: (connection: IConnection) => Promise<IConnection> = undefined;\n    protected handleSpeechPhraseMessage: (textBody: string) => Promise<void> = undefined;\n    protected handleSpeechHypothesisMessage: (textBody: string) => void = undefined;\n\n    protected sendSpeechServiceConfig(connection: IConnection, requestSession: RequestSession, SpeechServiceConfigJson: string): Promise<void> {\n        requestSession.onSpeechContext();\n        // filter out anything that is not required for the service to work.\n        if (ServiceRecognizerBase.telemetryDataEnabled !== true) {\n            const withTelemetry: { context: { system: string } } = JSON.parse(SpeechServiceConfigJson) as { context: { system: string } };\n\n            const replacement: any = {\n                context: {\n                    system: withTelemetry.context.system,\n                },\n            };\n\n            SpeechServiceConfigJson = JSON.stringify(replacement);\n        }\n\n        if (this.privRecognizerConfig.parameters.getProperty(\"f0f5debc-f8c9-4892-ac4b-90a7ab359fd2\", \"false\").toLowerCase() === \"true\") {\n            const json: { context: { DisableReferenceChannel: string; MicSpec: string } } = JSON.parse(SpeechServiceConfigJson) as { context: { DisableReferenceChannel: string; MicSpec: string } };\n            json.context.DisableReferenceChannel = \"True\";\n            json.context.MicSpec = \"1_0_0\";\n            SpeechServiceConfigJson = JSON.stringify(json);\n        }\n\n        if (SpeechServiceConfigJson) {\n            return connection.send(new SpeechConnectionMessage(\n                MessageType.Text,\n                \"speech.config\",\n                requestSession.requestId,\n                \"application/json\",\n                SpeechServiceConfigJson));\n        }\n\n        return;\n    }\n\n    protected async fetchConnection(): Promise<IConnection> {\n        if (this.privConnectionConfigurationPromise !== undefined) {\n            return this.privConnectionConfigurationPromise.then((connection: IConnection): Promise<IConnection> => {\n                if (connection.state() === ConnectionState.Disconnected) {\n                    this.privConnectionId = null;\n                    this.privConnectionConfigurationPromise = undefined;\n                    this.privServiceHasSentMessage = false;\n                    return this.fetchConnection();\n                }\n                return this.privConnectionConfigurationPromise;\n            }, (): Promise<IConnection> => {\n                this.privConnectionId = null;\n                this.privConnectionConfigurationPromise = undefined;\n                this.privServiceHasSentMessage = false;\n                return this.fetchConnection();\n            });\n        }\n\n        this.privConnectionConfigurationPromise = this.configureConnection();\n        return await this.privConnectionConfigurationPromise;\n    }\n\n    protected async sendAudio(audioStreamNode: IAudioStreamNode): Promise<void> {\n        const audioFormat: AudioStreamFormatImpl = await this.audioSource.format;\n        this.privAverageBytesPerMs = audioFormat.avgBytesPerSec / 1000;\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 = async (): Promise<void> => {\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\n                const connection: IConnection = await this.fetchConnection();\n                const audioStreamChunk: IStreamChunk<ArrayBuffer> = await audioStreamNode.read();\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                    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\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                if (0 !== sendDelay) {\n                    await this.delay(sendDelay);\n                }\n\n                if (payload !== null) {\n                    nextSendTime = Date.now() + (payload.byteLength * 1000 / (audioFormat.avgBytesPerSec * 2));\n                }\n\n                // Are we still alive?\n                if (!this.privIsDisposed &&\n                    !this.privRequestSession.isSpeechEnded &&\n                    this.privRequestSession.isRecognizing &&\n                    this.privRequestSession.recogNumber === startRecogNumber) {\n                    connection.send(\n                        new SpeechConnectionMessage(MessageType.Binary, \"audio\", this.privRequestSession.requestId, null, payload)\n                    ).catch((): void => {\n                        // eslint-disable-next-line @typescript-eslint/no-empty-function\n                        this.privRequestSession.onServiceTurnEndResponse(this.privRecognizerConfig.isContinuousRecognition).catch((): void => { });\n                    });\n\n                    if (!audioStreamChunk?.isEnd) {\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                        return readAndUploadCycle();\n                    } else {\n                        // the audio stream has been closed, no need to schedule next\n                        // read-upload cycle.\n                        if (!this.privIsLiveAudio) {\n                            this.privRequestSession.onSpeechEnded();\n                        }\n                    }\n                }\n            }\n        };\n\n        return readAndUploadCycle();\n    }\n\n    private async retryableConnect(): Promise<IConnection> {\n        let isUnAuthorized: boolean = false;\n\n        this.privAuthFetchEventId = createNoDashGuid();\n        const sessionId: string = this.privRequestSession.sessionId;\n        this.privConnectionId = (sessionId !== undefined) ? sessionId : createNoDashGuid();\n\n        this.privRequestSession.onPreConnectionStart(this.privAuthFetchEventId, this.privConnectionId);\n        let lastStatusCode: number = 0;\n        let lastReason: string = \"\";\n\n        while (this.privRequestSession.numConnectionAttempts <= this.privRecognizerConfig.maxRetryCount) {\n            this.privRequestSession.onRetryConnection();\n\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\n            // facing event when a connection fails to let them try and provide new auth information.\n            const authPromise = isUnAuthorized ? this.privAuthentication.fetchOnExpiry(this.privAuthFetchEventId) : this.privAuthentication.fetch(this.privAuthFetchEventId);\n            const auth: AuthInfo = await authPromise;\n\n            await this.privRequestSession.onAuthCompleted(false);\n\n            // Create the connection\n            const connection: IConnection = await this.privConnectionFactory.create(this.privRecognizerConfig, auth, this.privConnectionId);\n            // Attach the telemetry handlers.\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): void => {\n                this.connectionEvents.onEvent(event);\n            });\n\n            const response: ConnectionOpenResponse = await connection.open();\n            // 200 == everything is fine.\n            if (response.statusCode === 200) {\n                await this.privRequestSession.onConnectionEstablishCompleted(response.statusCode);\n                return Promise.resolve(connection);\n            } else if (response.statusCode === 1006) {\n                isUnAuthorized = true;\n            }\n\n            lastStatusCode = response.statusCode;\n            lastReason = response.reason;\n        }\n\n        await this.privRequestSession.onConnectionEstablishCompleted(lastStatusCode, lastReason);\n        return Promise.reject(`Unable to contact server. StatusCode: ${lastStatusCode}, ${this.privRecognizerConfig.parameters.getProperty(PropertyId.SpeechServiceConnection_Endpoint)} Reason: ${lastReason}`);\n    }\n\n    private delay(delayMs: number): Promise<void> {\n        return new Promise((resolve: () => void): number => this.privSetTimeout(resolve, delayMs));\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                if (((i + 1) % 16) === 0) {\n                    // eslint-disable-next-line no-console\n                    console.info(out);\n                    out = \"\";\n                }\n            }\n        }\n        // eslint-disable-next-line no-console\n        console.info(out);\n    }\n\n    private async sendFinalAudio(): Promise<void> {\n        const connection: IConnection = await this.fetchConnection();\n        await connection.send(new SpeechConnectionMessage(MessageType.Binary, \"audio\", this.privRequestSession.requestId, null, null));\n        return;\n    }\n\n    // Takes an established websocket connection to the endpoint and sends speech configuration information.\n    private async configureConnection(): Promise<IConnection> {\n        const connection: IConnection = await this.connectImpl();\n        if (this.configConnectionOverride !== undefined) {\n            return this.configConnectionOverride(connection);\n        }\n        await this.sendSpeechServiceConfig(connection, this.privRequestSession, this.privRecognizerConfig.SpeechServiceConfig.serialize());\n        await this.sendPrePayloadJSON(connection, false);\n        return connection;\n    }\n}\n"]}