diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 3ac160c31..2cf84e34d 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -188,10 +188,7 @@ import AVFoundation ) // 初始化Azure ASR辅助类 - azureAsrHelper = AzureAsrHelper { [weak self] eventName, eventData in - // 推送事件到Flutter - self?.sendAsrEvent(eventName: eventName, eventData: eventData) - } + azureAsrHelper = AzureAsrHelper() // 设置ASR方法处理器 azureAsrChannel.setMethodCallHandler { [weak self] (call, result) in @@ -246,7 +243,7 @@ import AVFoundation ) // 创建ASR流处理器 - let asrStreamHandler = AsrStreamHandler() + let asrStreamHandler = AsrStreamHandler(azureAsrHelper: azureAsrHelper as! AzureAsrHelper) azureAsrStreamChannel.setStreamHandler(asrStreamHandler) } @@ -260,10 +257,7 @@ import AVFoundation ) // 初始化Azure TTS辅助类 - azureTtsHelper = AzureTtsHelper { [weak self] eventName, eventData in - // 推送事件到Flutter - self?.sendTtsEvent(eventName: eventName, eventData: eventData) - } + azureTtsHelper = AzureTtsHelper() // 设置TTS方法处理器 azureTtsChannel.setMethodCallHandler { [weak self] (call, result) in @@ -343,7 +337,7 @@ import AVFoundation ) // 创建TTS流处理器 - let ttsStreamHandler = TtsStreamHandler() + let ttsStreamHandler = TtsStreamHandler(azureTtsHelper: azureTtsHelper as! AzureTtsHelper) azureTtsStreamChannel.setStreamHandler(ttsStreamHandler) } @@ -487,44 +481,6 @@ import AVFoundation let voiceInteractionStreamHandler = VoiceInteractionStreamHandler(voiceInteractionService: voiceInteractionService!) voiceInteractionStreamChannel.setStreamHandler(voiceInteractionStreamHandler) } - - // 存储ASR事件 - private var _cachedAsrEvent: [String: Any]? - - // 发送ASR事件 - @available(iOS 13.0, *) - private func sendAsrEvent(eventName: String, eventData: [String: Any]) { - // 调整格式:将event改为type,并将data的内容提升到顶层 - var eventMap = eventData - eventMap["type"] = eventName - - _cachedAsrEvent = eventMap - NotificationCenter.default.post( - name: Notification.Name("AzureAsrEvent"), - object: nil, - userInfo: eventMap - ) - } - - // 存储TTS事件 - private var _cachedTtsEvent: [String: Any]? - - // 发送TTS事件 - @available(iOS 13.0, *) - private func sendTtsEvent(eventName: String, eventData: [String: Any]) { - // 调整格式:将event改为type,并将data的内容提升到顶层 - var eventMap = eventData - eventMap["type"] = eventName - - _cachedTtsEvent = eventMap - NotificationCenter.default.post( - name: Notification.Name("AzureTtsEvent"), - object: nil, - userInfo: eventMap - ) - } - - } // 蓝牙事件流处理器 @@ -596,92 +552,90 @@ class MediaButtonStreamHandler: NSObject, FlutterStreamHandler, MediaButtonEvent } } +// ASR事件处理器协议 +@available(iOS 13.0, *) +protocol AsrEventHandler: AnyObject { + func sendEvent(_ event: [String: Any]) +} + // ASR事件流处理器 @available(iOS 13.0, *) -class AsrStreamHandler: NSObject, FlutterStreamHandler { +class AsrStreamHandler: NSObject, FlutterStreamHandler, AsrEventHandler { + private let azureAsrHelper: AzureAsrHelper private var eventSink: FlutterEventSink? - private var notificationObserver: Any? - deinit { - if let observer = notificationObserver { - NotificationCenter.default.removeObserver(observer) + init(azureAsrHelper: AzureAsrHelper) { + self.azureAsrHelper = azureAsrHelper + super.init() + + // 设置事件处理器 + azureAsrHelper.setEventHandler { [weak self] event in + self?.sendEvent(event) } } func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { + NSLog("AsrStreamHandler: 开始监听ASR事件") self.eventSink = events - - // 注册通知观察者 - notificationObserver = NotificationCenter.default.addObserver( - forName: Notification.Name("AzureAsrEvent"), - object: nil, - queue: .main) { [weak self] notification in - if let userInfo = notification.userInfo as? [String: Any], let eventSink = self?.eventSink { - // 确保在主线程上发送事件 - DispatchQueue.main.async { - eventSink(userInfo) - } - } - } - return nil } func onCancel(withArguments arguments: Any?) -> FlutterError? { + NSLog("AsrStreamHandler: 取消监听ASR事件") eventSink = nil - - // 移除通知观察者 - if let observer = notificationObserver { - NotificationCenter.default.removeObserver(observer) - notificationObserver = nil - } - return nil } + + // 实现AsrEventHandler协议 + func sendEvent(_ event: [String: Any]) { + NSLog("AsrStreamHandler: 收到事件 \(event)") + DispatchQueue.main.async { [weak self] in + self?.eventSink?(event) + } + } +} + +// TTS事件处理器协议 +@available(iOS 13.0, *) +protocol TtsEventHandler: AnyObject { + func sendEvent(_ event: [String: Any]) } // TTS事件流处理器 @available(iOS 13.0, *) -class TtsStreamHandler: NSObject, FlutterStreamHandler { +class TtsStreamHandler: NSObject, FlutterStreamHandler, TtsEventHandler { + private let azureTtsHelper: AzureTtsHelper private var eventSink: FlutterEventSink? - private var notificationObserver: Any? - deinit { - if let observer = notificationObserver { - NotificationCenter.default.removeObserver(observer) + init(azureTtsHelper: AzureTtsHelper) { + self.azureTtsHelper = azureTtsHelper + super.init() + + // 设置事件处理器 + azureTtsHelper.setEventHandler { [weak self] event in + self?.sendEvent(event) } } func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { + NSLog("TtsStreamHandler: 开始监听TTS事件") self.eventSink = events - - // 注册通知观察者 - notificationObserver = NotificationCenter.default.addObserver( - forName: Notification.Name("AzureTtsEvent"), - object: nil, - queue: .main) { [weak self] notification in - if let userInfo = notification.userInfo as? [String: Any], let eventSink = self?.eventSink { - // 确保在主线程上发送事件 - DispatchQueue.main.async { - eventSink(userInfo) - } - } - } - return nil } func onCancel(withArguments arguments: Any?) -> FlutterError? { + NSLog("TtsStreamHandler: 取消监听TTS事件") eventSink = nil - - // 移除通知观察者 - if let observer = notificationObserver { - NotificationCenter.default.removeObserver(observer) - notificationObserver = nil - } - return nil } + + // 实现TtsEventHandler协议 + func sendEvent(_ event: [String: Any]) { + NSLog("TtsStreamHandler: 收到事件 \(event)") + DispatchQueue.main.async { [weak self] in + self?.eventSink?(event) + } + } } // 火山AI事件流处理器 diff --git a/ios/Runner/AudioSessionManager.swift b/ios/Runner/AudioSessionManager.swift index 6020101d1..b4a78ec17 100644 --- a/ios/Runner/AudioSessionManager.swift +++ b/ios/Runner/AudioSessionManager.swift @@ -419,40 +419,6 @@ import UIKit - // MARK: - 蓝牙媒体按钮配置 - - /// 配置用于获取蓝牙媒体按钮响应的音频会话 - /// 此函数专门针对需要接收蓝牙耳机上媒体按钮事件的场景进行优化 - /// - Returns: 配置是否成功 - @objc func configureForBluetoothMediaButtons() -> Bool { - do { - // 配置音频会话以支持蓝牙媒体按钮 - // 使用playback类别确保可以接收远程控制事件 - // 使用spokenAudio模式优化语音交互 - try configureSession( - category: .playback, - mode: .default, // 使用default模式,避免与spokenAudio可能的冲突 - options: [ - .allowBluetooth, // 允许通过蓝牙SCO连接进行输入和输出 - .allowBluetoothA2DP, // 允许通过蓝牙A2DP连接进行高质量音频输出 - .mixWithOthers // 允许与其他音频混合,避免中断 - ], - force: true // 强制重新配置,确保设置生效 - ) - - NSLog("[AudioSessionManager] 已配置蓝牙媒体按钮响应的音频会话") - return true - } catch { - NSLog("[AudioSessionManager] 配置蓝牙媒体按钮响应的音频会话失败: \(error.localizedDescription)") - return false - } - } - - - // MARK: - Azure语音服务配置 - - - /// 配置用于语音交互(同时支持ASR和TTS)的音频会话 /// 此函数综合优化语音识别和语音合成,适用于需要双向交互的场景 @@ -466,11 +432,10 @@ import UIKit category: .playAndRecord, mode: .spokenAudio, options: [ + .allowBluetoothA2DP, // 允许通过蓝牙A2DP连接进行高质量音频输出 // .allowBluetooth, // 允许通过蓝牙SCO连接进行输入和输出 - .allowBluetoothA2DP, // 允许通过蓝牙A2DP连接进行高质量音频输出 // .defaultToSpeaker, // 默认使用扬声器输出 - // .duckOthers, // 降低其他应用的音量 - // .interruptSpokenAudioAndMixWithOthers // 中断其他语音音频并混合 + // .mixWithOthers ], force: force // 强制重新配置,确保设置生效 ) @@ -478,7 +443,7 @@ import UIKit // 设置首选输入设备 // try configurePreferredInput() - // NSLog("[AudioSessionManager] 已配置语音交互(ASR+TTS)的音频会话") + NSLog("[AudioSessionManager] 已配置语音交互(ASR+TTS)的音频会话") return true } catch { NSLog("[AudioSessionManager] 配置语音交互(ASR+TTS)的音频会话失败: \(error.localizedDescription)") diff --git a/ios/Runner/AzureAsrHelper.swift b/ios/Runner/AzureAsrHelper.swift index 390974877..652a57528 100644 --- a/ios/Runner/AzureAsrHelper.swift +++ b/ios/Runner/AzureAsrHelper.swift @@ -8,7 +8,7 @@ class AzureAsrHelper: NSObject { // MARK: - 属性 /// 事件处理回调 - private var eventHandler: (String, [String: Any]) -> Void + private var eventHandler: (([String: Any]) -> Void)? /// 语音配置信息 private var speechSubscriptionKey: String = "" @@ -33,14 +33,14 @@ class AzureAsrHelper: NSObject { // MARK: - 初始化 - init(eventHandler: @escaping (String, [String: Any]) -> Void) { - self.eventHandler = eventHandler + override init() { super.init() } deinit { dispose() } + // MARK: - ASR Service 接口实现 @@ -56,10 +56,15 @@ class AzureAsrHelper: NSObject { // 检查配置是否为空 if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty { print("[AzureAsrHelper] 错误: Azure 配置信息不完整") - eventHandler("error", ["message": "Azure 配置信息不完整"]) + eventHandler?(["type": "error", "message": "Azure 配置信息不完整"]) return false } + // 检查语言参数 + if let languages = supportedLanguages, languages.isEmpty { + print("[AzureAsrHelper] 警告: 传入的支持语言列表为空,将使用默认语言") + } + // 释放之前的资源 dispose() @@ -134,7 +139,7 @@ class AzureAsrHelper: NSObject { return true } catch { print("[AzureAsrHelper] 错误: 创建识别器失败: \(error.localizedDescription)") - eventHandler("error", ["message": "创建识别器失败: \(error.localizedDescription)"]) + eventHandler?(["type": "error", "message": "创建识别器失败: \(error.localizedDescription)"]) return false } } @@ -158,7 +163,7 @@ class AzureAsrHelper: NSObject { if event.result.reason == SPXResultReason.recognizedSpeech { let detectedLanguage = self.getDetectedLanguage(from: event.result) print("[AzureAsrHelper] 识别结果: \(event.result.text ?? ""), 语言: \(detectedLanguage)") - self.eventHandler("result", [ + self.eventHandler?(["type": "result", "text": event.result.text ?? "", "detectedLanguage": detectedLanguage ]) @@ -172,7 +177,7 @@ class AzureAsrHelper: NSObject { if event.result.reason == SPXResultReason.recognizingSpeech { let detectedLanguage = self.getDetectedLanguage(from: event.result) // print("[AzureAsrHelper] 识别中: \(event.result.text ?? ""), 语言: \(detectedLanguage)") - self.eventHandler("recognizing", [ + self.eventHandler?(["type": "recognizing", "text": event.result.text ?? "", "detectedLanguage": detectedLanguage ]) @@ -185,7 +190,7 @@ class AzureAsrHelper: NSObject { print("[AzureAsrHelper] 识别会话已开始") self._isContinuousRecognitionActive = true - self.eventHandler("sessionStarted", [:]) + self.eventHandler?(["type": "sessionStarted"]) } recognizer.addSessionStoppedEventHandler { [weak self] _, _ in @@ -193,7 +198,7 @@ class AzureAsrHelper: NSObject { print("[AzureAsrHelper] 识别会话已结束") self._isContinuousRecognitionActive = false - self.eventHandler("sessionStopped", [:]) + self.eventHandler?(["type": "sessionStopped"]) } // 取消事件 @@ -205,10 +210,7 @@ class AzureAsrHelper: NSObject { print("[AzureAsrHelper] 识别取消: \(errorDetails)") - self.eventHandler("canceled", [ - "reason": reason, - "errorDetails": errorDetails - ]) + self.eventHandler?(["type": "canceled", "reason": reason, "errorDetails": errorDetails]) self._isContinuousRecognitionActive = false } @@ -217,9 +219,17 @@ class AzureAsrHelper: NSObject { /// 执行一次性语音识别 /// - Returns: 是否成功启动识别 func recognizeOnce() -> Bool { + // 检查是否初始化 if !isInitialized { print("[AzureAsrHelper] 错误: 语音服务未初始化") - eventHandler("error", ["message": "语音服务未初始化"]) + eventHandler?(["type": "error", "message": "语音服务未初始化"]) + return false + } + + // 检查参数有效性 + if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty { + print("[AzureAsrHelper] 错误: Azure 配置信息不完整") + eventHandler?(["type": "error", "message": "Azure 配置信息不完整"]) return false } @@ -235,7 +245,7 @@ class AzureAsrHelper: NSObject { do { // 通知会话开始 - eventHandler("sessionStarted", [:]) + eventHandler?(["type": "sessionStarted"]) // 执行识别 try recognizer?.recognizeOnceAsync { [weak self] result in @@ -243,21 +253,21 @@ class AzureAsrHelper: NSObject { if result.reason == SPXResultReason.recognizedSpeech { let detectedLanguage = self.getDetectedLanguage(from: result) - self.eventHandler("result", [ + self.eventHandler?(["type": "result", "text": result.text ?? "", "detectedLanguage": detectedLanguage ]) } else if result.reason == SPXResultReason.noMatch { print("[AzureAsrHelper] 无匹配结果") - self.eventHandler("noMatch", [:]) + self.eventHandler?(["type": "noMatch"]) } else if result.reason == SPXResultReason.canceled { do { let details = try SPXCancellationDetails(fromCanceledRecognitionResult: result) let errorDetails = details.errorDetails ?? "未知错误" - self.eventHandler("error", ["message": "识别取消: \(errorDetails)"]) + self.eventHandler?(["type": "error", "message": "识别取消: \(errorDetails)"]) } catch { print("[AzureAsrHelper] 错误: 获取取消详情失败: \(error.localizedDescription)") - self.eventHandler("error", ["message": "识别取消,无法获取详细原因"]) + self.eventHandler?(["type": "error", "message": "识别取消,无法获取详细原因"]) } } } @@ -265,7 +275,7 @@ class AzureAsrHelper: NSObject { return true } catch { print("[AzureAsrHelper] 错误: 识别异常: \(error.localizedDescription)") - eventHandler("error", ["message": "识别异常: \(error.localizedDescription)"]) + eventHandler?(["type": "error", "message": "识别异常: \(error.localizedDescription)"]) return false } } @@ -273,9 +283,17 @@ class AzureAsrHelper: NSObject { /// 开始连续语音识别 /// - Returns: 是否成功启动识别 func startContinuousRecognition() -> Bool { + // 检查是否初始化 if !isInitialized { print("[AzureAsrHelper] 错误: 语音服务未初始化") - eventHandler("error", ["message": "语音服务未初始化"]) + eventHandler?(["type": "error", "message": "语音服务未初始化"]) + return false + } + + // 检查参数有效性 + if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty { + print("[AzureAsrHelper] 错误: Azure 配置信息不完整") + eventHandler?(["type": "error", "message": "Azure 配置信息不完整"]) return false } @@ -289,7 +307,7 @@ class AzureAsrHelper: NSObject { if recognizer == nil { print("[AzureAsrHelper] 尝试重新创建识别器...") if !createRecognizerAndSetupCallbacks() { - eventHandler("error", ["message": "无法创建语音识别器"]) + eventHandler?(["type": "error", "message": "无法创建语音识别器"]) return false } } @@ -309,7 +327,7 @@ class AzureAsrHelper: NSObject { print("[AzureAsrHelper] 错误: 开始连续识别失败: \(error.localizedDescription)") // 发送错误通知 - eventHandler("error", ["message": "开始连续识别失败: \(error.localizedDescription)"]) + eventHandler?(["type": "error", "message": "开始连续识别失败: \(error.localizedDescription)"]) return false } @@ -319,6 +337,13 @@ class AzureAsrHelper: NSObject { /// 停止连续语音识别 /// - Returns: 是否成功停止识别 func stopContinuousRecognition() -> Bool { + // 检查是否初始化 + if !isInitialized { + print("[AzureAsrHelper] 错误: 语音服务未初始化") + eventHandler?(["type": "error", "message": "语音服务未初始化"]) + return false + } + if !_isContinuousRecognitionActive { print("[AzureAsrHelper] 未进行连续识别,忽略停止请求") return true @@ -330,7 +355,7 @@ class AzureAsrHelper: NSObject { guard let recognizer = recognizer else { print("[AzureAsrHelper] 警告: 识别器为空,但状态显示活跃") _isContinuousRecognitionActive = false - eventHandler("sessionStopped", [:]) + eventHandler?(["type": "sessionStopped"]) return true } @@ -355,7 +380,7 @@ class AzureAsrHelper: NSObject { // 主线程回调通知结果 DispatchQueue.main.async { print("[AzureAsrHelper] 连续识别已停止") - self.eventHandler("sessionStopped", [:]) + self.eventHandler?(["type": "sessionStopped"]) } } catch { // 主线程回调通知错误 @@ -363,7 +388,7 @@ class AzureAsrHelper: NSObject { print("[AzureAsrHelper] 错误: 停止连续识别失败: \(error.localizedDescription)") // 停止失败,恢复状态 self._isContinuousRecognitionActive = true - self.eventHandler("error", ["error": "停止识别失败: \(error.localizedDescription)"]) + self.eventHandler?(["type": "error", "message": "停止识别失败: \(error.localizedDescription)"]) } } } @@ -380,9 +405,17 @@ class AzureAsrHelper: NSObject { /// 释放资源 func dispose() { - // 停止所有识别操作 + // 尝试停止所有识别操作 if _isContinuousRecognitionActive { - stopContinuousRecognition() + do { + if let recognizer = recognizer { + try recognizer.stopContinuousRecognition() + print("[AzureAsrHelper] 连续识别已停止") + } + } catch { + print("[AzureAsrHelper] 警告: 停止连续识别失败: \(error.localizedDescription)") + } + _isContinuousRecognitionActive = false } // 释放识别器 @@ -392,7 +425,6 @@ class AzureAsrHelper: NSObject { // 重置状态 isInitialized = false - _isContinuousRecognitionActive = false print("[AzureAsrHelper] 资源已释放") } @@ -411,4 +443,9 @@ class AzureAsrHelper: NSObject { return currentLanguage } } + + /// 设置事件处理器 + @objc func setEventHandler(_ handler: @escaping ([String: Any]) -> Void) { + self.eventHandler = handler + } } \ No newline at end of file diff --git a/ios/Runner/AzureTtsHelper.swift b/ios/Runner/AzureTtsHelper.swift index f4fd0478c..9d4a94e70 100644 --- a/ios/Runner/AzureTtsHelper.swift +++ b/ios/Runner/AzureTtsHelper.swift @@ -8,7 +8,7 @@ class AzureTtsHelper: NSObject { // MARK: - 属性 /// 事件处理回调 - private var eventHandler: (String, [String: Any]) -> Void + private var eventHandler: (([String: Any]) -> Void)? /// 语音配置信息 private var speechSubscriptionKey: String = "" @@ -54,8 +54,7 @@ class AzureTtsHelper: NSObject { // MARK: - 初始化 - init(eventHandler: @escaping (String, [String: Any]) -> Void) { - self.eventHandler = eventHandler + override init() { super.init() } @@ -63,6 +62,11 @@ class AzureTtsHelper: NSObject { dispose() } + /// 设置事件处理器 + @objc func setEventHandler(_ handler: @escaping ([String: Any]) -> Void) { + self.eventHandler = handler + } + // MARK: - TTS 接口实现 /// 初始化语音合成服务 @@ -77,7 +81,7 @@ class AzureTtsHelper: NSObject { // 检查配置是否为空 if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty { print("[AzureTtsHelper] 错误: Azure 配置信息不完整") - eventHandler("error", ["error": "Azure 配置信息不完整"]) + eventHandler?(["type": "error", "message": "Azure 配置信息不完整"]) return false } @@ -114,7 +118,7 @@ class AzureTtsHelper: NSObject { return true } catch { print("[AzureTtsHelper] 错误: 初始化语音合成服务失败: \(error.localizedDescription)") - eventHandler("error", ["error": "初始化语音合成服务失败: \(error.localizedDescription)"]) + eventHandler?(["type": "error", "message": "初始化语音合成服务失败: \(error.localizedDescription)"]) return false } } @@ -139,15 +143,23 @@ class AzureTtsHelper: NSObject { /// - Parameter voiceName: 语音名称 (如 "zh-CN-XiaoxiaoNeural") /// - Returns: 设置是否成功 func setVoice(voiceName: String) -> Bool { + // 检查是否初始化 if !isInitialized { print("[AzureTtsHelper] 错误: TTS 引擎尚未初始化") - eventHandler("error", ["error": "TTS 引擎尚未初始化"]) + eventHandler?(["type": "error", "message": "TTS 引擎尚未初始化"]) + return false + } + + // 检查参数有效性 + if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty { + print("[AzureTtsHelper] 错误: Azure 配置信息不完整") + eventHandler?(["type": "error", "message": "Azure 配置信息不完整"]) return false } if voiceName.isEmpty { print("[AzureTtsHelper] 错误: 声音名称为空") - eventHandler("error", ["error": "声音名称不能为空"]) + eventHandler?(["type": "error", "message": "声音名称不能为空"]) return false } @@ -175,9 +187,17 @@ class AzureTtsHelper: NSObject { /// - volume: 音量,范围 0 到 100,默认为 100 /// - Returns: 是否设置成功 func setSpeechParams(rate: Int = 0, pitch: Int = 0, volume: Int = 100) -> Bool { + // 检查是否初始化 if !isInitialized { print("[AzureTtsHelper] 错误: TTS 引擎尚未初始化") - eventHandler("error", ["error": "TTS 引擎尚未初始化"]) + eventHandler?(["type": "error", "message": "TTS 引擎尚未初始化"]) + return false + } + + // 检查参数有效性 + if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty { + print("[AzureTtsHelper] 错误: Azure 配置信息不完整") + eventHandler?(["type": "error", "message": "Azure 配置信息不完整"]) return false } @@ -194,9 +214,17 @@ class AzureTtsHelper: NSObject { /// - Parameter text: 要合成的文本 /// - Returns: 操作是否成功启动 func speakText(text: String) -> Bool { + // 检查是否初始化 if !isInitialized { print("[AzureTtsHelper] 错误: TTS 引擎尚未初始化") - eventHandler("error", ["error": "TTS 引擎尚未初始化"]) + eventHandler?(["type": "error", "message": "TTS 引擎尚未初始化"]) + return false + } + + // 检查参数有效性 + if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty { + print("[AzureTtsHelper] 错误: Azure 配置信息不完整") + eventHandler?(["type": "error", "message": "Azure 配置信息不完整"]) return false } @@ -223,12 +251,12 @@ class AzureTtsHelper: NSObject { private func speakSsmlInternal(text: String) -> Bool { guard let synthesizer = synthesizer else { print("[AzureTtsHelper] 错误: 合成器未初始化") - eventHandler("error", ["error": "合成器未初始化"]) + eventHandler?(["type": "error", "message": "合成器未初始化"]) return false } _isSpeaking = true - eventHandler("started", [:]) + eventHandler?(["type": "started"]) // Task { do { @@ -239,7 +267,7 @@ class AzureTtsHelper: NSObject { print("[AzureTtsHelper] 错误: 语音合成失败: \(error.localizedDescription)") DispatchQueue.main.async { self._isSpeaking = false - self.eventHandler("error", ["error": "语音合成失败: \(error.localizedDescription)"]) + self.eventHandler?(["type": "error", "message": "语音合成失败: \(error.localizedDescription)"]) } } // } @@ -250,7 +278,15 @@ class AzureTtsHelper: NSObject { /// 停止当前语音合成 /// - Returns: 操作是否成功 func stopSpeaking() -> Bool { - if !isInitialized || !_isSpeaking { + // 检查是否初始化 + if !isInitialized { + print("[AzureTtsHelper] 错误: TTS 引擎尚未初始化") + eventHandler?(["type": "error", "message": "TTS 引擎尚未初始化"]) + return false + } + + if !_isSpeaking { + print("[AzureTtsHelper] 未进行语音播放,忽略停止请求") return true } @@ -258,12 +294,12 @@ class AzureTtsHelper: NSObject { do { try synthesizer?.stopSpeaking() _isSpeaking = false - eventHandler("canceled", [:]) + eventHandler?(["type": "canceled"]) print("[AzureTtsHelper] 已停止语音合成") return true } catch { print("[AzureTtsHelper] 错误: 停止语音合成失败: \(error.localizedDescription)") - eventHandler("error", ["error": "停止语音合成失败: \(error.localizedDescription)"]) + eventHandler?(["type": "error", "message": "停止语音合成失败: \(error.localizedDescription)"]) return false } } @@ -276,9 +312,17 @@ class AzureTtsHelper: NSObject { /// 释放资源 func dispose() { - // 停止合成 + // 尝试停止所有语音合成 if _isSpeaking { - stopSpeaking() + do { + if let synthesizer = synthesizer { + try synthesizer.stopSpeaking() + print("[AzureTtsHelper] 语音合成已停止") + } + } catch { + print("[AzureTtsHelper] 警告: 停止语音合成失败: \(error.localizedDescription)") + } + _isSpeaking = false } // 释放合成器 @@ -287,7 +331,6 @@ class AzureTtsHelper: NSObject { // 重置状态 isInitialized = false - _isSpeaking = false isAudioSessionConfigured = false print("[AzureTtsHelper] 资源已释放") @@ -310,7 +353,7 @@ class AzureTtsHelper: NSObject { print("[AzureTtsHelper] 语音合成完成: 音频持续时间: \(e.result.audioDuration)") DispatchQueue.main.async { self._isSpeaking = false - self.eventHandler("completed", [:]) + self.eventHandler?(["type": "completed"]) } } @@ -330,14 +373,14 @@ class AzureTtsHelper: NSObject { DispatchQueue.main.async { self._isSpeaking = false - self.eventHandler("error", ["error": "语音合成取消: \(cancellationDetails.errorDetails ?? "未知错误")"]) + self.eventHandler?(["type": "error", "message": "语音合成取消: \(cancellationDetails.errorDetails ?? "未知错误")"]) } } catch { print("[AzureTtsHelper] 获取取消详情时出错: \(error)") DispatchQueue.main.async { self._isSpeaking = false - self.eventHandler("error", ["error": "语音合成被取消"]) + self.eventHandler?(["type": "error", "message": "语音合成被取消"]) } } } diff --git a/ios/Runner/VoiceInteractionService.swift b/ios/Runner/VoiceInteractionService.swift index e2ee54dac..f19baf2c9 100644 --- a/ios/Runner/VoiceInteractionService.swift +++ b/ios/Runner/VoiceInteractionService.swift @@ -162,15 +162,21 @@ class VoiceInteractionService: NSObject { NSLog("%@: 初始化服务组件", TAG) // 初始化Azure ASR - azureAsrHelper = AzureAsrHelper { [weak self] eventName, eventData in - // 处理ASR事件 - self?.handleAsrEvent(eventName: eventName, eventData: eventData) + azureAsrHelper = AzureAsrHelper() + if let helper = azureAsrHelper { + helper.setEventHandler({ [weak self] eventData in + // 处理ASR事件 + self?.handleAsrEvent(eventData: eventData) + }) } // 初始化Azure TTS - azureTtsHelper = AzureTtsHelper { [weak self] eventName, eventData in - // 处理TTS事件 - self?.handleTtsEvent(eventName: eventName, eventData: eventData) + azureTtsHelper = AzureTtsHelper() + if let helper = azureTtsHelper { + helper.setEventHandler({ [weak self] eventData in + // 处理TTS事件 + self?.handleTtsEvent(eventData: eventData) + }) } // 初始化火山AI服务 @@ -237,69 +243,97 @@ class VoiceInteractionService: NSObject { /** * 处理ASR事件 */ - private func handleAsrEvent(eventName: String, eventData: [String: Any]) { - // NSLog("%@: 处理ASR事件: %@", TAG, eventName) - switch eventName { - case "recognizing": - if let text = eventData["text"] as? String, !text.isEmpty { - hasSpeechDetected = true - stopCurrentTTS() - updateLastActivityTime() - } + private func handleAsrEvent(eventData: [String: Any]) { + if let type = eventData["type"] as? String { + // NSLog("%@: 处理ASR事件: %@", TAG, type) - case "result": - if let text = eventData["text"] as? String, !text.isEmpty { + switch type { + case "recognizing": + if let text = eventData["text"] as? String, !text.isEmpty { + hasSpeechDetected = true + stopCurrentTTS() + updateLastActivityTime() + } else if let data = eventData["data"] as? [String: Any], + let text = data["text"] as? String, + !text.isEmpty { + hasSpeechDetected = true + stopCurrentTTS() + updateLastActivityTime() + } + + case "result": + var text: String? + + // 直接读取text或从data中读取 + if let directText = eventData["text"] as? String, !directText.isEmpty { + text = directText + } else if let data = eventData["data"] as? [String: Any], + let dataText = data["text"] as? String, + !dataText.isEmpty { + text = dataText + } + + if let finalText = text { + updateLastActivityTime() + + // 保存用户消息 + lastUserMessage = finalText + + processWithVolcanoAI(finalText) + } + + // 重置状态,继续识别 + hasSpeechDetected = false + + case "sessionStarted": updateLastActivityTime() - // 保存用户消息 - lastUserMessage = text - - processWithVolcanoAI(text) + case "sessionStopped": + isRecognitionActive = false + + case "canceled": + isRecognitionActive = false + + case "error": + isRecognitionActive = false + if let message = eventData["message"] as? String { + NSLog("%@: ASR错误: %@", TAG, message) + } + playNotification("语音识别出错") + + default: + break } - - // 重置状态,继续识别 - hasSpeechDetected = false - - case "sessionStarted": - updateLastActivityTime() - - case "sessionStopped": - isRecognitionActive = false - - case "canceled": - isRecognitionActive = false - - case "error": - isRecognitionActive = false - playNotification("语音识别出错") - - default: - break } } /** * 处理TTS事件 */ - private func handleTtsEvent(eventName: String, eventData: [String: Any]) { - // NSLog("%@: 处理TTS事件: %@", TAG, eventName) - switch eventName { - case "started": - isTtsSpeaking = true + private func handleTtsEvent(eventData: [String: Any]) { + if let type = eventData["type"] as? String { + // NSLog("%@: 处理TTS事件: %@", TAG, type) - case "completed": - isTtsSpeaking = false - updateLastActivityTime() - - case "canceled": - isTtsSpeaking = false - - case "error": - isTtsSpeaking = false - NSLog("%@: TTS错误: %@", TAG, eventData["error"] as? String ?? "未知错误") - - default: - break + switch type { + case "started": + isTtsSpeaking = true + + case "completed": + isTtsSpeaking = false + updateLastActivityTime() + + case "canceled": + isTtsSpeaking = false + + case "error": + isTtsSpeaking = false + if let message = eventData["message"] as? String { + NSLog("%@: TTS错误: %@", TAG, message) + } + + default: + break + } } } diff --git a/lib/data/services/speech_impl/azure_asr_service.dart b/lib/data/services/speech_impl/azure_asr_service.dart index 41e9fd3f7..ed444566c 100644 --- a/lib/data/services/speech_impl/azure_asr_service.dart +++ b/lib/data/services/speech_impl/azure_asr_service.dart @@ -36,7 +36,6 @@ class AzureAsrService extends GetxService implements AsrService { AzureAsrService() { _loadConfig(); - _setupEventChannel(); } /// 从环境变量加载配置 @@ -80,7 +79,8 @@ class AzureAsrService extends GetxService implements AsrService { 'region': _serviceRegion, 'supportedLanguages': _supportedLanguages, }); - + _setupEventChannel(); + _isInitialized = result; Logger.info('Azure 语音识别服务初始化${result ? '成功' : '失败'}'); return result; diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 931bd270a..000a98608 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,9 +1,4 @@ PODS: - - app_settings (5.1.1): - - FlutterMacOS - - audio_service (0.0.1): - - Flutter - - FlutterMacOS - audio_session (0.0.1): - FlutterMacOS - device_info_plus (0.0.1): @@ -32,16 +27,11 @@ PODS: - Flutter - FlutterMacOS - Try - - sqflite_darwin (0.0.4): - - Flutter - - FlutterMacOS - Try (2.1.1) - window_size (0.0.2): - FlutterMacOS DEPENDENCIES: - - app_settings (from `Flutter/ephemeral/.symlinks/plugins/app_settings/macos`) - - audio_service (from `Flutter/ephemeral/.symlinks/plugins/audio_service/darwin`) - audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) @@ -54,7 +44,6 @@ DEPENDENCIES: - record_darwin (from `Flutter/ephemeral/.symlinks/plugins/record_darwin/macos`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - speech_to_text (from `Flutter/ephemeral/.symlinks/plugins/speech_to_text/darwin`) - - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) - window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`) SPEC REPOS: @@ -62,10 +51,6 @@ SPEC REPOS: - Try EXTERNAL SOURCES: - app_settings: - :path: Flutter/ephemeral/.symlinks/plugins/app_settings/macos - audio_service: - :path: Flutter/ephemeral/.symlinks/plugins/audio_service/darwin audio_session: :path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos device_info_plus: @@ -90,14 +75,10 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos speech_to_text: :path: Flutter/ephemeral/.symlinks/plugins/speech_to_text/darwin - sqflite_darwin: - :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin window_size: :path: Flutter/ephemeral/.symlinks/plugins/window_size/macos SPEC CHECKSUMS: - app_settings: cd21e176b56f8172043640ade81322a98896bff4 - audio_service: aa99a6ba2ae7565996015322b0bb024e1d25c6fd audio_session: eaca2512cf2b39212d724f35d11f46180ad3a33e device_info_plus: a56e6e74dbbd2bb92f2da12c64ddd4f67a749041 file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 @@ -110,7 +91,6 @@ SPEC CHECKSUMS: record_darwin: 30509266ae213af8afdb09a8ae7467cb64c1377e share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc speech_to_text: 9dc43a5df3cbc2813f8c7cc9bd0fbf94268ed7ac - sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 Try: 5ef669ae832617b3cee58cb2c6f99fb767a4ff96 window_size: 4bd15034e6e3d0720fd77928a7c42e5492cfece9