Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

weicu
lxm 1 year ago
parent
commit
5b8a41f127
  1. 27
      lib/data/services/ble_manager.dart
  2. 146
      lib/data/services/speech_impl/volcano_tts_service.dart
  3. 2
      lib/modules/meeting/bindings/meeting_details_binding.dart
  4. 166
      lib/modules/meeting/controllers/mind_map_controller.dart
  5. 85
      lib/modules/meeting/views/mind_map_tab.dart
  6. 26
      lib/modules/meeting/views/share_bottom_sheet.dart
  7. 4
      lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
  8. 210
      lib/modules/settings/views/settings_view.dart
  9. 23
      local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt

27
lib/data/services/ble_manager.dart

@ -184,6 +184,19 @@ class BleManager extends GetxService {
state == BleConnectionState.error) {
Logger.info('BLE设备已断开或发生错误');
_isConnected.value = false;
// 更新设备信息Add commentMore actions
_updateDeviceInfoIfNeeded(
batteryLevel: 0,
leftEarBattery: 0,
rightEarBattery: 0,
caseBattery: 0,
leftEarCharging: false,
rightEarCharging: false,
caseCharging: false,
firmwareVersion: '',
firmwareProductId: '',
firmwareProductColor: '',
);
// 断开连接时不清除设备信息,保留最后的电量信息
// 只有在需要完全清除设备时才重置信息
}
@ -405,7 +418,7 @@ class BleManager extends GetxService {
// 使用保存的设备信息创建新的DeviceInfo对象
String deviceAddress = _currentDeviceAddress ?? "";
String deviceName = _currentDeviceName ?? "";
Logger.info('创建新的设备信息对象: address=$deviceAddress, name=$deviceName');
deviceInfo.value = DeviceInfo(
address: deviceAddress,
@ -442,7 +455,7 @@ class BleManager extends GetxService {
// 创建初始设备信息对象
String deviceAddress = _currentDeviceAddress ?? "";
String deviceName = _currentDeviceName ?? "";
Logger.info('初始化设备信息对象: address=$deviceAddress, name=$deviceName');
deviceInfo.value = DeviceInfo(
address: deviceAddress,
@ -467,8 +480,9 @@ class BleManager extends GetxService {
_bleService.getProductId(),
]);
Logger.info('设备信息获取结果: 电池=${results[0]}, 版本=${results[1]}, 产品ID=${results[2]}');
Logger.info(
'设备信息获取结果: 电池=${results[0]}, 版本=${results[1]}, 产品ID=${results[2]}');
if (!results[0]) {
Logger.warning('电池信息获取指令发送失败');
}
@ -788,9 +802,10 @@ class BleManager extends GetxService {
// 从蓝牙管理器获取设备名称
final bluetoothManager = Get.find<BluetoothManager>();
_currentDeviceName = bluetoothManager.currentDevice?.name ?? "";
Logger.info('保存设备信息: address=$_currentDeviceAddress, name=$_currentDeviceName');
Logger.info(
'保存设备信息: address=$_currentDeviceAddress, name=$_currentDeviceName');
}
return await _bleService.registerBackgroundScan(macAddress: macAddress);
} catch (e) {
Logger.error('注册后台扫描失败: ${e.toString()}');

146
lib/data/services/speech_impl/volcano_tts_service.dart

@ -9,82 +9,84 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
class VolcanoTtsService extends GetxService implements TtsService {
/// 方法通道
static const MethodChannel _channel = MethodChannel('volcano_speech/tts');
/// 事件通道
static const EventChannel _eventChannel = EventChannel('volcano_speech/tts_events');
static const EventChannel _eventChannel =
EventChannel('volcano_speech/tts_events');
/// 音频数据通道
static const EventChannel _audioDataChannel = EventChannel('volcano_speech/tts_audio_data');
static const EventChannel _audioDataChannel =
EventChannel('volcano_speech/tts_audio_data');
/// 事件流控制器
final StreamController<TtsEvent> _eventStreamController =
final StreamController<TtsEvent> _eventStreamController =
StreamController<TtsEvent>.broadcast();
/// 音频数据流控制器
final StreamController<Uint8List> _audioDataStreamController =
final StreamController<Uint8List> _audioDataStreamController =
StreamController<Uint8List>.broadcast();
/// 事件处理器
StreamSubscription? _eventSubscription;
/// 音频数据处理器
StreamSubscription? _audioDataSubscription;
/// 是否初始化完成
bool _isInitialized = false;
/// 当前语音名称
String _currentVoice = "zh_female_qingxin";
/// 是否启用音频回调
bool _isAudioCallbackEnabled = false;
@override
String get currentVoice => _currentVoice;
@override
bool get isSpeaking => _isSpeaking;
/// 是否正在播放
bool _isSpeaking = false;
@override
Stream<TtsEvent> get onEvent => _eventStreamController.stream;
/// 音频数据流
Stream<Uint8List> get onAudioData => _audioDataStreamController.stream;
/// 构造函数
VolcanoTtsService() {
_setupEventListeners();
_setupAudioDataListeners();
}
/// 设置事件监听
void _setupEventListeners() {
_eventSubscription = _eventChannel.receiveBroadcastStream().listen(
(dynamic event) {
if (event is! Map) return;
final Map<dynamic, dynamic> eventMap = event;
final String? type = eventMap['type'] as String?;
switch (type) {
case 'started':
_isSpeaking = true;
_eventStreamController.add(TtsEvent(type: TtsEventType.started));
break;
case 'completed':
_isSpeaking = false;
_eventStreamController.add(TtsEvent(type: TtsEventType.completed));
break;
case 'canceled':
_isSpeaking = false;
_eventStreamController.add(TtsEvent(type: TtsEventType.canceled));
break;
case 'error':
_isSpeaking = false;
final errorCode = eventMap['errorCode'] as String? ?? "-1";
@ -105,7 +107,7 @@ class VolcanoTtsService extends GetxService implements TtsService {
},
);
}
/// 设置音频数据监听
void _setupAudioDataListeners() {
_audioDataSubscription = _audioDataChannel.receiveBroadcastStream().listen(
@ -119,32 +121,34 @@ class VolcanoTtsService extends GetxService implements TtsService {
},
);
}
@override
Future<bool> initialize({List<String>? supportedLanguages}) async {
if (_isInitialized) return true;
try {
// 从.env文件读取配置
final appId = dotenv.env['VOLCANO_SPEECH_APP_ID'] ?? '';
final appToken = dotenv.env['VOLCANO_SPEECH_APP_TOKEN'] ?? '';
if (appId.isEmpty || appToken.isEmpty) {
debugPrint('火山语音初始化失败:缺少VOLCANO_SPEECH_APP_ID或VOLCANO_SPEECH_APP_TOKEN环境变量');
debugPrint(
'火山语音初始化失败:缺少VOLCANO_SPEECH_APP_ID或VOLCANO_SPEECH_APP_TOKEN环境变量');
return false;
}
// 初始化火山语音TTS
final result = await _channel.invokeMethod<bool>('initialize', {
'appId': appId,
'appToken': appToken,
}) ?? false;
'appId': appId,
'appToken': appToken,
}) ??
false;
if (!result) {
debugPrint('火山语音TTS初始化失败');
return false;
}
_isInitialized = true;
return true;
} catch (e) {
@ -160,16 +164,17 @@ class VolcanoTtsService extends GetxService implements TtsService {
}
return true;
}
@override
Future<bool> setVoice(String voiceName) async {
if (!await _ensureInitialized()) return false;
try {
final result = await _channel.invokeMethod<bool>('setVoice', {
'voice': voiceName,
}) ?? false;
'voice': voiceName,
}) ??
false;
if (result) {
_currentVoice = voiceName;
}
@ -179,16 +184,17 @@ class VolcanoTtsService extends GetxService implements TtsService {
return false;
}
}
/// 设置是否启用音频数据回调
Future<bool> setEnableAudioCallback(bool enable) async {
if (!await _ensureInitialized()) return false;
try {
final result = await _channel.invokeMethod<bool>('enableAudioCallback', {
'enable': enable,
}) ?? false;
'enable': enable,
}) ??
false;
if (result) {
_isAudioCallbackEnabled = enable;
}
@ -198,17 +204,18 @@ class VolcanoTtsService extends GetxService implements TtsService {
return false;
}
}
@override
Future<bool> speakOnce(String text) async {
if (!await _ensureInitialized()) return false;
try {
// 单次播放
final result = await _channel.invokeMethod<bool>('speakOnce', {
'text': text,
}) ?? false;
'text': text,
}) ??
false;
return result;
} catch (e) {
debugPrint('单次播放失败:$e');
@ -216,17 +223,18 @@ class VolcanoTtsService extends GetxService implements TtsService {
return false;
}
}
@override
Future<bool> speakStream(String text) async {
if (!await _ensureInitialized()) return false;
try {
// 流式添加文本
final result = await _channel.invokeMethod<bool>('speakStream', {
'text': text,
}) ?? false;
'text': text,
}) ??
false;
return result;
} catch (e) {
debugPrint('流式播放失败:$e');
@ -234,11 +242,11 @@ class VolcanoTtsService extends GetxService implements TtsService {
return false;
}
}
@override
Future<bool> flushStream() async {
if (!await _ensureInitialized()) return false;
try {
// 刷新流式文本
return await _channel.invokeMethod<bool>('flushStream') ?? false;
@ -247,11 +255,11 @@ class VolcanoTtsService extends GetxService implements TtsService {
return false;
}
}
@override
Future<bool> stop() async {
if (!_isInitialized) return false;
try {
return await _channel.invokeMethod<bool>('stop') ?? false;
} catch (e) {
@ -259,27 +267,33 @@ class VolcanoTtsService extends GetxService implements TtsService {
return false;
}
}
@override
Future<void> dispose() async {
if (_isSpeaking) {
await stop();
}
await _eventSubscription?.cancel();
await _audioDataSubscription?.cancel();
if (!_eventStreamController.isClosed) {
await _eventStreamController.close();
}
if (!_audioDataStreamController.isClosed) {
await _audioDataStreamController.close();
}
if (_isInitialized) {
await _channel.invokeMethod<bool>('release');
_isInitialized = false;
}
}
}
@override
Future<bool> setAudioOutputDevice(int type) {
// TODO: implement setAudioOutputDevice
throw UnimplementedError();
}
}

2
lib/modules/meeting/bindings/meeting_details_binding.dart

@ -1,10 +1,12 @@
import 'package:get/get.dart';
import '../controllers/meeting_details_controller.dart';
import '../controllers/mind_map_controller.dart';
class MeetingDetailsBinding extends Bindings {
@override
void dependencies() {
// 注册控制器
Get.lazyPut<MeetingDetailsController>(() => MeetingDetailsController());
Get.lazyPut<MindMapController>(() => MindMapController());
}
}

166
lib/modules/meeting/controllers/mind_map_controller.dart

@ -0,0 +1,166 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'meeting_details_controller.dart';
class MindMapController extends GetxController {
late final WebViewController webViewController;
final _meetingDetailsController = Get.find<MeetingDetailsController>();
final double _width = 1.sw - 25.w;
bool isWebViewReady = false;
@override
void onInit() {
super.onInit();
String html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MindMap</title>
<script src="https://cdn.jsdelivr.net/npm/markmap-lib"></script>
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script src="https://cdn.jsdelivr.net/npm/markmap-view"></script>
<script src="https://cdn.jsdelivr.net/npm/markmap-toolbar"></script>
<script src="https://cdn.jsdelivr.net/npm/save-svg-as-png@1.4.17/lib/saveSvgAsPng.min.js"></script>
<style>
html,
body {
width: 100%;
height: 100%;
min-width: ${_width}px;
min-height: ${_width}px;
margin: 0;
padding: 0;
overflow: hidden;
}
.mind-map {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
.mind-map #markmap {
width: 100%;
height: 100%;
}
.mind-map .mm-toolbar {
height: 30px;
background: #fff;
border: 1px solid #d0d5dd;
border-radius: 8px;
display: flex;
justify-content: space-around;
align-items: center;
position: absolute;
top: 10px;
right: 2px;
}
.mind-map .mm-toolbar-item {
width: 24px;
height: 24px;
border-radius: 6px;
cursor: pointer;
color: #101828;
display: flex;
justify-content: center;
align-items: center;
}
.mind-map .mm-toolbar-item.active {
background-color: #d4d4d8;
}
</style>
</head>
<body>
<div class="mind-map">
<svg id="markmap"></svg>
<div id="toolbar"></div>
</div>
<script>
const { Transformer, Markmap, Toolbar } = window.markmap;
const transformer = new Transformer();
const { root } = transformer.transform(`${_meetingDetailsController.meetingDetails.value.summary}`);
let mm = Markmap.create('#markmap', {
maxWidth: 300,
initialExpandLevel: 3,
}, root);
const toolbar = Toolbar.create(mm);
toolbar.showBrand = false
toolbar.render();
const el = toolbar.el;
const toolbarElement = document.getElementById('toolbar');
toolbarElement.append(el);
window.captureImage = async function() {
const markmapElement = document.getElementById('markmap');
markmapElement.style.height = `${_width}px`;
await mm.fit();
const options = {
scale: 2,
backgroundColor: '#FFFFFF',
encoderOptions: 1,
};
const uri = await svgAsPngUri(markmapElement, options);
markmapElement.style.height = '100%';
mm.fit();
if (window.ImageChannel) {
window.ImageChannel.postMessage(uri);
}
}
</script>
</body>
</html>
''';
webViewController = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageFinished: (String url) {
isWebViewReady = true;
},
),
)
..addJavaScriptChannel(
'ImageChannel',
onMessageReceived: _messageReceived,
)
..setBackgroundColor(Colors.transparent)
..loadHtmlString(html);
}
void _messageReceived(message) async {
final String dataUrl = message.message;
final String base64Str = dataUrl.split(',').last;
final Uint8List bytes = base64.decode(base64Str);
final String fileName =
'${_meetingDetailsController.meetingData.value.title} mindmap.png';
final tempDir = await getTemporaryDirectory();
final File file = File('${tempDir.path}/$fileName');
await file.writeAsBytes(bytes);
final result = await SharePlus.instance.share(ShareParams(
text: '分享思维导图',
files: [XFile(file.path)],
));
if (result.status == ShareResultStatus.success) {
EasyLoading.showToast('分享完成!');
}
}
}

85
lib/modules/meeting/views/mind_map_tab.dart

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:webview_flutter/webview_flutter.dart';
import '../controllers/meeting_details_controller.dart';
import '../controllers/mind_map_controller.dart';
class MindMapTab extends StatefulWidget {
const MindMapTab({super.key});
@ -13,86 +13,7 @@ class MindMapTab extends StatefulWidget {
class _MindMapTabState extends State<MindMapTab>
with AutomaticKeepAliveClientMixin {
late final WebViewController _controller;
final _meetingDetailsController = Get.find<MeetingDetailsController>();
@override
void initState() {
super.initState();
String html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MindMap</title>
<script>
window.markmap = {
autoLoader: {
toolbar: true,
},
};
</script>
<script src="https://cdn.jsdelivr.net/npm/markmap-autoloader@latest"></script>
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.markmap {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
.markmap>svg {
width: 100%;
height: 100%;
}
.markmap>.mm-toolbar {
top: 20px !important;
bottom: initial !important;
}
.markmap>.mm-toolbar>.mm-toolbar-brand {
display: none;
}
.markmap>.mm-toolbar>.mm-toolbar-item:nth-child(2) {
margin-left: 0;
}
</style>
</head>
<body>
<div class="markmap">
<script type="text/template">
---
markmap:
maxWidth: 300
colorFreezeLevel: 2
initialExpandLevel: 3
---
${_meetingDetailsController.meetingDetails.value.summary}
</script>
</div>
</body>
</html>
''';
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(Colors.transparent)
..loadHtmlString(html);
}
final _controller = Get.find<MindMapController>();
@override
bool get wantKeepAlive => true;
@ -101,7 +22,7 @@ ${_meetingDetailsController.meetingDetails.value.summary}
Widget build(BuildContext context) {
super.build(context);
return WebViewWidget(
controller: _controller,
controller: _controller.webViewController,
);
}
}

26
lib/modules/meeting/views/share_bottom_sheet.dart

@ -9,8 +9,10 @@ import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:webview_flutter/webview_flutter.dart';
import '../controllers/meeting_details_controller.dart';
import '../controllers/mind_map_controller.dart';
class ShareBottomSheet extends StatefulWidget {
const ShareBottomSheet({super.key});
@ -125,28 +127,46 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> {
_share(_controller.meetingData.value.filepath, '分享会议音频');
}),
_shareItem(Icons.message, '导出转写', onTap: () async {
Get.back();
final tempDir = await getTemporaryDirectory();
final file = File(
'${tempDir.path}/${_controller.meetingData.value.title} speaker.txt');
String allText = _getAllText();
await file.writeAsString(allText);
Get.back();
_share(file.path, '分享会议转写');
}),
_shareItem(Icons.article, '导出总结', onTap: () async {
Get.back();
final tempDir = await getTemporaryDirectory();
final file = File(
'${tempDir.path}/${_controller.meetingData.value.title} summary.txt');
await file.writeAsString(
_controller.meetingDetails.value.summary);
Get.back();
_share(file.path, '分享会议总结');
}),
_shareItem(
Icons.lan,
'导出思维导图',
isBorder: false,
onTap: () {},
onTap: () {
Get.back();
final mindMapController = Get.find<MindMapController>();
if (mindMapController.isWebViewReady) {
mindMapController.webViewController
.runJavaScript('captureImage()');
} else {
mindMapController.webViewController
.setNavigationDelegate(
NavigationDelegate(
onPageFinished: (String url) {
mindMapController.isWebViewReady = true;
mindMapController.webViewController
.runJavaScript('captureImage()');
},
),
);
}
},
),
]),
],

4
lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart

@ -327,7 +327,7 @@ class OtaUpgradeController extends GetxController {
}
if (existingSameFile) {
Get.snackbar('文件已存在', '跳过下载,直接更新');
//Get.snackbar('文件已存在', '跳过下载,直接更新');
startUpgrading(savePath);
return;
@ -367,7 +367,7 @@ class OtaUpgradeController extends GetxController {
);
File file = File(savePath);
if (await file.exists()) {
Get.snackbar('下载完成', '文件已保存至: $savePath');
// Get.snackbar('下载完成', '文件已保存至: $savePath');
} else {
Get.snackbar('错误', '文件未找到,请检查写入权限');
}

210
lib/modules/settings/views/settings_view.dart

@ -525,114 +525,114 @@ class SettingsView extends GetView<SettingsController> {
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 杰理测试
_buildSimpleNavigationSetting(
title: '杰理蓝牙测试',
subtitle: '测试杰理蓝牙设备扫描、连接和功能',
icon: Icons.bluetooth,
iconBgColor: isDarkMode
? Colors.blue[900]!.withOpacity(0.3)
: Colors.blue[100]!,
iconColor:
isDarkMode ? Colors.blue[300]! : Colors.blue[600]!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () {
Get.toNamed(Routes.jieliTest);
},
isDarkMode: isDarkMode,
),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 面对面翻译测试
_buildSimpleNavigationSetting(
title: '面对面翻译测试',
subtitle: '测试面对面翻译',
icon: Icons.bluetooth_searching,
iconBgColor: isDarkMode
? Colors.green[900]!.withOpacity(0.3)
: Colors.green[100]!,
iconColor:
isDarkMode ? Colors.green[300]! : Colors.green[600]!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () {
Get.toNamed(Routes.ftftranslation);
},
isDarkMode: isDarkMode,
),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// BLE测试
_buildSimpleNavigationSetting(
title: 'opus解码测试',
subtitle: '测试opus解码',
icon: Icons.bluetooth_searching,
iconBgColor: isDarkMode
? Colors.green[900]!.withOpacity(0.3)
: Colors.green[100]!,
iconColor:
isDarkMode ? Colors.green[300]! : Colors.green[600]!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () {
Get.toNamed(Routes.opusTest);
},
isDarkMode: isDarkMode,
),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// // 杰理测试
// _buildSimpleNavigationSetting(
// title: '杰理蓝牙测试',
// subtitle: '测试杰理蓝牙设备扫描、连接和功能',
// icon: Icons.bluetooth,
// iconBgColor: isDarkMode
// ? Colors.blue[900]!.withOpacity(0.3)
// : Colors.blue[100]!,
// iconColor:
// isDarkMode ? Colors.blue[300]! : Colors.blue[600]!,
// titleColor: isDarkMode ? Colors.white : null,
// subtitleColor: isDarkMode ? Colors.white70 : null,
// onTap: () {
// Get.toNamed(Routes.jieliTest);
// },
// isDarkMode: isDarkMode,
// ),
// Divider(
// height: 1,
// color: isDarkMode
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
// // 面对面翻译测试
// _buildSimpleNavigationSetting(
// title: '面对面翻译测试',
// subtitle: '测试面对面翻译',
// icon: Icons.bluetooth_searching,
// iconBgColor: isDarkMode
// ? Colors.green[900]!.withOpacity(0.3)
// : Colors.green[100]!,
// iconColor:
// isDarkMode ? Colors.green[300]! : Colors.green[600]!,
// titleColor: isDarkMode ? Colors.white : null,
// subtitleColor: isDarkMode ? Colors.white70 : null,
// onTap: () {
// Get.toNamed(Routes.ftftranslation);
// },
// isDarkMode: isDarkMode,
// ),
// Divider(
// height: 1,
// color: isDarkMode
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
// // BLE测试
// _buildSimpleNavigationSetting(
// title: 'opus解码测试',
// subtitle: '测试opus解码',
// icon: Icons.bluetooth_searching,
// iconBgColor: isDarkMode
// ? Colors.green[900]!.withOpacity(0.3)
// : Colors.green[100]!,
// iconColor:
// isDarkMode ? Colors.green[300]! : Colors.green[600]!,
// titleColor: isDarkMode ? Colors.white : null,
// subtitleColor: isDarkMode ? Colors.white70 : null,
// onTap: () {
// Get.toNamed(Routes.opusTest);
// },
// isDarkMode: isDarkMode,
// ),
// Divider(
// height: 1,
// color: isDarkMode
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
// BLE测试
_buildSimpleNavigationSetting(
title: 'BLE蓝牙测试',
subtitle: '测试BLE蓝牙设备扫描、连接和功能',
icon: Icons.bluetooth_searching,
iconBgColor: isDarkMode
? Colors.green[900]!.withOpacity(0.3)
: Colors.green[100]!,
iconColor:
isDarkMode ? Colors.green[300]! : Colors.green[600]!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () {
Get.toNamed(Routes.bleTest);
},
isDarkMode: isDarkMode,
),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// _buildSimpleNavigationSetting(
// title: 'BLE蓝牙测试',
// subtitle: '测试BLE蓝牙设备扫描、连接和功能',
// icon: Icons.bluetooth_searching,
// iconBgColor: isDarkMode
// ? Colors.green[900]!.withOpacity(0.3)
// : Colors.green[100]!,
// iconColor:
// isDarkMode ? Colors.green[300]! : Colors.green[600]!,
// titleColor: isDarkMode ? Colors.white : null,
// subtitleColor: isDarkMode ? Colors.white70 : null,
// onTap: () {
// Get.toNamed(Routes.bleTest);
// },
// isDarkMode: isDarkMode,
// ),
// Divider(
// height: 1,
// color: isDarkMode
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
// 火山语音测试
_buildSimpleNavigationSetting(
title: '语音测试',
subtitle: '测试语音识别和合成功能,支持多种服务实现',
icon: Icons.record_voice_over,
iconBgColor: isDarkMode
? Colors.orange[900]!.withOpacity(0.3)
: Colors.orange[100]!,
iconColor:
isDarkMode ? Colors.orange[300]! : Colors.orange[600]!,
onTap: () => Get.toNamed(Routes.speechTest),
isDarkMode: isDarkMode,
),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// // 火山语音测试
// _buildSimpleNavigationSetting(
// title: '语音测试',
// subtitle: '测试语音识别和合成功能,支持多种服务实现',
// icon: Icons.record_voice_over,
// iconBgColor: isDarkMode
// ? Colors.orange[900]!.withOpacity(0.3)
// : Colors.orange[100]!,
// iconColor:
// isDarkMode ? Colors.orange[300]! : Colors.orange[600]!,
// onTap: () => Get.toNamed(Routes.speechTest),
// isDarkMode: isDarkMode,
// ),
// Divider(
// height: 1,
// color: isDarkMode
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
// 测试ble耳机固件版本升级
_buildSimpleNavigationSetting(
title: 'ota升级',

23
local_plugins/ota/android/src/main/kotlin/com/example/ota/OtaPlugin.kt

@ -146,9 +146,10 @@ class OtaPlugin : BleService.Callback, FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel
private lateinit var context: Context
private lateinit var otaDevice: BluetoothDevice
private lateinit var bluetoothHelper: BluetoothHelper
private var bluetoothHelper: BluetoothHelper? = null
private var otaManager: OTAManager? = null
private lateinit var otaManager: OTAManager
private lateinit var filePath: String//升级文件路径
private var isDisconnect = false//是否主动断开
private var streamSink: EventChannel.EventSink? = null
@ -216,8 +217,8 @@ class OtaPlugin : BleService.Callback, FlutterPlugin, MethodCallHandler {
private val bluetoothCallback = object : BtEventCallback() {
override fun onConnection(device: BluetoothDevice?, status: Int) {
if ((status == StateCode.CONNECTION_FAILED || status == StateCode.CONNECTION_DISCONNECT) && bluetoothHelper.getConnectedDevice() != null) {
bluetoothHelper.disconnectDevice(bluetoothHelper.getConnectedDevice())
if ((status == StateCode.CONNECTION_FAILED || status == StateCode.CONNECTION_DISCONNECT) && bluetoothHelper?.getConnectedDevice() != null) {
bluetoothHelper?.disconnectDevice(bluetoothHelper?.getConnectedDevice())
}
otaDevice = device!!
@ -238,10 +239,10 @@ class OtaPlugin : BleService.Callback, FlutterPlugin, MethodCallHandler {
)
)
Log.d("TAG", "startUpgrade,filePath=${filePath}")
otaManager.bluetoothOption.firmwareFilePath = filePath
otaManager.startOTA(
otaManager?.bluetoothOption?.firmwareFilePath = filePath
otaManager?.startOTA(
CustomUpdateCallback(
otaManager.bluetoothHelper.getConnectedDevice(), streamSink
otaManager?.bluetoothHelper?.getConnectedDevice(), streamSink
)
)
}
@ -280,7 +281,7 @@ class OtaPlugin : BleService.Callback, FlutterPlugin, MethodCallHandler {
otaManager = OTAManager(context)
bluetoothHelper = BluetoothHelper(context)
// 如果需要额外的初始化逻辑,请在这里添加
otaManager.registerBluetoothCallback(bluetoothCallback)
otaManager!!.registerBluetoothCallback(bluetoothCallback)
// 初始化BleService
// 2. 初始化BleService并注册回调
if (BleService.initialize(context)) {
@ -335,10 +336,12 @@ class OtaPlugin : BleService.Callback, FlutterPlugin, MethodCallHandler {
} else if (call.method == "onOtaClose") {
BleService.isEnterOta = false
isDisconnect = false
if(otaManager!= null) {
otaManager?.bluetoothHelper?.stopScan()
otaManager?.bluetoothHelper?.disconnectDevice(otaDevice)
}
result.success("")
} else {
result.notImplemented()
@ -370,7 +373,7 @@ class OtaPlugin : BleService.Callback, FlutterPlugin, MethodCallHandler {
if (state == BleConst.STATE_DISCONNECTED && isDisconnect) {
Log.d("TAG", "Ble设备已断开,onConnectionStateChanged")
Handler(Looper.getMainLooper()).postDelayed({
otaManager.bluetoothHelper.startScan(otaDevice)
otaManager?.bluetoothHelper?.startScan(otaDevice)
}, 2000) // 2000 毫秒 = 2 秒
}
}

Loading…
Cancel
Save