feat(speech): 添加语音识别和合成服务的自动重连机制
- 集成网络状态监听功能,网络恢复时自动重连语音服务 - 实现 ASR 和 TTS 服务的断线重连逻辑,提升连接稳定性 - 添加异常处理和重试机制,防止连接失败导致的服务中断 - 优化网络检测逻辑,统一使用 ConnectivityManager 管理网络状态 - 添加定时重连任务,支持延迟重连和立即重连两种模式 - 完善资源清理机制,确保关闭时正确释放所有连接和回调
这个提交包含在:
父节点
ec83fe0c2d
当前提交
edcd01d146
@ -7,11 +7,13 @@ import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import com.blankj.utilcode.util.Utils
|
||||
import com.nova.brain.glass.BuildConfig
|
||||
import com.nova.brain.glass.R
|
||||
import com.nova.brain.glass.model.RecognizeAction
|
||||
@ -55,10 +57,15 @@ object AsrHelper : OfflineCmdListener {
|
||||
private var isMicRunning = false
|
||||
private var isTtsConnected = false
|
||||
private var isAsrConnecting = false
|
||||
private var isTtsConnecting = false
|
||||
private var pendingStartMic = false
|
||||
private var isClosed = 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 val mainHandler = Handler(Looper.getMainLooper())
|
||||
private var listeningDialog: AlertDialog? = null
|
||||
@ -80,6 +87,27 @@ object AsrHelper : OfflineCmdListener {
|
||||
mainHandler.postDelayed(this, 1000L)
|
||||
}
|
||||
}
|
||||
private val asrReconnectRunnable = Runnable {
|
||||
if (shouldReconnectAsr()) {
|
||||
asrConnect()
|
||||
}
|
||||
}
|
||||
private val ttsReconnectRunnable = Runnable {
|
||||
if (shouldReconnectTts()) {
|
||||
ttsConnect()
|
||||
}
|
||||
}
|
||||
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
Log.d(TAG, "Network available, reconnect speech channels if needed")
|
||||
reconnectAllIfNeeded(immediate = true)
|
||||
dismissNoNetworkDialog()
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
Log.d(TAG, "Network lost")
|
||||
}
|
||||
}
|
||||
|
||||
// 拼接每次识别会话中的中间结果
|
||||
private var currentPartial = ""
|
||||
@ -103,6 +131,7 @@ object AsrHelper : OfflineCmdListener {
|
||||
var onDirectChat: ((text: String) -> Unit)? = null
|
||||
|
||||
fun init() {
|
||||
isClosed = false
|
||||
val cfg = OnlineSpeechSdkConfig(
|
||||
domain = DOMAIN,
|
||||
ak = AK,
|
||||
@ -129,20 +158,42 @@ object AsrHelper : OfflineCmdListener {
|
||||
// 注册离线关键词 Nova Nova,GlassSdk 触发后启动 ASR
|
||||
OfflineCmdServiceHelper.registerAsrWakeWord()
|
||||
OfflineCmdServiceHelper.addOnLineListener(this)
|
||||
registerNetworkCallback()
|
||||
|
||||
// 自动建立 ASR / TTS 连接
|
||||
asrConnect()
|
||||
tts?.connect()
|
||||
ttsConnect()
|
||||
|
||||
Log.d(TAG, "AsrHelper init done")
|
||||
}
|
||||
|
||||
private fun asrConnect() {
|
||||
if (isConnected || isAsrConnecting) return
|
||||
if (!shouldReconnectAsr()) return
|
||||
isAsrConnecting = true
|
||||
asr?.connect()
|
||||
runCatching { asr?.connect() }
|
||||
.onSuccess {
|
||||
Log.d(TAG, "ASR connect() called")
|
||||
}
|
||||
.onFailure {
|
||||
isAsrConnecting = false
|
||||
Log.e(TAG, "ASR connect failed: ${it.message}")
|
||||
scheduleAsrReconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ttsConnect() {
|
||||
if (!shouldReconnectTts()) return
|
||||
isTtsConnecting = true
|
||||
runCatching { tts?.connect() }
|
||||
.onSuccess {
|
||||
Log.d(TAG, "TTS connect() called")
|
||||
}
|
||||
.onFailure {
|
||||
isTtsConnecting = false
|
||||
Log.e(TAG, "TTS connect failed: ${it.message}")
|
||||
scheduleTtsReconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun asrStartMic() {
|
||||
if (!isConnected) {
|
||||
@ -173,6 +224,64 @@ object AsrHelper : OfflineCmdListener {
|
||||
mainHandler.removeCallbacks(listeningTimeoutRunnable)
|
||||
}
|
||||
|
||||
private fun scheduleAsrReconnect(delayMs: Long = RECONNECT_DELAY_MS) {
|
||||
if (!shouldReconnectAsr()) return
|
||||
mainHandler.removeCallbacks(asrReconnectRunnable)
|
||||
mainHandler.postDelayed(asrReconnectRunnable, delayMs)
|
||||
Log.d(TAG, "ASR reconnect scheduled in ${delayMs}ms")
|
||||
}
|
||||
|
||||
private fun scheduleTtsReconnect(delayMs: Long = RECONNECT_DELAY_MS) {
|
||||
if (!shouldReconnectTts()) return
|
||||
mainHandler.removeCallbacks(ttsReconnectRunnable)
|
||||
mainHandler.postDelayed(ttsReconnectRunnable, delayMs)
|
||||
Log.d(TAG, "TTS reconnect scheduled in ${delayMs}ms")
|
||||
}
|
||||
|
||||
private fun reconnectAllIfNeeded(immediate: Boolean = false) {
|
||||
if (!isNetworkAvailable()) return
|
||||
val delayMs = if (immediate) 0L else RECONNECT_DELAY_MS
|
||||
if (!isConnected) {
|
||||
scheduleAsrReconnect(delayMs)
|
||||
}
|
||||
if (!isTtsConnected) {
|
||||
scheduleTtsReconnect(delayMs)
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldReconnectAsr(): Boolean {
|
||||
return !isClosed && !isConnected && !isAsrConnecting && isNetworkAvailable()
|
||||
}
|
||||
|
||||
private fun shouldReconnectTts(): Boolean {
|
||||
return !isClosed && !isTtsConnected && !isTtsConnecting && isNetworkAvailable()
|
||||
}
|
||||
|
||||
private fun getContext(): Context = Utils.getApp()
|
||||
|
||||
private fun registerNetworkCallback() {
|
||||
if (networkCallbackRegistered) return
|
||||
val manager = getContext().getSystemService(ConnectivityManager::class.java) ?: return
|
||||
connectivityManager = manager
|
||||
runCatching {
|
||||
manager.registerDefaultNetworkCallback(networkCallback)
|
||||
networkCallbackRegistered = true
|
||||
}.onFailure {
|
||||
Log.e(TAG, "register network callback failed: ${it.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun unregisterNetworkCallback() {
|
||||
if (!networkCallbackRegistered) return
|
||||
runCatching {
|
||||
connectivityManager?.unregisterNetworkCallback(networkCallback)
|
||||
}.onFailure {
|
||||
Log.w(TAG, "unregister network callback failed: ${it.message}")
|
||||
}
|
||||
networkCallbackRegistered = false
|
||||
connectivityManager = null
|
||||
}
|
||||
|
||||
private fun showListeningDialog() {
|
||||
mainHandler.post {
|
||||
listeningDialog?.dismiss()
|
||||
@ -254,12 +363,12 @@ object AsrHelper : OfflineCmdListener {
|
||||
}
|
||||
|
||||
private fun isNetworkAvailable(): Boolean {
|
||||
val activity = runCatching { AppManager.getInstance().getActivity() }.getOrNull()
|
||||
val context: Context = activity ?: return false
|
||||
val connectivityManager =
|
||||
context.getSystemService(ConnectivityManager::class.java) ?: return false
|
||||
val network = connectivityManager.activeNetwork ?: return false
|
||||
val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
|
||||
val manager = connectivityManager
|
||||
?: getContext().getSystemService(ConnectivityManager::class.java)
|
||||
?: return false
|
||||
connectivityManager = manager
|
||||
val network = manager.activeNetwork ?: return false
|
||||
val capabilities = manager.getNetworkCapabilities(network) ?: return false
|
||||
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
}
|
||||
|
||||
@ -268,6 +377,7 @@ object AsrHelper : OfflineCmdListener {
|
||||
override fun onOpen() {
|
||||
isConnected = true
|
||||
isAsrConnecting = false
|
||||
mainHandler.removeCallbacks(asrReconnectRunnable)
|
||||
Log.d(TAG, "ASR websocket open")
|
||||
if (pendingStartMic) {
|
||||
asrStartMic()
|
||||
@ -329,6 +439,7 @@ object AsrHelper : OfflineCmdListener {
|
||||
isAsrConnecting = false
|
||||
isMicRunning = false
|
||||
dismissListeningDialog()
|
||||
scheduleAsrReconnect()
|
||||
}
|
||||
|
||||
override fun onClosed(code: Int, reason: String) {
|
||||
@ -337,6 +448,7 @@ object AsrHelper : OfflineCmdListener {
|
||||
isMicRunning = false
|
||||
dismissListeningDialog()
|
||||
Log.d(TAG, "ASR closed code=$code reason=$reason")
|
||||
scheduleAsrReconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -345,6 +457,8 @@ object AsrHelper : OfflineCmdListener {
|
||||
ttsClient.setListener(object : TtsClient.Listener {
|
||||
override fun onOpen() {
|
||||
isTtsConnected = true
|
||||
isTtsConnecting = false
|
||||
mainHandler.removeCallbacks(ttsReconnectRunnable)
|
||||
Log.d(TAG, "TTS websocket open")
|
||||
}
|
||||
|
||||
@ -354,13 +468,18 @@ 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")
|
||||
scheduleTtsReconnect()
|
||||
asrStartMic()
|
||||
}
|
||||
|
||||
override fun onClosed(code: Int, reason: String) {
|
||||
isTtsConnected = false
|
||||
isTtsConnecting = false
|
||||
Log.d(TAG, "TTS closed code=$code reason=$reason")
|
||||
scheduleTtsReconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -415,14 +534,18 @@ object AsrHelper : OfflineCmdListener {
|
||||
}
|
||||
|
||||
fun close() {
|
||||
isClosed = true
|
||||
OfflineCmdServiceHelper.removeOnLineListener(this)
|
||||
mainHandler.removeCallbacks(networkCheckRunnable)
|
||||
mainHandler.removeCallbacks(asrReconnectRunnable)
|
||||
mainHandler.removeCallbacks(ttsReconnectRunnable)
|
||||
dismissNoNetworkDialog()
|
||||
if (isMicRunning) {
|
||||
runCatching { asr?.stopAsrWithMic() }
|
||||
isMicRunning = false
|
||||
}
|
||||
dismissListeningDialog()
|
||||
unregisterNetworkCallback()
|
||||
asr?.close()
|
||||
tts?.close()
|
||||
sdk?.close()
|
||||
@ -430,7 +553,9 @@ object AsrHelper : OfflineCmdListener {
|
||||
tts = null
|
||||
sdk = null
|
||||
isConnected = false
|
||||
isAsrConnecting = false
|
||||
isTtsConnected = false
|
||||
isTtsConnecting = false
|
||||
Log.d(TAG, "AsrHelper closed")
|
||||
}
|
||||
}
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户