From 0854b4ec337fa68df2a790ef6a25a4ac778ccb74 Mon Sep 17 00:00:00 2001 From: wolfplus Date: Fri, 16 May 2025 18:02:57 +0100 Subject: [PATCH] add --- .mcp copy.json | 10 - .../bindings/jieli_test_binding.dart | 11 - .../controllers/jieli_test_controller.dart | 323 --------- .../jieli_test/views/jieli_test_view.dart | 568 --------------- .../splash/controllers/splash_controller.dart | 3 +- lib/routes/app_pages.dart | 9 - .../ble_service/android/build.gradle.kts | 1 - local_plugins/jieli/README.md | 144 ---- local_plugins/jieli/android/build.gradle.kts | 57 -- .../jieli/android/settings.gradle.kts | 1 - .../android/src/main/AndroidManifest.xml | 22 - .../com/yunqiinnovation/jieli/BleService.kt | 665 ------------------ .../com/yunqiinnovation/jieli/JieliPlugin.kt | 294 -------- local_plugins/jieli/lib/jieli.dart | 283 -------- local_plugins/jieli/pubspec.yaml | 23 - pubspec.yaml | 4 - test.json | 19 +- 17 files changed, 19 insertions(+), 2418 deletions(-) delete mode 100644 .mcp copy.json delete mode 100644 lib/modules/jieli_test/bindings/jieli_test_binding.dart delete mode 100644 lib/modules/jieli_test/controllers/jieli_test_controller.dart delete mode 100644 lib/modules/jieli_test/views/jieli_test_view.dart delete mode 100644 local_plugins/jieli/README.md delete mode 100644 local_plugins/jieli/android/build.gradle.kts delete mode 100644 local_plugins/jieli/android/settings.gradle.kts delete mode 100644 local_plugins/jieli/android/src/main/AndroidManifest.xml delete mode 100644 local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt delete mode 100644 local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/JieliPlugin.kt delete mode 100644 local_plugins/jieli/lib/jieli.dart delete mode 100644 local_plugins/jieli/pubspec.yaml diff --git a/.mcp copy.json b/.mcp copy.json deleted file mode 100644 index eaa12d328..000000000 --- a/.mcp copy.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "mcpServers": { - "web-search": { - "url": "http://mcp.ideapsound.com:8000/sse" - }, - "amap-amap-sse": { - "url": "https://mcp.amap.com/sse?key=df7b0b6747c632fc38ddad22e7176646" - } - } -} \ No newline at end of file diff --git a/lib/modules/jieli_test/bindings/jieli_test_binding.dart b/lib/modules/jieli_test/bindings/jieli_test_binding.dart deleted file mode 100644 index 9df61ef94..000000000 --- a/lib/modules/jieli_test/bindings/jieli_test_binding.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:get/get.dart'; -import '../controllers/jieli_test_controller.dart'; - -class JieliTestBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => JieliTestController(), - ); - } -} \ No newline at end of file diff --git a/lib/modules/jieli_test/controllers/jieli_test_controller.dart b/lib/modules/jieli_test/controllers/jieli_test_controller.dart deleted file mode 100644 index b6d896836..000000000 --- a/lib/modules/jieli_test/controllers/jieli_test_controller.dart +++ /dev/null @@ -1,323 +0,0 @@ -import 'dart:async'; -import 'dart:typed_data'; -import 'package:flutter/material.dart' hide ConnectionState; -import 'package:get/get.dart'; -import 'package:jieli/jieli.dart'; - -/// 杰理测试控制器 -class JieliTestController extends GetxController { - // Jieli插件实例 - late Jieli jieli; - - // 状态变量 - var isScanning = false.obs; - var deviceInfo = '未获取'.obs; - var connectStatus = '未连接'.obs; - var currentDevice = Rx(null); - var scannedDevices = RxList([]); - var wakeupReceived = false.obs; - var audioDataSize = 0.obs; - var receivedFrames = 0.obs; - var audioDataReceiving = false.obs; - var volumeCommandResult = ''.obs; - - // 事件订阅 - StreamSubscription? _scanSubscription; - StreamSubscription? _connectionSubscription; - StreamSubscription? _wakeupSubscription; - StreamSubscription? _audioSubscription; - StreamSubscription? _commandResultSubscription; - - @override - void onInit() { - super.onInit(); - _initializeJieli(); - } - - @override - void onClose() { - _cancelAllSubscriptions(); - _disposeJieli(); - super.onClose(); - } - - /// 取消所有订阅 - void _cancelAllSubscriptions() { - _scanSubscription?.cancel(); - _connectionSubscription?.cancel(); - _wakeupSubscription?.cancel(); - _audioSubscription?.cancel(); - _commandResultSubscription?.cancel(); - } - - /// 初始化杰理SDK - void _initializeJieli() { - try { - jieli = Jieli(); - _setupEventListeners(); - } catch (e) { - // 初始化错误处理 - } - } - - /// 设置事件监听器 - void _setupEventListeners() { - // 监听扫描结果 - _scanSubscription = jieli.scanResults.listen((devices) { - scannedDevices.assignAll(devices); - }); - - // 监听连接状态 - _connectionSubscription = jieli.connectionState.listen((state) { - switch (state) { - case ConnectionState.connected: - connectStatus.value = '已连接'; - break; - case ConnectionState.disconnected: - connectStatus.value = '已断开'; - currentDevice.value = null; - break; - case ConnectionState.connecting: - connectStatus.value = '连接中...'; - break; - case ConnectionState.disconnecting: - connectStatus.value = '断开中...'; - break; - case ConnectionState.error: - connectStatus.value = '连接错误'; - break; - } - }); - - // 监听唤醒信号 - _wakeupSubscription = jieli.wakeupStream.listen((wakeup) { - wakeupReceived.value = true; - Get.snackbar('收到唤醒信号', '耳机发送了唤醒信号'); - }); - - // 监听音频数据 - _audioSubscription = jieli.audioDataStream.listen((data) { - // 更新音频数据统计 - audioDataSize.value += data.length; - receivedFrames.value++; - audioDataReceiving.value = true; - }); - - // 监听命令结果 - _commandResultSubscription = jieli.commandResultStream.listen((result) { - // 处理音量调整命令结果 - if (result.commandType == Jieli.commandTypeVolume) { - volumeCommandResult.value = result.success ? '音量调整成功' : '音量调整失败'; - Get.snackbar( - result.success ? '音量调整成功' : '音量调整失败', - result.success ? '设备已更新音量' : '设备未能更新音量', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: result.success ? Colors.green.withOpacity(0.7) : Colors.red.withOpacity(0.7), - colorText: Colors.white, - duration: Duration(seconds: 2), - ); - } - }); - } - - /// 检查蓝牙状态 - Future checkBluetoothStatus() async { - try { - final isEnabled = await jieli.isBluetoothEnabled(); - if (!isEnabled) { - return await jieli.requestBluetoothEnable(); - } - return true; - } catch (e) { - return false; - } - } - - /// 开始扫描设备 - Future startScan() async { - if (isScanning.value) return; - - final bluetoothReady = await checkBluetoothStatus(); - if (!bluetoothReady) { - Get.snackbar('蓝牙未启用', '请启用蓝牙后再试'); - return; - } - - try { - isScanning.value = true; - - // 清空之前的设备列表 - scannedDevices.clear(); - - // 开始扫描,10秒后自动停止 - final success = await jieli.startScan(scanTimeout: 10000); - - if (!success) { - isScanning.value = false; - Get.snackbar('扫描失败', '启动扫描设备失败,请检查蓝牙权限'); - } - - // 10秒后自动更新UI状态 - Future.delayed(Duration(milliseconds: 10000), () { - isScanning.value = false; - }); - - } catch (e) { - isScanning.value = false; - Get.snackbar('扫描错误', e.toString()); - } - } - - /// 停止扫描 - Future stopScan() async { - if (!isScanning.value) return; - - try { - await jieli.stopScan(); - isScanning.value = false; - } catch (e) { - // 停止扫描错误处理 - } - } - - /// 连接到指定设备 - Future connectToDevice(BleDevice device) async { - try { - // 先断开当前连接 - if (currentDevice.value != null) { - await disconnectDevice(); - } - - // 更新UI状态 - connectStatus.value = '连接中...'; - - // 重置音频和唤醒相关的状态 - wakeupReceived.value = false; - audioDataSize.value = 0; - receivedFrames.value = 0; - audioDataReceiving.value = false; - - // 连接设备 - final success = await jieli.connect(device.id); - - if (success) { - // 连接成功,更新当前设备 - currentDevice.value = device; - deviceInfo.value = '设备名称: ${device.name}\n设备ID: ${device.id}\nRSSI: ${device.rssi} dBm'; - } else { - deviceInfo.value = '连接失败'; - } - } catch (e) { - deviceInfo.value = '连接错误: ${e.toString()}'; - } - } - - /// 断开设备连接 - Future disconnectDevice() async { - if (currentDevice.value == null) return; - - try { - connectStatus.value = '断开中...'; - - final success = await jieli.disconnect(); - - if (success) { - deviceInfo.value = '未连接设备'; - } - - } catch (e) { - // 断开连接错误处理 - } - } - - /// 发送开始录音命令 - Future sendStartRecordingCommand() async { - if (currentDevice.value == null) { - Get.snackbar('操作失败', '未连接设备'); - return; - } - - try { - final success = await jieli.sendStartRecordingCommand(); - - if (success) { - Get.snackbar('命令已发送', '已发送开始录音命令'); - } else { - Get.snackbar('命令发送失败', '发送开始录音命令失败'); - } - } catch (e) { - Get.snackbar('命令错误', e.toString()); - } - } - - /// 发送停止录音命令 - Future sendStopRecordingCommand() async { - if (currentDevice.value == null) { - Get.snackbar('操作失败', '未连接设备'); - return; - } - - try { - final success = await jieli.sendStopRecordingCommand(); - - if (success) { - Get.snackbar('命令已发送', '已发送停止录音命令'); - } else { - Get.snackbar('命令发送失败', '发送停止录音命令失败'); - } - } catch (e) { - Get.snackbar('命令错误', e.toString()); - } - } - - /// 增加音量 - Future increaseVolume() async { - if (currentDevice.value == null) { - Get.snackbar('操作失败', '未连接设备'); - return; - } - - try { - volumeCommandResult.value = '发送中...'; - final success = await jieli.increaseVolume(); - - if (!success) { - Get.snackbar('命令发送失败', '发送增加音量命令失败'); - volumeCommandResult.value = '发送失败'; - } - } catch (e) { - Get.snackbar('命令错误', e.toString()); - volumeCommandResult.value = '发送出错'; - } - } - - /// 减小音量 - Future decreaseVolume() async { - if (currentDevice.value == null) { - Get.snackbar('操作失败', '未连接设备'); - return; - } - - try { - volumeCommandResult.value = '发送中...'; - final success = await jieli.decreaseVolume(); - - if (!success) { - Get.snackbar('命令发送失败', '发送减小音量命令失败'); - volumeCommandResult.value = '发送失败'; - } - } catch (e) { - Get.snackbar('命令错误', e.toString()); - volumeCommandResult.value = '发送出错'; - } - } - - /// 清理资源 - void _disposeJieli() { - try { - jieli.dispose(); - } catch (e) { - // 释放资源错误处理 - } - } -} \ No newline at end of file diff --git a/lib/modules/jieli_test/views/jieli_test_view.dart b/lib/modules/jieli_test/views/jieli_test_view.dart deleted file mode 100644 index 46409651b..000000000 --- a/lib/modules/jieli_test/views/jieli_test_view.dart +++ /dev/null @@ -1,568 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import '../controllers/jieli_test_controller.dart'; -import 'package:jieli/jieli.dart'; - -class JieliTestView extends GetView { - const JieliTestView({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final isDarkMode = Theme.of(context).brightness == Brightness.dark; - - return Scaffold( - backgroundColor: isDarkMode ? Colors.black : Colors.grey[50], - appBar: AppBar( - backgroundColor: isDarkMode ? Colors.black : Colors.white, - elevation: 0.5, - title: Text( - '杰理蓝牙测试', - style: TextStyle( - fontSize: 17.sp, - fontWeight: FontWeight.w600, - color: isDarkMode ? Colors.white : Colors.black, - ), - ), - centerTitle: true, - leading: IconButton( - icon: Icon( - Icons.arrow_back_ios, - color: isDarkMode ? Colors.white : Colors.black, - size: 20.sp, - ), - onPressed: () => Get.back(), - ), - ), - body: Padding( - padding: EdgeInsets.all(16.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildStatusCard(isDarkMode), - SizedBox(height: 16.h), - _buildActionButtons(isDarkMode), - SizedBox(height: 16.h), - _buildDeviceInfo(isDarkMode), - SizedBox(height: 16.h), - _buildDevicesList(isDarkMode), - ], - ), - ), - ); - } - - Widget _buildStatusCard(bool isDarkMode) { - return Container( - padding: EdgeInsets.all(16.w), - decoration: BoxDecoration( - color: isDarkMode ? Colors.grey[900] : Colors.white, - borderRadius: BorderRadius.circular(12.r), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - '连接状态: ', - style: TextStyle( - fontSize: 14.sp, - color: isDarkMode ? Colors.white : Colors.black87, - ), - ), - Obx(() => Text( - controller.connectStatus.value, - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.bold, - color: _getStatusColor(controller.connectStatus.value), - ), - )), - ], - ), - SizedBox(height: 8.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Obx(() => _buildStatusChip( - label: '唤醒信号', - isActive: controller.wakeupReceived.value, - activeColor: Colors.orange, - isDarkMode: isDarkMode, - )), - Obx(() => _buildStatusChip( - label: '音频数据', - isActive: controller.audioDataReceiving.value, - activeColor: Colors.green, - isDarkMode: isDarkMode, - )), - ], - ), - SizedBox(height: 8.h), - Obx(() => controller.audioDataReceiving.value - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '音频数据统计:', - style: TextStyle( - fontSize: 12.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, - ), - ), - SizedBox(height: 4.h), - Text( - '已接收 ${(controller.audioDataSize.value / 1024).toStringAsFixed(2)} KB', - style: TextStyle( - fontSize: 12.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, - ), - ), - Text( - '共 ${controller.receivedFrames.value} 帧', - style: TextStyle( - fontSize: 12.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, - ), - ), - ], - ) - : SizedBox.shrink() - ), - ], - ), - ); - } - - Widget _buildStatusChip({ - required String label, - required bool isActive, - required Color activeColor, - required bool isDarkMode, - }) { - return Container( - padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), - decoration: BoxDecoration( - color: isActive - ? activeColor - : (isDarkMode ? Colors.grey[800] : Colors.grey[200]), - borderRadius: BorderRadius.circular(16.r), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 8.w, - height: 8.w, - decoration: BoxDecoration( - color: isActive ? Colors.white : Colors.grey, - shape: BoxShape.circle, - ), - ), - SizedBox(width: 6.w), - Text( - label, - style: TextStyle( - fontSize: 12.sp, - color: isActive - ? Colors.white - : (isDarkMode ? Colors.white70 : Colors.black54), - fontWeight: isActive ? FontWeight.bold : FontWeight.normal, - ), - ), - ], - ), - ); - } - - Widget _buildActionButtons(bool isDarkMode) { - return Column( - children: [ - Row( - children: [ - Expanded( - child: Obx(() => ElevatedButton.icon( - onPressed: controller.isScanning.value - ? controller.stopScan - : controller.startScan, - icon: Icon( - controller.isScanning.value ? Icons.stop : Icons.bluetooth_searching, - size: 18.sp, - ), - label: Text( - controller.isScanning.value ? '停止扫描' : '扫描设备', - style: TextStyle(fontSize: 14.sp), - ), - style: ElevatedButton.styleFrom( - backgroundColor: controller.isScanning.value - ? Colors.red[600] - : Colors.blue[600], - padding: EdgeInsets.symmetric(vertical: 12.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - ), - ), - )), - ), - ], - ), - SizedBox(height: 8.h), - Row( - children: [ - Expanded( - child: ElevatedButton.icon( - onPressed: controller.sendStartRecordingCommand, - icon: Icon( - Icons.mic, - size: 18.sp, - ), - label: Text( - '开始录音', - style: TextStyle(fontSize: 14.sp), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green[600], - padding: EdgeInsets.symmetric(vertical: 12.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - ), - ), - ), - ), - SizedBox(width: 8.w), - Expanded( - child: ElevatedButton.icon( - onPressed: controller.sendStopRecordingCommand, - icon: Icon( - Icons.stop_circle, - size: 18.sp, - ), - label: Text( - '停止录音', - style: TextStyle(fontSize: 14.sp), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red[600], - padding: EdgeInsets.symmetric(vertical: 12.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - ), - ), - ), - ), - ], - ), - SizedBox(height: 8.h), - Row( - children: [ - Expanded( - child: ElevatedButton.icon( - onPressed: controller.increaseVolume, - icon: Icon( - Icons.volume_up, - size: 18.sp, - ), - label: Text( - '增加音量', - style: TextStyle(fontSize: 14.sp), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue[600], - padding: EdgeInsets.symmetric(vertical: 12.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - ), - ), - ), - ), - SizedBox(width: 8.w), - Expanded( - child: ElevatedButton.icon( - onPressed: controller.decreaseVolume, - icon: Icon( - Icons.volume_down, - size: 18.sp, - ), - label: Text( - '减小音量', - style: TextStyle(fontSize: 14.sp), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.orange[600], - padding: EdgeInsets.symmetric(vertical: 12.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - ), - ), - ), - ), - ], - ), - SizedBox(height: 8.h), - Obx(() => controller.volumeCommandResult.value.isNotEmpty - ? Container( - width: double.infinity, - padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 16.w), - decoration: BoxDecoration( - color: controller.volumeCommandResult.value.contains('成功') - ? Colors.green[100] - : (controller.volumeCommandResult.value == '发送中...' - ? Colors.blue[100] - : Colors.red[100]), - borderRadius: BorderRadius.circular(8.r), - ), - child: Text( - '音量控制: ${controller.volumeCommandResult.value}', - style: TextStyle( - fontSize: 13.sp, - color: controller.volumeCommandResult.value.contains('成功') - ? Colors.green[900] - : (controller.volumeCommandResult.value == '发送中...' - ? Colors.blue[900] - : Colors.red[900]), - ), - textAlign: TextAlign.center, - ), - ) - : SizedBox.shrink() - ), - ], - ); - } - - Widget _buildDeviceInfo(bool isDarkMode) { - return Container( - padding: EdgeInsets.all(16.w), - decoration: BoxDecoration( - color: isDarkMode ? Colors.grey[900] : Colors.white, - borderRadius: BorderRadius.circular(12.r), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '设备信息:', - style: TextStyle( - fontSize: 14.sp, - color: isDarkMode ? Colors.white : Colors.black87, - ), - ), - SizedBox(height: 8.h), - Container( - padding: EdgeInsets.all(8.w), - decoration: BoxDecoration( - color: isDarkMode ? Colors.black : Colors.grey[100], - borderRadius: BorderRadius.circular(8.r), - ), - child: Obx(() => Text( - controller.deviceInfo.value, - style: TextStyle( - fontSize: 13.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, - height: 1.4, - ), - )), - ), - SizedBox(height: 16.h), - Obx(() => controller.currentDevice.value != null - ? ElevatedButton.icon( - onPressed: controller.disconnectDevice, - icon: Icon( - Icons.bluetooth_disabled, - size: 16.sp, - ), - label: Text( - '断开连接', - style: TextStyle(fontSize: 13.sp), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red[600], - padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - ), - ), - ) - : SizedBox.shrink() - ), - ], - ), - ); - } - - Widget _buildDevicesList(bool isDarkMode) { - return Expanded( - child: Container( - decoration: BoxDecoration( - color: isDarkMode ? Colors.grey[900] : Colors.white, - borderRadius: BorderRadius.circular(12.r), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.all(16.w), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '扫描到的设备', - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - color: isDarkMode ? Colors.white : Colors.black87, - ), - ), - Obx(() => Text( - '${controller.scannedDevices.length}个设备', - style: TextStyle( - fontSize: 12.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, - ), - )), - ], - ), - ), - Divider(height: 1, thickness: 1, color: isDarkMode ? Colors.grey[800] : Colors.grey[200]), - Expanded( - child: Obx(() => controller.scannedDevices.isEmpty - ? Center( - child: controller.isScanning.value - ? Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CircularProgressIndicator(), - SizedBox(height: 16.h), - Text( - '正在扫描...', - style: TextStyle( - fontSize: 14.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, - ), - ), - ], - ) - : Text( - '未扫描到设备', - style: TextStyle( - fontSize: 14.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, - ), - ), - ) - : ListView.separated( - padding: EdgeInsets.symmetric(vertical: 8.h), - itemCount: controller.scannedDevices.length, - separatorBuilder: (context, index) => Divider( - height: 1, - thickness: 1, - color: isDarkMode ? Colors.grey[800] : Colors.grey[200], - ), - itemBuilder: (context, index) { - final device = controller.scannedDevices[index]; - return ListTile( - dense: true, - title: Text( - device.name.isEmpty ? '未知设备' : device.name, - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.w500, - color: isDarkMode ? Colors.white : Colors.black87, - ), - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '设备ID: ${device.id}', - style: TextStyle( - fontSize: 11.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, - ), - ), - Text( - 'RSSI: ${device.rssi} dBm', - style: TextStyle( - fontSize: 11.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, - ), - ), - ], - ), - trailing: Obx(() => controller.currentDevice.value?.id == device.id - ? Chip( - label: Text( - '已连接', - style: TextStyle( - fontSize: 12.sp, - color: Colors.white, - ), - ), - backgroundColor: Colors.green, - padding: EdgeInsets.symmetric(horizontal: 8.w), - ) - : ElevatedButton( - onPressed: () => controller.connectToDevice(device), - child: Text( - '连接', - style: TextStyle(fontSize: 12.sp), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green[600], - padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6.r), - ), - ), - ), - ), - contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), - isThreeLine: true, - ); - }, - ), - ), - ), - ], - ), - ), - ); - } - - Color _getStatusColor(String status) { - switch (status) { - case '连接中...': - return Colors.orange; - case '已连接': - return Colors.green; - case '连接失败': - return Colors.red; - case '已断开': - return Colors.grey; - default: - return Colors.grey; - } - } -} \ No newline at end of file diff --git a/lib/modules/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart index 2788962eb..ef8d2de28 100644 --- a/lib/modules/splash/controllers/splash_controller.dart +++ b/lib/modules/splash/controllers/splash_controller.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'package:amap_flutter/amap_flutter.dart'; import 'package:deep_voice/data/models/appconfig.dart'; import 'package:deep_voice/data/models/appconfig_model.dart'; import 'package:flutter/material.dart'; @@ -9,7 +8,7 @@ import '../../../data/models/user_Info.dart'; import '../../../routes/app_routes.dart'; import '../../../core/themes/theme_service.dart'; import '../../../data/services/network/api.dart'; - +import 'package:amap_flutter/amap_flutter.dart'; class SplashController extends GetxController { final GetStorage _storage = GetStorage(); // 进度条动画控制 diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 3192a4927..558e7e361 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -29,8 +29,6 @@ import '../modules/pairing/bindings/pairing_binding.dart'; import './app_routes.dart'; import '../modules/settings/views/settings_view.dart'; import '../modules/settings/bindings/settings_binding.dart'; -import '../modules/jieli_test/views/jieli_test_view.dart'; -import '../modules/jieli_test/bindings/jieli_test_binding.dart'; import '../modules/opus_test/views/opus_test_view.dart'; import '../modules/opus_test/bindings/opus_test_binding.dart'; import '../modules/speech_test/views/speech_test_view.dart'; @@ -162,13 +160,6 @@ abstract class AppPages { transition: Transition.rightToLeft, transitionDuration: Duration(milliseconds: 250), ), - GetPage( - name: Routes.jieliTest, - page: () => const JieliTestView(), - binding: JieliTestBinding(), - transition: Transition.rightToLeft, - transitionDuration: Duration(milliseconds: 250), - ), GetPage( name: Routes.opusTest, page: () => const OpusTestView(), diff --git a/local_plugins/ble_service/android/build.gradle.kts b/local_plugins/ble_service/android/build.gradle.kts index 545a75224..398f8afa7 100644 --- a/local_plugins/ble_service/android/build.gradle.kts +++ b/local_plugins/ble_service/android/build.gradle.kts @@ -31,5 +31,4 @@ dependencies { // 使用从本地Maven仓库引用的AAR依赖 implementation("com.jieli.audio:jl_audio_decode:2.0.0") implementation("com.jieli.jl_bt_ota:jl_bt_ota:1.9.2") - implementation(project(":deepvoice_utils")) } \ No newline at end of file diff --git a/local_plugins/jieli/README.md b/local_plugins/jieli/README.md deleted file mode 100644 index d926109c8..000000000 --- a/local_plugins/jieli/README.md +++ /dev/null @@ -1,144 +0,0 @@ -# Jieli 插件 - -一个用于连接杰理蓝牙耳机的Flutter插件,支持Android 12+的BLE通信。 - -## 功能 - -- 扫描支持特定BLE服务的耳机设备 -- 连接设备并发现服务 -- 接收耳机的唤醒通知 -- 向耳机发送控制命令启动录音 -- 接收音频数据流 - -## 技术规范 - -此插件实现了以下BLE特性: - -- **服务UUID**: 0000A100-0000-1000-8000-00805F9B34FB -- **音频数据特征UUID**: 0000A101-0000-1000-8000-00805F9B34FB -- **控制特征UUID**: 0000A102-0000-1000-8000-00805F9B34FB - -## 使用方法 - -### 初始化插件 - -```dart -final jieli = Jieli(); -``` - -### 检查蓝牙状态 - -```dart -bool isEnabled = await jieli.isBluetoothEnabled(); -if (!isEnabled) { - await jieli.requestBluetoothEnable(); -} -``` - -### 扫描设备 - -```dart -// 监听扫描结果 -jieli.scanResults.listen((devices) { - // 处理扫描到的设备列表 - for (var device in devices) { - print('发现设备: ${device.name} (${device.id}), RSSI: ${device.rssi}'); - } -}); - -// 开始扫描,10秒后自动停止 -await jieli.startScan(scanTimeout: 10000); - -// 手动停止扫描 -await jieli.stopScan(); -``` - -### 连接设备 - -```dart -// 监听连接状态 -jieli.connectionState.listen((state) { - switch (state) { - case ConnectionState.connected: - print('设备已连接'); - break; - case ConnectionState.disconnected: - print('设备已断开'); - break; - case ConnectionState.connecting: - print('正在连接...'); - break; - case ConnectionState.disconnecting: - print('正在断开...'); - break; - case ConnectionState.error: - print('连接错误'); - break; - } -}); - -// 连接到设备 -await jieli.connect(deviceId); - -// 断开连接 -await jieli.disconnect(); -``` - -### 接收唤醒信号 - -```dart -// 监听唤醒信号 -jieli.wakeupStream.listen((wakeup) { - print('收到唤醒信号'); - // 通常在收到唤醒后自动发送开始录音命令 -}); -``` - -### 发送录音命令 - -```dart -// 发送开始录音命令 -await jieli.sendStartRecordingCommand(); - -// 发送停止录音命令 -await jieli.sendStopRecordingCommand(); -``` - -### 接收音频数据 - -```dart -// 监听音频数据流 -jieli.audioDataStream.listen((data) { - print('收到音频数据: ${data.length} 字节'); - // 处理音频数据 -}); -``` - -### 资源清理 - -```dart -// 在不再需要插件时释放资源 -await jieli.dispose(); -``` - -## 权限 - -使用此插件需要以下Android权限: - -- Android 12+ (API 31+): `BLUETOOTH_SCAN`, `BLUETOOTH_CONNECT` -- Android 11及以下: `BLUETOOTH`, `BLUETOOTH_ADMIN` - -插件会在需要时自动请求权限,但建议在应用清单文件中声明这些权限: - -```xml - - - - - - - -``` \ No newline at end of file diff --git a/local_plugins/jieli/android/build.gradle.kts b/local_plugins/jieli/android/build.gradle.kts deleted file mode 100644 index a8a6e7ac4..000000000 --- a/local_plugins/jieli/android/build.gradle.kts +++ /dev/null @@ -1,57 +0,0 @@ -plugins { - // Android Library 插件 - id("com.android.library") - // Kotlin Android 插件 - id("org.jetbrains.kotlin.android") - id("com.github.johnrengelman.shadow") version "8.1.1" -} - -android { - // 命名空间,对应你插件的包名(需与代码内包名保持一致) - namespace = "com.yunqiinnovation.jieli" - - // 目标 SDK 版本 - compileSdk = 35 - - defaultConfig { - // 最低 SDK 版本 - minSdk = 21 - targetSdk = 33 - } - - // Java 语言级别兼容配置 - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - // Kotlin 语言级别 - kotlinOptions { - jvmTarget = "11" - } - - // 添加packaging配置,排除冲突文件 - packaging { - resources { - excludes.add("META-INF/DEPENDENCIES") - excludes.add("META-INF/LICENSE") - excludes.add("META-INF/LICENSE.txt") - excludes.add("META-INF/license.txt") - excludes.add("META-INF/NOTICE") - excludes.add("META-INF/NOTICE.txt") - excludes.add("META-INF/notice.txt") - excludes.add("META-INF/*.kotlin_module") - } - } -} - -dependencies { - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10") - implementation("androidx.core:core-ktx:1.7.0") - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0") - implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0") - implementation("androidx.lifecycle:lifecycle-service:2.4.0") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2") - implementation(project(":azure_speech")) - implementation(project(":jl_opus")) -} \ No newline at end of file diff --git a/local_plugins/jieli/android/settings.gradle.kts b/local_plugins/jieli/android/settings.gradle.kts deleted file mode 100644 index c0323d3bc..000000000 --- a/local_plugins/jieli/android/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "jieli" \ No newline at end of file diff --git a/local_plugins/jieli/android/src/main/AndroidManifest.xml b/local_plugins/jieli/android/src/main/AndroidManifest.xml deleted file mode 100644 index 3cf7a945e..000000000 --- a/local_plugins/jieli/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt b/local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt deleted file mode 100644 index 5921ba603..000000000 --- a/local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt +++ /dev/null @@ -1,665 +0,0 @@ -package com.yunqiinnovation.jieli - -import android.bluetooth.* -import android.bluetooth.le.* -import android.content.Context -import android.os.Handler -import android.os.Looper -import android.os.ParcelUuid -import android.util.Log -import androidx.lifecycle.LifecycleService -import androidx.lifecycle.MutableLiveData -import kotlinx.coroutines.* -import java.io.File -import java.io.FileOutputStream -import java.text.SimpleDateFormat -import java.util.* -import com.yunqiinnovation.azure_speech.AzureAsrHelper -import com.yunqiinnovation.azure_speech.AzureTtsHelper -import com.yunqiinnovation.jl_opus.JlOpusPlugin - -/** - * BLE服务类,提供核心蓝牙功能 - */ -class BleService(private val context: Context) { - companion object { - private const val TAG = "JieliBleService" - - // 服务和特征UUID - 根据协议规范重新定义 - // private const val AUDIO_SERVICE_UUID = "0000faa0-0000-1000-8000-00805f9b34fb" // 主服务UUID - private const val AUDIO_SERVICE_UUID = "0000abc0-0000-1111-2222-123456789abc" // 主服务UUID - private const val NOTIFY_CHAR_UUID = - "0000abc2-0000-1111-2222-123456789abc" // 用于接收数据的特征(notify) - private const val WRITE_CHAR_UUID = - "0000abc1-0000-1111-2222-123456789abc" // 用于发送命令的特征(write without response) - private const val CLIENT_CHAR_CONFIG_UUID = - "00002902-0000-1000-8000-00805f9b34fb" // 标准的客户端特征配置描述符 - - // 连接状态常量 - const val STATE_DISCONNECTED = 0 - const val STATE_CONNECTING = 1 - const val STATE_CONNECTED = 2 - const val STATE_DISCONNECTING = 3 - const val STATE_ERROR = 4 - - // 扫描结果更新间隔(毫秒) - private const val SCAN_RESULT_UPDATE_INTERVAL = 500L - - // 命令类型常量 - const val COMMAND_VOLUME = 1 - } - - /** - * 回调接口 - */ - interface Callback { - fun onScanResult(devices: List>) - fun onConnectionStateChanged(state: Int) - fun onAudioDataReceived(data: ByteArray) - fun onWakeupSignalReceived() - fun onCommandResult(commandType: Int, success: Boolean) - } - - // region 文件相关 - private var outputFile: File? = null - private var fos: FileOutputStream? = null - - // Azure服务 - private var azureAsrHelper: AzureAsrHelper? = null - private var azureTtsHelper: AzureTtsHelper? = null - - // 获取插件实例 - private val opusPlugin = JlOpusPlugin() - - // 蓝牙管理器和适配器 - private val bluetoothManager = - context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager - private val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter - private val bluetoothLeScanner: BluetoothLeScanner? = bluetoothAdapter?.bluetoothLeScanner - - // GATT连接和特征 - private var bluetoothGatt: BluetoothGatt? = null - private var notifyChar: BluetoothGattCharacteristic? = null - private var writeChar: BluetoothGattCharacteristic? = null - - // 扫描相关 - private val scanHandler = Handler(Looper.getMainLooper()) - private val scanResults = ArrayList() - private var isScanning = false - private var isPendingScanResultUpdate = false - private val scanResultUpdateRunnable = Runnable { - notifyScanResults() - isPendingScanResultUpdate = false - } - - // 连接状态LiveData - val connectionState = MutableLiveData(STATE_DISCONNECTED) - - // 统一回调接口 - var callback: Callback? = null - - /** - * 开始扫描BLE设备 - */ - fun startScan(scanTimeout: Long = 5000): Boolean { - if (bluetoothLeScanner == null || isScanning) { - return false - } - - Log.i(TAG, "开始扫描BLE设备...") - scanResults.clear() - - val serviceUuid = UUID.fromString(AUDIO_SERVICE_UUID) - val scanFilter = ScanFilter.Builder() - // .setServiceUuid(ParcelUuid(serviceUuid)) - .build() - val scanFilters = listOf(scanFilter) - - val scanSettings = ScanSettings.Builder() - .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) - .build() - - bluetoothLeScanner.startScan(scanFilters, scanSettings, scanCallback) - isScanning = true - - // 设置超时停止扫描 - scanHandler.postDelayed({ - Log.d( - TAG, - "定时器触发,准备停止扫描 [主线程:${Looper.myLooper() == Looper.getMainLooper()}]" - ) - stopScan() - }, scanTimeout) - - return true - } - - /** - * 停止扫描 - */ - fun stopScan(): Boolean { - Log.i(TAG, "停止扫描BLE设备") - - if (bluetoothLeScanner == null || !isScanning) { - return false - } - - bluetoothLeScanner.stopScan(scanCallback) - scanHandler.removeCallbacksAndMessages(null) - isScanning = false - - // 最后一次发送扫描结果 - notifyScanResults() - - return true - } - - /** - * 扫描回调 - */ - private val scanCallback = object : ScanCallback() { - override fun onScanResult(callbackType: Int, result: ScanResult) { - val device = result.device - val deviceName = device.name ?: "未知设备" - val deviceAddress = device.address - val rssi = result.rssi - val scanRecord = result.scanRecord - if (!deviceName.contains("zxd")) { - return - } - Log.d(TAG, "============发现设备: $deviceName [$deviceAddress], RSSI: $rssi") - - // 打印所有发现的UUID以便调试 - val serviceUuids = scanRecord?.serviceUuids - if (serviceUuids != null) { - for (uuid in serviceUuids) { - Log.d(TAG, "设备UUID: ${uuid.uuid}") - } - } - - // 检查制造商数据 - for (i in 0 until 65535) { - val manufacturerData = scanRecord?.getManufacturerSpecificData(i) - if (manufacturerData != null) { - // Log.d(TAG, "发现厂商ID: $i, 数据长度: ${manufacturerData.size}") - // 这里可以根据已知的厂商ID和格式解析数据 - } - } - - // 避免重复添加 - - val existingIndex = scanResults.indexOfFirst { it.device.address == deviceAddress } - - if (existingIndex >= 0) { - - scanResults[existingIndex] = result - } else { - Log.d(TAG, "扫描回调A, scanResults[existingIndex]ADD") - scanResults.add(result) - } - - // 如果没有待处理的更新,安排一个 - if (!isPendingScanResultUpdate) { - isPendingScanResultUpdate = true - scanHandler.postDelayed(scanResultUpdateRunnable, SCAN_RESULT_UPDATE_INTERVAL) - } - } - - override fun onScanFailed(errorCode: Int) { - Log.e(TAG, "扫描失败,错误码: $errorCode") - isScanning = false - } - } - - /** - * 将扫描结果通知回调 - */ - private fun notifyScanResults() { - val devicesJson = scanResults.map { result -> - mapOf( - "id" to result.device.address, - "name" to (result.device.name ?: "未知设备"), - "rssi" to result.rssi - ) - } - - callback?.onScanResult(devicesJson) - } - - /** - * 连接到指定设备 - */ - fun connect(deviceAddress: String) { - if (bluetoothAdapter == null) { - Log.e(TAG, "蓝牙适配器不可用") - updateConnectionState(STATE_ERROR) - return - } - - // 先断开现有连接 - bluetoothGatt?.close() - bluetoothGatt = null - - try { - val device = bluetoothAdapter.getRemoteDevice(deviceAddress) - updateConnectionState(STATE_CONNECTING) - - // 连接GATT服务器 - Log.i(TAG, "正在连接设备: ${device.name ?: "未知设备"} [$deviceAddress]") - bluetoothGatt = - device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE) - } catch (e: Exception) { - Log.e(TAG, "连接设备时发生错误: ${e.message}") - updateConnectionState(STATE_ERROR) - } - } - - /** - * 断开连接 - */ - fun disconnect() { - bluetoothGatt?.disconnect() - updateConnectionState(STATE_DISCONNECTING) - } - - /** - * 更新连接状态并通知回调 - */ - private fun updateConnectionState(state: Int) { - connectionState.postValue(state) - callback?.onConnectionStateChanged(state) - } - - /** - * GATT回调 - */ - private val gattCallback = object : BluetoothGattCallback() { - - - override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { - if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) { - Log.i(TAG, "设备已连接,开始发现服务...") - updateConnectionState(STATE_CONNECTED) - - // 开始搜索服务 - gatt.discoverServices() - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - Log.i(TAG, "设备已断开连接") - updateConnectionState(STATE_DISCONNECTED) - - // 清理资源 - gatt.close() - bluetoothGatt = null - notifyChar = null - writeChar = null - } else if (status != BluetoothGatt.GATT_SUCCESS) { - Log.e(TAG, "连接失败,status=$status") - updateConnectionState(STATE_ERROR) - - gatt.close() - bluetoothGatt = null - } - } - - override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { - if (status == BluetoothGatt.GATT_SUCCESS) { - Log.i(TAG, "服务发现成功") - - // 查找目标服务 - val audioService = gatt.getService(UUID.fromString(AUDIO_SERVICE_UUID)) - if (audioService != null) { - Log.i(TAG, "找到音频服务,开始遍历特征...") - audioService.characteristics.forEach { char -> - Log.i(TAG, "特征UUID: ${char.uuid}") - } - Log.i(TAG, "找到音频服务$") - - // 获取特征 - notifyChar = audioService.getCharacteristic(UUID.fromString(NOTIFY_CHAR_UUID)) - writeChar = audioService.getCharacteristic(UUID.fromString(WRITE_CHAR_UUID)) - if (notifyChar != null && writeChar != null) { - Log.i(TAG, "找到所需特征") - - // 设置特征写入类型为无响应模式 - writeChar?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE - - // 注册特征通知 - setupNotifications(gatt) - - opusPlugin?.startOpusDecodeStream(false, 1, 16000, 40, object : - JlOpusPlugin.CallbackOpus { - override fun onAudioDataReceivedOpus(pcmData: ByteArray) { - // Log.i(TAG, "收到解码控制通知: ${pcmData.contentToString()}") - callback?.onAudioDataReceived(pcmData) - // 处理解码后的 PCM 数据(如播放或写入文件) - } - }) - } else { - Log.e(TAG, "未找到所需特征") - updateConnectionState(STATE_ERROR) - } - } else { - Log.e(TAG, "未找到音频服务") - updateConnectionState(STATE_ERROR) - } - } else { - Log.e(TAG, "服务发现失败,status=$status") - updateConnectionState(STATE_ERROR) - } - } - - // 添加扩展函数(在文件顶部声明) - fun List.toByteArray(): ByteArray { - return ByteArray(this.size) { this[it] } - } - - override fun onCharacteristicChanged( - gatt: BluetoothGatt, - characteristic: BluetoothGattCharacteristic - ) { - val characteristicUuid = characteristic.uuid.toString() - // Log.d(TAG, "收到特征变化通知: $characteristicUuid") - - when (characteristicUuid) { - // NOTIFY_CHAR_UUID -> { - // // 收到音频数据 - // val audioData = characteristic.value - // Log.d(TAG, "收到音频数据帧,大小: ${audioData.size} 字节") - - // callback?.onAudioDataReceived(audioData) - // } - NOTIFY_CHAR_UUID -> { - // 处理控制消息 - val value = characteristic.value - // Log.i(TAG, "收到控制通知: ${value.size}") - - // 判断是否收到音频数据 - if (value.isNotEmpty() && (value[0].toInt() and 0xFF == 0x4b || value[0].toInt() and 0xFF == 0x48)) { - // 处理设备返回的协议数据 - fos?.write(value) - - opusPlugin?.handleDecodeRequest(value) - - - } - // 判断是否是协议格式响应 - if (value.isNotEmpty() && value[0].toInt() and 0xFF == 0xA5) { - // 处理设备返回的协议数据 - processResponseData(value) - } - // 假设0x01表示唤醒信号 - else if (value.isNotEmpty() && value[0].toInt() == 0x01) { - Log.i(TAG, "收到唤醒信号") - - callback?.onWakeupSignalReceived() - - // 收到唤醒后自动发送开始录音命令 - sendStartRecordingCommand() - - - } - } - } - } - - override fun onCharacteristicWrite( - gatt: BluetoothGatt, - characteristic: BluetoothGattCharacteristic, - status: Int - ) { - if (characteristic.uuid.toString() == WRITE_CHAR_UUID) { - if (status == BluetoothGatt.GATT_SUCCESS) { - Log.i(TAG, "命令发送成功: ${characteristic.value.contentToString()}") - } else { - Log.e(TAG, "命令发送失败,status=$status") - } - } - } - - override fun onDescriptorWrite( - gatt: BluetoothGatt, - descriptor: BluetoothGattDescriptor, - status: Int - ) { - if (status == BluetoothGatt.GATT_SUCCESS) { - Log.i(TAG, "描述符写入成功: ${descriptor.uuid}") - Log.i(TAG, "特征通知设置成功,设备就绪") - } else { - Log.e(TAG, "描述符写入失败,status=$status") - updateConnectionState(STATE_ERROR) - } - } - } - - /** - * 设置特征通知 - */ - private fun setupNotifications(gatt: BluetoothGatt) { - // 启用 notify 特征通知 - if (notifyChar?.properties?.and(BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) { - gatt.setCharacteristicNotification(notifyChar, true) - - val descriptor = notifyChar?.getDescriptor(UUID.fromString(CLIENT_CHAR_CONFIG_UUID)) - descriptor?.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE - - if (descriptor != null) { - gatt.writeDescriptor(descriptor) - Log.i(TAG, "设置数据通知特征通知") - } - } else { - Log.e(TAG, "数据通知特征不支持通知") - } - } - - - /** - * 处理设备返回的数据 - * 协议格式: - * 协议头(0xA5) + 字段类型(1byte) + 数据长度(1byte) + 数据域(N byte) + 校验和(1byte) - */ - private fun processResponseData(data: ByteArray) { - if (data.size < 4) { - Log.e(TAG, "返回数据格式错误,长度不足") - return - } - - // 判断协议头 - if (data[0].toInt() and 0xFF != 0xA5) { - Log.e(TAG, "返回数据协议头错误: ${data[0].toInt() and 0xFF}") - return - } - - val fieldType = data[1].toInt() and 0xFF - val dataLen = data[2].toInt() and 0xFF - - // 验证长度 - if (data.size != dataLen + 4) { // 协议头+字段类型+数据长度+数据域+校验和 - Log.e(TAG, "返回数据长度不匹配: 应为${dataLen + 4},实际为${data.size}") - return - } - - // 计算校验和 - var checksum = 0 - for (i in 0 until data.size - 1) { - checksum += data[i].toInt() and 0xFF - } - checksum = checksum and 0xFF - - val receivedChecksum = data[data.size - 1].toInt() and 0xFF - - if (checksum != receivedChecksum) { - Log.e(TAG, "校验和错误: 计算值=$checksum, 接收值=$receivedChecksum") - return - } - - // 解析字段类型 - when (fieldType) { - 0x01 -> { - // 音量调整命令的响应 - val result = data[3].toInt() and 0xFF - val success = result == 0 - Log.i(TAG, "音量调整命令响应: ${if (success) "成功" else "失败"}") - callback?.onCommandResult(COMMAND_VOLUME, success) - } - // 其他命令类型可在此处添加 - else -> { - Log.d(TAG, "未知命令类型: ${String.format("0x%02X", fieldType)}") - } - } - } - - /** - * 发送开始录音命令 - */ - fun sendStartRecordingCommand(): Boolean { - if (bluetoothGatt == null || writeChar == null || connectionState.value != STATE_CONNECTED) { - Log.e(TAG, "无法发送开始录音命令,设备未连接") - return false - } - - try { - val commandData = byteArrayOf(0x01) - writeChar?.value = commandData - // 获取外部存储目录路径 - // 生成带时间戳的文件名(格式:yyyyMMdd_HHmmss_audio.opus) - val timeStamp = - SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) - val fileName = "${timeStamp}_audio.opus" - val externalDir = context.getExternalFilesDir(null) - outputFile = File(externalDir, fileName) - fos = FileOutputStream(outputFile).also { - Log.i(TAG, "音频文件创建成功:${outputFile?.absolutePath}") - } - - // val subscriptionKey = call.argument("subscriptionKey") ?: "" - // val region = call.argument("region") ?: "" - // val supportedLanguages = call.argument>("supportedLanguages") ?: listOf("zh-CN") - - // val success = azureAsrHelper.initialize(subscriptionKey, region, supportedLanguages.toTypedArray()) - // result.success(success) - - - val success = bluetoothGatt?.writeCharacteristic(writeChar) ?: false - - if (success) { - Log.i(TAG, "开始录音命令已发送") - } else { - Log.e(TAG, "开始录音命令发送失败") - } - - return success - } catch (e: Exception) { - Log.e(TAG, "发送开始录音命令时发生异常: ${e.message}") - return false - } - } - - /** - * 发送停止录音命令 - */ - fun sendStopRecordingCommand(): Boolean { - if (bluetoothGatt == null || writeChar == null || connectionState.value != STATE_CONNECTED) { - Log.e(TAG, "无法发送停止录音命令,设备未连接") - return false - } - - try { - val commandData = byteArrayOf(0x02) - writeChar?.value = commandData - - val success = bluetoothGatt?.writeCharacteristic(writeChar) ?: false - fos?.close() - if (success) { - Log.i(TAG, "停止录音命令已发送") - } else { - Log.e(TAG, "停止录音命令发送失败") - } - - return success - } catch (e: Exception) { - Log.e(TAG, "发送停止录音命令时发生异常: ${e.message}") - return false - } - } - - /** - * 发送音量调整命令 - * 协议格式: - * 协议头(0xAA) + 字段类型(0x01) + 数据长度(0x01) + 音量值(0~16) + 校验和 - */ - fun adjustVolume(volume: Int): Boolean { - Log.e( - TAG, - "adjustVolume,bluetoothGatt=${bluetoothGatt},writeChar=${writeChar},connectionState=${connectionState.value}" - ) - if (bluetoothGatt == null || writeChar == null || connectionState.value != STATE_CONNECTED) { - Log.e(TAG, "无法发送音量调整命令,设备未连接") - return false - } - - // 限制音量范围为0-16 - val safeVolume = volume.coerceIn(0, 16) - - try { - // 构建命令数据 - val header: Byte = 0xAA.toByte() - val fieldType: Byte = 0x01 - val dataLen: Byte = 0x01 - val volumeData: Byte = safeVolume.toByte() - - // 计算校验和 - val checksum = ((header.toInt() and 0xFF) + - (fieldType.toInt() and 0xFF) + - (dataLen.toInt() and 0xFF) + - (volumeData.toInt() and 0xFF)) and 0xFF - - // 创建完整命令 - val commandData = - byteArrayOf(header, fieldType, dataLen, volumeData, checksum.toByte()) - - // 写入特征 - writeChar?.value = commandData - - val success = bluetoothGatt?.writeCharacteristic(writeChar) ?: false - - if (success) { - Log.i(TAG, "音量调整命令已发送: $safeVolume") - } else { - Log.e(TAG, "音量调整命令发送失败") - } - - return success - } catch (e: Exception) { - Log.e(TAG, "发送音量调整命令时发生异常: ${e.message}") - return false - } - } - - /** - * 增加音量 - */ - fun increaseVolume(): Boolean { - // 假设当前音量保存在设备中,我们只需发送增加命令 - return adjustVolume(16) // 发送最大音量命令 - } - - /** - * 减小音量 - */ - fun decreaseVolume(): Boolean { - // 假设当前音量保存在设备中,我们只需发送减小命令 - return adjustVolume(0) // 发送最小音量命令 - } - - /** - * 清理资源 - */ - fun cleanup() { - stopScan() - scanHandler.removeCallbacks(scanResultUpdateRunnable) - bluetoothGatt?.close() - bluetoothGatt = null - notifyChar = null - writeChar = null - } -} \ No newline at end of file diff --git a/local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/JieliPlugin.kt b/local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/JieliPlugin.kt deleted file mode 100644 index f09c69f50..000000000 --- a/local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/JieliPlugin.kt +++ /dev/null @@ -1,294 +0,0 @@ -package com.yunqiinnovation.jieli - -import android.app.Activity -import android.bluetooth.BluetoothAdapter -import android.bluetooth.BluetoothManager -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.util.Log -import androidx.annotation.NonNull -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat -import io.flutter.embedding.engine.plugins.FlutterPlugin -import io.flutter.embedding.engine.plugins.activity.ActivityAware -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding -import io.flutter.plugin.common.EventChannel -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.common.MethodChannel.MethodCallHandler -import io.flutter.plugin.common.MethodChannel.Result -import kotlinx.coroutines.* - -/** JieliPlugin */ -class JieliPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, EventChannel.StreamHandler { - /// 方法通道 - private lateinit var methodChannel: MethodChannel - /// 统一事件通道 - private lateinit var eventChannel: EventChannel - - private lateinit var context: Context - private var activity: Activity? = null - - /// BLE服务实例 - private lateinit var bleService: BleService - - // 协程作用域 - private val pluginScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - - // 事件接收器 - private var eventSink: EventChannel.EventSink? = null - - // 事件类型常量 - companion object { - private const val EVENT_TYPE_AUDIO_DATA = "audio_data" - private const val EVENT_TYPE_CONNECTION_STATE = "connection_state" - private const val EVENT_TYPE_SCAN_RESULT = "scan_result" - private const val EVENT_TYPE_WAKEUP_SIGNAL = "wakeup_signal" - private const val EVENT_TYPE_COMMAND_RESULT = "command_result" - } - - // 蓝牙相关请求码 - private val REQUEST_ENABLE_BT = 1 - private val REQUEST_PERMISSION_BLE = 2 - - override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - val binaryMessenger = flutterPluginBinding.binaryMessenger - - // 设置方法通道 - methodChannel = MethodChannel(binaryMessenger, "com.yunqiinnovation.jieli") - methodChannel.setMethodCallHandler(this) - - // 设置统一事件通道 - eventChannel = EventChannel(binaryMessenger, "com.yunqiinnovation.jieli/events") - eventChannel.setStreamHandler(this) - - context = flutterPluginBinding.applicationContext - - // 初始化BLE服务 - bleService = BleService(context) - - // 设置回调 - setupBleCallbacks() - } - - /** - * 设置BLE服务回调 - */ - private fun setupBleCallbacks() { - // 设置统一回调接口 - bleService.callback = object : BleService.Callback { - override fun onScanResult(devices: List>) { - pluginScope.launch(Dispatchers.Main) { - sendEvent(EVENT_TYPE_SCAN_RESULT, devices) - } - } - - override fun onConnectionStateChanged(state: Int) { - pluginScope.launch(Dispatchers.Main) { - sendEvent(EVENT_TYPE_CONNECTION_STATE, state) - } - } - - override fun onAudioDataReceived(data: ByteArray) { - pluginScope.launch(Dispatchers.Main) { - // Log.d("","数据流onAudioDataReceived${data}") - sendEvent(EVENT_TYPE_AUDIO_DATA, data) - } - } - - override fun onWakeupSignalReceived() { - pluginScope.launch(Dispatchers.Main) { - sendEvent(EVENT_TYPE_WAKEUP_SIGNAL, true) - } - } - - override fun onCommandResult(commandType: Int, success: Boolean) { - pluginScope.launch(Dispatchers.Main) { - sendEvent(EVENT_TYPE_COMMAND_RESULT, mapOf( - "commandType" to commandType, - "success" to success - )) - } - } - } - } - - /** - * 发送事件到Flutter端 - */ - private fun sendEvent(eventType: String, data: Any?) { - eventSink?.success(mapOf( - "type" to eventType, - "data" to data - )) - } - - override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { - when (call.method) { - "startScan" -> { - val scanTimeout = call.argument("scanTimeout") ?: 10000 - - if (!hasRequiredPermissions()) { - requestBlePermissions() - result.error("PERMISSION_DENIED", "需要蓝牙权限才能扫描设备", null) - return - } - - val success = bleService.startScan(scanTimeout.toLong()) - result.success(success) - } - "stopScan" -> { - val success = bleService.stopScan() - result.success(success) - } - "connect" -> { - val deviceId = call.argument("deviceId") - if (deviceId == null) { - result.error("INVALID_ARGUMENT", "设备ID不能为空", null) - return - } - - if (!hasRequiredPermissions()) { - requestBlePermissions() - result.error("PERMISSION_DENIED", "需要蓝牙权限才能连接设备", null) - return - } - - bleService.connect(deviceId) - result.success(true) - } - "disconnect" -> { - bleService.disconnect() - result.success(true) - } - "sendStartRecordingCommand" -> { - val success = bleService.sendStartRecordingCommand() - result.success(success) - } - "sendStopRecordingCommand" -> { - val success = bleService.sendStopRecordingCommand() - result.success(success) - } - "isBluetoothEnabled" -> { - val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager - val bluetoothAdapter = bluetoothManager.adapter - result.success(bluetoothAdapter?.isEnabled == true) - } - "requestBluetoothEnable" -> { - if (activity == null) { - result.error("ACTIVITY_NOT_AVAILABLE", "Activity不可用,无法请求打开蓝牙", null) - return - } - - val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager - val bluetoothAdapter = bluetoothManager.adapter - - if (bluetoothAdapter == null) { - result.error("BLUETOOTH_UNAVAILABLE", "设备不支持蓝牙", null) - return - } - - if (bluetoothAdapter.isEnabled) { - result.success(true) - return - } - - // 请求打开蓝牙 - val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) - activity?.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT) - result.success(true) - } - "adjustVolume" -> { - val volume = call.argument("volume") ?: 0 - val success = bleService.adjustVolume(volume) - result.success(success) - } - "increaseVolume" -> { - val success = bleService.increaseVolume() - result.success(success) - } - "decreaseVolume" -> { - val success = bleService.decreaseVolume() - result.success(success) - } - else -> { - result.notImplemented() - } - } - } - - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { - methodChannel.setMethodCallHandler(null) - eventChannel.setStreamHandler(null) - - bleService.cleanup() - pluginScope.cancel() - } - - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - activity = binding.activity - } - - override fun onDetachedFromActivityForConfigChanges() { - activity = null - } - - override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { - activity = binding.activity - } - - override fun onDetachedFromActivity() { - activity = null - } - - /// EventChannel.StreamHandler接口实现 - override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { - eventSink = events - } - - override fun onCancel(arguments: Any?) { - eventSink = null - } - - /// 检查是否拥有所需权限 - private fun hasRequiredPermissions(): Boolean { - return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { - hasPermission(android.Manifest.permission.BLUETOOTH_SCAN) && - hasPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - } else { - hasPermission(android.Manifest.permission.BLUETOOTH) && - hasPermission(android.Manifest.permission.BLUETOOTH_ADMIN) - } - } - - /// 检查单个权限 - private fun hasPermission(permission: String): Boolean { - return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED - } - - /// 请求BLE权限 - private fun requestBlePermissions() { - activity?.let { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { - ActivityCompat.requestPermissions( - it, - arrayOf( - android.Manifest.permission.BLUETOOTH_SCAN, - android.Manifest.permission.BLUETOOTH_CONNECT - ), - REQUEST_PERMISSION_BLE - ) - } else { - ActivityCompat.requestPermissions( - it, - arrayOf( - android.Manifest.permission.BLUETOOTH, - android.Manifest.permission.BLUETOOTH_ADMIN - ), - REQUEST_PERMISSION_BLE - ) - } - } - } -} \ No newline at end of file diff --git a/local_plugins/jieli/lib/jieli.dart b/local_plugins/jieli/lib/jieli.dart deleted file mode 100644 index d0d318627..000000000 --- a/local_plugins/jieli/lib/jieli.dart +++ /dev/null @@ -1,283 +0,0 @@ -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:flutter/services.dart'; - -/// 设备类,代表一个BLE设备 -class BleDevice { - final String id; - final String name; - final int rssi; - - BleDevice({required this.id, required this.name, required this.rssi}); - - factory BleDevice.fromJson(Map json) { - return BleDevice( - id: json['id'] as String, - name: json['name'] as String? ?? 'Unknown Device', - rssi: json['rssi'] as int? ?? 0, - ); - } - - Map toJson() { - return { - 'id': id, - 'name': name, - 'rssi': rssi, - }; - } - - @override - String toString() => 'BleDevice(id: $id, name: $name, rssi: $rssi)'; -} - -/// 连接状态 -enum ConnectionState { - disconnected, - connecting, - connected, - disconnecting, - error -} - -/// 事件类型 -class EventType { - static const String audioData = 'audio_data'; - static const String connectionState = 'connection_state'; - static const String scanResult = 'scan_result'; - static const String wakeupSignal = 'wakeup_signal'; - static const String commandResult = 'command_result'; -} - -/// 杰理BLE插件主类 -class Jieli { - static const MethodChannel _methodChannel = - MethodChannel('com.yunqiinnovation.jieli'); - static const EventChannel _eventChannel = - EventChannel('com.yunqiinnovation.jieli/events'); - - /// 扫描结果流 - static final StreamController> _scanController = - StreamController>.broadcast(); - Stream> get scanResults => _scanController.stream; - - /// 连接状态变化流 - static final StreamController _connectionController = - StreamController.broadcast(); - Stream get connectionState => _connectionController.stream; - - /// 音频数据流 - static final StreamController _audioController = - StreamController.broadcast(); - Stream get audioDataStream => _audioController.stream; - - /// 唤醒信号流 - static final StreamController _wakeupController = - StreamController.broadcast(); - Stream get wakeupStream => _wakeupController.stream; - - /// 命令结果流 - static final StreamController _commandResultController = - StreamController.broadcast(); - Stream get commandResultStream => - _commandResultController.stream; - - /// 原始事件流 - Stream>? _eventStream; - - /// 事件流订阅 - StreamSubscription? _eventSubscription; - - /// 命令类型常量 - static const int commandTypeVolume = 1; - - /// 构造函数,初始化平台通道和事件监听 - Jieli() { - _setupEventListener(); - } - - /// 设置事件监听器 - void _setupEventListener() { - _eventSubscription = - _eventChannel.receiveBroadcastStream().listen(_handleEvent); - } - - /// 处理来自原生平台的事件 - void _handleEvent(dynamic event) { - if (event is! Map) return; - - final eventData = Map.from(event); - final String eventType = eventData['type'] as String; - final dynamic data = eventData['data']; - - switch (eventType) { - case EventType.scanResult: - if (data is List) { - final deviceList = data - .map( - (item) => BleDevice.fromJson(Map.from(item))) - .toList(); - _scanController.add(deviceList); - } - break; - case EventType.connectionState: - if (data is int && data >= 0 && data < ConnectionState.values.length) { - _connectionController.add(ConnectionState.values[data]); - } - break; - case EventType.audioData: - if (data is Uint8List) { - //print('收到ble通道消息: $data'); - _audioController.add(data); - } - break; - case EventType.wakeupSignal: - if (data is bool) { - _wakeupController.add(data); - } - break; - case EventType.commandResult: - if (data is Map) { - final commandType = data['commandType'] as int; - final success = data['success'] as bool; - _commandResultController.add(CommandResult( - commandType: commandType, - success: success, - )); - } - break; - } - } - - /// 开始扫描BLE设备 - Future startScan({int scanTimeout = 10000}) async { - final result = await _methodChannel.invokeMethod( - 'startScan', - {'scanTimeout': scanTimeout}, - ); - return result ?? false; - } - - /// 停止扫描BLE设备 - Future stopScan() async { - final result = await _methodChannel.invokeMethod('stopScan'); - return result ?? false; - } - - /// 连接到指定设备 - Future connect(String deviceId) async { - final result = await _methodChannel.invokeMethod( - 'connect', - {'deviceId': deviceId}, - ); - return result ?? false; - } - - /// 断开连接 - Future disconnect() async { - final result = await _methodChannel.invokeMethod('disconnect'); - return result ?? false; - } - - /// 发送开始录音命令 - Future sendStartRecordingCommand() async { - final result = - await _methodChannel.invokeMethod('sendStartRecordingCommand'); - return result ?? false; - } - - /// 发送停止录音命令 - Future sendStopRecordingCommand() async { - final result = - await _methodChannel.invokeMethod('sendStopRecordingCommand'); - return result ?? false; - } - - /// 关闭插件,释放资源 - Future dispose() async { - await stopScan(); - // await disconnect(); - // _eventSubscription?.cancel(); - // _scanController.close(); - // _connectionController.close(); - // _audioController.close(); - // _wakeupController.close(); - // _commandResultController.close(); - } - - /// 检查蓝牙是否已启用 - Future isBluetoothEnabled() async { - final result = - await _methodChannel.invokeMethod('isBluetoothEnabled'); - return result ?? false; - } - - /// 请求打开蓝牙 - Future requestBluetoothEnable() async { - final result = - await _methodChannel.invokeMethod('requestBluetoothEnable'); - return result ?? false; - } - - /// 调整音量 - /// [volume] 音量值,范围0-16 - Future adjustVolume(int volume) async { - try { - final result = await _methodChannel.invokeMethod( - 'adjustVolume', - {'volume': volume}, - ); - return result ?? false; - } catch (e) { - print('调整音量失败: $e'); - return false; - } - } - - /// 增加音量 - Future increaseVolume() async { - try { - final result = await _methodChannel.invokeMethod('increaseVolume'); - return result ?? false; - } catch (e) { - print('增加音量失败: $e'); - return false; - } - } - - /// 减小音量 - Future decreaseVolume() async { - try { - final result = await _methodChannel.invokeMethod('decreaseVolume'); - return result ?? false; - } catch (e) { - print('减小音量失败: $e'); - return false; - } - } - - /// 命令结果回调 - Stream get onCommandResult => commandResultStream - .where((event) => event.commandType == commandTypeVolume) - .map((event) => event); -} - -/// 蓝牙设备模型 -class BluetoothDevice { - // ... existing code ... -} - -/// 命令结果 -class CommandResult { - final int commandType; - final bool success; - - CommandResult({ - required this.commandType, - required this.success, - }); - - @override - String toString() => - 'CommandResult{commandType: $commandType, success: $success}'; -} diff --git a/local_plugins/jieli/pubspec.yaml b/local_plugins/jieli/pubspec.yaml deleted file mode 100644 index ab230e685..000000000 --- a/local_plugins/jieli/pubspec.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: jieli -description: A Flutter plugin for connecting with Jieli BLE earphones -version: 0.0.1 -homepage: - -environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.0.0" - -dependencies: - flutter: - sdk: flutter - -dev_dependencies: - flutter_test: - sdk: flutter - -flutter: - plugin: - platforms: - android: - package: com.yunqiinnovation.jieli - pluginClass: JieliPlugin \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 4f59af928..e55f4f571 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -69,8 +69,6 @@ dependencies: path: local_plugins/azure_speech open_ai_service: path: local_plugins/open_ai_service - jieli: - path: local_plugins/jieli chat_storage: path: local_plugins/chat_storage spotify_sdk: @@ -91,8 +89,6 @@ dependencies: path: local_plugins/agent_service music_service: path: local_plugins/music_service - # amap_service: - # path: local_plugins/amap_service dev_dependencies: diff --git a/test.json b/test.json index 8b7c60bb7..178eae9f1 100644 --- a/test.json +++ b/test.json @@ -15,4 +15,21 @@ adb shell am broadcast \ adb shell dumpsys package com.yunqiinnovation.deepsound | grep receiver - adb logcat -v time | grep -i BleForegroundService \ No newline at end of file + adb logcat -v time | grep -i BleForegroundService + + + + + mvn install:install-file \ + -Dfile= + -DgroupId=com.jieli.audio \ + -DartifactId=jl_audio_decode \ + -Dversion=2.0.0 \ + -Dpackaging=aar + + mvn install:install-file \ + -Dfile=/Users/wolfplus/Developer/Doc/Jieli_OTA_SDK_Android_1.9.2/libs/jl_bt_ota_V1.9.2-release.aar \ + -DgroupId=com.jieli.jl_bt_ota \ + -DartifactId=jl_bt_ota \ + -Dversion=1.9.2 \ + -Dpackaging=aar \ No newline at end of file