添加播放等待超时判断
This commit is contained in:
parent
e9eab6ae23
commit
724c5d51e0
13
app/src/main/java/com/zs/smarthuman/sherpa/TimeoutType.kt
Normal file
13
app/src/main/java/com/zs/smarthuman/sherpa/TimeoutType.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package com.zs.smarthuman.sherpa
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description:
|
||||||
|
* @author: lrs
|
||||||
|
* @date: 2026/1/8 17:39
|
||||||
|
*/
|
||||||
|
enum class TimeoutType {
|
||||||
|
IDLE_TIMEOUT, // 唤醒后全程没说话的闲时超时
|
||||||
|
INVALID_SPEECH_TIMEOUT // 唤醒后一直无效说话的超时
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias OnTimeoutTip = (TimeoutType) -> Unit
|
||||||
@ -2,17 +2,12 @@ package com.zs.smarthuman.sherpa
|
|||||||
|
|
||||||
import android.content.res.AssetManager
|
import android.content.res.AssetManager
|
||||||
import com.blankj.utilcode.util.LogUtils
|
import com.blankj.utilcode.util.LogUtils
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.util.ArrayDeque
|
import java.util.ArrayDeque
|
||||||
|
|
||||||
// 新增:超时类型枚举(区分两种超时)
|
|
||||||
enum class TimeoutType {
|
|
||||||
IDLE_TIMEOUT, // 唤醒后全程没说话的闲时超时
|
|
||||||
INVALID_SPEECH_TIMEOUT // 唤醒后一直无效说话的超时
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增:提示语回调(外部可通过此获取不同的提示语)
|
|
||||||
typealias OnTimeoutTip = (TimeoutType) -> Unit
|
|
||||||
|
|
||||||
class VoiceController(
|
class VoiceController(
|
||||||
assetManager: AssetManager,
|
assetManager: AssetManager,
|
||||||
private val onWakeup: () -> Unit,
|
private val onWakeup: () -> Unit,
|
||||||
@ -21,7 +16,6 @@ class VoiceController(
|
|||||||
maxRecordingSeconds: Int = 10,
|
maxRecordingSeconds: Int = 10,
|
||||||
private val onStateChanged: ((VoiceState) -> Unit)? = null,
|
private val onStateChanged: ((VoiceState) -> Unit)? = null,
|
||||||
private val stopBackendAudio: (() -> Unit)? = null,
|
private val stopBackendAudio: (() -> Unit)? = null,
|
||||||
// 新增:超时提示语回调
|
|
||||||
private val onTimeoutTip: OnTimeoutTip? = null
|
private val onTimeoutTip: OnTimeoutTip? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -120,6 +114,11 @@ class VoiceController(
|
|||||||
// ========== 补充:MIN_EFFECTIVE_SPEECH_RMS 常量(和VadManager对齐) ==========
|
// ========== 补充:MIN_EFFECTIVE_SPEECH_RMS 常量(和VadManager对齐) ==========
|
||||||
private val MIN_EFFECTIVE_SPEECH_RMS = 0.001f
|
private val MIN_EFFECTIVE_SPEECH_RMS = 0.001f
|
||||||
|
|
||||||
|
|
||||||
|
//播放等待超时
|
||||||
|
private val PLAY_WAIT_TIMEOUT_MS = 3000L
|
||||||
|
private var playWaitJob: Job? = null
|
||||||
|
|
||||||
/* ================= 音频入口 ================= */
|
/* ================= 音频入口 ================= */
|
||||||
fun acceptAudio(samples: FloatArray) {
|
fun acceptAudio(samples: FloatArray) {
|
||||||
cachePreBuffer(samples)
|
cachePreBuffer(samples)
|
||||||
@ -472,7 +471,12 @@ class VoiceController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onPlayStartBackend() {
|
fun onPlayStartBackend() {
|
||||||
LogUtils.d(TAG, "🎶 播放后台音频 | 基线: $currentEnvBaseline")
|
// 仅当上传完成(成功)且状态为 UPLOADING 时,才切换状态
|
||||||
|
if (state != VoiceState.UPLOADING) {
|
||||||
|
LogUtils.w(TAG, "🎶 非上传完成状态,禁止切换到 PLAYING_BACKEND | 当前状态: $state")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
LogUtils.d(TAG, "🎶 开始播放后台音频 | 基线: $currentEnvBaseline")
|
||||||
state = VoiceState.PLAYING_BACKEND
|
state = VoiceState.PLAYING_BACKEND
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,13 +489,43 @@ class VoiceController(
|
|||||||
fun onUploadFinished(success: Boolean) {
|
fun onUploadFinished(success: Boolean) {
|
||||||
if (state != VoiceState.UPLOADING) return
|
if (state != VoiceState.UPLOADING) return
|
||||||
LogUtils.d(TAG, "📤 上传完成 | 成功: $success | 基线: $currentEnvBaseline")
|
LogUtils.d(TAG, "📤 上传完成 | 成功: $success | 基线: $currentEnvBaseline")
|
||||||
state = if (success) VoiceState.PLAYING_BACKEND
|
|
||||||
else {
|
if (success) {
|
||||||
|
// 上传成功:启动协程超时任务
|
||||||
|
startPlayWaitTimer()
|
||||||
|
} else {
|
||||||
|
// 上传失败:取消超时任务,重置状态
|
||||||
|
cancelPlayWaitTimer()
|
||||||
speechEnableAtMs = System.currentTimeMillis() + SPEECH_COOLDOWN_MS
|
speechEnableAtMs = System.currentTimeMillis() + SPEECH_COOLDOWN_MS
|
||||||
VoiceState.WAIT_SPEECH_COOLDOWN
|
state = VoiceState.WAIT_SPEECH_COOLDOWN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startPlayWaitTimer() {
|
||||||
|
// 先取消旧任务,避免重复
|
||||||
|
cancelPlayWaitTimer()
|
||||||
|
|
||||||
|
// 启动协程超时任务(Dispatchers.Main保证状态修改在主线程)
|
||||||
|
playWaitJob = GlobalScope.launch {
|
||||||
|
delay(PLAY_WAIT_TIMEOUT_MS) // 挂起3秒,无线程阻塞
|
||||||
|
LogUtils.w(TAG, "⏱ 播放等待超时(${PLAY_WAIT_TIMEOUT_MS}ms),自动重置状态")
|
||||||
|
|
||||||
|
// 超时后重置状态(加同步锁,避免多线程冲突)
|
||||||
|
synchronized(this@VoiceController) {
|
||||||
|
speechEnableAtMs = System.currentTimeMillis() + SPEECH_COOLDOWN_MS
|
||||||
|
state = VoiceState.WAIT_SPEECH_COOLDOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================= 替换:取消协程任务 =================
|
||||||
|
private fun cancelPlayWaitTimer() {
|
||||||
|
playWaitJob?.cancel() // 取消协程(挂起函数会立即停止)
|
||||||
|
playWaitJob = null
|
||||||
|
LogUtils.d(TAG, "🔄 播放等待协程已取消")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun resetToWaitSpeech() {
|
private fun resetToWaitSpeech() {
|
||||||
LogUtils.d(TAG, "🔄 重置到等待说话 | 基线: $currentEnvBaseline | 已标记无效说话: $hasInvalidSpeech")
|
LogUtils.d(TAG, "🔄 重置到等待说话 | 基线: $currentEnvBaseline | 已标记无效说话: $hasInvalidSpeech")
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
@ -525,6 +559,7 @@ class VoiceController(
|
|||||||
currentTimeoutType = TimeoutType.IDLE_TIMEOUT
|
currentTimeoutType = TimeoutType.IDLE_TIMEOUT
|
||||||
LogUtils.d(TAG, "🔄 环境基线已重置 | 新基线: $currentEnvBaseline | 无效说话标记已重置")
|
LogUtils.d(TAG, "🔄 环境基线已重置 | 新基线: $currentEnvBaseline | 无效说话标记已重置")
|
||||||
state = VoiceState.WAIT_WAKEUP
|
state = VoiceState.WAIT_WAKEUP
|
||||||
|
cancelPlayWaitTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun release() {
|
fun release() {
|
||||||
@ -536,6 +571,7 @@ class VoiceController(
|
|||||||
// 核心新增:释放资源时重置标记
|
// 核心新增:释放资源时重置标记
|
||||||
hasInvalidSpeech = false
|
hasInvalidSpeech = false
|
||||||
currentTimeoutType = TimeoutType.IDLE_TIMEOUT
|
currentTimeoutType = TimeoutType.IDLE_TIMEOUT
|
||||||
|
cancelPlayWaitTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cachePreBuffer(samples: FloatArray) {
|
private fun cachePreBuffer(samples: FloatArray) {
|
||||||
|
|||||||
@ -148,8 +148,11 @@ class MainActivity : BaseViewModelActivity<ActivityMainBinding, MainViewModel>()
|
|||||||
voiceController?.onUploadFinished(false)
|
voiceController?.onUploadFinished(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
is ApiResult.Success<*> -> {
|
is ApiResult.Success<String> -> {
|
||||||
Toaster.showShort("上传成功")
|
if (!TextUtils.isEmpty(it.data)){
|
||||||
|
Toaster.showShort(it.data)
|
||||||
|
}
|
||||||
|
Toaster.showShort(it)
|
||||||
voiceController?.onUploadFinished(true)
|
voiceController?.onUploadFinished(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,7 +183,7 @@ class MainActivity : BaseViewModelActivity<ActivityMainBinding, MainViewModel>()
|
|||||||
onWakeup = {
|
onWakeup = {
|
||||||
Log.d("lrs", "当前状态: 唤醒成功wakeup")
|
Log.d("lrs", "当前状态: 唤醒成功wakeup")
|
||||||
//每次唤醒前都要把前面的音频停掉
|
//每次唤醒前都要把前面的音频停掉
|
||||||
UnityPlayerHolder.getInstance().cancelPCM()
|
// UnityPlayerHolder.getInstance().cancelPCM()
|
||||||
UnityPlayerHolder.getInstance()
|
UnityPlayerHolder.getInstance()
|
||||||
.sendVoiceToUnity(
|
.sendVoiceToUnity(
|
||||||
voiceInfo = mutableListOf<VoiceBeanResp>().apply {
|
voiceInfo = mutableListOf<VoiceBeanResp>().apply {
|
||||||
@ -200,12 +203,12 @@ class MainActivity : BaseViewModelActivity<ActivityMainBinding, MainViewModel>()
|
|||||||
1
|
1
|
||||||
)
|
)
|
||||||
// loadLocalJsonAndPlay()
|
// loadLocalJsonAndPlay()
|
||||||
// val file = File(
|
val file = File(
|
||||||
// getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)!!.getAbsolutePath(),
|
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)!!.getAbsolutePath(),
|
||||||
// "xxx.wav"
|
"xxx.wav"
|
||||||
// )
|
)
|
||||||
// AudioDebugUtil.saveFloatPcmAsWav(audio, file)
|
AudioDebugUtil.saveFloatPcmAsWav(audio, file)
|
||||||
// LogUtils.dTag("audioxx", "WAV saved: ${file.path}, samples=${audio.size}")
|
LogUtils.dTag("audioxx", "WAV saved: ${file.path}, samples=${audio.size}")
|
||||||
// lifecycleScope.launch(Dispatchers.Main) {
|
// lifecycleScope.launch(Dispatchers.Main) {
|
||||||
//
|
//
|
||||||
// mVerticalAnimator?.show()
|
// mVerticalAnimator?.show()
|
||||||
|
|||||||
@ -34,8 +34,8 @@ class MainViewModel: BaseViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val _uploadVoiceLiveData = ApiLiveData<Unit>()
|
private val _uploadVoiceLiveData = ApiLiveData<String>()
|
||||||
val uploadVoiceLiveData: LiveData<ApiResult<Unit>> = _uploadVoiceLiveData
|
val uploadVoiceLiveData: LiveData<ApiResult<String>> = _uploadVoiceLiveData
|
||||||
fun uploadVoice(audioVoice: String, sessionCode: Int){
|
fun uploadVoice(audioVoice: String, sessionCode: Int){
|
||||||
_uploadVoiceLiveData.request {
|
_uploadVoiceLiveData.request {
|
||||||
RxHttp.postJson(ApiService.UPLOAD_RECORD_VOICE_URL)
|
RxHttp.postJson(ApiService.UPLOAD_RECORD_VOICE_URL)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user