|
|
|
@ -161,8 +161,35 @@ object AgentService : CoroutineScope { |
|
|
|
// ========== 音频焦点(唤醒 AI 时打断外部和内部其他音频)========== |
|
|
|
private var audioManagerForFocus: AudioManager? = null |
|
|
|
private var aiAudioFocusRequest: AudioFocusRequest? = null |
|
|
|
|
|
|
|
// focusChange 整数 → 可读名字,方便导出的日志里一眼看懂 |
|
|
|
private fun focusChangeName(code: Int): String = when (code) { |
|
|
|
AudioManager.AUDIOFOCUS_GAIN -> "GAIN" |
|
|
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT -> "GAIN_TRANSIENT" |
|
|
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE -> "GAIN_TRANSIENT_EXCLUSIVE" |
|
|
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK -> "GAIN_TRANSIENT_MAY_DUCK" |
|
|
|
AudioManager.AUDIOFOCUS_LOSS -> "LOSS" |
|
|
|
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> "LOSS_TRANSIENT" |
|
|
|
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> "LOSS_TRANSIENT_CAN_DUCK" |
|
|
|
else -> "UNKNOWN($code)" |
|
|
|
} |
|
|
|
|
|
|
|
private fun requestResultName(code: Int): String = when (code) { |
|
|
|
AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> "GRANTED($code)" |
|
|
|
AudioManager.AUDIOFOCUS_REQUEST_FAILED -> "FAILED($code)" |
|
|
|
AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> "DELAYED($code)" |
|
|
|
else -> "UNKNOWN($code)" |
|
|
|
} |
|
|
|
|
|
|
|
private val aiAudioFocusListener = AudioManager.OnAudioFocusChangeListener { focusChange -> |
|
|
|
Log.d(TAG, "[Agent] 音频焦点变化: $focusChange") |
|
|
|
val musicActive = try { audioManagerForFocus?.isMusicActive ?: false } catch (_: Throwable) { false } |
|
|
|
_log("INFO", "[Agent][焦点] 焦点变化=${focusChangeName(focusChange)} isMusicActive=$musicActive cachedReq=${aiAudioFocusRequest != null}") |
|
|
|
// 永久丢失焦点:系统或其他 App 已把焦点抢走,清掉缓存, |
|
|
|
// 这样下次 requestAiAudioFocus 不会再被短路,而是重新发起请求 |
|
|
|
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { |
|
|
|
_log("WARNING", "[Agent][焦点] AUDIOFOCUS_LOSS —— 焦点被系统或其他 App 抢走,清空本地缓存") |
|
|
|
aiAudioFocusRequest = null |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private fun requestAiAudioFocus() { |
|
|
|
@ -171,36 +198,88 @@ object AgentService : CoroutineScope { |
|
|
|
val am = audioManagerForFocus |
|
|
|
?: (ctx.getSystemService(Context.AUDIO_SERVICE) as? AudioManager)?.also { audioManagerForFocus = it } |
|
|
|
?: return |
|
|
|
|
|
|
|
// 请求前快照:系统此刻是否有音乐在响 / 音频模式 / 本地是否还留着上次的 request |
|
|
|
val musicActiveBefore = try { am.isMusicActive } catch (_: Throwable) { false } |
|
|
|
val audioMode = try { am.mode } catch (_: Throwable) { -1 } |
|
|
|
_log("INFO", "[Agent][焦点] requestAiAudioFocus 进入: isMusicActive=$musicActiveBefore audioMode=$audioMode cachedReq=${aiAudioFocusRequest != null} sdk=${Build.VERSION.SDK_INT}") |
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|
|
|
if (aiAudioFocusRequest != null) return |
|
|
|
val attrs = AudioAttributes.Builder() |
|
|
|
// 每次唤醒强制重抢焦点: |
|
|
|
// 1) 先把上次缓存的 request 主动 abandon 一次 —— 彻底切断"我们以为还持有焦点但系统里早被抢走"的错判, |
|
|
|
// 也能避开部分 OEM 对复用同一个 AudioFocusRequest 对象不敏感的问题; |
|
|
|
// 2) 然后 build 一个全新的 request,再 requestAudioFocus,确保系统每次都真正 re-evaluate 一次焦点归属。 |
|
|
|
aiAudioFocusRequest?.let { prev -> |
|
|
|
try { |
|
|
|
val abandonResult = am.abandonAudioFocusRequest(prev) |
|
|
|
_log("INFO", "[Agent][焦点] 强制重抢前 abandon 旧 request 结果=${requestResultName(abandonResult)}") |
|
|
|
} catch (e: Throwable) { |
|
|
|
_log("WARNING", "[Agent][焦点] 强制重抢前 abandon 旧 request 异常: ${e.message}") |
|
|
|
} |
|
|
|
} |
|
|
|
aiAudioFocusRequest = null |
|
|
|
|
|
|
|
val req = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) |
|
|
|
.setAudioAttributes( |
|
|
|
AudioAttributes.Builder() |
|
|
|
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) |
|
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) |
|
|
|
.build() |
|
|
|
val req = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) |
|
|
|
.setAudioAttributes(attrs) |
|
|
|
) |
|
|
|
.setOnAudioFocusChangeListener(aiAudioFocusListener) |
|
|
|
.build() |
|
|
|
aiAudioFocusRequest = req |
|
|
|
val result = am.requestAudioFocus(req) |
|
|
|
Log.i(TAG, "[Agent] 请求音频焦点(EXCLUSIVE) 结果=$result") |
|
|
|
val resultName = requestResultName(result) |
|
|
|
when (result) { |
|
|
|
AudioManager.AUDIOFOCUS_REQUEST_GRANTED, |
|
|
|
AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> { |
|
|
|
aiAudioFocusRequest = req |
|
|
|
_log("INFO", "[Agent][焦点] 强制重抢音频焦点(EXCLUSIVE) 结果=$resultName") |
|
|
|
} |
|
|
|
else -> { |
|
|
|
aiAudioFocusRequest = null |
|
|
|
_log("WARNING", "[Agent][焦点] 强制重抢音频焦点(EXCLUSIVE) 失败 结果=$resultName —— 其他 App 正占用且拒绝让出") |
|
|
|
} |
|
|
|
} |
|
|
|
// 请求完给系统一点时间把 focus loss 通知到其他 App,再复查一次 |
|
|
|
// 如果这里 isMusicActive 还是 true,就说明对方没响应(常见于部分 OEM / 抖音 / 某些音乐 App) |
|
|
|
launch { |
|
|
|
try { |
|
|
|
delay(300) |
|
|
|
val musicActiveAfter = try { am.isMusicActive } catch (_: Throwable) { false } |
|
|
|
_log( |
|
|
|
if (musicActiveAfter) "WARNING" else "INFO", |
|
|
|
"[Agent][焦点] 请求后 300ms 复查: isMusicActive=$musicActiveAfter [${if (musicActiveAfter) "未暂停" else "已暂停"}]" |
|
|
|
) |
|
|
|
} catch (e: Exception) { |
|
|
|
_log("ERROR", "[Agent][焦点] 复查 isMusicActive 异常: ${e.message}") |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
// legacy 路径同样做一次"abandon + 重新 request",保证每次 wake 都强制刷新焦点归属 |
|
|
|
@Suppress("DEPRECATION") |
|
|
|
try { |
|
|
|
am.abandonAudioFocus(aiAudioFocusListener) |
|
|
|
} catch (e: Throwable) { |
|
|
|
_log("WARNING", "[Agent][焦点] 强制重抢前 abandon 旧焦点 (legacy) 异常: ${e.message}") |
|
|
|
} |
|
|
|
@Suppress("DEPRECATION") |
|
|
|
val result = am.requestAudioFocus( |
|
|
|
aiAudioFocusListener, |
|
|
|
AudioManager.STREAM_VOICE_CALL, |
|
|
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE |
|
|
|
) |
|
|
|
Log.i(TAG, "[Agent] 请求音频焦点(EXCLUSIVE, legacy) 结果=$result") |
|
|
|
_log("INFO", "[Agent][焦点] 强制重抢音频焦点(EXCLUSIVE, legacy) 结果=${requestResultName(result)}") |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "[Agent] 请求音频焦点失败: ${e.message}", e) |
|
|
|
_log("ERROR", "[Agent][焦点] 请求音频焦点异常: ${e.message}") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private fun abandonAiAudioFocus() { |
|
|
|
try { |
|
|
|
val am = audioManagerForFocus ?: return |
|
|
|
val had = aiAudioFocusRequest != null |
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|
|
|
aiAudioFocusRequest?.let { am.abandonAudioFocusRequest(it) } |
|
|
|
aiAudioFocusRequest = null |
|
|
|
@ -208,10 +287,38 @@ object AgentService : CoroutineScope { |
|
|
|
@Suppress("DEPRECATION") |
|
|
|
am.abandonAudioFocus(aiAudioFocusListener) |
|
|
|
} |
|
|
|
Log.i(TAG, "[Agent] 已归还音频焦点") |
|
|
|
_log("INFO", "[Agent][焦点] 已归还音频焦点 (之前 cached=$had)") |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "[Agent] 归还音频焦点失败: ${e.message}", e) |
|
|
|
_log("ERROR", "[Agent][焦点] 归还音频焦点异常: ${e.message}") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 仅当 AI / TTS / 延迟发起的 AI 任务都空闲时才释放焦点,避免识别结束后外部音乐抢回播放 |
|
|
|
private fun tryReleaseAiAudioFocus(reason: String = "") { |
|
|
|
val aiStreaming = _isAiStreaming.get() |
|
|
|
val ttsSpeaking = _isTtsSpeaking.get() |
|
|
|
val pendingActive = pendingProcessJob?.isActive == true |
|
|
|
val aiJobActive = currentAiJob?.isActive == true |
|
|
|
val asrActive = _isRecognitionActive.get() |
|
|
|
// 停止识别后 ASR 的最终回调是异步到达的,若累计识别文本还未被消费, |
|
|
|
// 紧接着就会调度 pendingProcessJob → processWithChatApi 触发 AI + TTS, |
|
|
|
// 此时若提前释放焦点,外部音乐会与 AI 播报同时响 |
|
|
|
val hasPendingText = recognitionResult.isNotEmpty() || currentRecognitionResult.isNotEmpty() |
|
|
|
if (aiStreaming || ttsSpeaking || pendingActive || aiJobActive || asrActive || hasPendingText) { |
|
|
|
// 明确把是哪一个 guard 拦下的打出来,方便你定位"为什么音乐没恢复" |
|
|
|
val blockers = buildList { |
|
|
|
if (aiStreaming) add("aiStreaming") |
|
|
|
if (ttsSpeaking) add("ttsSpeaking") |
|
|
|
if (pendingActive) add("pendingJob") |
|
|
|
if (aiJobActive) add("aiJob") |
|
|
|
if (asrActive) add("asr") |
|
|
|
if (hasPendingText) add("pendingText") |
|
|
|
}.joinToString(",") |
|
|
|
_log("INFO", "[Agent][焦点] 延迟释放焦点 reason=$reason blockers=[$blockers] streaming=$aiStreaming tts=$ttsSpeaking pendingJob=$pendingActive aiJob=$aiJobActive asr=$asrActive text=$hasPendingText") |
|
|
|
return |
|
|
|
} |
|
|
|
_log("INFO", "[Agent][焦点] 可以释放焦点 reason=$reason") |
|
|
|
abandonAiAudioFocus() |
|
|
|
} |
|
|
|
|
|
|
|
// 状态 - 使用原子类型确保线程安全 |
|
|
|
@ -566,7 +673,10 @@ object AgentService : CoroutineScope { |
|
|
|
} |
|
|
|
|
|
|
|
TtsEventType.SYNTHESIS_COMPLETED -> { |
|
|
|
_isTtsSpeaking.set(false) |
|
|
|
// 注意:SYNTHESIS_COMPLETED 只表示 Azure 服务端音频数据全部返回, |
|
|
|
// 本地扬声器此刻通常还在播放,真正"播完"以 PLAYBACK_COMPLETED 为准。 |
|
|
|
// 因此这里不再清 _isTtsSpeaking,也不去释放焦点,避免外部音乐 |
|
|
|
// 在 AI 实际说完前抢回播放。 |
|
|
|
restartIdleCheck() |
|
|
|
sendEvent("tts_completed", mapOf("status" to "completed")) |
|
|
|
} |
|
|
|
@ -577,6 +687,7 @@ object AgentService : CoroutineScope { |
|
|
|
sendEvent("tts_canceled", mapOf("status" to "canceled")) |
|
|
|
// TTS取消时停止气泡音 |
|
|
|
// audioPlayer?.stopAudio() |
|
|
|
tryReleaseAiAudioFocus("ttsSynthesisCanceled") |
|
|
|
} |
|
|
|
|
|
|
|
TtsEventType.PLAYBACK_STARTED -> { //开始AI播报内容 |
|
|
|
@ -603,6 +714,11 @@ object AgentService : CoroutineScope { |
|
|
|
} |
|
|
|
|
|
|
|
TtsEventType.PLAYBACK_COMPLETED -> { //结束AI播报内容 |
|
|
|
// 防御性重置:有些 TTS 引擎会把 PLAYBACK_COMPLETED 发在 SYNTHESIS_COMPLETED 之前, |
|
|
|
// 导致到这里时 _isTtsSpeaking 还是 true,进而被 tryReleaseAiAudioFocus 的 guard 拦下, |
|
|
|
// 结果就是 AI 说完了但外部音乐/焦点一直没恢复 |
|
|
|
_isTtsSpeaking.set(false) |
|
|
|
_log("INFO", "[Agent][焦点] TTS PLAYBACK_COMPLETED —— AI 对话结束,准备恢复音乐并释放焦点") |
|
|
|
restartIdleCheck() |
|
|
|
sendEvent("playback_completed", mapOf("status" to "playback_completed")) |
|
|
|
// 发送AI说话开始广播 |
|
|
|
@ -615,11 +731,7 @@ object AgentService : CoroutineScope { |
|
|
|
BleService.openB1Encoder() |
|
|
|
} |
|
|
|
resumeMusic() |
|
|
|
// if(isplaymusic){ |
|
|
|
// isplaymusic = false |
|
|
|
// Log.e(TAG, "QQ 音乐恢复播放") |
|
|
|
// QQMusicSingleton.getInstance().resumeMusic { } |
|
|
|
// } |
|
|
|
tryReleaseAiAudioFocus("ttsPlaybackCompleted") |
|
|
|
} |
|
|
|
|
|
|
|
TtsEventType.ERROR -> { |
|
|
|
@ -627,6 +739,7 @@ object AgentService : CoroutineScope { |
|
|
|
restartIdleCheck() |
|
|
|
// TTS错误时停止气泡音 |
|
|
|
audioPlayer?.stopAudio() |
|
|
|
tryReleaseAiAudioFocus("ttsError") |
|
|
|
val params = event.params |
|
|
|
val code = params["errorCode"] as? String ?: "UNKNOWN_ERROR" |
|
|
|
val message = params["errorMessage"] as? String ?: "未知错误" |
|
|
|
@ -784,6 +897,7 @@ object AgentService : CoroutineScope { |
|
|
|
* @param mode 识别模式:normal, ble_wakeup, phone_call, push_to_talk |
|
|
|
*/ |
|
|
|
fun startRecognition(isExternalActive: Boolean, mode: String = "normal"): Boolean { |
|
|
|
_log("INFO", "[Agent][焦点] startRecognition 调用 mode=$mode agentMode=$agentMode external=$isExternalActive") |
|
|
|
// 抢占音频焦点,让外部和内部其他音频暂停(参考翻译模块的实现) |
|
|
|
requestAiAudioFocus() |
|
|
|
if (agentMode == "sts") { |
|
|
|
@ -920,6 +1034,9 @@ object AgentService : CoroutineScope { |
|
|
|
{ |
|
|
|
text = text.replace(Regex("^[,。?、!, . ? !]+"), "") |
|
|
|
recognitionResult += text |
|
|
|
// 最终识别结果已消费 onRecognizing 积累的在途文本,必须清空, |
|
|
|
// 否则 tryReleaseAiAudioFocus 的 hasPendingText 守卫会因为它非空而永远拦住焦点释放 |
|
|
|
currentRecognitionResult = "" |
|
|
|
} |
|
|
|
else{ |
|
|
|
recognitionResult+= currentRecognitionResult |
|
|
|
@ -955,6 +1072,7 @@ object AgentService : CoroutineScope { |
|
|
|
) |
|
|
|
processWithChatApi(currsessionAsrId, recognitionResult) |
|
|
|
recognitionResult = "" |
|
|
|
currentRecognitionResult = "" |
|
|
|
currsessionAsrId = "" |
|
|
|
} |
|
|
|
} |
|
|
|
@ -1001,12 +1119,18 @@ object AgentService : CoroutineScope { |
|
|
|
) |
|
|
|
) |
|
|
|
processWithChatApi(currsessionAsrId, recognitionResult) |
|
|
|
// 清空已消费的识别文本,避免 tryReleaseAiAudioFocus 的 hasPendingText 守卫 |
|
|
|
// 在 AI/TTS 完成后仍误判为"有待触发内容"而长期 hold 住音频焦点 |
|
|
|
recognitionResult = "" |
|
|
|
currsessionAsrId = "" |
|
|
|
} |
|
|
|
sendEvent("recognition_stopped", mapOf("sessionid" to currsessionAsrId, "status" to "stopped")) |
|
|
|
}else{ |
|
|
|
sendEvent("recognition_stopped", mapOf("sessionid" to sessionid, "status" to "stopped")) |
|
|
|
|
|
|
|
audioPlayer?.playAudio(R.raw.stop) |
|
|
|
// 会话结束后在途识别文本一定无效,清空避免 hasPendingText 守卫拦住焦点释放 |
|
|
|
currentRecognitionResult = "" |
|
|
|
} |
|
|
|
stopIdleCheck() |
|
|
|
_isRecognitionActive.set(false) |
|
|
|
@ -1025,6 +1149,7 @@ object AgentService : CoroutineScope { |
|
|
|
"details" to errorDetails |
|
|
|
) |
|
|
|
) |
|
|
|
tryReleaseAiAudioFocus("asrCanceled") |
|
|
|
} |
|
|
|
|
|
|
|
override fun onError(sessionid:String, code: Int, error: String) { |
|
|
|
@ -1041,6 +1166,7 @@ object AgentService : CoroutineScope { |
|
|
|
"message" to error |
|
|
|
) |
|
|
|
) |
|
|
|
tryReleaseAiAudioFocus("asrError") |
|
|
|
} |
|
|
|
}) |
|
|
|
return true |
|
|
|
@ -1060,11 +1186,11 @@ object AgentService : CoroutineScope { |
|
|
|
if (agentMode == "sts") { |
|
|
|
// StsAgent 内部根据 inCall / pttRecording 状态自行决定停止方式 |
|
|
|
stsAgent?.stop() |
|
|
|
abandonAiAudioFocus() |
|
|
|
tryReleaseAiAudioFocus("stopRecognition_sts") |
|
|
|
return |
|
|
|
} |
|
|
|
if (!isRecognitionActive) { |
|
|
|
abandonAiAudioFocus() |
|
|
|
tryReleaseAiAudioFocus("stopRecognition_inactive") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
@ -1085,7 +1211,7 @@ object AgentService : CoroutineScope { |
|
|
|
// currentRecognitionMode = "normal" |
|
|
|
return |
|
|
|
} finally { |
|
|
|
abandonAiAudioFocus() |
|
|
|
tryReleaseAiAudioFocus("stopRecognition_finally") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -1165,6 +1291,8 @@ object AgentService : CoroutineScope { |
|
|
|
_isAiStreaming.set(false) |
|
|
|
currentAiJob = null |
|
|
|
restartIdleCheck() |
|
|
|
} finally { |
|
|
|
tryReleaseAiAudioFocus("stopAiStream") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -1422,6 +1550,7 @@ object AgentService : CoroutineScope { |
|
|
|
// 标记AI流式输出已完成 |
|
|
|
_isAiStreaming.set(false) |
|
|
|
currentAiJob = null |
|
|
|
tryReleaseAiAudioFocus("aiOnComplete") |
|
|
|
} catch (e: Exception) { |
|
|
|
_log("ERROR", "[Agent] AI onComplete异常: ${e.message}") |
|
|
|
} |
|
|
|
@ -1447,6 +1576,7 @@ object AgentService : CoroutineScope { |
|
|
|
// 标记AI流式输出已完成 |
|
|
|
_isAiStreaming.set(false) |
|
|
|
currentAiJob = null |
|
|
|
tryReleaseAiAudioFocus("aiOnError") |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
_log("ERROR", "[Agent] AI onError异常: ${e.message}") |
|
|
|
@ -1616,6 +1746,7 @@ object AgentService : CoroutineScope { |
|
|
|
_isTtsSpeaking.set(false) |
|
|
|
restartIdleCheck() // 状态变化,重启检测 |
|
|
|
sendEvent("tts_stopped", mapOf("status" to "stopped")) |
|
|
|
tryReleaseAiAudioFocus("stopTts") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -3043,29 +3174,48 @@ object AgentService : CoroutineScope { |
|
|
|
* 自动暂停音乐 |
|
|
|
*/ |
|
|
|
private fun autopauseMusic(){ |
|
|
|
_log("INFO", "[Agent][音乐] autopauseMusic 进入 type=$musicServiceType isplaymusic=$isplaymusic") |
|
|
|
if ( musicServiceType == "QQ" ){ |
|
|
|
QQMusicSingleton.getInstance().autopauseMusic { result -> |
|
|
|
if (result.isSuccess) { |
|
|
|
isplaymusic = true |
|
|
|
_log("INFO", "[Agent][音乐] QQ 音乐暂停成功,isplaymusic=true") |
|
|
|
} else { |
|
|
|
_log("WARNING", "[Agent][音乐] QQ 音乐暂停失败: ${result.exceptionOrNull()?.message}") |
|
|
|
} |
|
|
|
} |
|
|
|
}else if ( musicServiceType == "WY" ){ |
|
|
|
if (MusicService.isPlaying()){ |
|
|
|
MusicSingleton.startServiceWithCommand(context, "pause") |
|
|
|
isplaymusic = true |
|
|
|
_log("INFO", "[Agent][音乐] WY 音乐暂停指令已发,isplaymusic=true") |
|
|
|
} else { |
|
|
|
_log("INFO", "[Agent][音乐] WY 当前未在播放,跳过暂停") |
|
|
|
} |
|
|
|
} else { |
|
|
|
_log("INFO", "[Agent][音乐] 未配置 QQ/WY 内置音乐源,跳过 autopauseMusic") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private fun resumeMusic() { |
|
|
|
_log("INFO", "[Agent][音乐] resumeMusic 进入 type=$musicServiceType isplaymusic=$isplaymusic") |
|
|
|
if ( isplaymusic ){ |
|
|
|
Log.d(TAG, "音乐恢复 : $musicServiceType") |
|
|
|
isplaymusic = false |
|
|
|
if ( musicServiceType == "QQ" ){ |
|
|
|
_log("INFO", "[Agent][音乐] 调用 QQMusic.resumeMusic") |
|
|
|
QQMusicSingleton.getInstance().resumeMusic {} |
|
|
|
}else if ( musicServiceType == "WY" ){ |
|
|
|
_log("INFO", "[Agent][音乐] 调用 WY MusicSingleton resume") |
|
|
|
MusicSingleton.startServiceWithCommand(context, "resume") |
|
|
|
} else { |
|
|
|
_log("WARNING", "[Agent][音乐] isplaymusic=true 但 musicServiceType 未知: $musicServiceType") |
|
|
|
} |
|
|
|
} else { |
|
|
|
// isplaymusic=false 有两种情况: |
|
|
|
// 1) 本轮 AI 期间没暂停过内置音乐(因为外部音乐走音频焦点路径,不走这里); |
|
|
|
// 2) autopauseMusic 被调但没成功(比如 QQ SDK 调用失败)。 |
|
|
|
// 任一情况下都不应该操作内置音乐,但需要在日志里区分出来 |
|
|
|
_log("INFO", "[Agent][音乐] isplaymusic=false,无内置音乐需要恢复(外部音乐由音频焦点恢复)") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|