Browse Source

上传音乐播放能力

newdev_shunjiawei
liwei1dao 1 year ago
parent
commit
5e17884523
  1. 26
      lib/modules/agent/controllers/agent_controller.dart
  2. 1
      local_plugins/agent_service/android/build.gradle.kts
  3. 4
      local_plugins/agent_service/android/settings.gradle.kts
  4. 29
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  5. 8
      local_plugins/music_service/android/src/main/AndroidManifest.xml
  6. 205
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt

26
lib/modules/agent/controllers/agent_controller.dart

@ -265,6 +265,7 @@ class AgentController extends GetxController {
messages[lastIndex] = Message( messages[lastIndex] = Message(
isUser: false, isUser: false,
text: messages[lastIndex].text + token, text: messages[lastIndex].text + token,
meta: messages[lastIndex].meta,
timestamp: messages[lastIndex].timestamp, timestamp: messages[lastIndex].timestamp,
); );
} }
@ -337,19 +338,23 @@ class AgentController extends GetxController {
final responseId = event.data['responseId'] ?? ''; final responseId = event.data['responseId'] ?? '';
final result = event.data['result'] ?? ''; final result = event.data['result'] ?? '';
final functionCall = event.data['function_call'] ?? ''; final functionCall = event.data['function_call'] ?? '';
if (!result.isBlank) { logger.i('Flutter 结束调用 mcp: $result, $functionCall');
final meta = result["meta"] ?? {};
//不为空 if (result.isNotEmpty) {
// 如果是新的回复或者响应ID改变,创建新消息 final parsedResult = jsonDecode(result);
if (_isNewAssistantResponse || final meta = parsedResult["meta"] ?? "";
(_currentResponseId != responseId && responseId.isNotEmpty)) { final metaResult = jsonDecode(meta);
messages.add(Message(isUser: false, text: "", meta: meta));
logger.i('Flutter 结束调用 mcp metaResult: $metaResult');
// 判断是否为新的回复或响应ID是否改变
if (_isNewAssistantResponse || _currentResponseId != responseId) {
// 添加新消息
messages.add(Message(isUser: false, text: "", meta: metaResult));
_isNewAssistantResponse = false; _isNewAssistantResponse = false;
_currentResponseId = responseId; _currentResponseId = responseId;
} else { } else {
// 否则更新最后一条消息 // 更新最后一条消息
final lastIndex = messages.length - 1; final lastIndex = messages.length - 1;
if (lastIndex >= 0 && !messages[lastIndex].isUser) { if (lastIndex >= 0 && !messages[lastIndex].isUser) {
messages[lastIndex] = Message( messages[lastIndex] = Message(
isUser: false, isUser: false,
@ -359,8 +364,9 @@ class AgentController extends GetxController {
); );
} }
} }
// 滚动到底部
_scrollToBottom();
} }
logger.i('Fultter 结束调用 mcp: $result, $functionCall');
break; break;
default: default:
break; break;

1
local_plugins/agent_service/android/build.gradle.kts

@ -48,4 +48,5 @@ dependencies {
add("compileOnly", project(":deepvoice_utils")) add("compileOnly", project(":deepvoice_utils"))
add("compileOnly", project(":chat_storage")) add("compileOnly", project(":chat_storage"))
add("compileOnly", project(":ble_service")) add("compileOnly", project(":ble_service"))
add("compileOnly", project(":music_service"))
} }

4
local_plugins/agent_service/android/settings.gradle.kts

@ -5,11 +5,11 @@ include(":azure_speech")
include(":open_ai_service") include(":open_ai_service")
include(":deepvoice_utils") include(":deepvoice_utils")
include(":chat_storage") include(":chat_storage")
include(":deepvoice_utils") include(":music_service")
// 设置项目路径 // 设置项目路径
project(":azure_speech").projectDir = file("../../azure_speech/android") project(":azure_speech").projectDir = file("../../azure_speech/android")
project(":open_ai_service").projectDir = file("../../open_ai_service/android") project(":open_ai_service").projectDir = file("../../open_ai_service/android")
project(":deepvoice_utils").projectDir = file("../../deepvoice_utils/android") project(":deepvoice_utils").projectDir = file("../../deepvoice_utils/android")
project(":chat_storage").projectDir = file("../../chat_storage/android") project(":chat_storage").projectDir = file("../../chat_storage/android")
project(":deepvoice_utils").projectDir = file("../../deepvoice_utils/android") project(":music_service").projectDir = file("../../music_service/android")

29
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.azure_speech.AzureTtsHelper
import com.yunqiinnovation.open_ai_service.OpenAIService import com.yunqiinnovation.open_ai_service.OpenAIService
import com.yunqiinnovation.chat_storage.ChatStorageHelper import com.yunqiinnovation.chat_storage.ChatStorageHelper
import com.yunqiinnovation.music_service.MusicServiceStarter
import com.yunqiinnovation.deepvoice_utils.FileLogger import com.yunqiinnovation.deepvoice_utils.FileLogger
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.* import kotlinx.coroutines.*
@ -672,6 +673,19 @@ object AgentService : CoroutineScope {
"result" to functionCallResult.toString(), "result" to functionCallResult.toString(),
)) ))
FileLogger.d(TAG, "mcp调用结果: $functionCallResult") 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 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)
}
} }

8
local_plugins/music_service/android/src/main/AndroidManifest.xml

@ -1,6 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yunqiinnovation.music_service"> package="com.yunqiinnovation.music_service">
<!-- 基础前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Android 14 及以上需要声明具体的前台服务类型权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<application> <application>
<service <service
android:name=".MusicService" android:name=".MusicService"

205
local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt

@ -1,10 +1,6 @@
package com.yunqiinnovation.music_service package com.yunqiinnovation.music_service
import android.app.Notification import android.app.*
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.media.AudioManager import android.media.AudioManager
@ -13,8 +9,7 @@ import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.yunqiinnovation.music_service.MusicServiceStarter import com.yunqiinnovation.deepvoice_utils.FileLogger
import kotlinx.coroutines.CoroutineScope
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
class MusicService : Service() { class MusicService : Service() {
@ -23,131 +18,163 @@ class MusicService : Service() {
private const val TAG = "MusicService" private const val TAG = "MusicService"
private const val CHANNEL_ID = "music_media_service_channel" private const val CHANNEL_ID = "music_media_service_channel"
private const val NOTIFICATION_ID = 1002 private const val NOTIFICATION_ID = 1002
private const val PREF_NAME = "deep_voice_config"
// 用于跟踪服务是否正在运行
private val isRunning = AtomicBoolean(false) private val isRunning = AtomicBoolean(false)
// 获取服务运行状态
fun isServiceRunning(): Boolean = isRunning.get() fun isServiceRunning(): Boolean = isRunning.get()
} }
private var mediaPlayer: MediaPlayer? = null private var mediaPlayer: MediaPlayer? = null
private val playlist = mutableListOf<String>() // 可扩展支持播放列表 private val playlist = mutableListOf<String>() // 可选:初始化为预置歌曲
private var currentIndex = 0 private var currentIndex = 0
private var isPlaying = false
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
val notification = createNotification() // 创建一个非空通知 FileLogger.init(this)
startForeground(1, notification) FileLogger.i(TAG, "MusicService 正在创建...")
} isRunning.set(true)
private var isPlaying = false Log.i(TAG, "MusicService Created")
/** startForeground(NOTIFICATION_ID, createNotification("等待播放..."))
* 创建前台服务通知 isRunning.set(true)
*/
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()
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val command = intent?.getStringExtra("command") ?: return START_NOT_STICKY val command = intent?.getStringExtra("command") ?: return START_NOT_STICKY
Log.i(TAG, "onStartCommand: $command") Log.i(TAG, "onStartCommand: $command")
when (command) { when (command) {
"play" -> { "play" -> {
val url = intent.getStringExtra("url") ?: return START_NOT_STICKY val url = intent.getStringExtra("url") ?: return START_NOT_STICKY
if (!playlist.contains(url)) playlist.add(url)
currentIndex = playlist.indexOf(url)
play(url) play(url)
} }
"pause" -> mediaPlayer?.pause() "pause" -> pause()
"resume" -> mediaPlayer?.start() "resume" -> resume()
"stop" -> stopSelf() "stop" -> stopSelf()
"next" -> { "next" -> {
currentIndex++ if (currentIndex + 1 < playlist.size) {
if (currentIndex < playlist.size) play(playlist[currentIndex]) currentIndex++
play(playlist[currentIndex])
}
} }
"previous" -> { "previous" -> {
currentIndex-- if (currentIndex - 1 >= 0) {
if (currentIndex >= 0) play(playlist[currentIndex]) currentIndex--
play(playlist[currentIndex])
}
} }
"setVolume" -> { "setVolume" -> {
val volume = intent.getFloatExtra("volume", 1.0f) val volume = intent.getFloatExtra("volume", 1.0f).coerceIn(0f, 1f)
mediaPlayer?.setVolume(volume, volume) mediaPlayer?.setVolume(volume, volume)
} }
} }
return START_STICKY return START_STICKY
} }
private fun play(url: String) { private fun play(url: String) {
Log.i(TAG, "开始播放音乐: $url") Log.i(TAG, "播放音乐: $url")
mediaPlayer?.release() releaseMediaPlayer()
mediaPlayer = MediaPlayer().apply { mediaPlayer = MediaPlayer().apply {
setDataSource(url) setDataSource(url)
setAudioStreamType(AudioManager.STREAM_MUSIC) 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() 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() { override fun onDestroy() {
mediaPlayer?.release() Log.i(TAG, "服务销毁,释放播放器")
releaseMediaPlayer()
stopForeground(true) stopForeground(true)
isRunning.set(false)
super.onDestroy() super.onDestroy()
} }
override fun onBind(intent: Intent?): IBinder? = null 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
} }

Loading…
Cancel
Save