From 59f8a56740e1feb0caaecf81657810fc4a4ec283 Mon Sep 17 00:00:00 2001 From: wolfplus Date: Mon, 24 Mar 2025 12:31:11 +0000 Subject: [PATCH] add --- ios/Runner/AppDelegate.swift | 2 +- ios/Runner/AudioSessionManager.swift | 145 ++++++++++-------- ios/Runner/AzureAsrHelper.swift | 33 +++- ios/Runner/AzureTtsHelper.swift | 4 +- .../chat/controllers/chat_controller.dart | 40 +++-- .../controllers/translation_controller.dart | 16 +- 6 files changed, 151 insertions(+), 89 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 2cf84e34d..3bfd2c086 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -588,7 +588,7 @@ class AsrStreamHandler: NSObject, FlutterStreamHandler, AsrEventHandler { // 实现AsrEventHandler协议 func sendEvent(_ event: [String: Any]) { - NSLog("AsrStreamHandler: 收到事件 \(event)") + // NSLog("AsrStreamHandler: 收到事件 \(event)") DispatchQueue.main.async { [weak self] in self?.eventSink?(event) } diff --git a/ios/Runner/AudioSessionManager.swift b/ios/Runner/AudioSessionManager.swift index 011c32c45..feaeee830 100644 --- a/ios/Runner/AudioSessionManager.swift +++ b/ios/Runner/AudioSessionManager.swift @@ -264,12 +264,25 @@ import UIKit if !isConfigured { NSLog("[AudioSessionManager] 警告: 尝试激活未配置的音频会话,应用默认配置") // 未配置,应用默认配置 - try configureSession( - category: .playAndRecord, - mode: .spokenAudio, - options: [.allowBluetooth, .allowBluetoothA2DP, .duckOthers, .defaultToSpeaker], - force: true + // try configureSession( + // category: .playAndRecord, + // mode: .spokenAudio, + // options: [.allowBluetooth, .allowBluetoothA2DP, .duckOthers, .defaultToSpeaker], + // force: true + // ) + + try configureSession( + category: .playAndRecord, // 允许同时录音和播放 + mode: .voiceChat, // 使用voiceChat模式获得最佳回音消除效果 + options: [ + .allowBluetooth, // 允许蓝牙设备 + .defaultToSpeaker, // 默认使用扬声器 + .mixWithOthers // 允许与其他应用混音 + ], + force: false // 是否强制重新配置 ) + + } else if !isActive { NSLog("[AudioSessionManager] 激活已配置的音频会话") try audioSession.setActive(true, options: .notifyOthersOnDeactivation) @@ -289,12 +302,24 @@ import UIKit /// 重新配置当前会话 @objc func forceReconfigure() throws { NSLog("[AudioSessionManager] 强制重新配置音频会话") - try configureSession( - category: currentCategory ?? .playAndRecord, - mode: currentMode ?? .default, - options: currentOptions ?? [.allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker], - force: true - ) + // try configureSession( + // category: currentCategory ?? .playAndRecord, + // mode: currentMode ?? .default, + // options: currentOptions ?? [.allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker], + // force: true + // ) + + try configureSession( + category: .playAndRecord, // 允许同时录音和播放 + mode: .voiceChat, // 使用voiceChat模式获得最佳回音消除效果 + options: [ + .allowBluetooth, // 允许蓝牙设备 + .defaultToSpeaker, // 默认使用扬声器 + .mixWithOthers // 允许与其他应用混音 + ], + force: false // 是否强制重新配置 + ) + } // MARK: - 状态检查 @@ -420,60 +445,60 @@ import UIKit - /// 为Azure语音识别专门配置的音频会话 - /// 此函数优化Azure语音识别的音频处理配置,提供更强的回音消除能力 - /// - Parameters: - /// - force: 是否强制重新配置 - /// - configureAdditionalSettings: 配置完成后进行额外的音频设置 - /// - Returns: 配置是否成功 - @objc func configureForAzureSpeechRecognition(force: Bool = false, configureAdditionalSettings: Bool = true) -> Bool { - NSLog("[AudioSessionManager] 配置Azure语音识别的音频会话") - do { - // 配置音频会话以优化语音识别 - try configureSession( - category: .playAndRecord, // 允许同时录音和播放 - mode: .voiceChat, // 使用voiceChat模式获得最佳回音消除效果 - options: [ - .allowBluetooth, // 允许蓝牙设备 - .defaultToSpeaker, // 默认使用扬声器 - .mixWithOthers // 允许与其他应用混音 - ], - force: force // 是否强制重新配置 - ) + // /// 为Azure语音识别专门配置的音频会话 + // /// 此函数优化Azure语音识别的音频处理配置,提供更强的回音消除能力 + // /// - Parameters: + // /// - force: 是否强制重新配置 + // /// - configureAdditionalSettings: 配置完成后进行额外的音频设置 + // /// - Returns: 配置是否成功 + // @objc func configureForAzureSpeechRecognition(force: Bool = false, configureAdditionalSettings: Bool = true) -> Bool { + // NSLog("[AudioSessionManager] 配置Azure ASR语音识别的音频会话") + // do { + // // 配置音频会话以优化语音识别 + // try configureSession( + // category: .playAndRecord, // 允许同时录音和播放 + // mode: .voiceChat, // 使用voiceChat模式获得最佳回音消除效果 + // options: [ + // .allowBluetooth, // 允许蓝牙设备 + // .defaultToSpeaker, // 默认使用扬声器 + // .mixWithOthers // 允许与其他应用混音 + // ], + // force: force // 是否强制重新配置 + // ) - // 额外的优化配置 - if configureAdditionalSettings { - // 获取当前是否使用耳机 - let currentRoute = audioSession.currentRoute - let hasHeadphones = currentRoute.outputs.contains { - $0.portType == .headphones || $0.portType == .bluetoothA2DP || $0.portType == .bluetoothHFP - } + // // 额外的优化配置 + // if configureAdditionalSettings { + // // 获取当前是否使用耳机 + // let currentRoute = audioSession.currentRoute + // let hasHeadphones = currentRoute.outputs.contains { + // $0.portType == .headphones || $0.portType == .bluetoothA2DP || $0.portType == .bluetoothHFP + // } - // 设置采样率为16kHz(Azure Speech API推荐) - try audioSession.setPreferredSampleRate(16000.0) + // // 设置采样率为16kHz(Azure Speech API推荐) + // try audioSession.setPreferredSampleRate(16000.0) - // 设置较小的缓冲区大小以减少延迟 - try audioSession.setPreferredIOBufferDuration(0.01) + // // 设置较小的缓冲区大小以减少延迟 + // try audioSession.setPreferredIOBufferDuration(0.01) - // 根据是否有耳机连接调整输入增益 - if !hasHeadphones { - // 无耳机时降低输入增益以减少回音 - try audioSession.setInputGain(0.8) - NSLog("[AudioSessionManager] 启用扬声器回音消除优化") - } else { - // 使用耳机时可以使用较高增益 - try audioSession.setInputGain(1.0) - NSLog("[AudioSessionManager] 检测到耳机连接,应用耳机模式") - } - } + // // 根据是否有耳机连接调整输入增益 + // if !hasHeadphones { + // // 无耳机时降低输入增益以减少回音 + // try audioSession.setInputGain(0.8) + // NSLog("[AudioSessionManager] 启用扬声器回音消除优化") + // } else { + // // 使用耳机时可以使用较高增益 + // try audioSession.setInputGain(1.0) + // NSLog("[AudioSessionManager] 检测到耳机连接,应用耳机模式") + // } + // } - NSLog("[AudioSessionManager] 已成功配置Azure语音识别的音频会话") - return true - } catch { - NSLog("[AudioSessionManager] 配置Azure语音识别的音频会话失败: \(error.localizedDescription)") - return false - } - } + // NSLog("[AudioSessionManager] 已成功配置Azure语音识别的音频会话") + // return true + // } catch { + // NSLog("[AudioSessionManager] 配置Azure语音识别的音频会话失败: \(error.localizedDescription)") + // return false + // } + // } @objc func configureForVoiceInteraction(force: Bool = false, configureAdditionalSettings: Bool = true) -> Bool { diff --git a/ios/Runner/AzureAsrHelper.swift b/ios/Runner/AzureAsrHelper.swift index e43850165..7eb272c50 100644 --- a/ios/Runner/AzureAsrHelper.swift +++ b/ios/Runner/AzureAsrHelper.swift @@ -8,6 +8,9 @@ import AudioToolbox class AzureAsrHelper: NSObject { // MARK: - 属性 + /// 是否使用自定义音频处理器 - 设置为false时将使用系统默认麦克风输入 + private let useCustomAudioProcessor = false + /// 事件处理回调 private var eventHandler: (([String: Any]) -> Void)? @@ -115,12 +118,18 @@ class AzureAsrHelper: NSObject { // 设置音频输入参数 try setupAudioSession() - // 创建自定义音频流和处理器,替代默认的麦克风输入 - pushStream = try SPXPushAudioInputStream() - audioConfig = try SPXAudioConfiguration(streamInput: pushStream!) - - // 初始化自定义音频处理器 - audioProcessor = CustomAudioProcessor() + // 根据设置决定是否使用自定义音频处理器 + if useCustomAudioProcessor { + // 创建自定义音频流和处理器,替代默认的麦克风输入 + pushStream = try SPXPushAudioInputStream() + audioConfig = try SPXAudioConfiguration(streamInput: pushStream!) + + // 初始化自定义音频处理器 + audioProcessor = CustomAudioProcessor() + } else { + // 使用默认麦克风输入 + audioConfig = try SPXAudioConfiguration() + } // 设置语言配置 if isAutoDetectLanguage { @@ -160,7 +169,7 @@ class AzureAsrHelper: NSObject { print("[AzureAsrHelper] 开始配置音频会话...") // 使用AudioSessionManager配置音频会话,使用专门为Azure ASR优化的配置 - let success = audioSessionManager.configureForAzureSpeechRecognition(force: true) + let success = audioSessionManager.configureForVoiceInteraction() if !success { print("[AzureAsrHelper] 警告: 通过AudioSessionManager配置音频会话失败") } @@ -480,6 +489,11 @@ class AzureAsrHelper: NSObject { /// 开始音频处理 private func startAudioProcessing() { + // 如果未使用自定义音频处理器,跳过 + if !useCustomAudioProcessor { + return + } + guard !isProcessingAudio, let audioProcessor = audioProcessor else { return } isProcessingAudio = true @@ -516,6 +530,11 @@ class AzureAsrHelper: NSObject { /// 停止音频处理 private func stopAudioProcessing() { + // 如果未使用自定义音频处理器,跳过 + if !useCustomAudioProcessor { + return + } + // 停止定时器 audioProcessingTimer?.invalidate() audioProcessingTimer = nil diff --git a/ios/Runner/AzureTtsHelper.swift b/ios/Runner/AzureTtsHelper.swift index 9d4a94e70..f62c3f579 100644 --- a/ios/Runner/AzureTtsHelper.swift +++ b/ios/Runner/AzureTtsHelper.swift @@ -258,7 +258,7 @@ class AzureTtsHelper: NSObject { _isSpeaking = true eventHandler?(["type": "started"]) - // Task { + Task { do { // 使用异步方法进行合成并直接播放 _ = try synthesizer.startSpeakingSsml(text) @@ -270,7 +270,7 @@ class AzureTtsHelper: NSObject { self.eventHandler?(["type": "error", "message": "语音合成失败: \(error.localizedDescription)"]) } } - // } + } return true } diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index bcad7634a..6bc878c8f 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -48,6 +48,9 @@ class ChatController extends GetxController { // 临时消息索引,用于更新临时消息 int _tempMessageIndex = -1; + + // 防抖计时器 + Timer? _scrollDebounceTimer; // 流式响应控制 StreamSubscription? _aiResponseSubscription; @@ -201,6 +204,7 @@ class ChatController extends GetxController { _recognitionSubscription?.cancel(); _voiceInteractionSubscription?.cancel(); _autoStopTimer?.cancel(); + _scrollDebounceTimer?.cancel(); // 清理防抖计时器 // 停止TTS _ttsService.stop(); @@ -209,22 +213,28 @@ class ChatController extends GetxController { _asrService.stopContinuousRecognition(); } + // 保存当前聊天记录 + if (agent.id != null) { + _historyService.saveHistory(agent.id!, messages.toList()); + } + super.onClose(); } - /// 滚动到列表底部方法 + /// 滚动到列表底部方法,添加防抖功能 void _scrollToBottom() { - if (scrollController.hasClients && messages.isNotEmpty) { - try { - scrollController.animateTo( - scrollController.position.maxScrollExtent + 20, - duration: Duration(milliseconds: 300), - curve: Curves.easeOut, - ); - } catch (e) { - Logger.error('滚动到底部失败', e); + + if (scrollController.hasClients && messages.isNotEmpty) { + try { + scrollController.animateTo( + scrollController.position.maxScrollExtent + 30, + duration: Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } catch (e) { + Logger.error('滚动到底部失败', e); + } } - } } /// 中断当前响应 @@ -539,9 +549,15 @@ class ChatController extends GetxController { messages.add(tempMessage); _tempMessageIndex = messages.length - 1; messages.refresh(); + _scrollToBottom(); } + // 取消之前的计时器 + _scrollDebounceTimer?.cancel(); - _scrollToBottom(); + // 设置新的防抖计时器,延迟100毫秒执行滚动 + _scrollDebounceTimer = Timer(Duration(milliseconds: 500), () { + _scrollToBottom(); + }); } // 处理语音识别最终结果 diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index f46d48bca..940966590 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -244,7 +244,7 @@ class TranslationController extends GetxController { // 如果还没有翻译结果,尝试翻译 if (translationHistory.last.translatedText.isEmpty) { - await translateText(translationHistory.last.sourceText, translationHistory.last.timestamp); + await translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: true); } saveTranslationHistory(); @@ -288,7 +288,7 @@ class TranslationController extends GetxController { _translationDebounceTimer?.cancel(); _translationDebounceTimer = Timer(const Duration(milliseconds: 300), () { if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) { - translateText(translationHistory.last.sourceText, translationHistory.last.timestamp); + translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: false); } }); } @@ -307,7 +307,7 @@ class TranslationController extends GetxController { saveTranslationHistory(); // 使用项目的时间戳进行翻译 - translateText(text, translationHistory.last.timestamp); + translateText(text, translationHistory.last.timestamp, isFinal: true); } else { // 如果最后一项不是中间结果,创建新项 final newItem = TranslationItem( @@ -326,13 +326,14 @@ class TranslationController extends GetxController { _scrollToBottom(); saveTranslationHistory(); + // 使用新项目的时间戳进行翻译 - translateText(text, newItem.timestamp); + translateText(text, newItem.timestamp, isFinal: true); } } // 翻译文本 - Future translateText(String sourceText, DateTime timestamp) async { + Future translateText(String sourceText, DateTime timestamp, {bool isFinal = false}) async { if (sourceText.isEmpty) return null; isTranslating.value = true; @@ -367,8 +368,9 @@ class TranslationController extends GetxController { // 更新后滚动到底部 _scrollToBottom(); - // 播放TTS - if (isTtsEnabled.value && !translationHistory[index].isIntermediate) { + // 播放TTS - 只在最终结果时播放 + if (isTtsEnabled.value && isFinal) { + // Logger.info('播放翻译文本: ${translationResult}'); await playTranslatedText(translationResult); } } else {