From ce70a485a0213fdf4e8fcf5b9daf7b1b1ba8e251 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 13 May 2025 14:34:37 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E9=9F=B3=E4=B9=90=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/music_manager.dart | 4 +- .../agent/controllers/agent_controller.dart | 13 ++- lib/modules/agent/views/message_bubble.dart | 25 ++-- .../agent_service/AgentService.kt | 109 ++++++++++-------- .../music_service/MusicService.kt | 100 +++++++++++++--- .../music_service/MusicServicePlugin.kt | 30 +++-- .../music_service/MusicServiceStarter.kt | 36 ++++-- .../music_service/lib/music_service.dart | 4 +- 8 files changed, 219 insertions(+), 102 deletions(-) diff --git a/lib/data/services/music_manager.dart b/lib/data/services/music_manager.dart index 0732f0f6b..a577881c8 100644 --- a/lib/data/services/music_manager.dart +++ b/lib/data/services/music_manager.dart @@ -23,8 +23,8 @@ class MusicManager extends GetxService { } //播放 - Future play(String url) async { - await _musicService.playMusic(url); + Future play(Map song) async { + await _musicService.playMusic(song); } //暂停 diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 045c0222e..12fdc30e7 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -222,7 +222,8 @@ class AgentController extends GetxController { messages.refresh(); } else { // 如果没有找到识别中的消息,添加新消息 - messages.add(Message(isUser: true, text: text, isRecognizing: false)); + messages + .add(Message(isUser: true, text: text, isRecognizing: false)); } isProcessing.value = true; @@ -355,7 +356,7 @@ class AgentController extends GetxController { // if (meta.isNotEmpty) { // // logger.i('Flutter 结束调用 mcp metaResult: $metaResult'); // // 判断是否为新的回复或响应ID是否改变 - // if (_isNewAssistantResponse ) { + // if (_isNewAssistantResponse) { // // 添加新消息 // messages.add(Message(isUser: false, text: "", meta: meta)); // _isNewAssistantResponse = false; @@ -371,7 +372,7 @@ class AgentController extends GetxController { // ); // } // } - + // _scrollToBottom(); // } logger.i('mcp 结果处理完成'); @@ -625,10 +626,10 @@ class AgentController extends GetxController { } // 播放音乐 - void musicPlay(String url) { - _musiceManager.play(url); + void musicPlay(Map song) { + _musiceManager.play(song); isPlaying.value = true; - currentUrl.value = url; + currentUrl.value = song['url'] ?? ''; } // 播放音乐 diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index f1d939fc2..78f1acda4 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -197,11 +197,14 @@ class MessageBubble extends GetView { // 判断消息内容,显示特殊卡片 if (!isUser && mate != null) { if (mate!.keys.contains('card_music')) { + final musicId = mate!['card_music']['id'] as String; final musicName = mate!['card_music']['name'] as String; final musicUrl = mate!['card_music']['url'] 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')) { final rawList = mate!['card_musiclist']['musics'] as List; final playlist = @@ -327,13 +330,14 @@ class MessageBubble extends GetView { ...playlist.asMap().entries.map((entry) { final index = entry.key; final item = entry.value; + final id = item["id"] ?? ""; final name = item["name"] ?? "未知歌曲"; final url = item["url"] ?? ""; final singer = item["sgener"] ?? "未知歌手"; - + final image = item["image"] ?? ""; return Column( children: [ - _buildMusicItem(context, name, url, singer), + _buildMusicItem(context, id, name, url, singer, image), if (index != playlist.length - 1) Divider(height: 1, thickness: 1, color: Colors.grey[100]), ], @@ -531,9 +535,16 @@ class MessageBubble extends GetView { } /// 构建音乐项目 - Widget _buildMusicItem( - BuildContext context, String name, String url, String singer) { + Widget _buildMusicItem(BuildContext context, String id, String name, + String url, String singer, String image) { final controller = Get.find(); // 获取控制器 + Map song = { + 'id': id, + 'url': url, + 'title': name, + 'artist': singer, + 'coverUrl': image, + }; controller.musicStatus(); //刷新状态 return Padding( padding: const EdgeInsets.symmetric(vertical: 8), @@ -588,13 +599,13 @@ class MessageBubble extends GetView { if (controller.currentUrl.value == url) { controller.musicPause(); } else { - controller.musicPlay(url); + controller.musicPlay(song); } } else { if (controller.currentUrl.value == url) { controller.musicResume(); } else { - controller.musicPlay(url); + controller.musicPlay(song); } } }, 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 3ab2ae755..7ac469322 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 @@ -671,43 +671,8 @@ object AgentService : CoroutineScope { "function_call" to functionCall.toString(), "result" to functionCallResult.toString(), )) - // FileLogger.d(TAG, "mcp调用结果: $functionCallResult") - val metaStr = functionCallResult.optString("meta") - 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>() - 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 不是一个数组") - } - } - } + FileLogger.d(TAG, "mcp调用结果: $functionCallResult") + metadata = autoPlayMusic(functionCallResult); } } ) @@ -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>() + 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返回的音乐连接 在后台启动播放 * - * @param musicUrl 图片文件路径 + * @param musicId 音乐ID + * @param musicUrl 音乐文件路径 * @param name 歌曲名称,默认为空 - * @param singer 歌手,默认为false + * @param singer 歌手名称,默认为空 * @return 是否成功开始处理 */ - fun processMusicPlay(musicUrl:String,name: String = "",singer:String = ""){ + fun processMusicPlay(song: Map){ // 在其他 Service、BroadcastReceiver 或 Application 中调用 - FileLogger.i(TAG, "启动音乐服务 播放音乐 $musicUrl") - MusicServiceStarter.startServiceWithCommand(context, "play", url = musicUrl) + FileLogger.i(TAG, "启动音乐服务 播放音乐 $song") + MusicServiceStarter.startServiceWithCommand(context, command = "play", song = song) } /** @@ -1077,11 +1088,13 @@ object AgentService : CoroutineScope { * @param singer 歌手,默认为false * @return 是否成功开始处理 */ - fun processMusicPlayList(playlist:List>){ - // 在其他 Service、BroadcastReceiver 或 Application 中调用 - - val urls = playlist.mapNotNull { it["url"] }.distinct() - FileLogger.i(TAG, "启动音乐服务 播放音乐列表 $urls") - MusicServiceStarter.startServiceWithPlaylist(context, "playlist", urls = urls) + fun processMusicPlayList(songs:List>){ + // 如果 playlist 中有有效的歌曲,开始播放 + if (songs.isNotEmpty()) { + FileLogger.i(TAG, "启动音乐服务 播放音乐列表 ${songs}") + MusicServiceStarter.startServiceWithPlaylist(context, command = "playlist", songs = songs) + } else { + FileLogger.i(TAG, "音乐列表为空,未启动播放服务") + } } } \ No newline at end of file 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 5cca58168..e0091e1ff 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 @@ -5,6 +5,7 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.content.Intent +import android.graphics.BitmapFactory import android.media.AudioManager import android.media.MediaPlayer import android.os.Build @@ -17,6 +18,8 @@ import androidx.core.app.NotificationCompat import androidx.media.MediaBrowserServiceCompat import androidx.media.session.MediaButtonReceiver import com.yunqiinnovation.deepvoice_utils.FileLogger +import java.net.HttpURLConnection +import java.net.URL import java.util.concurrent.atomic.AtomicBoolean class MusicService : MediaBrowserServiceCompat() { @@ -29,7 +32,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() @@ -46,7 +49,7 @@ class MusicService : MediaBrowserServiceCompat() { FileLogger.init(this) isRunning.set(true) setupMediaSession() - startForeground(NOTIFICATION_ID, createNotification("等待播放…")) + startForeground(NOTIFICATION_ID, createNotification("等待播放","DeepSound","")) audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager FileLogger.i(TAG, "MediaService 启动完成") } @@ -59,12 +62,34 @@ class MusicService : MediaBrowserServiceCompat() { } val command = intent?.getStringExtra("command") ?: return START_NOT_STICKY - FileLogger.i(TAG, "收到命令: $command") + FileLogger.i(TAG, "liwei------------------- onStartCommand: $command") when (command) { - "play" -> intent.getStringExtra("url")?.let { url -> - if (!playlist.contains(url)) playlist.add(url) - currentIndex = playlist.indexOf(url) - play(url) + "play" -> intent.getSerializableExtra("song")?.let { url -> + val songMap = intent.getSerializableExtra("song") as? Map + songMap?.let { song -> + 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> + list?.let { + playlist.clear() + playlist.addAll(it) + currentIndex = 0 + play(playlist[currentIndex]) + } } "pause" -> pause() "resume" -> resume() @@ -106,7 +131,7 @@ class MusicService : MediaBrowserServiceCompat() { 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 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { manager.createNotificationChannel( @@ -124,9 +149,9 @@ class MusicService : MediaBrowserServiceCompat() { this, PlaybackStateCompat.ACTION_SKIP_TO_NEXT ) - return NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("DeepSound") - .setContentText(contentText) + val builder = NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle(title) + .setContentText(artist) .setSmallIcon(android.R.drawable.ic_media_play) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setStyle( @@ -142,7 +167,31 @@ class MusicService : MediaBrowserServiceCompat() { ) .addAction(android.R.drawable.ic_media_next, "下一首", nextIntent) .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) { + val url = song["url"] ?: return + FileLogger.i(TAG, "开始播放: $url") + val title = song["title"] ?: "未知标题" + val artist = song["artist"] ?: "未知歌手" + val coverUrl = song["coverUrl"] mediaPlayer?.release() currentStatus = true currentUrlInternal = url @@ -180,7 +234,7 @@ class MusicService : MediaBrowserServiceCompat() { setAudioStreamType(AudioManager.STREAM_MUSIC) setOnPreparedListener { start() - updateNotification("正在播放") + updateNotification(title,artist,coverUrl) FileLogger.i(TAG, "开始播放: $url") } setOnCompletionListener { playNextOrStop() } @@ -202,7 +256,11 @@ class MusicService : MediaBrowserServiceCompat() { .setState(PlaybackStateCompat.STATE_PAUSED, currentPosition.toLong(), 1.0f) .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); FileLogger.i(TAG, "已暂停") @@ -218,9 +276,13 @@ class MusicService : MediaBrowserServiceCompat() { .setState(PlaybackStateCompat.STATE_PLAYING, currentPosition.toLong(), 1.0f) .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); - FileLogger.i(TAG, "继续播放") +// FileLogger.i(TAG, "继续播放") } } @@ -240,8 +302,8 @@ class MusicService : MediaBrowserServiceCompat() { else stopSelf() } - private fun updateNotification(contentText: String) { - val notification = createNotification(contentText) + private fun updateNotification(title: String,artist: String,coverUrl: String?) { + val notification = createNotification(title, artist, coverUrl) val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { manager.notify(NOTIFICATION_ID, notification) 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 e85c4be44..89bf0edd8 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 @@ -4,7 +4,7 @@ import android.content.Context import android.content.Intent import androidx.core.content.ContextCompat 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.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel @@ -27,30 +27,38 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler { FileLogger.d(TAG, "Method call: ${call.method}") when (call.method) { "play" -> { - val url = call.argument("url") - startServiceWithCommand(context, "play", url) - result.success(null) + val song = call.argument>("song") + if (song != null) { + MusicServiceStarter.startServiceWithCommand(context, "play", song) + result.success(null) + } else { + result.error("INVALID_ARGUMENT", "Missing 'song' map", null) + } } "playlist" -> { - val urls = call.argument>("urls") - MusicServiceStarter.startServiceWithPlaylist(context, "playlist", urls) - result.success(null) + val songs = call.argument>>("songs") + if (songs != null && songs.isNotEmpty()) { + MusicServiceStarter.startServiceWithPlaylist(context, "playlist", songs) + result.success(null) + } else { + result.error("INVALID_ARGUMENT", "Missing or empty 'songs' list", null) + } } "pause" -> { - startServiceWithCommand(context, "pause") + MusicServiceStarter.startServiceWithCommand(context, "pause") result.success(null) } "resume" -> { - startServiceWithCommand(context, "resume") + MusicServiceStarter.startServiceWithCommand(context, "resume") result.success(null) } "stop" -> { - startServiceWithCommand(context, "stop") + MusicServiceStarter.startServiceWithCommand(context, "stop") result.success(null) } "setVolume" -> { val volume = call.argument("volume")?.toFloat() - startServiceWithCommand(context, "setVolume", volume = volume) + MusicServiceStarter.startServiceWithCommand(context, "setVolume", volume = volume) result.success(null) } "getStatus" -> { 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 9d8d290a1..4c743d18a 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 @@ -5,26 +5,48 @@ import android.content.Intent import android.util.Log import androidx.core.content.ContextCompat +//@Parcelize +//data class Song( +// val url: String, +// val title: String, +// val artist: String, +// val coverUrl: String? +//) + object MusicServiceStarter { private val TAG = "MusicServiceStarter" //播放单曲 - fun startServiceWithCommand(context: Context, command: String, url: String? = null, volume: Float? = null) { - Log.i(TAG, "startServiceWithCommand: $command $url") + 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) - url?.let { putExtra("url", it) } + song?.let { + putExtra("song", HashMap(it)) // 只有非 null 时才 putExtra + } 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?,volume: Float? = null) { - Log.i(TAG, "startServiceWithPlaylist: $urls") + fun startServiceWithPlaylist(context: Context,command: String, songs: List>,volume: Float? = null) { + Log.i(TAG, "liwei------------------- startServiceWithPlaylist: $command $songs") + val serializableList = ArrayList>().apply { + songs.forEach { add(HashMap(it)) } + } val intent = Intent(context, MusicService::class.java).apply { putExtra("command", command) - putStringArrayListExtra("urls", ArrayList(urls!!)) // 注意类型是 ArrayList + putExtra("songs", serializableList) // ✅ Serializable + volume?.let { putExtra("volume", it) } } ContextCompat.startForegroundService(context, intent) + } } \ No newline at end of file diff --git a/local_plugins/music_service/lib/music_service.dart b/local_plugins/music_service/lib/music_service.dart index ce3bb9e6d..02e3756ec 100644 --- a/local_plugins/music_service/lib/music_service.dart +++ b/local_plugins/music_service/lib/music_service.dart @@ -39,8 +39,8 @@ class MusicService { _statusStream = _statusEventChannel.receiveBroadcastStream(); } - Future playMusic(String url) async { - await _methodChannel.invokeMethod('play', {'url': url}); + Future playMusic(Map song) async { + await _methodChannel.invokeMethod('play', {'song': song}); } Future pauseMusic() async => _methodChannel.invokeMethod('pause');