diff --git a/lib/data/services/music_manager.dart b/lib/data/services/music_manager.dart index 8bfea2f47..d74fa0b83 100644 --- a/lib/data/services/music_manager.dart +++ b/lib/data/services/music_manager.dart @@ -52,7 +52,7 @@ class MusicManager extends GetxService { } //播放 - Future play(Map song) async { + Future play(Map song) async { await _musicService.playMusic(song); } diff --git a/lib/data/services/network/api.dart b/lib/data/services/network/api.dart index ff8adaa70..b2f43ac10 100644 --- a/lib/data/services/network/api.dart +++ b/lib/data/services/network/api.dart @@ -98,4 +98,13 @@ class Api { params: params, ); } + + //获取音乐连接 + static getMusicUrl([params]) { + return DioManager().request( + NWMethod.post, + '/api/home/music_getmusicurl', + params: params, + ); + } } diff --git a/lib/data/services/network/nw_api.dart b/lib/data/services/network/nw_api.dart index 94d7115f8..8feff1db5 100644 --- a/lib/data/services/network/nw_api.dart +++ b/lib/data/services/network/nw_api.dart @@ -1,5 +1,5 @@ class NWApi { // static const baseApi = "https://www.deapsound.com"; static const baseApi = "https://api.deapsound.com"; - //static const baseApi = "https://api.ideapsound.com"; + // static const baseApi = "https://api.ideapsound.com"; } diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 698a44d99..e322eefaf 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -7,6 +7,7 @@ import 'package:deep_voice/data/models/user_Info.dart'; import 'package:deep_voice/data/services/location_manager.dart'; import 'package:deep_voice/data/services/music_manager.dart'; import 'package:deep_voice/data/services/navigation_manager.dart'; +import 'package:deep_voice/data/services/network/api.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:agent_service/agent_service.dart'; @@ -805,7 +806,32 @@ class AgentController extends GetxController { } // 播放音乐 - void musicPlay(Map song) { + void musicPlay(Map song) async { + final id = song['id'] ?? ""; + final timestampMs = song['ts'] ?? 0; + // 当前时间 + final now = DateTime.now(); + // 将时间戳转换为 DateTime + final tsTime = DateTime.fromMillisecondsSinceEpoch(timestampMs).add( + const Duration(minutes: 10), // 转换为北京时间 + ); + // 判断是否过期 + if (tsTime.isBefore(now)) { + //获取新的连接 + var response = await Api.getMusicUrl({ + "ids": [id] + }); + // 2. 将响应数据转换为User对象 + try { + print('getMusicUrl 响应数据: ${response.toString()}'); // 调试输出原始数据 + final urldata = response['urls'][0] as Map; + song["url"] = urldata['url'] ?? ''; + } catch (e) { + Logger.e(TAG, '转换响应数据失败: $e'); + return; + } + } + _musiceManager.play(song); isPlaying.value = true; currentUrl.value = song['url'] ?? ''; diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index c72c22ee5..819c822b9 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:ffi'; import 'package:common_utils/common_utils.dart'; import 'package:deep_voice/modules/agent/views/navigation_card.dart'; @@ -233,7 +234,7 @@ class MessageBubble extends GetView { if (meta!.keys.contains('music_play')) { final card_music = meta!["music_play"]['card_music'] as Map; - final musicId = card_music!['id'].toString(); + final musicId = card_music!['id']; final musicName = card_music!['name'] as String; final musicUrl = card_music!['url'] as String; final musicSinger = card_music!['sgener'] as String; diff --git a/lib/modules/agent/views/music_card.dart b/lib/modules/agent/views/music_card.dart index d76270cad..0202e1bb8 100644 --- a/lib/modules/agent/views/music_card.dart +++ b/lib/modules/agent/views/music_card.dart @@ -3,12 +3,12 @@ import 'package:get/get.dart'; import '../controllers/agent_controller.dart'; /// 构建音乐项目 -Widget buildMusicItem(BuildContext context, String id, String name, String url, +Widget buildMusicItem(BuildContext context, int id, String name, String url, String singer, String image) { final controller = Get.find(); // 获取控制器 final isDarkMode = Theme.of(context).brightness == Brightness.dark; - Map song = { + Map song = { 'id': id, 'url': url, 'title': name, @@ -19,7 +19,7 @@ Widget buildMusicItem(BuildContext context, String id, String name, String url, return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( - color: isDarkMode ? Color(0xFF1E1E1E) : Colors.white, + color: isDarkMode ? const Color(0xFF1E1E1E) : Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( @@ -154,6 +154,7 @@ Widget buildMusicContent( Icon( Icons.music_note, size: 16, + // ignore: deprecated_member_use color: Theme.of(context).primaryColor.withOpacity(0.7), ), const SizedBox(width: 6), @@ -175,7 +176,7 @@ Widget buildMusicContent( ...playlist.asMap().entries.take(4).map((entry) { final index = entry.key; final item = entry.value; - final id = item["id"]?.toString() ?? ""; + final id = item["id"] ?? 0; final name = item["name"] ?? "未知歌曲"; final url = item["url"] ?? ""; final singer = item["sgener"] ?? "未知歌手"; 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 c5a3b18a3..adccc22f8 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 @@ -612,7 +612,7 @@ object AgentService : CoroutineScope { val responseBuilder = StringBuilder() var aiMetadata:String = "" var userMetadata = JSONObject() - var nobroadcast = false + var broadcast = false //是否播发 // 如果有图片,添加图片信息到元数据 if (hasImage) { userMetadata.put("hasImage", true) @@ -673,16 +673,18 @@ object AgentService : CoroutineScope { object : StreamCallback { override fun onToken(token: String) { responseBuilder.append(token) - if (speakResponse && !nobroadcast) { + if (speakResponse && broadcast) { ttsService?.speakStream(token) } - // 发送流式回复token - sendEvent("assistant_token", mapOf("token" to token)) + if (broadcast){ + // 发送流式回复token + sendEvent("assistant_token", mapOf("token" to token)) + } } override fun onComplete() { // 视情况决定是否朗读回复 - if (speakResponse && !nobroadcast) { + if (speakResponse && broadcast) { ttsService?.flushStream() } val response = responseBuilder.toString() @@ -737,15 +739,14 @@ object AgentService : CoroutineScope { override fun onFunctionCallResult(functionCall: JSONObject, functionCallResult: JSONObject) { audioPlayer?.stopAudio() val name = functionCall.get("name") as String; - val (metestr, broadcast)= autoHandleFcunCallResult(name,functionCallResult); + val (metestr, _broadcast)= autoHandleFcunCallResult(name,functionCallResult); aiMetadata = metestr; - nobroadcast = broadcast + broadcast = _broadcast sendEvent("function_call_result", mapOf( "function_call" to functionCall.toString(), "result" to functionCallResult.toString(), "meta" to metestr, )) - } } ) @@ -859,7 +860,7 @@ object AgentService : CoroutineScope { ) if (userMessageId != -1L) { - if (assistantMessage.isNotEmpty() ) { + if (assistantMessage.isNotEmpty() || aiMetadata.isNotEmpty()) { // 保存AI回复 val assistantMessageId = chatStorageHelper.saveMessage( sessionId = sessionId, @@ -1124,54 +1125,58 @@ object AgentService : CoroutineScope { val textStr = content.optString("text") println("协议工具返回数据 检查是否存在卡片! textStr: ${textStr}") val metadata: MutableMap = mutableMapOf() - var broadcast = false; - println("协议工具返回数据 检查是否存在卡片!") + var broadcast = true; if (textStr.isNotEmpty()) { val meta = JSONObject(textStr) metadata[toolname] = meta // 现在可以赋值 val metaStr = JSONObject(metadata as Map<*, *>).toString() + if (meta.has("broadcast")){ + broadcast = meta["broadcast"] as Boolean + } // Log.d(TAG, "mcp调用结果: $metaStr") if (meta.has("card_music")) { //音乐卡片 val cardMusic = meta.getJSONObject("card_music") - val id = cardMusic.optString("id", "") + val id = cardMusic.optInt("id", 0) val url = cardMusic.optString("url", "") val name = cardMusic.optString("name", "") val sgener = cardMusic.optString("sgener", "") val image = cardMusic.optString("image", "") + val ts = cardMusic.optInt("ts", 0) processMusicPlay( mapOf( "id" to id, "url" to url, "title" to name, "artist" to sgener, - "coverUrl" to image + "coverUrl" to image, + "ts" to ts ) ) - broadcast = true; } else if (meta.has("card_musiclist")) { //音乐列表 val cardMusiclist = meta.getJSONObject("card_musiclist") val musics = cardMusiclist.optJSONArray("musics") if (musics != null) { - val playlist = mutableListOf>() + val playlist = mutableListOf>() for (i in 0 until musics.length()) { val item = musics.optJSONObject(i) ?: continue - val id = item.optString("id", "") + val id = item.optInt("id", 0) val url = item.optString("url", "") val name = item.optString("name", "") val sgener = item.optString("sgener", "") val image = item.optString("image", "") + val ts = item.optInt("ts", 0) playlist.add( mapOf( "id" to id, "url" to url, "title" to name, "artist" to sgener, - "coverUrl" to image + "coverUrl" to image, + "ts" to ts ) ) } if (playlist.isNotEmpty()) { - broadcast = true; processMusicPlayList(playlist) } else { Log.w(TAG, "card_musiclist 中没有有效的音乐条目") @@ -1183,7 +1188,6 @@ object AgentService : CoroutineScope { val cardNavigation = meta.getJSONObject("card_navigation") val start = cardNavigation.optString("start", "") val end = cardNavigation.optString("end", "") - broadcast = true; processNavigation(start, end); } return Pair(metaStr, broadcast); @@ -1192,7 +1196,7 @@ object AgentService : CoroutineScope { // 捕获其他类型的异常(可选) println("为解析到卡片数据!") } - return Pair("", false); + return Pair("", true); } /** @@ -1205,7 +1209,7 @@ object AgentService : CoroutineScope { * @param singer 歌手名称,默认为空 * @return 是否成功开始处理 */ - fun processMusicPlay(song: Map){ + fun processMusicPlay(song: Map){ // 在其他 Service、BroadcastReceiver 或 Application 中调用 Log.i(TAG, "播放音乐: ${song}") MusicServiceStarter.startServiceWithCommand(context, command = "play", song = song) @@ -1220,7 +1224,7 @@ object AgentService : CoroutineScope { * @param singer 歌手,默认为false * @return 是否成功开始处理 */ - fun processMusicPlayList(songs:List>){ + fun processMusicPlayList(songs:List>){ // 如果 playlist 中有有效的歌曲,开始播放 if (songs.isNotEmpty()) { MusicServiceStarter.startServiceWithPlaylist(context, command = "playlist", songs = songs) 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 dfe71e4c2..b4dc2a923 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 @@ -37,7 +37,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() @@ -86,7 +86,7 @@ class MusicService : MediaBrowserServiceCompat() { val command = intent?.getStringExtra("command") ?: return START_NOT_STICKY when (command) { "play" -> intent.getSerializableExtra("song")?.let { url -> - val songMap = intent.getSerializableExtra("song") as? Map + val songMap = intent.getSerializableExtra("song") as? Map songMap?.let { song -> val songId = song["id"] ?: return@let @@ -319,15 +319,15 @@ class MusicService : MediaBrowserServiceCompat() { } } - private fun play(song: Map) { + private fun play(song: Map) { val url = song["url"] ?: return Log.i(TAG, "开始播放: $url") - val title = song["title"] ?: "未知标题" - val artist = song["artist"] ?: "未知歌手" - val coverUrl = song["coverUrl"] + val title = song["title"] as? String ?: "未知标题" + val artist = song["artist"] as? String ?: "未知歌手" + val coverUrl = song["coverUrl"] as String mediaPlayer?.release() currentStatus = true - currentUrlInternal = url + currentUrlInternal = url as String mediaPlayer = MediaPlayer().apply { setDataSource(url) setAudioStreamType(AudioManager.STREAM_MUSIC) @@ -356,9 +356,9 @@ class MusicService : MediaBrowserServiceCompat() { .build() ) val song = playlist[currentIndex] - val title = song["title"] ?: "未知标题" - val artist = song["artist"] ?: "未知歌手" - val coverUrl = song["coverUrl"] + val title = song["title"] as? String ?: "未知标题" + val artist = song["artist"] as? String ?: "未知歌手" + val coverUrl = song["coverUrl"] as String updateNotification(title,artist,coverUrl) setPlaybackState(PlaybackStateCompat.STATE_PAUSED); Log.i(TAG, "已暂停") @@ -376,9 +376,9 @@ class MusicService : MediaBrowserServiceCompat() { .build() ) val song = playlist[currentIndex] - val title = song["title"] ?: "未知标题" - val artist = song["artist"] ?: "未知歌手" - val coverUrl = song["coverUrl"] + val title = song["title"] as? String ?: "未知标题" + val artist = song["artist"] as? String ?: "未知歌手" + val coverUrl = song["coverUrl"] as String updateNotification(title,artist,coverUrl) setPlaybackState(PlaybackStateCompat.STATE_PLAYING); // Log.i(TAG, "继续播放") 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 9756b25f8..b5cfd25cb 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 @@ -27,7 +27,7 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler { Log.d(TAG, "Method call: ${call.method}") when (call.method) { "play" -> { - val song = call.argument>("song") + val song = call.argument>("song") if (song != null) { MusicServiceStarter.startServiceWithCommand(context, "play", song) result.success(null) @@ -36,7 +36,7 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler { } } "playlist" -> { - val songs = call.argument>>("songs") + val songs = call.argument>>("songs") if (songs != null && songs.isNotEmpty()) { MusicServiceStarter.startServiceWithPlaylist(context, "playlist", songs) result.success(null) 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 4ea229c4e..91ee32b28 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 @@ -18,7 +18,7 @@ import java.util.jar.Manifest object MusicServiceStarter { private val TAG = "MusicServiceStarter" //播放单曲 - fun startServiceWithCommand(context: Context, command: String, song: Map? = null, volume: Float? = null) { + 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) @@ -38,9 +38,9 @@ object MusicServiceStarter { } //启动播放列表 - fun startServiceWithPlaylist(context: Context,command: String, songs: List>,volume: Float? = null) { + fun startServiceWithPlaylist(context: Context,command: String, songs: List>,volume: Float? = null) { Log.i(TAG, "liwei------------------- startServiceWithPlaylist: $command $songs") - val serializableList = ArrayList>().apply { + val serializableList = ArrayList>().apply { songs.forEach { add(HashMap(it)) } } val intent = Intent(context, MusicService::class.java).apply { diff --git a/local_plugins/music_service/lib/music_service.dart b/local_plugins/music_service/lib/music_service.dart index 02e3756ec..082e2e2c8 100644 --- a/local_plugins/music_service/lib/music_service.dart +++ b/local_plugins/music_service/lib/music_service.dart @@ -39,7 +39,7 @@ class MusicService { _statusStream = _statusEventChannel.receiveBroadcastStream(); } - Future playMusic(Map song) async { + Future playMusic(Map song) async { await _methodChannel.invokeMethod('play', {'song': song}); }