From 3fa5133a62d9553222ecb52d546268544b5f9d1d Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 13 May 2025 14:34:37 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E9=9F=B3=E4=B9=90?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/music_manager.dart | 4 +- .../agent/controllers/agent_controller.dart | 13 ++- lib/modules/agent/views/message_bubble.dart | 25 ++-- .../agent_service/AgentService.kt | 109 ++++++++++-------- .../music_service/MusicService.kt | 100 +++++++++++++--- .../music_service/MusicServicePlugin.kt | 30 +++-- .../music_service/MusicServiceStarter.kt | 36 ++++-- .../music_service/lib/music_service.dart | 4 +- 8 files changed, 219 insertions(+), 102 deletions(-) diff --git a/lib/data/services/music_manager.dart b/lib/data/services/music_manager.dart index 0732f0f6b..a577881c8 100644 --- a/lib/data/services/music_manager.dart +++ b/lib/data/services/music_manager.dart @@ -23,8 +23,8 @@ class MusicManager extends GetxService { } //播放 - Future play(String url) async { - await _musicService.playMusic(url); + Future play(Map song) async { + await _musicService.playMusic(song); } //暂停 diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 045c0222e..12fdc30e7 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -222,7 +222,8 @@ class AgentController extends GetxController { messages.refresh(); } else { // 如果没有找到识别中的消息,添加新消息 - messages.add(Message(isUser: true, text: text, isRecognizing: false)); + messages + .add(Message(isUser: true, text: text, isRecognizing: false)); } isProcessing.value = true; @@ -355,7 +356,7 @@ class AgentController extends GetxController { // if (meta.isNotEmpty) { // // logger.i('Flutter 结束调用 mcp metaResult: $metaResult'); // // 判断是否为新的回复或响应ID是否改变 - // if (_isNewAssistantResponse ) { + // if (_isNewAssistantResponse) { // // 添加新消息 // messages.add(Message(isUser: false, text: "", meta: meta)); // _isNewAssistantResponse = false; @@ -371,7 +372,7 @@ class AgentController extends GetxController { // ); // } // } - + // _scrollToBottom(); // } logger.i('mcp 结果处理完成'); @@ -625,10 +626,10 @@ class AgentController extends GetxController { } // 播放音乐 - void musicPlay(String url) { - _musiceManager.play(url); + void musicPlay(Map song) { + _musiceManager.play(song); isPlaying.value = true; - currentUrl.value = url; + currentUrl.value = song['url'] ?? ''; } // 播放音乐 diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index f1d939fc2..78f1acda4 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -197,11 +197,14 @@ class MessageBubble extends GetView { // 判断消息内容,显示特殊卡片 if (!isUser && mate != null) { if (mate!.keys.contains('card_music')) { + final musicId = mate!['card_music']['id'] as String; final musicName = mate!['card_music']['name'] as String; final musicUrl = mate!['card_music']['url'] as String; final musicSinger = mate!['card_music']['sgener'] as String; + final musicImage = mate!['card_music']['image'] as String; //音乐卡片 - return _buildMusicItem(context, musicName, musicUrl, musicSinger); + return _buildMusicItem( + context, musicId, musicName, musicUrl, musicSinger, musicImage); } else if (mate!.keys.contains('card_musiclist')) { final rawList = mate!['card_musiclist']['musics'] as List; final playlist = @@ -327,13 +330,14 @@ class MessageBubble extends GetView { ...playlist.asMap().entries.map((entry) { final index = entry.key; final item = entry.value; + final id = item["id"] ?? ""; final name = item["name"] ?? "未知歌曲"; final url = item["url"] ?? ""; final singer = item["sgener"] ?? "未知歌手"; - + final image = item["image"] ?? ""; return Column( children: [ - _buildMusicItem(context, name, url, singer), + _buildMusicItem(context, id, name, url, singer, image), if (index != playlist.length - 1) Divider(height: 1, thickness: 1, color: Colors.grey[100]), ], @@ -531,9 +535,16 @@ class MessageBubble extends GetView { } /// 构建音乐项目 - Widget _buildMusicItem( - BuildContext context, String name, String url, String singer) { + Widget _buildMusicItem(BuildContext context, String id, String name, + String url, String singer, String image) { final controller = Get.find(); // 获取控制器 + Map song = { + 'id': id, + 'url': url, + 'title': name, + 'artist': singer, + 'coverUrl': image, + }; controller.musicStatus(); //刷新状态 return Padding( padding: const EdgeInsets.symmetric(vertical: 8), @@ -588,13 +599,13 @@ class MessageBubble extends GetView { if (controller.currentUrl.value == url) { controller.musicPause(); } else { - controller.musicPlay(url); + controller.musicPlay(song); } } else { if (controller.currentUrl.value == url) { controller.musicResume(); } else { - controller.musicPlay(url); + controller.musicPlay(song); } } }, 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 3ab2ae755..7ac469322 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 @@ -671,43 +671,8 @@ object AgentService : CoroutineScope { "function_call" to functionCall.toString(), "result" to functionCallResult.toString(), )) - // FileLogger.d(TAG, "mcp调用结果: $functionCallResult") - val metaStr = functionCallResult.optString("meta") - if (metaStr.isNotEmpty()) { - metadata = metaStr; - val meta = JSONObject(metaStr) - // FileLogger.d(TAG, "mcp调用结果: $metaStr") - if (meta.has("card_music")) { //音乐卡片 - val cardMusic = meta.getJSONObject("card_music") - FileLogger.d(TAG, "检查到音乐卡片: $cardMusic") - val musicUrl = cardMusic.optString("url", "") - val name = cardMusic.optString("name", "") - val sgener = cardMusic.optString("sgener", "") - processMusicPlay(musicUrl, name, sgener) - }else if (meta.has("card_musiclist")) { //音乐列表 - val cardMusiclist = meta.getJSONObject("card_musiclist") - val musics = cardMusiclist.optJSONArray("musics") - if (musics != null) { - val playlist = mutableListOf>() - for (i in 0 until musics.length()) { - val item = musics.optJSONObject(i) ?: continue - val url = item.optString("url", "") - val name = item.optString("name", "") - val sgener = item.optString("sgener", "") - if (url.isNotEmpty()) { - playlist.add(mapOf("url" to url, "name" to name, "sgener" to sgener)) - } - } - if (playlist.isNotEmpty()) { - processMusicPlayList(playlist) - } else { - FileLogger.w(TAG, "card_musiclist 中没有有效的音乐条目") - } - } else { - FileLogger.w(TAG, "card_musiclist.musics 不是一个数组") - } - } - } + FileLogger.d(TAG, "mcp调用结果: $functionCallResult") + metadata = autoPlayMusic(functionCallResult); } } ) @@ -1052,20 +1017,66 @@ object AgentService : CoroutineScope { } } } + /* + * 自动播放音乐 + * */ + fun autoPlayMusic(functionCallResult: JSONObject): String{ + val metaStr = functionCallResult.optString("meta") + if (metaStr.isNotEmpty()) { + val meta = JSONObject(metaStr) + FileLogger.d(TAG, "mcp调用结果: $metaStr") + if (meta.has("card_music")) { //音乐卡片 + val cardMusic = meta.getJSONObject("card_music") + FileLogger.d(TAG, "检查到音乐卡片: $cardMusic") + val id = cardMusic.optString("id", "") + val url = cardMusic.optString("url", "") + val name = cardMusic.optString("name", "") + val sgener = cardMusic.optString("sgener", "") + val image = cardMusic.optString("image", "") + processMusicPlay(mapOf("id" to id,"url" to url, "title" to name, "artist" to sgener,"coverUrl" to image)) + }else if (meta.has("card_musiclist")) { //音乐列表 + val cardMusiclist = meta.getJSONObject("card_musiclist") + val musics = cardMusiclist.optJSONArray("musics") + if (musics != null) { + val playlist = mutableListOf>() + for (i in 0 until musics.length()) { + val item = musics.optJSONObject(i) ?: continue + val id = item.optString("id", "") + val url = item.optString("url", "") + val name = item.optString("name", "") + val sgener = item.optString("sgener", "") + val image = item.optString("image", "") + playlist.add(mapOf("id" to id,"url" to url, "title" to name, "artist" to sgener,"coverUrl" to image)) + } + if (playlist.isNotEmpty()) { + FileLogger.w(TAG, "自动播放音乐列表 ${playlist}") + processMusicPlayList(playlist) + } else { + FileLogger.w(TAG, "card_musiclist 中没有有效的音乐条目") + } + } else { + FileLogger.w(TAG, "card_musiclist.musics 不是一个数组") + } + } + return metaStr; + } + return ""; + } /** * 处理音乐播放 * 将ai返回的音乐连接 在后台启动播放 * - * @param musicUrl 图片文件路径 + * @param musicId 音乐ID + * @param musicUrl 音乐文件路径 * @param name 歌曲名称,默认为空 - * @param singer 歌手,默认为false + * @param singer 歌手名称,默认为空 * @return 是否成功开始处理 */ - fun processMusicPlay(musicUrl:String,name: String = "",singer:String = ""){ + fun processMusicPlay(song: Map){ // 在其他 Service、BroadcastReceiver 或 Application 中调用 - FileLogger.i(TAG, "启动音乐服务 播放音乐 $musicUrl") - MusicServiceStarter.startServiceWithCommand(context, "play", url = musicUrl) + FileLogger.i(TAG, "启动音乐服务 播放音乐 $song") + MusicServiceStarter.startServiceWithCommand(context, command = "play", song = song) } /** @@ -1077,11 +1088,13 @@ object AgentService : CoroutineScope { * @param singer 歌手,默认为false * @return 是否成功开始处理 */ - fun processMusicPlayList(playlist:List>){ - // 在其他 Service、BroadcastReceiver 或 Application 中调用 - - val urls = playlist.mapNotNull { it["url"] }.distinct() - FileLogger.i(TAG, "启动音乐服务 播放音乐列表 $urls") - MusicServiceStarter.startServiceWithPlaylist(context, "playlist", urls = urls) + fun processMusicPlayList(songs:List>){ + // 如果 playlist 中有有效的歌曲,开始播放 + if (songs.isNotEmpty()) { + FileLogger.i(TAG, "启动音乐服务 播放音乐列表 ${songs}") + MusicServiceStarter.startServiceWithPlaylist(context, command = "playlist", songs = songs) + } else { + FileLogger.i(TAG, "音乐列表为空,未启动播放服务") + } } } \ No newline at end of file diff --git a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt index 5cca58168..e0091e1ff 100644 --- a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt +++ b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt @@ -5,6 +5,7 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.content.Intent +import android.graphics.BitmapFactory import android.media.AudioManager import android.media.MediaPlayer import android.os.Build @@ -17,6 +18,8 @@ import androidx.core.app.NotificationCompat import androidx.media.MediaBrowserServiceCompat import androidx.media.session.MediaButtonReceiver import com.yunqiinnovation.deepvoice_utils.FileLogger +import java.net.HttpURLConnection +import java.net.URL import java.util.concurrent.atomic.AtomicBoolean class MusicService : MediaBrowserServiceCompat() { @@ -29,7 +32,7 @@ class MusicService : MediaBrowserServiceCompat() { private val isRunning = AtomicBoolean(false) private var currentUrlInternal: String? = null private var currentStatus = false - private var playlist = mutableListOf() + private var playlist = mutableListOf>() private var currentIndex = 0 fun isServiceRunning() = isRunning.get() @@ -46,7 +49,7 @@ class MusicService : MediaBrowserServiceCompat() { FileLogger.init(this) isRunning.set(true) setupMediaSession() - startForeground(NOTIFICATION_ID, createNotification("等待播放…")) + startForeground(NOTIFICATION_ID, createNotification("等待播放","DeepSound","")) audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager FileLogger.i(TAG, "MediaService 启动完成") } @@ -59,12 +62,34 @@ class MusicService : MediaBrowserServiceCompat() { } val command = intent?.getStringExtra("command") ?: return START_NOT_STICKY - FileLogger.i(TAG, "收到命令: $command") + FileLogger.i(TAG, "liwei------------------- onStartCommand: $command") when (command) { - "play" -> intent.getStringExtra("url")?.let { url -> - if (!playlist.contains(url)) playlist.add(url) - currentIndex = playlist.indexOf(url) - play(url) + "play" -> intent.getSerializableExtra("song")?.let { url -> + val songMap = intent.getSerializableExtra("song") as? Map + songMap?.let { song -> + val songId = song["id"] ?: return@let + + val existingIndex = playlist.indexOfFirst { it["id"] == songId } + if (existingIndex == -1) { + playlist.add(song) + currentIndex = playlist.size - 1 + } else { + currentIndex = existingIndex + } + + play(song) + } + } + "playlist" -> { + + @Suppress("UNCHECKED_CAST") + val list = intent.getSerializableExtra("songs") as? ArrayList> + list?.let { + playlist.clear() + playlist.addAll(it) + currentIndex = 0 + play(playlist[currentIndex]) + } } "pause" -> pause() "resume" -> resume() @@ -106,7 +131,7 @@ class MusicService : MediaBrowserServiceCompat() { setSessionToken(mediaSession.sessionToken) } - private fun createNotification(contentText: String): Notification { + private fun createNotification(title: String,artist: String,coverUrl: String?): Notification { val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { manager.createNotificationChannel( @@ -124,9 +149,9 @@ class MusicService : MediaBrowserServiceCompat() { this, PlaybackStateCompat.ACTION_SKIP_TO_NEXT ) - return NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("DeepSound") - .setContentText(contentText) + val builder = NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle(title) + .setContentText(artist) .setSmallIcon(android.R.drawable.ic_media_play) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setStyle( @@ -142,7 +167,31 @@ class MusicService : MediaBrowserServiceCompat() { ) .addAction(android.R.drawable.ic_media_next, "下一首", nextIntent) .setOngoing(currentStatus) - .build() + + // 异步加载封面图 + if (!coverUrl.isNullOrEmpty()) { + Thread { + try { + val url = URL(coverUrl) + val connection = url.openConnection() as HttpURLConnection + connection.doInput = true + connection.connect() + val inputStream = connection.inputStream + val bitmap = BitmapFactory.decodeStream(inputStream) + + // 设置大图标并更新通知 + builder.setLargeIcon(bitmap) + val builtNotification = builder.build() + manager.notify(NOTIFICATION_ID, builtNotification) + } catch (e: Exception) { + e.printStackTrace() + manager.notify(NOTIFICATION_ID, builder.build()) + } + }.start() + } + + + return builder.build() } /** @@ -171,7 +220,12 @@ class MusicService : MediaBrowserServiceCompat() { } } - private fun play(url: String) { + private fun play(song: Map) { + val url = song["url"] ?: return + FileLogger.i(TAG, "开始播放: $url") + val title = song["title"] ?: "未知标题" + val artist = song["artist"] ?: "未知歌手" + val coverUrl = song["coverUrl"] mediaPlayer?.release() currentStatus = true currentUrlInternal = url @@ -180,7 +234,7 @@ class MusicService : MediaBrowserServiceCompat() { setAudioStreamType(AudioManager.STREAM_MUSIC) setOnPreparedListener { start() - updateNotification("正在播放") + updateNotification(title,artist,coverUrl) FileLogger.i(TAG, "开始播放: $url") } setOnCompletionListener { playNextOrStop() } @@ -202,7 +256,11 @@ class MusicService : MediaBrowserServiceCompat() { .setState(PlaybackStateCompat.STATE_PAUSED, currentPosition.toLong(), 1.0f) .build() ) - updateNotification("已暂停") + val song = playlist[currentIndex] + val title = song["title"] ?: "未知标题" + val artist = song["artist"] ?: "未知歌手" + val coverUrl = song["coverUrl"] + updateNotification(title,artist,coverUrl) setPlaybackState(PlaybackStateCompat.STATE_PLAYING); FileLogger.i(TAG, "已暂停") @@ -218,9 +276,13 @@ class MusicService : MediaBrowserServiceCompat() { .setState(PlaybackStateCompat.STATE_PLAYING, currentPosition.toLong(), 1.0f) .build() ) - updateNotification("继续播放") + val song = playlist[currentIndex] + val title = song["title"] ?: "未知标题" + val artist = song["artist"] ?: "未知歌手" + val coverUrl = song["coverUrl"] + updateNotification(title,artist,coverUrl) setPlaybackState(PlaybackStateCompat.STATE_PAUSED); - FileLogger.i(TAG, "继续播放") +// FileLogger.i(TAG, "继续播放") } } @@ -240,8 +302,8 @@ class MusicService : MediaBrowserServiceCompat() { else stopSelf() } - private fun updateNotification(contentText: String) { - val notification = createNotification(contentText) + private fun updateNotification(title: String,artist: String,coverUrl: String?) { + val notification = createNotification(title, artist, coverUrl) val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { manager.notify(NOTIFICATION_ID, notification) diff --git a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt index e85c4be44..89bf0edd8 100644 --- a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt +++ b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt @@ -4,7 +4,7 @@ import android.content.Context import android.content.Intent import androidx.core.content.ContextCompat import com.yunqiinnovation.deepvoice_utils.FileLogger -import com.yunqiinnovation.music_service.MusicServiceStarter.startServiceWithCommand +import com.yunqiinnovation.music_service.MusicServiceStarter import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel @@ -27,30 +27,38 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler { FileLogger.d(TAG, "Method call: ${call.method}") when (call.method) { "play" -> { - val url = call.argument("url") - startServiceWithCommand(context, "play", url) - result.success(null) + val song = call.argument>("song") + if (song != null) { + MusicServiceStarter.startServiceWithCommand(context, "play", song) + result.success(null) + } else { + result.error("INVALID_ARGUMENT", "Missing 'song' map", null) + } } "playlist" -> { - val urls = call.argument>("urls") - MusicServiceStarter.startServiceWithPlaylist(context, "playlist", urls) - result.success(null) + val songs = call.argument>>("songs") + if (songs != null && songs.isNotEmpty()) { + MusicServiceStarter.startServiceWithPlaylist(context, "playlist", songs) + result.success(null) + } else { + result.error("INVALID_ARGUMENT", "Missing or empty 'songs' list", null) + } } "pause" -> { - startServiceWithCommand(context, "pause") + MusicServiceStarter.startServiceWithCommand(context, "pause") result.success(null) } "resume" -> { - startServiceWithCommand(context, "resume") + MusicServiceStarter.startServiceWithCommand(context, "resume") result.success(null) } "stop" -> { - startServiceWithCommand(context, "stop") + MusicServiceStarter.startServiceWithCommand(context, "stop") result.success(null) } "setVolume" -> { val volume = call.argument("volume")?.toFloat() - startServiceWithCommand(context, "setVolume", volume = volume) + MusicServiceStarter.startServiceWithCommand(context, "setVolume", volume = volume) result.success(null) } "getStatus" -> { diff --git a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServiceStarter.kt b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServiceStarter.kt index 9d8d290a1..4c743d18a 100644 --- a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServiceStarter.kt +++ b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServiceStarter.kt @@ -5,26 +5,48 @@ import android.content.Intent import android.util.Log import androidx.core.content.ContextCompat +//@Parcelize +//data class Song( +// val url: String, +// val title: String, +// val artist: String, +// val coverUrl: String? +//) + object MusicServiceStarter { private val TAG = "MusicServiceStarter" //播放单曲 - fun startServiceWithCommand(context: Context, command: String, url: String? = null, volume: Float? = null) { - Log.i(TAG, "startServiceWithCommand: $command $url") + fun startServiceWithCommand(context: Context, command: String, song: Map? = null, volume: Float? = null) { + Log.i(TAG, "liwei------------------- startServiceWithCommand: $command $song") val intent = Intent(context, MusicService::class.java).apply { putExtra("command", command) - url?.let { putExtra("url", it) } + song?.let { + putExtra("song", HashMap(it)) // 只有非 null 时才 putExtra + } volume?.let { putExtra("volume", it) } } - ContextCompat.startForegroundService(context, intent) + val success = try { + ContextCompat.startForegroundService(context, intent) + true + } catch (e: Exception) { + Log.e("MusicServiceStarter", "liwei------------------- Failed to start service", e) + false + } + Log.d(" MusicServiceStarter", "liwei------------------- Service start success: $success") } //启动播放列表 - fun startServiceWithPlaylist(context: Context,command: String, urls: List?,volume: Float? = null) { - Log.i(TAG, "startServiceWithPlaylist: $urls") + fun startServiceWithPlaylist(context: Context,command: String, songs: List>,volume: Float? = null) { + Log.i(TAG, "liwei------------------- startServiceWithPlaylist: $command $songs") + val serializableList = ArrayList>().apply { + songs.forEach { add(HashMap(it)) } + } val intent = Intent(context, MusicService::class.java).apply { putExtra("command", command) - putStringArrayListExtra("urls", ArrayList(urls!!)) // 注意类型是 ArrayList + putExtra("songs", serializableList) // ✅ Serializable + volume?.let { putExtra("volume", it) } } ContextCompat.startForegroundService(context, intent) + } } \ No newline at end of file diff --git a/local_plugins/music_service/lib/music_service.dart b/local_plugins/music_service/lib/music_service.dart index ce3bb9e6d..02e3756ec 100644 --- a/local_plugins/music_service/lib/music_service.dart +++ b/local_plugins/music_service/lib/music_service.dart @@ -39,8 +39,8 @@ class MusicService { _statusStream = _statusEventChannel.receiveBroadcastStream(); } - Future playMusic(String url) async { - await _methodChannel.invokeMethod('play', {'url': url}); + Future playMusic(Map song) async { + await _methodChannel.invokeMethod('play', {'song': song}); } Future pauseMusic() async => _methodChannel.invokeMethod('pause'); From b7dfc99c6d7fd3c12668ddfec7534e5c827f0124 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 13 May 2025 15:42:58 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E5=BA=8F=E5=88=97=E5=8C=96=20=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent/controllers/agent_controller.dart | 59 ++++++++--------- lib/modules/agent/views/message_bubble.dart | 4 -- .../controllers/pairing_controller.dart | 63 ++++++++++--------- 3 files changed, 64 insertions(+), 62 deletions(-) diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 12fdc30e7..2aef7f7ac 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -347,35 +347,38 @@ class AgentController extends GetxController { break; case AgentServiceEventType.functionCallResult: + final responseId = event.data['responseId'] ?? ''; final result = event.data['result'] ?? ''; - final parsedResult = result.isNotEmpty ? jsonDecode(result) : {}; - final meta = parsedResult["meta"] ?? {}; - // final functionCall = event.data['function_call'] ?? ''; - logger.d('mcp 执行结果meta: $meta'); - - // if (meta.isNotEmpty) { - // // logger.i('Flutter 结束调用 mcp metaResult: $metaResult'); - // // 判断是否为新的回复或响应ID是否改变 - // if (_isNewAssistantResponse) { - // // 添加新消息 - // messages.add(Message(isUser: false, text: "", meta: meta)); - // _isNewAssistantResponse = false; - // } else { - // // 更新最后一条消息 - // final lastIndex = messages.length - 1; - // if (lastIndex >= 0 && !messages[lastIndex].isUser) { - // messages[lastIndex] = Message( - // isUser: false, - // text: messages[lastIndex].text, - // meta: meta, - // timestamp: messages[lastIndex].timestamp, - // ); - // } - // } - - // _scrollToBottom(); - // } - logger.i('mcp 结果处理完成'); + final functionCall = event.data['function_call'] ?? ''; + logger.i('Flutter 结束调用 mcp: $result, $functionCall'); + + if (result.isNotEmpty) { + final parsedResult = jsonDecode(result); + final meta = parsedResult["meta"] ?? ""; + final metaResult = jsonDecode(meta); + + logger.i('Flutter 结束调用 mcp metaResult: $metaResult'); + // 判断是否为新的回复或响应ID是否改变 + if (_isNewAssistantResponse || _currentResponseId != responseId) { + // 添加新消息 + messages.add(Message(isUser: false, text: "", meta: metaResult)); + _isNewAssistantResponse = false; + _currentResponseId = responseId; + } else { + // 更新最后一条消息 + final lastIndex = messages.length - 1; + if (lastIndex >= 0 && !messages[lastIndex].isUser) { + messages[lastIndex] = Message( + isUser: false, + text: messages[lastIndex].text, + meta: meta, + timestamp: messages[lastIndex].timestamp, + ); + } + } + // 滚动到底部 + _scrollToBottom(); + } break; default: break; diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index 78f1acda4..952551e18 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -209,14 +209,10 @@ class MessageBubble extends GetView { final rawList = mate!['card_musiclist']['musics'] as List; final playlist = rawList.map((e) => Map.from(e)).toList(); - // final playlist = - // mate!['card_musiclist']['musics'] as List>; - //音乐播放列表 return _buildMusicContent(context, playlist); } else if (mate!.keys.contains('card_weather')) { final city = mate!['card_weather']['city'] as String; - final rawList = mate!['card_weather']['dayweather'] as List; final daylist = rawList.map((e) => Map.from(e)).toList(); diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 88a349161..7ecf4e076 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -14,6 +14,8 @@ import 'package:ble_service/ble_service.dart'; class PairingController extends GetxController { static PairingController get to => Get.find(); + // 需要设备 + final bool needdeivice = false; // 状态标志 final isLoading = false.obs; @@ -28,7 +30,7 @@ class PairingController extends GetxController { // 状态描述(用于UI显示) final statusDescription = 'initializing'.tr.obs; - + // 按钮文本 final actionButtonText = '去系统设置'.obs; @@ -117,7 +119,7 @@ class PairingController extends GetxController { // 刷新连接设备并检查配对状态 statusDescription.value = 'searchingDevice'.tr; await _bluetoothService.refreshDeviceConnection(); - + // 设备连接时检查配对状态 if (isDeviceConnected) { await _checkDevicePairingStatus(); @@ -146,7 +148,7 @@ class PairingController extends GetxController { } Logger.info('发现已关联的Companion设备: $pairedMac'); - + // 检查当前连接设备与已保存的设备是否一致 if (currentDevice!.address != pairedMac) { // MAC地址不匹配 @@ -154,13 +156,13 @@ class PairingController extends GetxController { statusDescription.value = 'pleaseConnectPairedDevice'.tr; return; } - + // 判断设备名称是否符合要求 if (!_isValidDeviceName(currentDevice!.name)) { statusDescription.value = 'pleaseConnectDeepSoundDevice'.tr; return; } - + // 设备匹配且合规,标记为已配对 Logger.info('当前设备与已配对设备匹配'); _companionAssociated.value = true; @@ -223,7 +225,7 @@ class PairingController extends GetxController { _bluetoothState.value = state; _updateStatusDescription(); }); - + // 监听设备连接状态变化 _deviceWorker = ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) { @@ -238,12 +240,13 @@ class PairingController extends GetxController { /// 处理设备连接成功事件 Future _handleDeviceConnected() async { if (!isDeviceConnected || _isNavigatingToHome) return; - + // 检查配对状态,决定下一步操作 await _checkDevicePairingStatus(); - + // 如果还未配对且设备合规,启动配对流程 - if (!_companionAssociated.value && _isValidDeviceName(currentDevice!.name)) { + if (!_companionAssociated.value && + _isValidDeviceName(currentDevice!.name)) { _startCompanionAssociation(); } } @@ -256,7 +259,7 @@ class PairingController extends GetxController { actionButtonText.value = '关联DeapSound耳机'; return; } - + if (!hasPermissions) { statusDescription.value = 'permissionDenied'.tr; actionButtonText.value = '去系统设置'; @@ -271,9 +274,11 @@ class PairingController extends GetxController { if (isDeviceConnected) { if (Platform.isAndroid && !isCompanionAssociated && _isAssociating) { - statusDescription.value = '${'associatingDevice'.tr} ${currentDevice!.name}'; + statusDescription.value = + '${'associatingDevice'.tr} ${currentDevice!.name}'; } else { - statusDescription.value = '${'connectedDevice'.tr} ${currentDevice!.name}'; + statusDescription.value = + '${'connectedDevice'.tr} ${currentDevice!.name}'; } return; } @@ -286,21 +291,20 @@ class PairingController extends GetxController { statusDescription.value = 'notFound'.tr; } - /// 启动Companion设备关联流程 Future _startCompanionAssociation() async { // 如果不是Android平台,已跳转,已完成关联或正在进行关联,直接返回 - if (!Platform.isAndroid || - _isNavigatingToHome || - _companionAssociated.value || - _isAssociating || + if (!Platform.isAndroid || + _isNavigatingToHome || + _companionAssociated.value || + _isAssociating || !isDeviceConnected) { return; } - + // 重置拒绝状态 _userRejectedAssociation.value = false; - + // 设置关联状态标志 _isAssociating = true; _updateStatusDescription(); @@ -314,14 +318,14 @@ class PairingController extends GetxController { _autoNavigateToHome(); return; } - + // 检查设备名称是否符合要求 if (!_isValidDeviceName(currentDevice!.name)) { statusDescription.value = 'pleaseConnectDeepSoundDevice'.tr; _isAssociating = false; return; } - + Logger.info('开始Companion设备关联流程, 设备MAC: ${currentDevice!.address}'); var success = await _bleService.startDeviceAssociation( @@ -329,7 +333,7 @@ class PairingController extends GetxController { macAddress: currentDevice!.address, ); _isAssociating = false; - + if (success) { Logger.info('Companion设备关联成功'); _companionAssociated.value = true; @@ -338,8 +342,7 @@ class PairingController extends GetxController { // 注册后台扫描,确保应用被杀死后仍能被唤醒 await _bleService.registerBackgroundScan( macAddress: currentDevice!.address); - - + // 关联成功后跳转 _autoNavigateToHome(); } else { @@ -359,9 +362,9 @@ class PairingController extends GetxController { /// 自动跳转到主页 Future _autoNavigateToHome() async { - if (_isNavigatingToHome || !isDeviceConnected) return; + if (needdeivice && (_isNavigatingToHome || !isDeviceConnected)) return; - // 启动BLE后台服务 + // 启动BLE后台服务 await _startAgentService(); _isNavigatingToHome = true; @@ -375,7 +378,7 @@ class PairingController extends GetxController { /// 启动BLE后台服务 Future _startAgentService() async { try { - if (!isDeviceConnected) return; + if (needdeivice && !isDeviceConnected) return; final result = await BleManager.to.startAgentService(); Logger.info(result ? 'BLE后台服务启动成功' : 'BLE后台服务启动失败'); @@ -426,7 +429,7 @@ class PairingController extends GetxController { Future openAppSettings() async { await AppSettings.openAppSettings(); } - + /// 重新开始关联 void restartAssociation() { if (isDeviceConnected && !isCompanionAssociated) { @@ -435,8 +438,8 @@ class PairingController extends GetxController { } /// 跳过连接 - void skipConnecting() async{ - // 启动BLE后台服务 + void skipConnecting() async { + // 启动BLE后台服务 await _startAgentService(); _isNavigatingToHome = true; Get.offAllNamed(Routes.home);