package com.nova.brain.glass.helper import android.util.Log import com.nova.brain.glass.BuildConfig import com.nova.brain.glass.model.RecognizeAction import com.rokid.online.speech.AsrClient import com.rokid.online.speech.OnlineSpeechSdk import com.rokid.online.speech.OnlineSpeechSdkConfig import com.rokid.online.speech.TtsClient import com.rokid.online.speech.open.AndroidPcmTtsStreamPlayer import com.rokid.online.speech.open.OpenSdkAudioSource import com.xuqm.base.extensions.showMessage object AsrHelper : OfflineCmdListener { private const val TAG = "AsrHelper" // 配置信息来自 BuildConfig(在 app/build.gradle 的 buildConfigField 中维护) private val DOMAIN get() = BuildConfig.SPEECH_DOMAIN private val AK get() = BuildConfig.SPEECH_AK private val SK get() = BuildConfig.SPEECH_SK private val UID get() = BuildConfig.SPEECH_UID private val DEVICE_ID get() = BuildConfig.SPEECH_DEVICE_ID private val ASR_PATH get() = BuildConfig.SPEECH_ASR_PATH private val TTS_PATH get() = BuildConfig.SPEECH_TTS_PATH // 唤醒词:Nova Nova private const val WAKE_WORD = "Nova Nova" private const val WAKE_WORD_PINYIN = "nou wa nou wa" private var sdk: OnlineSpeechSdk? = null private var asr: AsrClient? = null private var tts: TtsClient? = null private val audioSource = OpenSdkAudioSource() private val ttsPlayer = AndroidPcmTtsStreamPlayer() private var isConnected = false private var isMicRunning = false private var isTtsConnected = false private const val WAKE_RESPONSE = "在呢,您请说" // 拼接每次识别会话中的中间结果 private var currentPartial = "" /** 当前页面的场景标识,由各 Activity 在 onResume/onPause 中维护 */ var scene: String = "home" /** goToDecisionCenter 命中时的回调,由各 Activity 在 onResume/onPause 中注册/清空 */ var onGoToDecisionCenter: ((action: RecognizeAction) -> Unit)? = null fun init() { val cfg = OnlineSpeechSdkConfig( domain = DOMAIN, ak = AK, sk = SK, uid = UID, deviceId = DEVICE_ID, asrPath = ASR_PATH, ttsPath = TTS_PATH, trustAllCerts = true, staticHttpHeaders = mapOf( "appCredential" to "userInfo", "messageId" to "android-asr-${System.currentTimeMillis()}", ), staticMessageHeaders = mapOf( "appCredential" to "userInfo", "messageId" to "android-asr-${System.currentTimeMillis()}", ), ) sdk = OnlineSpeechSdk(cfg) asr = sdk!!.createAsrClient().attachAudioSource(audioSource).also { setupAsrCallbacks(it) } tts = sdk!!.createTtsClient().attachStreamPlayer(ttsPlayer).also { setupTtsCallbacks(it) } // 注册离线关键词 Nova Nova,GlassSdk 触发后启动 ASR OfflineCmdServiceHelper.registerAsrWakeWord() OfflineCmdServiceHelper.addOnLineListener(this) // 自动建立 ASR / TTS 连接 asrConnect() tts?.connect() Log.d(TAG, "AsrHelper init done") } private fun asrConnect() { asr?.connect() Log.d(TAG, "ASR connect() called") } private fun asrStartMic() { if (!isConnected) { Log.w(TAG, "ASR startMic ignored: not connected") return } if (isMicRunning) { Log.w(TAG, "ASR startMic ignored: mic already running") return } runCatching { asr?.startAsrWithMic() } .onSuccess { isMicRunning = true Log.d(TAG, "ASR startAsrWithMic()") } .onFailure { Log.e(TAG, "ASR startAsrWithMic failed: ${it.message}") } } private fun setupAsrCallbacks(asrClient: AsrClient) { asrClient.setListener(object : AsrClient.Listener { override fun onOpen() { isConnected = true Log.d(TAG, "ASR websocket open") } override fun onStart(taskId: String) { currentPartial = "" Log.d(TAG, "ASR started: $taskId") } override fun onPartialResult(taskId: String, text: String) { // 滚动更新当前识别中间结果 currentPartial += text Log.d(TAG, "ASR partial: $text") } override fun onFinalResult(taskId: String, text: String) { // 将最终结果追加拼接到会话字符串 isMicRunning = false // 滚动更新当前识别中间结果 currentPartial += text Log.d(TAG, "ASR final result: $currentPartial") IntentRecognizeHelper.recognize( text = currentPartial, scence = scene, onSuccess = { action -> if (action.name == "goToDecisionCenter") { onGoToDecisionCenter?.invoke(action) } else { "需要跳转任务列表".showMessage() } } ) } override fun onFinished(taskId: String) { Log.d(TAG, "ASR ended: $taskId") } override fun onError(code: Int, message: String) { Log.e(TAG, "ASR error code=$code msg=$message") isMicRunning = false } override fun onClosed(code: Int, reason: String) { isConnected = false isMicRunning = false Log.d(TAG, "ASR closed code=$code reason=$reason") } }) } private fun setupTtsCallbacks(ttsClient: TtsClient) { ttsClient.setListener(object : TtsClient.Listener { override fun onOpen() { isTtsConnected = true Log.d(TAG, "TTS websocket open") } override fun onFinished(taskId: String) { Log.d(TAG, "TTS ended: $taskId, starting mic") asrStartMic() } override fun onError(code: Int, message: String) { Log.e(TAG, "TTS error code=$code msg=$message, fallback to mic") asrStartMic() } override fun onClosed(code: Int, reason: String) { isTtsConnected = false Log.d(TAG, "TTS closed code=$code reason=$reason") } }) } // 离线关键词回调:唤醒词触发时先 TTS 播报,播报结束后启动麦克风 override fun onOfflineCmd(cmd: String) { if (cmd == WAKE_WORD) { Log.d(TAG, "Wake word triggered") if (isTtsConnected) { tts?.speak(WAKE_RESPONSE) } else { Log.w(TAG, "TTS not connected, starting mic directly") asrStartMic() } } } fun close() { OfflineCmdServiceHelper.removeOnLineListener(this) if (isMicRunning) { runCatching { asr?.stopAsrWithMic() } isMicRunning = false } asr?.close() tts?.close() sdk?.close() asr = null tts = null sdk = null isConnected = false isTtsConnected = false Log.d(TAG, "AsrHelper closed") } }