Browse Source

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

newdev_shunjiawei
tanlongsheng 1 year ago
parent
commit
c1174ee17f
  1. 27
      lib/data/services/ble_manager.dart
  2. 5
      lib/data/services/music_manager.dart
  3. 146
      lib/data/services/speech_impl/volcano_tts_service.dart
  4. 80
      lib/modules/agent/controllers/agent_controller.dart
  5. 31
      lib/modules/meeting/controllers/meeting_controller.dart
  6. 17
      lib/modules/meeting/controllers/meeting_record_controller.dart
  7. 8
      lib/modules/music/views/music_playlist_view.dart
  8. 4
      lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
  9. 210
      lib/modules/settings/views/settings_view.dart
  10. 62
      lib/modules/translation/controllers/translation_controller.dart
  11. 95
      lib/modules/translation/views/translation_view.dart
  12. 110
      local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceAudioPlayer.kt
  13. 2
      local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt
  14. 5
      local_plugins/music_service/lib/music_service.dart
  15. 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) { state == BleConnectionState.error) {
Logger.info('BLE设备已断开或发生错误'); Logger.info('BLE设备已断开或发生错误');
_isConnected.value = false; _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对象 // 使用保存的设备信息创建新的DeviceInfo对象
String deviceAddress = _currentDeviceAddress ?? ""; String deviceAddress = _currentDeviceAddress ?? "";
String deviceName = _currentDeviceName ?? ""; String deviceName = _currentDeviceName ?? "";
Logger.info('创建新的设备信息对象: address=$deviceAddress, name=$deviceName'); Logger.info('创建新的设备信息对象: address=$deviceAddress, name=$deviceName');
deviceInfo.value = DeviceInfo( deviceInfo.value = DeviceInfo(
address: deviceAddress, address: deviceAddress,
@ -442,7 +455,7 @@ class BleManager extends GetxService {
// 创建初始设备信息对象 // 创建初始设备信息对象
String deviceAddress = _currentDeviceAddress ?? ""; String deviceAddress = _currentDeviceAddress ?? "";
String deviceName = _currentDeviceName ?? ""; String deviceName = _currentDeviceName ?? "";
Logger.info('初始化设备信息对象: address=$deviceAddress, name=$deviceName'); Logger.info('初始化设备信息对象: address=$deviceAddress, name=$deviceName');
deviceInfo.value = DeviceInfo( deviceInfo.value = DeviceInfo(
address: deviceAddress, address: deviceAddress,
@ -467,8 +480,9 @@ class BleManager extends GetxService {
_bleService.getProductId(), _bleService.getProductId(),
]); ]);
Logger.info('设备信息获取结果: 电池=${results[0]}, 版本=${results[1]}, 产品ID=${results[2]}'); Logger.info(
'设备信息获取结果: 电池=${results[0]}, 版本=${results[1]}, 产品ID=${results[2]}');
if (!results[0]) { if (!results[0]) {
Logger.warning('电池信息获取指令发送失败'); Logger.warning('电池信息获取指令发送失败');
} }
@ -788,9 +802,10 @@ class BleManager extends GetxService {
// 从蓝牙管理器获取设备名称 // 从蓝牙管理器获取设备名称
final bluetoothManager = Get.find<BluetoothManager>(); final bluetoothManager = Get.find<BluetoothManager>();
_currentDeviceName = bluetoothManager.currentDevice?.name ?? ""; _currentDeviceName = bluetoothManager.currentDevice?.name ?? "";
Logger.info('保存设备信息: address=$_currentDeviceAddress, name=$_currentDeviceName'); Logger.info(
'保存设备信息: address=$_currentDeviceAddress, name=$_currentDeviceName');
} }
return await _bleService.registerBackgroundScan(macAddress: macAddress); return await _bleService.registerBackgroundScan(macAddress: macAddress);
} catch (e) { } catch (e) {
Logger.error('注册后台扫描失败: ${e.toString()}'); Logger.error('注册后台扫描失败: ${e.toString()}');

5
lib/data/services/music_manager.dart

@ -80,4 +80,9 @@ class MusicManager extends GetxService {
Future<void> seekTo(int positionMs) async { Future<void> seekTo(int positionMs) async {
await _musicService.seekTo(positionMs); await _musicService.seekTo(positionMs);
} }
/// 播放音乐列表
Future<void> playPlaylist(List<Map<String, dynamic>> playlist) async {
await _musicService.playMusicPlaylist(playlist);
}
} }

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

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

@ -85,6 +85,16 @@ class AgentController extends GetxController {
// 是否已加载聊天记录 // 是否已加载聊天记录
final isHistoryLoaded = false.obs; final isHistoryLoaded = false.obs;
// 添加加载状态跟踪
final RxBool isLoadingMusic = false.obs;
final RxString loadingMusicId = ''.obs;
// 设置加载状态的方法
void setLoadingMusic(String id, bool loading) {
loadingMusicId.value = loading ? id : '';
isLoadingMusic.value = loading;
}
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@ -938,12 +948,14 @@ class AgentController extends GetxController {
// 恢复音乐播放 // 恢复音乐播放
Future<void> musicResume() async { Future<void> musicResume() async {
try { try {
Logger.d('AgentController', '恢复音乐播放'); Logger.d('AgentController', '恢复播放音乐');
await _musiceManager.resume(); await _musiceManager.resume();
isPlaying.value = true; isPlaying.value = true;
_startProgressTimer(); _startProgressTimer();
Logger.d('AgentController', '音乐恢复播放成功');
} catch (e) { } catch (e) {
Logger.e('AgentController', '恢复音乐失败: $e'); Logger.e('AgentController', '恢复播放音乐失败: $e');
isPlaying.value = false;
} }
} }
@ -1049,4 +1061,68 @@ class AgentController extends GetxController {
), ),
); );
} }
/// 播放音乐列表
Future<void> playPlaylist(List<Map<String, dynamic>> playlist) async {
try {
if (playlist.isEmpty) {
Logger.e(TAG, '播放列表为空,无法播放');
return;
}
Logger.d(TAG, '开始播放音乐列表,共${playlist.length}首歌曲');
// 设置加载状态
isLoadingMusic.value = true;
loadingMusicId.value = playlist[0]['id']?.toString() ?? '';
// 重置播放状态
isPlaying.value = true;
currentPosition.value = 0;
duration.value = 0;
progress.value = 0.0;
// 格式化歌曲列表数据
final formattedPlaylist = playlist.map((song) {
return {
'id': song['id']?.toString() ?? '',
'url': song['url']?.toString() ?? '',
'title': song['title']?.toString() ?? song['name']?.toString() ?? '',
'artist': song['artist']?.toString() ?? song['sgener']?.toString() ?? '',
'coverUrl': song['coverUrl']?.toString() ?? song['image']?.toString() ?? '',
};
}).toList();
// 调用音乐管理器播放列表
await _musiceManager.playPlaylist(formattedPlaylist);
// 更新当前播放歌曲ID
if (formattedPlaylist.isNotEmpty) {
currentId.value = int.tryParse(formattedPlaylist[0]['id'] ?? '0') ?? 0;
}
// 启动进度定时器
_startProgressTimer();
Logger.d(TAG, '音乐列表播放启动成功');
} catch (e) {
Logger.e(TAG, '播放音乐列表失败: $e');
isPlaying.value = false;
currentId.value = 0;
// 显示错误提示
Get.snackbar(
'播放失败',
'无法播放音乐列表,请稍后重试',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red[400],
colorText: Colors.white,
duration: const Duration(seconds: 2),
);
} finally {
// 清除加载状态
isLoadingMusic.value = false;
loadingMusicId.value = '';
}
}
} }

31
lib/modules/meeting/controllers/meeting_controller.dart

@ -182,27 +182,34 @@ class MeetingController extends GetxController {
void addMeetingAudio() async { void addMeetingAudio() async {
EasyLoading.show(status: '正在导入本地录音...'); EasyLoading.show(status: '正在导入本地录音...');
try { try {
int addUp = 0;
final appDir = await appDocDir; final appDir = await appDocDir;
final sourceDir = Directory("${appDir.path}/MeetingAudio/");
// 检查源目录是否存在 // 定义源目录列表
if (!await sourceDir.exists()) { final sourceDirs = [
EasyLoading.dismiss(); Directory("${appDir.path}/MeetingAudio/"),
return; Directory("${appDir.path}/DestAudio/"),
} ];
// 创建目标目录(如果不存在) // 创建目标目录(如果不存在)
final destDir = Directory("${appDir.path}/LocalAudio/"); final destDir = Directory("${appDir.path}/LocalAudio/");
if (!await destDir.exists()) { if (!await destDir.exists()) {
await destDir.create(recursive: true); await destDir.create(recursive: true);
} }
// 获取目录下所有文件 List<FileSystemEntity> allFiles = [];
final files =
await sourceDir.list().where((entity) => entity is File).toList();
int addUp = 0; for (final dir in sourceDirs) {
for (final fileEntity in files) { if (await dir.exists()) {
final files =
await dir.list().where((entity) => entity is File).toList();
allFiles.addAll(files);
}
}
// 此时 allFiles 包含 MeetingAudio 和 DestAudio 目录下所有的文件
for (final fileEntity in allFiles) {
try { try {
final file = File(fileEntity.path); final file = File(fileEntity.path);
final filePath = file.path; final filePath = file.path;

17
lib/modules/meeting/controllers/meeting_record_controller.dart

@ -335,10 +335,19 @@ class MeetingRecordController extends GetxController
final appDir = await appDocDir; final appDir = await appDocDir;
final dir = Directory("${appDir.path}/MeetingAudio/"); final dir = Directory("${appDir.path}/MeetingAudio/");
if (!await dir.exists()) await dir.create(recursive: true); if (!await dir.exists()) await dir.create(recursive: true);
await _asrService.renameFile( if (isMarked.value) {
"${dir.path}/${fileName.value}.wav", final Newdir = Directory("${appDir.path}/DestAudio/");
newName.value, if (!await Newdir.exists()) await Newdir.create(recursive: true);
); await _asrService.moveFile(
"${dir.path}/${fileName.value}.wav",
"${Newdir.path}/${fileName.value}.wav",
);
} else {
await _asrService.renameFile(
"${dir.path}/${fileName.value}.wav",
newName.value,
);
}
} }
await _asrService.stopContinuousRecognition(); await _asrService.stopContinuousRecognition();

8
lib/modules/music/views/music_playlist_view.dart

@ -14,7 +14,11 @@ class MusicPlaylistView extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('音乐播放列表'), leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, size: 20), // iOS风格箭头
onPressed: () => Navigator.pop(context),
),
//title: const Text('音乐播放列表'),
backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white, backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white,
foregroundColor: isDarkMode ? Colors.white : Colors.black, foregroundColor: isDarkMode ? Colors.white : Colors.black,
elevation: 0, elevation: 0,
@ -162,7 +166,7 @@ class MusicPlaylistView extends StatelessWidget {
// 播放按钮 // 播放按钮
Obx(() { Obx(() {
final isThisPlaying = final isThisPlaying =
controller.isPlaying.value && controller.currentId.value == url; controller.isPlaying.value && controller.currentId.value == id;
// 只有当前播放的歌曲才显示进度 // 只有当前播放的歌曲才显示进度
final currentProgress = final currentProgress =

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

@ -327,7 +327,7 @@ class OtaUpgradeController extends GetxController {
} }
if (existingSameFile) { if (existingSameFile) {
Get.snackbar('文件已存在', '跳过下载,直接更新'); //Get.snackbar('文件已存在', '跳过下载,直接更新');
startUpgrading(savePath); startUpgrading(savePath);
return; return;
@ -367,7 +367,7 @@ class OtaUpgradeController extends GetxController {
); );
File file = File(savePath); File file = File(savePath);
if (await file.exists()) { if (await file.exists()) {
Get.snackbar('下载完成', '文件已保存至: $savePath'); // Get.snackbar('下载完成', '文件已保存至: $savePath');
} else { } else {
Get.snackbar('错误', '文件未找到,请检查写入权限'); 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.white.withOpacity(0.1)
: Colors.grey[200]), : Colors.grey[200]),
// 杰理测试 // // 杰理测试
_buildSimpleNavigationSetting( // _buildSimpleNavigationSetting(
title: '杰理蓝牙测试', // title: '杰理蓝牙测试',
subtitle: '测试杰理蓝牙设备扫描、连接和功能', // subtitle: '测试杰理蓝牙设备扫描、连接和功能',
icon: Icons.bluetooth, // icon: Icons.bluetooth,
iconBgColor: isDarkMode // iconBgColor: isDarkMode
? Colors.blue[900]!.withOpacity(0.3) // ? Colors.blue[900]!.withOpacity(0.3)
: Colors.blue[100]!, // : Colors.blue[100]!,
iconColor: // iconColor:
isDarkMode ? Colors.blue[300]! : Colors.blue[600]!, // isDarkMode ? Colors.blue[300]! : Colors.blue[600]!,
titleColor: isDarkMode ? Colors.white : null, // titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null, // subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () { // onTap: () {
Get.toNamed(Routes.jieliTest); // Get.toNamed(Routes.jieliTest);
}, // },
isDarkMode: isDarkMode, // isDarkMode: isDarkMode,
), // ),
Divider( // Divider(
height: 1, // height: 1,
color: isDarkMode // color: isDarkMode
? Colors.white.withOpacity(0.1) // ? Colors.white.withOpacity(0.1)
: Colors.grey[200]), // : Colors.grey[200]),
// 面对面翻译测试 // // 面对面翻译测试
_buildSimpleNavigationSetting( // _buildSimpleNavigationSetting(
title: '面对面翻译测试', // title: '面对面翻译测试',
subtitle: '测试面对面翻译', // subtitle: '测试面对面翻译',
icon: Icons.bluetooth_searching, // icon: Icons.bluetooth_searching,
iconBgColor: isDarkMode // iconBgColor: isDarkMode
? Colors.green[900]!.withOpacity(0.3) // ? Colors.green[900]!.withOpacity(0.3)
: Colors.green[100]!, // : Colors.green[100]!,
iconColor: // iconColor:
isDarkMode ? Colors.green[300]! : Colors.green[600]!, // isDarkMode ? Colors.green[300]! : Colors.green[600]!,
titleColor: isDarkMode ? Colors.white : null, // titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null, // subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () { // onTap: () {
Get.toNamed(Routes.ftftranslation); // Get.toNamed(Routes.ftftranslation);
}, // },
isDarkMode: isDarkMode, // isDarkMode: isDarkMode,
), // ),
Divider( // Divider(
height: 1, // height: 1,
color: isDarkMode // color: isDarkMode
? Colors.white.withOpacity(0.1) // ? Colors.white.withOpacity(0.1)
: Colors.grey[200]), // : Colors.grey[200]),
// BLE测试 // // BLE测试
_buildSimpleNavigationSetting( // _buildSimpleNavigationSetting(
title: 'opus解码测试', // title: 'opus解码测试',
subtitle: '测试opus解码', // subtitle: '测试opus解码',
icon: Icons.bluetooth_searching, // icon: Icons.bluetooth_searching,
iconBgColor: isDarkMode // iconBgColor: isDarkMode
? Colors.green[900]!.withOpacity(0.3) // ? Colors.green[900]!.withOpacity(0.3)
: Colors.green[100]!, // : Colors.green[100]!,
iconColor: // iconColor:
isDarkMode ? Colors.green[300]! : Colors.green[600]!, // isDarkMode ? Colors.green[300]! : Colors.green[600]!,
titleColor: isDarkMode ? Colors.white : null, // titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null, // subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () { // onTap: () {
Get.toNamed(Routes.opusTest); // Get.toNamed(Routes.opusTest);
}, // },
isDarkMode: isDarkMode, // isDarkMode: isDarkMode,
), // ),
Divider( // Divider(
height: 1, // height: 1,
color: isDarkMode // color: isDarkMode
? Colors.white.withOpacity(0.1) // ? Colors.white.withOpacity(0.1)
: Colors.grey[200]), // : Colors.grey[200]),
// BLE测试 // BLE测试
_buildSimpleNavigationSetting( // _buildSimpleNavigationSetting(
title: 'BLE蓝牙测试', // title: 'BLE蓝牙测试',
subtitle: '测试BLE蓝牙设备扫描、连接和功能', // subtitle: '测试BLE蓝牙设备扫描、连接和功能',
icon: Icons.bluetooth_searching, // icon: Icons.bluetooth_searching,
iconBgColor: isDarkMode // iconBgColor: isDarkMode
? Colors.green[900]!.withOpacity(0.3) // ? Colors.green[900]!.withOpacity(0.3)
: Colors.green[100]!, // : Colors.green[100]!,
iconColor: // iconColor:
isDarkMode ? Colors.green[300]! : Colors.green[600]!, // isDarkMode ? Colors.green[300]! : Colors.green[600]!,
titleColor: isDarkMode ? Colors.white : null, // titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null, // subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () { // onTap: () {
Get.toNamed(Routes.bleTest); // Get.toNamed(Routes.bleTest);
}, // },
isDarkMode: isDarkMode, // isDarkMode: isDarkMode,
), // ),
Divider( // Divider(
height: 1, // height: 1,
color: isDarkMode // color: isDarkMode
? Colors.white.withOpacity(0.1) // ? Colors.white.withOpacity(0.1)
: Colors.grey[200]), // : Colors.grey[200]),
// 火山语音测试 // // 火山语音测试
_buildSimpleNavigationSetting( // _buildSimpleNavigationSetting(
title: '语音测试', // title: '语音测试',
subtitle: '测试语音识别和合成功能,支持多种服务实现', // subtitle: '测试语音识别和合成功能,支持多种服务实现',
icon: Icons.record_voice_over, // icon: Icons.record_voice_over,
iconBgColor: isDarkMode // iconBgColor: isDarkMode
? Colors.orange[900]!.withOpacity(0.3) // ? Colors.orange[900]!.withOpacity(0.3)
: Colors.orange[100]!, // : Colors.orange[100]!,
iconColor: // iconColor:
isDarkMode ? Colors.orange[300]! : Colors.orange[600]!, // isDarkMode ? Colors.orange[300]! : Colors.orange[600]!,
onTap: () => Get.toNamed(Routes.speechTest), // onTap: () => Get.toNamed(Routes.speechTest),
isDarkMode: isDarkMode, // isDarkMode: isDarkMode,
), // ),
Divider( // Divider(
height: 1, // height: 1,
color: isDarkMode // color: isDarkMode
? Colors.white.withOpacity(0.1) // ? Colors.white.withOpacity(0.1)
: Colors.grey[200]), // : Colors.grey[200]),
// 测试ble耳机固件版本升级 // 测试ble耳机固件版本升级
_buildSimpleNavigationSetting( _buildSimpleNavigationSetting(
title: 'ota升级', title: 'ota升级',

62
lib/modules/translation/controllers/translation_controller.dart

@ -36,6 +36,11 @@ class TranslationController extends GetxController {
final bleManager = Get.find<BleManager>(); final bleManager = Get.find<BleManager>();
// 音频输入源 // 音频输入源
bool _audioSourceType = false; bool _audioSourceType = false;
// 倒计时器
final RxString recordDurationText = '00:00'.obs;
Timer? _recordTimer;
int _seconds = 0;
// 存储相关 // 存储相关
static const String _historyKey = 'translation_history'; static const String _historyKey = 'translation_history';
static const String _sourceLanguageKey = 'translation_source_language'; static const String _sourceLanguageKey = 'translation_source_language';
@ -198,7 +203,7 @@ class TranslationController extends GetxController {
return true; return true;
} else { } else {
Logger.d("Permission", "用户未授予存储权限"); Logger.d("Permission", "用户未授予存储权限");
return false; return true;
} }
} catch (e) { } catch (e) {
Logger.e("Permission", "请求存储权限时出错: $e"); Logger.e("Permission", "请求存储权限时出错: $e");
@ -267,16 +272,45 @@ class TranslationController extends GetxController {
Future<void> toggleisRecord() async { Future<void> toggleisRecord() async {
isRecording.toggle(); isRecording.toggle();
if (isRecording.value && isRecognizing.value) { if (isRecording.value && isRecognizing.value) {
// 使用可读性更好的时间格式 startRecording();
final formattedTime =
DateFormat('yyyyMMdd_HHmmss').format(DateTime.now());
await _asrService.enableRecord(
"${dir.path}/${currentModeTitle.value.tr}_$formattedTime.wav");
} else if (!isRecording.value) { } else if (!isRecording.value) {
await _asrService.stopRecord(true); stopRecording();
} else {} } else {}
} }
Future<void> startRecording() async {
final bool storage = await _requestStoragePermission();
if (!storage) {
Logger.d("Permission", "未授予存储权限,无法提供录音功能");
return;
}
//isRecording.value = true;
_seconds = 0;
recordDurationText.value = '00:00';
_recordTimer = Timer.periodic(Duration(seconds: 1), (_) {
_seconds++;
final minutes = (_seconds ~/ 60).toString().padLeft(2, '0');
final seconds = (_seconds % 60).toString().padLeft(2, '0');
recordDurationText.value = '$minutes:$seconds';
});
// 启动实际录音逻辑
// ...
final formattedTime = DateFormat('yyyyMMdd_HHmmss').format(DateTime.now());
await _asrService.enableRecord(
"${dir.path}/${currentModeTitle.value.tr}_$formattedTime.wav");
}
Future<void> stopRecording() async {
_recordTimer?.cancel();
_recordTimer = null;
isRecording.value = false;
recordDurationText.value = '00:00';
await _asrService.stopRecord(true);
// 停止实际录音逻辑
// ...
}
// 开始语音识别 // 开始语音识别
Future<void> startRecognition() async { Future<void> startRecognition() async {
Logger.info('开始语音识别'); Logger.info('开始语音识别');
@ -343,15 +377,7 @@ class TranslationController extends GetxController {
await _asrService.startContinuousRecognition(_audioSourceType); await _asrService.startContinuousRecognition(_audioSourceType);
isRecognizing.value = true; isRecognizing.value = true;
if (isRecording.value && isRecognizing.value) { if (isRecording.value && isRecognizing.value) {
final bool storage = await _requestStoragePermission(); startRecording();
if (!storage) {
Logger.d("Permission", "未授予存储权限,无法提供录音功能");
return;
}
final formattedTime =
DateFormat('yyyyMMdd_HHmmss').format(DateTime.now());
await _asrService.enableRecord(
"${dir.path}/${currentModeTitle.value.tr}_$formattedTime.wav");
} }
// 监听识别结果 // 监听识别结果
_recognitionSubscription?.cancel(); _recognitionSubscription?.cancel();
@ -411,7 +437,7 @@ class TranslationController extends GetxController {
saveTranslationHistory(); saveTranslationHistory();
} }
stopRecording();
currentSessionId = null; currentSessionId = null;
} catch (e) { } catch (e) {
Logger.error('停止语音识别失败: ${e.toString()}'); Logger.error('停止语音识别失败: ${e.toString()}');
@ -607,7 +633,7 @@ class TranslationController extends GetxController {
if (isRecognizing.value) { if (isRecognizing.value) {
await stopRecognition(); await stopRecognition();
} }
await _asrService.stopRecord(true); stopRecording();
_translationDebounceTimer?.cancel(); _translationDebounceTimer?.cancel();
await _ttsService.stop(); await _ttsService.stop();
} catch (e) { } catch (e) {

95
lib/modules/translation/views/translation_view.dart

@ -107,37 +107,70 @@ class TranslationView extends GetView<TranslationController> {
)), )),
centerTitle: true, centerTitle: true,
actions: [ actions: [
// TTS toggle button Obx(() {
Obx(() => IconButton( if (controller.isRecording.value) {
icon: Icon( // 录音中:显示红色计时器样式,点击可停止录音
controller.isRecording.value return Padding(
? Icons.mic padding: EdgeInsets.only(right: 16.w),
: Icons.fiber_manual_record, child: GestureDetector(
color: isDarkMode ? Colors.white : Colors.black, onTap: controller.toggleisRecord, // 点击结束录音
size: 24.sp, child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(20.r),
),
child: Row(
children: [
Icon(
Icons.fiber_manual_record,
color: Colors.white,
size: 18.sp,
),
SizedBox(width: 4.w),
Text(
controller.recordDurationText.value,
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
fontWeight: FontWeight.bold,
),
),
],
),
),
), ),
// onPressed: controller.toggleTts,默认播放 );
onPressed: controller.toggleisRecord, } else {
tooltip: '是否开启录音', // 未录音:显示红色录音按钮 + 文件夹按钮
)), return Row(
children: [
// 历史记录按钮 IconButton(
IconButton( icon: Icon(
icon: Icon( Icons.radio_button_checked,
Icons.folder, color: Colors.red,
color: isDarkMode ? Colors.white : Colors.black, size: 24.sp,
size: 24.sp, ),
), onPressed: controller.toggleisRecord,
onPressed: () async { tooltip: '是否开启录音',
// 停止语音识别和TTS,释放资源 ),
await controller.stopAll(); IconButton(
Get.toNamed(Routes.translationList); icon: Icon(
Icons.folder,
// 返回时不自动开始翻译,只需重新设置滚动监听器 color: isDarkMode ? Colors.white : Colors.black,
controller.setupScrollListener(); size: 24.sp,
}, ),
tooltip: '查看录音文件', onPressed: () async {
), await controller.stopAll();
Get.toNamed(Routes.translationList);
controller.setupScrollListener();
},
tooltip: '查看录音文件',
),
],
);
}
}),
], ],
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: Size.fromHeight(50.h), preferredSize: Size.fromHeight(50.h),
@ -328,7 +361,7 @@ class TranslationView extends GetView<TranslationController> {
onTap: () => controller.playTranslationItem(item), onTap: () => controller.playTranslationItem(item),
child: Icon( child: Icon(
Icons.volume_up, Icons.volume_up,
size: 18, size: 20,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
), ),

110
local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceAudioPlayer.kt

@ -15,12 +15,13 @@ import com.deep_voice.speech.tts.AudioDataListener
import com.deep_voice.speech.tts.AudioOutputDevice import com.deep_voice.speech.tts.AudioOutputDevice
/** /**
* 高性能音频播放器 - 16kHz采样率 * 语音通信音频播放器 - 16kHz采样率
* 设计要点: * 设计要点:
* • 永远使用 USAGE_MEDIA + MODE_NORMAL 媒体播放通路 * • 使用 USAGE_VOICE_COMMUNICATION + MODE_IN_COMMUNICATION 通信通路
* • 利用 AudioDeviceCallback 监听设备热插拔 * • 利用 AudioDeviceCallback 监听设备热插拔
* • API 31+ 使用 setPreferredDevice() 精准路由 * • API 31+ 使用 setPreferredDevice() 精准路由
* • 有耳机自动从耳机播放,无耳机自动切到扬声器 * • 有耳机自动从耳机播放,无耳机自动切到扬声器
* • 内置回声消除和噪音抑制功能
*/ */
class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, AudioDeviceCallback() { class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, AudioDeviceCallback() {
companion object { companion object {
@ -35,6 +36,35 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, Au
private var isFirstData = true private var isFirstData = true
private var totalBytesWritten = 0 private var totalBytesWritten = 0
private var sessionActive = true private var sessionActive = true
private var audioFocusRequest: Any? = null // 兼容不同API版本
// 音频焦点变化监听器
private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
when (focusChange) {
AudioManager.AUDIOFOCUS_LOSS -> {
// 永久失去焦点,停止播放
Log.d(TAG, "音频焦点丢失")
stop()
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
// 暂时失去焦点,暂停播放
Log.d(TAG, "音频焦点暂时丢失")
audioTrack?.pause()
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
// 暂时失去焦点但可降低音量,这里选择暂停
Log.d(TAG, "音频焦点暂时丢失(可降低音量)")
audioTrack?.pause()
}
AudioManager.AUDIOFOCUS_GAIN -> {
// 重新获得焦点,恢复播放
Log.d(TAG, "音频焦点恢复")
if (sessionActive) {
audioTrack?.play()
}
}
}
}
// 回调 // 回调
private var onPlayStarted: (() -> Unit)? = null private var onPlayStarted: (() -> Unit)? = null
@ -62,6 +92,8 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, Au
init { init {
// 注册设备热插拔监听 // 注册设备热插拔监听
audioManager.registerAudioDeviceCallback(this, Handler(Looper.getMainLooper())) audioManager.registerAudioDeviceCallback(this, Handler(Looper.getMainLooper()))
// 请求音频焦点
requestAudioFocus()
buildTrack(selectOutputDevice()) // 首次创建 buildTrack(selectOutputDevice()) // 首次创建
} }
@ -126,7 +158,7 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, Au
audioTrack = AudioTrack.Builder() audioTrack = AudioTrack.Builder()
.setAudioAttributes( .setAudioAttributes(
AudioAttributes.Builder() AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA) // 统一媒体通路 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) // 语音通信通路
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build() .build()
) )
@ -159,8 +191,8 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, Au
track.play() track.play()
} }
// 配置音频管理器 - 统一使用 MODE_NORMAL // 配置音频管理器 - 使用通信模式以启用回声消除和噪音抑制
audioManager.mode = AudioManager.MODE_NORMAL audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
// 切换扬声器开关仅在无耳机时打开 // 切换扬声器开关仅在无耳机时打开
audioManager.isSpeakerphoneOn = (device == null) audioManager.isSpeakerphoneOn = (device == null)
@ -168,6 +200,71 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, Au
Log.d(TAG, "AudioTrack已创建 - 输出设备: $deviceName, 采样率: ${SAMPLE_RATE}Hz") Log.d(TAG, "AudioTrack已创建 - 输出设备: $deviceName, 采样率: ${SAMPLE_RATE}Hz")
} }
/**
* 请求音频焦点
*/
private fun requestAudioFocus() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Android O及以上使用新的API
val focusRequest = android.media.AudioFocusRequest.Builder(
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(audioFocusChangeListener)
.build()
val result = audioManager.requestAudioFocus(focusRequest)
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
audioFocusRequest = focusRequest
Log.d(TAG, "音频焦点请求成功")
} else {
Log.w(TAG, "音频焦点请求失败")
}
} else {
// Android O以下使用旧的API
@Suppress("DEPRECATION")
val result = audioManager.requestAudioFocus(
audioFocusChangeListener,
AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
)
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(TAG, "音频焦点请求成功(旧API)")
} else {
Log.w(TAG, "音频焦点请求失败(旧API)")
}
}
} catch (e: Exception) {
Log.e(TAG, "请求音频焦点异常: ${e.message}")
}
}
/**
* 释放音频焦点
*/
private fun releaseAudioFocus() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
audioFocusRequest?.let {
audioManager.abandonAudioFocusRequest(it as android.media.AudioFocusRequest)
}
} else {
@Suppress("DEPRECATION")
audioManager.abandonAudioFocus(audioFocusChangeListener)
}
Log.d(TAG, "音频焦点已释放")
} catch (e: Exception) {
Log.e(TAG, "释放音频焦点异常: ${e.message}")
}
}
/** /**
* 开始新的播放会话 * 开始新的播放会话
*/ */
@ -262,7 +359,10 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, Au
stop() stop()
audioTrack?.release() audioTrack?.release()
audioTrack = null audioTrack = null
releaseAudioFocus()
audioManager.unregisterAudioDeviceCallback(this) audioManager.unregisterAudioDeviceCallback(this)
// 恢复音频模式
audioManager.mode = AudioManager.MODE_NORMAL
} }
/** /**

2
local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt

@ -116,7 +116,7 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope {
// TTS配置参数 // TTS配置参数
private var appId: String = "" private var appId: String = ""
private var accessToken: String = "" private var accessToken: String = ""
private var speaker: String = "zh_female_shuangkuaisisi_emo_v2_mars_bigtts" // 默认语音,使用Java示例的音色 private var speaker: String = "zh_female_yuanqinvyou_moon_bigtts" // 默认语音,使用Java示例的音色
private var format: String = "pcm" // pcm 格式 private var format: String = "pcm" // pcm 格式
private var sampleRate: Int = 16000 // 使用16kHz采样率 private var sampleRate: Int = 16000 // 使用16kHz采样率
private var enableTimestamp: Boolean = false private var enableTimestamp: Boolean = false

5
local_plugins/music_service/lib/music_service.dart

@ -74,4 +74,9 @@ class MusicService {
Future<void> seekTo(int positionMs) async { Future<void> seekTo(int positionMs) async {
await _methodChannel.invokeMethod('seekTo', {'position': positionMs}); await _methodChannel.invokeMethod('seekTo', {'position': positionMs});
} }
/// 播放音乐列表
Future<void> playMusicPlaylist(List<Map<String, dynamic>> songs) async {
await _methodChannel.invokeMethod('playlist', {'songs': songs});
}
} }

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

Loading…
Cancel
Save