Browse Source

上传iOS 的音乐服务和卡片话逻辑优化

newdev_shunjiawei
liwei1dao 1 year ago
parent
commit
a4e406ac0f
  1. 6
      ios/Podfile.lock
  2. 16
      ios/Runner.xcodeproj/project.pbxproj
  3. 48
      lib/modules/agent/controllers/agent_controller.dart
  4. 34
      lib/modules/agent/views/message_bubble.dart
  5. 6
      local_plugins/agent_service/ios/agent_service/Package.swift
  6. 137
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  7. 17
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift
  8. 13
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift
  9. 24
      local_plugins/music_service/ios/music_service/Package.swift
  10. 120
      local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift
  11. 71
      local_plugins/music_service/ios/music_service/Sources/music_service/MusicServicePlugin.swift

6
ios/Podfile.lock

@ -53,8 +53,6 @@ PODS:
- Mantle (2.2.0):
- Mantle/extobjc (= 2.2.0)
- Mantle/extobjc (2.2.0)
- music_service (0.0.1):
- Flutter
- ota (0.0.1):
- Flutter
- permission_handler_apple (9.3.0):
@ -96,7 +94,6 @@ DEPENDENCIES:
- flutter_tts (from `.symlinks/plugins/flutter_tts/ios`)
- fluwx (from `.symlinks/plugins/fluwx/ios`)
- haptic_feedback (from `.symlinks/plugins/haptic_feedback/ios`)
- music_service (from `.symlinks/plugins/music_service/ios`)
- ota (from `.symlinks/plugins/ota/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- record_darwin (from `.symlinks/plugins/record_darwin/ios`)
@ -143,8 +140,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/fluwx/ios"
haptic_feedback:
:path: ".symlinks/plugins/haptic_feedback/ios"
music_service:
:path: ".symlinks/plugins/music_service/ios"
ota:
:path: ".symlinks/plugins/ota/ios"
permission_handler_apple:
@ -175,7 +170,6 @@ SPEC CHECKSUMS:
haptic_feedback: fd1d8509833f58f164fc12122c0357fa9b2750e0
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
music_service: 85abecc8fa21599a06907ad50d83f48a58e0dc0d
ota: 20c3e522c57e7ba70ce118109e73650acc9fec76
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
QCloudCore: 49e7571984a8e5fd8f0fd58c2f3b806255796223

16
ios/Runner.xcodeproj/project.pbxproj

@ -252,7 +252,8 @@
);
mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
D013B7972DFA83E400B2B69B /* XCRemoteSwiftPackageReference "flutterfire" */,
);
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
@ -792,12 +793,23 @@
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCRemoteSwiftPackageReference section */
D013B7972DFA83E400B2B69B /* XCRemoteSwiftPackageReference "flutterfire" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/firebase/flutterfire";
requirement = {
branch = main;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;

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

@ -418,39 +418,14 @@ class AgentController extends GetxController {
break;
case AgentServiceEventType.functionCallResult:
final responseId = event.data['responseId'] ?? '';
final result = event.data['result'];
final functionCall = event.data['function_call'] ?? '';
Logger.i(TAG, 'Flutter 结束调用 mcp: $result, $functionCall');
if (result != null) {
// result 可能已经是 Map 类型,也可能是 String 类型
Map<String, dynamic> parsedResult;
if (result is String && result.isNotEmpty) {
try {
parsedResult = jsonDecode(result);
} catch (e) {
Logger.e(TAG, '解析result数据失败: $e');
break;
}
} else if (result is Map<String, dynamic>) {
parsedResult = result;
} else {
Logger.e(TAG, 'result数据类型不支持: ${result.runtimeType}');
break;
}
final meta = parsedResult["meta"];
final responseId = event.data['responseId'] ?? '';
// final result = event.data['result'];
final metaStr = event.data['meta'] ?? '';
// final functionCall = event.data['function_call'] ?? '';
Logger.i(TAG, 'Flutter 结束调用 mcp: $metaStr');
if (metaStr.isNotEmpty) {
Map<String, dynamic> metaResult = {};
if (meta != null && meta is String && meta.isNotEmpty) {
try {
metaResult = jsonDecode(meta) as Map<String, dynamic>;
} catch (e) {
Logger.e(TAG, '解析meta数据失败: $e');
}
} else if (meta != null && meta is Map<String, dynamic>) {
metaResult = meta;
}
metaResult = jsonDecode(metaStr) as Map<String, dynamic>;
if (metaResult.isNotEmpty) {
// 判断是否为新的回复或响应ID是否改变
if (_isNewAssistantResponse || _currentResponseId != responseId) {
@ -471,9 +446,12 @@ class AgentController extends GetxController {
);
}
}
if (metaResult.containsKey("card_navigation")) {
final start = metaResult["card_navigation"]["start"] as String;
final end = metaResult["card_navigation"]["end"] as String;
if (metaResult.containsKey("maps_route_navigation")) {
//导航
final start = metaResult["maps_route_navigation"]
["card_navigation"]["start"] as String;
final end = metaResult["maps_route_navigation"]
["card_navigation"]["end"] as String;
// Logger.i(TAG, 'liwei-------- 打开导航页面: $start $end ');
Future.delayed(Duration(seconds: 2), () {
// Logger.i(TAG, 'liwei-------- 打开导航页面: $start $end ');

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

@ -230,17 +230,21 @@ class MessageBubble extends GetView<AgentController> {
// 判断消息内容,显示特殊卡片
if (!isUser && meta != null) {
// 判断消息类型,显示不同的卡片
if (meta!.keys.contains('card_music')) {
final musicId = meta!['card_music']['id'].toString();
final musicName = meta!['card_music']['name'] as String;
final musicUrl = meta!['card_music']['url'] as String;
final musicSinger = meta!['card_music']['sgener'] as String;
final musicImage = meta!['card_music']['image'] as String;
if (meta!.keys.contains('music_play')) {
final card_music =
meta!["music_play"]['card_music'] as Map<String, dynamic>;
final musicId = card_music!['id'].toString();
final musicName = card_music!['name'] as String;
final musicUrl = card_music!['url'] as String;
final musicSinger = card_music!['sgener'] as String;
final musicImage = card_music!['image'] as String;
//音乐卡片
return buildMusicItem(
context, musicId, musicName, musicUrl, musicSinger, musicImage);
} else if (meta!.keys.contains('card_musiclist')) {
final rawList = meta!['card_musiclist']['musics'] as List;
} else if (meta!.keys.contains('music_playlist')) {
final card_musiclist =
meta!["music_playlist"]['card_musiclist'] as Map<String, dynamic>;
final rawList = card_musiclist['musics'] as List;
final playlist =
rawList.map((e) => Map<String, dynamic>.from(e)).toList();
//音乐播放列表
@ -264,13 +268,15 @@ class MessageBubble extends GetView<AgentController> {
rawList.map((e) => Map<String, dynamic>.from(e)).toList();
// 旅游
return buildTravelContent(context, travellist);
} else if (meta!.keys.contains('card_navigation')) {
final start = meta!['card_navigation']['start'] as String;
final end = meta!['card_navigation']['end'] as String;
final destination = meta!['card_navigation']['destination'] as String;
final rawList = meta!['card_navigation']['items'] as List;
} else if (meta!.keys.contains('maps_route_navigation')) {
final card_navigation = meta!['maps_route_navigation']
['card_navigation'] as Map<String, dynamic>;
final start = card_navigation['start'] as String;
final end = card_navigation['end'] as String;
final destination = card_navigation['destination'] as String;
final rawList = card_navigation['items'] as List;
final list = rawList.map((e) => Map<String, dynamic>.from(e)).toList();
final rawNavigation = meta!['card_navigation']['navigation'];
final rawNavigation = card_navigation['navigation'];
final Map<String, dynamic>? navigation =
rawNavigation is Map<String, dynamic> ? rawNavigation : null;
print("导航实时数据 ${rawNavigation}");

6
local_plugins/agent_service/ios/agent_service/Package.swift

@ -13,7 +13,8 @@ let package = Package(
.package(path: "../../../speech/ios/speech"),
.package(path: "../../../azure_speech/ios/azure_speech"),
.package(path: "../../../chat_api/ios/chat_api"),
.package(path: "../../../chat_storage/ios/chat_storage")
.package(path: "../../../chat_storage/ios/chat_storage"),
.package(path: "../../../music_service/ios/music_service")
],
targets: [
.target(
@ -24,7 +25,8 @@ let package = Package(
.product(name: "speech", package: "speech"),
.product(name: "azure-speech", package: "azure_speech"),
.product(name: "chat-api", package: "chat_api"),
.product(name: "chat-storage", package: "chat_storage")
.product(name: "chat-storage", package: "chat_storage"),
.product(name: "music-service", package: "music_service")
],
path: "Sources/agent_service",
resources: [

137
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift

@ -7,6 +7,7 @@ import os
import os.log
import chat_api
import chat_storage
import music_service
protocol AgentServiceListener: AnyObject {
func onEvent(eventName: String, data: [String: Any])
@ -595,43 +596,98 @@ class AgentServiceImpl: NSObject {
}
}
}
//解析卡片数据
@discardableResult
internal func autoHandleFunctionCallResult(_ functionCallResult: [String: Any]) -> String {
guard let metaStr = functionCallResult["meta"] as? String, !metaStr.isEmpty else {
return ""
}
do {
guard let metaData = metaStr.data(using: .utf8),
let meta = try JSONSerialization.jsonObject(with: metaData) as? [String: Any] else {
return metaStr
}
if let cardMusic = meta["card_music"] as? [String: Any] {
let id = cardMusic["id"] as? String ?? ""
let url = cardMusic["url"] as? String ?? ""
let name = cardMusic["name"] as? String ?? ""
let sgener = cardMusic["sgener"] as? String ?? ""
let image = cardMusic["image"] as? String ?? ""
processMusicPlay([
"id": id,
"url": url,
"title": name,
"artist": sgener,
"coverUrl": image
])
internal func autoHandleFunctionCallResult(toolName: String, functionCallResult: [String: Any]) -> (String, Bool) {
if let contentArray = functionCallResult["content"] as? [[String: Any]],
let firstContent = contentArray.first,
let textStr = firstContent["text"] as? String,
let jsonData = textStr.data(using: .utf8) {
do {
if let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] {
print("解析成功:\(jsonObject)")
var metadata: [String: Any] = [:]
metadata[toolName] = jsonObject
// 将 metadata 转为 JSON 字符串
let metaStr: String
if let jsonData = try? JSONSerialization.data(withJSONObject: metadata, options: [.prettyPrinted]),
let jsonString = String(data: jsonData, encoding: .utf8) {
metaStr = jsonString
} else {
metaStr = ""
}
// 处理 card_music
if let cardMusic = jsonObject["card_music"] as? [String: Any] {
let id = cardMusic["id"] as? String ?? ""
let url = cardMusic["url"] as? String ?? ""
let title = cardMusic["name"] as? String ?? ""
let artist = cardMusic["sgener"] as? String ?? ""
let coverUrl = cardMusic["image"] as? String ?? ""
processMusicPlay([
"id": id,
"url": url,
"title": title,
"artist": artist,
"coverUrl": coverUrl
])
return (metaStr, false)
// 处理 card_musiclist
} else if let cardMusiclist = jsonObject["card_musiclist"] as? [String: Any],
let musics = cardMusiclist["musics"] as? [[String: Any]] {
var playlist: [[String: String]] = []
for item in musics {
let id = item["id"] as? String ?? ""
let url = item["url"] as? String ?? ""
let title = item["name"] as? String ?? ""
let artist = item["sgener"] as? String ?? ""
let coverUrl = item["image"] as? String ?? ""
let song: [String: String] = [
"id": id,
"url": url,
"title": title,
"artist": artist,
"coverUrl": coverUrl
]
playlist.append(song)
}
if !playlist.isEmpty {
print("自动播放音乐列表: \(playlist)")
processMusicPlayList(playlist)
return (metaStr, false)
} else {
print("card_musiclist 中没有有效的音乐条目")
}
}else if let cardNavigation = jsonObject["card_navigation"] as? [String: Any] { //导航
let start:String = cardNavigation["start"] as? String ?? ""
let end:String = cardNavigation["end"] as? String ?? ""
// processNavigation(start,end)
return (metaStr, false)
}
return (metaStr, true)
}
} catch {
print("JSON 解析失败: \(error)")
}
return metaStr
} catch {
return metaStr
}
return ("", true)
}
private func processMusicPlay(_ musicData: [String: Any]) {
sendEvent(name: "music_play", data: musicData)
// 后台播放单曲
private func processMusicPlay(_ song: [String: String]) {
sendEvent(name: "music_play", data: song)
MusicService.shared.play(song: song)
}
// 后台播放音乐列表
private func processMusicPlayList(_ songs: [[String: String]]) {
// sendEvent(name: "music_playlist", data: songs)
MusicService.shared.playPlaylist(songs: songs)
}
func clearChatHistory() -> Bool {
@ -694,6 +750,7 @@ class ChatApiStreamCallback: StreamCallback {
private let hasImage: Bool
private var responseBuilder = ""
private var metadata = ""
private var nobroadcast = true
init(agentService: AgentServiceImpl, speakResponse: Bool, displayText: String, hasImage: Bool) {
self.agentService = agentService
@ -712,7 +769,7 @@ class ChatApiStreamCallback: StreamCallback {
agentService.sendEvent(name: "assistant_token", data: ["token": token])
if speakResponse {
if speakResponse && nobroadcast{
agentService.azureTtsHelper?.speakStream(token)
}
}
@ -721,7 +778,7 @@ class ChatApiStreamCallback: StreamCallback {
guard let agentService = agentService else { return }
if speakResponse {
if speakResponse && nobroadcast{
agentService.azureTtsHelper?.flushStream()
}
@ -792,16 +849,18 @@ class ChatApiStreamCallback: StreamCallback {
guard let agentService = agentService else { return }
let name = functionCall["name"] as? String ?? ""
os_log("收到函数调用结果: %{public}@", log: agentService.logger, type: .info, name)
os_log("收到函数调用结果: %{public}@", log: agentService.logger, type: .info, functionCallResult)
agentService.audioPlayer?.stopCallingSound()
let (metestr, broadcast) = agentService.autoHandleFunctionCallResult(toolName:name,functionCallResult:functionCallResult)
metadata = metestr;
nobroadcast = broadcast
os_log("卡片数据: %{public}@", log: agentService.logger, type: .info, metadata)
agentService.sendEvent(name: "function_call_result", data: [
"function_call": functionCall,
"result": functionCallResult
"result": functionCallResult,
"meta":metadata,
])
metadata = agentService.autoHandleFunctionCallResult(functionCallResult)
os_log("处理函数调用结果完成", log: agentService.logger, type: .info)
}
}

17
local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift

@ -715,15 +715,26 @@ public class ChatApiService: NSObject {
type == "function",
let function = tool["function"] as? [String: Any],
let name = function["name"] as? String,
let description = function["description"] as? String else {
let description = function["description"] as? String,
let parameters: [String: Any] = function["parameters"] as? [String: Any] else {
continue
}
// 将字典转换为 JSON 数据
guard let jsonData = try? JSONSerialization.data(withJSONObject: parameters, options: []) else {
print("无法将 parameters 转换为 JSON 数据")
continue
}
// 解码 JSON 数据为 AnyJSONSchema 类型
let decoder = JSONDecoder()
guard let schema = try? decoder.decode(AnyJSONSchema.self, from: jsonData) else {
print("无法将 JSON 数据解码为 AnyJSONSchema")
continue
}
// 创建函数定义
let functionDefinition = ChatQuery.ChatCompletionToolParam.FunctionDefinition(
name: name,
description: description,
parameters: nil
parameters: schema
)
// 创建工具参数

13
local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift

@ -31,16 +31,18 @@ public protocol FunctionHandler {
public class MCPSubClient {
private let serverId: String
private let serverUrl: String
private let filterTools: String
// 使用官方MCP Swift SDK
private var mcpClient: Client?
private var tools: [Tool] = []
private var toolMaps: [[String: Any]] = []
private var isConnectedFlag = false
public init(serverId: String, serverUrl: String) {
public init(serverId: String, serverUrl: String,filterTools:String) {
self.serverId = serverId
self.serverUrl = serverUrl
self.filterTools = filterTools
}
public func connect() async -> Bool {
@ -78,6 +80,9 @@ public class MCPSubClient {
toolMaps.removeAll()
for tool in toolList {
if !filterTools.isEmpty && !filterTools.contains(tool.name) {
continue
}
let parameters = convertInputSchema(tool.inputSchema)
let functionMap: [String: Any] = [
@ -271,8 +276,8 @@ public class MCPClient {
guard let url = configDict["url"] as? String, !url.isEmpty else {
continue
}
let subClient = MCPSubClient(serverId: serverId, serverUrl: url)
let filtertools = configDict["tools"] as? String ?? ""
let subClient = MCPSubClient(serverId: serverId, serverUrl: url,filterTools: filtertools)
if await subClient.connect() {
subClients[serverId] = subClient

24
local_plugins/music_service/ios/music_service/Package.swift

@ -0,0 +1,24 @@
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "music_service",
platforms: [.iOS("18.0")],
products: [
.library(name: "music-service", targets: ["music_service"])
],
dependencies: [
// 例如,添加 Alamofire 作为网络请求库
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.6.0")
],
targets: [
.target(
name: "music_service",
dependencies: [
// 指定使用 Alamofire 库
.product(name: "Alamofire", package: "Alamofire")
],
path: "Sources/music_service"
)
]
)

120
local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift

@ -0,0 +1,120 @@
//
// MusicService.swift
// music_service
//
// Created by 李伟 on 2025/6/4.
//
import Foundation
import AVFoundation
import MediaPlayer
import UIKit
public class MusicService: NSObject {
public static let shared: MusicService = MusicService()
private var player: AVPlayer?
private var playlist: [[String: String]] = []
private var currentIndex = 0
public var isPlaying = false
private override init() {
super.init()
setupRemoteCommandCenter()
}
func getCurrentUrlString() -> String? {
guard let urlAsset = player?.currentItem?.asset as? AVURLAsset else {
return nil
}
return urlAsset.url.absoluteString
}
public func play(song: [String: String]) {
guard let urlString = song["url"], let url = URL(string: urlString) else { return }
player = AVPlayer(url: url)
player?.play()
isPlaying = true
updateNowPlayingInfo(song: song)
}
public func playPlaylist(songs: [[String: String]]) {
playlist = songs
currentIndex = 0
play(song: playlist[currentIndex])
}
func pause() {
player?.pause()
isPlaying = false
updatePlaybackState()
}
func resume() {
player?.play()
isPlaying = true
updatePlaybackState()
}
func next() {
guard currentIndex + 1 < playlist.count else { return }
currentIndex += 1
play(song: playlist[currentIndex])
}
func previous() {
guard currentIndex - 1 >= 0 else { return }
currentIndex -= 1
play(song: playlist[currentIndex])
}
func stop() {
player?.pause()
player?.replaceCurrentItem(with: nil)
isPlaying = false
updatePlaybackState()
}
func setVolume(volume: Float) {
player?.volume = volume
}
private func setupRemoteCommandCenter() {
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.addTarget { [unowned self] _ in
self.resume()
return .success
}
commandCenter.pauseCommand.addTarget { [unowned self] _ in
self.pause()
return .success
}
commandCenter.nextTrackCommand.addTarget { [unowned self] _ in
self.next()
return .success
}
commandCenter.previousTrackCommand.addTarget { [unowned self] _ in
self.previous()
return .success
}
}
private func updateNowPlayingInfo(song: [String: String]) {
var nowPlayingInfo: [String: Any] = [
MPMediaItemPropertyTitle: song["title"] ?? "Unknown Title",
MPMediaItemPropertyArtist: song["artist"] ?? "Unknown Artist"
]
if let coverUrlString = song["coverUrl"], let coverUrl = URL(string: coverUrlString),
let data = try? Data(contentsOf: coverUrl),
let image = UIImage(data: data) {
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in image }
}
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
private func updatePlaybackState() {
// iOS manages playback state automatically, but you can update NowPlayingInfo if needed
}
}

71
local_plugins/music_service/ios/music_service/Sources/music_service/MusicServicePlugin.swift

@ -0,0 +1,71 @@
import Flutter
import UIKit
public class MusicServicePlugin: NSObject, FlutterPlugin {
private static var channel: FlutterMethodChannel?
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "com.yunqiinnovation.music_service", binaryMessenger: registrar.messenger())
let instance = MusicServicePlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
self.channel = channel
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
print("Method call: \(call.method)")
switch call.method {
case "play":
if let args = call.arguments as? [String: Any],
let song = args["song"] as? [String: String] {
MusicService.shared.play(song: song)
result(nil)
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing 'song' map", details: nil))
}
case "playlist":
if let args = call.arguments as? [String: Any],
let songs = args["songs"] as? [[String: String]], !songs.isEmpty {
// MusicServiceStarter.startServiceWithPlaylist(command: "playlist", songs: songs)
MusicService.shared.playPlaylist(songs: songs)
result(nil)
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing or empty 'songs' list", details: nil))
}
case "pause":
MusicService.shared.pause()
result(nil)
case "resume":
MusicService.shared.resume()
result(nil)
case "stop":
// MusicServiceStarter.startService(command: "stop")
MusicService.shared.stop()
result(nil)
case "setVolume":
if let args = call.arguments as? [String: Any],
let volume = args["volume"] as? Double {
MusicService.shared.setVolume(volume: Float(volume))
result(nil)
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing or invalid 'volume'", details: nil))
}
case "getStatus":
let isPlaying = MusicService.shared.isPlaying
let currentUrl = MusicService.shared.getCurrentUrlString()
let status: [String: Any] = [
"isPlaying": isPlaying,
"url": currentUrl != nil ? currentUrl! : NSNull()
]
result(status)
default:
result(FlutterMethodNotImplemented)
}
}
}
Loading…
Cancel
Save