From 64cf8676d3c37fca9d249e9b67951def1d83c815 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Wed, 2 Jul 2025 17:32:32 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BC=9A=E8=AE=AE?= =?UTF-8?q?=E5=8A=A9=E6=89=8B=E6=95=B0=E6=8D=AE=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/utils/synchrodata.dart | 5 +++-- pubspec.yaml | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/core/utils/synchrodata.dart b/lib/core/utils/synchrodata.dart index 5cca0d2e1..a7fa19d1d 100644 --- a/lib/core/utils/synchrodata.dart +++ b/lib/core/utils/synchrodata.dart @@ -113,9 +113,10 @@ class Synchrodata { // 同步数据 static Future _synchrodata() async { - final db = await DatabaseHelper().database; var result = await Api.getOperationRecord(); - List records = result['records']; + List records = result['records'] ?? []; + if (records.isEmpty) return; + final db = await DatabaseHelper().database; await db.transaction((txn) async { for (var element in records) { final List existList = await txn.query( diff --git a/pubspec.yaml b/pubspec.yaml index 6fb187beb..6262a2de8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,10 @@ dependencies: audio_session: ^0.1.18 flutter_markdown: ^0.7.7+1 markdown: ^7.1.1 + appflowy_editor: + git: + url: https://github.com/AppFlowy-IO/appflowy-editor.git + ref: main permission_handler: ^10.4.5 web_socket_channel: ^2.4.0 flutter_dotenv: ^5.1.0 @@ -47,7 +51,7 @@ dependencies: path: plugins/window_size ref: 6c66ad23ee79749f30a8eece542cf54eaf157ed8 flutter_local_notifications: ^18.0.1 - device_info_plus: ^10.1.2 + device_info_plus: ^11.5.0 uuid: ^4.3.3 record: ^5.2.1 share_plus: ^11.0.0 From dd9e6ddf042daf5e9d8ba4b6200331712508e4e7 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Wed, 2 Jul 2025 22:16:38 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E6=8E=88=E6=9D=83=E7=A0=81=E8=87=AA=E5=8A=A8=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/ble_manager.dart | 19 +++ .../controllers/pairing_controller.dart | 131 ++++++++++++------ .../yunqiinnovation/ble_service/BleService.kt | 26 +++- .../ble_service/BleServicePlugin.kt | 10 ++ .../ble_service/lib/ble_service.dart | 32 +++++ 5 files changed, 169 insertions(+), 49 deletions(-) diff --git a/lib/data/services/ble_manager.dart b/lib/data/services/ble_manager.dart index f379b53d8..42af34e4f 100644 --- a/lib/data/services/ble_manager.dart +++ b/lib/data/services/ble_manager.dart @@ -767,6 +767,25 @@ class BleManager extends GetxService { } } + /// 验证设备是否可以配对 + Future startScan() async { + try { + await _bleService.startScan(); + } catch (e) { + Logger.error('开始扫描设备: ${e.toString()}'); + } + } + + Future?> getDeviceInfo(String address) async { + try { + final result = _bleService.getDeviceInfo(address); + return result; + } catch (e) { + Logger.error('获取设备信息: ${e.toString()}'); + return null; + } + } + /// 验证设备是否可以配对 Future verifyDeviceCanPair(String macAddress) async { try { diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 0cd9fb15a..e06a8ed5a 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -1,3 +1,6 @@ +import 'dart:convert'; +import 'dart:typed_data'; + import 'package:app_settings/app_settings.dart'; import 'package:deep_voice/data/models/user_Info.dart'; import 'package:deep_voice/data/services/network/api.dart'; @@ -418,41 +421,32 @@ class PairingController extends GetxController { // TODO: 实现API验证逻辑 // 暂时模拟验证通过 // await Future.delayed(const Duration(seconds: 2));; - if (await authbindingDevice()) { - // 检查设备名称是否包含 DeepSound 或 DeapSound - final deviceName = device.name ?? ''; - if (deviceName.contains('DeepSound') || - deviceName.contains('DeapSound')) { - Logger.i(_tag, '设备验证通过'); - _verifiedDeviceAddress = device.address; - - // 检查是否已有BLE关联 - final pairedMacAddress = await _bleManager.getPairedMacAddress(); - Logger.i(_tag, '已关联的MAC地址: $pairedMacAddress'); - - if (pairedMacAddress != null && - pairedMacAddress.toUpperCase() == device.address.toUpperCase()) { - // 已有关联,检查BLE连接状态 - Logger.i(_tag, '设备已关联,检查BLE连接状态'); - final currentBleState = await _bleManager.getConnectionState(); - Logger.i(_tag, '验证时BLE状态: $currentBleState'); - - if (currentBleState == 2) { - // BleConnectionState.connected - Logger.i(_tag, 'BLE已连接,直接完成配对'); - _transition(PairingState.completed); - } else { - Logger.i(_tag, 'BLE未连接,等待BLE连接'); - _transition(PairingState.waitingBle); - } + if (await authbindingDevice(device)) { + Logger.i(_tag, '设备验证通过'); + _verifiedDeviceAddress = device.address; + // 检查是否已有BLE关联 + final pairedMacAddress = await _bleManager.getPairedMacAddress(); + Logger.i(_tag, '已关联的MAC地址: $pairedMacAddress'); + + if (pairedMacAddress != null && + pairedMacAddress.toUpperCase() == device.address.toUpperCase()) { + // 已有关联,检查BLE连接状态 + Logger.i(_tag, '设备已关联,检查BLE连接状态'); + final currentBleState = await _bleManager.getConnectionState(); + Logger.i(_tag, '验证时BLE状态: $currentBleState'); + + if (currentBleState == 2) { + // BleConnectionState.connected + Logger.i(_tag, 'BLE已连接,直接完成配对'); + _transition(PairingState.completed); } else { - // 没有关联或关联的是其他设备,需要进行关联 - Logger.i(_tag, '需要进行BLE设备关联'); - _transition(PairingState.associating); + Logger.i(_tag, 'BLE未连接,等待BLE连接'); + _transition(PairingState.waitingBle); } } else { - // _setError(ErrorType.deviceNotMatch, '请连接 DeepSound 设备'); - _transition(PairingState.completed); + // 没有关联或关联的是其他设备,需要进行关联 + Logger.i(_tag, '需要进行BLE设备关联'); + _transition(PairingState.associating); } } else { //验证失败 进入手动授权 @@ -531,27 +525,76 @@ class PairingController extends GetxController { } //自动绑定设备 - Future authbindingDevice() async { - if (User.instance.earphonemac == currentDevice?.address) { + Future authbindingDevice(BluetoothDevice device) async { + if (User.instance.earphonemac == device.address) { // 已经绑定 return true; } - // 调用BLE管理器的自动绑定方法 - final response = await Api.bindearphone({ - "earphonemac": currentDevice?.address, - }); - Logger.i(_tag, '手动授权结果 ${response}'); - if (response != null) { - Logger.i(_tag, '设备自动绑定成功'); - User.instance.earphonemac = currentDevice?.address ?? ''; - return true; + // 检查设备名称是否包含 DeepSound 或 DeapSound + final deviceName = device.name ?? ''; + print('获取到 deviceName: $deviceName'); + if (deviceName.contains('DeepSound') || deviceName.contains('DeapSound')) { + await _bleManager.startScan(); + // 尝试最多读取 3 次设备信息,每次间隔 1 秒 + Map? deviceinfo; + for (int i = 0; i < 3; i++) { + deviceinfo = await _bleManager.getDeviceInfo(device.address); + print('获取到 deviceinfo: $deviceinfo'); + if (deviceinfo != null) { + break; + } + await Future.delayed(Duration(seconds: 1)); + } + print('获取到 deviceinfo: $deviceinfo'); + if (deviceinfo != null) { + // final code = processHex(deviceinfo["hex"]!, 15, 25); + String code = extractAndReverseBytes(deviceinfo["hex"]!, 15, 24); + print('获取到 code: $code'); + // 安全检查长度 + if (code != "") { + final response = await Api.bindearphone({ + "code": code, + "earphonemac": device.address, + }); + Logger.i(_tag, '手动授权结果 ${response}'); + if (response != null) { + Logger.i(_tag, '设备自动绑定成功'); + User.instance.earphonemac = device.address ?? ''; + return true; + } else { + Logger.e(_tag, '手动绑定失败!'); + return false; + } + } else { + return false; + } + } + return false; + // 调用BLE管理器的自动绑定方法 } else { - Logger.e(_tag, '手动绑定失败!'); return false; } } + int reverseBits(int byte) { + // 这里是 nibble swap 还是 bit 反转?看你需求,先保持 nibble swap + return ((byte & 0x0F) << 4) | ((byte & 0xF0) >> 4); + } + + String extractAndReverseBytes(String hex, int start, int end) { + List bytes = []; + for (int i = 0; i < hex.length; i += 2) { + bytes.add(int.parse(hex.substring(i, i + 2), radix: 16)); + } + List slice = bytes.sublist(start, end + 1); + List reversed = slice.reversed.toList(); + return reversed + .map((b) => b.toRadixString(16).padLeft(2, '0')) + .join() + .toUpperCase(); + } + /// 用户点击授权码输入框 void onAutoCodeInputTap() async { // 调用BLE管理器的自动绑定方法 diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt index 70c58d3d0..b2fbe0cab 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt @@ -298,32 +298,48 @@ object BleService { } } + fun getDeviceInfo(address: String): Map? { + + // 更新缓存 + val idx = scanResults.indexOfFirst { it.device.address == address } + Log.d(TAG, "获取设备信息:${address} idx:$idx") + if (idx >= 0){ + val result = scanResults[idx]; + return printDeviceInfo(result) + } + return null; + } + /** * 打印设备信息和服务UUID */ - private fun printDeviceInfo(result: ScanResult) { + private fun printDeviceInfo(result: ScanResult):Map?{ val device = result.device val scanRecord = result.scanRecord val serviceUuids = scanRecord?.serviceUuids?.joinToString(", ") { it.uuid.toString() } ?: "无" Log.d( - TAG, "扫描到设备: 名称=${device.name ?: "未知"}, 地址=${device.address}, " + + TAG, "liwei--------- 扫描到设备: 名称=${device.name ?: "未知"}, 地址=${device.address}, " + "RSSI=${result.rssi}, 服务UUID=[$serviceUuids]" ) - + val info = mutableMapOf( + "name" to (device.name ?: "未知"), + "address" to device.address + ) // 打印广播数据的详细信息 val scanData = scanRecord?.bytes if (scanData != null) { val hexString = scanData.joinToString("") { String.format("%02X", it) } - Log.d(TAG, "广播数据: $hexString") - + Log.d(TAG, "liwei------------ 广播数据: $hexString") + info["hex"] = hexString // 解析服务数据 scanRecord.serviceData?.forEach { (uuid, data) -> val dataHex = data.joinToString("") { String.format("%02X", it) } Log.d(TAG, "服务数据: UUID=${uuid.uuid}, 数据=$dataHex") } } + return info } // ====================================================================================================== diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt index 07f3ab396..3e5062717 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt @@ -294,6 +294,16 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, result.error("UNSUPPORTED", "设备不支持Companion Device Manager", null) } } + "startScan" -> { + val connected = BleService.startScan() + result.success(connected) + } + "getDeviceInfo" -> { + val address = call.argument("address")?:"" + val info = BleService.getDeviceInfo(address); + Log.d(TAG, "getDeviceInfo $info") + result.success(info) + } else -> { result.notImplemented() } diff --git a/local_plugins/ble_service/lib/ble_service.dart b/local_plugins/ble_service/lib/ble_service.dart index ad11aa617..956e3a28e 100644 --- a/local_plugins/ble_service/lib/ble_service.dart +++ b/local_plugins/ble_service/lib/ble_service.dart @@ -281,6 +281,38 @@ class BleService { } } + /// 断开连接 + Future startScan() async { + try { + final result = await _methodChannel.invokeMethod('startScan'); + return result ?? false; + } catch (e) { + print('开启扫描ble设备失败: $e'); + return false; + } + } + + /// 断开连接 + Future?> getDeviceInfo(String address) async { + try { + final result = await _methodChannel + .invokeMethod('getDeviceInfo', {'address': address}); + print('获取设备信息!: $result'); + if (result != null) { + return { + 'name': result['name'] ?? "未知", + 'address': result['address'] ?? "", + 'hex': result['hex'] ?? "", + }; + } else { + return null; + } + } catch (e) { + print('获取设备信息失败!: $e'); + return null; + } + } + /// 注册BLE后台扫描 /// /// 注册后,即使应用被杀死,系统也会在检测到设备广播时唤醒应用 From c26c6ce4da8a58857c51925bc49222f7ff2f151c Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Thu, 3 Jul 2025 11:44:24 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BC=9A=E8=AE=AE?= =?UTF-8?q?=E5=8A=A9=E6=89=8B=E8=BF=9E=E6=8E=A5=E8=AE=BE=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bindings/meeting_connect_binding.dart | 10 +++ .../meeting_connect_controller.dart | 3 + .../meeting/views/meeting_connect_view.dart | 13 ++++ lib/modules/meeting/views/meeting_view.dart | 66 ++++++++++++++++++- lib/routes/app_pages.dart | 9 +++ lib/routes/app_routes.dart | 1 + 6 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 lib/modules/meeting/bindings/meeting_connect_binding.dart create mode 100644 lib/modules/meeting/controllers/meeting_connect_controller.dart create mode 100644 lib/modules/meeting/views/meeting_connect_view.dart diff --git a/lib/modules/meeting/bindings/meeting_connect_binding.dart b/lib/modules/meeting/bindings/meeting_connect_binding.dart new file mode 100644 index 000000000..0120ae056 --- /dev/null +++ b/lib/modules/meeting/bindings/meeting_connect_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; +import '../controllers/meeting_connect_controller.dart'; + +class MeetingConnectBinding extends Bindings { + @override + void dependencies() { + // 注册控制器 + Get.lazyPut(() => MeetingConnectController()); + } +} diff --git a/lib/modules/meeting/controllers/meeting_connect_controller.dart b/lib/modules/meeting/controllers/meeting_connect_controller.dart new file mode 100644 index 000000000..ff2d95a6b --- /dev/null +++ b/lib/modules/meeting/controllers/meeting_connect_controller.dart @@ -0,0 +1,3 @@ +import 'package:get/get.dart'; + +class MeetingConnectController extends GetxController {} diff --git a/lib/modules/meeting/views/meeting_connect_view.dart b/lib/modules/meeting/views/meeting_connect_view.dart new file mode 100644 index 000000000..a864a097a --- /dev/null +++ b/lib/modules/meeting/views/meeting_connect_view.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../controllers/meeting_connect_controller.dart'; + +class MeetingConnectView extends GetView { + const MeetingConnectView({super.key}); + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/modules/meeting/views/meeting_view.dart b/lib/modules/meeting/views/meeting_view.dart index 7107f8de5..db4fe3ba8 100644 --- a/lib/modules/meeting/views/meeting_view.dart +++ b/lib/modules/meeting/views/meeting_view.dart @@ -23,7 +23,15 @@ class MeetingView extends GetView { appBar: _buildAppBar(context, isDarkMode), body: Stack( children: [ - _buildBody(context, isDarkMode), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildConnect(isDarkMode), + Expanded( + child: _buildBody(context, isDarkMode), + ), + ], + ), _buildMicButton(context, isDarkMode), ], ), @@ -32,6 +40,62 @@ class MeetingView extends GetView { ); } + Widget _buildConnect(bool isDarkMode) { + return GestureDetector( + onTap: () { + Get.toNamed(Routes.meetingConnect); + }, + child: Container( + padding: EdgeInsets.all(5.w), + margin: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 10.w, + ), + decoration: BoxDecoration( + color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white, + borderRadius: BorderRadius.circular(50.w), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 32.w, + height: 32.w, + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(50.w), + ), + child: Icon( + Icons.add, + color: Colors.white, + size: 20.sp, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 5.w), + child: Text( + '连接', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 14.sp, + ), + ), + ) + ], + ), + ), + ); + } + /// 构建底部中间的录音按钮 Widget _buildMicButton(BuildContext context, bool isDarkMode) { return Positioned( diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 1d544a9e7..dd4cff768 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -6,7 +6,9 @@ import '../modules/home/bindings/home_binding.dart'; import '../modules/explore/views/explore_view.dart'; import '../modules/explore/bindings/explore_binding.dart'; import '../modules/meeting/bindings/meeting_binding.dart'; +import '../modules/meeting/bindings/meeting_connect_binding.dart'; import '../modules/meeting/bindings/meeting_details_binding.dart'; +import '../modules/meeting/views/meeting_connect_view.dart'; import '../modules/meeting/views/meeting_details_view.dart'; import '../modules/meeting/bindings/meeting_record_binding.dart'; import '../modules/meeting/views/meeting_record_view.dart'; @@ -157,6 +159,13 @@ abstract class AppPages { transition: Transition.rightToLeft, transitionDuration: Duration(milliseconds: 250), ), + GetPage( + name: Routes.meetingConnect, + page: () => const MeetingConnectView(), + binding: MeetingConnectBinding(), + transition: Transition.rightToLeft, + transitionDuration: Duration(milliseconds: 250), + ), GetPage( name: Routes.meetingRecord, page: () => const MeetingRecordView(), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 550ab16cc..ddf525e1f 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -22,6 +22,7 @@ abstract class Routes { // 会议相关 static const meeting = '/meeting'; static const meetingDetails = '/meeting/details'; + static const meetingConnect = '/meeting/connect'; static const meetingRecord = '/meeting/record'; // 语言功能 static const translation = '/translation'; // 实时翻译 From 63cfcccedeb36a41f362affb9c9d61b3947a6f92 Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Thu, 3 Jul 2025 14:22:20 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=9F=B3=E4=B9=90?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E5=99=A8=E5=8D=A1=E7=89=87=E4=B8=8D=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/bindings/initial_binding.dart | 6 +- lib/data/services/music_manager.dart | 2 + .../agent/controllers/agent_controller.dart | 534 +++++++++++++++++- lib/modules/agent/views/music_card.dart | 36 +- lib/modules/music/bindings/music_binding.dart | 15 - .../music/controllers/music_controller.dart | 493 ---------------- .../music/services/music_service_facade.dart | 125 ---- .../music/views/music_playlist_view.dart | 108 ++-- .../music_service/MusicService.kt | 129 +++-- .../music_service/MusicServicePlugin.kt | 8 +- .../music_service/lib/music_service.dart | 13 +- 11 files changed, 701 insertions(+), 768 deletions(-) delete mode 100644 lib/modules/music/bindings/music_binding.dart delete mode 100644 lib/modules/music/controllers/music_controller.dart delete mode 100644 lib/modules/music/services/music_service_facade.dart diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart index 01564b26a..b916bc88d 100644 --- a/lib/core/bindings/initial_binding.dart +++ b/lib/core/bindings/initial_binding.dart @@ -10,8 +10,6 @@ import '../../data/services/bluetooth_manager.dart'; import '../../data/services/ble_manager.dart'; import '../../data/services/account_service.dart'; import '../../data/services/version_update_service.dart'; -import '../../modules/music/controllers/music_controller.dart'; -import '../../modules/music/services/music_service_facade.dart'; /// 初始绑定,用于管理全局依赖 class InitialBinding extends Bindings { @@ -37,10 +35,8 @@ class InitialBinding extends Bindings { Get.lazyPut(() => BleManager(), fenix: true); // MusicManager Get.lazyPut(() => MusicManager(), fenix: true); - + // 音乐模块 - Get.lazyPut(() => MusicController(), fenix: true); - Get.lazyPut(() => MusicServiceFacade(), fenix: true); Get.lazyPut(() => NavigationManager(), fenix: true); Get.lazyPut(() => LocationManager(), fenix: true); // 火山AI服务 diff --git a/lib/data/services/music_manager.dart b/lib/data/services/music_manager.dart index 9bfae0e7a..51975424f 100644 --- a/lib/data/services/music_manager.dart +++ b/lib/data/services/music_manager.dart @@ -105,6 +105,7 @@ class MusicManager extends GetxService { required Function() onPrevious, required Function(int) onSeekTo, required Function() onStop, + required Function(int, int, int, double) onStop1, }) async { try { await _musicService.setMediaSessionCallbacks( @@ -114,6 +115,7 @@ class MusicManager extends GetxService { onPrevious: onPrevious, onSeekTo: onSeekTo, onStop: onStop, + onStop1: onStop1, ); } catch (e) { Logger.d('MusicManager', '设置媒体会话回调失败: $e'); diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index d1ae83ce3..4fd37f1cd 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -25,23 +25,35 @@ import '../../../data/models/message_model.dart'; // 语音输入模式枚举 enum VoiceInputMode { - text, // 文字输入模式 - pushToTalk, // 按住说话模式 - phoneCall, // 打电话模式 + text, // 文字输入模式 + pushToTalk, // 按住说话模式 + phoneCall, // 打电话模式 } class AgentController extends GetxController { static AgentController get to => Get.find(); //单例访问模式 - // 聊天消息列表 final messages = [].obs; // UI控制器 final TextEditingController textController = TextEditingController(); final ScrollController scrollController = ScrollController(); + //音乐播放管理 + final _musiceManager = Get.find(); // 导航管理 final _navigationManager = Get.find(); + RxBool isPlaying = false.obs; + RxInt currentId = 0.obs; + + // 播放进度相关状态 + RxInt currentPosition = 0.obs; // 当前播放位置(毫秒) + RxInt duration = 0.obs; // 总时长(毫秒) + RxDouble progress = 0.0.obs; // 播放进度(0.0-1.0) + + // 当前播放列表管理 + final currentPlaylist = >[].obs; // 当前播放列表 + final currentPlaylistIndex = 0.obs; // 当前播放歌曲在列表中的索引 // 各种状态标志 final isInitialized = false.obs; @@ -98,6 +110,8 @@ class AgentController extends GetxController { // BLE事件订阅 StreamSubscription? _bleEventSubscription; + // 进度更新定时器 + Timer? _progressTimer; // 用于跟踪AI回复状态 bool _isNewAssistantResponse = true; String _currentResponseId = ''; @@ -110,17 +124,54 @@ class AgentController extends GetxController { // 是否已加载聊天记录 final isHistoryLoaded = false.obs; + // 添加加载状态跟踪 + final RxBool isLoadingMusic = false.obs; + final RxString loadingMusicId = ''.obs; + + // 设置加载状态的方法 + void setLoadingMusic(String id, bool loading) { + loadingMusicId.value = loading ? id : ''; + isLoadingMusic.value = loading; + } + @override void onInit() { super.onInit(); _loadTtsPreference(); //加载TTS偏好设置 _loadChatHistory(); //加载聊天历史记录 _subscribeToEvents(); //订阅代理服务事件 - _subscribeToBleEvents(); //订阅BLE事件 + _initMusicStatus(); //初始化音乐状态 + _setupMediaSessionCallbacks(); //设置媒体会话回调 _setupTextInputListener(); //设置文本输入监听器 } + // 初始化音乐播放状态 + Future _initMusicStatus() async { + try { + await musicStatus(); + Logger.d('AgentController', '音乐状态初始化完成'); + } catch (e) { + Logger.e('AgentController', '音乐状态初始化失败: $e'); + } + } + // 设置媒体会话回调 + void _setupMediaSessionCallbacks() async { + try { + await _musiceManager.setMediaSessionCallbacks( + onPlay: onMediaSessionPlay, + onPause: onMediaSessionPause, + onNext: onMediaSessionNext, + onPrevious: onMediaSessionPrevious, + onSeekTo: onMediaSessionSeekTo, + onStop: onMediaSessionStop, + onStop1: onMediaSessionSeekTo1, + ); + Logger.d('AgentController', '媒体会话回调设置完成'); + } catch (e) { + Logger.e('AgentController', '媒体会话回调设置失败: $e'); + } + } @override void onClose() { @@ -994,7 +1045,8 @@ class AgentController extends GetxController { _pendingPlusOptionsExpansion = false; // 添加小延迟确保UI稳定 Future.delayed(const Duration(milliseconds: 50), () { - if (!isKeyboardVisible.value) { // 再次确认键盘状态 + if (!isKeyboardVisible.value) { + // 再次确认键盘状态 isPlusOptionsExpanded.value = true; } }); @@ -1194,7 +1246,8 @@ class AgentController extends GetxController { } // 检查文字模式与语音模式的一致性 - if (isTextInputMode.value && (isPushToTalkMode.value || isPhoneCallMode.value)) { + if (isTextInputMode.value && + (isPushToTalkMode.value || isPhoneCallMode.value)) { Logger.w(TAG, '状态冲突:文字模式与语音模式同时启用,优先保持语音模式'); isTextInputMode.value = false; } @@ -1268,12 +1321,298 @@ class AgentController extends GetxController { await stopVoiceInput(); } + // 获取音乐播放状态 + Future musicStatus() async { + try { + var status = await _musiceManager.getStatus(); + isPlaying.value = status['isPlaying'] ?? false; + currentId.value = status['id'] ?? 0; + Logger.d('AgentController', + '音乐状态更新: playing=${isPlaying.value}, url=${currentId.value}'); + print("音乐状态更新: playing=${isPlaying.value}, url=${currentId.value}"); + } catch (e) { + Logger.e('AgentController', '获取音乐状态失败: $e'); + } + } + // 播放音乐 + Future musicPlay(Map song) async { + try { + Logger.d(TAG, '开始播放音乐: ${song['title'] ?? song['name']}'); + + // 立即更新UI状态,提供即时反馈 + isPlaying.value = true; + // 修复:确保currentId接收int类型 + final songId = song['id']; + currentId.value = songId is int + ? songId + : (int.tryParse(songId?.toString() ?? '0') ?? 0); + + // 重置进度状态 + currentPosition.value = 0; + duration.value = 0; + progress.value = 0.0; + + final id = song['id'] ?? ""; + final timestampMs = song['ts'] ?? 0; + + // 检查URL是否需要刷新(只有当有时间戳时才检查) + if (timestampMs > 0) { + final now = DateTime.now(); + final tsTime = DateTime.fromMillisecondsSinceEpoch(timestampMs).add( + const Duration(minutes: 10), // 转换为北京时间 + ); + + // 判断是否过期 + if (tsTime.isBefore(now)) { + Logger.d(TAG, '音乐URL已过期,正在获取新URL...'); + try { + var response = await Api.getMusicUrl({ + "ids": [id] + }); + print('getMusicUrl 响应数据: ${response.toString()}'); // 调试输出原始数据 + final urldata = response['urls'][0] as Map; + song["url"] = urldata['url'] ?? ''; + // 修复:确保currentId接收int类型 + final newSongId = song['id']; + currentId.value = newSongId is int + ? newSongId + : (int.tryParse(newSongId?.toString() ?? '0') ?? 0); + Logger.d(TAG, '获取新URL成功'); + } catch (e) { + Logger.e(TAG, '获取新URL失败: $e'); + // 如果获取新URL失败,尝试使用原URL + } + } + } + + // 转换为MusicManager期望的格式 + final musicData = { + 'id': song['id']?.toString() ?? '', + 'url': song['url']?.toString() ?? '', + 'title': song['title']?.toString() ?? song['name']?.toString() ?? '', + 'artist': + song['artist']?.toString() ?? song['sgener']?.toString() ?? '', + 'coverUrl': + song['coverUrl']?.toString() ?? song['image']?.toString() ?? '', + }; + + // 调用音乐管理器播放 + await _musiceManager.play(musicData); + + // 更新媒体会话元数据和播放状态 + await _updateMediaSessionMetadata(song); + await _updateMediaSessionPlaybackState(true); + // 启动进度定时器 + _startProgressTimer(); + Logger.d(TAG, '音乐播放启动成功'); + } catch (e) { + Logger.e(TAG, '播放音乐失败: $e'); + // 发生错误时重置状态 + isPlaying.value = false; + currentId.value = 0; + } + } + // 暂停音乐 + Future musicPause() async { + try { + Logger.d('AgentController', '暂停音乐播放'); + await _musiceManager.pause(); + isPlaying.value = false; + _stopProgressTimer(); + // 更新媒体会话播放状态 + await _updateMediaSessionPlaybackState(false); + } catch (e) { + Logger.e('AgentController', '暂停音乐失败: $e'); + } + } + + // 恢复音乐播放 + Future musicResume() async { + try { + Logger.d('AgentController', '恢复播放音乐'); + await _musiceManager.resume(); + isPlaying.value = true; + _startProgressTimer(); + // 更新媒体会话播放状态 + await _updateMediaSessionPlaybackState(true); + + Logger.d('AgentController', '音乐恢复播放成功'); + } catch (e) { + Logger.e('AgentController', '恢复播放音乐失败: $e'); + isPlaying.value = false; + } + } + + // 启动进度更新定时器 + void _startProgressTimer() { + _stopProgressTimer(); // 先停止现有定时器 + + Logger.d('AgentController', '启动播放进度定时器'); + _progressTimer = Timer.periodic(const Duration(milliseconds: 500), (timer) { + if (isPlaying.value && currentId.value != 0) { + updateMusicProgress(); + } else if (!isPlaying.value) { + // 如果不在播放状态,停止定时器 + timer.cancel(); + _progressTimer = null; + Logger.d('AgentController', '播放已停止,取消进度定时器'); + } + }); + } + + // 停止进度更新定时器 + void _stopProgressTimer() { + if (_progressTimer != null) { + Logger.d('AgentController', '停止播放进度定时器'); + _progressTimer?.cancel(); + _progressTimer = null; + } + } + + // 获取播放进度 + Future updateMusicProgress() async { + try { + if (!isPlaying.value || currentId.value == 0) { + return; // 如果没有在播放,直接返回 + } + + // var progressInfo = await _musiceManager.getProgress(); + // final newPosition = progressInfo['currentPosition'] ?? 0; + // final newDuration = progressInfo['duration'] ?? 0; + // final newProgress = progressInfo['progress'] ?? 0.0; + + // bool hasUpdate = false; + + // // 只有当值发生变化时才更新,避免不必要的UI刷新 + // if (currentPosition.value != newPosition) { + // currentPosition.value = newPosition; + // hasUpdate = true; + // } + // if (duration.value != newDuration) { + // duration.value = newDuration; + // hasUpdate = true; + // } + // if ((progress.value - newProgress).abs() > 0.001) { + // // 避免浮点数精度问题 + // progress.value = newProgress; + // hasUpdate = true; + // } + + // 如果有进度更新,通知系统媒体会话更新进度信息 + // if (hasUpdate) { + // await _updateMediaSessionProgress(); + // } + } catch (e) { + Logger.e('AgentController', '获取播放进度失败: $e'); + } + } + +// 获取播放进度 + Future updateMusicProgress1(int currentIdInternal, int newPosition, + int newDuration, double newProgress) async { + try { + if (!isPlaying.value || currentId.value == 0) { + return; // 如果没有在播放,直接返回 + } + + // // var progressInfo = await _musiceManager.getProgress(); + // final newPosition = progressInfo['currentPosition'] ?? 0; + // final newDuration = progressInfo['duration'] ?? 0; + // final newProgress = progressInfo['progress'] ?? 0.0; + print( + "updateMusicProgress1:currentIdInternal:$currentIdInternal newPosition:$newPosition newDuration:$newDuration newProgress:$newProgress"); + bool hasUpdate = false; + if (currentId.value != currentIdInternal) { + currentId.value = currentIdInternal; + hasUpdate = true; + } + // 只有当值发生变化时才更新,避免不必要的UI刷新 + if (currentPosition.value != newPosition) { + currentPosition.value = newPosition; + hasUpdate = true; + } + if (duration.value != newDuration) { + duration.value = newDuration; + hasUpdate = true; + } + if ((progress.value - newProgress).abs() > 0.001) { + // 避免浮点数精度问题 + progress.value = newProgress; + hasUpdate = true; + } + + // // 如果有进度更新,通知系统媒体会话更新进度信息 + // if (hasUpdate) { + // await _updateMediaSessionProgress(); + // } + } catch (e) { + Logger.e('AgentController', '获取播放进度失败: $e'); + } + } + + /// 更新系统媒体会话的进度信息 + Future _updateMediaSessionProgress() async { + try { + // 这里将调用music_service插件的方法来更新系统媒体会话进度 + // 具体实现将在music_service插件中添加 + await _musiceManager.updateMediaSessionProgress( + currentPosition.value, + duration.value, + progress.value, + ); + } catch (e) { + // 忽略错误,避免影响正常播放 + Logger.d('AgentController', '更新媒体会话进度失败: $e'); + } + } + + /// 更新系统媒体会话的元数据 + Future _updateMediaSessionMetadata(Map song) async { + try { + await _musiceManager.updateMediaSessionMetadata(song); + } catch (e) { + Logger.d('AgentController', '更新媒体会话元数据失败: $e'); + } + } + + /// 更新系统媒体会话的播放状态 + Future _updateMediaSessionPlaybackState(bool isPlaying) async { + try { + await _musiceManager.updateMediaSessionPlaybackState(isPlaying); + } catch (e) { + Logger.d('AgentController', '更新媒体会话播放状态失败: $e'); + } + } + + // 跳转到指定位置 + Future seekToPosition(int positionMs) async { + try { + await _musiceManager.seekTo(positionMs); + // 立即更新进度状态 + // await updateMusicProgress(); + } catch (e) { + Logger.e('AgentController', '跳转播放位置失败: $e'); + } + } + + // 跳转到指定位置 + Future seekToPosition1(int currentIdInternal, int newPosition, + int newDuration, double newProgress) async { + try { + //await _musiceManager.seekTo(positionMs); + // 立即更新进度状态 + await updateMusicProgress1( + currentIdInternal, newPosition, newDuration, newProgress); + } catch (e) { + Logger.e('AgentController', '跳转播放位置失败: $e'); + } + } // 修改自动停止处理,遵循TTS设置 void _handleAutoStop(int index) { @@ -1313,7 +1652,188 @@ class AgentController extends GetxController { ); } + /// 播放音乐列表 + Future playPlaylist(List> playlist) async { + try { + if (playlist.isEmpty) { + Logger.e(TAG, '播放列表为空,无法播放'); + return; + } + + Logger.d(TAG, '开始播放音乐列表,共${playlist.length}首歌曲'); + + // 保存当前播放列表 + currentPlaylist.assignAll(playlist); + currentPlaylistIndex.value = 0; + + // 设置加载状态 + isLoadingMusic.value = true; + loadingMusicId.value = playlist[0]['id']?.toString() ?? ''; + + // 重置播放状态 + isPlaying.value = true; + currentPosition.value = 0; + duration.value = 0; + progress.value = 0.0; + + // 格式化歌曲列表数据 + final formattedPlaylist = playlist.map((song) { + return { + 'id': song['id']?.toString() ?? '', + 'url': song['url']?.toString() ?? '', + 'title': song['title']?.toString() ?? song['name']?.toString() ?? '', + 'artist': + song['artist']?.toString() ?? song['sgener']?.toString() ?? '', + 'coverUrl': + song['coverUrl']?.toString() ?? song['image']?.toString() ?? '', + }; + }).toList(); + + // 调用音乐管理器播放列表 + await _musiceManager.playPlaylist(formattedPlaylist); + + // 更新当前播放歌曲ID + if (formattedPlaylist.isNotEmpty) { + currentId.value = int.tryParse(formattedPlaylist[0]['id'] ?? '0') ?? 0; + } + + // 启动进度定时器 + _startProgressTimer(); + + Logger.d(TAG, '音乐列表播放启动成功'); + } catch (e) { + Logger.e(TAG, '播放音乐列表失败: $e'); + isPlaying.value = false; + currentId.value = 0; + + // 显示错误提示 + Get.snackbar( + '播放失败', + '无法播放音乐列表,请稍后重试', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red[400], + colorText: Colors.white, + duration: const Duration(seconds: 2), + ); + } finally { + // 清除加载状态 + isLoadingMusic.value = false; + loadingMusicId.value = ''; + } + } + + /// 播放上一曲 + Future playPrevious() async { + if (currentPlaylist.isEmpty) { + Logger.w(TAG, '当前播放列表为空,无法播放上一曲'); + return; + } + + // 计算上一曲的索引 + int previousIndex = currentPlaylistIndex.value - 1; + if (previousIndex < 0) { + previousIndex = currentPlaylist.length - 1; // 循环到最后一首 + } + + currentPlaylistIndex.value = previousIndex; + final previousSong = currentPlaylist[previousIndex]; + + Logger.d(TAG, '播放上一曲: ${previousSong['name'] ?? previousSong['title']}'); + await musicPlay(previousSong); + } + + /// 播放下一曲 + Future playNext() async { + if (currentPlaylist.isEmpty) { + Logger.w(TAG, '当前播放列表为空,无法播放下一曲'); + return; + } + + // 计算下一曲的索引 + int nextIndex = currentPlaylistIndex.value + 1; + if (nextIndex >= currentPlaylist.length) { + nextIndex = 0; // 循环到第一首 + } + + currentPlaylistIndex.value = nextIndex; + final nextSong = currentPlaylist[nextIndex]; + + Logger.d(TAG, '播放下一曲: ${nextSong['name'] ?? nextSong['title']}'); + await musicPlay(nextSong); + } + + /// 更新当前播放列表和索引(当单独播放某首歌曲时调用) + void updateCurrentPlaylistContext( + List> playlist, int songId) { + currentPlaylist.assignAll(playlist); + + // 查找当前歌曲在列表中的索引 + int index = -1; + for (int i = 0; i < playlist.length; i++) { + if (int.tryParse(playlist[i]['id']?.toString() ?? '0') == songId) { + index = i; + break; + } + } + + if (index != -1) { + currentPlaylistIndex.value = index; + Logger.d(TAG, '更新播放列表上下文,当前歌曲索引: $index'); + } + } + + // ========== 媒体会话回调处理方法 ========== + + /// 处理来自系统通知栏的播放操作 + Future onMediaSessionPlay() async { + Logger.d(TAG, '收到媒体会话播放回调'); + if (!isPlaying.value) { + await musicResume(); + } + } + + /// 处理来自系统通知栏的暂停操作 + Future onMediaSessionPause() async { + Logger.d(TAG, '收到媒体会话暂停回调'); + if (isPlaying.value) { + await musicPause(); + } + } + /// 处理来自系统通知栏的下一曲操作 + Future onMediaSessionNext() async { + Logger.d(TAG, '收到媒体会话下一曲回调'); + await playNext(); + } + /// 处理来自系统通知栏的上一曲操作 + Future onMediaSessionPrevious() async { + Logger.d(TAG, '收到媒体会话上一曲回调'); + await playPrevious(); + } + /// 处理来自系统通知栏的跳转操作 + Future onMediaSessionSeekTo(int positionMs) async { + Logger.d(TAG, '收到媒体会话跳转回调: ${positionMs}ms'); + await seekToPosition(positionMs); + } + + /// 处理来自系统通知栏的停止操作 + Future onMediaSessionStop() async { + Logger.d(TAG, '收到媒体会话停止回调'); + await musicPause(); + // 重置播放状态 + currentPosition.value = 0; + progress.value = 0.0; + _stopProgressTimer(); + } + + /// 处理来自系统通知栏的跳转操作 + Future onMediaSessionSeekTo1(int currentIdInternal, int newPosition, + int newDuration, double newProgress) async { + print( + "onMediaSessionSeekTo1 newPosition:$newPosition newDuration:$newDuration newProgress:$newProgress"); + await seekToPosition1( + currentIdInternal, newPosition, newDuration, newProgress); + } } diff --git a/lib/modules/agent/views/music_card.dart b/lib/modules/agent/views/music_card.dart index f64564c1d..ecc1e0fe1 100644 --- a/lib/modules/agent/views/music_card.dart +++ b/lib/modules/agent/views/music_card.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import '../../music/services/music_service_facade.dart'; +import '../../../routes/app_routes.dart'; +import '../controllers/agent_controller.dart'; import 'circular_progress_button.dart'; /// 构建音乐项目 Widget buildMusicItem(BuildContext context, int id, String name, String url, String singer, String image) { - final musicFacade = Get.find(); // 获取音乐服务门面 + final controller = Get.find(); // 获取控制器 final isDarkMode = Theme.of(context).brightness == Brightness.dark; - Map song = { 'id': id, 'url': url, @@ -16,7 +16,8 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, 'artist': singer, 'coverUrl': image, }; - // 音乐状态由MusicController自动管理,无需手动刷新 + + controller.musicStatus(); //刷新状态 return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( @@ -82,11 +83,11 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, ), Obx(() { final isThisPlaying = - musicFacade.isPlaying.value && musicFacade.currentId.value == id; + controller.isPlaying.value && controller.currentId.value == id; // 只有当前播放的歌曲才显示进度 final currentProgress = - isThisPlaying ? musicFacade.progress.value : 0.0; + isThisPlaying ? controller.progress.value : 0.0; return CircularProgressButton( isPlaying: isThisPlaying, @@ -97,21 +98,21 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, strokeWidth: 2.0, onPlayPause: () async { try { - if (musicFacade.isPlaying.value) { - if (musicFacade.currentId.value == id) { + if (controller.isPlaying.value) { + if (controller.currentId.value == id) { // 当前歌曲正在播放,暂停它 - await musicFacade.musicPause(); + await controller.musicPause(); } else { // 其他歌曲正在播放,播放这首歌 - await musicFacade.musicPlay(song); + await controller.musicPlay(song); } } else { - if (musicFacade.currentId.value == id) { + if (controller.currentId.value == id) { // 当前歌曲已暂停,恢复播放 - await musicFacade.musicResume(); + await controller.musicResume(); } else { // 播放新歌曲 - await musicFacade.musicPlay(song); + await controller.musicPlay(song); } } } catch (e) { @@ -122,8 +123,8 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, ? (seekProgress) { // 只有当前播放的歌曲才允许跳转 final targetPosition = - (musicFacade.duration.value * seekProgress).round(); - musicFacade.seekToPosition(targetPosition); + (controller.duration.value * seekProgress).round(); + controller.seekToPosition(targetPosition); } : null, ); @@ -137,7 +138,8 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, Widget buildMusicContent( BuildContext context, List> playlist) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; - + final controller = Get.find(); // 获取控制器 + controller.updateCurrentPlaylistContext(playlist, controller.currentId.value); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -221,7 +223,7 @@ Widget buildMusicContent( child: TextButton( onPressed: () { // 跳转到完整歌单页面,传递完整的歌单数据 - Get.toNamed('/music/playlist', arguments: playlist); + Get.toNamed(Routes.musicPlaylist, arguments: playlist); }, style: TextButton.styleFrom( minimumSize: Size.zero, diff --git a/lib/modules/music/bindings/music_binding.dart b/lib/modules/music/bindings/music_binding.dart deleted file mode 100644 index 231934cfa..000000000 --- a/lib/modules/music/bindings/music_binding.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:get/get.dart'; -import '../controllers/music_controller.dart'; - -/// 音乐模块绑定 -/// 负责注册音乐相关的控制器和服务 -class MusicBinding extends Bindings { - @override - void dependencies() { - // 注册音乐控制器为单例 - Get.lazyPut( - () => MusicController(), - fenix: true, // 允许重新创建 - ); - } -} \ No newline at end of file diff --git a/lib/modules/music/controllers/music_controller.dart b/lib/modules/music/controllers/music_controller.dart deleted file mode 100644 index a092d69f7..000000000 --- a/lib/modules/music/controllers/music_controller.dart +++ /dev/null @@ -1,493 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import '../../../core/utils/logger.dart'; -import '../../../data/services/music_manager.dart'; -import '../../../data/services/network/api.dart'; - -/// 音乐播放控制器 -/// 负责管理音乐播放状态、播放列表和播放控制 -class MusicController extends GetxController { - static const String TAG = 'MusicController'; - - // 音乐播放管理器 - final _musicManager = Get.find(); - - // 播放状态 - final isPlaying = false.obs; - final currentId = 0.obs; - - // 播放进度相关状态 - final currentPosition = 0.obs; // 当前播放位置(毫秒) - final duration = 0.obs; // 总时长(毫秒) - final progress = 0.0.obs; // 播放进度(0.0-1.0) - - // 当前播放列表管理 - final currentPlaylist = >[].obs; // 当前播放列表 - final currentPlaylistIndex = 0.obs; // 当前播放歌曲在列表中的索引 - - // 加载状态跟踪 - final isLoadingMusic = false.obs; - final loadingMusicId = ''.obs; - - // 进度更新定时器 - Timer? _progressTimer; - - @override - void onInit() { - super.onInit(); - _initMusicStatus(); - _setupMediaSessionCallbacks(); - } - - @override - void onClose() { - _stopProgressTimer(); - super.onClose(); - } - - // 初始化音乐播放状态 - Future _initMusicStatus() async { - try { - await musicStatus(); - Logger.d(TAG, '音乐状态初始化完成'); - } catch (e) { - Logger.e(TAG, '音乐状态初始化失败: $e'); - } - } - - // 设置媒体会话回调 - void _setupMediaSessionCallbacks() async { - try { - await _musicManager.setMediaSessionCallbacks( - onPlay: onMediaSessionPlay, - onPause: onMediaSessionPause, - onNext: onMediaSessionNext, - onPrevious: onMediaSessionPrevious, - onSeekTo: onMediaSessionSeekTo, - onStop: onMediaSessionStop, - ); - Logger.d(TAG, '媒体会话回调设置完成'); - } catch (e) { - Logger.e(TAG, '媒体会话回调设置失败: $e'); - } - } - - // 设置加载状态的方法 - void setLoadingMusic(String id, bool loading) { - loadingMusicId.value = loading ? id : ''; - isLoadingMusic.value = loading; - } - - // 获取音乐播放状态 - Future musicStatus() async { - try { - var status = await _musicManager.getStatus(); - isPlaying.value = status['isPlaying'] ?? false; - currentId.value = status['id'] ?? 0; - Logger.d(TAG, '音乐状态更新: playing=${isPlaying.value}, id=${currentId.value}'); - } catch (e) { - Logger.e(TAG, '获取音乐状态失败: $e'); - } - } - - // 播放音乐 - Future musicPlay(Map song) async { - try { - Logger.d(TAG, '开始播放音乐: ${song['title'] ?? song['name']}'); - - // 立即更新UI状态,提供即时反馈 - isPlaying.value = true; - // 修复:确保currentId接收int类型 - final songId = song['id']; - currentId.value = songId is int - ? songId - : (int.tryParse(songId?.toString() ?? '0') ?? 0); - - // 重置进度状态 - currentPosition.value = 0; - duration.value = 0; - progress.value = 0.0; - - final id = song['id'] ?? ""; - final timestampMs = song['ts'] ?? 0; - - // 检查URL是否需要刷新(只有当有时间戳时才检查) - if (timestampMs > 0) { - final now = DateTime.now(); - final tsTime = DateTime.fromMillisecondsSinceEpoch(timestampMs).add( - const Duration(minutes: 10), // 转换为北京时间 - ); - - // 判断是否过期 - if (tsTime.isBefore(now)) { - Logger.d(TAG, '音乐URL已过期,正在获取新URL...'); - try { - var response = await Api.getMusicUrl({ - "ids": [id] - }); - print('getMusicUrl 响应数据: ${response.toString()}'); // 调试输出原始数据 - final urldata = response['urls'][0] as Map; - song["url"] = urldata['url'] ?? ''; - // 修复:确保currentId接收int类型 - final newSongId = song['id']; - currentId.value = newSongId is int - ? newSongId - : (int.tryParse(newSongId?.toString() ?? '0') ?? 0); - Logger.d(TAG, '获取新URL成功'); - } catch (e) { - Logger.e(TAG, '获取新URL失败: $e'); - // 如果获取新URL失败,尝试使用原URL - } - } - } - - // 转换为MusicManager期望的格式 - final musicData = { - 'id': song['id']?.toString() ?? '', - 'url': song['url']?.toString() ?? '', - 'title': song['title']?.toString() ?? song['name']?.toString() ?? '', - 'artist': - song['artist']?.toString() ?? song['sgener']?.toString() ?? '', - 'coverUrl': - song['coverUrl']?.toString() ?? song['image']?.toString() ?? '', - }; - - // 调用音乐管理器播放 - await _musicManager.play(musicData); - - // 更新媒体会话元数据和播放状态 - await _updateMediaSessionMetadata(song); - await _updateMediaSessionPlaybackState(true); - - // 启动进度定时器 - _startProgressTimer(); - - Logger.d(TAG, '音乐播放启动成功'); - } catch (e) { - Logger.e(TAG, '播放音乐失败: $e'); - // 发生错误时重置状态 - isPlaying.value = false; - currentId.value = 0; - } - } - - // 暂停音乐 - Future musicPause() async { - try { - Logger.d(TAG, '暂停音乐播放'); - await _musicManager.pause(); - isPlaying.value = false; - _stopProgressTimer(); - - // 更新媒体会话播放状态 - await _updateMediaSessionPlaybackState(false); - } catch (e) { - Logger.e(TAG, '暂停音乐失败: $e'); - } - } - - // 恢复音乐播放 - Future musicResume() async { - try { - Logger.d(TAG, '恢复播放音乐'); - await _musicManager.resume(); - isPlaying.value = true; - _startProgressTimer(); - - // 更新媒体会话播放状态 - await _updateMediaSessionPlaybackState(true); - - Logger.d(TAG, '音乐恢复播放成功'); - } catch (e) { - Logger.e(TAG, '恢复播放音乐失败: $e'); - isPlaying.value = false; - } - } - - // 启动进度更新定时器 - void _startProgressTimer() { - _stopProgressTimer(); // 先停止现有定时器 - - Logger.d(TAG, '启动播放进度定时器'); - _progressTimer = Timer.periodic(const Duration(milliseconds: 500), (timer) { - if (isPlaying.value && currentId.value != 0) { - updateMusicProgress(); - } else if (!isPlaying.value) { - // 如果不在播放状态,停止定时器 - timer.cancel(); - _progressTimer = null; - Logger.d(TAG, '播放已停止,取消进度定时器'); - } - }); - } - - // 停止进度更新定时器 - void _stopProgressTimer() { - if (_progressTimer != null) { - Logger.d(TAG, '停止播放进度定时器'); - _progressTimer?.cancel(); - _progressTimer = null; - } - } - - // 获取播放进度 - Future updateMusicProgress() async { - try { - if (!isPlaying.value || currentId.value == 0) { - return; // 如果没有在播放,直接返回 - } - - var progressInfo = await _musicManager.getProgress(); - final newPosition = progressInfo['currentPosition'] ?? 0; - final newDuration = progressInfo['duration'] ?? 0; - final newProgress = progressInfo['progress'] ?? 0.0; - - bool hasUpdate = false; - - // 只有当值发生变化时才更新,避免不必要的UI刷新 - if (currentPosition.value != newPosition) { - currentPosition.value = newPosition; - hasUpdate = true; - } - if (duration.value != newDuration) { - duration.value = newDuration; - hasUpdate = true; - } - if ((progress.value - newProgress).abs() > 0.001) { - // 避免浮点数精度问题 - progress.value = newProgress; - hasUpdate = true; - } - - // 如果有进度更新,通知系统媒体会话更新进度信息 - if (hasUpdate) { - await _updateMediaSessionProgress(); - } - } catch (e) { - Logger.e(TAG, '获取播放进度失败: $e'); - } - } - - // 跳转到指定位置 - Future seekToPosition(int positionMs) async { - try { - await _musicManager.seekTo(positionMs); - // 立即更新进度状态 - await updateMusicProgress(); - } catch (e) { - Logger.e(TAG, '跳转播放位置失败: $e'); - } - } - - /// 播放音乐列表 - Future playPlaylist(List> playlist) async { - try { - if (playlist.isEmpty) { - Logger.e(TAG, '播放列表为空,无法播放'); - return; - } - - Logger.d(TAG, '开始播放音乐列表,共${playlist.length}首歌曲'); - - // 保存当前播放列表 - currentPlaylist.assignAll(playlist); - currentPlaylistIndex.value = 0; - - // 设置加载状态 - isLoadingMusic.value = true; - loadingMusicId.value = playlist[0]['id']?.toString() ?? ''; - - // 重置播放状态 - isPlaying.value = true; - currentPosition.value = 0; - duration.value = 0; - progress.value = 0.0; - - // 格式化歌曲列表数据 - final formattedPlaylist = playlist.map((song) { - return { - 'id': song['id']?.toString() ?? '', - 'url': song['url']?.toString() ?? '', - 'title': song['title']?.toString() ?? song['name']?.toString() ?? '', - 'artist': - song['artist']?.toString() ?? song['sgener']?.toString() ?? '', - 'coverUrl': - song['coverUrl']?.toString() ?? song['image']?.toString() ?? '', - }; - }).toList(); - - // 调用音乐管理器播放列表 - await _musicManager.playPlaylist(formattedPlaylist); - - // 更新当前播放歌曲ID - if (formattedPlaylist.isNotEmpty) { - currentId.value = int.tryParse(formattedPlaylist[0]['id'] ?? '0') ?? 0; - } - - // 启动进度定时器 - _startProgressTimer(); - - Logger.d(TAG, '音乐列表播放启动成功'); - } catch (e) { - Logger.e(TAG, '播放音乐列表失败: $e'); - isPlaying.value = false; - currentId.value = 0; - - // 显示错误提示 - Get.snackbar( - '播放失败', - '无法播放音乐列表,请稍后重试', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red[400], - colorText: Colors.white, - duration: const Duration(seconds: 2), - ); - } finally { - // 清除加载状态 - isLoadingMusic.value = false; - loadingMusicId.value = ''; - } - } - - /// 播放上一曲 - Future playPrevious() async { - if (currentPlaylist.isEmpty) { - Logger.w(TAG, '当前播放列表为空,无法播放上一曲'); - return; - } - - // 计算上一曲的索引 - int previousIndex = currentPlaylistIndex.value - 1; - if (previousIndex < 0) { - previousIndex = currentPlaylist.length - 1; // 循环到最后一首 - } - - currentPlaylistIndex.value = previousIndex; - final previousSong = currentPlaylist[previousIndex]; - - Logger.d(TAG, '播放上一曲: ${previousSong['name'] ?? previousSong['title']}'); - await musicPlay(previousSong); - } - - /// 播放下一曲 - Future playNext() async { - if (currentPlaylist.isEmpty) { - Logger.w(TAG, '当前播放列表为空,无法播放下一曲'); - return; - } - - // 计算下一曲的索引 - int nextIndex = currentPlaylistIndex.value + 1; - if (nextIndex >= currentPlaylist.length) { - nextIndex = 0; // 循环到第一首 - } - - currentPlaylistIndex.value = nextIndex; - final nextSong = currentPlaylist[nextIndex]; - - Logger.d(TAG, '播放下一曲: ${nextSong['name'] ?? nextSong['title']}'); - await musicPlay(nextSong); - } - - /// 更新当前播放列表和索引(当单独播放某首歌曲时调用) - void updateCurrentPlaylistContext( - List> playlist, int songId) { - currentPlaylist.assignAll(playlist); - - // 查找当前歌曲在列表中的索引 - int index = -1; - for (int i = 0; i < playlist.length; i++) { - if (int.tryParse(playlist[i]['id']?.toString() ?? '0') == songId) { - index = i; - break; - } - } - - if (index != -1) { - currentPlaylistIndex.value = index; - Logger.d(TAG, '更新播放列表上下文,当前歌曲索引: $index'); - } - } - - // ========== 媒体会话回调处理方法 ========== - - /// 处理来自系统通知栏的播放操作 - Future onMediaSessionPlay() async { - Logger.d(TAG, '收到媒体会话播放回调'); - if (!isPlaying.value) { - await musicResume(); - } - } - - /// 处理来自系统通知栏的暂停操作 - Future onMediaSessionPause() async { - Logger.d(TAG, '收到媒体会话暂停回调'); - if (isPlaying.value) { - await musicPause(); - } - } - - /// 处理来自系统通知栏的下一曲操作 - Future onMediaSessionNext() async { - Logger.d(TAG, '收到媒体会话下一曲回调'); - await playNext(); - } - - /// 处理来自系统通知栏的上一曲操作 - Future onMediaSessionPrevious() async { - Logger.d(TAG, '收到媒体会话上一曲回调'); - await playPrevious(); - } - - /// 处理来自系统通知栏的跳转操作 - Future onMediaSessionSeekTo(int positionMs) async { - Logger.d(TAG, '收到媒体会话跳转回调: ${positionMs}ms'); - await seekToPosition(positionMs); - } - - /// 处理来自系统通知栏的停止操作 - Future onMediaSessionStop() async { - Logger.d(TAG, '收到媒体会话停止回调'); - await musicPause(); - // 重置播放状态 - currentPosition.value = 0; - progress.value = 0.0; - _stopProgressTimer(); - } - - // ========== 私有辅助方法 ========== - - /// 更新系统媒体会话的进度信息 - Future _updateMediaSessionProgress() async { - try { - // 具体实现将在music_service插件中添加 - await _musicManager.updateMediaSessionProgress( - currentPosition.value, - duration.value, - progress.value, - ); - } catch (e) { - // 忽略错误,避免影响正常播放 - Logger.d(TAG, '更新媒体会话进度失败: $e'); - } - } - - /// 更新系统媒体会话的元数据 - Future _updateMediaSessionMetadata(Map song) async { - try { - await _musicManager.updateMediaSessionMetadata(song); - } catch (e) { - Logger.d(TAG, '更新媒体会话元数据失败: $e'); - } - } - - /// 更新系统媒体会话的播放状态 - Future _updateMediaSessionPlaybackState(bool isPlaying) async { - try { - await _musicManager.updateMediaSessionPlaybackState(isPlaying); - } catch (e) { - Logger.d(TAG, '更新媒体会话播放状态失败: $e'); - } - } -} \ No newline at end of file diff --git a/lib/modules/music/services/music_service_facade.dart b/lib/modules/music/services/music_service_facade.dart deleted file mode 100644 index 689d249f7..000000000 --- a/lib/modules/music/services/music_service_facade.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'package:get/get.dart'; -import '../controllers/music_controller.dart'; -import '../../../data/services/music_manager.dart'; - -/// 音乐服务门面 -/// 作为音乐功能的统一入口,简化模块间的交互 -class MusicServiceFacade { - // 单例实现 - static final MusicServiceFacade _instance = MusicServiceFacade._internal(); - - factory MusicServiceFacade() => _instance; - - MusicServiceFacade._internal(); - - // 获取音乐控制器实例 - MusicController get musicController => Get.find(); - - // 获取音乐管理器实例 - MusicManager get musicManager => Get.find(); - - /// 播放音乐 - Future musicPlay(Map song) async { - await musicController.musicPlay(song); - } - - /// 暂停音乐 - Future musicPause() async { - await musicController.musicPause(); - } - - /// 恢复播放 - Future musicResume() async { - await musicController.musicResume(); - } - - /// 播放音乐(兼容旧接口) - Future playMusic(Map song) async { - await musicPlay(song); - } - - /// 暂停音乐(兼容旧接口) - Future pauseMusic() async { - await musicPause(); - } - - /// 恢复播放(兼容旧接口) - Future resumeMusic() async { - await musicResume(); - } - - /// 播放/暂停切换 - Future togglePlayPause() async { - if (musicController.isPlaying.value) { - await musicPause(); - } else { - await musicResume(); - } - } - - /// 播放上一曲 - Future playPrevious() async { - await musicController.playPrevious(); - } - - /// 播放下一曲 - Future playNext() async { - await musicController.playNext(); - } - - /// 播放音乐列表 - Future playPlaylist(List> playlist) async { - await musicController.playPlaylist(playlist); - } - - /// 跳转到指定位置 - Future seekToPosition(int positionMs) async { - await musicController.seekToPosition(positionMs); - } - - /// 获取当前播放状态 - bool get isPlayingValue => musicController.isPlaying.value; - - /// 获取当前播放歌曲ID - int get currentIdValue => musicController.currentId.value; - - /// 获取当前播放位置(毫秒) - int get currentPositionValue => musicController.currentPosition.value; - - /// 获取当前歌曲总时长(毫秒) - int get durationValue => musicController.duration.value; - - /// 获取当前播放进度(0.0-1.0) - double get progressValue => musicController.progress.value; - - /// 获取响应式播放状态(用于Obx) - get isPlaying => musicController.isPlaying; - - /// 获取响应式当前播放歌曲ID(用于Obx) - get currentId => musicController.currentId; - - /// 获取响应式当前播放位置(用于Obx) - get currentPosition => musicController.currentPosition; - - /// 获取响应式当前歌曲总时长(用于Obx) - get duration => musicController.duration; - - /// 获取响应式当前播放进度(用于Obx) - get progress => musicController.progress; - - /// 获取当前播放列表 - List> get currentPlaylist => musicController.currentPlaylist; - - /// 获取当前播放索引 - int get currentPlaylistIndex => musicController.currentPlaylistIndex.value; - - /// 设置加载状态 - void setLoadingMusic(String id, bool loading) { - musicController.setLoadingMusic(id, loading); - } - - /// 更新当前播放列表上下文 - void updateCurrentPlaylistContext(List> playlist, int songId) { - musicController.updateCurrentPlaylistContext(playlist, songId); - } -} \ No newline at end of file diff --git a/lib/modules/music/views/music_playlist_view.dart b/lib/modules/music/views/music_playlist_view.dart index bda2fb878..69fa15a85 100644 --- a/lib/modules/music/views/music_playlist_view.dart +++ b/lib/modules/music/views/music_playlist_view.dart @@ -3,8 +3,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter/services.dart'; -import '../services/music_service_facade.dart'; import '../../../core/utils/logger.dart'; +import '../../agent/controllers/agent_controller.dart'; import '../../agent/views/circular_progress_button.dart'; class MusicPlaylistView extends StatelessWidget { @@ -13,7 +13,7 @@ class MusicPlaylistView extends StatelessWidget { @override Widget build(BuildContext context) { ScreenUtil.init(context, designSize: const Size(375, 812)); // 添加设计尺寸参考 - final musicFacade = Get.find(); + final controller = Get.find(); // 获取控制器 final List> playlist = Get.arguments ?? []; final isDarkMode = Theme.of(context).brightness == Brightness.dark; @@ -55,14 +55,14 @@ class MusicPlaylistView extends StatelessWidget { Container( height: MediaQuery.of(context).size.height * 0.38, // 增大高度比例 child: _buildCurrentPlayingSection( - musicFacade, playlist, isDarkMode), + controller, playlist, isDarkMode), ), // 中部:播放全部按钮 - _buildPlayAllSection(musicFacade, playlist, isDarkMode), + _buildPlayAllSection(controller, playlist, isDarkMode), // 下部:歌单列表 - 使用剩余空间 Expanded( child: - _buildPlaylistSection(musicFacade, playlist, isDarkMode), + _buildPlaylistSection(controller, playlist, isDarkMode), ), ], ), @@ -70,19 +70,19 @@ class MusicPlaylistView extends StatelessWidget { } // 构建当前播放音乐详情部分 - Widget _buildCurrentPlayingSection(MusicServiceFacade musicFacade, + Widget _buildCurrentPlayingSection(AgentController controller, List> playlist, bool isDarkMode) { return SingleChildScrollView( // 添加滚动支持 child: Obx(() { // 查找当前播放的歌曲 Map? currentSong; - if (musicFacade.currentId.value != 0) { + if (controller.currentId.value != 0) { try { currentSong = playlist.firstWhere( (song) => int.tryParse(song['id']?.toString() ?? '0') == - musicFacade.currentId.value, + controller.currentId.value, ); } catch (e) { // 如果没找到,使用第一首歌曲 @@ -137,10 +137,10 @@ class MusicPlaylistView extends StatelessWidget { _buildSongInfo(title, artist, isDarkMode), SizedBox(height: 16.h), // 进度条 - _buildProgressBar(musicFacade, isDarkMode), + _buildProgressBar(controller, isDarkMode), SizedBox(height: 15.h), // 播放控制按钮 - _buildPlayControls(musicFacade, playlist, isDarkMode), + _buildPlayControls(controller, playlist, isDarkMode), ], ), ); @@ -149,7 +149,7 @@ class MusicPlaylistView extends StatelessWidget { } // 构建播放全部按钮部分 - Widget _buildPlayAllSection(MusicServiceFacade musicFacade, + Widget _buildPlayAllSection(AgentController controller, List> playlist, bool isDarkMode) { return Container( padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 12.h), @@ -159,7 +159,7 @@ class MusicPlaylistView extends StatelessWidget { child: ElevatedButton.icon( onPressed: () async { if (playlist.isNotEmpty) { - await musicFacade.playPlaylist(playlist); + await controller.playPlaylist(playlist); } }, icon: Icon(Icons.play_arrow, color: Colors.white, size: 20.w), @@ -185,7 +185,7 @@ class MusicPlaylistView extends StatelessWidget { } // 构建歌单列表部分 - Widget _buildPlaylistSection(MusicServiceFacade musicFacade, + Widget _buildPlaylistSection(AgentController controller, List> playlist, bool isDarkMode) { return ListView.builder( padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 8.h), @@ -200,7 +200,7 @@ class MusicPlaylistView extends StatelessWidget { return _buildMusicItem( context, - musicFacade, + controller, id, name, url, @@ -214,7 +214,7 @@ class MusicPlaylistView extends StatelessWidget { Widget _buildMusicItem( BuildContext context, - MusicServiceFacade musicFacade, + AgentController controller, String id, String name, String url, @@ -232,7 +232,7 @@ class MusicPlaylistView extends StatelessWidget { return GestureDetector( onTap: () { - _playSelectedSong(musicFacade, song, Get.arguments ?? []); + _playSelectedSong(controller, song, Get.arguments ?? []); }, child: Container( margin: EdgeInsets.only(bottom: 10.h), @@ -324,10 +324,10 @@ class MusicPlaylistView extends StatelessWidget { // 播放按钮 Obx(() { final currentIdInt = int.tryParse(id) ?? 0; - final isThisPlaying = musicFacade.isPlaying.value && - musicFacade.currentId.value == currentIdInt; + final isThisPlaying = controller.isPlaying.value && + controller.currentId.value == currentIdInt; final currentProgress = - isThisPlaying ? musicFacade.progress.value : 0.0; + isThisPlaying ? controller.progress.value : 0.0; return CircularProgressButton( isPlaying: isThisPlaying, @@ -339,20 +339,20 @@ class MusicPlaylistView extends StatelessWidget { onPlayPause: () async { try { Logger.d('MusicPlaylistView', - '播放按钮被点击,当前播放状态: ${musicFacade.isPlaying.value}'); + '播放按钮被点击,当前播放状态: ${controller.isPlaying.value}'); Logger.d('MusicPlaylistView', - '当前歌曲ID: ${musicFacade.currentId.value}, 点击歌曲ID: $currentIdInt'); - if (musicFacade.isPlaying.value) { - if (musicFacade.currentId.value == currentIdInt) { + '当前歌曲ID: ${controller.currentId.value}, 点击歌曲ID: $currentIdInt'); + if (controller.isPlaying.value) { + if (controller.currentId.value == currentIdInt) { Logger.d('MusicPlaylistView', '暂停当前播放的歌曲'); - await musicFacade.musicPause(); + await controller.musicPause(); } else { Logger.d('MusicPlaylistView', '播放新歌曲'); - await musicFacade.musicPlay(song); + await controller.musicPlay(song); } } else { Logger.d('MusicPlaylistView', '开始播放歌曲'); - await musicFacade.musicPlay(song); + await controller.musicPlay(song); } } catch (e) { Logger.e('MusicPlaylistView', '播放音乐时发生错误: $e'); @@ -362,8 +362,8 @@ class MusicPlaylistView extends StatelessWidget { ? (seekProgress) { // 只有当前播放的歌曲才允许跳转 final targetPosition = - (musicFacade.duration.value * seekProgress).round(); - musicFacade.seekToPosition(targetPosition); + (controller.duration.value * seekProgress).round(); + controller.seekToPosition(targetPosition); } : null, ); @@ -449,14 +449,14 @@ class MusicPlaylistView extends StatelessWidget { } // 构建进度条 - Widget _buildProgressBar(MusicServiceFacade musicFacade, bool isDarkMode) { + Widget _buildProgressBar(AgentController controller, bool isDarkMode) { return Obx(() { return Row( children: [ SizedBox( width: 40.w, child: Text( - _formatDuration(musicFacade.currentPosition.value), + _formatDuration(controller.currentPosition.value), style: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[600], @@ -480,11 +480,11 @@ class MusicPlaylistView extends StatelessWidget { overlayShape: const RoundSliderOverlayShape(overlayRadius: 14), ), child: Slider( - value: musicFacade.progress.value.clamp(0.0, 1.0), + value: controller.progress.value.clamp(0.0, 1.0), onChanged: (value) { final targetPosition = - (musicFacade.duration.value * value).round(); - musicFacade.seekToPosition(targetPosition); + (controller.duration.value * value).round(); + controller.seekToPosition(targetPosition); }, ), ), @@ -493,7 +493,7 @@ class MusicPlaylistView extends StatelessWidget { SizedBox( width: 40.w, child: Text( - _formatDuration(musicFacade.duration.value), + _formatDuration(controller.duration.value), style: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[600], @@ -508,14 +508,14 @@ class MusicPlaylistView extends StatelessWidget { } // 构建播放控制按钮 - Widget _buildPlayControls(MusicServiceFacade musicFacade, + Widget _buildPlayControls(AgentController controller, List> playlist, bool isDarkMode) { return Obx(() { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( - onPressed: () => _playPrevious(musicFacade, playlist), + onPressed: () => _playPrevious(controller, playlist), iconSize: 32.w, padding: EdgeInsets.all(8.w), // 增大触摸区域 icon: Icon( @@ -540,10 +540,10 @@ class MusicPlaylistView extends StatelessWidget { ], ), child: IconButton( - onPressed: () => _togglePlayPause(musicFacade), + onPressed: () => _togglePlayPause(controller), padding: EdgeInsets.zero, icon: Icon( - musicFacade.isPlaying.value ? Icons.pause : Icons.play_arrow, + controller.isPlaying.value ? Icons.pause : Icons.play_arrow, size: 28.w, color: Colors.white, ), @@ -551,7 +551,7 @@ class MusicPlaylistView extends StatelessWidget { ), SizedBox(width: 16.w), IconButton( - onPressed: () => _playNext(musicFacade, playlist), + onPressed: () => _playNext(controller, playlist), iconSize: 32.w, padding: EdgeInsets.all(8.w), // 增大触摸区域 icon: Icon( @@ -574,35 +574,35 @@ class MusicPlaylistView extends StatelessWidget { } // 播放/暂停切换 - Future _togglePlayPause(MusicServiceFacade musicFacade) async { - if (musicFacade.isPlaying.value) { - await musicFacade.musicPause(); + Future _togglePlayPause(AgentController controller) async { + if (controller.isPlaying.value) { + await controller.musicPause(); } else { - await musicFacade.musicResume(); + await controller.musicResume(); } } // 播放上一曲 void _playPrevious( - MusicServiceFacade musicFacade, List> playlist) { - musicFacade.updateCurrentPlaylistContext( - playlist, musicFacade.currentId.value); - musicFacade.playPrevious(); + AgentController controller, List> playlist) { + controller.updateCurrentPlaylistContext( + playlist, controller.currentId.value); + controller.playPrevious(); } // 播放下一曲 void _playNext( - MusicServiceFacade musicFacade, List> playlist) { - musicFacade.updateCurrentPlaylistContext( - playlist, musicFacade.currentId.value); - musicFacade.playNext(); + AgentController controller, List> playlist) { + controller.updateCurrentPlaylistContext( + playlist, controller.currentId.value); + controller.playNext(); } // 播放选中的歌曲 - void _playSelectedSong(MusicServiceFacade musicFacade, Map song, + void _playSelectedSong(AgentController controller, Map song, List> playlist) { final songId = int.tryParse(song['id']?.toString() ?? '0') ?? 0; - musicFacade.updateCurrentPlaylistContext(playlist, songId); - musicFacade.musicPlay(song); + controller.updateCurrentPlaylistContext(playlist, songId); + controller.musicPlay(song); } } 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 e1d38f7e4..84964300a 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 @@ -1,5 +1,6 @@ package com.yunqiinnovation.music_service +import android.annotation.SuppressLint import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager @@ -13,7 +14,9 @@ import android.media.AudioManager import android.media.MediaPlayer import android.os.Build import android.os.Bundle +import android.os.Handler import android.os.IBinder +import android.os.Looper import android.support.v4.media.MediaBrowserCompat import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.PlaybackStateCompat @@ -47,36 +50,36 @@ class MusicService : MediaBrowserServiceCompat() { fun isPlaying() = currentStatus fun getCurrentId(): Int? = currentIdInternal - // 获取播放进度信息 - fun getProgress(): Map { - return if (instance != null && instance!!.mediaPlayer != null) { - val player = instance!!.mediaPlayer!! - try { - val currentPosition = if (player.isPlaying || currentStatus) player.currentPosition else 0 - val duration = player.duration - val progress = if (duration > 0) currentPosition.toDouble() / duration.toDouble() else 0.0 - - mapOf( - "currentPosition" to currentPosition, - "duration" to duration, - "progress" to progress - ) - } catch (e: Exception) { - Log.e(TAG, "获取播放进度失败: ${e.message}") - mapOf( - "currentPosition" to 0, - "duration" to 0, - "progress" to 0.0 - ) - } - } else { - mapOf( - "currentPosition" to 0, - "duration" to 0, - "progress" to 0.0 - ) - } - } + // // 获取播放进度信息 + // fun getProgress(): Map { + // return if (instance != null && instance!!.mediaPlayer != null) { + // val player = instance!!.mediaPlayer!! + // try { + // val currentPosition = if (player.isPlaying || currentStatus) player.currentPosition else 0 + // val duration = player.duration + // val progress = if (duration > 0) currentPosition.toDouble() / duration.toDouble() else 0.0 + + // mapOf( + // "currentPosition" to currentPosition, + // "duration" to duration, + // "progress" to progress + // ) + // } catch (e: Exception) { + // Log.e(TAG, "获取播放进度失败: ${e.message}") + // mapOf( + // "currentPosition" to 0, + // "duration" to 0, + // "progress" to 0.0 + // ) + // } + // } else { + // mapOf( + // "currentPosition" to 0, + // "duration" to 0, + // "progress" to 0.0 + // ) + // } + // } private var instance: MusicService? = null private var flutterChannel: io.flutter.plugin.common.MethodChannel? = null @@ -118,7 +121,7 @@ class MusicService : MediaBrowserServiceCompat() { val title = song["title"] as? String ?: song["name"] as? String ?: "未知标题" val artist = song["artist"] as? String ?: song["sgener"] as? String ?: "未知歌手" val coverUrl = song["coverUrl"] as? String ?: song["image"] as? String ?: "" - +currentIdInternal = song["id"] as? Int ?: song["id"] as? Int ?: 0 val metadata = android.support.v4.media.MediaMetadataCompat.Builder() .putString(android.support.v4.media.MediaMetadataCompat.METADATA_KEY_TITLE, title) .putString(android.support.v4.media.MediaMetadataCompat.METADATA_KEY_ARTIST, artist) @@ -166,6 +169,8 @@ class MusicService : MediaBrowserServiceCompat() { private lateinit var mediaSession: MediaSessionCompat private lateinit var audioManager: AudioManager private var mediaPlayer: MediaPlayer? = null + private val progressHandler = Handler(Looper.getMainLooper()) + private lateinit var progressUpdater: Runnable override fun onCreate() { super.onCreate() @@ -191,11 +196,35 @@ class MusicService : MediaBrowserServiceCompat() { registerReceiver(controlReceiver, filter, Context.RECEIVER_NOT_EXPORTED) } else { registerReceiver(controlReceiver, filter) + } + // 初始化进度更新任务 + progressUpdater = Runnable { + updateProgress() + progressHandler.postDelayed(progressUpdater, 1000) // 每秒更新一次 } isReceiverRegistered = true Log.i(TAG, "MediaService 启动完成") } - +@SuppressLint("SuspiciousIndentation") +private fun updateProgress() { + mediaPlayer?.let { player -> + if (player.isPlaying || currentStatus) { + val currentPosition = player.currentPosition + val duration = player.duration + val progress = if (duration > 0) currentPosition.toFloat() / duration else 0f + // 更新媒体会话进度 + + // 通知Flutter层 + flutterChannel?.invokeMethod("onMediaSessionSeekTo1", + mapOf( + "currentIdInternal" to currentIdInternal, + "currentPosition" to currentPosition, + "duration" to duration, + "progress" to progress + )) + } + } + } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Log.i(TAG, "onStartCommand: $intent") intent?.action?.takeIf { it == Intent.ACTION_MEDIA_BUTTON }?.let { @@ -210,7 +239,7 @@ class MusicService : MediaBrowserServiceCompat() { val songMap = intent.getSerializableExtra("song") as? Map songMap?.let { song -> val songId = song["id"] ?: return@let - + // currentIdInternal = songId val existingIndex = playlist.indexOfFirst { it["id"] == songId } if (existingIndex == -1) { playlist.add(song) @@ -323,9 +352,9 @@ class MusicService : MediaBrowserServiceCompat() { } override fun onSeekTo(pos: Long) { Log.i(TAG, "onSeekTo 通知: ${pos}ms") - seekTo(pos.toInt()) - // 通知Flutter层 - flutterChannel?.invokeMethod("onMediaSessionSeekTo", mapOf("position" to pos.toInt())) + // seekTo(pos.toInt()) + // // 通知Flutter层 + // flutterChannel?.invokeMethod("onMediaSessionSeekTo", mapOf("position" to pos.toInt())) } override fun onStop() { Log.i(TAG, "onStop 通知!") @@ -531,9 +560,13 @@ class MusicService : MediaBrowserServiceCompat() { setOnPreparedListener { start() updateNotification(title,artist,coverUrl) + // 启动进度更新 + progressHandler.post(progressUpdater) Log.i(TAG, "开始播放: $url") } - setOnCompletionListener { playNextOrStop() } + setOnCompletionListener { // 停止进度更新 + progressHandler.removeCallbacks(progressUpdater) + playNextOrStop() } setOnErrorListener { _, what, extra -> Log.e(TAG, "播放错误 what=$what extra=$extra") true @@ -547,17 +580,17 @@ class MusicService : MediaBrowserServiceCompat() { mediaPlayer?.takeIf { it.isPlaying }?.apply { pause() currentStatus = false - mediaSession.setPlaybackState( - PlaybackStateCompat.Builder() - .setState(PlaybackStateCompat.STATE_PAUSED, currentPosition.toLong(), 1.0f) - .build() - ) + val song = playlist[currentIndex] val title = song["title"] as? String ?: "未知标题" val artist = song["artist"] as? String ?: "未知歌手" val coverUrl = song["coverUrl"] as String + val id = song["id"] as? Int ?: 0 + currentIdInternal = id updateNotification(title,artist,coverUrl) setPlaybackState(PlaybackStateCompat.STATE_PAUSED); + // 停止进度更新 + progressHandler.removeCallbacks(progressUpdater) Log.i(TAG, "已暂停") } @@ -567,17 +600,17 @@ class MusicService : MediaBrowserServiceCompat() { mediaPlayer?.takeIf { !it.isPlaying }?.apply { start() currentStatus = true - mediaSession.setPlaybackState( - PlaybackStateCompat.Builder() - .setState(PlaybackStateCompat.STATE_PLAYING, currentPosition.toLong(), 1.0f) - .build() - ) + val song = playlist[currentIndex] val title = song["title"] as? String ?: "未知标题" val artist = song["artist"] as? String ?: "未知歌手" val coverUrl = song["coverUrl"] as String + val id = song["id"] as? Int ?: 0 + currentIdInternal = id updateNotification(title,artist,coverUrl) setPlaybackState(PlaybackStateCompat.STATE_PLAYING); + // 重启进度更新 + progressHandler.post(progressUpdater) // Log.i(TAG, "继续播放") } } @@ -635,6 +668,8 @@ class MusicService : MediaBrowserServiceCompat() { unregisterReceiver(controlReceiver) isReceiverRegistered = false } + // 停止进度更新 + progressHandler.removeCallbacks(progressUpdater) mediaPlayer?.release() stopForeground(true) isRunning.set(false) 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 0c2fe1256..9d5f1043b 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 @@ -70,10 +70,10 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler { ) result.success(status) } - "getProgress" -> { - val progressInfo = MusicService.getProgress() - result.success(progressInfo) - } + // "getProgress" -> { + // // val progressInfo = MusicService.getProgress() + // result.success(progressInfo) + // } "seekTo" -> { val position = call.argument("position") if (position != null) { diff --git a/local_plugins/music_service/lib/music_service.dart b/local_plugins/music_service/lib/music_service.dart index b9d43552e..4c59cd5c0 100644 --- a/local_plugins/music_service/lib/music_service.dart +++ b/local_plugins/music_service/lib/music_service.dart @@ -82,7 +82,10 @@ class MusicService { /// 更新媒体会话进度信息 Future updateMediaSessionProgress( - int currentPosition, int duration, double progress) async { + int currentPosition, + int duration, + double progress, + ) async { await _methodChannel.invokeMethod('updateMediaSessionProgress', { 'currentPosition': currentPosition, 'duration': duration, @@ -98,6 +101,7 @@ class MusicService { required Function() onPrevious, required Function(int) onSeekTo, required Function() onStop, + required Function(int, int, int, double) onStop1, }) async { // 设置方法调用处理器来处理来自原生平台的回调 _methodChannel.setMethodCallHandler((call) async { @@ -121,6 +125,13 @@ class MusicService { case 'onMediaSessionStop': onStop(); break; + case 'onMediaSessionSeekTo1': + final currentIdInternal = call.arguments['currentIdInternal'] as int; + final currentPosition = call.arguments['currentPosition'] as int; + final duration = call.arguments['duration'] as int; + final progress = call.arguments['progress'] as double; + onStop1(currentIdInternal, currentPosition, duration, progress); + break; } }); From 0ad22cbb6a8ba7bdcbe9b709731302c83f91f9d7 Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Thu, 3 Jul 2025 14:24:24 +0800 Subject: [PATCH 5/6] add --- lib/modules/agent/controllers/agent_controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 4fd37f1cd..11ce214b8 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -1528,7 +1528,7 @@ class AgentController extends GetxController { print( "updateMusicProgress1:currentIdInternal:$currentIdInternal newPosition:$newPosition newDuration:$newDuration newProgress:$newProgress"); bool hasUpdate = false; - if (currentId.value != currentIdInternal) { + if (currentId.value != currentIdInternal && currentIdInternal != 0) { currentId.value = currentIdInternal; hasUpdate = true; } From 50c3d1fc0570285181ff7686f1b751e526f22ef9 Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Thu, 3 Jul 2025 16:42:02 +0800 Subject: [PATCH 6/6] add --- devtools_options.yaml | 3 +++ lib/data/services/music_manager.dart | 2 +- .../agent/controllers/agent_controller.dart | 19 +++++++-------- lib/modules/agent/views/message_bubble.dart | 9 +++++--- lib/modules/agent/views/music_card.dart | 6 +++-- .../music_service/MusicService.kt | 23 ++++++++++++------- .../music_service/lib/music_service.dart | 5 ++-- 7 files changed, 42 insertions(+), 25 deletions(-) create mode 100644 devtools_options.yaml diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 000000000..fa0b357c4 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/data/services/music_manager.dart b/lib/data/services/music_manager.dart index 51975424f..621cd927c 100644 --- a/lib/data/services/music_manager.dart +++ b/lib/data/services/music_manager.dart @@ -105,7 +105,7 @@ class MusicManager extends GetxService { required Function() onPrevious, required Function(int) onSeekTo, required Function() onStop, - required Function(int, int, int, double) onStop1, + required Function(String, int, int, double) onStop1, }) async { try { await _musicService.setMediaSessionCallbacks( diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 11ce214b8..adf43b71a 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -1401,7 +1401,7 @@ class AgentController extends GetxController { await _musiceManager.play(musicData); // 更新媒体会话元数据和播放状态 - await _updateMediaSessionMetadata(song); + await _updateMediaSessionMetadata(musicData); await _updateMediaSessionPlaybackState(true); // 启动进度定时器 @@ -1514,11 +1514,12 @@ class AgentController extends GetxController { } // 获取播放进度 - Future updateMusicProgress1(int currentIdInternal, int newPosition, + Future updateMusicProgress1(String currentIdInternal, int newPosition, int newDuration, double newProgress) async { try { - if (!isPlaying.value || currentId.value == 0) { - return; // 如果没有在播放,直接返回 + if (int.parse(currentIdInternal) != 0) { + isPlaying.value = true; + // return; // 如果没有在播放,直接返回 } // // var progressInfo = await _musiceManager.getProgress(); @@ -1528,8 +1529,8 @@ class AgentController extends GetxController { print( "updateMusicProgress1:currentIdInternal:$currentIdInternal newPosition:$newPosition newDuration:$newDuration newProgress:$newProgress"); bool hasUpdate = false; - if (currentId.value != currentIdInternal && currentIdInternal != 0) { - currentId.value = currentIdInternal; + if (currentId.value != int.parse(currentIdInternal)) { + currentId.value = int.parse(currentIdInternal); hasUpdate = true; } // 只有当值发生变化时才更新,避免不必要的UI刷新 @@ -1602,7 +1603,7 @@ class AgentController extends GetxController { } // 跳转到指定位置 - Future seekToPosition1(int currentIdInternal, int newPosition, + Future seekToPosition1(String currentIdInternal, int newPosition, int newDuration, double newProgress) async { try { //await _musiceManager.seekTo(positionMs); @@ -1829,10 +1830,10 @@ class AgentController extends GetxController { } /// 处理来自系统通知栏的跳转操作 - Future onMediaSessionSeekTo1(int currentIdInternal, int newPosition, + Future onMediaSessionSeekTo1(String currentIdInternal, int newPosition, int newDuration, double newProgress) async { print( - "onMediaSessionSeekTo1 newPosition:$newPosition newDuration:$newDuration newProgress:$newProgress"); + "onMediaSessionSeekTo1 currentIdInternal :$currentIdInternal ,newPosition:$newPosition newDuration:$newDuration newProgress:$newProgress"); await seekToPosition1( currentIdInternal, newPosition, newDuration, newProgress); } diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index 0c4e2d782..51f7cf8e2 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -240,9 +240,11 @@ class MessageBubble extends GetView { final musicUrl = card_music['url'] as String? ?? ''; final musicSinger = card_music['sgener'] as String? ?? '未知歌手'; final musicImage = card_music['image'] as String? ?? ''; + final ts = card_music['ts'] as int? ?? 0; + print("music_play card_music: $card_music"); //音乐卡片 - return buildMusicItem( - context, musicId, musicName, musicUrl, musicSinger, musicImage); + return buildMusicItem(context, musicId, musicName, musicUrl, + musicSinger, musicImage, ts); } } else if (meta!.keys.contains('music_playlist')) { final card_musiclist = @@ -281,7 +283,8 @@ class MessageBubble extends GetView { 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.from(e)).toList(); + final list = + rawList.map((e) => Map.from(e)).toList(); final rawNavigation = card_navigation['navigation']; final Map? navigation = rawNavigation is Map ? rawNavigation : null; diff --git a/lib/modules/agent/views/music_card.dart b/lib/modules/agent/views/music_card.dart index ecc1e0fe1..f4af0c4fb 100644 --- a/lib/modules/agent/views/music_card.dart +++ b/lib/modules/agent/views/music_card.dart @@ -6,7 +6,7 @@ import 'circular_progress_button.dart'; /// 构建音乐项目 Widget buildMusicItem(BuildContext context, int id, String name, String url, - String singer, String image) { + String singer, String image, int ts) { final controller = Get.find(); // 获取控制器 final isDarkMode = Theme.of(context).brightness == Brightness.dark; Map song = { @@ -15,6 +15,7 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, 'title': name, 'artist': singer, 'coverUrl': image, + 'ts': ts, }; controller.musicStatus(); //刷新状态 @@ -204,9 +205,10 @@ Widget buildMusicContent( final url = item["url"] ?? ""; final singer = item["sgener"] ?? "未知歌手"; final image = item["image"] ?? ""; + final ts = item["ts"] ?? 0; return Column( children: [ - buildMusicItem(context, id, name, url, singer, image), + buildMusicItem(context, id, name, url, singer, image, ts), if (index != playlist.length - 1) Divider( height: 1, 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 84964300a..98c400caa 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 @@ -38,7 +38,7 @@ class MusicService : MediaBrowserServiceCompat() { private const val DELETE_ACTION = "com.yunqiinnovation.music_service.DELETE_NOTIFICATION" private val isRunning = AtomicBoolean(false) - private var currentIdInternal: Int? = null + private var currentIdInternal: String? = null private var currentStatus = false private var playlist = mutableListOf>() private var currentIndex = 0 @@ -48,7 +48,7 @@ class MusicService : MediaBrowserServiceCompat() { fun isServiceRunning() = isRunning.get() fun isPlaying() = currentStatus - fun getCurrentId(): Int? = currentIdInternal + fun getCurrentId(): String? = currentIdInternal // // 获取播放进度信息 // fun getProgress(): Map { @@ -121,7 +121,8 @@ class MusicService : MediaBrowserServiceCompat() { val title = song["title"] as? String ?: song["name"] as? String ?: "未知标题" val artist = song["artist"] as? String ?: song["sgener"] as? String ?: "未知歌手" val coverUrl = song["coverUrl"] as? String ?: song["image"] as? String ?: "" -currentIdInternal = song["id"] as? Int ?: song["id"] as? Int ?: 0 +currentIdInternal = song["id"] as? String ?: song["id"] as? String ?: "0" + Log.i(TAG, "updateMediaSessionMetadata currentIdInternal: $currentIdInternal") val metadata = android.support.v4.media.MediaMetadataCompat.Builder() .putString(android.support.v4.media.MediaMetadataCompat.METADATA_KEY_TITLE, title) .putString(android.support.v4.media.MediaMetadataCompat.METADATA_KEY_ARTIST, artist) @@ -238,8 +239,10 @@ private fun updateProgress() { "play" -> intent.getSerializableExtra("song")?.let { url -> val songMap = intent.getSerializableExtra("song") as? Map songMap?.let { song -> - val songId = song["id"] ?: return@let - // currentIdInternal = songId + val songId = song["id"]as? String ?:"0" + Log.i(TAG, "command songId: $songId") + currentIdInternal = songId + val existingIndex = playlist.indexOfFirst { it["id"] == songId } if (existingIndex == -1) { playlist.add(song) @@ -547,13 +550,15 @@ private fun updateProgress() { private fun play(song: Map) { val url = song["url"] as? String ?: return Log.i(TAG, "开始播放: $url") - val id = song["id"] as? Int ?: 0 + val id = song["id"] as? String ?: "0" + Log.i(TAG, "开始播放play id: $id") val title = song["title"] as? String ?: "未知标题" val artist = song["artist"] as? String ?: "未知歌手" val coverUrl = song["coverUrl"] as String mediaPlayer?.release() currentStatus = true currentIdInternal = id + Log.i(TAG, "开始播放play currentIdInternal: $currentIdInternal") mediaPlayer = MediaPlayer().apply { setDataSource(url) setAudioStreamType(AudioManager.STREAM_MUSIC) @@ -585,7 +590,8 @@ private fun updateProgress() { val title = song["title"] as? String ?: "未知标题" val artist = song["artist"] as? String ?: "未知歌手" val coverUrl = song["coverUrl"] as String - val id = song["id"] as? Int ?: 0 + val id = song["id"] as? String ?: "0" + Log.i(TAG, "pause id: $id") currentIdInternal = id updateNotification(title,artist,coverUrl) setPlaybackState(PlaybackStateCompat.STATE_PAUSED); @@ -605,7 +611,8 @@ private fun updateProgress() { val title = song["title"] as? String ?: "未知标题" val artist = song["artist"] as? String ?: "未知歌手" val coverUrl = song["coverUrl"] as String - val id = song["id"] as? Int ?: 0 + val id = song["id"] as? String ?: "0" + Log.i(TAG, "resume id: $id") currentIdInternal = id updateNotification(title,artist,coverUrl) setPlaybackState(PlaybackStateCompat.STATE_PLAYING); diff --git a/local_plugins/music_service/lib/music_service.dart b/local_plugins/music_service/lib/music_service.dart index 4c59cd5c0..8be33fe68 100644 --- a/local_plugins/music_service/lib/music_service.dart +++ b/local_plugins/music_service/lib/music_service.dart @@ -101,7 +101,7 @@ class MusicService { required Function() onPrevious, required Function(int) onSeekTo, required Function() onStop, - required Function(int, int, int, double) onStop1, + required Function(String, int, int, double) onStop1, }) async { // 设置方法调用处理器来处理来自原生平台的回调 _methodChannel.setMethodCallHandler((call) async { @@ -126,7 +126,8 @@ class MusicService { onStop(); break; case 'onMediaSessionSeekTo1': - final currentIdInternal = call.arguments['currentIdInternal'] as int; + final currentIdInternal = + call.arguments['currentIdInternal'] as String; final currentPosition = call.arguments['currentPosition'] as int; final duration = call.arguments['duration'] as int; final progress = call.arguments['progress'] as double;