You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
797 lines
25 KiB
797 lines
25 KiB
import Foundation
|
|
import AVFoundation
|
|
import ble_service
|
|
import speech
|
|
|
|
/// 代理服务事件监听器接口
|
|
protocol AgentServiceListener {
|
|
/// 当事件发生时调用
|
|
/// - 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 = Set<AgentServiceListener>()
|
|
private let listenersLock = NSLock()
|
|
|
|
// Azure 语音相关
|
|
private var azureSpeechKey: String = ""
|
|
private var azureSpeechRegion: String = ""
|
|
private var isRecognizing: Bool = false
|
|
private var isSpeaking: Bool = false
|
|
|
|
// 语音识别和合成服务
|
|
private var azureAsrHelper: AzureAsrHelper?
|
|
private var azureTtsHelper: AzureTtsHelper?
|
|
|
|
// OpenAI相关
|
|
private var apiKey: String = ""
|
|
private var baseUrl: String = "https://api.openai.com"
|
|
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 hasSpeechDetected: Bool = false
|
|
private var isAiStreaming: Bool = false
|
|
|
|
// 空闲检测相关
|
|
private var idleCheckTimer: Timer?
|
|
private let maxIdleSeconds: TimeInterval = 10 // 最大空闲秒数
|
|
|
|
// 音频播放器
|
|
private var audioPlayer: AudioPlayer?
|
|
|
|
// MARK: - 初始化
|
|
|
|
/// 私有初始化方法
|
|
private override init() {
|
|
super.init()
|
|
|
|
// 设置音频会话
|
|
setupAudioSession()
|
|
|
|
// 初始化音频播放器
|
|
audioPlayer = AudioPlayer()
|
|
|
|
// 初始化语音助手服务
|
|
azureAsrHelper = AzureAsrHelper()
|
|
azureTtsHelper = AzureTtsHelper()
|
|
|
|
// 设置BleService回调
|
|
BleService.shared.setDelegate(self)
|
|
}
|
|
|
|
// MARK: - 监听器管理
|
|
|
|
/// 添加事件监听器
|
|
/// - Parameter listener: 要添加的监听器
|
|
func addListener(_ listener: AgentServiceListener) {
|
|
listenersLock.lock()
|
|
listeners.insert(listener)
|
|
listenersLock.unlock()
|
|
}
|
|
|
|
/// 移除事件监听器
|
|
/// - Parameter listener: 要移除的监听器
|
|
func removeListener(_ listener: AgentServiceListener) {
|
|
listenersLock.lock()
|
|
listeners.remove(listener)
|
|
listenersLock.unlock()
|
|
}
|
|
|
|
/// 清除所有监听器
|
|
func clearListeners() {
|
|
listenersLock.lock()
|
|
listeners.removeAll()
|
|
listenersLock.unlock()
|
|
}
|
|
|
|
/// 向所有监听器发送事件
|
|
/// - Parameters:
|
|
/// - eventName: 事件名称
|
|
/// - data: 事件数据
|
|
private 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 }
|
|
|
|
// 设置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()
|
|
|
|
isInitialized = asrInitSuccess
|
|
return asrInitSuccess
|
|
}
|
|
|
|
// MARK: - 音频会话设置
|
|
|
|
/// 设置音频会话
|
|
private func setupAudioSession() {
|
|
let audioSession = AVAudioSession.sharedInstance()
|
|
do {
|
|
try audioSession.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker, .allowBluetooth])
|
|
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
|
|
} catch {
|
|
sendError("设置音频会话失败: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
// MARK: - Azure 语音服务
|
|
|
|
/// 初始化Azure语音服务
|
|
private func initializeAzureSpeech() -> Bool {
|
|
// 添加事件监听器
|
|
azureAsrHelper?.addListener(self)
|
|
azureTtsHelper?.addListener(self)
|
|
|
|
// 初始化ASR服务
|
|
guard let asrSuccess = azureAsrHelper?.initialize(
|
|
subscriptionKey: azureSpeechKey,
|
|
region: azureSpeechRegion,
|
|
supportedLanguages: ["zh-CN"]
|
|
), 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: - 空闲检测
|
|
|
|
/// 启动空闲检测
|
|
private func startIdleCheck() {
|
|
stopIdleCheck() // 先停止现有的检查
|
|
|
|
if !isRecognizing { return }
|
|
|
|
// 创建一个新的定时器
|
|
idleCheckTimer = Timer.scheduledTimer(withTimeInterval: maxIdleSeconds, repeats: false) { [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
|
|
])
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 停止空闲检测
|
|
private func stopIdleCheck() {
|
|
idleCheckTimer?.invalidate()
|
|
idleCheckTimer = nil
|
|
}
|
|
|
|
/// 重启空闲检测
|
|
private func restartIdleCheck() {
|
|
if isRecognizing {
|
|
startIdleCheck()
|
|
}
|
|
}
|
|
|
|
// MARK: - 语音识别
|
|
|
|
/// 开始语音识别
|
|
/// - Returns: 是否成功启动识别
|
|
func startRecognition() -> Bool {
|
|
if !isInitialized {
|
|
sendError("服务未初始化", code: "NOT_INITIALIZED")
|
|
return false
|
|
}
|
|
|
|
// 防止重复启动
|
|
if isRecognizing {
|
|
stopRecognition()
|
|
}
|
|
|
|
// 启动语音识别
|
|
guard let success = azureAsrHelper?.startContinuousRecognition(), 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
|
|
}
|
|
|
|
// 将音频数据推送到语音识别引擎
|
|
return azureAsrHelper?.pushAudioData(audioData: audioData) ?? false
|
|
}
|
|
|
|
// 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服务
|
|
return azureTtsHelper?.stop() ?? false
|
|
}
|
|
|
|
/// 打断当前响应
|
|
/// - Returns: 是否成功打断
|
|
func interruptCurrentResponse() -> Bool {
|
|
var interrupted = false
|
|
|
|
// 停止TTS播放
|
|
if isSpeaking {
|
|
interrupted = stopTts() || interrupted
|
|
}
|
|
|
|
// 停止AI流输出
|
|
if isAiStreaming {
|
|
isAiStreaming = false
|
|
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
|
|
}
|
|
|
|
// TODO: 实现与OpenAI的通信
|
|
// 这里只是模拟处理
|
|
|
|
// 将用户消息添加到聊天历史
|
|
let userMessage: [String: Any] = [
|
|
"role": "user",
|
|
"content": text
|
|
]
|
|
chatHistory.append(userMessage)
|
|
|
|
// 更新状态
|
|
isAiStreaming = true
|
|
|
|
// 模拟助手响应
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
|
guard let self = self else { return }
|
|
|
|
// 模拟流式输出
|
|
let response = "这是对"\(text)"的回复。我是AI助手,很高兴为您服务。"
|
|
|
|
// 逐词输出模拟
|
|
let words = response.split(separator: " ")
|
|
for (index, word) in words.enumerated() {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.1) { [weak self] in
|
|
guard let self = self, self.isAiStreaming else { return }
|
|
self.sendEvent(name: "assistant_token", data: ["token": String(word) + " "])
|
|
|
|
// 如果需要语音播报,则合成语音
|
|
if speakResponse {
|
|
self.azureTtsHelper?.speakStream(String(word) + " ")
|
|
}
|
|
}
|
|
}
|
|
|
|
// 完整响应
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + Double(words.count) * 0.1 + 0.2) { [weak self] in
|
|
guard let self = self else { return }
|
|
|
|
// 结束AI流式输出
|
|
self.isAiStreaming = false
|
|
|
|
// 发送完整响应
|
|
self.sendEvent(name: "assistant_response", data: ["text": response])
|
|
|
|
// 将助手消息添加到聊天历史
|
|
let assistantMessage: [String: Any] = [
|
|
"role": "assistant",
|
|
"content": response
|
|
]
|
|
self.chatHistory.append(assistantMessage)
|
|
|
|
// 如果需要语音播报,则完成语音流
|
|
if speakResponse {
|
|
self.azureTtsHelper?.flushStream()
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 发送图片处理中事件
|
|
sendEvent(name: "image_processing", data: [:])
|
|
|
|
// TODO: 实现与OpenAI Vision API的通信
|
|
// 这里只是模拟处理
|
|
|
|
// 模拟图片处理
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
|
|
guard let self = self else { return }
|
|
|
|
// 图片就绪事件
|
|
self.sendEvent(name: "image_ready", data: [:])
|
|
|
|
// 更新状态
|
|
self.isAiStreaming = true
|
|
|
|
// 模拟AI分析结果
|
|
let imageDescription = "这是一张图片分析结果。"
|
|
let additionalText = text.isEmpty ? "" : "关于您提问的"\(text)","
|
|
let response = "\(imageDescription)\(additionalText)我可以看到这是一张图片。"
|
|
|
|
// 发送AI助手token流
|
|
let words = response.split(separator: " ")
|
|
for (index, word) in words.enumerated() {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.1) { [weak self] in
|
|
guard let self = self, self.isAiStreaming else { return }
|
|
|
|
let token = String(word) + " "
|
|
self.sendEvent(name: "assistant_token", data: ["token": token])
|
|
|
|
// 如果需要语音播报,则合成语音
|
|
if speakResponse {
|
|
self.azureTtsHelper?.speakStream(token)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 发送完整响应
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + Double(words.count) * 0.1 + 0.2) { [weak self] in
|
|
guard let self = self else { return }
|
|
|
|
// 结束AI流式输出
|
|
self.isAiStreaming = false
|
|
|
|
self.sendEvent(name: "assistant_response", data: [
|
|
"text": response,
|
|
"userInput": text.isEmpty ? "[图片]" : text,
|
|
"hasImage": true
|
|
])
|
|
|
|
// 如果需要语音播报,则完成语音
|
|
if speakResponse {
|
|
self.azureTtsHelper?.flushStream()
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// MARK: - 聊天历史管理
|
|
|
|
/// 清除聊天历史
|
|
/// - 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资源
|
|
azureAsrHelper?.removeListener(self)
|
|
azureTtsHelper?.removeListener(self)
|
|
azureAsrHelper?.dispose()
|
|
azureTtsHelper?.dispose()
|
|
|
|
// 释放音频播放器
|
|
audioPlayer = nil
|
|
|
|
// 清除监听器
|
|
clearListeners()
|
|
|
|
isInitialized = false
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
// MARK: - AsrEventListener 实现
|
|
extension AgentServiceImpl: AsrEventListener {
|
|
func onEvent(_ event: AsrEvent) {
|
|
switch event.type {
|
|
case .sessionStarted:
|
|
sendEvent(name: "recognition_started", data: [:])
|
|
isRecognizing = true
|
|
startIdleCheck()
|
|
audioPlayer?.playStartSound()
|
|
|
|
case .sessionStopped:
|
|
sendEvent(name: "recognition_stopped", data: [:])
|
|
isRecognizing = false
|
|
stopIdleCheck()
|
|
audioPlayer?.playStopSound()
|
|
|
|
case .recognizing:
|
|
if let text = event.params["text"] as? String {
|
|
// 检测到语音,更新状态
|
|
let previousHasSpeech = hasSpeechDetected
|
|
hasSpeechDetected = true
|
|
|
|
if !previousHasSpeech {
|
|
restartIdleCheck()
|
|
}
|
|
|
|
var data: [String: Any] = ["text": text]
|
|
if let language = event.params["language"] as? String, !language.isEmpty {
|
|
data["language"] = language
|
|
}
|
|
|
|
sendEvent(name: "recognizing", data: data)
|
|
|
|
// 如果TTS正在播放或AI正在生成,则触发打断
|
|
if isSpeaking || isAiStreaming {
|
|
interruptCurrentResponse()
|
|
}
|
|
}
|
|
|
|
case .recognized:
|
|
if let text = event.params["text"] as? String, !text.isEmpty {
|
|
var data: [String: Any] = ["text": text]
|
|
if let language = event.params["language"] as? String, !language.isEmpty {
|
|
data["language"] = language
|
|
}
|
|
|
|
sendEvent(name: "recognition_result", data: data)
|
|
|
|
// 处理识别结果,发送到OpenAI
|
|
processTextInput(text, speakResponse: true)
|
|
}
|
|
|
|
// 重置状态,继续识别
|
|
let previousHasSpeech = hasSpeechDetected
|
|
hasSpeechDetected = false
|
|
|
|
if previousHasSpeech {
|
|
restartIdleCheck()
|
|
}
|
|
|
|
case .canceled:
|
|
var data: [String: Any] = [:]
|
|
if let reason = event.params["reason"] as? String {
|
|
data["reason"] = reason
|
|
}
|
|
if let errorDetails = event.params["errorDetails"] as? String {
|
|
data["details"] = errorDetails
|
|
}
|
|
|
|
sendEvent(name: "recognition_canceled", data: data)
|
|
isRecognizing = false
|
|
stopIdleCheck()
|
|
|
|
case .error:
|
|
var data: [String: Any] = [:]
|
|
if let errorCode = event.params["errorCode"] as? String {
|
|
data["errorCode"] = errorCode
|
|
}
|
|
if let message = event.params["message"] as? String {
|
|
data["message"] = message
|
|
} else if let error = event.params["error"] as? String {
|
|
data["message"] = error
|
|
} else {
|
|
data["message"] = "未知错误"
|
|
}
|
|
|
|
sendEvent(name: "recognition_error", data: data)
|
|
isRecognizing = false
|
|
stopIdleCheck()
|
|
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - TtsEventListener 实现
|
|
extension AgentServiceImpl: TtsEventListener {
|
|
func onEvent(_ event: TtsEvent) {
|
|
switch event.type {
|
|
case .synthesisStarted:
|
|
isSpeaking = true
|
|
restartIdleCheck()
|
|
sendEvent(name: "tts_started", data: [:])
|
|
|
|
case .synthesisCompleted:
|
|
isSpeaking = false
|
|
restartIdleCheck()
|
|
sendEvent(name: "tts_completed", data: [:])
|
|
|
|
case .synthesisCanceled:
|
|
isSpeaking = false
|
|
restartIdleCheck()
|
|
|
|
var data: [String: Any] = [:]
|
|
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() {
|
|
NSLog("\(TAG): 收到唤醒信号")
|
|
|
|
// 停止当前TTS,避免冲突
|
|
stopTts()
|
|
|
|
// 如果已经在识别,则重新启动
|
|
stopRecognition()
|
|
startRecognition()
|
|
}
|
|
|
|
func onDeviceInfoReceived(infoType: Int, infoData: [String: Any]) {
|
|
// 可以处理设备信息,例如电量、设备名称等
|
|
}
|
|
}
|
|
|
|
// MARK: - 音频播放器
|
|
|
|
/// 简单的音频播放器,用于播放提示音
|
|
class AudioPlayer {
|
|
private var player: AVAudioPlayer?
|
|
|
|
/// 播放开始识别提示音
|
|
func playStartSound() {
|
|
playSound(named: "start")
|
|
}
|
|
|
|
/// 播放停止识别提示音
|
|
func playStopSound() {
|
|
playSound(named: "stop")
|
|
}
|
|
|
|
/// 播放音频文件
|
|
/// - Parameter named: 音频文件名
|
|
private func playSound(named: String) {
|
|
guard let path = Bundle.main.path(forResource: named, ofType: "wav") else {
|
|
print("找不到音频文件: \(named).wav")
|
|
return
|
|
}
|
|
|
|
let url = URL(fileURLWithPath: path)
|
|
|
|
do {
|
|
player = try AVAudioPlayer(contentsOf: url)
|
|
player?.prepareToPlay()
|
|
player?.play()
|
|
} catch {
|
|
print("播放音频失败: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
}
|