From 56349b4f13fcf1bac2535cd232bc46701cbc46fa Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Mon, 8 Dec 2025 17:21:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AE=AF=E9=A3=9E=E8=AF=AD?= =?UTF-8?q?=E9=9F=B3=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../azure_speech/AzureAsrHelper.kt | 1457 +++++++++-------- .../azure_speech/XunFeiAsrHelper.kt | 57 +- 2 files changed, 789 insertions(+), 725 deletions(-) diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt index 2f0989c62..34ec10deb 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt @@ -24,795 +24,874 @@ import java.util.UUID */ class AzureAsrHelper(private val context: Context) { - private val tag = "AzureAsrHelper" + private val tag = "AzureAsrHelper" - // 核心组件 - private var speechConfig: SpeechConfig? = null - private var recognizer: SpeechRecognizer? = null - private var audioConfig: AudioConfig? = null - private var continuousCallback: ContinuousRecognizeCallback? = null + // 核心组件 + private var speechConfig: SpeechConfig? = null + private var recognizer: SpeechRecognizer? = null + private var audioConfig: AudioConfig? = null + private var continuousCallback: ContinuousRecognizeCallback? = null - // 添加前台服务管理标志 - private var isForegroundServiceRunning = false - // 配置参数 - private var currentLanguage = "zh-CN" - private var supportedLanguages = arrayOf("zh-CN") - private var isAutoDetectLanguage = false - private var subscriptionKey = "" - private var region = "" + // 添加前台服务管理标志 + private var isForegroundServiceRunning = false - // 音频源配置 - var audioSourceType = AudioSourceType.MICROPHONE + // 配置参数 + private var currentLanguage = "zh-CN" + private var supportedLanguages = arrayOf("zh-CN") + private var isAutoDetectLanguage = false + private var subscriptionKey = "" + private var region = "" - // 音频处理 - Initialize immediately to avoid null checks - var audioStream: SimpleAudioReceiver = SimpleAudioReceiver(context) + private var useXunfei = false + private var xunFeiAsrHelper: XunFeiAsrHelper? = null + + // 音频源配置 + var audioSourceType = AudioSourceType.MICROPHONE + + // 音频处理 - Initialize immediately to avoid null checks + var audioStream: SimpleAudioReceiver = SimpleAudioReceiver(context) // 网络状态监听 - private var networkMonitor: NetworkStateMonitor = NetworkStateMonitor(context) - private var isNetworkRecovering = false - + private var networkMonitor: NetworkStateMonitor = NetworkStateMonitor(context) + private var isNetworkRecovering = false + // 网络丢失计数器 - private var networkLostCount = 0 + private var networkLostCount = 0 + // 最大网络丢失次数(3秒检测) - private val maxNetworkLostCount = 3 - + private val maxNetworkLostCount = 3 + // 防抖机制相关变量 - private var lastNetworkLostTime = 0L - private val networkLostDebounceInterval = 3000L // 2秒防抖间隔 - private var currsessionid = "" //识别回话id 关联到整个聊天过程中 - + private var lastNetworkLostTime = 0L + private val networkLostDebounceInterval = 3000L // 2秒防抖间隔 + private var currsessionid = "" //识别回话id 关联到整个聊天过程中 - /** - * 音频来源类型 - */ - enum class AudioSourceType { - /** 使用设备麦克风 */ - MICROPHONE, - /** 使用外部提供的音频数据 */ - EXTERNAL - } + /** + * 音频来源类型 + */ + enum class AudioSourceType { + /** 使用设备麦克风 */ + MICROPHONE, + /** 使用外部提供的音频数据 */ + EXTERNAL + } - /** - * 初始化Azure语音服务 - * - * @param subscriptionKey Azure 订阅密钥 - * @param region Azure 区域 - * @param supportedLanguages 支持的语言数组 - * @param audioSourceType 音频源类型 - * @return 初始化是否成功 - */ - fun initialize( - subscriptionKey: String, - region: String, - supportedLanguages: Array = arrayOf("zh-CN", "en-US"), - audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE - ): Boolean { - try { - // 检查配置是否为空 - if (subscriptionKey.isEmpty() || region.isEmpty()) { - Log.e(tag, "Azure 配置信息不完整") - return false - } - // 释放之前的资源 - dispose() + /** + * 初始化Azure语音服务 + * + * @param subscriptionKey Azure 订阅密钥 + * @param region Azure 区域 + * @param supportedLanguages 支持的语言数组 + * @param audioSourceType 音频源类型 + * @return 初始化是否成功 + */ + fun initialize( + subscriptionKey: String, + region: String, + supportedLanguages: Array = arrayOf("zh-CN", "en-US"), + audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE + ): Boolean { + try { + // 检查配置是否为空 + if (subscriptionKey.isEmpty() || region.isEmpty()) { + Log.e(tag, "Azure 配置信息不完整") + return false + } + + // 释放之前的资源 + dispose() + + // 保存配置 + this.subscriptionKey = subscriptionKey + this.region = region + this.audioSourceType = audioSourceType + + // 设置语言 + if (supportedLanguages.isNotEmpty()) { + this.supportedLanguages = supportedLanguages + } + + this.useXunfei = supportedLanguages.any { it == "zh-CN" || it == "en-US" } + + // 根据支持的语言数量决定是否启用自动语言检测 + this.isAutoDetectLanguage = supportedLanguages.size >= 2 + + // 如果只有一种语言,设置为当前语言 + if (!isAutoDetectLanguage && supportedLanguages.isNotEmpty()) { + this.currentLanguage = supportedLanguages[0] + } + + // 创建语音配置 + speechConfig = SpeechConfig.fromSubscription(subscriptionKey, region).apply { + if (isAutoDetectLanguage) { + // 直接启用语言检测模式 + setProperty(PropertyId.SpeechServiceConnection_LanguageIdMode, "Continuous") + } else { + // 设置指定的识别语言 + speechRecognitionLanguage = currentLanguage + } - // 保存配置 - this.subscriptionKey = subscriptionKey - this.region = region - this.audioSourceType = audioSourceType + setProperty("SpeechServiceConnection_EndSilenceTimeoutMs", "300") + setProperty("Speech_SegmentationSilenceTimeoutMs", "300") + + // 优化:添加低延迟连接配置 + setProperty("SpeechServiceConnection_InitialSilenceTimeoutMs", "200") + setProperty("SpeechServiceConnection_RecoMode", "INTERACTIVE") + setProperty("Speech_PushStreamFormat", "PCM") + + // 设置分段策略为时间模式 + setProperty("Speech_SegmentationStrategy", "Time") + } + // 初始化网络监听 + networkMonitor.initialize(object : NetworkStateMonitor.NetworkStateListener { + override fun onNetworkAvailable() { + handleNetworkAvailable() + } - // 设置语言 - if (supportedLanguages.isNotEmpty()) { - this.supportedLanguages = supportedLanguages - } + override fun onNetworkLost() { + handleNetworkLost() + } + }) - // 根据支持的语言数量决定是否启用自动语言检测 - this.isAutoDetectLanguage = supportedLanguages.size >= 2 + return true + } catch (e: Exception) { + Log.e(tag, "初始化失败: ${e.message}") + return false + } + } + + // /** + // * 设置音频配置 + // * @param sampleRate 采样率 (8000, 16000, 24000, 32000, 44100, 48000) + // * @param channels 声道数 (1=单声道, 2=立体声) + // */ + // fun setAudioConfig(sampleRate: Int, channels: Int) { + // try { + // // 设置RecordFile的音频配置 + // recordfile?.setAudioConfig(sampleRate, channels) + // Log.d(tag, "音频配置已设置: 采样率=${sampleRate}Hz, 声道数=${channels}") + // } catch (e: Exception) { + // Log.e(tag, "设置音频配置失败: ${e.message}") + // } + // } + + private fun isRecognizerValid(): Boolean { + return recognizer != null + } - // 如果只有一种语言,设置为当前语言 - if (!isAutoDetectLanguage && supportedLanguages.isNotEmpty()) { - this.currentLanguage = supportedLanguages[0] - } + /** + * 设置识别器 + */ + private fun setupRecognizer(): Boolean { + try { + // 如果正在进行连续识别,先停止 + if (audioStream.isContinuousRecognitionActive) { + // 直接停止,不等待结果 + recognizer?.stopContinuousRecognitionAsync() + audioStream.isContinuousRecognitionActive = false + } + setupMicrophoneStream() + + recognizer?.close() + recognizer = null + //创建识别器 + recognizer = if (isAutoDetectLanguage) { + val autoDetectConfig = + AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList()) + SpeechRecognizer(speechConfig, autoDetectConfig, audioConfig) + } else { + SpeechRecognizer(speechConfig, audioConfig) + } + + return true + } catch (e: Exception) { + Log.e(tag, "创建识别器失败: ${e.message}") + stopAudioProcessing() + return false + } + } - // 创建语音配置 - speechConfig = SpeechConfig.fromSubscription(subscriptionKey, region).apply { - if (isAutoDetectLanguage) { - // 直接启用语言检测模式 - setProperty(PropertyId.SpeechServiceConnection_LanguageIdMode, "Continuous") - } else { - // 设置指定的识别语言 - speechRecognitionLanguage = currentLanguage - } - - setProperty("SpeechServiceConnection_EndSilenceTimeoutMs", "300") - setProperty("Speech_SegmentationSilenceTimeoutMs", "300") - - // 优化:添加低延迟连接配置 - setProperty("SpeechServiceConnection_InitialSilenceTimeoutMs", "200") - setProperty("SpeechServiceConnection_RecoMode", "INTERACTIVE") - setProperty("Speech_PushStreamFormat", "PCM") - - // 设置分段策略为时间模式 - setProperty("Speech_SegmentationStrategy", "Time") - } - // 初始化网络监听 - networkMonitor.initialize(object : NetworkStateMonitor.NetworkStateListener { - override fun onNetworkAvailable() { - handleNetworkAvailable() - } - - override fun onNetworkLost() { - handleNetworkLost() - } - }) - - return true - } catch (e: Exception) { - Log.e(tag, "初始化失败: ${e.message}") - return false - } + /** + * 设置麦克风流 - 使用拉流方式 + */ + private fun setupMicrophoneStream() { + try { + Log.d(tag, "设置麦克风流 - 使用拉流方式: }") + + // Initialize if not already done + if (!audioStream.isInitialized()) { + audioStream.initAudioRecord() + } + audioStream.setupStream() + audioConfig = AudioConfig.fromStreamInput(audioStream.pushAudioStream) + + } catch (e: Exception) { + Log.e(tag, "设置麦克风流失败: ${e.message}") } + } - // /** - // * 设置音频配置 - // * @param sampleRate 采样率 (8000, 16000, 24000, 32000, 44100, 48000) - // * @param channels 声道数 (1=单声道, 2=立体声) - // */ - // fun setAudioConfig(sampleRate: Int, channels: Int) { - // try { - // // 设置RecordFile的音频配置 - // recordfile?.setAudioConfig(sampleRate, channels) - // Log.d(tag, "音频配置已设置: 采样率=${sampleRate}Hz, 声道数=${channels}") - // } catch (e: Exception) { - // Log.e(tag, "设置音频配置失败: ${e.message}") - // } - // } - - private fun isRecognizerValid(): Boolean { - return recognizer != null + + /** + * 开始连续语音识别 + * @param audioSourceType 音频源类型 + * @param audioDataCallback 音频数据回调 + * @return 是否成功开始识别 + */ + fun startContinuousRecognition( + audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE, + audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = null + ): Boolean { + + Log.d(tag, "startContinuousRecognition:${audioStream.isContinuousRecognitionActive} ") + + // 检查网络状态 + if (!checkNetworkStatus()) { + Log.e(tag, "网络不可用,无法开始语音识别") + continuousCallback?.onError(currsessionid, 1000, "网络连接不可用,请检查网络设置") + return false } - /** - * 设置识别器 - */ - private fun setupRecognizer(): Boolean { - try { - // 如果正在进行连续识别,先停止 - if (audioStream.isContinuousRecognitionActive) { - // 直接停止,不等待结果 - recognizer?.stopContinuousRecognitionAsync() - audioStream.isContinuousRecognitionActive = false - } - setupMicrophoneStream() - - recognizer?.close() - recognizer = null - //创建识别器 - recognizer = if (isAutoDetectLanguage) { - val autoDetectConfig = - AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList()) - SpeechRecognizer(speechConfig, autoDetectConfig, audioConfig) - } else { - SpeechRecognizer(speechConfig, audioConfig) - } + if (audioStream.isContinuousRecognitionActive) { + return true + } - return true - } catch (e: Exception) { - Log.e(tag, "创建识别器失败: ${e.message}") - stopAudioProcessing() - return false - } + // 启动前台服务(在开始音频录制前) + if (!isForegroundServiceRunning && audioSourceType == AudioSourceType.MICROPHONE) { + try { + AudioRecordingForegroundService.startService(context) + isForegroundServiceRunning = true + Log.d(tag, "前台服务已启动") + } catch (e: Exception) { + Log.e(tag, "启动前台服务失败: ${e.message}") + continuousCallback?.onError(currsessionid, 1003, "启动前台服务失败: ${e.message}") + return false + } } - /** - * 设置麦克风流 - 使用拉流方式 - */ - private fun setupMicrophoneStream() { - try { - Log.d(tag, "设置麦克风流 - 使用拉流方式: }") + if (useXunfei) { + Log.d(tag, "Using Xunfei Recognition") + val callback = continuousCallback + if (callback == null) { + Log.e(tag, "无法启动连续识别:回调为空") + continuousCallback?.onError(currsessionid, 1001, "回调为空") + return false + } - // Initialize if not already done - if (!audioStream.isInitialized()) { - audioStream.initAudioRecord() - } - audioStream.setupStream() - audioConfig = AudioConfig.fromStreamInput(audioStream.pushAudioStream) + if (xunFeiAsrHelper == null) { + xunFeiAsrHelper = XunFeiAsrHelper(context) + } - } catch (e: Exception) { - Log.e(tag, "设置麦克风流失败: ${e.message}") + try { + xunFeiAsrHelper?.start(callback) + + this.audioSourceType = audioSourceType + if (!audioStream.isContinuousRecognitionActive) { + audioStream.startAudioRecord( + when (audioSourceType) { + AudioSourceType.MICROPHONE -> SimpleAudioReceiver.AudioSourceType.MICROPHONE + AudioSourceType.EXTERNAL -> SimpleAudioReceiver.AudioSourceType.EXTERNAL + }, + object : SimpleAudioReceiver.AudioDataCallback { + override fun onAudio(data: ByteArray) { + audioDataCallback?.onAudio(data) + xunFeiAsrHelper?.sendAudio(data) + } + } + ) } + audioStream.isContinuousRecognitionActive = true + return true + } catch (e: Exception) { + Log.e(tag, "Start Xunfei failed: ${e.message}") + xunFeiAsrHelper?.stop() + audioStream.isContinuousRecognitionActive = false + return false + } + } + + if (!isRecognizerValid()) { + Log.d(tag, "重新启动连续识别") + val callback = continuousCallback + if (callback == null) { + Log.e(tag, "无法重新启动连续识别:回调为空") + continuousCallback?.onError(currsessionid, 1001, "回调为空") + return false + } + setupEventListeners(callback) } + this.audioSourceType = audioSourceType + + try { + Log.d(tag, "startContinuousRecognition: ${audioStream.isWriting()}") + // 启动音频处理(若已启动则跳过) + if (!audioStream.isContinuousRecognitionActive) { + Log.d(tag, "startAudioRecord:$audioSourceType") + audioStream.startAudioRecord( + when (audioSourceType) { + AudioSourceType.MICROPHONE -> SimpleAudioReceiver.AudioSourceType.MICROPHONE + AudioSourceType.EXTERNAL -> SimpleAudioReceiver.AudioSourceType.EXTERNAL + }, + audioDataCallback + ) + } else { + Log.d(tag, "音频已启动,跳过重复启动") + } + + recognizer?.startContinuousRecognitionAsync() + audioStream.isContinuousRecognitionActive = true + return true + } catch (e: Exception) { + Log.e(tag, "开始连续识别失败: ${e.message}") + stopContinuousRecognition() + audioStream.isContinuousRecognitionActive = false + return false + } + } + /** + * 设置事件监听器 + */ + fun setupEventListeners(callback: ContinuousRecognizeCallback): Boolean { + Log.d(tag, "设置ssssss监听器:${speechConfig ?: "null"} ") + if (speechConfig == null) { + callback.onError(currsessionid, 1002, "语音服务未初始化") + return false + } - /** - * 开始连续语音识别 - * @param audioSourceType 音频源类型 - * @param audioDataCallback 音频数据回调 - * @return 是否成功开始识别 - */ - fun startContinuousRecognition( - audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE, - audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = null - ): Boolean { - - Log.d(tag, "startContinuousRecognition:${audioStream.isContinuousRecognitionActive} ") - - // 检查网络状态 - if (!checkNetworkStatus()) { - Log.e(tag, "网络不可用,无法开始语音识别") - continuousCallback?.onError(currsessionid,1000,"网络连接不可用,请检查网络设置") - return false - } - - if (audioStream.isContinuousRecognitionActive) { - return true - } - - // 启动前台服务(在开始音频录制前) - if (!isForegroundServiceRunning && audioSourceType == AudioSourceType.MICROPHONE) { - try { - AudioRecordingForegroundService.startService(context) - isForegroundServiceRunning = true - Log.d(tag, "前台服务已启动") - } catch (e: Exception) { - Log.e(tag, "启动前台服务失败: ${e.message}") - continuousCallback?.onError(currsessionid,1003, "启动前台服务失败: ${e.message}") - return false - } - } - - if (!isRecognizerValid()) { - Log.d(tag, "重新启动连续识别") - val callback = continuousCallback - if (callback == null) { - Log.e(tag, "无法重新启动连续识别:回调为空") - continuousCallback?.onError(currsessionid,1001,"回调为空") - return false + // 重设识别器 + if (!setupRecognizer()) { + Log.d(tag, "初始化失败: ") + return false + } + try { + continuousCallback = callback; + Log.d(tag, "设置ssssss监听器: ") + // 优化:识别中事件 - 添加文本长度检查 + recognizer?.recognizing?.addEventListener( + EventHandler { _, event -> + // 优化:只处理非空结果 + if (event.result.text.isNotEmpty()) { + val detectedLanguage = if (isAutoDetectLanguage) { + AutoDetectSourceLanguageResult.fromResult(event.result)?.language ?: "" + } else { + currentLanguage } - setupEventListeners(callback) + callback.onRecognizing(currsessionid, event.result.text, detectedLanguage) + } } - this.audioSourceType = audioSourceType - - try { - Log.d(tag, "startContinuousRecognition: ${audioStream.isWriting()}") - // 启动音频处理(若已启动则跳过) - if (!audioStream.isContinuousRecognitionActive) { - Log.d(tag, "startAudioRecord:$audioSourceType") - audioStream.startAudioRecord( - when (audioSourceType) { - AudioSourceType.MICROPHONE -> SimpleAudioReceiver.AudioSourceType.MICROPHONE - AudioSourceType.EXTERNAL -> SimpleAudioReceiver.AudioSourceType.EXTERNAL - }, - audioDataCallback - ) + ) + + // 优化:识别完成事件 - 优化语言检测 + recognizer?.recognized?.addEventListener( + EventHandler { _, event -> + if (event.result.reason == ResultReason.RecognizedSpeech && event.result.text.isNotEmpty()) { + val detectedLanguage = if (isAutoDetectLanguage) { + AutoDetectSourceLanguageResult.fromResult(event.result)?.language + ?: supportedLanguages[0] } else { - Log.d(tag, "音频已启动,跳过重复启动") + currentLanguage } - - recognizer?.startContinuousRecognitionAsync() - audioStream.isContinuousRecognitionActive = true - return true - } catch (e: Exception) { - Log.e(tag, "开始连续识别失败: ${e.message}") - stopContinuousRecognition() - audioStream.isContinuousRecognitionActive = false - return false + callback.onResult(currsessionid, event.result.text, detectedLanguage) + currsessionid = UUID.randomUUID().toString() + } } - } + ) + + // 会话开始事件 + recognizer?.sessionStarted?.addEventListener( + EventHandler { _, _ -> + // 直接在当前线程调用回调 + Log.d(tag, "会话开始事件") + currsessionid = UUID.randomUUID().toString() + callback.onSessionStarted(currsessionid) + } + ) + + // 会话结束事件 + recognizer?.sessionStopped?.addEventListener( + EventHandler { _, _ -> + // 直接在当前线程调用回调 + Log.d(tag, "会话结束事件") + if (audioSourceType == AudioSourceType.EXTERNAL) { + callback.onSessionStopped(currsessionid) + audioStream?.isContinuousRecognitionActive = false + } + } + ) - /** - * 设置事件监听器 - */ - fun setupEventListeners(callback: ContinuousRecognizeCallback): Boolean { - Log.d(tag, "设置ssssss监听器:${speechConfig ?: "null"} ") - if (speechConfig == null) { - callback.onError(currsessionid,1002,"语音服务未初始化") - return false + // 取消事件 + recognizer?.canceled?.addEventListener( + EventHandler { _, event -> + val errorDetails = event.errorDetails ?: "未知错误" + val reason = event.reason.toString() + + Log.d(tag, "识别被取消: reason=$reason, details=$errorDetails") + callback.onCanceled(currsessionid, reason, errorDetails) } + ) + } catch (e: Exception) { + callback.onError(currsessionid, 1001, "启动连续识别失败: ${e.message}") + return false + } + return true + } + - // 重设识别器 - if (!setupRecognizer()) { - Log.d(tag, "初始化失败: ") - return false + /** + * 停止连续语音识别 + * + * @return 是否成功停止 + */ + fun stopContinuousRecognition(): Boolean { + if (useXunfei) { + try { + xunFeiAsrHelper?.stop() + stopAudioProcessing() + audioStream.isContinuousRecognitionActive = false + + if (isForegroundServiceRunning) { + try { + AudioRecordingForegroundService.stopService(context) + isForegroundServiceRunning = false + } catch (e: Exception) { + Log.e(tag, "停止前台服务失败: ${e.message}") + } } + return true + } catch (e: Exception) { + Log.e(tag, "Stop Xunfei failed: ${e.message}") + return false + } + } + + if (speechConfig == null) { + Log.e(tag, "语音服务未初始化") + return false + } + try { + Log.d(tag, "停止连续语音识别: ") + // 停止音频处理 + audioStream.stopMicrophoneCapture() + // 直接停止连续识别(SDK内部已是异步操作) + recognizer?.stopContinuousRecognitionAsync()?.get(1000, TimeUnit.MILLISECONDS) + + recognizer?.close() + recognizer = null + audioStream.isContinuousRecognitionActive = false + + // 停止前台服务 + if (isForegroundServiceRunning) { try { - continuousCallback = callback; - Log.d(tag, "设置ssssss监听器: ") - // 优化:识别中事件 - 添加文本长度检查 - recognizer?.recognizing?.addEventListener( - EventHandler { _, event -> - // 优化:只处理非空结果 - if (event.result.text.isNotEmpty()) { - val detectedLanguage = if (isAutoDetectLanguage) { - AutoDetectSourceLanguageResult.fromResult(event.result)?.language ?: "" - } else { - currentLanguage - } - callback.onRecognizing(currsessionid,event.result.text, detectedLanguage) - } - } - ) - - // 优化:识别完成事件 - 优化语言检测 - recognizer?.recognized?.addEventListener( - EventHandler { _, event -> - if (event.result.reason == ResultReason.RecognizedSpeech && event.result.text.isNotEmpty()) { - val detectedLanguage = if (isAutoDetectLanguage) { - AutoDetectSourceLanguageResult.fromResult(event.result)?.language - ?: supportedLanguages[0] - } else { - currentLanguage - } - callback.onResult(currsessionid,event.result.text, detectedLanguage) - currsessionid = UUID.randomUUID().toString() - } - } - ) - - // 会话开始事件 - recognizer?.sessionStarted?.addEventListener( - EventHandler { _, _ -> - // 直接在当前线程调用回调 - Log.d(tag, "会话开始事件") - currsessionid = UUID.randomUUID().toString() - callback.onSessionStarted(currsessionid) - } - ) - - // 会话结束事件 - recognizer?.sessionStopped?.addEventListener( - EventHandler { _, _ -> - // 直接在当前线程调用回调 - Log.d(tag, "会话结束事件") - if (audioSourceType == AudioSourceType.EXTERNAL) { - callback.onSessionStopped(currsessionid) - audioStream?.isContinuousRecognitionActive = false - } - } - ) - - // 取消事件 - recognizer?.canceled?.addEventListener( - EventHandler { _, event -> - val errorDetails = event.errorDetails ?: "未知错误" - val reason = event.reason.toString() - - Log.d(tag, "识别被取消: reason=$reason, details=$errorDetails") - callback.onCanceled(currsessionid,reason,errorDetails) - } - ) + AudioRecordingForegroundService.stopService(context) + isForegroundServiceRunning = false + Log.d(tag, "前台服务已停止") } catch (e: Exception) { - callback.onError(currsessionid,1001,"启动连续识别失败: ${e.message}") - return false + Log.e(tag, "停止前台服务失败: ${e.message}") } - return true + } + + return true + } catch (e: Exception) { + // 强制重置状态 + audioStream.isContinuousRecognitionActive = false + Log.e(tag, "停止连续识别失败: ${e.message}") + // 停止音频处理 + audioStream.stopMicrophoneCapture() + recognizer?.stopContinuousRecognitionAsync() + recognizer?.close() + recognizer = null + audioStream.isContinuousRecognitionActive = false + + // 确保前台服务被停止 + if (isForegroundServiceRunning) { + try { + AudioRecordingForegroundService.stopService(context) + isForegroundServiceRunning = false + } catch (e2: Exception) { + Log.e(tag, "强制停止前台服务失败: ${e2.message}") + } + } + + return false } + } + /** + * 检查连续识别是否活跃 + */ + fun isContinuousRecognitionActive(): Boolean = audioStream.isContinuousRecognitionActive - /** - * 停止连续语音识别 - * - * @return 是否成功停止 - */ - fun stopContinuousRecognition(): Boolean { - if (speechConfig == null) { - Log.e(tag, "语音服务未初始化") - return false + /** + * 释放所有资源 + */ + fun dispose() { + try { + // 如果正在进行连续识别,先停止 + if (audioStream.isContinuousRecognitionActive) { + if (useXunfei) { + xunFeiAsrHelper?.stop() + } else { + // 直接停止,不等待结果 + recognizer?.stopContinuousRecognitionAsync() } + audioStream.isContinuousRecognitionActive = false + } + // 停止音频处理 + stopAudioProcessing() + // 清理网络监听 + clearNetworkDetection() + // 停止录音 + audioStream.recordfile?.closeFile(true) + + // 释放recognizer + recognizer?.close() + recognizer = null + + xunFeiAsrHelper?.stop() + xunFeiAsrHelper = null + + // 释放speechConfig + speechConfig?.close() + speechConfig = null + + // 释放音频配置 + audioConfig?.close() + audioConfig = null + + // 确保前台服务被停止 + if (isForegroundServiceRunning) { try { - Log.d(tag, "停止连续语音识别: ") - // 停止音频处理 - audioStream.stopMicrophoneCapture() - // 直接停止连续识别(SDK内部已是异步操作) - recognizer?.stopContinuousRecognitionAsync()?.get(1000, TimeUnit.MILLISECONDS) - - recognizer?.close() - recognizer = null - audioStream.isContinuousRecognitionActive = false - - // 停止前台服务 - if (isForegroundServiceRunning) { - try { - AudioRecordingForegroundService.stopService(context) - isForegroundServiceRunning = false - Log.d(tag, "前台服务已停止") - } catch (e: Exception) { - Log.e(tag, "停止前台服务失败: ${e.message}") - } - } - - return true + AudioRecordingForegroundService.stopService(context) + isForegroundServiceRunning = false + Log.d(tag, "dispose时前台服务已停止") } catch (e: Exception) { - // 强制重置状态 - audioStream.isContinuousRecognitionActive = false - Log.e(tag, "停止连续识别失败: ${e.message}") - // 停止音频处理 - audioStream.stopMicrophoneCapture() - recognizer?.stopContinuousRecognitionAsync() - recognizer?.close() - recognizer = null - audioStream.isContinuousRecognitionActive = false - - // 确保前台服务被停止 - if (isForegroundServiceRunning) { - try { - AudioRecordingForegroundService.stopService(context) - isForegroundServiceRunning = false - } catch (e2: Exception) { - Log.e(tag, "强制停止前台服务失败: ${e2.message}") - } - } - - return false + Log.e(tag, "dispose时停止前台服务失败: ${e.message}") } + } + + // 确保状态被重置 + audioStream.isContinuousRecognitionActive = false + currsessionid = "" + } catch (e: Exception) { + // 确保状态被重置 + audioStream.isContinuousRecognitionActive = false + audioConfig = null + recognizer = null + speechConfig = null + isForegroundServiceRunning = false + currsessionid = "" } + } - /** - * 检查连续识别是否活跃 - */ - fun isContinuousRecognitionActive(): Boolean = audioStream.isContinuousRecognitionActive + // 音频处理相关方法 + /** + * 停止音频处理 + */ + private fun stopAudioProcessing() { + try { + Log.e(tag, "停止音频处理: }") + audioStream.releaseAudioResources() + audioStream.pushAudioStream?.close() + } catch (e: Exception) { + Log.e(tag, "关闭麦克风流失败: ${e.message}") + e.printStackTrace() + } + } - /** - * 释放所有资源 - */ - fun dispose() { - try { - // 如果正在进行连续识别,先停止 - if (audioStream.isContinuousRecognitionActive) { - // 直接停止,不等待结果 - recognizer?.stopContinuousRecognitionAsync() - audioStream.isContinuousRecognitionActive = false - } - // 停止音频处理 - stopAudioProcessing() - // 清理网络监听 - clearNetworkDetection() - // 停止录音 - audioStream.recordfile?.closeFile(true) - - // 释放recognizer - recognizer?.close() - recognizer = null - - // 释放speechConfig - speechConfig?.close() - speechConfig = null - - // 释放音频配置 - audioConfig?.close() - audioConfig = null - - // 确保前台服务被停止 - if (isForegroundServiceRunning) { - try { - AudioRecordingForegroundService.stopService(context) - isForegroundServiceRunning = false - Log.d(tag, "dispose时前台服务已停止") - } catch (e: Exception) { - Log.e(tag, "dispose时停止前台服务失败: ${e.message}") - } - } - - // 确保状态被重置 - audioStream.isContinuousRecognitionActive = false - currsessionid = "" - } catch (e: Exception) { - // 确保状态被重置 - audioStream.isContinuousRecognitionActive = false - audioConfig = null - recognizer = null - speechConfig = null - isForegroundServiceRunning = false - currsessionid = "" - } + +//录音音频文件 + /** + * 开启录音 + */ + fun enableRecord( + audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE, + filePath: String, + audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = null + ) { + Log.i(tag, "开启录音:") + this.audioSourceType = audioSourceType + if (audioSourceType == AudioSourceType.EXTERNAL) { + Log.i(tag, "外部音频源不在这里录音") + return } - // 音频处理相关方法 - /** - * 停止音频处理 - */ - private fun stopAudioProcessing() { + // 启动前台服务(在开始录音前) + if (!isForegroundServiceRunning) { + try { + AudioRecordingForegroundService.startService(context) + isForegroundServiceRunning = true + Log.d(tag, "录音前台服务已启动") + } catch (e: Exception) { + Log.e(tag, "启动录音前台服务失败: ${e.message}") + } + } + if (audioStream.recordfile == null) { + audioStream.recordfile = RecordFile() + } + + if (!audioStream.isContinuousRecognitionActive) { + // 启动音频处理 + Log.i(tag, "开启录音:audioSourceType${audioSourceType}") + // Initialize if not already done + if (!audioStream.isInitialized()) { + audioStream.initAudioRecord() + } + audioStream.setupStream() + + + audioStream.startAudioRecord( + when (audioSourceType) { + AudioSourceType.MICROPHONE -> SimpleAudioReceiver.AudioSourceType.MICROPHONE + AudioSourceType.EXTERNAL -> SimpleAudioReceiver.AudioSourceType.EXTERNAL + }, + audioDataCallback + ) + } + + audioStream.isRecord = true + audioStream.recordfile?.closeFile(true) + audioStream.recordfile?.creatingFiles(filePath) + } + + /** + * 移动文件到新路径 + */ + fun moveFile(sourcePath: String, destPath: String): Boolean { + if (audioSourceType == AudioSourceType.EXTERNAL) { + Log.i(tag, "外部音频源不在这里移动文件") + return false + } + Log.i(tag, "移动文件到新路径:") + audioStream.recordfile?.moveFile(sourcePath, destPath) ?: return false + return true + } + + /** + * 重命名指定路径的音频文件 + */ + fun renameFile(filePath: String, newName: String): Boolean { + if (audioSourceType == AudioSourceType.EXTERNAL) { + Log.i(tag, "外部音频源不在这里重命名文件") + return false + } + Log.i(tag, "重命名指定路径的音频文件:") + audioStream.recordfile?.renameFile(filePath, newName) ?: return false + return true + } + + /** + * 停止录音 + */ + fun pauseRecord() { + if (audioSourceType == AudioSourceType.EXTERNAL) { + Log.i(tag, "外部音频源不在这里暂停录音") + return + } + Log.i(tag, "停止连续录音:") + audioStream.recordfile ?: return + audioStream.isRecord = false + } + + /** + * 继续录音 + */ + fun resumeRecord() { + if (audioSourceType == AudioSourceType.EXTERNAL) { + Log.i(tag, "外部音频源不在这里继续录音") + return + } + Log.i(tag, "继续录音:") + audioStream.recordfile ?: return + audioStream.isRecord = true + } + + /** + * 关闭录音 + */ + fun stopRecord(isSave: Boolean) { + if (audioSourceType == AudioSourceType.EXTERNAL) { + Log.i(tag, "外部音频源不在这里关闭录音") + return + } + Log.i(tag, "关闭录音:") + audioStream.recordfile ?: return + + if (!audioStream.isContinuousRecognitionActive) { + audioStream.stopMicrophoneCapture() + + // 停止录音时也停止前台服务(如果没有其他音频任务) + if (isForegroundServiceRunning) { try { - Log.e(tag, "停止音频处理: }") - audioStream.releaseAudioResources() - audioStream.pushAudioStream?.close() + AudioRecordingForegroundService.stopService(context) + isForegroundServiceRunning = false + Log.d(tag, "录音前台服务已停止") } catch (e: Exception) { - Log.e(tag, "关闭麦克风流失败: ${e.message}") - e.printStackTrace() + Log.e(tag, "停止录音前台服务失败: ${e.message}") } + } } - - + audioStream.isRecord = false + audioStream.recordfile?.closeFile(isSave) + } -//录音音频文件 + /** + * 一次性识别回调接口 + */ + interface RecognizeCallback { /** - * 开启录音 + * 返回识别结果 + * + * @param text 识别的文本 + * @param detectedLanguage 检测到的语言 */ - fun enableRecord(audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE,filePath: String, audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = null) { - Log.i(tag, "开启录音:") - this.audioSourceType = audioSourceType - if(audioSourceType == AudioSourceType.EXTERNAL){ - Log.i(tag, "外部音频源不在这里录音") - return - } - - // 启动前台服务(在开始录音前) - if (!isForegroundServiceRunning) { - try { - AudioRecordingForegroundService.startService(context) - isForegroundServiceRunning = true - Log.d(tag, "录音前台服务已启动") - } catch (e: Exception) { - Log.e(tag, "启动录音前台服务失败: ${e.message}") - } - } - if (audioStream.recordfile == null) { - audioStream.recordfile = RecordFile() - } - - if (!audioStream.isContinuousRecognitionActive) { - // 启动音频处理 - Log.i(tag, "开启录音:audioSourceType${audioSourceType}") - // Initialize if not already done - if (!audioStream.isInitialized()) { - audioStream.initAudioRecord() - } - audioStream.setupStream() - - - audioStream.startAudioRecord( when (audioSourceType) { - AudioSourceType.MICROPHONE -> SimpleAudioReceiver.AudioSourceType.MICROPHONE - AudioSourceType.EXTERNAL -> SimpleAudioReceiver.AudioSourceType.EXTERNAL - }, - audioDataCallback - ) - } - - audioStream.isRecord = true - audioStream.recordfile?.closeFile(true) - audioStream.recordfile?.creatingFiles(filePath) - } + fun onResult(text: String, detectedLanguage: String) /** - * 移动文件到新路径 + * 识别错误时调用 + * + * @param code 错误码 + * @param error 错误信息 */ - fun moveFile(sourcePath: String, destPath: String): Boolean { - if(audioSourceType == AudioSourceType.EXTERNAL){ - Log.i(tag, "外部音频源不在这里移动文件") - return false - } - Log.i(tag, "移动文件到新路径:") - audioStream.recordfile?.moveFile(sourcePath, destPath) ?: return false - return true - } + fun onError(code: Int, error: String) + } + /** + * 连续识别回调接口 + */ + interface ContinuousRecognizeCallback { /** - * 重命名指定路径的音频文件 + * 返回识别结果 + * + * @param text 识别的文本 + * @param detectedLanguage 检测到的语言 */ - fun renameFile(filePath: String, newName: String): Boolean { - if(audioSourceType == AudioSourceType.EXTERNAL){ - Log.i(tag, "外部音频源不在这里重命名文件") - return false - } - Log.i(tag, "重命名指定路径的音频文件:") - audioStream.recordfile?.renameFile(filePath, newName) ?: return false - return true - } + fun onResult(sessiond: String, text: String, detectedLanguage: String) /** - * 停止录音 + * 识别进行中调用 + * + * @param recognizing 正在识别的文本 + * @param detectedLanguage 检测到的语言 */ - fun pauseRecord() { - if(audioSourceType == AudioSourceType.EXTERNAL){ - Log.i(tag, "外部音频源不在这里暂停录音") - return - } - Log.i(tag, "停止连续录音:") - audioStream.recordfile ?: return - audioStream.isRecord = false - } + fun onRecognizing(sessiond: String, recognizing: String, detectedLanguage: String) /** - * 继续录音 + * 会话开始时调用 */ - fun resumeRecord() { - if(audioSourceType == AudioSourceType.EXTERNAL){ - Log.i(tag, "外部音频源不在这里继续录音") - return - } - Log.i(tag, "继续录音:") - audioStream.recordfile ?: return - audioStream.isRecord = true - } + fun onSessionStarted(sessiond: String) /** - * 关闭录音 + * 会话结束时调用 */ - fun stopRecord(isSave: Boolean) { - if(audioSourceType == AudioSourceType.EXTERNAL){ - Log.i(tag, "外部音频源不在这里关闭录音") - return - } - Log.i(tag, "关闭录音:") - audioStream.recordfile ?: return - - if (!audioStream.isContinuousRecognitionActive) { - audioStream.stopMicrophoneCapture() - - // 停止录音时也停止前台服务(如果没有其他音频任务) - if (isForegroundServiceRunning) { - try { - AudioRecordingForegroundService.stopService(context) - isForegroundServiceRunning = false - Log.d(tag, "录音前台服务已停止") - } catch (e: Exception) { - Log.e(tag, "停止录音前台服务失败: ${e.message}") - } - } - } - - audioStream.isRecord = false - audioStream.recordfile?.closeFile(isSave) - } - + fun onSessionStopped(sessiond: String) /** - * 一次性识别回调接口 + * 识别取消时调用 + * + * @param reason 取消原因 + * @param errorDetails 错误详情 */ - interface RecognizeCallback { - /** - * 返回识别结果 - * - * @param text 识别的文本 - * @param detectedLanguage 检测到的语言 - */ - fun onResult(text: String, detectedLanguage: String) - - /** - * 识别错误时调用 - * - * @param code 错误码 - * @param error 错误信息 - */ - fun onError(code: Int,error: String) - } + fun onCanceled(sessiond: String, reason: String, errorDetails: String) /** - * 连续识别回调接口 + * 识别出错时调用 + * + * @param error 错误信息 */ - interface ContinuousRecognizeCallback { - /** - * 返回识别结果 - * - * @param text 识别的文本 - * @param detectedLanguage 检测到的语言 - */ - fun onResult(sessiond:String, text: String, detectedLanguage: String) - - /** - * 识别进行中调用 - * - * @param recognizing 正在识别的文本 - * @param detectedLanguage 检测到的语言 - */ - fun onRecognizing(sessiond:String, recognizing: String, detectedLanguage: String) - - /** - * 会话开始时调用 - */ - fun onSessionStarted(sessiond:String) - - /** - * 会话结束时调用 - */ - fun onSessionStopped(sessiond:String) - - - /** - * 识别取消时调用 - * - * @param reason 取消原因 - * @param errorDetails 错误详情 - */ - fun onCanceled(sessiond:String,reason: String, errorDetails: String) - - /** - * 识别出错时调用 - * - * @param error 错误信息 - */ - fun onError(sessiond:String, code: Int,error: String) - } + fun onError(sessiond: String, code: Int, error: String) + } - /** - * 检查当前网络状态 - * @return true表示网络可用,false表示网络不可用 - */ - private fun checkNetworkStatus(): Boolean { - return networkMonitor.checkNetworkStatus() - } /** - * 检查是否是网络相关错误 - */ - private fun isNetworkRelatedError(reason: String, errorDetails: String): Boolean { - return networkMonitor.isNetworkRelatedError(reason, errorDetails) + * 检查当前网络状态 + * @return true表示网络可用,false表示网络不可用 + */ + private fun checkNetworkStatus(): Boolean { + return networkMonitor.checkNetworkStatus() + } + + /** + * 检查是否是网络相关错误 + */ + private fun isNetworkRelatedError(reason: String, errorDetails: String): Boolean { + return networkMonitor.isNetworkRelatedError(reason, errorDetails) + } + + /** + * 处理网络连接可用 + */ + private fun handleNetworkAvailable() { + // 网络恢复时重置计数器 + networkLostCount = 0 + lastNetworkLostTime = 0L + if (!isNetworkRecovering) { + isNetworkRecovering = true + Log.d(tag, "网络恢复,准备重新启动识别") } - - /** - * 处理网络连接可用 - */ - private fun handleNetworkAvailable() { - // 网络恢复时重置计数器 - networkLostCount = 0 - lastNetworkLostTime = 0L - if (!isNetworkRecovering) { - isNetworkRecovering = true - Log.d(tag, "网络恢复,准备重新启动识别") - } + } + + /** + * 处理网络连接丢失(带防抖机制) + */ + private fun handleNetworkLost() { + val currentTime = System.currentTimeMillis() + + // 防抖机制:如果距离上次触发时间小于防抖间隔,则忽略本次触发 + if (currentTime - lastNetworkLostTime < networkLostDebounceInterval) { + Log.d(tag, "网络丢失事件被防抖过滤,距离上次触发仅${currentTime - lastNetworkLostTime}ms") + return } - - /** - * 处理网络连接丢失(带防抖机制) - */ - private fun handleNetworkLost() { - val currentTime = System.currentTimeMillis() - - // 防抖机制:如果距离上次触发时间小于防抖间隔,则忽略本次触发 - if (currentTime - lastNetworkLostTime < networkLostDebounceInterval) { - Log.d(tag, "网络丢失事件被防抖过滤,距离上次触发仅${currentTime - lastNetworkLostTime}ms") - return - } - - // 更新最后触发时间 - lastNetworkLostTime = currentTime + + // 更新最后触发时间 + lastNetworkLostTime = currentTime - //这里启动一个3秒定时器,3秒后检查网络是否恢复 - // 启动3秒定时器,3秒后检查网络是否恢复 + //这里启动一个3秒定时器,3秒后检查网络是否恢复 + // 启动3秒定时器,3秒后检查网络是否恢复 Handler(Looper.getMainLooper()).postDelayed({ - Log.e(tag, "网络不可用") - val isNetworkAvailable = checkNetworkStatus() - if (isNetworkAvailable) { - // 如果网络恢复,重置计数器和防抖时间 - Log.d(tag, "网络已恢复,重置计数器") - lastNetworkLostTime = 0L - isNetworkRecovering = true - } else { - continuousCallback?.onError(currsessionid,1000,"网络连接不可用,请检查网络设置") - Log.d(tag,"网络仍未恢复,继续检测") - networkLostCount = 0 + Log.e(tag, "网络不可用") + val isNetworkAvailable = checkNetworkStatus() + if (isNetworkAvailable) { + // 如果网络恢复,重置计数器和防抖时间 + Log.d(tag, "网络已恢复,重置计数器") + lastNetworkLostTime = 0L + isNetworkRecovering = true + } else { + continuousCallback?.onError(currsessionid, 1000, "网络连接不可用,请检查网络设置") + Log.d(tag, "网络仍未恢复,继续检测") + networkLostCount = 0 stopAudioProcessingImmediately() - } - }, 3000L) // 3秒延时 - } - - /** - * 立即停止音频处理(网络断开时使用) - */ - private fun stopAudioProcessingImmediately() { - try { - Log.d(tag, "立即停止音频处理") - stopContinuousRecognition() - // 不改变isContinuousRecognitionActive状态,保持识别意图 - } catch (e: Exception) { - Log.e(tag, "立即停止音频处理失败: ${e.message}") - } + } + }, 3000L) // 3秒延时 + } + + /** + * 立即停止音频处理(网络断开时使用) + */ + private fun stopAudioProcessingImmediately() { + try { + Log.d(tag, "立即停止音频处理") + stopContinuousRecognition() + // 不改变isContinuousRecognitionActive状态,保持识别意图 + } catch (e: Exception) { + Log.e(tag, "立即停止音频处理失败: ${e.message}") } -/** - * 清理网络检测相关资源 - */ -private fun clearNetworkDetection() { + } + + /** + * 清理网络检测相关资源 + */ + private fun clearNetworkDetection() { networkLostCount = 0 lastNetworkLostTime = 0L -} + } } diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/XunFeiAsrHelper.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/XunFeiAsrHelper.kt index 51e15ad21..18da0e3ae 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/XunFeiAsrHelper.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/XunFeiAsrHelper.kt @@ -4,11 +4,12 @@ import android.content.Context import android.util.Base64 import android.util.Log import okhttp3.* -import okio.ByteString +import okio.ByteString.Companion.toByteString import org.json.JSONObject import java.net.URLEncoder import java.nio.charset.StandardCharsets import java.security.MessageDigest +import java.util.UUID import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec @@ -16,77 +17,62 @@ class XunFeiAsrHelper(private val context: Context) { private val tag = "XunFeiAsrHelper" private val appId = "43d87d22" - private val accessKeyId = "2bd01ce06ac35957fc0c082f9b6284ec" + private val accessKeyId = "83f063755e8d342670002f50a4471d42" private var webSocket: WebSocket? = null private val client = OkHttpClient() - // Status: "UNDEFINED", "CONNECTING", "OPEN", "CLOSING", "CLOSED" - private var btnStatus = "UNDEFINED" + private var currentSessionId = "" - private var resultText = "" + fun start(callback: AzureAsrHelper.ContinuousRecognizeCallback) { + currentSessionId = UUID.randomUUID().toString() - var onResult: ((String) -> Unit)? = null - var onError: ((String) -> Unit)? = null - var onStatusChange: ((String) -> Unit)? = null - - fun connectWebSocket() { val url = getWebSocketUrl() val request = Request.Builder().url(url).build() - changeBtnStatus("CONNECTING") - webSocket = client.newWebSocket(request, object : WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) { Log.d(tag, "WebSocket Opened") - changeBtnStatus("OPEN") + callback.onSessionStarted(currentSessionId) } override fun onMessage(webSocket: WebSocket, text: String) { - renderResult(text) + renderResult(text, callback) } override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { Log.d(tag, "WebSocket Closing: $code / $reason") - changeBtnStatus("CLOSING") webSocket.close(1000, null) } override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { Log.d(tag, "WebSocket Closed: $code / $reason") - changeBtnStatus("CLOSED") + callback.onSessionStopped(currentSessionId) } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { Log.e(tag, "WebSocket Error", t) - changeBtnStatus("CLOSED") - onError?.invoke(t.message ?: "WebSocket connection failed") + callback.onError(currentSessionId, 1004, "Xunfei connection failed: ${t.message}") } }) } fun sendAudio(frameBuffer: ByteArray) { - webSocket?.let { ws -> - ws.send(ByteString.of(frameBuffer, 0, frameBuffer.size)) + webSocket?.let { ws: WebSocket -> + ws.send(frameBuffer.toByteString(0, frameBuffer.size)) } } - fun close() { - webSocket?.let { ws -> + fun stop() { + webSocket?.let { ws: WebSocket -> ws.send("{\"end\": true}") - changeBtnStatus("CLOSING") - } - } - - private fun changeBtnStatus(status: String) { - btnStatus = status - if (status == "CONNECTING") { - resultText = "" } - onStatusChange?.invoke(status) } - private fun renderResult(resultData: String) { + private fun renderResult( + resultData: String, + callback: AzureAsrHelper.ContinuousRecognizeCallback + ) { try { val jsonData = JSONObject(resultData) val action = jsonData.optString("action") @@ -120,16 +106,15 @@ class XunFeiAsrHelper(private val context: Context) { } if (st.optInt("type") == 0) { - resultText += resultTextTemp - onResult?.invoke(resultText) + callback.onResult(currentSessionId, resultTextTemp, "zh-CN") } else { - onResult?.invoke(resultText + resultTextTemp) + callback.onRecognizing(currentSessionId, resultTextTemp, "zh-CN") } } "error" -> { Log.e(tag, "Error: $resultData") - onError?.invoke(jsonData.toString()) + callback.onError(currentSessionId, 1005, "Xunfei error: $resultData") } } } catch (e: Exception) {