Browse Source

Merge commit '88587ef9aa0f60e9722eaa1d8d6994a9efc34a0a' into new_dev

weicu
lxm 1 year ago
parent
commit
a22a5eaacd
  1. 22
      lib/data/services/bluetooth_manager.dart
  2. 16
      lib/modules/pairing/controllers/pairing_controller.dart
  3. 4
      lib/modules/translation/views/translation_view.dart
  4. 81
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  5. 13
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt
  6. 2
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCompanionManager.kt
  7. 78
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/CustomSseClientTransport.kt
  8. 1
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt

22
lib/data/services/bluetooth_manager.dart

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:classic_bluetooth/classic_bluetooth.dart'; import 'package:classic_bluetooth/classic_bluetooth.dart';
import '../../core/utils/logger.dart'; import '../../core/utils/logger.dart';
import '../../routes/app_routes.dart';
// 重新导出蓝牙设备类型,让外部可以通过BluetoothManager访问 // 重新导出蓝牙设备类型,让外部可以通过BluetoothManager访问
export 'package:classic_bluetooth/classic_bluetooth.dart' show BluetoothDevice; export 'package:classic_bluetooth/classic_bluetooth.dart' show BluetoothDevice;
@ -58,6 +59,7 @@ class BluetoothManager extends GetxService {
try { try {
_bluetoothSubscription = ClassicBluetooth.events.listen( _bluetoothSubscription = ClassicBluetooth.events.listen(
(event) { (event) {
print("_setupBluetoothListener$event");
if (event.type == 'stateChanged' && event.state != null) { if (event.type == 'stateChanged' && event.state != null) {
_bluetoothState.value = event.state!; _bluetoothState.value = event.state!;
Logger.info('蓝牙状态变更: ${event.state}'); Logger.info('蓝牙状态变更: ${event.state}');
@ -70,9 +72,15 @@ class BluetoothManager extends GetxService {
// 直接从事件中获取设备信息 // 直接从事件中获取设备信息
final newDevice = event.device!; final newDevice = event.device!;
// 更新当前设备
currentDeviceRx.value = newDevice; currentDeviceRx.value = newDevice;
// 检查当前是否在OTA升级界面,如果是则不跳转
if (!_isInOtaUpgradePage()) {
Get.toNamed(Routes.pairing, arguments: newDevice);
} else {
Logger.info('当前在OTA升级界面,跳过配对页面跳转');
}
// 正常模式:更新当前设备并触发监听器
Logger.info('经典蓝牙设备已连接: ${newDevice.name} (${newDevice.address})'); Logger.info('经典蓝牙设备已连接: ${newDevice.name} (${newDevice.address})');
} else if (event.type == 'deviceDisconnected' && } else if (event.type == 'deviceDisconnected' &&
event.device != null) { event.device != null) {
@ -138,4 +146,16 @@ class BluetoothManager extends GetxService {
return false; return false;
} }
} }
/// 检查当前是否在OTA升级界面
/// 返回true表示当前在OTA界面,不应该跳转到配对页面
bool _isInOtaUpgradePage() {
try {
final currentRoute = Get.currentRoute;
return currentRoute == Routes.OTA_UPGRADE;
} catch (e) {
Logger.error('检查当前路由失败: ${e.toString()}');
return false;
}
}
} }

16
lib/modules/pairing/controllers/pairing_controller.dart

@ -708,13 +708,13 @@ class PairingController extends GetxController with WidgetsBindingObserver {
// 注册后台扫描以开始BLE配对 // 注册后台扫描以开始BLE配对
if (_verifiedDeviceAddress != null) { if (_verifiedDeviceAddress != null) {
await _bleManager.connect(); // await _bleManager.connect();
// final success = await _bleManager.registerBackgroundScan( final success = await _bleManager.registerBackgroundScan(
// macAddress: _verifiedDeviceAddress); macAddress: _verifiedDeviceAddress);
// if (!success && _currentState.value == PairingState.waitingBle) { if (!success && _currentState.value == PairingState.waitingBle) {
// _setError( _setError(
// ErrorType.bleTimeout, 'blePairingStartupFailed'.tr); //BLE配对启动失败 ErrorType.bleTimeout, 'blePairingStartupFailed'.tr); //BLE配对启动失败
// } }
} }
} catch (e) { } catch (e) {
Logger.e(_tag, 'BLE连接失败: $e'); Logger.e(_tag, 'BLE连接失败: $e');
@ -1037,7 +1037,7 @@ class PairingController extends GetxController with WidgetsBindingObserver {
try { try {
Logger.i(_tag, '启动AgentService服务'); Logger.i(_tag, '启动AgentService服务');
await _bleManager.startAgentService(); await _bleManager.startAgentService();
_bleManager.registerBackgroundScan(macAddress: _verifiedDeviceAddress); // _bleManager.registerBackgroundScan(macAddress: _verifiedDeviceAddress);
} catch (e) { } catch (e) {
Logger.e(_tag, '启动AgentService失败: $e'); Logger.e(_tag, '启动AgentService失败: $e');
} }

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

@ -1073,6 +1073,7 @@ class TranslationView extends GetView<TranslationController> {
Colors.blue, Colors.blue,
onTap: () async { onTap: () async {
Get.back(); Get.back();
await controller.stopAll();
await controller.changeTranslationMode('simultaneous'); await controller.changeTranslationMode('simultaneous');
await controller.startRecognition(); await controller.startRecognition();
}, },
@ -1084,6 +1085,7 @@ class TranslationView extends GetView<TranslationController> {
Colors.green, Colors.green,
onTap: () async { onTap: () async {
Get.back(); Get.back();
await controller.stopAll();
await controller.changeTranslationMode('faceToFace'); await controller.changeTranslationMode('faceToFace');
await controller.startRecognition(); await controller.startRecognition();
}, },
@ -1096,6 +1098,7 @@ class TranslationView extends GetView<TranslationController> {
onTap: controller.bleManager.isConnected onTap: controller.bleManager.isConnected
? () async { ? () async {
Get.back(); Get.back();
await controller.stopAll();
await controller.changeTranslationMode('audioVideo'); await controller.changeTranslationMode('audioVideo');
await controller.startRecognition(); await controller.startRecognition();
} }
@ -1109,6 +1112,7 @@ class TranslationView extends GetView<TranslationController> {
onTap: controller.bleManager.isConnected onTap: controller.bleManager.isConnected
? () async { ? () async {
Get.back(); Get.back();
await controller.stopAll();
await controller.changeTranslationMode('call'); await controller.changeTranslationMode('call');
await controller.startRecognition(); await controller.startRecognition();
} }

81
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

@ -12,7 +12,7 @@ import android.os.Looper
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import com.yunqiinnovation.azure_speech.tools.RecordFile import com.yunqiinnovation.azure_speech.tools.RecordFile
import com.microsoft.cognitiveservices.speech.audio.AudioConfig import com.microsoft.cognitiveservices.speech.audio.AudioConfig
import com.yunqiinnovation.azure_speech.tools.AudioRecordingForegroundService
/** /**
@ -31,6 +31,8 @@ class AzureAsrHelper(private val context: Context) {
private var audioConfig: AudioConfig? = null private var audioConfig: AudioConfig? = null
private var continuousCallback: ContinuousRecognizeCallback? = null private var continuousCallback: ContinuousRecognizeCallback? = null
// 添加前台服务管理标志
private var isForegroundServiceRunning = false
// 配置参数 // 配置参数
private var currentLanguage = "zh-CN" private var currentLanguage = "zh-CN"
private var supportedLanguages = arrayOf("zh-CN") private var supportedLanguages = arrayOf("zh-CN")
@ -247,6 +249,20 @@ class AzureAsrHelper(private val context: Context) {
if (audioStream.isContinuousRecognitionActive) { if (audioStream.isContinuousRecognitionActive) {
return true return true
} }
// 启动前台服务(在开始音频录制前)
if (!isForegroundServiceRunning && audioSourceType == AudioSourceType.MICROPHONE) {
try {
AudioRecordingForegroundService.startService(context)
isForegroundServiceRunning = true
Log.d(tag, "前台服务已启动")
} catch (e: Exception) {
Log.e(tag, "启动前台服务失败: ${e.message}")
continuousCallback?.onError(1003, "启动前台服务失败: ${e.message}")
return false
}
}
if (!isRecognizerValid()) { if (!isRecognizerValid()) {
Log.d(tag, "重新启动连续识别") Log.d(tag, "重新启动连续识别")
val callback = continuousCallback val callback = continuousCallback
@ -393,6 +409,18 @@ class AzureAsrHelper(private val context: Context) {
recognizer?.close() recognizer?.close()
recognizer = null recognizer = null
audioStream.isContinuousRecognitionActive = false audioStream.isContinuousRecognitionActive = false
// 停止前台服务
if (isForegroundServiceRunning) {
try {
AudioRecordingForegroundService.stopService(context)
isForegroundServiceRunning = false
Log.d(tag, "前台服务已停止")
} catch (e: Exception) {
Log.e(tag, "停止前台服务失败: ${e.message}")
}
}
return true return true
} catch (e: Exception) { } catch (e: Exception) {
// 强制重置状态 // 强制重置状态
@ -404,6 +432,17 @@ class AzureAsrHelper(private val context: Context) {
recognizer?.close() recognizer?.close()
recognizer = null recognizer = null
audioStream.isContinuousRecognitionActive = false audioStream.isContinuousRecognitionActive = false
// 确保前台服务被停止
if (isForegroundServiceRunning) {
try {
AudioRecordingForegroundService.stopService(context)
isForegroundServiceRunning = false
} catch (e2: Exception) {
Log.e(tag, "强制停止前台服务失败: ${e2.message}")
}
}
return false return false
} }
} }
@ -442,6 +481,18 @@ class AzureAsrHelper(private val context: Context) {
// 释放音频配置 // 释放音频配置
audioConfig?.close() audioConfig?.close()
audioConfig = null audioConfig = null
// 确保前台服务被停止
if (isForegroundServiceRunning) {
try {
AudioRecordingForegroundService.stopService(context)
isForegroundServiceRunning = false
Log.d(tag, "dispose时前台服务已停止")
} catch (e: Exception) {
Log.e(tag, "dispose时停止前台服务失败: ${e.message}")
}
}
// 确保状态被重置 // 确保状态被重置
audioStream.isContinuousRecognitionActive = false audioStream.isContinuousRecognitionActive = false
@ -451,6 +502,7 @@ class AzureAsrHelper(private val context: Context) {
audioConfig = null audioConfig = null
recognizer = null recognizer = null
speechConfig = null speechConfig = null
isForegroundServiceRunning = false
} }
} }
@ -479,11 +531,23 @@ class AzureAsrHelper(private val context: Context) {
*/ */
fun enableRecord(audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE,filePath: String, audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = null) { fun enableRecord(audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE,filePath: String, audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = null) {
Log.i(tag, "开启录音:") Log.i(tag, "开启录音:")
this.audioSourceType = audioSourceType this.audioSourceType = audioSourceType
if(audioSourceType == AudioSourceType.EXTERNAL){ if(audioSourceType == AudioSourceType.EXTERNAL){
Log.i(tag, "外部音频源不在这里录音") Log.i(tag, "外部音频源不在这里录音")
return return
} }
// 启动前台服务(在开始录音前)
if (!isForegroundServiceRunning) {
try {
AudioRecordingForegroundService.startService(context)
isForegroundServiceRunning = true
Log.d(tag, "录音前台服务已启动")
} catch (e: Exception) {
Log.e(tag, "启动录音前台服务失败: ${e.message}")
}
}
// Initialize if not already done // Initialize if not already done
if (!audioStream.isInitialized()) { if (!audioStream.isInitialized()) {
audioStream.initAudioRecord() audioStream.initAudioRecord()
@ -574,6 +638,17 @@ class AzureAsrHelper(private val context: Context) {
if (!audioStream.isContinuousRecognitionActive) { if (!audioStream.isContinuousRecognitionActive) {
audioStream.stopMicrophoneCapture() audioStream.stopMicrophoneCapture()
// 停止录音时也停止前台服务(如果没有其他音频任务)
if (isForegroundServiceRunning) {
try {
AudioRecordingForegroundService.stopService(context)
isForegroundServiceRunning = false
Log.d(tag, "录音前台服务已停止")
} catch (e: Exception) {
Log.e(tag, "停止录音前台服务失败: ${e.message}")
}
}
} }
audioStream.isRecord = false audioStream.isRecord = false

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

@ -17,7 +17,7 @@ import android.media.AudioFocusRequest
import android.media.AudioAttributes import android.media.AudioAttributes
import com.yunqiinnovation.azure_speech.tools.RecordFile import com.yunqiinnovation.azure_speech.tools.RecordFile
import android.os.Build // 新增:用于获取设备信息 import android.os.Build // 新增:用于获取设备信息
import com.yunqiinnovation.azure_speech.tools.AudioRecordingForegroundService
/** /**
* 简单音频接收器类,用于处理音频录制和流传输 * 简单音频接收器类,用于处理音频录制和流传输
@ -219,8 +219,6 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
pushAudioStream?.close() pushAudioStream?.close()
pushAudioStream = null pushAudioStream = null
isWriting.set(false) isWriting.set(false)
// 停止前台服务
AudioRecordingForegroundService.stopService(context)
throw e // 重新抛出异常让调用者处理 throw e // 重新抛出异常让调用者处理
} }
} }
@ -296,8 +294,7 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
*/ */
private fun runMicrophoneCapture() { private fun runMicrophoneCapture() {
try { try {
// 启动前台服务
AudioRecordingForegroundService.startService(context)
Log.d(TAG, "前台服务已启动,开始音频录制") Log.d(TAG, "前台服务已启动,开始音频录制")
// 根据手机品牌决定是否设置通话音频模式 // 根据手机品牌决定是否设置通话音频模式
if (isHonorDevice()) { if (isHonorDevice()) {
@ -410,8 +407,7 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
try { try {
writeQueue.clear() writeQueue.clear()
isWriting.set(false) isWriting.set(false)
// 停止前台服务
AudioRecordingForegroundService.stopService(context)
// 停止录音 // 停止录音
@ -431,8 +427,7 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
try { try {
isRunning.set(false) isRunning.set(false)
isWriting.set(false) isWriting.set(false)
// 停止前台服务
AudioRecordingForegroundService.stopService(context)
Log.d(TAG, "前台服务已停止,音频资源已释放") Log.d(TAG, "前台服务已停止,音频资源已释放")
// 中断并等待捕获线程结束 // 中断并等待捕获线程结束
writeThread?.interrupt() writeThread?.interrupt()

2
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCompanionManager.kt

@ -396,7 +396,7 @@ class BleCompanionManager(private val context: Context) {
val filter = ScanFilter.Builder() val filter = ScanFilter.Builder()
.setDeviceAddress(mac) .setDeviceAddress(mac)
// .setServiceUuid(ParcelUuid(java.util.UUID.fromString("0000FEAA-0000-1000-8000-00805F9B34FB"))) // .setServiceUuid(ParcelUuid(java.util.UUID.fromString("0000FEAA-0000-1000-8000-00805F9B34FB")))
.setManufacturerData(0xEFAB, byteArrayOf(0xA0.toByte(), 0x01)) //.setManufacturerData(0xEFAB, byteArrayOf(0xA0.toByte(), 0x01))
.build() .build()
// 扫描设置 // 扫描设置

78
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/CustomSseClientTransport.kt

@ -86,7 +86,7 @@ class CustomSseClientTransport(
// 路径部分 // 路径部分
val path = urlObj.path val path = urlObj.path
Log.e(TAG, "$serviceidString 解析成功: hostUrl:$hostUrl,path:$path,params:$params")
Triple(hostUrl, path, params) Triple(hostUrl, path, params)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "$serviceidString 解析URL失败: $url, ${e.message}") Log.e(TAG, "$serviceidString 解析URL失败: $url, ${e.message}")
@ -101,9 +101,10 @@ class CustomSseClientTransport(
job = scope.launch(CoroutineName("CustomSseMcpClientTransport.collect#${hashCode()}")) { job = scope.launch(CoroutineName("CustomSseMcpClientTransport.collect#${hashCode()}")) {
try { try {
session.incoming.collect { event -> session.incoming.collect { event ->
Log.e(TAG, "$serviceidString event:${event.event}-${event.data}")
when (event.event) { when (event.event) {
"error" -> { "error" -> {
Log.e(TAG, "$serviceidString SSE错误: ${event.data}") // Log.e(TAG, "$serviceidString SSE错误: ${event.data}")
isConnected.set(false) isConnected.set(false)
val exception = Exception("$serviceidString SSE Error: ${event.data}") val exception = Exception("$serviceidString SSE Error: ${event.data}")
_onError(exception) _onError(exception)
@ -113,7 +114,7 @@ class CustomSseClientTransport(
"open" -> { "open" -> {
// SSE连接已打开 // SSE连接已打开
Log.d(TAG, "$serviceidString SSE连接已打开") // Log.d(TAG, "$serviceidString SSE连接已打开")
isConnected.set(true) isConnected.set(true)
} }
"ping" -> { "ping" -> {
@ -122,29 +123,73 @@ class CustomSseClientTransport(
"endpoint" -> { "endpoint" -> {
try { try {
val eventData = event.data ?: "" val eventData = event.data ?: ""
// 构建完整的端点URL // 构建完整的端点URL
val fullEndpoint = if (eventData.contains(hostPart)) { val fullEndpoint = when {
eventData // 如果eventData已经包含完整的URL(包含协议)
} else if (eventData.startsWith("/")) { eventData.startsWith("http://") || eventData.startsWith("https://") -> {
"$hostPart$eventData" eventData
} else { }
eventData // 如果eventData包含hostPart但没有协议
eventData.contains(hostPart.substringAfter("://")) -> {
if (eventData.startsWith("://")) {
"${hostPart.substringBefore("://")}$eventData"
} else {
"https://$eventData"
}
}
// 如果eventData以/开头,说明是路径
eventData.startsWith("/") -> {
"$hostPart$eventData"
}
// 如果eventData不为空但不包含完整信息,尝试构建完整URL
eventData.isNotEmpty() -> {
if (hostPart.isNotEmpty()) {
// 如果eventData看起来像是路径片段,添加/前缀
val path = if (!eventData.startsWith("/")) "/" + eventData else eventData
"$hostPart$path"
} else {
// 如果没有hostPart,尝试添加默认协议
if (!eventData.contains("://")) {
"https://$eventData"
} else {
eventData
}
}
}
// 如果eventData为空,使用hostPart
else -> hostPart
}
// 验证URL格式
val validatedEndpoint = try {
val url = java.net.URL(fullEndpoint)
fullEndpoint
} catch (e: Exception) {
Log.w(TAG, "$serviceidString 端点URL格式可能有问题: $fullEndpoint, 尝试修复")
// 尝试修复URL
when {
!fullEndpoint.startsWith("http://") && !fullEndpoint.startsWith("https://") -> {
"https://$fullEndpoint"
}
else -> fullEndpoint
}
} }
// 添加查询参数 // 添加查询参数
val endpointWithParams = if (queryParams.isNotEmpty()) { val endpointWithParams = if (queryParams.isNotEmpty()) {
if (fullEndpoint.contains("?")) { if (validatedEndpoint.contains("?")) {
val queryString = queryParams.entries.joinToString("&") { "${it.key}=${it.value}" } val queryString = queryParams.entries.joinToString("&") { "${it.key}=${it.value}" }
"$fullEndpoint&$queryString" "$validatedEndpoint&$queryString"
} else { } else {
val queryString = queryParams.entries.joinToString("&") { "${it.key}=${it.value}" } val queryString = queryParams.entries.joinToString("&") { "${it.key}=${it.value}" }
"$fullEndpoint?$queryString" "$validatedEndpoint?$queryString"
} }
} else { } else {
fullEndpoint validatedEndpoint
} }
Log.d(TAG, "$serviceidString 处理endpoint事件: $endpointWithParams")
endpoint.complete(endpointWithParams) endpoint.complete(endpointWithParams)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "$serviceidString 处理endpoint事件失败: ${e.message}", e) Log.e(TAG, "$serviceidString 处理endpoint事件失败: ${e.message}", e)
@ -174,7 +219,7 @@ class CustomSseClientTransport(
} }
} }
} catch (e: CancellationException) { } catch (e: CancellationException) {
Log.d(TAG, "$serviceidString SSE事件收集被取消") Log.e(TAG, "$serviceidString SSE事件收集被取消 : ${e.message}", e)
throw e throw e
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "$serviceidString SSE连接异常断开: ${e.message}", e) Log.e(TAG, "$serviceidString SSE连接异常断开: ${e.message}", e)
@ -219,11 +264,12 @@ class CustomSseClientTransport(
* 启动传输层 * 启动传输层
*/ */
override suspend fun start() { override suspend fun start() {
if (!initialized.compareAndSet(false, true)) { if (!initialized.compareAndSet(false, true)) {
Log.e(TAG, "$serviceidString 传输层已经启动,不能重复启动") Log.e(TAG, "$serviceidString 传输层已经启动,不能重复启动")
error("CustomSseClientTransport already started!") error("CustomSseClientTransport already started!")
} }
Log.d(TAG, "CustomSseClientTransport $serviceidString 启动")
// 解析URL // 解析URL
if (urlString != null) { if (urlString != null) {
val urlInfo = parseUrl(urlString) val urlInfo = parseUrl(urlString)

1
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt

@ -1352,6 +1352,7 @@ private class UpdateUserPortraitHandler(private val context: Context?) : Functio
context.sendBroadcast(intent) context.sendBroadcast(intent)
// 发送广播通知退出交互 // 发送广播通知退出交互
return JSONObject().apply { return JSONObject().apply {
put("nocard", true)
put("result", "更新成功!") put("result", "更新成功!")
}.toString() }.toString()
} }

Loading…
Cancel
Save