import Foundation import AVFoundation import ble_service import speech import azure_speech import os import os.log import agent_service import open_ai_service // 导入AudioSessionHub,确保可以使用它的功能 // 由于它在同一个目录中,不需要额外的模块名 /// 代理服务事件监听器接口 protocol AgentServiceListener: AnyObject { /// 当事件发生时调用 /// - Parameters: /// - eventName: 事件名称 /// - data: 事件数据 func onEvent(eventName: String, data: [String: Any]) } /// 代理服务实现类,负责语音识别、OpenAI对话和语音合成 class AgentServiceImpl: NSObject { // MARK: - 常量 private let TAG = "AgentServiceImpl" // MARK: - 单例实现 static let shared = AgentServiceImpl() // MARK: - 属性 // 监听器数组 private var listeners = [AgentServiceListener]() private let listenersLock = NSLock() // Azure 语音相关 private var azureSpeechKey: String = "" private var azureSpeechRegion: String = "" private var isSpeaking: Bool = false // 语音识别和合成服务 private var azureAsrHelper: AzureAsrHelper? internal var azureTtsHelper: AzureTtsHelper? // OpenAI相关 private var openAIService: OpenAIService? private var apiKey: String = "" private var baseUrl: String = "https://api.openai.com/v1/chat/completions" private var model: String = "gpt-3.5-turbo" private var systemPrompt: String = "" private var chatHistory: [[String: Any]] = [] // MCP服务相关 private var mcpServer: String = "" // 状态标志 private var isInitialized: Bool = false private var isRecognizing: Bool = false private var hasSpeechDetected: Bool = false internal var isAiStreaming: Bool = false // 空闲检测相关 private var idleTimer: DispatchSourceTimer? private let maxIdleSeconds: TimeInterval = 10 // 最大空闲秒数 // 音频播放器 internal var audioPlayer: AudioPlayer? // 日志对象 internal let logger = OSLog(subsystem: "com.yunqiinnovation.agent_service", category: "AgentServiceImpl") // MARK: - 初始化 /// 私有初始化方法 private override init() { super.init() // 初始化音频播放器 audioPlayer = AudioPlayer() // 初始化语音助手服务 azureAsrHelper = AzureAsrHelper() azureTtsHelper = AzureTtsHelper() // 初始化OpenAI服务 openAIService = OpenAIService() // 设置BleService回调 BleService.shared.setDelegate(self) os_log("AgentServiceImpl已初始化", log: logger, type: .debug) } // MARK: - 监听器管理 /// 添加事件监听器 /// - Parameter listener: 要添加的监听器 func addListener(_ listener: AgentServiceListener) { listenersLock.lock() if !listeners.contains(where: { $0 === listener }) { listeners.append(listener) } listenersLock.unlock() } /// 移除事件监听器 /// - Parameter listener: 要移除的监听器 func removeListener(_ listener: AgentServiceListener) { listenersLock.lock() listeners.removeAll(where: { $0 === listener }) listenersLock.unlock() } /// 清除所有监听器 func clearListeners() { listenersLock.lock() listeners.removeAll() listenersLock.unlock() } /// 向所有监听器发送事件 /// - Parameters: /// - eventName: 事件名称 /// - data: 事件数据 internal func sendEvent(name eventName: String, data: [String: Any]) { listenersLock.lock() let currentListeners = self.listeners listenersLock.unlock() for listener in currentListeners { listener.onEvent(eventName: eventName, data: data) } } /// 发送错误事件 /// - Parameter message: 错误信息 private func sendError(_ message: String, code: String = "ERROR") { sendEvent(name: "error", data: ["code": code, "message": message]) } // MARK: - 初始化配置 /// 初始化系统提示词 private func initSystemPrompt() { systemPrompt = """ 你是小言,一个具备专业能力的智能助手,需根据用户场景灵活切换回答模式,确保服务精准高效。 """ } /// 初始化配置 /// - Parameter config: 配置参数 /// - Returns: 是否初始化成功 func initialize(config: [String: Any]) -> Bool { if isInitialized { return true } os_log("初始化配置: %{public}@", log: logger, type: .info, config) // 设置OpenAI配置 if let openaiApiKey = config["openaiApiKey"] as? String { self.apiKey = openaiApiKey } else { sendError("OpenAI API密钥缺失", code: "CONFIG_ERROR") return false } if let openaiBaseUrl = config["openaiBaseUrl"] as? String { self.baseUrl = openaiBaseUrl } if let openaiModel = config["openaiModel"] as? String { self.model = openaiModel } // 设置系统提示词 if let customSystemPrompt = config["systemPrompt"] as? String, !customSystemPrompt.isEmpty { self.systemPrompt = customSystemPrompt } else { initSystemPrompt() } // 设置Azure语音配置 if let azureSpeechKey = config["azureSpeechKey"] as? String { self.azureSpeechKey = azureSpeechKey } else { sendError("Azure语音密钥缺失", code: "CONFIG_ERROR") return false } if let azureSpeechRegion = config["azureSpeechRegion"] as? String { self.azureSpeechRegion = azureSpeechRegion } else { sendError("Azure语音区域缺失", code: "CONFIG_ERROR") return false } // 设置MCP服务 if let mcpServer = config["mcpServer"] as? String { self.mcpServer = mcpServer } // 初始化Azure语音服务 let asrInitSuccess = initializeAzureSpeech() // 初始化OpenAI服务 let openaiInitSuccess = initializeOpenAIService() isInitialized = asrInitSuccess && openaiInitSuccess return isInitialized } // MARK: - Azure 语音服务 /// 初始化Azure语音服务 private func initializeAzureSpeech() -> Bool { // 添加TTS事件监听器 azureTtsHelper?.addListener(self) // 初始化ASR服务 guard let asrSuccess = azureAsrHelper?.initialize( subscriptionKey: azureSpeechKey, region: azureSpeechRegion, supportedLanguages: ["zh-CN"], audioSourceType: .microphone ), asrSuccess else { sendError("初始化语音识别服务失败", code: "ASR_INIT_ERROR") return false } // 初始化TTS服务 guard let ttsSuccess = azureTtsHelper?.initialize( ttsAppId: "", // Azure TTS不需要appId ttsAppToken: azureSpeechKey, ttsResource: azureSpeechRegion, language: "zh-CN" ), ttsSuccess else { sendError("初始化语音合成服务失败", code: "TTS_INIT_ERROR") return false } // 设置TTS语音 _ = azureTtsHelper?.setVoice("zh-CN-XiaoxiaoNeural") return true } // MARK: - OpenAI 服务 /// 初始化OpenAI服务 private func initializeOpenAIService() -> Bool { guard let openAIService = openAIService else { sendError("OpenAI服务未创建", code: "OPENAI_INIT_ERROR") return false } let success = openAIService.initialize( apiKey: apiKey, baseUrl: baseUrl, model: model, mcpServer: mcpServer ) if !success { sendError("初始化OpenAI服务失败", code: "OPENAI_INIT_ERROR") return false } return true } // MARK: - 空闲检测 /// 启动空闲检测 private func startIdleCheck() { stopIdleCheck() // 先停止现有的检查 guard isRecognizing else { return } // 使用DispatchSourceTimer,iOS最佳实践 idleTimer = DispatchSource.makeTimerSource(queue: DispatchQueue.main) idleTimer?.schedule(deadline: .now() + maxIdleSeconds) idleTimer?.setEventHandler { [weak self] in guard let self = self else { return } if self.isRecognizing && !self.hasSpeechDetected && !self.isSpeaking && !self.isAiStreaming { self.stopRecognition() self.sendEvent(name: "auto_stop", data: [ "reason": "idle_timeout", "seconds": self.maxIdleSeconds ]) } } idleTimer?.resume() } /// 停止空闲检测 private func stopIdleCheck() { guard let timer = idleTimer else { return } // os_log("停止空闲检测", log: logger, type: .debug) timer.cancel() idleTimer = nil } /// 重启空闲检测 private func restartIdleCheck() { if isRecognizing { // os_log("重启空闲检测", log: logger, type: .debug) startIdleCheck() } } // MARK: - 语音识别 /// 开始语音识别 /// - Parameter useBle: 是否使用蓝牙设备作为音频源 /// - Returns: 是否成功启动识别 func startRecognition(useBle: Bool = false) -> Bool { if !isInitialized { sendError("服务未初始化", code: "NOT_INITIALIZED") return false } // 1. 如果不使用蓝牙,则检查麦克风权限 if !useBle { guard AVAudioSession.sharedInstance().recordPermission == .granted else { os_log("无麦克风权限", log: logger, type: .error) sendError("无麦克风权限", code: "PERMISSION_DENIED") return false } } // 2. 防止重复启动 if isRecognizing { stopRecognition() } // 3. 选择合适的音频源类型 let audioSourceType: AzureAsrHelper.AudioSourceType = useBle ? .external : .microphone os_log("使用音频源: %{public}@", log: logger, type: .debug, useBle ? "external" : "microphone") // 4. 设置适当的音频会话模式 if useBle { // BLE模式:使用external音频源,不设置音频会话 // Azure SDK会通过拉流获取数据,无需系统音频会话管理 AudioSessionHub.shared.begin(.playback) } else { // 麦克风模式:需要播放和录音 AudioSessionHub.shared.begin(.voice) } // 5. 启动语音识别(直接使用self作为回调) guard let success = azureAsrHelper?.startContinuousRecognition( callback: self, audioSourceType: audioSourceType ), success else { sendError("启动语音识别失败", code: "RECOGNITION_START_ERROR") return false } return true } /// 停止语音识别 /// - Returns: 是否成功停止识别 func stopRecognition() -> Bool { if !isRecognizing { return true } // 停止语音识别 guard let success = azureAsrHelper?.stopContinuousRecognition(), success else { return false } // 回调已集成到主类中,无需额外清理 return true } /// 推送音频数据 /// - Parameter audioData: 音频数据 /// - Returns: 是否成功推送 func pushAudioData(_ audioData: Data) -> Bool { if !isRecognizing || !isInitialized { return false } // 将音频数据推送到语音识别引擎 azureAsrHelper?.pushAudioData(data: audioData) return true } // MARK: - 语音合成 /// 语音合成播放文本 /// - Parameter text: 要播放的文本 /// - Returns: 是否成功开始播放 func speakText(_ text: String) -> Bool { if !isInitialized { sendError("服务未初始化", code: "NOT_INITIALIZED") return false } if text.isEmpty { return false } // 使用TTS服务进行语音合成 return azureTtsHelper?.speakOnce(text) ?? false } /// 停止语音合成 /// - Returns: 是否成功停止合成 func stopTts() -> Bool { if !isSpeaking { return true } // 停止TTS服务 let success = azureTtsHelper?.stop() ?? false if success { isSpeaking = false restartIdleCheck() sendEvent(name: "tts_stopped", data: ["status": "stopped"]) } return success } /// 打断当前响应 /// - Returns: 是否成功打断 func interruptCurrentResponse() -> Bool { var interrupted = false // 停止TTS播放 if isSpeaking { interrupted = stopTts() || interrupted } // 停止AI流输出 if isAiStreaming { isAiStreaming = false _ = openAIService?.cancelCurrentStream() interrupted = true } // 发送打断事件 if interrupted { sendEvent(name: "response_interrupted", data: ["status": "interrupted"]) } return interrupted } // MARK: - 文本处理 /// 处理文本输入 /// - Parameters: /// - text: 文本内容 /// - speakResponse: 是否朗读响应 /// - Returns: 是否成功处理 func processTextInput(_ text: String, speakResponse: Bool) -> Bool { if !isInitialized { sendError("服务未初始化", code: "NOT_INITIALIZED") return false } if text.isEmpty { sendError("文本输入不能为空", code: "EMPTY_TEXT") return false } // 使用OpenAI处理文本 processWithOpenAI(text: text, speakResponse: speakResponse) return true } /// 使用OpenAI处理文本消息 /// - Parameters: /// - text: 用户输入文本 /// - speakResponse: 是否使用TTS朗读回复 private func processWithOpenAI(text: String, speakResponse: Bool = true) { os_log("用户问题: %{public}@", log: logger, type: .info, text) guard let openAIService = openAIService else { sendError("OpenAI服务未初始化", code: "OPENAI_NOT_INITIALIZED") return } // 创建用户消息 let userMessage = openAIService.createUserMessage(content: text) processWithOpenAIInternal(userMessage: userMessage, displayText: text, speakResponse: speakResponse) } /// 内部方法:通用的OpenAI处理逻辑 /// - Parameters: /// - userMessage: 用户消息(可以是文本或图片格式) /// - displayText: 用于显示和存储的文本 /// - speakResponse: 是否朗读回复 /// - hasImage: 是否包含图片 private func processWithOpenAIInternal(userMessage: [String: Any], displayText: String, speakResponse: Bool = true, hasImage: Bool = false) { guard let openAIService = openAIService else { sendError("OpenAI服务未初始化", code: "OPENAI_NOT_INITIALIZED") return } // 如果有正在进行的AI流式输出,先停止它 if isAiStreaming { _ = interruptCurrentResponse() } // 设置状态为正在流式输出 isAiStreaming = true // 构建完整的消息列表 var messages: [[String: Any]] = [] // 添加系统提示词 if !systemPrompt.isEmpty { messages.append(openAIService.createSystemMessage(content: systemPrompt)) } // 添加历史消息 messages.append(contentsOf: chatHistory) // 添加当前用户消息 messages.append(userMessage) // 将用户消息添加到历史记录(注意:要去掉图片数据再保存) addToHistoryMessages(openAIService.createUserMessage(content: displayText)) // 创建流式回调 let callback = OpenAIStreamCallback( agentService: self, speakResponse: speakResponse, displayText: displayText, hasImage: hasImage ) // 发送流式请求 openAIService.sendMessageStream(messages: messages, callback: callback) } // MARK: - 图片处理 /// 处理图片输入 /// - Parameters: /// - imagePath: 图片路径 /// - text: 文本描述 /// - speakResponse: 是否朗读响应 /// - Returns: 是否成功处理 func processImageInput(imagePath: String, text: String, speakResponse: Bool) -> Bool { if !isInitialized { sendError("服务未初始化", code: "NOT_INITIALIZED") return false } if imagePath.isEmpty { sendError("图片路径不能为空", code: "EMPTY_IMAGE_PATH") return false } guard let openAIService = openAIService else { sendError("OpenAI服务未初始化", code: "OPENAI_NOT_INITIALIZED") return false } // 发送图片处理中事件 sendEvent(name: "image_processing", data: [ "status": "processing", "imagePath": imagePath ]) // 异步处理图片 DispatchQueue.global(qos: .userInitiated).async { [weak self] in guard let self = self else { return } // 将图片转换为Base64格式 guard let imageBase64 = openAIService.fileToBase64(filePath: imagePath) else { DispatchQueue.main.async { self.sendEvent(name: "error", data: [ "code": "IMAGE_CONVERSION_FAILED", "message": "图片转换失败" ]) } return } DispatchQueue.main.async { // 通知图片准备完成 self.sendEvent(name: "image_ready", data: [ "status": "ready", "imagePath": imagePath ]) // 创建带图片的用户消息 let userMessage = openAIService.createUserMessageWithImage(text: text, imageBase64: imageBase64) let displayText = text.isEmpty ? "[图片]" : text // 处理包含图片的消息 self.processWithOpenAIInternal( userMessage: userMessage, displayText: displayText, speakResponse: speakResponse, hasImage: true ) } } return true } // MARK: - 聊天历史管理 /// 添加消息到历史记录,保持最近10条 internal func addToHistoryMessages(_ message: [String: Any]) { chatHistory.append(message) // 如果超过10条,删除最早的消息 while chatHistory.count > 10 { chatHistory.removeFirst() } } /// 自动处理函数调用结果 /// - Parameter functionCallResult: 函数调用结果 /// - Returns: 处理后的元数据字符串 @discardableResult internal func autoHandleFunctionCallResult(_ functionCallResult: [String: Any]) -> String { guard let metaStr = functionCallResult["meta"] as? String, !metaStr.isEmpty else { return "" } do { guard let metaData = metaStr.data(using: .utf8), let meta = try JSONSerialization.jsonObject(with: metaData) as? [String: Any] else { return metaStr } // 处理音乐卡片 if let cardMusic = meta["card_music"] as? [String: Any] { os_log("检查到音乐卡片: %{public}@", log: logger, type: .info, cardMusic) let id = cardMusic["id"] as? String ?? "" let url = cardMusic["url"] as? String ?? "" let name = cardMusic["name"] as? String ?? "" let sgener = cardMusic["sgener"] as? String ?? "" let image = cardMusic["image"] as? String ?? "" processMusicPlay([ "id": id, "url": url, "title": name, "artist": sgener, "coverUrl": image ]) } return metaStr } catch { os_log("解析函数调用结果元数据失败: %{public}@", log: logger, type: .error, error.localizedDescription) return metaStr } } /// 处理音乐播放 /// - Parameter musicData: 音乐数据 private func processMusicPlay(_ musicData: [String: Any]) { // 发送音乐播放事件 sendEvent(name: "music_play", data: musicData) // 这里可以添加实际的音乐播放逻辑 // 例如调用音乐服务插件 os_log("处理音乐播放: %{public}@", log: logger, type: .info, musicData["title"] as? String ?? "未知") } /// 清除聊天历史 /// - Returns: 是否成功清除 func clearChatHistory() -> Bool { chatHistory.removeAll() return true } // MARK: - 资源释放 /// 释放资源 /// - Returns: 是否成功释放 func dispose() -> Bool { // 停止语音识别 if isRecognizing { stopRecognition() } // 停止语音合成 if isSpeaking { stopTts() } // 停止空闲检测 stopIdleCheck() // 移除BleService代理 BleService.shared.setDelegate(nil) // 释放Azure Speech资源 azureTtsHelper?.removeListener(self) azureAsrHelper?.dispose() azureTtsHelper?.dispose() // ASR回调已集成到主类中,无需额外清理 // 释放OpenAI服务 openAIService?.cancelAll() openAIService = nil // 释放音频播放器 audioPlayer = nil // 清除监听器 clearListeners() // 释放音频会话 AudioSessionHub.shared.appDidEnterBackground() isInitialized = false return true } } // MARK: - OpenAI 流式回调实现 class OpenAIStreamCallback: StreamCallback { private weak var agentService: AgentServiceImpl? private let speakResponse: Bool private let displayText: String private let hasImage: Bool private var responseBuilder = "" init(agentService: AgentServiceImpl, speakResponse: Bool, displayText: String, hasImage: Bool) { self.agentService = agentService self.speakResponse = speakResponse self.displayText = displayText self.hasImage = hasImage } func onToken(_ token: String) { guard let agentService = agentService else { return } responseBuilder += token // 发送流式回复token agentService.sendEvent(name: "assistant_token", data: ["token": token]) // 如果需要语音播报,则合成语音 if speakResponse { agentService.azureTtsHelper?.speakStream(token) } } func onComplete() { guard let agentService = agentService else { return } os_log("AI完整回复: %{public}@", log: agentService.logger, type: .info, responseBuilder) // 如果需要语音播报,则完成语音流 if speakResponse { agentService.azureTtsHelper?.flushStream() } let response = responseBuilder if !response.isEmpty { // 发送完整回复,包含是否有图片的标记 var responseData: [String: Any] = [ "text": response, "userInput": displayText ] if hasImage { responseData["hasImage"] = true } agentService.sendEvent(name: "assistant_response", data: responseData) // 添加AI回复到历史记录 let assistantMessage: [String: Any] = [ "role": "assistant", "content": response ] agentService.addToHistoryMessages(assistantMessage) } // 标记AI流式输出已完成 agentService.isAiStreaming = false } func onError(_ error: Error) { guard let agentService = agentService else { return } os_log("AI处理出错: %{public}@", log: agentService.logger, type: .error, error.localizedDescription) agentService.sendEvent(name: "error", data: [ "code": "AI_ERROR", "message": error.localizedDescription ]) // 标记AI流式输出已完成 agentService.isAiStreaming = false } func onFunctionCall(_ functionCall: [String: Any]) { guard let agentService = agentService else { return } os_log("收到函数调用: %{public}@", log: agentService.logger, type: .info, functionCall) // 播放函数调用提示音(循环播放) agentService.audioPlayer?.playCallingSound() // 发送函数调用事件,与Android版本保持一致 let name = functionCall["name"] as? String ?? "" agentService.sendEvent(name: "function_call", data: [ "name": name, "arguments": functionCall ]) // 如果是退出交互函数,停止识别 if name == "exit_interaction" { agentService.stopRecognition() } } func onFunctionCallResult(_ functionCall: [String: Any], _ functionCallResult: [String: Any]) { guard let agentService = agentService else { return } os_log("函数调用结果: %{public}@", log: agentService.logger, type: .info, functionCallResult) // 停止函数调用提示音(循环播放) agentService.audioPlayer?.stopCallingSound() // 发送函数调用结果事件,与Android版本保持一致 agentService.sendEvent(name: "function_call_result", data: [ "function_call": functionCall, "result": functionCallResult ]) // 自动处理函数调用结果(如音乐播放等) agentService.autoHandleFunctionCallResult(functionCallResult) } } // MARK: - TtsEventListener 实现 extension AgentServiceImpl: TtsEventListener { func onEvent(_ event: TtsEvent) { switch event.type { case .synthesisStarted: isSpeaking = true restartIdleCheck() sendEvent(name: "tts_started", data: ["status": "started"]) case .synthesisCompleted: isSpeaking = false restartIdleCheck() sendEvent(name: "tts_completed", data: ["status": "completed"]) case .synthesisCanceled: isSpeaking = false restartIdleCheck() var data: [String: Any] = ["status": "canceled"] if let reason = event.params["reason"] as? String { data["reason"] = reason } if let errorDetails = event.params["errorDetails"] as? String { data["details"] = errorDetails } sendEvent(name: "tts_canceled", data: data) case .error: isSpeaking = false restartIdleCheck() var data: [String: Any] = [:] if let errorCode = event.params["errorCode"] as? String { data["errorCode"] = errorCode } if let errorMessage = event.params["errorMessage"] as? String { data["message"] = errorMessage } else { data["message"] = "TTS错误" } sendEvent(name: "error", data: data) default: break } } } // MARK: - BleService.Callback 实现 extension AgentServiceImpl: BleService.Callback { func onScanResult(devices: [[String: Any]]) { // 不处理扫描结果 } func onConnectionStateChanged(state: Int) { // 可以在这里处理连接状态变化,例如通知UI更新 } func onAudioDataReceived(data: Data) { // 直接将音频数据推送到Azure Speech进行识别 pushAudioData(data) } func onWakeupSignalReceived() { os_log("收到BLE设备唤醒信号", log: logger, type: .info) // 停止当前TTS,避免冲突 stopTts() // 如果已经在识别,则重新启动 stopRecognition() // 使用BLE模式启动识别 startRecognition(useBle: true) } func onDeviceInfoReceived(infoType: Int, infoData: [String: Any]) { // 可以处理设备信息,例如电量、设备名称等 } } // MARK: - 音频播放器 /// 简单的音频播放器,用于播放提示音 class AudioPlayer { private var player: AVAudioPlayer? private var callingPlayer: AVAudioPlayer? private let logger = OSLog(subsystem: "com.yunqiinnovation.agent_service", category: "AudioPlayer") private let audioQueue = DispatchQueue(label: "agent.audio.player", qos: .userInitiated) /// 播放开始识别提示音 func playStartSound() { playSound(named: "start", fileType: "mp3") } /// 播放停止识别提示音 func playStopSound() { playSound(named: "stop", fileType: "mp3") } /// 播放函数调用提示音(循环播放) func playCallingSound() { playLoopSound(named: "calling", fileType: "mp3") } /// 停止函数调用提示音 func stopCallingSound() { callingPlayer?.stop() callingPlayer = nil os_log("停止函数调用提示音", log: logger, type: .info) } /// 播放音频文件 /// - Parameters: /// - named: 音频文件名 /// - fileType: 音频文件类型 (默认mp3) private func playSound(named: String, fileType: String = "mp3") { // 直接从插件Bundle查找资源(已确认这种方式可以找到) if let path = Bundle(for: type(of: self)).path(forResource: named, ofType: fileType) { let url = URL(fileURLWithPath: path) safePlayAudio(url: url) return } os_log("未找到音频文件: %{public}@.%{public}@", log: logger, type: .error, named, fileType) } /// 播放循环音频文件 /// - Parameters: /// - named: 音频文件名 /// - fileType: 音频文件类型 (默认mp3) private func playLoopSound(named: String, fileType: String = "mp3") { // 先停止之前的循环播放 stopCallingSound() // 直接从插件Bundle查找资源 if let path = Bundle(for: type(of: self)).path(forResource: named, ofType: fileType) { let url = URL(fileURLWithPath: path) safePlayLoopAudio(url: url) return } os_log("未找到循环音频文件: %{public}@.%{public}@", log: logger, type: .error, named, fileType) } /// 安全播放音频文件,处理各种异常情况 private func safePlayAudio(url: URL) { do { // 1. 确保没有活跃的播放器 player?.stop() player = nil // 3. 创建播放器 player = try AVAudioPlayer(contentsOf: url) // 4. 确保播放器创建成功 guard let player = player else { os_log("无法创建音频播放器", log: logger, type: .error) return } // 5. 设置音量和准备播放 player.volume = 0.7 // 降低音量以避免冲突 player.prepareToPlay() // 6. 异步播放,避免阻塞主线程 audioQueue.async { [weak self] in guard let self = self else { return } // 开始播放 let playResult = player.play() // 等待播放完成 let playTime = player.duration + 0.5 Thread.sleep(forTimeInterval: playTime) // 播放完成不再需要处理AudioSessionHub } } catch { os_log("播放音频失败: %{public}@", log: logger, type: .error, error.localizedDescription) } } /// 安全播放循环音频文件 private func safePlayLoopAudio(url: URL) { do { // 创建循环播放器 callingPlayer = try AVAudioPlayer(contentsOf: url) // 确保播放器创建成功 guard let callingPlayer = callingPlayer else { os_log("无法创建循环音频播放器", log: logger, type: .error) return } // 设置循环播放 callingPlayer.numberOfLoops = -1 // -1 表示无限循环 callingPlayer.volume = 0.5 // 降低音量 callingPlayer.prepareToPlay() // 开始播放 let playResult = callingPlayer.play() if playResult { os_log("开始播放函数调用提示音(循环)", log: logger, type: .info) } else { os_log("播放函数调用提示音失败", log: logger, type: .error) } } catch { os_log("播放循环音频失败: %{public}@", log: logger, type: .error, error.localizedDescription) } } } // MARK: - ASR回调实现(使用Extension模式) extension AgentServiceImpl: AzureAsrHelper.ContinuousRecognizeCallback { func onResult(_ text: String, _ detectedLanguage: String) { if !text.isEmpty { var data: [String: Any] = ["text": text] if !detectedLanguage.isEmpty { data["language"] = detectedLanguage } sendEvent(name: "recognition_result", data: data) // 处理识别结果,发送到OpenAI processTextInput(text, speakResponse: true) } // 重置状态,继续识别 let previousHasSpeech = hasSpeechDetected hasSpeechDetected = false // 只有在之前检测到语音的情况下才重启检测 if previousHasSpeech { restartIdleCheck() } } func onRecognizing(_ recognizing: String, _ detectedLanguage: String) { os_log("识别中: %{public}@", log: logger, type: .info, recognizing) // 只有在有实际语音内容时才更新状态 if !recognizing.isEmpty { // 检测到语音,更新状态 let previousHasSpeech = hasSpeechDetected hasSpeechDetected = true if !previousHasSpeech { os_log("检测到语音,重启空闲检测", log: logger, type: .debug) restartIdleCheck() } var data: [String: Any] = ["text": recognizing] if !detectedLanguage.isEmpty { data["language"] = detectedLanguage } sendEvent(name: "recognizing", data: data) // 如果TTS正在播放或AI正在生成,则触发打断 if isSpeaking || isAiStreaming { interruptCurrentResponse() } } } func onSessionStarted() { os_log("开始识别", log: logger, type: .info) sendEvent(name: "recognition_started", data: ["status": "started"]) isRecognizing = true hasSpeechDetected = false // 重置语音检测状态 startIdleCheck() audioPlayer?.playStartSound() } func onSessionStopped() { os_log("停止识别", log: logger, type: .info) sendEvent(name: "recognition_stopped", data: ["status": "stopped"]) isRecognizing = false hasSpeechDetected = false // 重置语音检测状态 stopIdleCheck() audioPlayer?.playStopSound() } func onCanceled(_ reason: String, _ errorDetails: String) { var data: [String: Any] = [:] if !reason.isEmpty { data["reason"] = reason } if !errorDetails.isEmpty { data["details"] = errorDetails } sendEvent(name: "recognition_canceled", data: data) isRecognizing = false stopIdleCheck() } func onError(_ error: String) { let data: [String: Any] = ["message": error.isEmpty ? "未知错误" : error] sendEvent(name: "error", data: data) isRecognizing = false stopIdleCheck() } }