import Flutter import UIKit import AVFoundation import MicrosoftCognitiveServicesSpeech // 自定义语音处理组件,提供音频流处理等功能 import speech import os.log /** * Azure Speech Plugin * * 基于微软Azure语音服务的Flutter插件 * 提供语音识别(ASR)和语音合成(TTS)功能 */ @objc public class AzureSpeechPlugin: NSObject, FlutterPlugin { // 日志标签 private let tag = "AzureSpeechPlugin" // 日志对象 private let log = OSLog(subsystem: "com.azure.speech", category: "AzureSpeechPlugin") // ASR相关 private var asrChannel: FlutterMethodChannel? private var asrEventChannel: FlutterEventChannel? private var asrEventSink: FlutterEventSink? private let azureAsrHelper = AzureAsrHelper() // TTS相关 private var ttsChannel: FlutterMethodChannel? private var ttsEventChannel: FlutterEventChannel? private var ttsEventSink: FlutterEventSink? private let azureTtsHelper = AzureTtsHelper() // 是否已添加TTS事件监听器 private var isTtsListenerAdded = false // 当前的连续识别回调 private var currentAsrCallback: AsrCallbackWrapper? // 插件注册 public static func register(with registrar: FlutterPluginRegistrar) { let instance = AzureSpeechPlugin() // 初始化ASR通道 let asrChannel = FlutterMethodChannel(name: "azure_speech/asr", binaryMessenger: registrar.messenger()) registrar.addMethodCallDelegate(instance, channel: asrChannel) instance.asrChannel = asrChannel // 初始化TTS通道 let ttsChannel = FlutterMethodChannel(name: "azure_speech/tts", binaryMessenger: registrar.messenger()) registrar.addMethodCallDelegate(instance, channel: ttsChannel) instance.ttsChannel = ttsChannel // 初始化ASR事件通道 let asrEventChannel = FlutterEventChannel(name: "azure_speech/asr_events", binaryMessenger: registrar.messenger()) asrEventChannel.setStreamHandler(instance) instance.asrEventChannel = asrEventChannel // 初始化TTS事件通道 let ttsEventChannel = FlutterEventChannel(name: "azure_speech/tts_events", binaryMessenger: registrar.messenger()) ttsEventChannel.setStreamHandler(instance) instance.ttsEventChannel = ttsEventChannel } // 发送ASR事件方法 internal func sendAsrEvent(_ event: [String: Any]) { if asrEventSink == nil { os_log("无法发送ASR事件:事件通道未准备好", log: log, type: .error) return } DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.asrEventSink?(event) } } // 发送TTS事件方法 private func sendTtsEvent(_ event: [String: Any]) { if ttsEventSink == nil { os_log("无法发送TTS事件:事件通道未准备好", log: log, type: .error) return } DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.ttsEventSink?(event) } } // 设置TTS事件监听器 private func setupTtsEventListener() { if !isTtsListenerAdded { azureTtsHelper.addListener(self) isTtsListenerAdded = true } } // 处理Flutter方法调用 public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { if call.method.hasPrefix("tts_") { handleTtsMethodCall(call, result) } else { handleAsrMethodCall(call, result) } } // MARK: - ASR 方法处理 private func handleAsrMethodCall(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { switch call.method { case "initialize": guard let args = call.arguments as? [String: Any], let subscriptionKey = args["subscriptionKey"] as? String, let region = args["region"] as? String else { result(FlutterError(code: "INVALID_ARGUMENTS", message: "必要的参数不能为空", details: nil)) return } let supportedLanguages = args["supportedLanguages"] as? [String] ?? ["zh-CN"] // 初始化ASR引擎 let success = azureAsrHelper.initialize( subscriptionKey: subscriptionKey, region: region, supportedLanguages: supportedLanguages, audioSourceType: .microphone ) result(success) case "recognizeOnce": // iOS版本不支持recognizeOnce result(FlutterError(code: "NOT_SUPPORTED", message: "iOS版本不支持recognizeOnce", details: nil)) case "startContinuousRecognition": // 确保事件通道已准备好 guard asrEventSink != nil else { result(FlutterError(code: "EVENT_CHANNEL_NOT_READY", message: "事件通道未准备好,无法开始连续识别", details: nil)) return } // 解析音频源类型 let audioSourceType: AzureAsrHelper.AudioSourceType if let args = call.arguments as? [String: Any], let audioSourceString = args["audioSourceType"] as? String { switch audioSourceString.lowercased() { case "external": audioSourceType = .external default: audioSourceType = .microphone } } else { audioSourceType = .microphone } // 创建回调包装器 currentAsrCallback = AsrCallbackWrapper(plugin: self) let success = azureAsrHelper.startContinuousRecognition( callback: currentAsrCallback!, audioSourceType: audioSourceType ) result(success) case "stopContinuousRecognition": let success = azureAsrHelper.stopContinuousRecognition() currentAsrCallback = nil result(success) case "isContinuousRecognitionActive": result(azureAsrHelper.isContinuousRecognitionActive()) case "dispose": azureAsrHelper.dispose() currentAsrCallback = nil result(true) case "pushAudioData": guard let args = call.arguments as? [String: Any], let audioBytes = args["data"] as? FlutterStandardTypedData else { result(FlutterError(code: "INVALID_ARGUMENTS", message: "音频数据不能为空", details: nil)) return } azureAsrHelper.pushAudioData(data: audioBytes.data) result(true) default: result(FlutterMethodNotImplemented) } } // MARK: - TTS 方法处理 private func handleTtsMethodCall(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { switch call.method { case "tts_initialize": guard let args = call.arguments as? [String: Any], let subscriptionKey = args["subscriptionKey"] as? String, let region = args["region"] as? String else { result(FlutterError(code: "INVALID_ARGUMENTS", message: "必要的参数不能为空", details: nil)) return } // 初始化TTS引擎 let language = args["language"] as? String ?? "zh-CN" let success = azureTtsHelper.initialize(ttsAppId: "", ttsAppToken: subscriptionKey, ttsResource: region, language: language) // 设置TTS事件监听器 setupTtsEventListener() result(success) case "tts_set_voice": guard let args = call.arguments as? [String: Any], let voiceName = args["voiceName"] as? String else { result(FlutterError(code: "INVALID_ARGUMENTS", message: "语音名称不能为空", details: nil)) return } let success = azureTtsHelper.setVoice(voiceName) result(success) case "tts_speak_once": guard let args = call.arguments as? [String: Any], let text = args["text"] as? String else { result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil)) return } let success = azureTtsHelper.speakOnce(text) result(success) case "tts_speak_stream": guard let args = call.arguments as? [String: Any], let text = args["text"] as? String else { result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil)) return } let success = azureTtsHelper.speakStream(text) result(success) case "tts_flush_stream": let success = azureTtsHelper.flushStream() result(success) case "tts_stop": let success = azureTtsHelper.stop() result(success) case "tts_isSpeaking": result(azureTtsHelper.isSpeaking()) case "tts_release": azureTtsHelper.dispose() result(true) default: result(FlutterMethodNotImplemented) } } } // MARK: - ASR回调包装器 private class AsrCallbackWrapper: AzureAsrHelper.ContinuousRecognizeCallback { private weak var plugin: AzureSpeechPlugin? init(plugin: AzureSpeechPlugin) { self.plugin = plugin } func onResult(_ text: String, _ detectedLanguage: String) { plugin?.sendAsrEvent([ "type": "result", "text": text, "language": detectedLanguage ]) } func onRecognizing(_ recognizing: String, _ detectedLanguage: String) { plugin?.sendAsrEvent([ "type": "recognizing", "text": recognizing, "language": detectedLanguage ]) } func onSessionStarted() { plugin?.sendAsrEvent([ "type": "sessionStarted" ]) } func onSessionStopped() { plugin?.sendAsrEvent([ "type": "sessionStopped" ]) } func onCanceled(_ reason: String, _ errorDetails: String) { plugin?.sendAsrEvent([ "type": "canceled", "reason": reason, "errorDetails": errorDetails ]) } func onError(_ error: String) { plugin?.sendAsrEvent([ "type": "error", "error": error ]) } } // MARK: - 事件处理 extension AzureSpeechPlugin: FlutterStreamHandler { public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { // 根据通道类型设置事件接收器 if let args = arguments as? [String: Any], let channel = args["channel"] as? String { if channel == "asr" { asrEventSink = events } else if channel == "tts" { ttsEventSink = events setupTtsEventListener() } } else if arguments == nil { // 如果没有指定通道,尝试弄清楚是哪个通道在监听 if asrEventSink == nil && ttsEventSink != nil { asrEventSink = events } else if asrEventSink != nil && ttsEventSink == nil { ttsEventSink = events setupTtsEventListener() } else { // 无法确定哪个通道,默认设置为ASR asrEventSink = events } } return nil } public func onCancel(withArguments arguments: Any?) -> FlutterError? { // 根据通道类型清除事件接收器 if let args = arguments as? [String: Any], let channel = args["channel"] as? String { if channel == "asr" { asrEventSink = nil } else if channel == "tts" { ttsEventSink = nil } } else { // 如果没有指定通道,清除所有通道 asrEventSink = nil ttsEventSink = nil } return nil } } // MARK: - TTS 事件监听实现 extension AzureSpeechPlugin: TtsEventListener { // 使用@objc特性为方法提供一个不同的Objective-C选择器名称 @objc(onTtsEvent:) public func onEvent(_ event: TtsEvent) { var eventMap: [String: Any] = [:] // 根据事件类型转换 switch event.type { case .synthesisStarted: eventMap["type"] = "synthesis_started" case .synthesisCompleted: eventMap["type"] = "synthesis_completed" case .synthesisCanceled: eventMap["type"] = "synthesis_canceled" if let reason = event.params["reason"] { eventMap["reason"] = reason } if let errorDetails = event.params["errorDetails"] { eventMap["errorDetails"] = errorDetails } case .error: eventMap["type"] = "error" if let errorCode = event.params["errorCode"] { eventMap["errorCode"] = errorCode } if let errorMessage = event.params["errorMessage"] { eventMap["errorMessage"] = errorMessage } @unknown default: eventMap["type"] = "unknown" for (key, value) in event.params { eventMap[key] = value } } sendTtsEvent(eventMap) } } // MARK: - 音频数据监听实现 extension AzureSpeechPlugin: AudioDataListener { public func onAudioData(_ data: Data) { if ttsEventSink == nil { return } let eventMap: [String: Any] = [ "type": "audio_data", "data": FlutterStandardTypedData(bytes: data) ] sendTtsEvent(eventMap) } }