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:classic_bluetooth/classic_bluetooth.dart';
import '../../core/utils/logger.dart';
import '../../routes/app_routes.dart';
// 重新导出蓝牙设备类型,让外部可以通过BluetoothManager访问
export 'package:classic_bluetooth/classic_bluetooth.dart' show BluetoothDevice;
@ -58,6 +59,7 @@ class BluetoothManager extends GetxService {
try {
_bluetoothSubscription = ClassicBluetooth.events.listen(
(event) {
print("_setupBluetoothListener$event");
if (event.type == 'stateChanged' && event.state != null) {
_bluetoothState.value = event.state!;
Logger.info('蓝牙状态变更: ${event.state}');
@ -70,9 +72,15 @@ class BluetoothManager extends GetxService {
// 直接从事件中获取设备信息
final newDevice = event.device!;
// 更新当前设备
currentDeviceRx.value = newDevice;
// 检查当前是否在OTA升级界面,如果是则不跳转
if (!_isInOtaUpgradePage()) {
Get.toNamed(Routes.pairing, arguments: newDevice);
} else {
Logger.info('当前在OTA升级界面,跳过配对页面跳转');
}
// 正常模式:更新当前设备并触发监听器
Logger.info('经典蓝牙设备已连接: ${newDevice.name} (${newDevice.address})');
} else if (event.type == 'deviceDisconnected' &&
event.device != null) {
@ -138,4 +146,16 @@ class BluetoothManager extends GetxService {
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配对
if (_verifiedDeviceAddress != null) {
await _bleManager.connect();
// final success = await _bleManager.registerBackgroundScan(
// macAddress: _verifiedDeviceAddress);
// if (!success && _currentState.value == PairingState.waitingBle) {
// _setError(
// ErrorType.bleTimeout, 'blePairingStartupFailed'.tr); //BLE配对启动失败
// }
// await _bleManager.connect();
final success = await _bleManager.registerBackgroundScan(
macAddress: _verifiedDeviceAddress);
if (!success && _currentState.value == PairingState.waitingBle) {
_setError(
ErrorType.bleTimeout, 'blePairingStartupFailed'.tr); //BLE配对启动失败
}
}
} catch (e) {
Logger.e(_tag, 'BLE连接失败: $e');
@ -1037,7 +1037,7 @@ class PairingController extends GetxController with WidgetsBindingObserver {
try {
Logger.i(_tag, '启动AgentService服务');
await _bleManager.startAgentService();
_bleManager.registerBackgroundScan(macAddress: _verifiedDeviceAddress);
// _bleManager.registerBackgroundScan(macAddress: _verifiedDeviceAddress);
} catch (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,
onTap: () async {
Get.back();
await controller.stopAll();
await controller.changeTranslationMode('simultaneous');
await controller.startRecognition();
},
@ -1084,6 +1085,7 @@ class TranslationView extends GetView<TranslationController> {
Colors.green,
onTap: () async {
Get.back();
await controller.stopAll();
await controller.changeTranslationMode('faceToFace');
await controller.startRecognition();
},
@ -1096,6 +1098,7 @@ class TranslationView extends GetView<TranslationController> {
onTap: controller.bleManager.isConnected
? () async {
Get.back();
await controller.stopAll();
await controller.changeTranslationMode('audioVideo');
await controller.startRecognition();
}
@ -1109,6 +1112,7 @@ class TranslationView extends GetView<TranslationController> {
onTap: controller.bleManager.isConnected
? () async {
Get.back();
await controller.stopAll();
await controller.changeTranslationMode('call');
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 com.yunqiinnovation.azure_speech.tools.RecordFile
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 continuousCallback: ContinuousRecognizeCallback? = null
// 添加前台服务管理标志
private var isForegroundServiceRunning = false
// 配置参数
private var currentLanguage = "zh-CN"
private var supportedLanguages = arrayOf("zh-CN")
@ -247,6 +249,20 @@ class AzureAsrHelper(private val context: Context) {
if (audioStream.isContinuousRecognitionActive) {
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()) {
Log.d(tag, "重新启动连续识别")
val callback = continuousCallback
@ -393,6 +409,18 @@ class AzureAsrHelper(private val context: Context) {
recognizer?.close()
recognizer = null
audioStream.isContinuousRecognitionActive = false
// 停止前台服务
if (isForegroundServiceRunning) {
try {
AudioRecordingForegroundService.stopService(context)
isForegroundServiceRunning = false
Log.d(tag, "前台服务已停止")
} catch (e: Exception) {
Log.e(tag, "停止前台服务失败: ${e.message}")
}
}
return true
} catch (e: Exception) {
// 强制重置状态
@ -404,6 +432,17 @@ class AzureAsrHelper(private val context: Context) {
recognizer?.close()
recognizer = null
audioStream.isContinuousRecognitionActive = false
// 确保前台服务被停止
if (isForegroundServiceRunning) {
try {
AudioRecordingForegroundService.stopService(context)
isForegroundServiceRunning = false
} catch (e2: Exception) {
Log.e(tag, "强制停止前台服务失败: ${e2.message}")
}
}
return false
}
}
@ -442,6 +481,18 @@ class AzureAsrHelper(private val context: Context) {
// 释放音频配置
audioConfig?.close()
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
@ -451,6 +502,7 @@ class AzureAsrHelper(private val context: Context) {
audioConfig = null
recognizer = 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) {
Log.i(tag, "开启录音:")
this.audioSourceType = audioSourceType
this.audioSourceType = audioSourceType
if(audioSourceType == AudioSourceType.EXTERNAL){
Log.i(tag, "外部音频源不在这里录音")
Log.i(tag, "外部音频源不在这里录音")
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
if (!audioStream.isInitialized()) {
audioStream.initAudioRecord()
@ -574,6 +638,17 @@ class AzureAsrHelper(private val context: Context) {
if (!audioStream.isContinuousRecognitionActive) {
audioStream.stopMicrophoneCapture()
// 停止录音时也停止前台服务(如果没有其他音频任务)
if (isForegroundServiceRunning) {
try {
AudioRecordingForegroundService.stopService(context)
isForegroundServiceRunning = false
Log.d(tag, "录音前台服务已停止")
} catch (e: Exception) {
Log.e(tag, "停止录音前台服务失败: ${e.message}")
}
}
}
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 com.yunqiinnovation.azure_speech.tools.RecordFile
import android.os.Build // 新增:用于获取设备信息
import com.yunqiinnovation.azure_speech.tools.AudioRecordingForegroundService
/**
* 简单音频接收器类,用于处理音频录制和流传输
@ -219,8 +219,6 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
pushAudioStream?.close()
pushAudioStream = null
isWriting.set(false)
// 停止前台服务
AudioRecordingForegroundService.stopService(context)
throw e // 重新抛出异常让调用者处理
}
}
@ -296,8 +294,7 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
*/
private fun runMicrophoneCapture() {
try {
// 启动前台服务
AudioRecordingForegroundService.startService(context)
Log.d(TAG, "前台服务已启动,开始音频录制")
// 根据手机品牌决定是否设置通话音频模式
if (isHonorDevice()) {
@ -410,8 +407,7 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
try {
writeQueue.clear()
isWriting.set(false)
// 停止前台服务
AudioRecordingForegroundService.stopService(context)
// 停止录音
@ -431,8 +427,7 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
try {
isRunning.set(false)
isWriting.set(false)
// 停止前台服务
AudioRecordingForegroundService.stopService(context)
Log.d(TAG, "前台服务已停止,音频资源已释放")
// 中断并等待捕获线程结束
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()
.setDeviceAddress(mac)
// .setServiceUuid(ParcelUuid(java.util.UUID.fromString("0000FEAA-0000-1000-8000-00805F9B34FB")))
.setManufacturerData(0xEFAB, byteArrayOf(0xA0.toByte(), 0x01))
//.setManufacturerData(0xEFAB, byteArrayOf(0xA0.toByte(), 0x01))
.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
Log.e(TAG, "$serviceidString 解析成功: hostUrl:$hostUrl,path:$path,params:$params")
Triple(hostUrl, path, params)
} catch (e: Exception) {
Log.e(TAG, "$serviceidString 解析URL失败: $url, ${e.message}")
@ -101,9 +101,10 @@ class CustomSseClientTransport(
job = scope.launch(CoroutineName("CustomSseMcpClientTransport.collect#${hashCode()}")) {
try {
session.incoming.collect { event ->
Log.e(TAG, "$serviceidString event:${event.event}-${event.data}")
when (event.event) {
"error" -> {
Log.e(TAG, "$serviceidString SSE错误: ${event.data}")
// Log.e(TAG, "$serviceidString SSE错误: ${event.data}")
isConnected.set(false)
val exception = Exception("$serviceidString SSE Error: ${event.data}")
_onError(exception)
@ -113,7 +114,7 @@ class CustomSseClientTransport(
"open" -> {
// SSE连接已打开
Log.d(TAG, "$serviceidString SSE连接已打开")
// Log.d(TAG, "$serviceidString SSE连接已打开")
isConnected.set(true)
}
"ping" -> {
@ -122,29 +123,73 @@ class CustomSseClientTransport(
"endpoint" -> {
try {
val eventData = event.data ?: ""
// 构建完整的端点URL
val fullEndpoint = if (eventData.contains(hostPart)) {
eventData
} else if (eventData.startsWith("/")) {
"$hostPart$eventData"
} else {
eventData
val fullEndpoint = when {
// 如果eventData已经包含完整的URL(包含协议)
eventData.startsWith("http://") || eventData.startsWith("https://") -> {
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()) {
if (fullEndpoint.contains("?")) {
if (validatedEndpoint.contains("?")) {
val queryString = queryParams.entries.joinToString("&") { "${it.key}=${it.value}" }
"$fullEndpoint&$queryString"
"$validatedEndpoint&$queryString"
} else {
val queryString = queryParams.entries.joinToString("&") { "${it.key}=${it.value}" }
"$fullEndpoint?$queryString"
"$validatedEndpoint?$queryString"
}
} else {
fullEndpoint
validatedEndpoint
}
Log.d(TAG, "$serviceidString 处理endpoint事件: $endpointWithParams")
endpoint.complete(endpointWithParams)
} catch (e: Exception) {
Log.e(TAG, "$serviceidString 处理endpoint事件失败: ${e.message}", e)
@ -174,7 +219,7 @@ class CustomSseClientTransport(
}
}
} catch (e: CancellationException) {
Log.d(TAG, "$serviceidString SSE事件收集被取消")
Log.e(TAG, "$serviceidString SSE事件收集被取消 : ${e.message}", e)
throw e
} catch (e: Exception) {
Log.e(TAG, "$serviceidString SSE连接异常断开: ${e.message}", e)
@ -219,11 +264,12 @@ class CustomSseClientTransport(
* 启动传输层
*/
override suspend fun start() {
if (!initialized.compareAndSet(false, true)) {
Log.e(TAG, "$serviceidString 传输层已经启动,不能重复启动")
error("CustomSseClientTransport already started!")
}
Log.d(TAG, "CustomSseClientTransport $serviceidString 启动")
// 解析URL
if (urlString != null) {
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)
// 发送广播通知退出交互
return JSONObject().apply {
put("nocard", true)
put("result", "更新成功!")
}.toString()
}

Loading…
Cancel
Save