refactor(AsrHelper): 优化语音识别连接重连机制
- 移除未使用的 import 语句 - 添加指数退避算法计算重连延迟时间 - 引入最大重连尝试次数限制防止无限重连 - 添加重连状态管理变量控制重连行为 - 实现重连状态重置功能 - 优化网络可用时的重连逻辑 - 改进连接失败时的状态清理 - 添加详细的重连日志记录
这个提交包含在:
父节点
292a4352ac
当前提交
3e4198aa7c
@ -1,6 +1,5 @@
|
||||
package com.nova.brain.glass.helper
|
||||
|
||||
import android.R.attr.action
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@ -13,6 +12,7 @@ import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import androidx.core.graphics.toColorInt
|
||||
import com.blankj.utilcode.util.Utils
|
||||
import com.nova.brain.glass.BuildConfig
|
||||
import com.nova.brain.glass.R
|
||||
@ -25,9 +25,7 @@ import com.rokid.online.speech.TtsClient
|
||||
import com.rokid.online.speech.open.AndroidPcmTtsStreamPlayer
|
||||
import com.rokid.online.speech.open.OpenSdkAudioSource
|
||||
import com.xuqm.base.common.AppManager
|
||||
import com.xuqm.base.extensions.showMessage
|
||||
import kotlin.system.exitProcess
|
||||
import androidx.core.graphics.toColorInt
|
||||
|
||||
object AsrHelper : OfflineCmdListener {
|
||||
|
||||
@ -60,12 +58,18 @@ object AsrHelper : OfflineCmdListener {
|
||||
private var isTtsConnecting = false
|
||||
private var pendingStartMic = false
|
||||
private var isClosed = false
|
||||
private var asrReconnectAttempt = 0
|
||||
private var ttsReconnectAttempt = 0
|
||||
private var asrReconnectPaused = false
|
||||
private var ttsReconnectPaused = false
|
||||
private var connectivityManager: ConnectivityManager? = null
|
||||
private var networkCallbackRegistered = false
|
||||
|
||||
private const val WAKE_RESPONSE = "在呢,您请说"
|
||||
private const val LISTENING_TIMEOUT_MS = 30_000L
|
||||
private const val RECONNECT_DELAY_MS = 3_000L
|
||||
private const val BASE_RECONNECT_DELAY_MS = 3_000L
|
||||
private const val MAX_RECONNECT_DELAY_MS = 30_000L
|
||||
private const val MAX_RECONNECT_ATTEMPTS = 5
|
||||
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
private var listeningDialog: AlertDialog? = null
|
||||
@ -100,6 +104,7 @@ object AsrHelper : OfflineCmdListener {
|
||||
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
Log.d(TAG, "Network available, reconnect speech channels if needed")
|
||||
resetReconnectState()
|
||||
reconnectAllIfNeeded(immediate = true)
|
||||
dismissNoNetworkDialog()
|
||||
}
|
||||
@ -161,6 +166,7 @@ object AsrHelper : OfflineCmdListener {
|
||||
registerNetworkCallback()
|
||||
|
||||
// 自动建立 ASR / TTS 连接
|
||||
resetReconnectState()
|
||||
asrConnect()
|
||||
ttsConnect()
|
||||
|
||||
@ -197,6 +203,9 @@ object AsrHelper : OfflineCmdListener {
|
||||
|
||||
private fun asrStartMic() {
|
||||
if (!isConnected) {
|
||||
if (asrReconnectPaused) {
|
||||
resetAsrReconnectState()
|
||||
}
|
||||
pendingStartMic = true
|
||||
asrConnect()
|
||||
Log.w(TAG, "ASR not connected, queue startMic after reconnect")
|
||||
@ -224,23 +233,38 @@ object AsrHelper : OfflineCmdListener {
|
||||
mainHandler.removeCallbacks(listeningTimeoutRunnable)
|
||||
}
|
||||
|
||||
private fun scheduleAsrReconnect(delayMs: Long = RECONNECT_DELAY_MS) {
|
||||
private fun scheduleAsrReconnect(delayMs: Long? = null) {
|
||||
if (!shouldReconnectAsr()) return
|
||||
if (asrReconnectAttempt >= MAX_RECONNECT_ATTEMPTS) {
|
||||
asrReconnectPaused = true
|
||||
pendingStartMic = false
|
||||
Log.w(TAG, "ASR reconnect paused after $MAX_RECONNECT_ATTEMPTS failures")
|
||||
return
|
||||
}
|
||||
asrReconnectAttempt += 1
|
||||
val actualDelayMs = delayMs ?: calculateReconnectDelay(asrReconnectAttempt)
|
||||
mainHandler.removeCallbacks(asrReconnectRunnable)
|
||||
mainHandler.postDelayed(asrReconnectRunnable, delayMs)
|
||||
Log.d(TAG, "ASR reconnect scheduled in ${delayMs}ms")
|
||||
mainHandler.postDelayed(asrReconnectRunnable, actualDelayMs)
|
||||
Log.d(TAG, "ASR reconnect scheduled in ${actualDelayMs}ms, attempt=$asrReconnectAttempt")
|
||||
}
|
||||
|
||||
private fun scheduleTtsReconnect(delayMs: Long = RECONNECT_DELAY_MS) {
|
||||
private fun scheduleTtsReconnect(delayMs: Long? = null) {
|
||||
if (!shouldReconnectTts()) return
|
||||
if (ttsReconnectAttempt >= MAX_RECONNECT_ATTEMPTS) {
|
||||
ttsReconnectPaused = true
|
||||
Log.w(TAG, "TTS reconnect paused after $MAX_RECONNECT_ATTEMPTS failures")
|
||||
return
|
||||
}
|
||||
ttsReconnectAttempt += 1
|
||||
val actualDelayMs = delayMs ?: calculateReconnectDelay(ttsReconnectAttempt)
|
||||
mainHandler.removeCallbacks(ttsReconnectRunnable)
|
||||
mainHandler.postDelayed(ttsReconnectRunnable, delayMs)
|
||||
Log.d(TAG, "TTS reconnect scheduled in ${delayMs}ms")
|
||||
mainHandler.postDelayed(ttsReconnectRunnable, actualDelayMs)
|
||||
Log.d(TAG, "TTS reconnect scheduled in ${actualDelayMs}ms, attempt=$ttsReconnectAttempt")
|
||||
}
|
||||
|
||||
private fun reconnectAllIfNeeded(immediate: Boolean = false) {
|
||||
if (!isNetworkAvailable()) return
|
||||
val delayMs = if (immediate) 0L else RECONNECT_DELAY_MS
|
||||
val delayMs = if (immediate) 0L else null
|
||||
if (!isConnected) {
|
||||
scheduleAsrReconnect(delayMs)
|
||||
}
|
||||
@ -250,11 +274,33 @@ object AsrHelper : OfflineCmdListener {
|
||||
}
|
||||
|
||||
private fun shouldReconnectAsr(): Boolean {
|
||||
return !isClosed && !isConnected && !isAsrConnecting && isNetworkAvailable()
|
||||
return !isClosed && !isConnected && !isAsrConnecting && !asrReconnectPaused && isNetworkAvailable()
|
||||
}
|
||||
|
||||
private fun shouldReconnectTts(): Boolean {
|
||||
return !isClosed && !isTtsConnected && !isTtsConnecting && isNetworkAvailable()
|
||||
return !isClosed && !isTtsConnected && !isTtsConnecting && !ttsReconnectPaused && isNetworkAvailable()
|
||||
}
|
||||
|
||||
private fun calculateReconnectDelay(attempt: Int): Long {
|
||||
val factor = 1L shl (attempt - 1).coerceAtLeast(0)
|
||||
return (BASE_RECONNECT_DELAY_MS * factor).coerceAtMost(MAX_RECONNECT_DELAY_MS)
|
||||
}
|
||||
|
||||
private fun resetAsrReconnectState() {
|
||||
asrReconnectAttempt = 0
|
||||
asrReconnectPaused = false
|
||||
mainHandler.removeCallbacks(asrReconnectRunnable)
|
||||
}
|
||||
|
||||
private fun resetTtsReconnectState() {
|
||||
ttsReconnectAttempt = 0
|
||||
ttsReconnectPaused = false
|
||||
mainHandler.removeCallbacks(ttsReconnectRunnable)
|
||||
}
|
||||
|
||||
private fun resetReconnectState() {
|
||||
resetAsrReconnectState()
|
||||
resetTtsReconnectState()
|
||||
}
|
||||
|
||||
private fun getContext(): Context = Utils.getApp()
|
||||
@ -377,7 +423,7 @@ object AsrHelper : OfflineCmdListener {
|
||||
override fun onOpen() {
|
||||
isConnected = true
|
||||
isAsrConnecting = false
|
||||
mainHandler.removeCallbacks(asrReconnectRunnable)
|
||||
resetAsrReconnectState()
|
||||
Log.d(TAG, "ASR websocket open")
|
||||
if (pendingStartMic) {
|
||||
asrStartMic()
|
||||
@ -421,7 +467,10 @@ object AsrHelper : OfflineCmdListener {
|
||||
when (action.name) {
|
||||
"goToDecisionCenter" -> onGoToDecisionCenter?.invoke(action)
|
||||
"goToTaskCenter" -> onGoToTaskCenter?.invoke(action)
|
||||
"openTaskDetail", "openTaskDetailWithFilter" -> onOpenTaskDetail?.invoke(action)
|
||||
"openTaskDetail", "openTaskDetailWithFilter" -> onOpenTaskDetail?.invoke(
|
||||
action
|
||||
)
|
||||
|
||||
else -> Log.d(TAG, "unhandled action: $action")
|
||||
}
|
||||
},
|
||||
@ -438,6 +487,10 @@ object AsrHelper : OfflineCmdListener {
|
||||
isConnected = false
|
||||
isAsrConnecting = false
|
||||
isMicRunning = false
|
||||
if (pendingStartMic) {
|
||||
Log.w(TAG, "ASR pending startMic cleared after websocket failure")
|
||||
}
|
||||
pendingStartMic = false
|
||||
dismissListeningDialog()
|
||||
scheduleAsrReconnect()
|
||||
}
|
||||
@ -446,6 +499,7 @@ object AsrHelper : OfflineCmdListener {
|
||||
isConnected = false
|
||||
isAsrConnecting = false
|
||||
isMicRunning = false
|
||||
pendingStartMic = false
|
||||
dismissListeningDialog()
|
||||
Log.d(TAG, "ASR closed code=$code reason=$reason")
|
||||
scheduleAsrReconnect()
|
||||
@ -458,7 +512,7 @@ object AsrHelper : OfflineCmdListener {
|
||||
override fun onOpen() {
|
||||
isTtsConnected = true
|
||||
isTtsConnecting = false
|
||||
mainHandler.removeCallbacks(ttsReconnectRunnable)
|
||||
resetTtsReconnectState()
|
||||
Log.d(TAG, "TTS websocket open")
|
||||
}
|
||||
|
||||
@ -470,7 +524,7 @@ object AsrHelper : OfflineCmdListener {
|
||||
override fun onError(code: Int, message: String) {
|
||||
isTtsConnected = false
|
||||
isTtsConnecting = false
|
||||
Log.e(TAG, "TTS error code=$code msg=$message, fallback to mic")
|
||||
Log.e(TAG, "TTS error code=$code msg=$message")
|
||||
scheduleTtsReconnect()
|
||||
asrStartMic()
|
||||
}
|
||||
@ -497,6 +551,7 @@ object AsrHelper : OfflineCmdListener {
|
||||
showListeningDialog()
|
||||
restartListeningTimeout()
|
||||
pendingStartMic = true
|
||||
resetReconnectState()
|
||||
if (isTtsConnected) {
|
||||
tts?.speak(WAKE_RESPONSE)
|
||||
} else {
|
||||
@ -519,6 +574,7 @@ object AsrHelper : OfflineCmdListener {
|
||||
activity.finishAffinity()
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
"返回", "退回" -> {
|
||||
activity.startActivity(
|
||||
Intent(activity, WelcomeActivity::class.java).apply {
|
||||
@ -556,6 +612,11 @@ object AsrHelper : OfflineCmdListener {
|
||||
isAsrConnecting = false
|
||||
isTtsConnected = false
|
||||
isTtsConnecting = false
|
||||
asrReconnectAttempt = 0
|
||||
ttsReconnectAttempt = 0
|
||||
asrReconnectPaused = false
|
||||
ttsReconnectPaused = false
|
||||
pendingStartMic = false
|
||||
Log.d(TAG, "AsrHelper closed")
|
||||
}
|
||||
}
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户