diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 7428e3b19..7e85a3fac 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -265,6 +265,7 @@ class AgentController extends GetxController { messages[lastIndex] = Message( isUser: false, text: messages[lastIndex].text + token, + meta: messages[lastIndex].meta, timestamp: messages[lastIndex].timestamp, ); } @@ -337,19 +338,23 @@ class AgentController extends GetxController { final responseId = event.data['responseId'] ?? ''; final result = event.data['result'] ?? ''; final functionCall = event.data['function_call'] ?? ''; - if (!result.isBlank) { - final meta = result["meta"] ?? {}; - //不为空 - // 如果是新的回复或者响应ID改变,创建新消息 - if (_isNewAssistantResponse || - (_currentResponseId != responseId && responseId.isNotEmpty)) { - messages.add(Message(isUser: false, text: "", meta: meta)); + 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, @@ -359,8 +364,9 @@ class AgentController extends GetxController { ); } } + // 滚动到底部 + _scrollToBottom(); } - logger.i('Fultter 结束调用 mcp: $result, $functionCall'); break; default: break; diff --git a/local_plugins/agent_service/android/build.gradle.kts b/local_plugins/agent_service/android/build.gradle.kts index 5a0a26504..b6c1b4d21 100644 --- a/local_plugins/agent_service/android/build.gradle.kts +++ b/local_plugins/agent_service/android/build.gradle.kts @@ -48,4 +48,5 @@ dependencies { add("compileOnly", project(":deepvoice_utils")) add("compileOnly", project(":chat_storage")) add("compileOnly", project(":ble_service")) + add("compileOnly", project(":music_service")) } \ No newline at end of file diff --git a/local_plugins/agent_service/android/settings.gradle.kts b/local_plugins/agent_service/android/settings.gradle.kts index 181d483f4..0aa37a16f 100644 --- a/local_plugins/agent_service/android/settings.gradle.kts +++ b/local_plugins/agent_service/android/settings.gradle.kts @@ -5,11 +5,11 @@ include(":azure_speech") include(":open_ai_service") include(":deepvoice_utils") include(":chat_storage") -include(":deepvoice_utils") +include(":music_service") // 设置项目路径 project(":azure_speech").projectDir = file("../../azure_speech/android") project(":open_ai_service").projectDir = file("../../open_ai_service/android") project(":deepvoice_utils").projectDir = file("../../deepvoice_utils/android") project(":chat_storage").projectDir = file("../../chat_storage/android") -project(":deepvoice_utils").projectDir = file("../../deepvoice_utils/android") \ No newline at end of file +project(":music_service").projectDir = file("../../music_service/android") \ No newline at end of file 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 35ed506ed..92a329613 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 @@ -7,6 +7,7 @@ import com.yunqiinnovation.azure_speech.AzureAsrHelper import com.yunqiinnovation.azure_speech.AzureTtsHelper import com.yunqiinnovation.open_ai_service.OpenAIService import com.yunqiinnovation.chat_storage.ChatStorageHelper +import com.yunqiinnovation.music_service.MusicServiceStarter import com.yunqiinnovation.deepvoice_utils.FileLogger import io.flutter.plugin.common.MethodChannel import kotlinx.coroutines.* @@ -672,6 +673,19 @@ object AgentService : CoroutineScope { "result" to functionCallResult.toString(), )) FileLogger.d(TAG, "mcp调用结果: $functionCallResult") + 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 musicUrl = cardMusic.optString("url", "") + val name = cardMusic.optString("name", "") + val sgener = cardMusic.optString("sgener", "") + processMusicPlay(musicUrl, name, sgener) + } + } } } ) @@ -948,4 +962,19 @@ object AgentService : CoroutineScope { return true } + + /** + * 处理音乐播放 + * 将ai返回的音乐连接 在后台启动播放 + * + * @param musicUrl 图片文件路径 + * @param name 歌曲名称,默认为空 + * @param singer 歌手,默认为false + * @return 是否成功开始处理 + */ + fun processMusicPlay(musicUrl:String,name: String = "",singer:String = ""){ + // 在其他 Service、BroadcastReceiver 或 Application 中调用 + FileLogger.i(TAG, "启动音乐服务 播放音乐 $musicUrl") + MusicServiceStarter.startServiceWithCommand(context, "play", url = musicUrl) + } } \ No newline at end of file diff --git a/local_plugins/music_service/android/src/main/AndroidManifest.xml b/local_plugins/music_service/android/src/main/AndroidManifest.xml index 7653a17a6..bacfd3dee 100644 --- a/local_plugins/music_service/android/src/main/AndroidManifest.xml +++ b/local_plugins/music_service/android/src/main/AndroidManifest.xml @@ -1,6 +1,12 @@ + package="com.yunqiinnovation.music_service"> + + + + + + () // 可扩展支持播放列表 + private val playlist = mutableListOf() // 可选:初始化为预置歌曲 private var currentIndex = 0 + private var isPlaying = false override fun onCreate() { super.onCreate() - val notification = createNotification() // 创建一个非空通知 - startForeground(1, notification) - } - private var isPlaying = false - /** - * 创建前台服务通知 - */ - private fun createNotification(): Notification { - val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - // Android 8.0+ 需要创建通知渠道 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel( - CHANNEL_ID, - "音乐播放服务", - NotificationManager.IMPORTANCE_LOW - ) - manager.createNotificationChannel(channel) - } - - // 点击通知打开主界面 - val intent = packageManager.getLaunchIntentForPackage(packageName)?.apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP - } - val pendingIntent = if (intent != null) { - PendingIntent.getActivity( - this, - 0, - intent, - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - ) - } else { - null - } - - // 构建通知 - val builder = NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("DeepSound") - .setContentText(if (isPlaying) "正在播放" else "已暂停") - .setSmallIcon(android.R.drawable.ic_media_play) - - // 设置点击意图 - pendingIntent?.let { builder.setContentIntent(it) } - - return builder.build() + FileLogger.init(this) + FileLogger.i(TAG, "MusicService 正在创建...") + isRunning.set(true) + Log.i(TAG, "MusicService Created") + startForeground(NOTIFICATION_ID, createNotification("等待播放...")) + isRunning.set(true) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val command = intent?.getStringExtra("command") ?: return START_NOT_STICKY Log.i(TAG, "onStartCommand: $command") + when (command) { "play" -> { val url = intent.getStringExtra("url") ?: return START_NOT_STICKY + if (!playlist.contains(url)) playlist.add(url) + currentIndex = playlist.indexOf(url) play(url) } - "pause" -> mediaPlayer?.pause() - "resume" -> mediaPlayer?.start() + "pause" -> pause() + "resume" -> resume() "stop" -> stopSelf() "next" -> { - currentIndex++ - if (currentIndex < playlist.size) play(playlist[currentIndex]) + if (currentIndex + 1 < playlist.size) { + currentIndex++ + play(playlist[currentIndex]) + } } "previous" -> { - currentIndex-- - if (currentIndex >= 0) play(playlist[currentIndex]) + if (currentIndex - 1 >= 0) { + currentIndex-- + play(playlist[currentIndex]) + } } "setVolume" -> { - val volume = intent.getFloatExtra("volume", 1.0f) + val volume = intent.getFloatExtra("volume", 1.0f).coerceIn(0f, 1f) mediaPlayer?.setVolume(volume, volume) } } + return START_STICKY } private fun play(url: String) { - Log.i(TAG, "开始播放音乐: $url") - mediaPlayer?.release() + Log.i(TAG, "播放音乐: $url") + releaseMediaPlayer() + mediaPlayer = MediaPlayer().apply { setDataSource(url) setAudioStreamType(AudioManager.STREAM_MUSIC) + setOnPreparedListener { + start() + this@MusicService.isPlaying = true + updateNotification("正在播放") + Log.i(TAG, "开始播放") + } + setOnCompletionListener { + Log.i(TAG, "播放完成") + this@MusicService.isPlaying = false + stopSelf() + } + setOnErrorListener { _, what, extra -> + Log.e(TAG, "播放错误: what=$what extra=$extra") + this@MusicService.isPlaying = false + stopSelf() + true + } prepareAsync() - setOnPreparedListener { start() } - setOnCompletionListener { stopSelf() } } -// startForeground(1, NotificationUtils.createNotification(this, "Playing...")) + } + + private fun pause() { + mediaPlayer?.let { + if (it.isPlaying) { + it.pause() + this@MusicService.isPlaying = false + updateNotification("已暂停") + Log.i(TAG, "暂停播放") + } + } + } + + private fun resume() { + mediaPlayer?.let { + if (!it.isPlaying) { + it.start() + this@MusicService.isPlaying = true + updateNotification("继续播放") + Log.i(TAG, "继续播放") + } + } + } + + private fun releaseMediaPlayer() { + mediaPlayer?.apply { + stop() + release() + } + mediaPlayer = null + } + + private fun updateNotification(contentText: String) { + val notification = createNotification(contentText) + val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + manager.notify(NOTIFICATION_ID, notification) + } + + private fun createNotification(contentText: String): Notification { + val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + CHANNEL_ID, + "音乐播放服务", + NotificationManager.IMPORTANCE_LOW + ) + manager.createNotificationChannel(channel) + } + + val intent = packageManager.getLaunchIntentForPackage(packageName)?.apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + } + val pendingIntent = intent?.let { + PendingIntent.getActivity( + this, 0, it, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + } + + return NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle("DeepSound") + .setContentText(contentText) + .setSmallIcon(android.R.drawable.ic_media_play) + .setContentIntent(pendingIntent) + .setOngoing(true) + .build() } override fun onDestroy() { - mediaPlayer?.release() + Log.i(TAG, "服务销毁,释放播放器") + releaseMediaPlayer() stopForeground(true) + isRunning.set(false) super.onDestroy() } override fun onBind(intent: Intent?): IBinder? = null - - /** - * 释放资源 - */ - fun release() { - try { - mediaPlayer?.apply { - if (isPlaying) stop() - release() - } - mediaPlayer = null - } catch (e: Exception) { - // 忽略异常 - } - } - /** - * 是否正在播放 - */ - fun isPlaying() = mediaPlayer?.isPlaying == true }