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