From 7032d15cbcba631c91d41ce88c4fc5dbbcb24f00 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 19 Aug 2025 18:21:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0qq=E9=9F=B3=E4=B9=90=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E6=8E=A7=E5=88=B6=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent_service/AgentService.kt | 12 ++ .../chat_api/QQMusicSystemFunctionHandler.kt | 136 ++++++++++++------ .../qq_music/QQMusicManager.kt | 37 +++++ .../qq_music/QQMusicSingleton.kt | 17 ++- 4 files changed, 156 insertions(+), 46 deletions(-) diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 6fc1400c2..648b37238 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -131,6 +131,9 @@ object AgentService : CoroutineScope { // 语音识别模式 private var currentRecognitionMode = "normal" // 当前识别模式:normal, ble_wakeup, phone_call, push_to_talk + //是否播放音乐中 + private var isplaymusic = false; + // 打断防抖相关 private var lastInterruptTime = 0L private val INTERRUPT_DEBOUNCE_MS = 300L @@ -358,6 +361,11 @@ object AgentService : CoroutineScope { setPackage(context.packageName) } context.sendBroadcast(intent) + QQMusicSingleton.getInstance().autopauseMusic { result -> + if (result.isSuccess) { + isplaymusic = true + } + } } TtsEventType.PLAYBACK_COMPLETED -> { //结束AI播报内容 @@ -369,6 +377,10 @@ object AgentService : CoroutineScope { setPackage(context.packageName) } context.sendBroadcast(intent) + if(isplaymusic){ + isplaymusic = false + QQMusicSingleton.getInstance().resumeMusic { } + } } TtsEventType.ERROR -> { diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt index 6b1235040..08a0578c5 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt @@ -4,6 +4,8 @@ import android.content.Context import android.util.Log import kotlinx.coroutines.delay import com.yunqiinnovation.qq_music.QQMusicSingleton +import kotlin.coroutines.suspendCoroutine +import kotlin.coroutines.resume /** * QQ音乐系统功能处理器 * 负责注册QQ音乐相关的MCP函数 @@ -195,60 +197,110 @@ private class QQMusicLoginHandler(private val context: Context?) : FunctionHandl } } } - private class QQMusicSearchHandler(private val context: Context?) : FunctionHandler { override suspend fun handle(arguments: Map): String { return try { val keyword = arguments["keyword"] as? String ?: return "{\"success\": false, \"message\": \"缺少搜索关键词\"}" val searchType = arguments["searchType"] as? Int ?: 0 // 默认搜索歌曲 val qqMusicSingleton = QQMusicSingleton.getInstance() - var result = "" - - // 直接调用搜索,内部会自动处理登录检查 - qqMusicSingleton.searchSongs(keyword) { searchResult -> - result = if (searchResult.isSuccess) { - val songs = searchResult.getOrNull() ?: emptyList() - // 根据搜索类型处理结果 - val processedSongs = when (searchType) { - 0 -> { // 歌曲搜索,只返回第一个结果 -// if (songs.isNotEmpty()) listOf(songs.first()) else emptyList() - songs - } - else -> { // 专辑、歌手等其他类型,返回所有结果 - songs + // 使用 suspendCoroutine 将回调转换为协程 + suspendCoroutine { continuation -> + qqMusicSingleton.searchSongs(keyword) { searchResult -> + val result = if (searchResult.isSuccess) { + val songs = searchResult.getOrNull() ?: emptyList() + + // 根据搜索类型处理结果 + val processedSongs = when (searchType) { + 0 -> { // 歌曲搜索,返回所有结果 + songs + } + else -> { // 专辑、歌手等其他类型,返回所有结果 + songs + } } - } - val songsJson = processedSongs.joinToString(",") { song -> - val songMap = song as Map - // 添加调试日志 - Log.d("QQMusicSearchHandler", "原始数据: $songMap") - // 修正字段名映射 - val mid = songMap["mid"] ?: "unknown" - val name = songMap["name"] ?: "unknown" - val artist = songMap["singer"] ?: "unknown" - val album = songMap["album"] ?: "" - val pic = songMap["pic"] ?: "" - val duration = songMap["duration"] ?: 0 - "{\"mid\": \"$mid\", \"name\": \"$name\", \"artist\": \"$artist\", \"album\": \"$album\", \"duration\":$duration,\"pic\": \"$pic\"}" + val songsJson = processedSongs.joinToString(",") { song -> + val songMap = song as Map + // 添加调试日志 + Log.d("QQMusicSearchHandler", "原始数据: $songMap") + // 修正字段名映射 + val mid = songMap["mid"] ?: "unknown" + val name = songMap["name"] ?: "unknown" + val artist = songMap["singer"] ?: "unknown" + val album = songMap["album"] ?: "" + val pic = songMap["pic"] ?: "" + val duration = songMap["duration"] ?: 0 + "{\"mid\": \"$mid\", \"name\": \"$name\", \"artist\": \"$artist\", \"album\": \"$album\", \"duration\":$duration,\"pic\": \"$pic\"}" + } + "{\"success\": true, \"broadcast\": false, \"message\": \"搜索成功,正在播放\", \"data\": [$songsJson]}" + } else { + val error = searchResult.exceptionOrNull() + "{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}" } - "{\"success\": true, \"broadcast\": false, \"message\": \"搜索成功,增在播放\", \"data\": [$songsJson]}" - } else { - val error = searchResult.exceptionOrNull() - "{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}" + // 恢复协程执行 + continuation.resume(result) } } - - // 等待回调完成 - delay(5000) - result.ifEmpty { "{\"success\": false, \"message\": \"搜索超时\"}" } } catch (e: Exception) { Log.e("QQMusicSearchHandler", "QQ音乐搜索失败", e) "{\"success\": false, \"message\": \"搜索异常: ${e.message}\"}" } } } +//private class QQMusicSearchHandler(private val context: Context?) : FunctionHandler { +// override suspend fun handle(arguments: Map): String { +// return try { +// val keyword = arguments["keyword"] as? String ?: return "{\"success\": false, \"message\": \"缺少搜索关键词\"}" +// val searchType = arguments["searchType"] as? Int ?: 0 // 默认搜索歌曲 +// val qqMusicSingleton = QQMusicSingleton.getInstance() +// var result = "" +// +// // 直接调用搜索,内部会自动处理登录检查 +// qqMusicSingleton.searchSongs(keyword) { searchResult -> +// result = if (searchResult.isSuccess) { +// val songs = searchResult.getOrNull() ?: emptyList() +// +// // 根据搜索类型处理结果 +// val processedSongs = when (searchType) { +// 0 -> { // 歌曲搜索,只返回第一个结果 +//// if (songs.isNotEmpty()) listOf(songs.first()) else emptyList() +// songs +// } +// else -> { // 专辑、歌手等其他类型,返回所有结果 +// songs +// } +// } +// +// val songsJson = processedSongs.joinToString(",") { song -> +// val songMap = song as Map +// // 添加调试日志 +// Log.d("QQMusicSearchHandler", "原始数据: $songMap") +// // 修正字段名映射 +// val mid = songMap["mid"] ?: "unknown" +// val name = songMap["name"] ?: "unknown" +// val artist = songMap["singer"] ?: "unknown" +// val album = songMap["album"] ?: "" +// val pic = songMap["pic"] ?: "" +// val duration = songMap["duration"] ?: 0 +// "{\"mid\": \"$mid\", \"name\": \"$name\", \"artist\": \"$artist\", \"album\": \"$album\", \"duration\":$duration,\"pic\": \"$pic\"}" +// } +// "{\"success\": true, \"broadcast\": false, \"message\": \"搜索成功,增在播放\", \"data\": [$songsJson]}" +// } else { +// val error = searchResult.exceptionOrNull() +// "{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}" +// } +// } +// +// // 等待回调完成 +// delay(5000) +// result.ifEmpty { "{\"success\": false, \"message\": \"搜索超时\"}" } +// } catch (e: Exception) { +// Log.e("QQMusicSearchHandler", "QQ音乐搜索失败", e) +// "{\"success\": false, \"message\": \"搜索异常: ${e.message}\"}" +// } +// } +//} private class QQMusicPlaySongsHandler(private val context: Context?) : FunctionHandler { override suspend fun handle(arguments: Map): String { @@ -310,7 +362,7 @@ private class QQMusicPauseHandler(private val context: Context?) : FunctionHandl qqMusicSingleton.pauseMusic { pauseResult -> result = if (pauseResult.isSuccess) { - "{\"notreply\": true, \"success\": true, \"message\": \"暂停播放\"}" + "{\"broadcast\": false, \"success\": true, \"message\": \"暂停播放\"}" } else { val error = pauseResult.exceptionOrNull() "{\"success\": false, \"message\": \"暂停失败: ${error?.message}\"}" @@ -334,7 +386,7 @@ private class QQMusicResumeHandler(private val context: Context?) : FunctionHand qqMusicSingleton.resumeMusic { resumeResult -> result = if (resumeResult.isSuccess) { - "{\"notreply\": true, \"success\": true, \"message\": \"恢复播放\"}" + "{\"broadcast\": false, \"success\": true, \"message\": \"恢复播放\"}" } else { val error = resumeResult.exceptionOrNull() "{\"success\": false, \"message\": \"恢复播放失败: ${error?.message}\"}" @@ -358,7 +410,7 @@ private class QQMusicStopHandler(private val context: Context?) : FunctionHandle qqMusicSingleton.stopMusic { stopResult -> result = if (stopResult.isSuccess) { - "{\"notreply\": true, \"success\": true, \"message\": \"停止播放\"}" + "{\"broadcast\": false, \"success\": true, \"message\": \"停止播放\"}" } else { val error = stopResult.exceptionOrNull() "{\"success\": false, \"message\": \"停止失败: ${error?.message}\"}" @@ -382,7 +434,7 @@ private class QQMusicNextHandler(private val context: Context?) : FunctionHandle qqMusicSingleton.skipToNext { nextResult -> result = if (nextResult.isSuccess) { - "{\"notreply\": true, \"success\": true, \"message\": \"切换到下一首\"}" + "{\"broadcast\": false, \"success\": true, \"message\": \"切换到下一首\"}" } else { val error = nextResult.exceptionOrNull() "{\"success\": false, \"message\": \"切换失败: ${error?.message}\"}" @@ -406,7 +458,7 @@ private class QQMusicPreviousHandler(private val context: Context?) : FunctionHa qqMusicSingleton.skipToPrevious { previousResult -> result = if (previousResult.isSuccess) { - "{\"notreply\": true, \"success\": true, \"message\": \"切换到上一首\"}" + "{\"broadcast\": false, \"success\": true, \"message\": \"切换到上一首\"}" } else { val error = previousResult.exceptionOrNull() "{\"success\": false, \"message\": \"切换失败: ${error?.message}\"}" @@ -434,7 +486,7 @@ private class QQMusicGetPlaybackStateHandler(private val context: Context?) : Fu val stateJson = state.entries.joinToString(",") { (key, value) -> "\"$key\": \"$value\"" } - "{\"notreply\": true, \"success\": true, \"message\": \"获取播放状态成功\", \"data\": {$stateJson}}" + "{\"broadcast\": false, \"success\": true, \"message\": \"获取播放状态成功\", \"data\": {$stateJson}}" } else { val error = stateResult.exceptionOrNull() "{\"success\": false, \"message\": \"获取播放状态失败: ${error?.message}\"}" diff --git a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicManager.kt b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicManager.kt index 1e4f8a5c1..b7851ff0c 100644 --- a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicManager.kt +++ b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicManager.kt @@ -319,6 +319,43 @@ class QQMusicManager(private val activity: Activity) { } } + fun autopauseMusic(callback: PlayCallback){ + if (!isBindQQMusicService || qqmusicApi == null) { + callback.onError(-1, "QQ音乐服务未连接") + return + } + // 获取播放状态 + val stateResult = qqmusicApi?.execute("getPlaybackState", null) + val stateCode = stateResult?.getInt(Keys.API_RETURN_KEY_CODE) ?: -1 + + // 处理播放状态 - 使用正确的常量 + if (stateCode == ErrorCodes.ERROR_OK) { + val playState = stateResult?.getInt(Keys.API_RETURN_KEY_DATA) ?: 0 + val isPlaying = when (playState) { + PlayState.STARTED -> true + PlayState.PREPARED -> true + PlayState.BUFFERING -> true + PlayState.PAUSED -> false + PlayState.STOPPED -> false + PlayState.ERROR -> false + else -> false + } + if(isPlaying){ + val result = qqmusicApi?.execute("pauseMusic", null) + val code = result?.getInt(Keys.API_RETURN_KEY_CODE) ?: -1 + if (code == ErrorCodes.ERROR_OK) { + callback.onSuccess() + } else { + callback.onError(code, "暂停失败") + } + }else { + callback.onError(-1, "未播放") + } + } else { + callback.onError(-1, "未播放") + } + } + /** * 暂停音乐 */ diff --git a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt index e617a7db6..6cd6de9de 100644 --- a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt +++ b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt @@ -25,8 +25,8 @@ class QQMusicSingleton private constructor() { private const val KEY_EXPIRE_TIME = "expire_time" private const val LOGIN_WAIT_DELAY_MS = 30000L // 添加搜索重试相关常量 - private const val SEARCH_TIMEOUT_MS = 50000L - private const val MAX_RETRY_COUNT = 3 + private const val SEARCH_TIMEOUT_MS = 10000L + private const val MAX_RETRY_COUNT = 1 private const val RETRY_DELAY_MS = 1000L @Volatile @@ -194,7 +194,7 @@ class QQMusicSingleton private constructor() { callback(Result.failure(Exception("未安装QQ音乐软件,请先安装QQ音乐软件"))) return } - searchWithRetry(keyword, searchType, 0, callback) + searchWithRetry(keyword, searchType, 1, callback) } /** @@ -413,7 +413,16 @@ class QQMusicSingleton private constructor() { musicManager.pauseMusic(playCallback) }(callback) } - + + + /** + * 暂停音乐 + */ + fun autopauseMusic(callback: (Result) -> Unit) { + executePlaybackAction("自动暂停") { musicManager, playCallback -> + musicManager.autopauseMusic(playCallback) + }(callback) + } /** * 恢复播放 */