Browse Source

上传音乐播放能力

weicu
liwei1dao 1 year ago
parent
commit
0749c2259f
  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(
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;

1
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"))
}

4
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")
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.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)
}
}

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

@ -1,6 +1,12 @@
<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" />
<!-- Android 14 及以上需要声明具体的前台服务类型权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<application>
<service
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
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.app.*
import android.content.Context
import android.content.Intent
import android.media.AudioManager
@ -13,8 +9,7 @@ import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import com.yunqiinnovation.music_service.MusicServiceStarter
import kotlinx.coroutines.CoroutineScope
import com.yunqiinnovation.deepvoice_utils.FileLogger
import java.util.concurrent.atomic.AtomicBoolean
class MusicService : Service() {
@ -23,131 +18,163 @@ class MusicService : Service() {
private const val TAG = "MusicService"
private const val CHANNEL_ID = "music_media_service_channel"
private const val NOTIFICATION_ID = 1002
private const val PREF_NAME = "deep_voice_config"
// 用于跟踪服务是否正在运行
private val isRunning = AtomicBoolean(false)
// 获取服务运行状态
fun isServiceRunning(): Boolean = isRunning.get()
}
private var mediaPlayer: MediaPlayer? = null
private val playlist = mutableListOf<String>() // 可扩展支持播放列表
private val playlist = mutableListOf<String>() // 可选:初始化为预置歌曲
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
}

Loading…
Cancel
Save