From 158760ad2d908227aee3e29363ac7f41cea6076a Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Mon, 12 May 2025 12:17:56 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=98=E5=8E=9F=E8=87=AA=E5=8A=A8=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/pairing_controller.dart | 129 ++++++++++-------- .../agent_service/android/build.gradle.kts | 1 + .../agent_service/AgentService.kt | 70 +++++++++- .../music_service/MusicServicePlugin.kt | 11 +- 4 files changed, 146 insertions(+), 65 deletions(-) diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 4dedaefcb..1a0e0e55c 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -10,13 +10,14 @@ import 'package:classic_bluetooth/classic_bluetooth.dart' show BluetoothDevice; import '../../../data/services/ble_manager.dart'; import 'package:agent_service/agent_service.dart'; import 'package:ble_service/ble_service.dart'; + /// 配对控制器,负责检查蓝牙权限、状态,并处理配对流程 class PairingController extends GetxController { static PairingController get to => Get.find(); // 状态标志 final isLoading = false.obs; - + // 权限是否已授予 final _hasPermissions = false.obs; bool get hasPermissions => _hasPermissions.value; @@ -25,22 +26,22 @@ class PairingController extends GetxController { final _bluetoothState = 'unknown'.obs; String get bluetoothState => _bluetoothState.value; bool get isBluetoothOn => _bluetoothState.value == 'on'; - + // 状态描述(用于UI显示) final statusDescription = 'initializing'.tr.obs; // 是否正在跳转到主页 bool _isNavigatingToHome = false; - + // 是否正在执行Companion关联流程 bool _isAssociating = false; // 当前连接设备信息 BluetoothDevice? get currentDevice => _bluetoothService.currentDevice; - + // 是否已连接设备 bool get isDeviceConnected => currentDevice != null; - + // 是否已完成Companion关联 final _companionAssociated = false.obs; bool get isCompanionAssociated => _companionAssociated.value; @@ -48,10 +49,10 @@ class PairingController extends GetxController { // 服务状态监听 Worker? _bluetoothStateWorker; Worker? _deviceWorker; - + // 蓝牙服务 final _bluetoothService = Get.find(); - + // BLE服务 final _bleService = BleService(); @@ -96,7 +97,7 @@ class PairingController extends GetxController { // 刷新已连接设备 statusDescription.value = 'searchingDevice'.tr; await refreshConnectedDevices(); - + // 检查是否已存在Companion设备关联,但不要重复调用关联流程 await _checkExistingCompanionAssociation(); } catch (e) { @@ -107,16 +108,16 @@ class PairingController extends GetxController { _updateStatusDescription(); } } - + /// 检查是否已存在Companion设备关联 Future _checkExistingCompanionAssociation() async { // 只在Android平台执行 if (!Platform.isAndroid) return; - + try { final isSupported = await _bleService.isCompanionSupported(); if (!isSupported) return; - + final pairedMac = await _bleService.getPairedMacAddress(); if (pairedMac != null) { Logger.info('发现已关联的Companion设备: $pairedMac'); @@ -156,18 +157,18 @@ class PairingController extends GetxController { Logger.warning('权限 ${permission.toString()} 未被授予: $status'); } }); - + _hasPermissions.value = allGranted; return allGranted; } - + // 辅助方法获取Android SDK版本 Future _getAndroidSdkVersion() async { if (Platform.isAndroid) { - // 这里需要原生代码支持来获取确切的SDK版本, - // 或者使用 device_info_plus 插件 - // 暂时返回一个较高值以触发新权限请求 - return 31; // 假设运行在需要新权限的设备上 + // 这里需要原生代码支持来获取确切的SDK版本, + // 或者使用 device_info_plus 插件 + // 暂时返回一个较高值以触发新权限请求 + return 31; // 假设运行在需要新权限的设备上 } return 0; } @@ -175,38 +176,41 @@ class PairingController extends GetxController { /// 设置服务状态监听 void _setupServiceStateListeners() { // 监听蓝牙服务的蓝牙状态变化 - _bluetoothStateWorker = ever(_bluetoothService.bluetoothState.obs, (String state) { + _bluetoothStateWorker = + ever(_bluetoothService.bluetoothState.obs, (String state) { _bluetoothState.value = state; _updateStatusDescription(); - + // 如果蓝牙关闭,可能需要更新UI或提示用户 if (state == 'off') { Logger.info('蓝牙已关闭'); } }); - + Logger.info('liwei----------------- _setupServiceStateListeners 1'); // 监听当前设备变化 - 使用公开的currentDeviceRx字段 - _deviceWorker = ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) { + _deviceWorker = + ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) { if (device != null && !_isNavigatingToHome) { Logger.info('配对页面发现设备已连接: ${device.name}'); - + Logger.info('liwei----------------- _setupServiceStateListeners 2'); // 当经典蓝牙设备连接成功,尝试进行Companion关联 _handleDeviceConnected(); } _updateStatusDescription(); }); } - + /// 处理设备连接成功事件 Future _handleDeviceConnected() async { + Logger.info('liwei----------------- _handleDeviceConnected 1'); if (!isDeviceConnected || _isNavigatingToHome) return; - + Logger.info('liwei----------------- _handleDeviceConnected 2'); // 如果已经完成关联,直接跳转 if (_companionAssociated.value) { _autoNavigateToHome(); return; } - + Logger.info('liwei----------------- _handleDeviceConnected 3'); // 启动关联流程 _startCompanionAssociation(); } @@ -217,7 +221,7 @@ class PairingController extends GetxController { statusDescription.value = 'permissionDenied'.tr; return; } - + if (bluetoothState == 'off') { statusDescription.value = 'bluetoothTurnedOff'.tr; return; @@ -225,11 +229,14 @@ class PairingController extends GetxController { if (isDeviceConnected) { if (Platform.isAndroid && !isCompanionAssociated && _isAssociating) { - statusDescription.value = '${'associatingDevice'.tr} ${currentDevice!.name}'; + statusDescription.value = + '${'associatingDevice'.tr} ${currentDevice!.name}'; } else if (isCompanionAssociated) { - statusDescription.value = '${'connectedDevice'.tr} ${currentDevice!.name}'; + statusDescription.value = + '${'connectedDevice'.tr} ${currentDevice!.name}'; } else { - statusDescription.value = '${'connectedDevice'.tr} ${currentDevice!.name}'; + statusDescription.value = + '${'connectedDevice'.tr} ${currentDevice!.name}'; } return; } @@ -248,18 +255,18 @@ class PairingController extends GetxController { statusDescription.value = 'permissionDenied'.tr; return; } - + try { isLoading.value = true; statusDescription.value = 'searchingDevice'.tr; - + // 使用蓝牙服务刷新连接 await _bluetoothService.refreshDeviceConnection(); - + // 检查连接状态 if (isDeviceConnected) { Logger.info('刷新发现设备已连接: ${currentDevice!.name}'); - + // 当发现设备已连接,统一由_handleDeviceConnected处理 _handleDeviceConnected(); } else { @@ -273,18 +280,22 @@ class PairingController extends GetxController { _updateStatusDescription(); } } - + /// 启动Companion设备关联流程 Future _startCompanionAssociation() async { + Logger.info('liwei----------------- _startCompanionAssociation 1'); // 如果不是Android平台,已经跳转到首页,已经完成关联或正在进行关联,直接返回 - if (!Platform.isAndroid || _isNavigatingToHome || _companionAssociated.value || _isAssociating) { + if (!Platform.isAndroid || + _isNavigatingToHome || + _companionAssociated.value || + _isAssociating) { return; } - + Logger.info('liwei----------------- _startCompanionAssociation 2'); // 设置关联状态标志 _isAssociating = true; _updateStatusDescription(); - + try { // 检查是否支持Companion Device功能 final isSupported = await _bleService.isCompanionSupported(); @@ -294,7 +305,7 @@ class PairingController extends GetxController { _autoNavigateToHome(); return; } - + Logger.info('liwei----------------- _startCompanionAssociation 3'); // 检查是否已有配对的MAC地址 final pairedMac = await _bleService.getPairedMacAddress(); if (pairedMac != null) { @@ -303,34 +314,35 @@ class PairingController extends GetxController { Logger.info('已关联的Companion设备与当前连接的设备一致'); _companionAssociated.value = true; await _bleService.registerBackgroundScan(); - _autoNavigateToHome(); - return; + // _autoNavigateToHome(); + // return; } else { // 不一致,清除旧关联 Logger.info('已关联的Companion设备与当前连接的设备不一致,清除旧关联'); await _bleService.clearAssociations(); } } - + Logger.info('liwei----------------- _startCompanionAssociation 4'); // 开始关联流程 if (currentDevice != null) { Logger.info('开始Companion设备关联流程, 设备MAC: ${currentDevice!.address}'); - - final success = await _bleService.startDeviceAssociation( + + var success = await _bleService.startDeviceAssociation( deviceNamePattern: currentDevice!.name, macAddress: currentDevice!.address, ); - + success = true; if (success) { Logger.info('Companion设备关联成功'); _companionAssociated.value = true; - + // 注册后台扫描,确保应用被杀死后仍能被唤醒 - await _bleService.registerBackgroundScan(macAddress: currentDevice!.address); - + await _bleService.registerBackgroundScan( + macAddress: currentDevice!.address); + Logger.info('liwei----------------- _startCompanionAssociation 5'); // 启动BLE后台服务(可选) await _startAgentService(); - + // 关联和后台扫描都成功后,再跳转 _autoNavigateToHome(); } else { @@ -353,10 +365,10 @@ class PairingController extends GetxController { /// 自动跳转到主页 Future _autoNavigateToHome() async { if (_isNavigatingToHome) return; - + // 如果没有连接,不进行跳转 if (!isDeviceConnected) return; - + // 在Android平台上,如果支持Companion且仍在关联中,不跳转 if (Platform.isAndroid) { final isSupported = await _bleService.isCompanionSupported(); @@ -365,25 +377,24 @@ class PairingController extends GetxController { return; } } - + _isNavigatingToHome = true; - + Logger.info('已找到连接的设备 ${currentDevice!.name},自动跳转到主页'); - + Future.delayed(const Duration(milliseconds: 500), () { Get.offAllNamed(Routes.home); }); } - + /// 启动BLE后台服务 Future _startAgentService() async { try { if (!isDeviceConnected) return; - // 启动BLE服务,无需传递配置参数,将从.env读取 final result = await BleManager.to.startAgentService(); - + if (result) { Logger.info('BLE后台服务启动成功'); } else { @@ -393,12 +404,12 @@ class PairingController extends GetxController { Logger.error('启动BLE后台服务发生错误: $e'); } } - + /// 停止BLE后台服务 Future _stopAgentService() async { try { final result = await BleManager.to.stopAgentService(); - + if (result) { Logger.info('BLE后台服务停止成功'); } else { @@ -425,7 +436,7 @@ class PairingController extends GetxController { Logger.error('打开设置失败: $e'); } } - + /// 打开应用设置 Future openAppSettings() async { await AppSettings.openAppSettings(); diff --git a/local_plugins/agent_service/android/build.gradle.kts b/local_plugins/agent_service/android/build.gradle.kts index 5a0a26504..b6c1b4d21 100644 --- a/local_plugins/agent_service/android/build.gradle.kts +++ b/local_plugins/agent_service/android/build.gradle.kts @@ -48,4 +48,5 @@ dependencies { add("compileOnly", project(":deepvoice_utils")) add("compileOnly", project(":chat_storage")) add("compileOnly", project(":ble_service")) + add("compileOnly", project(":music_service")) } \ No newline at end of file diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 3bea170d1..a00625399 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -690,6 +690,42 @@ object AgentService : CoroutineScope { "result" to functionCallResult.toString(), )) FileLogger.d(TAG, "mcp调用结果: $functionCallResult") + val metaStr = functionCallResult.optString("meta") + if (metaStr.isNotEmpty()) { + metadata = metaStr; + val meta = JSONObject(metaStr) + FileLogger.d(TAG, "mcp调用结果: $metaStr") + if (meta.has("card_music")) { //音乐卡片 + val cardMusic = meta.getJSONObject("card_music") + FileLogger.d(TAG, "检查到音乐卡片: $cardMusic") + val musicUrl = cardMusic.optString("url", "") + val name = cardMusic.optString("name", "") + val sgener = cardMusic.optString("sgener", "") + processMusicPlay(musicUrl, name, sgener) + }else if (meta.has("card_musiclist")) { //音乐列表 + val cardMusiclist = meta.getJSONObject("card_musiclist") + val musics = cardMusiclist.optJSONArray("musics") + if (musics != null) { + val playlist = mutableListOf>() + for (i in 0 until musics.length()) { + val item = musics.optJSONObject(i) ?: continue + val url = item.optString("url", "") + val name = item.optString("name", "") + val sgener = item.optString("sgener", "") + if (url.isNotEmpty()) { + playlist.add(mapOf("url" to url, "name" to name, "sgener" to sgener)) + } + } + if (playlist.isNotEmpty()) { + processMusicPlayList(playlist) + } else { + FileLogger.w(TAG, "card_musiclist 中没有有效的音乐条目") + } + } else { + FileLogger.w(TAG, "card_musiclist.musics 不是一个数组") + } + } + } } } ) @@ -1040,4 +1076,36 @@ object AgentService : CoroutineScope { } } } -} \ No newline at end of file + + /** + * 处理音乐播放 + * 将ai返回的音乐连接 在后台启动播放 + * + * @param musicUrl 图片文件路径 + * @param name 歌曲名称,默认为空 + * @param singer 歌手,默认为false + * @return 是否成功开始处理 + */ + fun processMusicPlay(musicUrl:String,name: String = "",singer:String = ""){ + // 在其他 Service、BroadcastReceiver 或 Application 中调用 + FileLogger.i(TAG, "启动音乐服务 播放音乐 $musicUrl") + MusicServiceStarter.startServiceWithCommand(context, "play", url = musicUrl) + } + + /** + * 处理音乐播放列表 + * 将ai返回的音乐连接 在后台启动播放 + * + * @param musicUrl 图片文件路径 + * @param name 歌曲名称,默认为空 + * @param singer 歌手,默认为false + * @return 是否成功开始处理 + */ + fun processMusicPlayList(playlist:List>){ + // 在其他 Service、BroadcastReceiver 或 Application 中调用 + + val urls = playlist.mapNotNull { it["url"] }.distinct() + FileLogger.i(TAG, "启动音乐服务 播放音乐列表 $urls") + MusicServiceStarter.startServiceWithPlaylist(context, "playlist", urls = urls) + } +} \ No newline at end of file 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 60bd63f40..e85c4be44 100644 --- a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt +++ b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import androidx.core.content.ContextCompat import com.yunqiinnovation.deepvoice_utils.FileLogger +import com.yunqiinnovation.music_service.MusicServiceStarter.startServiceWithCommand import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel @@ -27,7 +28,7 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler { when (call.method) { "play" -> { val url = call.argument("url") - MusicServiceStarter.startServiceWithCommand(context, "play", url) + startServiceWithCommand(context, "play", url) result.success(null) } "playlist" -> { @@ -36,20 +37,20 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler { result.success(null) } "pause" -> { - MusicServiceStarter.startServiceWithCommand(context, "pause") + startServiceWithCommand(context, "pause") result.success(null) } "resume" -> { - MusicServiceStarter.startServiceWithCommand(context, "resume") + startServiceWithCommand(context, "resume") result.success(null) } "stop" -> { - MusicServiceStarter.startServiceWithCommand(context, "stop") + startServiceWithCommand(context, "stop") result.success(null) } "setVolume" -> { val volume = call.argument("volume")?.toFloat() - MusicServiceStarter.startServiceWithCommand(context, "setVolume", volume = volume) + startServiceWithCommand(context, "setVolume", volume = volume) result.success(null) } "getStatus" -> {