Browse Source

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

weicu
liwei1dao 1 year ago
parent
commit
101511aac2
  1. 13
      lib/data/services/ble_manager.dart
  2. 245
      lib/modules/ota_upgrade/controllers/ota_core_contropller.dart
  3. 8
      lib/modules/ota_upgrade/controllers/ota_download_contropller.dart
  4. 57
      lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
  5. 99
      lib/modules/ota_upgrade/services/device_checker.dart
  6. 43
      lib/modules/ota_upgrade/services/ota_status_manager.dart
  7. 114
      lib/modules/ota_upgrade/services/version_manager.dart
  8. 9
      lib/modules/ota_upgrade/views/ota_upgrade_popup_view.dart
  9. 39
      lib/modules/translation/controllers/translation_controller.dart
  10. 187
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrToAsr.kt
  11. 1
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  12. 4
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt
  13. 933
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/IntegratedSpeechTranslationService.swift
  14. 510
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/VolcanoTranslationServiceImpl.swift
  15. 21
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

13
lib/data/services/ble_manager.dart

@ -123,8 +123,10 @@ class BleManager extends GetxService {
RxInt get connectionStateRx => _connectionState;
// 连接状态监听
final _isCall = false.obs;
final _isConnected = false.obs;
bool get isConnected => _isConnected.value;
bool get isCall => _isCall.value;
// 设备信息缓存
final deviceInfo = Rx<DeviceInfo?>(null); // 使用 Rx 包装
@ -262,8 +264,13 @@ class BleManager extends GetxService {
case BleCommands.GET_BATTERY_INFO:
_updateBatteryInfoFromData(data);
break;
case BleCommands.CONTROL_CODEC:
// 处理通话状态
print(" CONTROL_CODEC: $commandId,$data");
//_isCall.value = data['callStatus'] as bool? ?? false;
break;
default:
print(" 未处理的命令ID: $commandId,$data");
Logger.info('未处理的命令ID: $commandId');
}
}
@ -283,7 +290,11 @@ class BleManager extends GetxService {
break;
case 'productColor':
break;
case 'Error':
print(" Error: $commandName,$data");
break;
default:
print(" 未处理的命令名称: $commandName,$data");
Logger.info('未处理的命令名称: $commandName');
}
}

245
lib/modules/ota_upgrade/controllers/ota_core_contropller.dart

@ -0,0 +1,245 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:logger/web.dart';
import '../../../data/services/ble_manager.dart';
import '../../../data/models/appconfig.dart';
import '../../../data/models/appconfig_model.dart';
/// OTA核心服务
/// 整合设备检查、版本管理和状态管理功能
class OtaCoreService {
final BleManager _bleManager = Get.find<BleManager>();
final int maxRetryAttempts = 3;
int currentRetryCount = 0;
// ==================== 状态常量定义 ====================
static const int NOT_STARTED = -1; // 未开始
static const int VALIDATION_FILE = 0; // 验证文件
static const int ONUPGRADE = 1; // 升级中
static const int START_UPGRADING = 2; // 开始升级
static const int UPGRADE_SUCCESS = 3; // 升级成功
static const int UPGRADE_FAILED = 4; // 升级失败
static const int CONNECTION_DISCONNECTION = 5; // 连接断开
static const int CONNECTION_SUCCESS = 6; // 连接成功
static const int DOWNLOADING_UP_FILES = 7; // 下载升级文件
/// 获取状态描述信息
/// [type] 状态类型
/// 返回对应的状态描述文本
static String getStatusMessage(int type) {
switch (type) {
case NOT_STARTED:
return "未开始".tr;
case VALIDATION_FILE:
return "验证文件中".tr;
case ONUPGRADE:
return "升级中".tr;
case START_UPGRADING:
return "开始升级".tr;
case UPGRADE_SUCCESS:
return "升级成功".tr;
case UPGRADE_FAILED:
return "升级失败".tr;
case CONNECTION_DISCONNECTION:
return "连接断开".tr;
case CONNECTION_SUCCESS:
return "连接成功".tr;
case DOWNLOADING_UP_FILES:
return "下载升级文件".tr;
default:
return "未知状态".tr;
}
}
// ==================== 设备检查功能 ====================
/// 检查设备电量是否满足升级条件
/// 返回true表示电量充足,可以进行升级
/// 返回false表示电量不足,不能进行升级
bool checkBatteryLevel() {
try {
final deviceInfo = _bleManager.deviceInfo.value;
if (deviceInfo == null) {
_showBatteryCheckFailedSnackbar('无法获取设备信息,请检查设备连接');
return false;
}
final leftBattery = deviceInfo.leftEarBattery;
final rightBattery = deviceInfo.rightEarBattery;
// 检查左右耳电量是否都大于30%
if (leftBattery <= 30 || rightBattery <= 30) {
_showLowBatteryDialog(leftBattery, rightBattery);
return false;
}
return true;
} catch (e) {
_showBatteryCheckFailedSnackbar('检查设备电量时发生错误:$e');
return false;
}
}
/// 显示电量不足对话框
/// [leftBattery] 左耳电量
/// [rightBattery] 右耳电量
void _showLowBatteryDialog(int leftBattery, int rightBattery) {
String message = '设备电量不足,无法进行固件升级\n';
message += '升级要求:左右耳电量均需大于30%\n';
message += '当前电量:左耳 ${leftBattery}%,右耳 ${rightBattery}%\n';
message += '请充电后再试';
Get.dialog(
AlertDialog(
title: Text(
'电量不足',
style: TextStyle(
color: Colors.orange[700],
fontWeight: FontWeight.w600,
),
),
content: Text(
message,
style: TextStyle(
color: Colors.orange[600],
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
style: TextButton.styleFrom(
foregroundColor: Colors.orange[700],
),
child: const Text('确定'),
),
],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: Colors.orange.withValues(alpha: 0.3),
width: 1,
),
),
backgroundColor: Colors.orange[50],
),
);
}
/// 显示电量检查失败提示
/// [message] 错误信息
void _showBatteryCheckFailedSnackbar(String message) {
Get.snackbar(
'电量检查失败',
message,
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.orange[600],
colorText: Colors.white,
icon: const Icon(
Icons.warning_amber,
color: Colors.white,
),
);
}
// ==================== 版本管理功能 ====================
/// 获取当前设备版本信息
/// 返回包含当前版本、产品ID和设备信息的Map
/// 抛出异常如果获取失败
Future<Map<String, dynamic>> getCurrentVersionInfo() async {
DeviceInfo? deviceInfo = _bleManager.deviceInfo.value;
// 重试机制获取设备信息
while (currentRetryCount < maxRetryAttempts) {
deviceInfo = _bleManager.deviceInfo.value;
if (deviceInfo != null &&
deviceInfo.firmwareVersion.isNotEmpty &&
deviceInfo.firmwareProductId.isNotEmpty) {
break;
}
await _bleManager.fetchDeviceInfo();
await Future.delayed(const Duration(seconds: 5));
currentRetryCount++;
}
if (deviceInfo == null ||
deviceInfo.firmwareVersion.isEmpty ||
deviceInfo.firmwareProductId.isEmpty) {
throw "获取设备信息失败,请检查连接";
}
// 验证固件版本格式
final firmwareParts = deviceInfo.firmwareVersion.split('.');
final firstPart = firmwareParts.sublist(0, 3).join('.');
final secondPart = firmwareParts.sublist(3).join('.');
Logger().d("firstPart: $firstPart, secondPart: $secondPart");
if (firstPart != secondPart) {
throw "固件版本格式错误:${deviceInfo.firmwareVersion}";
}
return {
'currentVersion': firstPart,
'productId': deviceInfo.firmwareProductId,
'deviceInfo': deviceInfo,
};
}
/// 获取最新版本信息
/// 返回包含最新版本、更新描述和更新包地址的Map
/// 抛出异常如果获取失败
Future<Map<String, dynamic>> getLatestVersionInfo() async {
DBProduct? product = AppConfig.getproduct("DeapSound");
if (product == null) {
throw "无法获取产品信息";
}
return {
'latestVersion': product.version ?? "",
'updateDescription': product.updatedescription ?? "",
'updatePackageAddress': product.updatepackageaddress ?? "",
};
}
/// 比较版本号,判断是否有新版本可用
/// [currentVersion] 当前版本号
/// [latestVersion] 最新版本号
/// 返回true表示有新版本可用(支持升级和降级)
bool isNewVersionAvailable(String currentVersion, String latestVersion) {
try {
List<int> currentParts =
currentVersion.split('.').map((e) => int.parse(e.trim())).toList();
List<int> latestParts =
latestVersion.split('.').map((e) => int.parse(e.trim())).toList();
// 确保两个版本号长度一致,短的用0补齐
int maxLength = currentParts.length > latestParts.length
? currentParts.length
: latestParts.length;
while (currentParts.length < maxLength) {
currentParts.add(0);
}
while (latestParts.length < maxLength) {
latestParts.add(0);
}
// 比较每一段,只要有不同就返回true(支持升级和降级)
for (int i = 0; i < maxLength; i++) {
if (latestParts[i] != currentParts[i]) {
return true;
}
}
return false;
} catch (e) {
Logger().d('版本比较错误: $e');
return false;
}
}
}

8
lib/modules/ota_upgrade/services/download_manager.dart → lib/modules/ota_upgrade/controllers/ota_download_contropller.dart

@ -5,14 +5,14 @@ import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';
import 'package:logger/web.dart';
/// 文件下载管理器
/// OTA下载服务
/// 负责处理固件包的下载、文件管理等功能
class DownloadManager {
class OtaDownloadService {
late final Dio _dio;
/// 构造函数
/// 初始化Dio实例并配置SSL证书处理
DownloadManager() {
OtaDownloadService() {
_dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 30), // 增加连接超时时间
receiveTimeout: Duration.zero,
@ -150,4 +150,4 @@ class DownloadManager {
throw Exception('删除旧文件失败: $e');
}
}
}
}

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

@ -4,20 +4,17 @@ import 'package:get/get.dart';
import 'package:logger/web.dart';
import 'package:ota/ota.dart';
import '../../../data/services/ble_manager.dart';
import '../services/version_manager.dart';
import '../services/download_manager.dart';
import '../services/device_checker.dart';
import '../services/ui_manager.dart';
import '../services/ota_status_manager.dart';
import 'ota_core_contropller.dart';
import 'ota_download_contropller.dart';
import '../views/ota_upgrade_popup_view.dart';
/// OTA升级主控制器 - 重构后的简化版本
/// 负责协调各个服务,管理升级流程的整体状态
class OtaUpgradeController extends GetxController {
// 服务依赖
final _versionManager = VersionManager();
final _downloadManager = DownloadManager();
final _deviceChecker = DeviceChecker();
final _uiManager = UiManager();
// 服务依赖 - 使用新的合并后的服务
final _coreService = OtaCoreService();
final _downloadService = OtaDownloadService();
final _uiService = OtaUpgradePopUp();
final _bleManager = Get.find<BleManager>();
final _jlota = Ota.instance;
@ -26,10 +23,10 @@ class OtaUpgradeController extends GetxController {
final RxString latestVersion = ''.obs;
final RxString currentVersion = ''.obs;
final RxString detailedStatus = ''.obs;
final RxString updatedescription = ''.obs; // 添加缺失的更新描述属性
final RxString updatedescription = ''.obs;
final RxDouble downloadProgress = 0.0.obs;
final RxDouble progress = 0.0.obs;
final RxInt type = OtaStatusManager.NOT_STARTED.obs;
final RxInt type = OtaCoreService.NOT_STARTED.obs;
final RxBool isUpdateAvailable = true.obs;
final RxBool isDownloading = false.obs;
final RxBool isUpgrade = false.obs;
@ -68,7 +65,7 @@ class OtaUpgradeController extends GetxController {
errorMessage.value = '';
// 获取当前版本信息
final currentInfo = await _versionManager.getCurrentVersionInfo();
final currentInfo = await _coreService.getCurrentVersionInfo();
currentVersion.value = currentInfo['currentVersion'];
// 获取蓝牙地址
@ -78,7 +75,7 @@ class OtaUpgradeController extends GetxController {
}
// 获取最新版本信息
final latestInfo = await _versionManager.getLatestVersionInfo();
final latestInfo = await _coreService.getLatestVersionInfo();
latestVersion.value = latestInfo['latestVersion'];
_updatePackageAddress = latestInfo['updatePackageAddress'];
@ -87,7 +84,7 @@ class OtaUpgradeController extends GetxController {
latestInfo['updateDescription'] ?? '本次更新包含性能优化和错误修复。';
// 检查是否有更新
isUpdateAvailable.value = _versionManager.isNewVersionAvailable(
isUpdateAvailable.value = _coreService.isNewVersionAvailable(
currentVersion.value,
latestVersion.value,
);
@ -103,13 +100,13 @@ class OtaUpgradeController extends GetxController {
Future<void> checkUpdate() async {
try {
if (latestVersion.value.isNotEmpty) {
_uiManager.showUpdateDialog(latestVersion.value, _startUpdateProcess);
_uiService.showUpdateDialog(latestVersion.value, _startUpdateProcess);
} else {
_uiManager.showErrorSnackbar('错误', '获取最新版本信息失败',
_uiService.showErrorSnackbar('错误', '获取最新版本信息失败',
backgroundColor: Colors.orange[800]);
}
} catch (e) {
_uiManager.showErrorSnackbar('错误', '获取版本信息失败',
_uiService.showErrorSnackbar('错误', '获取版本信息失败',
backgroundColor: Colors.orange[800]);
}
}
@ -123,7 +120,7 @@ class OtaUpgradeController extends GetxController {
/// 检查设备状态后开始下载和升级
Future<void> _startUpdateProcess() async {
// 检查设备电量
if (!_deviceChecker.checkBatteryLevel()) {
if (!_coreService.checkBatteryLevel()) {
return;
}
@ -140,9 +137,9 @@ class OtaUpgradeController extends GetxController {
isDownloading.value = true;
showDetailedStatus.value = true;
type.value = OtaStatusManager.DOWNLOADING_UP_FILES;
type.value = OtaCoreService.DOWNLOADING_UP_FILES;
final filePath = await _downloadManager.downloadFirmware(
final filePath = await _downloadService.downloadFirmware(
downloadUrl: _updatePackageAddress,
onProgress: (progress) {
downloadProgress.value = progress;
@ -190,7 +187,7 @@ class OtaUpgradeController extends GetxController {
/// [type] 状态类型
/// 返回对应的状态描述文本
String getStatusMessage(int type) {
return OtaStatusManager.getStatusMessage(type);
return OtaCoreService.getStatusMessage(type);
}
/// 监听OTA事件
@ -216,7 +213,7 @@ class OtaUpgradeController extends GetxController {
}
break;
case 'onStartOTA':
type.value = OtaStatusManager.START_UPGRADING;
type.value = OtaCoreService.START_UPGRADING;
isUpgrade.value = true;
detailedStatus.value = 'OTA升级已开始...';
break;
@ -238,7 +235,7 @@ class OtaUpgradeController extends GetxController {
/// 处理升级成功
/// 更新状态并显示成功对话框
void _handleUpgradeSuccess() {
type.value = OtaStatusManager.UPGRADE_SUCCESS;
type.value = OtaCoreService.UPGRADE_SUCCESS;
isUpgrade.value = false;
detailedStatus.value = '升级成功完成';
showDetailedStatus.value = false;
@ -248,18 +245,18 @@ class OtaUpgradeController extends GetxController {
isUpdateAvailable.value = false;
// 显示成功对话框
_uiManager.showUpgradeSuccessDialog();
_uiService.showUpgradeSuccessDialog();
}
/// 处理升级错误
/// [errorCode] 错误代码
/// 更新状态并显示错误提示
void _handleUpgradeError(dynamic errorCode) {
type.value = OtaStatusManager.UPGRADE_FAILED;
type.value = OtaCoreService.UPGRADE_FAILED;
isUpgrade.value = false;
detailedStatus.value = '升级失败,错误码:$errorCode';
showDetailedStatus.value = false;
_uiManager.showErrorSnackbar(
_uiService.showErrorSnackbar(
'升级失败',
'请检查设备连接或升级包,错误码:$errorCode',
);
@ -268,7 +265,7 @@ class OtaUpgradeController extends GetxController {
/// 处理连接断开
/// 更新连接状态
void _handleConnectionLost() {
type.value = OtaStatusManager.CONNECTION_DISCONNECTION;
type.value = OtaCoreService.CONNECTION_DISCONNECTION;
isUpgrade.value = false;
detailedStatus.value = '设备连接断开';
showDetailedStatus.value = false;
@ -277,7 +274,7 @@ class OtaUpgradeController extends GetxController {
/// 处理连接成功
/// 更新连接状态
void _handleConnectionSuccess() {
type.value = OtaStatusManager.CONNECTION_SUCCESS;
type.value = OtaCoreService.CONNECTION_SUCCESS;
isUpgrade.value = true;
detailedStatus.value = '设备连接成功';
}
@ -288,6 +285,6 @@ class OtaUpgradeController extends GetxController {
/// 设置错误状态并显示错误提示
void _handleError(String title, String message) {
errorMessage.value = message;
_uiManager.showErrorSnackbar(title, message);
_uiService.showErrorSnackbar(title, message);
}
}

99
lib/modules/ota_upgrade/services/device_checker.dart

@ -1,99 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../../data/services/ble_manager.dart';
/// 设备检查服务
/// 负责检查设备状态,如电量检查等
class DeviceChecker {
final BleManager _bleManager = Get.find<BleManager>();
/// 检查设备电量是否满足升级条件
/// 返回true表示电量充足,可以进行升级
/// 返回false表示电量不足,不能进行升级
bool checkBatteryLevel() {
try {
final deviceInfo = _bleManager.deviceInfo.value;
if (deviceInfo == null) {
_showBatteryCheckFailedSnackbar('无法获取设备信息,请检查设备连接');
return false;
}
final leftBattery = deviceInfo.leftEarBattery;
final rightBattery = deviceInfo.rightEarBattery;
// 检查左右耳电量是否都大于30%
if (leftBattery <= 30 || rightBattery <= 30) {
_showLowBatteryDialog(leftBattery, rightBattery);
return false;
}
return true;
} catch (e) {
_showBatteryCheckFailedSnackbar('检查设备电量时发生错误:$e');
return false;
}
}
/// 显示电量不足对话框
/// [leftBattery] 左耳电量
/// [rightBattery] 右耳电量
void _showLowBatteryDialog(int leftBattery, int rightBattery) {
String message = '设备电量不足,无法进行固件升级\n';
message += '升级要求:左右耳电量均需大于30%\n';
message += '当前电量:左耳 ${leftBattery}%,右耳 ${rightBattery}%\n';
message += '请充电后再试';
Get.dialog(
AlertDialog(
title: Text(
'电量不足',
style: TextStyle(
color: Colors.orange[700],
fontWeight: FontWeight.w600,
),
),
content: Text(
message,
style: TextStyle(
color: Colors.orange[600],
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
style: TextButton.styleFrom(
foregroundColor: Colors.orange[700],
),
child: const Text('确定'),
),
],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: Colors.orange.withValues(alpha: 0.3),
width: 1,
),
),
backgroundColor: Colors.orange[50],
),
);
}
/// 显示电量检查失败提示
/// [message] 错误信息
void _showBatteryCheckFailedSnackbar(String message) {
Get.snackbar(
'电量检查失败',
message,
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.orange[600],
colorText: Colors.white,
icon: const Icon(
Icons.warning_amber,
color: Colors.white,
),
);
}
}

43
lib/modules/ota_upgrade/services/ota_status_manager.dart

@ -1,43 +0,0 @@
import 'package:get/get.dart';
/// OTA升级状态管理器
/// 负责管理OTA升级过程中的各种状态常量和状态描述
class OtaStatusManager {
static const int NOT_STARTED = -1; // 未开始
static const int VALIDATION_FILE = 0; // 验证文件
static const int ONUPGRADE = 1; // 升级中
static const int START_UPGRADING = 2; // 开始升级
static const int UPGRADE_SUCCESS = 3; // 升级成功
static const int UPGRADE_FAILED = 4; // 升级失败
static const int CONNECTION_DISCONNECTION = 5; // 连接断开
static const int CONNECTION_SUCCESS = 6; // 连接成功
static const int DOWNLOADING_UP_FILES = 7; // 下载升级文件
/// 获取状态描述信息
/// [type] 状态类型
/// 返回对应的状态描述文本
static String getStatusMessage(int type) {
switch (type) {
case NOT_STARTED:
return "未开始".tr;
case VALIDATION_FILE:
return "验证文件中".tr;
case ONUPGRADE:
return "升级中".tr;
case START_UPGRADING:
return "开始升级".tr;
case UPGRADE_SUCCESS:
return "升级成功".tr;
case UPGRADE_FAILED:
return "升级失败".tr;
case CONNECTION_DISCONNECTION:
return "连接断开".tr;
case CONNECTION_SUCCESS:
return "连接成功".tr;
case DOWNLOADING_UP_FILES:
return "下载升级文件".tr;
default:
return "未知状态".tr;
}
}
}

114
lib/modules/ota_upgrade/services/version_manager.dart

@ -1,114 +0,0 @@
import 'package:logger/web.dart';
import '../../../data/services/ble_manager.dart';
import '../../../data/models/appconfig.dart';
import '../../../data/models/appconfig_model.dart';
import 'package:get/get.dart';
/// 版本管理服务
/// 负责处理设备版本信息获取、版本比较等功能
class VersionManager {
final BleManager _bleManager = Get.find<BleManager>();
final int maxRetryAttempts = 3;
int currentRetryCount = 0;
/// 获取当前设备版本信息
/// 返回包含当前版本、产品ID和设备信息的Map
/// 抛出异常如果获取失败
Future<Map<String, dynamic>> getCurrentVersionInfo() async {
DeviceInfo? deviceInfo = _bleManager.deviceInfo.value;
// 重试机制获取设备信息
while (currentRetryCount < maxRetryAttempts) {
deviceInfo = _bleManager.deviceInfo.value;
if (deviceInfo != null &&
deviceInfo.firmwareVersion.isNotEmpty &&
deviceInfo.firmwareProductId.isNotEmpty) {
break;
}
await _bleManager.fetchDeviceInfo();
await Future.delayed(const Duration(seconds: 5));
currentRetryCount++;
}
if (deviceInfo == null ||
deviceInfo.firmwareVersion.isEmpty ||
deviceInfo.firmwareProductId.isEmpty) {
throw "获取设备信息失败,请检查连接";
}
// 验证固件版本格式
final firmwareParts = deviceInfo.firmwareVersion.split('.');
final firstPart = firmwareParts.sublist(0, 3).join('.');
final secondPart = firmwareParts.sublist(3).join('.');
Logger().d("firstPart: $firstPart, secondPart: $secondPart");
if (firstPart != secondPart) {
throw "固件版本格式错误:${deviceInfo.firmwareVersion}";
}
return {
'currentVersion': firstPart,
'productId': deviceInfo.firmwareProductId,
'deviceInfo': deviceInfo,
};
}
/// 获取最新版本信息
/// 返回包含最新版本、更新描述和更新包地址的Map
/// 抛出异常如果获取失败
Future<Map<String, dynamic>> getLatestVersionInfo() async {
DBProduct? product = AppConfig.getproduct("DeapSound");
if (product == null) {
throw "无法获取产品信息";
}
return {
'latestVersion': product.version ?? "",
'updateDescription': product.updatedescription ?? "",
'updatePackageAddress': product.updatepackageaddress ?? "",
};
}
/// 比较版本号,判断是否有新版本可用
/// [currentVersion] 当前版本号
/// [latestVersion] 最新版本号
/// 返回true表示有新版本可用(支持升级和降级)
bool isNewVersionAvailable(String currentVersion, String latestVersion) {
try {
List<int> currentParts = currentVersion
.split('.')
.map((e) => int.parse(e.trim()))
.toList();
List<int> latestParts =
latestVersion.split('.').map((e) => int.parse(e.trim())).toList();
// 确保两个版本号长度一致,短的用0补齐
int maxLength = currentParts.length > latestParts.length
? currentParts.length
: latestParts.length;
while (currentParts.length < maxLength) {
currentParts.add(0);
}
while (latestParts.length < maxLength) {
latestParts.add(0);
}
// 比较每一段,只要有不同就返回true(支持升级和降级)
for (int i = 0; i < maxLength; i++) {
if (latestParts[i] != currentParts[i]) {
return true;
}
}
return false;
} catch (e) {
Logger().d('版本比较错误: $e');
return false;
}
}
}

9
lib/modules/ota_upgrade/services/ui_manager.dart → lib/modules/ota_upgrade/views/ota_upgrade_popup_view.dart

@ -4,9 +4,9 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:logger/web.dart';
import '/routes/app_routes.dart';
/// UI交互管理器
/// OTA UI服务
/// 负责处理所有与用户界面交互相关的功能
class UiManager {
class OtaUpgradePopUp {
/// 显示更新确认对话框
/// [latestVersion] 最新版本号
/// [onConfirm] 确认更新的回调函数
@ -101,7 +101,8 @@ class UiManager {
/// [title] 错误标题
/// [message] 错误信息
/// [backgroundColor] 背景颜色,可选
void showErrorSnackbar(String title, String message, {Color? backgroundColor}) {
void showErrorSnackbar(String title, String message,
{Color? backgroundColor}) {
Get.snackbar(
title,
message,
@ -114,4 +115,4 @@ class UiManager {
),
);
}
}
}

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

@ -32,8 +32,7 @@ class TranslationController extends GetxController {
static const String TAG = 'TranslationController';
// 服务
final BluetoothManager _bluetoothManager = Get.find<BluetoothManager>();
final BleManager _bleManager = Get.find<BleManager>();
final AsrService _asrService = Get.find<AsrService>();
final VolcanoTranslationService _translationService =
Get.find<VolcanoTranslationService>();
@ -43,7 +42,8 @@ class TranslationController extends GetxController {
final LanguageManager _languageManager = Get.find<LanguageManager>();
final GetStorage _storage = GetStorage();
// 蓝牙服务
final bleManager = Get.find<BleManager>();
final BluetoothManager bluetoothManager = Get.find<BluetoothManager>();
final BleManager bleManager = Get.find<BleManager>();
//音乐播放管理
final _musiceManager = Get.find<MusicManager>();
// 统计服务
@ -124,6 +124,10 @@ class TranslationController extends GetxController {
currentMode.value = mode;
currentModeTitle.value = _getModeTitle(mode);
stopAll();
if (mode == 'call') {
_initializeCallModeTranslationService();
}
}
String _getModeTitle(String mode) {
@ -277,10 +281,6 @@ class TranslationController extends GetxController {
// 初始化服务
_initServices();
// 初始化语音翻译服务
_initializeCallModeTranslationService();
// 🔑 reverse模式下不需要手动滚动,ListView会自然显示最新内容
// 参考Agent模块的实现模式
}
/// 预先检查权限
@ -457,6 +457,7 @@ class TranslationController extends GetxController {
// required String translationSecretKey,
// String translationRegion = 'cn-north-1',
// });
// await _astService.dispose();
// 重新初始化ASR服务以支持通话音频源
await _astService.initialize(supportedLanguages: callModeLanguages);
@ -620,8 +621,8 @@ class TranslationController extends GetxController {
final formattedTime = DateFormat('yyyyMMdd_HHmmss').format(DateTime.now());
await _asrService.enableRecord(
"${dir.path}/${currentModeTitle.value.tr}_$formattedTime.wav");
// await _astService.enableRecord(
// "${dir.path}/${currentModeTitle.value.tr}_${formattedTime}_mic.wav");
await _astService.enableRecord(
"${dir.path}/${currentModeTitle.value.tr}_${formattedTime}_mic.wav");
return;
}
@ -653,19 +654,6 @@ class TranslationController extends GetxController {
if (isRecognizing.value) return;
try {
// 每次开始识别前检查 ASR 服务是否已配置为当前语言对
final List<String> currentLanguages = [
sourceLanguageCode.value,
targetLanguageCode.value
];
final bool needsReinit =
!_asrService.supportedLanguages.toSet().containsAll(currentLanguages);
if (needsReinit) {
Logger.info('识别开始前发现语言变更,重新初始化ASR服务');
await _reinitializeAsrService();
}
currentSessionId = DateTime.now().millisecondsSinceEpoch.toString();
// 开始新的翻译会话统计
@ -688,9 +676,14 @@ class TranslationController extends GetxController {
} else if (currentMode.value == "call") {
// 发送ble系统mic和dac(音乐或者通话远端)声音
bleManager.openA2DPDecoder();
//判断是否进入通话状态
// if (bleManager.isCall) {
_audioSourceType = true;
isTtsEnabled.value = false;
await _astService.startContinuousTranslation();
// }
// await _astService.path("${dir.path}/8_mic.wav");
Logger.info('发送ble系统mic和dac(音乐或者通话远端)声音');
} else {
@ -699,7 +692,7 @@ class TranslationController extends GetxController {
isTtsEnabled.value = true;
}
if (_bluetoothManager.currentDeviceRx.value != null) {
if (bluetoothManager.currentDeviceRx.value != null) {
restoreOriginalAudioState();
} else {
disableBluetoothAudio();

187
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrToAsr.kt

@ -49,9 +49,10 @@ class IntegratedSpeechTranslationService(
private const val INITIAL_SILENCE_TIMEOUT = "200"
}
// 协程上下文
private val job = SupervisorJob()
override val coroutineContext: CoroutineContext = Dispatchers.Main + job
// 改为可变的协程作用域
private var job = SupervisorJob()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
// Azure服务组件
private var speechConfig: SpeechConfig? = null
@ -153,6 +154,18 @@ class IntegratedSpeechTranslationService(
val confidence: Float = 0f
)
/**
* 重置协程作用域
* 用于重新初始化时清理旧的协程
*/
private fun resetCoroutineScope() {
// 取消所有正在运行的协程
job.cancel()
// 创建新的协程作用域
job = SupervisorJob()
Log.d(TAG, "协程作用域已重置")
}
/**
* 初始化整合服务
*/
@ -163,6 +176,14 @@ class IntegratedSpeechTranslationService(
callback: ServiceEventCallback
): Boolean = withContext(Dispatchers.IO) {
try {
Log.d(TAG, "开始初始化服务")
// 重新初始化前先清理
if (serviceState.isInitialized.get()) {
Log.d(TAG, "检测到服务已初始化,先进行清理")
dispose()
}
this@IntegratedSpeechTranslationService.eventCallback = callback
serviceConfig?.let { this@IntegratedSpeechTranslationService.serviceConfig = it }
@ -309,6 +330,7 @@ class IntegratedSpeechTranslationService(
Log.d(TAG, "识别完成事件: ${event.result.text},serviceConfig.sourceLanguage:${serviceConfig.sourceLanguage}")
// 触发翻译流程
launch {
Log.d(TAG, "触发翻译流程: ${event.result.text}")
processTranslationAndSynthesis(event.result.text)
}
}
@ -407,7 +429,9 @@ class IntegratedSpeechTranslationService(
/**
* 开始连续语音翻译
*/
fun startContinuousTranslation(): Boolean {
fun startContinuousTranslation(): Boolean {
Log.d(TAG, "尝试启动连续翻译,当前状态:初始化=${serviceState.isInitialized.get()}, 识别中=${serviceState.isRecognizing.get()}")
if (!serviceState.isInitialized.get()) {
eventCallback?.onError("Service", "服务未初始化")
return false
@ -419,15 +443,16 @@ class IntegratedSpeechTranslationService(
}
return try {
// 启动音频处理器
audioProcessor?.startRecording()
if (serviceConfig.enableContinuousRecognition) {
Log.d(TAG, "startContinuousTranslation")
Log.d(TAG, "启动连续识别")
recognizer?.startContinuousRecognitionAsync()
serviceState.isRecognizing.set(true)
}
Log.d(TAG, "开始连续语音翻译")
Log.d(TAG, "连续语音翻译启动成功")
true
} catch (e: Exception) {
Log.e(TAG, "启动连续翻译失败", e)
@ -469,6 +494,7 @@ class IntegratedSpeechTranslationService(
* 处理翻译和合成流程
*/
private suspend fun processTranslationAndSynthesis(text: String) {
Log.d(TAG, " 翻译状态:${serviceState.isTranslating.get()}")
if (serviceState.isTranslating.get()) {
Log.w(TAG, "翻译正在进行中,跳过当前请求")
return
@ -477,7 +503,7 @@ class IntegratedSpeechTranslationService(
serviceState.isTranslating.set(true)
eventCallback?.onTranslationStarted(text)
eventCallback?.onStateChanged("Translation", true)
Log.d(TAG, "翻译开始${text}")
try {
val translationResult = withTimeout(serviceConfig.translationTimeout) {
translationService?.translateText(
@ -491,7 +517,9 @@ class IntegratedSpeechTranslationService(
eventCallback?.onStateChanged("Translation", false)
when {
translationResult?.success == true && !translationResult.translatedText.isNullOrEmpty() -> {
Log.d(TAG, "翻译成功${translationResult.translatedText}")
eventCallback?.onTranslated(
text,
translationResult.translatedText,
@ -949,126 +977,71 @@ fun setAudioOutputDevice(device: com.deep_voice.speech.tts.AudioOutputDevice) {
)
}
/**
* 完善的资源释放方法
*/
fun dispose() {
try {
Log.d(TAG, "开始释放服务资源...")
// 1. 设置释放标志,防止新的操作
serviceState.isInitialized.set(false)
Log.d(TAG, "开始清理服务资源")
// 2. 停止所有活动(使用runBlocking确保同步完成)
runBlocking {
try {
withTimeout(5000) { // 5秒超时
stopContinuousTranslation()
}
} catch (e: TimeoutCancellationException) {
Log.w(TAG, "停止识别超时,强制继续释放资源")
}
}
// 3. 等待当前处理完成(带超时)
val maxWaitTime = 3000L // 3秒
val startTime = System.currentTimeMillis()
while ((serviceState.isTranslating.get() || serviceState.isSynthesizing.get() || serviceState.isRecognizing.get())
&& (System.currentTimeMillis() - startTime) < maxWaitTime) {
Thread.sleep(100)
}
// 4. 强制停止识别器
try {
recognizer?.stopContinuousRecognitionAsync()?.get(2, TimeUnit.SECONDS)
} catch (e: Exception) {
Log.w(TAG, "停止识别器超时,强制关闭")
}
// 5. 释放Azure资源(按顺序释放)
try {
recognizer?.close()
recognizer = null
Log.d(TAG, "语音识别器已释放")
} catch (e: Exception) {
Log.e(TAG, "释放识别器失败", e)
// 停止连续识别
if (serviceState.isRecognizing.get()) {
stopContinuousTranslation()
}
try {
synthesizer?.close()
synthesizer = null
Log.d(TAG, "语音合成器已释放")
} catch (e: Exception) {
Log.e(TAG, "释放合成器失败", e)
}
// 添加协程清理
resetCoroutineScope()
try {
audioConfig?.close()
audioConfig = null
Log.d(TAG, "音频配置已释放")
} catch (e: Exception) {
Log.e(TAG, "释放音频配置失败", e)
// 清理识别器
recognizer?.let {
try {
it.stopContinuousRecognitionAsync()
it.close()
} catch (e: Exception) {
Log.w(TAG, "清理识别器失败", e)
}
}
recognizer = null
try {
speechConfig?.close()
speechConfig = null
Log.d(TAG, "语音配置已释放")
} catch (e: Exception) {
Log.e(TAG, "释放语音配置失败", e)
// 清理合成器
synthesizer?.let {
try {
it.close()
} catch (e: Exception) {
Log.w(TAG, "清理合成器失败", e)
}
}
synthesizer = null
// 6. 释放音频处理器
try {
audioProcessor?.dispose()
audioProcessor = null
Log.d(TAG, "音频处理器已释放")
} catch (e: Exception) {
Log.e(TAG, "释放音频处理器失败", e)
}
// 清理音频处理器
audioProcessor?.dispose()
audioProcessor = null
// 7. 释放录音文件
try {
recordfile1?.closeFile(true)
recordfile1 = null
Log.d(TAG, "录音文件已释放")
} catch (e: Exception) {
Log.e(TAG, "释放录音文件失败", e)
}
// 清理音频配置
audioConfig?.close()
audioConfig = null
// 8. 释放翻译服务
try {
translationService?.dispose()
translationService = null
Log.d(TAG, "翻译服务已释放")
} catch (e: Exception) {
Log.e(TAG, "释放翻译服务失败", e)
}
// 清理语音配置
speechConfig?.close()
speechConfig = null
// 9. 取消协程(使用cancelAndJoin确保完全停止)
try {
runBlocking {
job.cancelAndJoin()
}
Log.d(TAG, "协程已取消")
} catch (e: Exception) {
Log.e(TAG, "取消协程失败", e)
}
// 清理翻译服务
translationService?.dispose()
translationService = null
// 10. 清理缓存和回调
voiceCache.clear()
eventCallback = null
filePath = null
// 清理录音文件
recordfile1?.closeFile(true)
recordfile1 = null
// 11. 重置状态
// 重置状态
serviceState.isInitialized.set(false)
serviceState.isRecognizing.set(false)
serviceState.isSynthesizing.set(false)
serviceState.isTranslating.set(false)
Log.d(TAG, "服务资源释放完成")
// 清理缓存
voiceCache.clear()
Log.d(TAG, "服务资源清理完成")
} catch (e: Exception) {
Log.e(TAG, "释放资源过程中发生异常", e)
Log.e(TAG, "清理服务资源失败", e)
}
}

1
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -1035,6 +1035,7 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
override fun onAudioDataReceived1(data: ByteArray) {
// FileLogger.d(tag, "onAudioDataReceived1: ${data.size}")
azureAstHelper.pushAudioData(data)
// 可选:处理音频数据

4
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt

@ -129,7 +129,7 @@ class SimpleAudioReceiver(private val context: Context) {
// 情况2:不在录制但队列有数据 -> 从队列获取
else if (writeQueue.isNotEmpty()) {
data = writeQueue.poll()
Log.d("tag", "写入数据: ${data?.size}")
//Log.d("tag", "写入数据: ${data?.size}")
bytesToWrite = data?.size ?: 0
}
// 确保有有效数据再写入
@ -139,7 +139,7 @@ class SimpleAudioReceiver(private val context: Context) {
if (bytesToWrite < data.size) data.copyOf(bytesToWrite) else data
try {
Log.d(TAG, "写入数据大小: ${finalData.size}")
// Log.d(TAG, "写入数据大小: ${finalData.size}")
if(audioDataCallback!=null){
audioDataCallback?.onAudio(finalData)
}

933
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/IntegratedSpeechTranslationService.swift

@ -0,0 +1,933 @@
import Foundation
import MicrosoftCognitiveServicesSpeech
import AVFoundation
import os.log
/**
* 整合的语音翻译服务
* 集成ASR语音识别、翻译服务和TTS语音合成
* 实现音频输入 -> 语音识别 -> 翻译 -> 语音合成 -> 音频输出的完整流程
*/
@objc public class IntegratedSpeechTranslationService: NSObject {
// MARK: - Constants
private static let TAG = "IntegratedSpeechService"
// 音频源配置
public enum AudioSourceType {
/** 使用设备麦克风 */
case microphone
/** 使用外部提供的音频数据 */
case external
}
// 音频配置常量
private static let SAMPLE_RATE: UInt32 = 16000
private static let CHANNELS: UInt32 = 1
private static let BITS_PER_SAMPLE: UInt32 = 16
private static let BUFFER_SIZE = 4096
// 超时配置
private static let END_SILENCE_TIMEOUT = "300"
private static let SEGMENTATION_SILENCE_TIMEOUT = "300"
private static let INITIAL_SILENCE_TIMEOUT = "200"
// MARK: - Properties
private let log = OSLog(subsystem: "com.azure.speech", category: "IntegratedSpeechService")
// Azure服务组件
private var speechConfig: SPXSpeechConfiguration?
private var recognizer: SPXSpeechRecognizer?
private var synthesizer: SPXSpeechSynthesizer?
// 翻译服务
private var translationService: TranslationServiceInterface?
// 音频处理
private var audioProcessor: SimpleAudioReceiver?
private var audioConfig: SPXAudioConfiguration?
// 录音文件处理
private var recordFile: RecordFile?
private var filePath: String?
// 配置管理
private var serviceConfig = ServiceConfiguration()
// 状态管理
private let serviceState = ServiceState()
// 事件回调
private weak var eventCallback: ServiceEventCallback?
// 语音映射缓存
private var voiceCache: [String: String] = [:]
// 处理队列
private let processingQueue = DispatchQueue(label: "com.azure.speech.processing", qos: .userInitiated)
// MARK: - Configuration Structures
/**
* 服务配置类
*/
public struct ServiceConfiguration {
var sourceLanguage: String = "zh-CN"
var targetLanguage: String = "en-US"
var currentVoice: String = "en-US-AriaNeural"
var speechRate: String = "0%"
var speechPitch: String = "0%"
var speechVolume: String = "100%"
var enableContinuousRecognition: Bool = true
var enableAutoLanguageDetection: Bool = false
var maxRetryAttempts: Int = 3
var translationTimeout: TimeInterval = 10.0
// 新增:控制是否播放合成的音频
var enableAudioPlayback: Bool = false
public init(
sourceLanguage: String = "zh-CN",
targetLanguage: String = "en-US",
currentVoice: String = "en-US-AriaNeural",
speechRate: String = "0%",
speechPitch: String = "0%",
speechVolume: String = "100%",
enableContinuousRecognition: Bool = true,
enableAutoLanguageDetection: Bool = false,
maxRetryAttempts: Int = 3,
translationTimeout: TimeInterval = 10.0,
enableAudioPlayback: Bool = false
) {
self.sourceLanguage = sourceLanguage
self.targetLanguage = targetLanguage
self.currentVoice = currentVoice
self.speechRate = speechRate
self.speechPitch = speechPitch
self.speechVolume = speechVolume
self.enableContinuousRecognition = enableContinuousRecognition
self.enableAutoLanguageDetection = enableAutoLanguageDetection
self.maxRetryAttempts = maxRetryAttempts
self.translationTimeout = translationTimeout
self.enableAudioPlayback = enableAudioPlayback
}
}
/**
* 服务状态类
*/
public class ServiceState {
private let queue = DispatchQueue(label: "com.azure.speech.state")
private var _isInitialized = false
private var _isRecognizing = false
private var _isSynthesizing = false
private var _isTranslating = false
var isInitialized: Bool {
get { queue.sync { _isInitialized } }
set { queue.sync { _isInitialized = newValue } }
}
var isRecognizing: Bool {
get { queue.sync { _isRecognizing } }
set { queue.sync { _isRecognizing = newValue } }
}
var isSynthesizing: Bool {
get { queue.sync { _isSynthesizing } }
set { queue.sync { _isSynthesizing = newValue } }
}
var isTranslating: Bool {
get { queue.sync { _isTranslating } }
set { queue.sync { _isTranslating = newValue } }
}
}
// MARK: - Protocols
/**
* 服务事件回调接口
*/
public protocol ServiceEventCallback: AnyObject {
func onServiceInitialized()
func onRecognizing(text: String, language: String, confidence: Float)
func onRecognized(text: String, language: String, confidence: Float)
func onTranslated(originalText: String, translatedText: String, targetLanguage: String)
func onTranslationStarted(text: String)
func onTranslationFailed(text: String, error: String)
func onSynthesisStarted(text: String)
func onSynthesisCompleted(text: String)
func onSynthesisFailed(text: String, error: String)
func onSynthesisProgress(text: String, progress: Float)
func onRecognitionStarted()
func onRecognitionStopped()
func onStateChanged(component: String, isActive: Bool)
func onError(component: String, error: String)
// 新增:返回合成的音频数据
func onSynthesisAudioGenerated(text: String, audioData: Data)
}
/**
* 翻译服务接口
*/
public protocol TranslationServiceInterface {
func initialize(config: [String: String]) async -> Bool
func translateText(
text: String,
sourceLanguage: String,
targetLanguage: String
) async -> TranslationResult
func dispose()
}
/**
* 翻译结果类
*/
public struct TranslationResult {
let success: Bool
let translatedText: String?
let error: String?
let confidence: Float
public init(success: Bool, translatedText: String? = nil, error: String? = nil, confidence: Float = 0.0) {
self.success = success
self.translatedText = translatedText
self.error = error
self.confidence = confidence
}
}
// MARK: - Initialization
public override init() {
super.init()
}
/**
* 初始化整合服务
*/
public func initialize(
azureConfig: AzureConfiguration,
translationConfig: TranslationConfiguration,
serviceConfig: ServiceConfiguration? = nil,
callback: ServiceEventCallback
) async -> Bool {
do {
self.eventCallback = callback
if let config = serviceConfig {
self.serviceConfig = config
}
os_log("开始初始化整合服务", log: log, type: .info)
// 初始化Azure语音服务
if !initializeAzureServices(azureConfig: azureConfig) {
await MainActor.run {
callback.onError(component: "Initialization", error: "Azure服务初始化失败")
}
return false
}
// 初始化翻译服务
if !(await initializeTranslationService(translationConfig: translationConfig)) {
await MainActor.run {
callback.onError(component: "Initialization", error: "翻译服务初始化失败")
}
return false
}
// 初始化音频处理器
initializeAudioProcessor()
// 设置语音识别器
setupSpeechRecognizer()
// 设置语音合成器
setupSpeechSynthesizer()
serviceState.isInitialized = true
os_log("整合服务初始化成功", log: log, type: .info)
await MainActor.run {
callback.onServiceInitialized()
}
recordFile = RecordFile()
return true
} catch {
os_log("初始化失败: %@", log: log, type: .error, error.localizedDescription)
await MainActor.run {
callback.onError(
component: "Initialization",
error: "初始化失败: \(error.localizedDescription)"
)
}
return false
}
}
/**
* 初始化Azure服务
*/
private func initializeAzureServices(azureConfig: AzureConfiguration) -> Bool {
do {
os_log("开始初始化Azure服务", log: log, type: .info)
speechConfig = try SPXSpeechConfiguration(subscription: azureConfig.subscriptionKey, region: azureConfig.region)
guard let config = speechConfig else {
return false
}
config.speechRecognitionLanguage = serviceConfig.sourceLanguage
config.speechSynthesisVoiceName = getVoiceForLanguage(serviceConfig.targetLanguage)
config.setSpeechSynthesisOutputFormat(.riff16Khz16BitMonoPcm)
// 优化配置
config.setPropertyTo(Self.END_SILENCE_TIMEOUT, byName: "SpeechServiceConnection_EndSilenceTimeoutMs")
config.setPropertyTo(Self.SEGMENTATION_SILENCE_TIMEOUT, byName: "Speech_SegmentationSilenceTimeoutMs")
config.setPropertyTo(Self.INITIAL_SILENCE_TIMEOUT, byName: "SpeechServiceConnection_InitialSilenceTimeoutMs")
config.setPropertyTo("INTERACTIVE", byName: "SpeechServiceConnection_RecoMode")
// 启用详细结果
config.setPropertyTo("true", byName: "SpeechServiceResponse_RequestDetailedResultTrueFalse")
// 自动语言检测
if serviceConfig.enableAutoLanguageDetection {
config.setPropertyTo("Continuous", byName: "SpeechServiceConnection_LanguageIdMode")
}
os_log("Azure语音服务配置完成", log: log, type: .info)
return true
} catch {
os_log("Azure服务初始化失败: %@", log: log, type: .error, error.localizedDescription)
return false
}
}
/**
* 初始化翻译服务
*/
private func initializeTranslationService(translationConfig: TranslationConfiguration) async -> Bool {
do {
translationService = VolcanoTranslationServiceImpl()
let success = await translationService?.initialize(config: translationConfig.toConfigMap()) ?? false
if success {
os_log("翻译服务初始化完成", log: log, type: .info)
}
return success
} catch {
os_log("翻译服务初始化失败: %@", log: log, type: .error, error.localizedDescription)
return false
}
}
/**
* 初始化音频处理器
*/
private func initializeAudioProcessor() {
audioProcessor = SimpleAudioReceiver(parentHelper: self as! AzureAsrHelper)
audioProcessor?.initAudioRecord()
// 检查音频配置是否已存在
if audioConfig == nil, let pushStream = audioProcessor?.pushAudioStream {
audioConfig = SPXAudioConfiguration(streamInput: pushStream)
os_log("设置麦克风流完成", log: log, type: .info)
}
os_log("音频处理器初始化完成", log: log, type: .info)
}
/**
* 设置语音识别器
*/
private func setupSpeechRecognizer() {
guard let speechConfig = speechConfig, let audioConfig = audioConfig else {
return
}
do {
recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig, audioConfiguration: audioConfig)
// 识别中事件
recognizer?.addRecognizingEventHandler { [weak self] (_, event) in
guard let self = self else { return }
let result = event.result
guard let text = result.text, !text.isEmpty else { return }
let confidence = self.extractConfidence(from: result)
os_log("识别中事件: %@", log: self.log, type: .debug, text)
DispatchQueue.main.async {
self.eventCallback?.onRecognizing(
text: text,
language: self.serviceConfig.sourceLanguage,
confidence: confidence
)
}
}
// 识别完成事件
recognizer?.addRecognizedEventHandler { [weak self] (_, event) in
guard let self = self else { return }
let result = event.result
switch result.reason {
case .recognizedSpeech:
if let text = result.text, !text.isEmpty {
let confidence = self.extractConfidence(from: result)
DispatchQueue.main.async {
self.eventCallback?.onRecognized(
text: text,
language: self.serviceConfig.sourceLanguage,
confidence: confidence
)
}
os_log("识别完成事件: %@", log: self.log, type: .info, text)
// 触发翻译流程
Task {
await self.processTranslationAndSynthesis(text: text)
}
}
case .noMatch:
os_log("未识别到语音", log: self.log, type: .debug)
default:
os_log("识别结果: %@", log: self.log, type: .info, String(describing: result.reason))
}
}
// 会话开始事件
recognizer?.addSessionStartedEventHandler { [weak self] (recognizer, event) in
guard let self = self else { return }
os_log("会话开始事件", log: self.log, type: .info)
self.serviceState.isRecognizing = true
DispatchQueue.main.async {
self.eventCallback?.onRecognitionStarted()
self.eventCallback?.onStateChanged(component: "Recognition", isActive: true)
}
}
// 会话停止事件
recognizer?.addSessionStoppedEventHandler { [weak self] (recognizer, event) in
guard let self = self else { return }
os_log("会话停止事件", log: self.log, type: .info)
self.serviceState.isRecognizing = false
DispatchQueue.main.async {
self.eventCallback?.onRecognitionStopped()
self.eventCallback?.onStateChanged(component: "Recognition", isActive: false)
}
}
// 取消事件
recognizer?.addCanceledEventHandler { [weak self] (recognizer, event) in
guard let self = self else { return }
os_log("取消事件", log: self.log, type: .info)
self.serviceState.isRecognizing = false
let errorDetails = event.errorDetails ?? "未知错误"
DispatchQueue.main.async {
self.eventCallback?.onError(component: "Recognition", error: "识别被取消: \(errorDetails)")
self.eventCallback?.onStateChanged(component: "Recognition", isActive: false)
}
}
os_log("语音识别器设置完成", log: log, type: .info)
} catch {
os_log("语音识别器设置失败: %@", log: log, type: .error, error.localizedDescription)
}
}
/**
* 设置语音合成器
*/
private func setupSpeechSynthesizer() {
guard let speechConfig = speechConfig else {
return
}
do {
// 根据配置决定音频输出方式
let audioConfig: SPXAudioConfiguration?
if serviceConfig.enableAudioPlayback {
audioConfig = SPXAudioConfiguration()
} else {
// 不输出到扬声器,只生成音频数据
audioConfig = nil
}
synthesizer = try SPXSpeechSynthesizer(speechConfiguration: speechConfig, audioConfiguration: audioConfig)
// 合成开始事件
synthesizer?.addSynthesisStartedEventHandler { [weak self] (synthesizer, event) in
guard let self = self else { return }
os_log("合成开始事件", log: self.log, type: .info)
self.serviceState.isSynthesizing = true
DispatchQueue.main.async {
self.eventCallback?.onStateChanged(component: "Synthesis", isActive: true)
}
}
// 合成进行中事件
synthesizer?.addSynthesizingEventHandler { [weak self] (synthesizer, event) in
guard let self = self else { return }
os_log("合成进行中事件", log: self.log, type: .debug)
// 计算进度(简化版)
let progress: Float = 0.5
DispatchQueue.main.async {
self.eventCallback?.onSynthesisProgress(text: "", progress: progress)
}
}
// 合成完成事件
synthesizer?.addSynthesisCompletedEventHandler { [weak self] (synthesizer, event) in
guard let self = self else { return }
os_log("合成完成事件", log: self.log, type: .info)
DispatchQueue.main.async {
self.eventCallback?.onStateChanged(component: "Synthesis", isActive: false)
}
}
// 合成取消事件
synthesizer?.addSynthesisCanceledEventHandler { [weak self] (synthesizer, event) in
guard let self = self else { return }
os_log("合成取消事件", log: self.log, type: .info)
self.serviceState.isSynthesizing = false
// 修复:移除可选链,因为event.result不是可选类型
let reason = event.result.reason
DispatchQueue.main.async {
self.eventCallback?.onError(component: "Synthesis", error: "语音合成取消: \(reason)")
self.eventCallback?.onStateChanged(component: "Synthesis", isActive: false)
}
}
os_log("语音合成器设置完成,播放模式: %@", log: log, type: .info, serviceConfig.enableAudioPlayback ? "启用" : "禁用")
} catch {
os_log("语音合成器设置失败: %@", log: log, type: .error, error.localizedDescription)
}
}
// MARK: - Public Methods
/**
* 开始连续语音翻译
*/
public func startContinuousTranslation() -> Bool {
os_log("尝试启动连续翻译,当前状态:初始化=%@, 识别中=%@", log: log, type: .info,
serviceState.isInitialized ? "true" : "false",
serviceState.isRecognizing ? "true" : "false")
guard serviceState.isInitialized else {
eventCallback?.onError(component: "Service", error: "服务未初始化")
return false
}
if serviceState.isRecognizing {
os_log("语音识别已在进行中", log: log, type: .info)
return true
}
do {
// 启动音频处理器
audioProcessor?.startAudioRecord( audioSourceType: .external,
audioDataCallback: nil)
if serviceConfig.enableContinuousRecognition {
os_log("启动连续识别", log: log, type: .info)
try recognizer?.startContinuousRecognition()
serviceState.isRecognizing = true
}
os_log("连续语音翻译启动成功", log: log, type: .info)
return true
} catch {
os_log("启动连续翻译失败: %@", log: log, type: .error, error.localizedDescription)
eventCallback?.onError(component: "Service", error: "启动失败: \(error.localizedDescription)")
return false
}
}
/**
* 启用录音
*/
public func enableRecord(filePath: String) {
os_log("开启录音: %@", log: log, type: .info, filePath)
recordFile?.closeFile(isSave: true)
recordFile?.creatingFiles(atPath: filePath)
}
/**
* 停止连续语音翻译
*/
public func stopContinuousTranslation() {
do {
try recognizer?.stopContinuousRecognition()
audioProcessor?.stopMicrophoneCapture()
recordFile?.closeFile(isSave: true)
// 等待当前处理完成
while serviceState.isTranslating || serviceState.isSynthesizing {
Thread.sleep(forTimeInterval: 0.1)
}
os_log("停止连续语音翻译", log: log, type: .info)
} catch {
os_log("停止翻译失败: %@", log: log, type: .error, error.localizedDescription)
eventCallback?.onError(component: "Service", error: "停止失败: \(error.localizedDescription)")
}
}
/**
* 处理翻译和合成流程
*/
private func processTranslationAndSynthesis(text: String) async {
os_log("翻译状态: %@", log: log, type: .debug, serviceState.isTranslating ? "进行中" : "空闲")
if serviceState.isTranslating {
os_log("翻译正在进行中,跳过当前请求", log: log, type: .info)
return
}
serviceState.isTranslating = true
await MainActor.run {
eventCallback?.onTranslationStarted(text: text)
eventCallback?.onStateChanged(component: "Translation", isActive: true)
}
os_log("翻译开始: %@", log: log, type: .info, text)
do {
let translationResult = try await withTimeout(self.serviceConfig.translationTimeout) {
// 修复:在闭包中显式使用self
return await self.translationService?.translateText(
text: text,
sourceLanguage: self.serviceConfig.sourceLanguage,
targetLanguage: self.serviceConfig.targetLanguage
)
}
serviceState.isTranslating = false
await MainActor.run {
eventCallback?.onStateChanged(component: "Translation", isActive: false)
}
if let result = translationResult, result.success, let translatedText = result.translatedText, !translatedText.isEmpty {
os_log("翻译成功: %@", log: log, type: .info, translatedText)
await MainActor.run {
eventCallback?.onTranslated(
originalText: text,
translatedText: translatedText,
targetLanguage: serviceConfig.targetLanguage
)
}
// 进行语音合成
if let audioData = synthesizeText(translatedText) {
await MainActor.run {
eventCallback?.onSynthesisAudioGenerated(text: translatedText, audioData: audioData)
}
}
} else {
let errorMessage = translationResult?.error ?? "翻译失败"
os_log("翻译失败: %@", log: log, type: .error, errorMessage)
await MainActor.run {
eventCallback?.onTranslationFailed(text: text, error: errorMessage)
}
}
} catch {
serviceState.isTranslating = false
await MainActor.run {
eventCallback?.onStateChanged(component: "Translation", isActive: false)
eventCallback?.onTranslationFailed(text: text, error: "翻译超时或异常: \(error.localizedDescription)")
}
os_log("翻译异常: %@", log: log, type: .error, error.localizedDescription)
}
}
/**
* 语音合成
* @param text 要合成的文本
* @return 返回合成的音频数据,如果合成失败则返回nil
*/
/**
* 合成文本为音频数据
*/
private func synthesizeText(_ text: String) -> Data? {
guard let synthesizer = synthesizer else {
os_log("语音合成器未初始化", log: log, type: .error)
return nil
}
do {
let ssml = buildSSML(text: text)
os_log("开始合成语音: %@", log: log, type: .info, text)
serviceState.isSynthesizing = true
DispatchQueue.main.async {
self.eventCallback?.onSynthesisStarted(text: text)
self.eventCallback?.onStateChanged(component: "Synthesis", isActive: true)
}
let result = try synthesizer.speakSsml(ssml)
serviceState.isSynthesizing = false
DispatchQueue.main.async {
self.eventCallback?.onStateChanged(component: "Synthesis", isActive: false)
}
if result.reason == .synthesizingAudioCompleted {
os_log("语音合成成功", log: log, type: .info)
DispatchQueue.main.async {
self.eventCallback?.onSynthesisCompleted(text: text)
}
return result.audioData
} else {
let errorMessage = "合成失败: \(result.reason)"
os_log("%@", log: log, type: .error, errorMessage)
DispatchQueue.main.async {
self.eventCallback?.onSynthesisFailed(text: text, error: errorMessage)
}
return nil
}
} catch {
serviceState.isSynthesizing = false
let errorMessage = "合成异常: \(error.localizedDescription)"
os_log("%@", log: log, type: .error, errorMessage)
DispatchQueue.main.async {
self.eventCallback?.onSynthesisFailed(text: text, error: errorMessage)
self.eventCallback?.onStateChanged(component: "Synthesis", isActive: false)
}
return nil
}
}
/**
* 构建SSML
*/
private func buildSSML(text: String) -> String {
let voice = getVoiceForLanguage(serviceConfig.targetLanguage)
return """
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="\(serviceConfig.targetLanguage)">
<voice name="\(voice)">
<prosody rate="\(serviceConfig.speechRate)" pitch="\(serviceConfig.speechPitch)" volume="\(serviceConfig.speechVolume)">
\(text)
</prosody>
</voice>
</speak>
"""
}
/**
* 根据语言获取对应的语音
*/
private func getVoiceForLanguage(_ language: String) -> String {
if let cachedVoice = voiceCache[language] {
return cachedVoice
}
let voice: String
switch language {
case "zh-CN":
voice = "zh-CN-XiaoxiaoNeural"
case "en-US":
voice = "en-US-AriaNeural"
case "ja-JP":
voice = "ja-JP-NanamiNeural"
case "ko-KR":
voice = "ko-KR-SunHiNeural"
case "fr-FR":
voice = "fr-FR-DeniseNeural"
case "de-DE":
voice = "de-DE-KatjaNeural"
case "es-ES":
voice = "es-ES-ElviraNeural"
default:
voice = "en-US-AriaNeural"
}
voiceCache[language] = voice
return voice
}
/**
* 提取置信度
*/
private func extractConfidence(from result: SPXSpeechRecognitionResult) -> Float {
// 从结果中提取置信度,这里简化处理
return 0.9
}
/**
* 更新配置
*/
public func updateConfiguration(newConfig: ServiceConfiguration) {
serviceConfig = newConfig
// 更新语音配置
speechConfig?.speechRecognitionLanguage = newConfig.sourceLanguage
speechConfig?.speechSynthesisVoiceName = getVoiceForLanguage(newConfig.targetLanguage)
os_log("服务配置已更新", log: log, type: .info)
}
/**
* 切换语言
*/
public func switchLanguages() {
let newConfig = ServiceConfiguration(
sourceLanguage: serviceConfig.targetLanguage,
targetLanguage: serviceConfig.sourceLanguage,
currentVoice: getVoiceForLanguage(serviceConfig.sourceLanguage),
speechRate: serviceConfig.speechRate,
speechPitch: serviceConfig.speechPitch,
speechVolume: serviceConfig.speechVolume,
enableContinuousRecognition: serviceConfig.enableContinuousRecognition,
enableAutoLanguageDetection: serviceConfig.enableAutoLanguageDetection,
maxRetryAttempts: serviceConfig.maxRetryAttempts,
translationTimeout: serviceConfig.translationTimeout,
enableAudioPlayback: serviceConfig.enableAudioPlayback
)
updateConfiguration(newConfig: newConfig)
os_log("语言已切换: %@ <-> %@", log: log, type: .info, newConfig.sourceLanguage, newConfig.targetLanguage)
}
/**
* 推送外部音频数据
*/
public func pushAudioData(audioData: Data) {
audioProcessor?.saveAudioDataTo(data:audioData)
}
/**
* 获取服务状态
*/
public func getServiceStatus() -> [String: Bool] {
return [
"isInitialized": serviceState.isInitialized,
"isRecognizing": serviceState.isRecognizing,
"isTranslating": serviceState.isTranslating,
"isSynthesizing": serviceState.isSynthesizing
]
}
/**
* 释放资源
*/
public func dispose() {
os_log("开始释放服务资源", log: log, type: .info)
// 停止所有操作
stopContinuousTranslation()
// 释放Azure组件
recognizer = nil
synthesizer = nil
speechConfig = nil
audioConfig = nil
// 释放其他组件
audioProcessor?.releaseAudioResources()
audioProcessor = nil
translationService?.dispose()
translationService = nil
recordFile?.closeFile(isSave: false)
recordFile = nil
// 清理缓存
voiceCache.removeAll()
// 重置状态
serviceState.isInitialized = false
serviceState.isRecognizing = false
serviceState.isTranslating = false
serviceState.isSynthesizing = false
// 清理回调
eventCallback = nil
os_log("服务资源释放完成", log: log, type: .info)
}
}
// MARK: - Helper Extensions
/**
* 超时错误
*/
struct TimeoutError: Error {
let message: String
}
/**
* 带超时的异步操作
*/
func withTimeout<T>(_ timeout: TimeInterval, operation: @escaping () async throws -> T) async throws -> T {
return try await withThrowingTaskGroup(of: T.self) { group in
group.addTask {
return try await operation()
}
group.addTask {
try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000))
throw TimeoutError(message: "操作超时")
}
guard let result = try await group.next() else {
throw TimeoutError(message: "操作被取消")
}
group.cancelAll()
return result
}
}

510
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/VolcanoTranslationServiceImpl.swift

@ -0,0 +1,510 @@
import Foundation
import CryptoKit
import os.log
/**
* 火山翻译服务实现
*/
public class VolcanoTranslationServiceImpl: IntegratedSpeechTranslationService.TranslationServiceInterface {
// MARK: - Constants
private let tag = "VolcanoTranslationService"
private let log = OSLog(subsystem: "com.azure.speech", category: "VolcanoTranslationService")
private let baseURL = "https://translate.volcengineapi.com"
private let endpoint = "/"
private let service = "translate"
private let version = "2020-06-01"
private let action = "TranslateText"
private let algorithm = "HMAC-SHA256"
// MARK: - Properties
private var isInitialized = false
private var accessKey = ""
private var secretKey = ""
private var region = "cn-north-1"
private var maxRetryAttempts = 3
private var timeout: TimeInterval = 10.0
// HTTP客户端
private lazy var urlSession: URLSession = {
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 10.0
config.timeoutIntervalForResource = 30.0
return URLSession(configuration: config)
}()
// 语言代码映射
private let languageCodeMap: [String: String] = [
"zh-CN": "zh",
"zh-TW": "zh-Hant",
"en-US": "en",
"en-GB": "en",
"ja-JP": "ja",
"ko-KR": "ko",
"fr-FR": "fr",
"de-DE": "de",
"es-ES": "es",
"ru-RU": "ru",
"ar-SA": "ar",
"pt-BR": "pt",
"it-IT": "it",
"th-TH": "th",
"vi-VN": "vi",
"hi-IN": "hi"
]
// MARK: - TranslationServiceInterface Implementation
/**
* 初始化翻译服务
*/
public func initialize(config: [String: String]) async -> Bool {
guard let accessKey = config["accessKey"],
let secretKey = config["secretKey"] else {
os_log("翻译服务配置缺失", log: log, type: .error)
return false
}
self.accessKey = accessKey
self.secretKey = secretKey
self.region = config["region"] ?? "cn-north-1"
if let maxRetryStr = config["maxRetryAttempts"], let maxRetry = Int(maxRetryStr) {
self.maxRetryAttempts = maxRetry
}
if let timeoutStr = config["timeout"], let timeoutValue = Double(timeoutStr) {
self.timeout = timeoutValue
}
isInitialized = true
os_log("火山翻译服务初始化成功", log: log, type: .info)
return true
}
/**
* 翻译文本
*/
public func translateText(
text: String,
sourceLanguage: String,
targetLanguage: String
) async -> IntegratedSpeechTranslationService.TranslationResult {
guard isInitialized else {
return IntegratedSpeechTranslationService.TranslationResult(
success: false,
error: "翻译服务未初始化"
)
}
guard !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
return IntegratedSpeechTranslationService.TranslationResult(
success: false,
error: "翻译文本为空"
)
}
os_log("开始翻译: %@ -> %@, 文本: %@", log: log, type: .info, sourceLanguage, targetLanguage, text)
return await performTranslationWithRetry(
text: text,
sourceLanguage: sourceLanguage,
targetLanguage: targetLanguage
)
}
/**
* 释放资源
*/
public func dispose() {
isInitialized = false
urlSession.invalidateAndCancel()
os_log("火山翻译服务已释放", log: log, type: .info)
}
// MARK: - Private Methods
/**
* 带重试的翻译执行
*/
private func performTranslationWithRetry(
text: String,
sourceLanguage: String,
targetLanguage: String
) async -> IntegratedSpeechTranslationService.TranslationResult {
var lastError: String = "未知错误"
for attempt in 1...maxRetryAttempts {
os_log("翻译尝试 %d/%d", log: log, type: .debug, attempt, maxRetryAttempts)
let result = await performTranslation(
text: text,
sourceLanguage: sourceLanguage,
targetLanguage: targetLanguage
)
if result.success {
os_log("翻译成功,尝试次数: %d", log: log, type: .info, attempt)
return result
}
lastError = result.error ?? "翻译失败"
os_log("翻译尝试 %d 失败: %@", log: log, type: .default, attempt, lastError)
// 最后一次尝试不需要等待
if attempt < maxRetryAttempts {
let delay = TimeInterval(attempt) // 递增延迟
try? await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
}
}
os_log("翻译最终失败,已尝试 %d 次", log: log, type: .error, maxRetryAttempts)
return IntegratedSpeechTranslationService.TranslationResult(
success: false,
error: "翻译失败,已重试 \(maxRetryAttempts) 次。最后错误: \(lastError)"
)
}
/**
* 执行翻译
*/
private func performTranslation(
text: String,
sourceLanguage: String,
targetLanguage: String
) async -> IntegratedSpeechTranslationService.TranslationResult {
do {
// 转换语言代码
let sourceCode = getLanguageCode(languageCode: sourceLanguage)
let targetCode = getLanguageCode(languageCode: targetLanguage)
// 构建请求体
let requestBody: [String: Any] = [
"TargetLanguage": targetCode,
"TextList": [text]
]
// 如果源语言不是自动检测,则添加源语言
var finalRequestBody = requestBody
if sourceCode != "auto" {
finalRequestBody["SourceLanguage"] = sourceCode
}
let jsonData = try JSONSerialization.data(withJSONObject: finalRequestBody)
// 构建查询参数
let queryParams: [String: String] = [
"Action": action,
"Version": version
]
// 生成签名
let headers = try generateSignature(
method: "POST",
requestBody: finalRequestBody,
queryParams: queryParams
)
// 构建URL
var urlComponents = URLComponents(string: baseURL + endpoint)!
urlComponents.queryItems = queryParams.map { URLQueryItem(name: $0.key, value: $0.value) }
guard let url = urlComponents.url else {
throw TranslationError.invalidURL
}
// 创建请求
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = jsonData
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// 添加签名头
for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
os_log("发送翻译请求到: %@", log: log, type: .debug, url.absoluteString)
// 发送请求
let (data, response) = try await urlSession.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw TranslationError.invalidResponse
}
os_log("翻译响应状态码: %d", log: log, type: .debug, httpResponse.statusCode)
if httpResponse.statusCode == 200 {
// 解析响应
guard let jsonObject = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
throw TranslationError.invalidJSON
}
if let translatedText = extractTranslation(jsonResponse: jsonObject) {
os_log("翻译成功: %@", log: log, type: .info, translatedText)
return IntegratedSpeechTranslationService.TranslationResult(
success: true,
translatedText: translatedText,
confidence: 0.9
)
} else if let error = extractError(jsonResponse: jsonObject) {
os_log("翻译API返回错误: %@", log: log, type: .error, error)
return IntegratedSpeechTranslationService.TranslationResult(
success: false,
error: error
)
} else {
throw TranslationError.noTranslationFound
}
} else {
let errorMessage = "HTTP错误: \(httpResponse.statusCode)"
if let errorData = String(data: data, encoding: .utf8) {
os_log("HTTP错误响应: %@", log: log, type: .error, errorData)
}
return IntegratedSpeechTranslationService.TranslationResult(
success: false,
error: errorMessage
)
}
} catch {
os_log("翻译请求异常: %@", log: log, type: .error, error.localizedDescription)
return IntegratedSpeechTranslationService.TranslationResult(
success: false,
error: "翻译请求异常: \(error.localizedDescription)"
)
}
}
/**
* 获取语言代码
*/
private func getLanguageCode(languageCode: String) -> String {
return languageCodeMap[languageCode] ?? languageCode
}
/**
* 提取翻译结果
*/
private func extractTranslation(jsonResponse: [String: Any]) -> String? {
guard let responseMetadata = jsonResponse["ResponseMetadata"] as? [String: Any],
let error = responseMetadata["Error"] as? [String: Any] else {
// 没有错误,尝试提取翻译结果
if let translationList = jsonResponse["TranslationList"] as? [[String: Any]],
let firstTranslation = translationList.first,
let translation = firstTranslation["Translation"] as? String {
return translation
}
return nil
}
// 有错误信息
return nil
}
/**
* 提取错误信息
*/
private func extractError(jsonResponse: [String: Any]) -> String? {
if let responseMetadata = jsonResponse["ResponseMetadata"] as? [String: Any],
let error = responseMetadata["Error"] as? [String: Any] {
let code = error["Code"] as? String ?? "UnknownError"
let message = error["Message"] as? String ?? "未知错误"
return "\(code): \(message)"
}
return nil
}
/**
* 生成签名
*/
private func generateSignature(
method: String,
requestBody: [String: Any],
queryParams: [String: String]
) throws -> [String: String] {
let now = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
let timestamp = dateFormatter.string(from: now)
let shortDateFormatter = DateFormatter()
shortDateFormatter.dateFormat = "yyyyMMdd"
shortDateFormatter.timeZone = TimeZone(abbreviation: "UTC")
let shortDate = shortDateFormatter.string(from: now)
// 构建规范请求
let httpRequestMethod = method
let canonicalURI = endpoint
// 构建规范查询字符串
let sortedQueryParams = queryParams.sorted { $0.key < $1.key }
let canonicalQueryString = sortedQueryParams
.map { "\($0.key)=\($0.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? $0.value)" }
.joined(separator: "&")
// 构建规范头部
let host = URL(string: baseURL)!.host!
let canonicalHeaders = "host:\(host)\nx-date:\(timestamp)\n"
let signedHeaders = "host;x-date"
// 计算请求体哈希
let requestBodyData = try JSONSerialization.data(withJSONObject: requestBody)
let hashedRequestPayload = sha256(data: requestBodyData)
// 构建规范请求
let canonicalRequest = "\(httpRequestMethod)\n\(canonicalURI)\n\(canonicalQueryString)\n\(canonicalHeaders)\n\(signedHeaders)\n\(hashedRequestPayload)"
// 创建待签字符串
let credentialScope = "\(shortDate)/\(region)/\(service)/request"
let stringToSign = "\(algorithm)\n\(timestamp)\n\(credentialScope)\n\(sha256(string: canonicalRequest))"
// 计算签名
let kSecret = Data(secretKey.utf8)
let kDate = try hmacSha256(key: kSecret, data: shortDate)
let kRegion = try hmacSha256(key: kDate, data: region)
let kService = try hmacSha256(key: kRegion, data: service)
let kSigning = try hmacSha256(key: kService, data: "request")
let signature = try hmacSha256(key: kSigning, data: stringToSign)
let signatureHex = signature.map { String(format: "%02x", $0) }.joined()
// 构建授权头
let authorization = "\(algorithm) Credential=\(accessKey)/\(credentialScope), SignedHeaders=\(signedHeaders), Signature=\(signatureHex)"
return [
"Content-Type": "application/json",
"X-Date": timestamp,
"Authorization": authorization,
"Host": host
]
}
/**
* SHA256哈希函数(字符串输入)
*/
private func sha256(string: String) -> String {
let data = Data(string.utf8)
return sha256(data: data)
}
/**
* SHA256哈希函数(数据输入)
*/
private func sha256(data: Data) -> String {
let hash = SHA256.hash(data: data)
return hash.compactMap { String(format: "%02x", $0) }.joined()
}
/**
* HMAC-SHA256函数
*/
private func hmacSha256(key: Data, data: String) throws -> Data {
let dataToSign = Data(data.utf8)
let symmetricKey = SymmetricKey(data: key)
let signature = HMAC<SHA256>.authenticationCode(for: dataToSign, using: symmetricKey)
return Data(signature)
}
}
/**
* 翻译错误枚举
*/
enum TranslationError: Error {
case invalidURL
case invalidResponse
case invalidJSON
case noTranslationFound
case networkError(String)
case authenticationError
case rateLimitExceeded
case serviceUnavailable
var localizedDescription: String {
switch self {
case .invalidURL:
return "无效的URL"
case .invalidResponse:
return "无效的响应"
case .invalidJSON:
return "无效的JSON格式"
case .noTranslationFound:
return "未找到翻译结果"
case .networkError(let message):
return "网络错误: \(message)"
case .authenticationError:
return "认证失败"
case .rateLimitExceeded:
return "请求频率超限"
case .serviceUnavailable:
return "服务不可用"
}
}
}
/**
* 配置扩展
*/
extension TranslationConfiguration {
func toConfigMap() -> [String: String] {
var config: [String: String] = [
"accessKey": accessKey,
"secretKey": secretKey,
"region": region
]
if let maxRetry = maxRetryAttempts {
config["maxRetryAttempts"] = String(maxRetry)
}
if let timeoutValue = timeout {
config["timeout"] = String(timeoutValue)
}
return config
}
}
/**
* 翻译配置类
*/
public struct TranslationConfiguration {
let accessKey: String
let secretKey: String
let region: String
let maxRetryAttempts: Int?
let timeout: TimeInterval?
public init(
accessKey: String,
secretKey: String,
region: String = "cn-north-1",
maxRetryAttempts: Int? = nil,
timeout: TimeInterval? = nil
) {
self.accessKey = accessKey
self.secretKey = secretKey
self.region = region
self.maxRetryAttempts = maxRetryAttempts
self.timeout = timeout
}
}
/**
* Azure配置类
*/
public struct AzureConfiguration {
let subscriptionKey: String
let region: String
public init(subscriptionKey: String, region: String) {
self.subscriptionKey = subscriptionKey
self.region = region
}
}

21
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -154,7 +154,7 @@ object BleService {
val timeDiff = currentTime - lastStatisticsTime
if (timeDiff >= 1000) { // 每秒统计一次
// Log.i(TAG, "每秒接收音频数据: $bytesReceivedInCurrentSecond 字节")
// Log.i(TAG, "每秒发送音频数据: $bytesSentInCurrentSecond 字节")
//Log.i(TAG, "每秒发送音频数据: $bytesSentInCurrentSecond 字节")
bytesReceivedInCurrentSecond = 0
bytesSentInCurrentSecond = 0
lastStatisticsTime = currentTime
@ -1061,6 +1061,7 @@ startBytesStatistics()
recordfile!!.closeFile()
recordfile1!!.closeFile()
}
Log.d(TAG, "收到设备响应,channelMode: $channelMode")
// 声道模式描述
val channelDesc = when (channelMode) {
BleConst.AUDIO_CHANNEL_LEFT -> "左声道"
@ -1073,8 +1074,23 @@ startBytesStatistics()
resultMap["channelMode"] = channelMode
resultMap["statusDesc"] = statusDesc
resultMap["channelDesc"] = channelDesc
Log.i(TAG, "编解码控制响应: $statusDesc, $channelDesc")
}else if (responseData.isNotEmpty() && responseData.size == 1) {
val codecStatus = responseData[0].toInt() and 0xFF
if(codecStatus==0x00){
Log.d(
"BleService",
"收到设备响应:失败,codecStatus: $codecStatus"
)
val resultMap = mutableMapOf<String, Any>(
"success" to true,
"command" to "Error",
"rawData" to responseData
)
}
}
notifyDeviceInfoReceived(cmdType, resultMap)
@ -2082,6 +2098,7 @@ class RecordingFile(private val context: Context) {
// 追加音频数据到文件
fos = FileOutputStream(currentAudioFile, true)
startWriteThread() // 新增:启动写入线程
Log.d("TAG", "创建文件: ${currentAudioFile?.absolutePath}")
}
// 新增:启动写入线程

Loading…
Cancel
Save