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.
259 lines
10 KiB
259 lines
10 KiB
import Flutter
|
|
import UIKit
|
|
import MicrosoftCognitiveServicesSpeech
|
|
import AVFoundation
|
|
|
|
@available(iOS 13.0, *)
|
|
public class SwiftAzureSpeechRecognitionPlugin: NSObject, FlutterPlugin {
|
|
private var azureChannel: FlutterMethodChannel
|
|
private var ttsChannel: FlutterMethodChannel
|
|
private var asrHelper: AzureAsrHelper
|
|
private var ttsHelper: AzureTtsHelper
|
|
private static var eventStreamHandler: AzureEventStreamHandler?
|
|
|
|
// 创建方法到通道的映射
|
|
private static var ttsMethodHandlers = [String: FlutterMethodCallHandler]()
|
|
private static var asrMethodHandlers = [String: FlutterMethodCallHandler]()
|
|
|
|
public static func register(with registrar: FlutterPluginRegistrar) {
|
|
// ASR通道
|
|
let channel = FlutterMethodChannel(name: "com.deep_voice.azure_asr", binaryMessenger: registrar.messenger())
|
|
|
|
// TTS通道
|
|
let ttsChannel = FlutterMethodChannel(name: "com.deep_voice.azure_tts", binaryMessenger: registrar.messenger())
|
|
|
|
// 设置ASR事件通道
|
|
let eventChannel = FlutterEventChannel(name: "com.deep_voice.azure_asr_events", binaryMessenger: registrar.messenger())
|
|
eventStreamHandler = AzureEventStreamHandler()
|
|
eventChannel.setStreamHandler(eventStreamHandler)
|
|
|
|
let instance = SwiftAzureSpeechRecognitionPlugin(
|
|
azureChannel: channel,
|
|
ttsChannel: ttsChannel,
|
|
eventStreamHandler: eventStreamHandler!
|
|
)
|
|
|
|
// 直接设置各自通道的处理器
|
|
channel.setMethodCallHandler(instance.handleAsrMethodCalls)
|
|
ttsChannel.setMethodCallHandler(instance.handleTtsMethodCalls)
|
|
}
|
|
|
|
|
|
|
|
// 新增直接处理方法调用的函数
|
|
private func handleTtsMethodCalls(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
|
handleTtsMethod(call, result)
|
|
}
|
|
|
|
private func handleAsrMethodCalls(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
|
handleAsrMethod(call, result)
|
|
}
|
|
|
|
|
|
init(azureChannel: FlutterMethodChannel, ttsChannel: FlutterMethodChannel, eventStreamHandler: AzureEventStreamHandler) {
|
|
self.azureChannel = azureChannel
|
|
self.ttsChannel = ttsChannel
|
|
|
|
// 创建辅助类实例,使用自定义事件回调处理器
|
|
let eventHandler: (String, [String: Any]) -> Void = { eventName, arguments in
|
|
DispatchQueue.main.async {
|
|
if let eventSink = SwiftAzureSpeechRecognitionPlugin.eventStreamHandler?.eventSink {
|
|
var eventData = arguments
|
|
eventData["type"] = eventName
|
|
eventSink(eventData)
|
|
}
|
|
}
|
|
}
|
|
|
|
asrHelper = AzureAsrHelper(eventHandler: eventHandler)
|
|
ttsHelper = AzureTtsHelper(eventHandler: eventHandler)
|
|
|
|
super.init()
|
|
}
|
|
|
|
private func handleAsrMethod(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
|
|
|
|
let args = call.arguments as? Dictionary<String, Any>
|
|
|
|
switch call.method {
|
|
case "initialize":
|
|
// 仅在初始化时读取必要参数
|
|
guard let speechSubscriptionKey = args?["subscriptionKey"] as? String, !speechSubscriptionKey.isEmpty else {
|
|
let errorMsg = "语音订阅密钥不能为空"
|
|
print("[AzurePlugin] 错误: \(errorMsg)")
|
|
result(FlutterError(code: "INVALID_SUBSCRIPTION_KEY", message: errorMsg, details: nil))
|
|
return
|
|
}
|
|
|
|
guard let serviceRegion = args?["region"] as? String, !serviceRegion.isEmpty else {
|
|
let errorMsg = "服务区域不能为空"
|
|
print("[AzurePlugin] 错误: \(errorMsg)")
|
|
result(FlutterError(code: "INVALID_REGION", message: errorMsg, details: nil))
|
|
return
|
|
}
|
|
|
|
let supportedLanguages = args?["supportedLanguages"] as? [String] ?? []
|
|
|
|
let success = asrHelper.initialize(
|
|
speechSubscriptionKey: speechSubscriptionKey,
|
|
serviceRegion: serviceRegion,
|
|
supportedLanguages: supportedLanguages.isEmpty ? nil : supportedLanguages
|
|
)
|
|
result(success)
|
|
|
|
case "startContinuousRecognition":
|
|
// 只有使用参数时才验证
|
|
let success = asrHelper.startContinuousRecognition()
|
|
result(success)
|
|
|
|
case "stopContinuousRecognition":
|
|
// 不需要额外参数
|
|
let success = asrHelper.stopContinuousRecognition()
|
|
result(success)
|
|
|
|
case "recognizeOnce":
|
|
// 只有使用参数时才验证
|
|
let success = asrHelper.recognizeOnce()
|
|
result(success)
|
|
|
|
case "isContinuousRecognitionActive":
|
|
// 不需要额外参数
|
|
result(asrHelper.isContinuousRecognitionActive())
|
|
|
|
case "dispose":
|
|
// 不需要额外参数
|
|
print("[AzurePlugin] 释放ASR资源")
|
|
asrHelper.dispose()
|
|
result(true)
|
|
|
|
default:
|
|
print("[AzurePlugin] 错误: 未知ASR方法: \(call.method)")
|
|
result(FlutterMethodNotImplemented)
|
|
}
|
|
}
|
|
|
|
private func handleTtsMethod(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
|
|
|
|
let args = call.arguments as? Dictionary<String, Any>
|
|
|
|
switch call.method {
|
|
case "initialize":
|
|
// 仅在初始化时验证参数
|
|
guard let speechSubscriptionKey = args?["subscriptionKey"] as? String, !speechSubscriptionKey.isEmpty else {
|
|
let errorMsg = "语音订阅密钥不能为空"
|
|
print("[AzurePlugin] 错误: \(errorMsg)")
|
|
result(FlutterError(code: "INVALID_SUBSCRIPTION_KEY", message: errorMsg, details: nil))
|
|
return
|
|
}
|
|
|
|
guard let serviceRegion = args?["region"] as? String, !serviceRegion.isEmpty else {
|
|
let errorMsg = "服务区域不能为空"
|
|
print("[AzurePlugin] 错误: \(errorMsg)")
|
|
result(FlutterError(code: "INVALID_REGION", message: errorMsg, details: nil))
|
|
return
|
|
}
|
|
|
|
let language = args?["language"] as? String ?? "zh-CN"
|
|
|
|
print("[AzurePlugin] 初始化TTS,语言: \(language)")
|
|
|
|
let success = ttsHelper.initialize(speechSubscriptionKey: speechSubscriptionKey, serviceRegion: serviceRegion, language: language)
|
|
result(success)
|
|
|
|
case "setVoice":
|
|
// 仅获取voice参数
|
|
guard let voiceName = args?["voiceName"] as? String, !voiceName.isEmpty else {
|
|
let errorMsg = "声音名称不能为空"
|
|
print("[AzurePlugin] 错误: \(errorMsg)")
|
|
result(FlutterError(code: "INVALID_VOICE", message: errorMsg, details: nil))
|
|
return
|
|
}
|
|
|
|
print("[AzurePlugin] 设置声音: \(voiceName)")
|
|
|
|
let success = ttsHelper.setVoice(voiceName: voiceName)
|
|
result(success)
|
|
|
|
case "speakText":
|
|
// 仅获取text参数
|
|
let text = args?["text"] as? String ?? ""
|
|
|
|
if text.isEmpty {
|
|
print("[AzurePlugin] 警告: 要播放的文本为空")
|
|
result("OK")
|
|
return
|
|
}
|
|
|
|
print("[AzurePlugin] 播放文本: \(text.prefix(50))...")
|
|
|
|
let success = ttsHelper.speakText(text: text)
|
|
result(success ? "OK" : "ERROR")
|
|
|
|
case "speakSsml":
|
|
// 仅获取ssml参数
|
|
guard let ssml = args?["ssml"] as? String, !ssml.isEmpty else {
|
|
let errorMsg = "SSML内容不能为空"
|
|
print("[AzurePlugin] 错误: \(errorMsg)")
|
|
result(FlutterError(code: "INVALID_SSML", message: errorMsg, details: nil))
|
|
return
|
|
}
|
|
|
|
print("[AzurePlugin] 播放SSML: \(ssml.prefix(100))...")
|
|
|
|
// 由于我们移除了speakSsml方法,这里改用speakText方法
|
|
// Azure SDK内部会自动检测是普通文本还是SSML
|
|
let success = ttsHelper.speakText(text: ssml)
|
|
result(success)
|
|
|
|
case "stopSpeaking":
|
|
// 不需要参数
|
|
print("[AzurePlugin] 停止播放")
|
|
let success = ttsHelper.stopSpeaking()
|
|
result(success)
|
|
|
|
case "isSpeaking":
|
|
// 不需要参数
|
|
result(ttsHelper.isSpeaking())
|
|
|
|
case "setSpeechParams":
|
|
// 仅获取语音参数
|
|
let rate = args?["rate"] as? Int ?? 0
|
|
let pitch = args?["pitch"] as? Int ?? 0
|
|
let volume = args?["volume"] as? Int ?? 100
|
|
|
|
print("[AzurePlugin] 设置语音参数: rate=\(rate), pitch=\(pitch), volume=\(volume)")
|
|
let success = ttsHelper.setSpeechParams(rate: rate, pitch: pitch, volume: volume)
|
|
result(success)
|
|
|
|
case "dispose":
|
|
// 释放TTS资源
|
|
print("[AzurePlugin] 释放TTS资源")
|
|
ttsHelper.dispose()
|
|
result(true)
|
|
|
|
default:
|
|
print("[AzurePlugin] 错误: 未知TTS方法: \(call.method)")
|
|
result(FlutterMethodNotImplemented)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 用于处理事件流的辅助类
|
|
@available(iOS 13.0, *)
|
|
class AzureEventStreamHandler: NSObject, FlutterStreamHandler {
|
|
var eventSink: FlutterEventSink?
|
|
|
|
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
|
|
self.eventSink = events
|
|
// 通知Flutter端事件通道已准备好
|
|
DispatchQueue.main.async {
|
|
events(["type": "channelReady"])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func onCancel(withArguments arguments: Any?) -> FlutterError? {
|
|
self.eventSink = nil
|
|
return nil
|
|
}
|
|
}
|