Browse Source

上传音乐服务优化

weicu
liwei1dao 1 year ago
parent
commit
ce70a485a0
  1. 4
      lib/data/services/music_manager.dart
  2. 13
      lib/modules/agent/controllers/agent_controller.dart
  3. 25
      lib/modules/agent/views/message_bubble.dart
  4. 109
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  5. 100
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt
  6. 30
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt
  7. 36
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServiceStarter.kt
  8. 4
      local_plugins/music_service/lib/music_service.dart

4
lib/data/services/music_manager.dart

@ -23,8 +23,8 @@ class MusicManager extends GetxService {
} }
//播放 //播放
Future<void> play(String url) async { Future<void> play(Map<String, String> song) async {
await _musicService.playMusic(url); await _musicService.playMusic(song);
} }
//暂停 //暂停

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

@ -222,7 +222,8 @@ class AgentController extends GetxController {
messages.refresh(); messages.refresh();
} else { } else {
// 如果没有找到识别中的消息,添加新消息 // 如果没有找到识别中的消息,添加新消息
messages.add(Message(isUser: true, text: text, isRecognizing: false)); messages
.add(Message(isUser: true, text: text, isRecognizing: false));
} }
isProcessing.value = true; isProcessing.value = true;
@ -355,7 +356,7 @@ class AgentController extends GetxController {
// if (meta.isNotEmpty) { // if (meta.isNotEmpty) {
// // logger.i('Flutter 结束调用 mcp metaResult: $metaResult'); // // logger.i('Flutter 结束调用 mcp metaResult: $metaResult');
// // 判断是否为新的回复或响应ID是否改变 // // 判断是否为新的回复或响应ID是否改变
// if (_isNewAssistantResponse ) { // if (_isNewAssistantResponse) {
// // 添加新消息 // // 添加新消息
// messages.add(Message(isUser: false, text: "", meta: meta)); // messages.add(Message(isUser: false, text: "", meta: meta));
// _isNewAssistantResponse = false; // _isNewAssistantResponse = false;
@ -371,7 +372,7 @@ class AgentController extends GetxController {
// ); // );
// } // }
// } // }
// _scrollToBottom(); // _scrollToBottom();
// } // }
logger.i('mcp 结果处理完成'); logger.i('mcp 结果处理完成');
@ -625,10 +626,10 @@ class AgentController extends GetxController {
} }
// 播放音乐 // 播放音乐
void musicPlay(String url) { void musicPlay(Map<String, String> song) {
_musiceManager.play(url); _musiceManager.play(song);
isPlaying.value = true; isPlaying.value = true;
currentUrl.value = url; currentUrl.value = song['url'] ?? '';
} }
// 播放音乐 // 播放音乐

25
lib/modules/agent/views/message_bubble.dart

@ -197,11 +197,14 @@ class MessageBubble extends GetView<AgentController> {
// 判断消息内容,显示特殊卡片 // 判断消息内容,显示特殊卡片
if (!isUser && mate != null) { if (!isUser && mate != null) {
if (mate!.keys.contains('card_music')) { if (mate!.keys.contains('card_music')) {
final musicId = mate!['card_music']['id'] as String;
final musicName = mate!['card_music']['name'] as String; final musicName = mate!['card_music']['name'] as String;
final musicUrl = mate!['card_music']['url'] as String; final musicUrl = mate!['card_music']['url'] as String;
final musicSinger = mate!['card_music']['sgener'] 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')) { } else if (mate!.keys.contains('card_musiclist')) {
final rawList = mate!['card_musiclist']['musics'] as List; final rawList = mate!['card_musiclist']['musics'] as List;
final playlist = final playlist =
@ -327,13 +330,14 @@ class MessageBubble extends GetView<AgentController> {
...playlist.asMap().entries.map((entry) { ...playlist.asMap().entries.map((entry) {
final index = entry.key; final index = entry.key;
final item = entry.value; final item = entry.value;
final id = item["id"] ?? "";
final name = item["name"] ?? "未知歌曲"; final name = item["name"] ?? "未知歌曲";
final url = item["url"] ?? ""; final url = item["url"] ?? "";
final singer = item["sgener"] ?? "未知歌手"; final singer = item["sgener"] ?? "未知歌手";
final image = item["image"] ?? "";
return Column( return Column(
children: [ children: [
_buildMusicItem(context, name, url, singer), _buildMusicItem(context, id, name, url, singer, image),
if (index != playlist.length - 1) if (index != playlist.length - 1)
Divider(height: 1, thickness: 1, color: Colors.grey[100]), Divider(height: 1, thickness: 1, color: Colors.grey[100]),
], ],
@ -531,9 +535,16 @@ class MessageBubble extends GetView<AgentController> {
} }
/// 构建音乐项目 /// 构建音乐项目
Widget _buildMusicItem( Widget _buildMusicItem(BuildContext context, String id, String name,
BuildContext context, String name, String url, String singer) { String url, String singer, String image) {
final controller = Get.find<AgentController>(); // 获取控制器 final controller = Get.find<AgentController>(); // 获取控制器
Map<String, String> song = {
'id': id,
'url': url,
'title': name,
'artist': singer,
'coverUrl': image,
};
controller.musicStatus(); //刷新状态 controller.musicStatus(); //刷新状态
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
@ -588,13 +599,13 @@ class MessageBubble extends GetView<AgentController> {
if (controller.currentUrl.value == url) { if (controller.currentUrl.value == url) {
controller.musicPause(); controller.musicPause();
} else { } else {
controller.musicPlay(url); controller.musicPlay(song);
} }
} else { } else {
if (controller.currentUrl.value == url) { if (controller.currentUrl.value == url) {
controller.musicResume(); controller.musicResume();
} else { } else {
controller.musicPlay(url); controller.musicPlay(song);
} }
} }
}, },

109
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(), "function_call" to functionCall.toString(),
"result" to functionCallResult.toString(), "result" to functionCallResult.toString(),
)) ))
// FileLogger.d(TAG, "mcp调用结果: $functionCallResult") FileLogger.d(TAG, "mcp调用结果: $functionCallResult")
val metaStr = functionCallResult.optString("meta") metadata = autoPlayMusic(functionCallResult);
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<Map<String, String>>()
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 不是一个数组")
}
}
}
} }
} }
) )
@ -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<Map<String, String>>()
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返回的音乐连接 在后台启动播放 * 将ai返回的音乐连接 在后台启动播放
* *
* @param musicUrl 图片文件路径 * @param musicId 音乐ID
* @param musicUrl 音乐文件路径
* @param name 歌曲名称,默认为空 * @param name 歌曲名称,默认为空
* @param singer 歌手,默认为false * @param singer 歌手名称,默认为空
* @return 是否成功开始处理 * @return 是否成功开始处理
*/ */
fun processMusicPlay(musicUrl:String,name: String = "",singer:String = ""){ fun processMusicPlay(song: Map<String,String>){
// 在其他 Service、BroadcastReceiver 或 Application 中调用 // 在其他 Service、BroadcastReceiver 或 Application 中调用
FileLogger.i(TAG, "启动音乐服务 播放音乐 $musicUrl") FileLogger.i(TAG, "启动音乐服务 播放音乐 $song")
MusicServiceStarter.startServiceWithCommand(context, "play", url = musicUrl) MusicServiceStarter.startServiceWithCommand(context, command = "play", song = song)
} }
/** /**
@ -1077,11 +1088,13 @@ object AgentService : CoroutineScope {
* @param singer 歌手,默认为false * @param singer 歌手,默认为false
* @return 是否成功开始处理 * @return 是否成功开始处理
*/ */
fun processMusicPlayList(playlist:List<Map<String,String>>){ fun processMusicPlayList(songs:List<Map<String,String>>){
// 在其他 Service、BroadcastReceiver 或 Application 中调用 // 如果 playlist 中有有效的歌曲,开始播放
if (songs.isNotEmpty()) {
val urls = playlist.mapNotNull { it["url"] }.distinct() FileLogger.i(TAG, "启动音乐服务 播放音乐列表 ${songs}")
FileLogger.i(TAG, "启动音乐服务 播放音乐列表 $urls") MusicServiceStarter.startServiceWithPlaylist(context, command = "playlist", songs = songs)
MusicServiceStarter.startServiceWithPlaylist(context, "playlist", urls = urls) } else {
FileLogger.i(TAG, "音乐列表为空,未启动播放服务")
}
} }
} }

100
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.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory
import android.media.AudioManager import android.media.AudioManager
import android.media.MediaPlayer import android.media.MediaPlayer
import android.os.Build import android.os.Build
@ -17,6 +18,8 @@ import androidx.core.app.NotificationCompat
import androidx.media.MediaBrowserServiceCompat import androidx.media.MediaBrowserServiceCompat
import androidx.media.session.MediaButtonReceiver import androidx.media.session.MediaButtonReceiver
import com.yunqiinnovation.deepvoice_utils.FileLogger import com.yunqiinnovation.deepvoice_utils.FileLogger
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
class MusicService : MediaBrowserServiceCompat() { class MusicService : MediaBrowserServiceCompat() {
@ -29,7 +32,7 @@ class MusicService : MediaBrowserServiceCompat() {
private val isRunning = AtomicBoolean(false) private val isRunning = AtomicBoolean(false)
private var currentUrlInternal: String? = null private var currentUrlInternal: String? = null
private var currentStatus = false private var currentStatus = false
private var playlist = mutableListOf<String>() private var playlist = mutableListOf<Map<String,String>>()
private var currentIndex = 0 private var currentIndex = 0
fun isServiceRunning() = isRunning.get() fun isServiceRunning() = isRunning.get()
@ -46,7 +49,7 @@ class MusicService : MediaBrowserServiceCompat() {
FileLogger.init(this) FileLogger.init(this)
isRunning.set(true) isRunning.set(true)
setupMediaSession() setupMediaSession()
startForeground(NOTIFICATION_ID, createNotification("等待播放…")) startForeground(NOTIFICATION_ID, createNotification("等待播放","DeepSound",""))
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
FileLogger.i(TAG, "MediaService 启动完成") FileLogger.i(TAG, "MediaService 启动完成")
} }
@ -59,12 +62,34 @@ class MusicService : MediaBrowserServiceCompat() {
} }
val command = intent?.getStringExtra("command") ?: return START_NOT_STICKY val command = intent?.getStringExtra("command") ?: return START_NOT_STICKY
FileLogger.i(TAG, "收到命令: $command") FileLogger.i(TAG, "liwei------------------- onStartCommand: $command")
when (command) { when (command) {
"play" -> intent.getStringExtra("url")?.let { url -> "play" -> intent.getSerializableExtra("song")?.let { url ->
if (!playlist.contains(url)) playlist.add(url) val songMap = intent.getSerializableExtra("song") as? Map<String, String>
currentIndex = playlist.indexOf(url) songMap?.let { song ->
play(url) 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<HashMap<String, String>>
list?.let {
playlist.clear()
playlist.addAll(it)
currentIndex = 0
play(playlist[currentIndex])
}
} }
"pause" -> pause() "pause" -> pause()
"resume" -> resume() "resume" -> resume()
@ -106,7 +131,7 @@ class MusicService : MediaBrowserServiceCompat() {
setSessionToken(mediaSession.sessionToken) 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 val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
manager.createNotificationChannel( manager.createNotificationChannel(
@ -124,9 +149,9 @@ class MusicService : MediaBrowserServiceCompat() {
this, PlaybackStateCompat.ACTION_SKIP_TO_NEXT this, PlaybackStateCompat.ACTION_SKIP_TO_NEXT
) )
return NotificationCompat.Builder(this, CHANNEL_ID) val builder = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("DeepSound") .setContentTitle(title)
.setContentText(contentText) .setContentText(artist)
.setSmallIcon(android.R.drawable.ic_media_play) .setSmallIcon(android.R.drawable.ic_media_play)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setStyle( .setStyle(
@ -142,7 +167,31 @@ class MusicService : MediaBrowserServiceCompat() {
) )
.addAction(android.R.drawable.ic_media_next, "下一首", nextIntent) .addAction(android.R.drawable.ic_media_next, "下一首", nextIntent)
.setOngoing(currentStatus) .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<String,String>) {
val url = song["url"] ?: return
FileLogger.i(TAG, "开始播放: $url")
val title = song["title"] ?: "未知标题"
val artist = song["artist"] ?: "未知歌手"
val coverUrl = song["coverUrl"]
mediaPlayer?.release() mediaPlayer?.release()
currentStatus = true currentStatus = true
currentUrlInternal = url currentUrlInternal = url
@ -180,7 +234,7 @@ class MusicService : MediaBrowserServiceCompat() {
setAudioStreamType(AudioManager.STREAM_MUSIC) setAudioStreamType(AudioManager.STREAM_MUSIC)
setOnPreparedListener { setOnPreparedListener {
start() start()
updateNotification("正在播放") updateNotification(title,artist,coverUrl)
FileLogger.i(TAG, "开始播放: $url") FileLogger.i(TAG, "开始播放: $url")
} }
setOnCompletionListener { playNextOrStop() } setOnCompletionListener { playNextOrStop() }
@ -202,7 +256,11 @@ class MusicService : MediaBrowserServiceCompat() {
.setState(PlaybackStateCompat.STATE_PAUSED, currentPosition.toLong(), 1.0f) .setState(PlaybackStateCompat.STATE_PAUSED, currentPosition.toLong(), 1.0f)
.build() .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); setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
FileLogger.i(TAG, "已暂停") FileLogger.i(TAG, "已暂停")
@ -218,9 +276,13 @@ class MusicService : MediaBrowserServiceCompat() {
.setState(PlaybackStateCompat.STATE_PLAYING, currentPosition.toLong(), 1.0f) .setState(PlaybackStateCompat.STATE_PLAYING, currentPosition.toLong(), 1.0f)
.build() .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); setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
FileLogger.i(TAG, "继续播放") // FileLogger.i(TAG, "继续播放")
} }
} }
@ -240,8 +302,8 @@ class MusicService : MediaBrowserServiceCompat() {
else stopSelf() else stopSelf()
} }
private fun updateNotification(contentText: String) { private fun updateNotification(title: String,artist: String,coverUrl: String?) {
val notification = createNotification(contentText) val notification = createNotification(title, artist, coverUrl)
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
manager.notify(NOTIFICATION_ID, notification) manager.notify(NOTIFICATION_ID, notification)

30
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 android.content.Intent
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.yunqiinnovation.deepvoice_utils.FileLogger 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.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
@ -27,30 +27,38 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler {
FileLogger.d(TAG, "Method call: ${call.method}") FileLogger.d(TAG, "Method call: ${call.method}")
when (call.method) { when (call.method) {
"play" -> { "play" -> {
val url = call.argument<String>("url") val song = call.argument<Map<String, String>>("song")
startServiceWithCommand(context, "play", url) if (song != null) {
result.success(null) MusicServiceStarter.startServiceWithCommand(context, "play", song)
result.success(null)
} else {
result.error("INVALID_ARGUMENT", "Missing 'song' map", null)
}
} }
"playlist" -> { "playlist" -> {
val urls = call.argument<List<String>>("urls") val songs = call.argument<List<Map<String, String>>>("songs")
MusicServiceStarter.startServiceWithPlaylist(context, "playlist", urls) if (songs != null && songs.isNotEmpty()) {
result.success(null) MusicServiceStarter.startServiceWithPlaylist(context, "playlist", songs)
result.success(null)
} else {
result.error("INVALID_ARGUMENT", "Missing or empty 'songs' list", null)
}
} }
"pause" -> { "pause" -> {
startServiceWithCommand(context, "pause") MusicServiceStarter.startServiceWithCommand(context, "pause")
result.success(null) result.success(null)
} }
"resume" -> { "resume" -> {
startServiceWithCommand(context, "resume") MusicServiceStarter.startServiceWithCommand(context, "resume")
result.success(null) result.success(null)
} }
"stop" -> { "stop" -> {
startServiceWithCommand(context, "stop") MusicServiceStarter.startServiceWithCommand(context, "stop")
result.success(null) result.success(null)
} }
"setVolume" -> { "setVolume" -> {
val volume = call.argument<Double>("volume")?.toFloat() val volume = call.argument<Double>("volume")?.toFloat()
startServiceWithCommand(context, "setVolume", volume = volume) MusicServiceStarter.startServiceWithCommand(context, "setVolume", volume = volume)
result.success(null) result.success(null)
} }
"getStatus" -> { "getStatus" -> {

36
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 android.util.Log
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
//@Parcelize
//data class Song(
// val url: String,
// val title: String,
// val artist: String,
// val coverUrl: String?
//)
object MusicServiceStarter { object MusicServiceStarter {
private val TAG = "MusicServiceStarter" private val TAG = "MusicServiceStarter"
//播放单曲 //播放单曲
fun startServiceWithCommand(context: Context, command: String, url: String? = null, volume: Float? = null) { fun startServiceWithCommand(context: Context, command: String, song: Map<String,String>? = null, volume: Float? = null) {
Log.i(TAG, "startServiceWithCommand: $command $url") Log.i(TAG, "liwei------------------- startServiceWithCommand: $command $song")
val intent = Intent(context, MusicService::class.java).apply { val intent = Intent(context, MusicService::class.java).apply {
putExtra("command", command) putExtra("command", command)
url?.let { putExtra("url", it) } song?.let {
putExtra("song", HashMap(it)) // 只有非 null 时才 putExtra
}
volume?.let { putExtra("volume", it) } 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<String>?,volume: Float? = null) { fun startServiceWithPlaylist(context: Context,command: String, songs: List<Map<String,String>>,volume: Float? = null) {
Log.i(TAG, "startServiceWithPlaylist: $urls") Log.i(TAG, "liwei------------------- startServiceWithPlaylist: $command $songs")
val serializableList = ArrayList<HashMap<String, String>>().apply {
songs.forEach { add(HashMap(it)) }
}
val intent = Intent(context, MusicService::class.java).apply { val intent = Intent(context, MusicService::class.java).apply {
putExtra("command", command) putExtra("command", command)
putStringArrayListExtra("urls", ArrayList(urls!!)) // 注意类型是 ArrayList<String> putExtra("songs", serializableList) // ✅ Serializable
volume?.let { putExtra("volume", it) }
} }
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)
} }
} }

4
local_plugins/music_service/lib/music_service.dart

@ -39,8 +39,8 @@ class MusicService {
_statusStream = _statusEventChannel.receiveBroadcastStream(); _statusStream = _statusEventChannel.receiveBroadcastStream();
} }
Future<void> playMusic(String url) async { Future<void> playMusic(Map<String, String> song) async {
await _methodChannel.invokeMethod('play', {'url': url}); await _methodChannel.invokeMethod('play', {'song': song});
} }
Future<void> pauseMusic() async => _methodChannel.invokeMethod('pause'); Future<void> pauseMusic() async => _methodChannel.invokeMethod('pause');

Loading…
Cancel
Save