|
|
@ -93,6 +93,19 @@ import ble_service |
|
|
internal var astEventSink: FlutterEventSink? |
|
|
internal var astEventSink: FlutterEventSink? |
|
|
private let azureAstHelper = IntegratedSpeechTranslationService() |
|
|
private let azureAstHelper = IntegratedSpeechTranslationService() |
|
|
|
|
|
|
|
|
|
|
|
// 端到端翻译 Helpers(豆包 / 阿里百炼) |
|
|
|
|
|
private var doubaoAstHelperA = DoubaoE2ETranslateHelper() |
|
|
|
|
|
private var doubaoAstHelperB = DoubaoE2ETranslateHelper() |
|
|
|
|
|
private var bailianAstHelperA = AliyunBailianE2EHelper() |
|
|
|
|
|
private var bailianAstHelperB = AliyunBailianE2EHelper() |
|
|
|
|
|
private var currentAstProvider: String = "azure" |
|
|
|
|
|
private var astWsConfig: DoubaoE2ETranslateHelper.Config? |
|
|
|
|
|
internal var doubaoFinalSourceTextCache: [String: String] = [:] |
|
|
|
|
|
private var doubaoCallbackA: DoubaoCallbackProxy? |
|
|
|
|
|
private var doubaoCallbackB: DoubaoCallbackProxy? |
|
|
|
|
|
private var bailianCallbackA: AliyunCallbackProxy? |
|
|
|
|
|
private var bailianCallbackB: AliyunCallbackProxy? |
|
|
|
|
|
|
|
|
// 音频数据相关 |
|
|
// 音频数据相关 |
|
|
private var audioDataEventChannel: FlutterEventChannel? |
|
|
private var audioDataEventChannel: FlutterEventChannel? |
|
|
internal var audioDataEventSink: FlutterEventSink? |
|
|
internal var audioDataEventSink: FlutterEventSink? |
|
|
@ -747,112 +760,190 @@ private func sendAstEvent(_ event: [String: Any]) { |
|
|
// } |
|
|
// } |
|
|
|
|
|
|
|
|
case "startContinuousTranslation": |
|
|
case "startContinuousTranslation": |
|
|
do { |
|
|
if currentAstProvider == "volcano" { |
|
|
os_log("开启翻译", log: log, type: .info) |
|
|
_ = doubaoAstHelperA.startContinuousTranslation() |
|
|
try azureAstHelper.startContinuousTranslation() |
|
|
_ = doubaoAstHelperB.startContinuousTranslation() |
|
|
result(true) |
|
|
result(true) |
|
|
} catch { |
|
|
} else if currentAstProvider == "alibaba" { |
|
|
result(FlutterError(code: "START_CONTINUOUS_TRANSLATION_ERROR", message: error.localizedDescription, details: nil)) |
|
|
_ = bailianAstHelperA.startContinuousConversation() |
|
|
|
|
|
_ = bailianAstHelperB.startContinuousConversation() |
|
|
|
|
|
result(true) |
|
|
|
|
|
} else { |
|
|
|
|
|
do { |
|
|
|
|
|
os_log("开启翻译", log: log, type: .info) |
|
|
|
|
|
try azureAstHelper.startContinuousTranslation() |
|
|
|
|
|
result(true) |
|
|
|
|
|
} catch { |
|
|
|
|
|
result(FlutterError(code: "START_CONTINUOUS_TRANSLATION_ERROR", message: error.localizedDescription, details: nil)) |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
case "stopContinuousTranslation": |
|
|
case "stopContinuousTranslation": |
|
|
do { |
|
|
if currentAstProvider == "volcano" { |
|
|
os_log("停止翻译", log: log, type: .info) |
|
|
_ = doubaoAstHelperA.stopContinuousTranslation() |
|
|
try azureAstHelper.stopContinuousTranslation() |
|
|
_ = doubaoAstHelperB.stopContinuousTranslation() |
|
|
result(true) |
|
|
result(true) |
|
|
} catch { |
|
|
} else if currentAstProvider == "alibaba" { |
|
|
result(FlutterError(code: "STOP_CONTINUOUS_TRANSLATION_ERROR", message: error.localizedDescription, details: nil)) |
|
|
_ = bailianAstHelperA.stopContinuousConversation() |
|
|
} |
|
|
_ = bailianAstHelperB.stopContinuousConversation() |
|
|
|
|
|
|
|
|
case "dispose": |
|
|
|
|
|
do { |
|
|
|
|
|
os_log("释放AST资源", log: log, type: .info) |
|
|
|
|
|
azureAstHelper.dispose() |
|
|
|
|
|
result(true) |
|
|
result(true) |
|
|
} catch { |
|
|
} else { |
|
|
result(FlutterError(code: "DISPOSE_ERROR", message: error.localizedDescription, details: nil)) |
|
|
do { |
|
|
|
|
|
os_log("停止翻译", log: log, type: .info) |
|
|
|
|
|
try azureAstHelper.stopContinuousTranslation() |
|
|
|
|
|
result(true) |
|
|
|
|
|
} catch { |
|
|
|
|
|
result(FlutterError(code: "STOP_CONTINUOUS_TRANSLATION_ERROR", message: error.localizedDescription, details: nil)) |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
case "dispose": |
|
|
|
|
|
os_log("释放AST资源", log: log, type: .info) |
|
|
|
|
|
azureAstHelper.dispose() |
|
|
|
|
|
doubaoAstHelperA.dispose() |
|
|
|
|
|
doubaoAstHelperB.dispose() |
|
|
|
|
|
bailianAstHelperA.dispose() |
|
|
|
|
|
bailianAstHelperB.dispose() |
|
|
|
|
|
result(true) |
|
|
|
|
|
|
|
|
case "recognizeCallback": |
|
|
case "recognizeCallback": |
|
|
os_log("recognizeCallback:", log: log, type: .info) |
|
|
os_log("recognizeCallback:", log: log, type: .info) |
|
|
result(true) |
|
|
result(true) |
|
|
|
|
|
|
|
|
case "path": |
|
|
case "path": |
|
|
guard let args = call.arguments as? [String: Any], |
|
|
guard let args = call.arguments as? [String: Any], |
|
|
let filePath = args["filePath"] as? String else { |
|
|
let filePath = args["filePath"] as? String else { |
|
|
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing filePath", details: nil)) |
|
|
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing filePath", details: nil)) |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let fileURL = URL(fileURLWithPath: filePath) |
|
|
let fileURL = URL(fileURLWithPath: filePath) |
|
|
|
|
|
result(true) |
|
|
// if FileManager.default.fileExists(atPath: filePath) { |
|
|
|
|
|
// // 启动异步任务来按播放速度读取音频文件 |
|
|
|
|
|
// Task { |
|
|
|
|
|
// // await streamAudioFile(fileURL: fileURL) |
|
|
|
|
|
// } |
|
|
|
|
|
// result(true) |
|
|
|
|
|
// } else { |
|
|
|
|
|
// result(FlutterError(code: "FILE_NOT_FOUND", message: "音频文件不存在: \(filePath)", details: nil)) |
|
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
|
|
case "initialize": |
|
|
case "initialize": |
|
|
guard let args = call.arguments as? [String: Any] else { |
|
|
guard let args = call.arguments as? [String: Any] else { |
|
|
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing arguments", details: nil)) |
|
|
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing arguments", details: nil)) |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
let provider = (args["provider"] as? String ?? "azure").lowercased() |
|
|
let subscriptionKey = args["subscriptionKey"] as? String ?? "" |
|
|
let supported = (args["supportedLanguages"] as? [String]) ?? ["zh-CN"] |
|
|
let region = args["region"] as? String ?? "" |
|
|
let lang0 = supported.count > 0 ? supported[0] : "zh-CN" |
|
|
let supportedLanguages = args["supportedLanguages"] as? [String] ?? ["zh-CN"] |
|
|
let lang1 = supported.count > 1 ? supported[1] : "en-US" |
|
|
let useExternalAudio = args["useExternalAudio"] as? Bool ?? false |
|
|
let translationLang0 = supported.count > 2 ? supported[2] : "zh-Hans" |
|
|
|
|
|
let translationLang1 = supported.count > 3 ? supported[3] : "en-US" |
|
|
// 获取翻译服务配置参数 |
|
|
let ttsLang0 = supported.count > 4 ? supported[4] : "zh-CN-XiaoxiaoNeural" |
|
|
let translationAccessKey = args["translationAccessKey"] as? String ?? "" |
|
|
let ttsLang1 = supported.count > 5 ? supported[5] : "en-US-AriaNeural" |
|
|
let translationSecretKey = args["translationSecretKey"] as? String ?? "" |
|
|
currentAstProvider = provider |
|
|
let translationRegion = args["translationRegion"] as? String ?? "cn-north-1" |
|
|
|
|
|
|
|
|
let subscriptionKeyI = (args["subscriptionKey"] as? String) ?? "" |
|
|
os_log("初始化AST服务", log: log, type: .info) |
|
|
let regionI = (args["region"] as? String) ?? "" |
|
|
|
|
|
let volcanoTranslationAccessKey = (args["volcanoTranslationAccessKey"] as? String) ?? "" |
|
|
// 创建Azure配置 |
|
|
let volcanoTranslationSecretKey = (args["volcanoTranslationSecretKey"] as? String) ?? "" |
|
|
let azureConfig = AzureConfiguration( |
|
|
let volcanoTranslationRegion = (args["volcanoTranslationRegion"] as? String) ?? "cn-north-1" |
|
|
subscriptionKey: subscriptionKey, |
|
|
let azureTranslationKey = (args["azureTranslationKey"] as? String) ?? "" |
|
|
region: region |
|
|
let azureTranslationRegion = (args["azureTranslationServiceRegion"] as? String) ?? "" |
|
|
) |
|
|
// 豆包 AST WS 配置 |
|
|
|
|
|
let wsUrl = (args["wsUrl"] as? String) ?? "wss://openspeech.bytedance.com/api/v4/ast/v2/translate" |
|
|
// 创建翻译配置 |
|
|
let appKey = (args["appKey"] as? String) ?? "" |
|
|
let translationConfig = TranslationConfiguration( |
|
|
let accessKeyD = (args["accessKey"] as? String) ?? "" |
|
|
accessKey: translationAccessKey, |
|
|
let resourceId = (args["resourceId"] as? String) ?? "volc.service_type.10053" |
|
|
secretKey: translationSecretKey, |
|
|
// 阿里百炼配置 |
|
|
region: translationRegion |
|
|
let alibabaAppKey = (args["alibabaAppKey"] as? String) ?? "" |
|
|
) |
|
|
let alibabaAppId = (args["alibabaAppId"] as? String) ?? "" |
|
|
|
|
|
let alibabaAppURL = (args["alibabaAppURL"] as? String) ?? "" |
|
|
// 创建服务配置 |
|
|
|
|
|
let serviceConfig = IntegratedSpeechTranslationService.ServiceConfiguration( |
|
|
os_log("initialize provider=%@ lang0=%@ lang1=%@ trans0=%@ trans1=%@", log: log, type: .info, provider, lang0, lang1, translationLang0, translationLang1) |
|
|
sourceLanguage: supportedLanguages.first ?? "zh-CN", |
|
|
|
|
|
targetLanguage: supportedLanguages.count > 1 ? supportedLanguages[1] : "en-US" |
|
|
if currentAstProvider == "azure" { |
|
|
) |
|
|
let svcA = IntegratedSpeechTranslationService.ServiceConfiguration( |
|
|
|
|
|
sourceLanguage: lang0, |
|
|
// 创建事件回调 |
|
|
targetLanguage: lang1, |
|
|
let callback = AstEventCallback(plugin: self) |
|
|
currentVoice: ttsLang1, |
|
|
astEventCallback = callback |
|
|
translationSourceLanguage: translationLang0, |
|
|
|
|
|
translationTargetLanguage: translationLang1 |
|
|
// 使用异步任务调用初始化方法 |
|
|
) |
|
|
Task { |
|
|
let svcB = IntegratedSpeechTranslationService.ServiceConfiguration( |
|
|
await azureAstHelper.initialize( |
|
|
sourceLanguage: lang1, |
|
|
azureConfig: azureConfig, |
|
|
targetLanguage: lang0, |
|
|
translationConfig: translationConfig, |
|
|
currentVoice: ttsLang0, |
|
|
serviceConfig: serviceConfig, |
|
|
translationSourceLanguage: translationLang1, |
|
|
callback: callback |
|
|
translationTargetLanguage: translationLang0 |
|
|
) |
|
|
) |
|
|
|
|
|
let callbackA = AstEventCallback(plugin: self) |
|
|
|
|
|
let callbackB = AstEventCallback(plugin: self) |
|
|
|
|
|
astEventCallback = callbackA |
|
|
|
|
|
|
|
|
|
|
|
let azureConfigI = AzureConfiguration(subscriptionKey: subscriptionKeyI, region: regionI) |
|
|
|
|
|
let translationConfigI = TranslationConfiguration(region: azureTranslationRegion, subscriptionKey: azureTranslationKey, location: azureTranslationRegion) |
|
|
|
|
|
|
|
|
|
|
|
Task { |
|
|
|
|
|
await azureAstHelper.initialize(azureConfig: azureConfigI, translationConfig: translationConfigI, serviceConfig: svcA, callback: callbackA) |
|
|
|
|
|
} |
|
|
|
|
|
} else if currentAstProvider == "volcano" { |
|
|
|
|
|
// 初始化豆包 AST(双路) |
|
|
|
|
|
let cfg = DoubaoE2ETranslateHelper.Config(wsUrl: wsUrl, appKey: appKey, accessKey: accessKeyD, resourceId: resourceId, sourceLanguage: translationLang0, targetLanguage: translationLang1, clientWaitMs: 60_000) |
|
|
|
|
|
astWsConfig = cfg |
|
|
|
|
|
let cbA = DoubaoCallbackProxy(plugin: self, serviceId: "A", direction: "\(translationLang0)->\(translationLang1)", targetLanguage: translationLang1) |
|
|
|
|
|
let cbB = DoubaoCallbackProxy(plugin: self, serviceId: "B", direction: "\(translationLang1)->\(translationLang0)", targetLanguage: translationLang0) |
|
|
|
|
|
doubaoCallbackA = cbA |
|
|
|
|
|
doubaoCallbackB = cbB |
|
|
|
|
|
var cfgA = cfg |
|
|
|
|
|
cfgA.sourceLanguage = translationLang0 |
|
|
|
|
|
cfgA.targetLanguage = translationLang1 |
|
|
|
|
|
var cfgB = cfg |
|
|
|
|
|
cfgB.sourceLanguage = translationLang1 |
|
|
|
|
|
cfgB.targetLanguage = translationLang0 |
|
|
|
|
|
|
|
|
|
|
|
Task { |
|
|
|
|
|
_ = await doubaoAstHelperA.initialize(config: cfgA, cb: cbA) |
|
|
|
|
|
try? await Task.sleep(nanoseconds: 500_000_000) |
|
|
|
|
|
_ = await doubaoAstHelperB.initialize(config: cfgB, cb: cbB) |
|
|
|
|
|
} |
|
|
|
|
|
} else if currentAstProvider == "alibaba" { |
|
|
|
|
|
// 初始化阿里百炼(双路) |
|
|
|
|
|
var cfgA = AliyunBailianE2EHelper.Config() |
|
|
|
|
|
cfgA.wsUrl = alibabaAppURL |
|
|
|
|
|
cfgA.apiKey = alibabaAppKey |
|
|
|
|
|
cfgA.appId = "qwen3-livetranslate-flash-realtime" |
|
|
|
|
|
cfgA.sampleRate = 16000 |
|
|
|
|
|
cfgA.sourceLanguage = translationLang0 |
|
|
|
|
|
cfgA.targetLanguage = translationLang1 |
|
|
|
|
|
cfgA.voice = ttsLang1 |
|
|
|
|
|
|
|
|
|
|
|
var cfgB = AliyunBailianE2EHelper.Config() |
|
|
|
|
|
cfgB.wsUrl = alibabaAppURL |
|
|
|
|
|
cfgB.apiKey = alibabaAppKey |
|
|
|
|
|
cfgB.appId = "qwen3-livetranslate-flash-realtime" |
|
|
|
|
|
cfgB.sampleRate = 16000 |
|
|
|
|
|
cfgB.sourceLanguage = translationLang1 |
|
|
|
|
|
cfgB.targetLanguage = translationLang0 |
|
|
|
|
|
cfgB.voice = ttsLang0 |
|
|
|
|
|
|
|
|
|
|
|
let cbA = AliyunCallbackProxy(plugin: self, serviceId: "A", direction: "\(translationLang0)->\(translationLang1)", targetLanguage: translationLang1) |
|
|
|
|
|
let cbB = AliyunCallbackProxy(plugin: self, serviceId: "B", direction: "\(translationLang1)->\(translationLang0)", targetLanguage: translationLang0) |
|
|
|
|
|
bailianCallbackA = cbA |
|
|
|
|
|
bailianCallbackB = cbB |
|
|
|
|
|
|
|
|
|
|
|
Task { |
|
|
|
|
|
_ = await bailianAstHelperA.initialize(config: cfgA, cb: cbA) |
|
|
|
|
|
_ = await bailianAstHelperB.initialize(config: cfgB, cb: cbB) |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
result(true) |
|
|
result(true) |
|
|
|
|
|
|
|
|
default: |
|
|
default: |
|
|
result(FlutterMethodNotImplemented) |
|
|
result(FlutterMethodNotImplemented) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 发送AST事件到Flutter层 |
|
|
|
|
|
internal func sendAstEvent(_ event: [String: Any]) { |
|
|
|
|
|
if astEventSink == nil { return } |
|
|
|
|
|
DispatchQueue.main.async { [weak self] in |
|
|
|
|
|
self?.astEventSink?(event) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
// MARK: - AST Event Callback |
|
|
// MARK: - AST Event Callback |
|
|
/** |
|
|
/** |
|
|
@ -1141,13 +1232,14 @@ extension AzureSpeechPlugin: BleService.Callback { |
|
|
azureAsrHelper.audioStream?.saveAudioDataTo(data: rightBuffer) |
|
|
azureAsrHelper.audioStream?.saveAudioDataTo(data: rightBuffer) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// // 安全解包版本 |
|
|
// 根据当前 AST 提供商路由音频数据 |
|
|
// guard let audioProcessor = azureAstHelper.audioProcessor else { |
|
|
if currentAstProvider == "volcano" { |
|
|
// os_log("音频流未初始化", type: .error) |
|
|
doubaoAstHelperA.pushAudioData(leftBuffer) |
|
|
// return |
|
|
} else if currentAstProvider == "alibaba" { |
|
|
// } |
|
|
bailianAstHelperA.pushAudioData(leftBuffer) |
|
|
// print("liwei--------------接收到音频数据回调1") |
|
|
} else { |
|
|
azureAstHelper.pushAudioData(audioData: leftBuffer) |
|
|
azureAstHelper.pushAudioData(audioData: leftBuffer) |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -1233,6 +1325,203 @@ private class BaseEventStreamHandler: NSObject, FlutterStreamHandler { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - Doubao AST 回调桥接 |
|
|
|
|
|
private class DoubaoCallbackProxy: DoubaoE2ETranslateHelper.Callback { |
|
|
|
|
|
private weak var plugin: AzureSpeechPlugin? |
|
|
|
|
|
private let serviceId: String |
|
|
|
|
|
private let direction: String |
|
|
|
|
|
private let targetLanguage: String |
|
|
|
|
|
|
|
|
|
|
|
init(plugin: AzureSpeechPlugin, serviceId: String, direction: String, targetLanguage: String) { |
|
|
|
|
|
self.plugin = plugin |
|
|
|
|
|
self.serviceId = serviceId |
|
|
|
|
|
self.direction = direction |
|
|
|
|
|
self.targetLanguage = targetLanguage |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onSessionStarted(sessionId: String) { |
|
|
|
|
|
plugin?.sendAstEvent([ |
|
|
|
|
|
"type": "serviceInitialized", |
|
|
|
|
|
"serviceId": serviceId, |
|
|
|
|
|
"direction": direction |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onPartialSourceText(sessionId: String, text: String) { |
|
|
|
|
|
plugin?.sendAstEvent([ |
|
|
|
|
|
"type": "recognizing", |
|
|
|
|
|
"serviceId": serviceId, |
|
|
|
|
|
"text": text, |
|
|
|
|
|
"language": direction, |
|
|
|
|
|
"utteranceId": sessionId |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onFinalSourceText(sessionId: String, finalText: String) { |
|
|
|
|
|
if let p = plugin { p.doubaoFinalSourceTextCache["\(serviceId):\(sessionId)"] = finalText } |
|
|
|
|
|
plugin?.sendAstEvent([ |
|
|
|
|
|
"type": "recognized", |
|
|
|
|
|
"serviceId": serviceId, |
|
|
|
|
|
"text": finalText, |
|
|
|
|
|
"language": direction, |
|
|
|
|
|
"utteranceId": sessionId |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onPartialText(sessionId: String, text: String) { |
|
|
|
|
|
plugin?.sendAstEvent([ |
|
|
|
|
|
"type": "translatedInterim", |
|
|
|
|
|
"serviceId": serviceId, |
|
|
|
|
|
"translatedText": text, |
|
|
|
|
|
"targetLanguage": targetLanguage, |
|
|
|
|
|
"utteranceId": sessionId |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onPartialAudio(sessionId: String, data: Data) { |
|
|
|
|
|
let isRightChannel = (serviceId == "A") |
|
|
|
|
|
if data.count > 0 { |
|
|
|
|
|
if isRightChannel { |
|
|
|
|
|
BleService.shared.writeExternalRightAudioData(data) |
|
|
|
|
|
} else { |
|
|
|
|
|
BleService.shared.writeExternalLeftAudioData(data) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onSessionFinished(sessionId: String, finalText: String, finalAudio: Data) { |
|
|
|
|
|
let key = "\(serviceId):\(sessionId)" |
|
|
|
|
|
let original = plugin?.doubaoFinalSourceTextCache.removeValue(forKey: key) ?? "" |
|
|
|
|
|
plugin?.sendAstEvent([ |
|
|
|
|
|
"type": "translated", |
|
|
|
|
|
"serviceId": serviceId, |
|
|
|
|
|
"translatedText": finalText, |
|
|
|
|
|
"targetLanguage": targetLanguage, |
|
|
|
|
|
"utteranceId": sessionId, |
|
|
|
|
|
"originalText": original |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onSessionError(sessionId: String, code: Int, message: String) { |
|
|
|
|
|
plugin?.sendAstEvent([ |
|
|
|
|
|
"type": "error", |
|
|
|
|
|
"serviceId": serviceId, |
|
|
|
|
|
"direction": direction, |
|
|
|
|
|
"component": "doubaoAst", |
|
|
|
|
|
"error": message, |
|
|
|
|
|
"code": code |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onFinalTranslatedText(sessionId: String, finalText: String) { |
|
|
|
|
|
let key = "\(serviceId):\(sessionId)" |
|
|
|
|
|
let original = plugin?.doubaoFinalSourceTextCache.removeValue(forKey: key) ?? "" |
|
|
|
|
|
plugin?.sendAstEvent([ |
|
|
|
|
|
"type": "translated", |
|
|
|
|
|
"serviceId": serviceId, |
|
|
|
|
|
"translatedText": finalText, |
|
|
|
|
|
"targetLanguage": targetLanguage, |
|
|
|
|
|
"utteranceId": sessionId, |
|
|
|
|
|
"originalText": original |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// MARK: - Aliyun Bailian AST 回调桥接 |
|
|
|
|
|
private class AliyunCallbackProxy: AliyunBailianE2EHelper.Callback { |
|
|
|
|
|
private weak var plugin: AzureSpeechPlugin? |
|
|
|
|
|
private let serviceId: String |
|
|
|
|
|
private let direction: String |
|
|
|
|
|
private let targetLanguage: String |
|
|
|
|
|
|
|
|
|
|
|
init(plugin: AzureSpeechPlugin, serviceId: String, direction: String, targetLanguage: String) { |
|
|
|
|
|
self.plugin = plugin |
|
|
|
|
|
self.serviceId = serviceId |
|
|
|
|
|
self.direction = direction |
|
|
|
|
|
self.targetLanguage = targetLanguage |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onSessionStarted(sessionId: String) { |
|
|
|
|
|
plugin?.sendAstEvent([ |
|
|
|
|
|
"type": "serviceInitialized", |
|
|
|
|
|
"serviceId": serviceId, |
|
|
|
|
|
"direction": direction |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onPartialSourceText(sessionId: String, text: String) { |
|
|
|
|
|
plugin?.sendAstEvent([ |
|
|
|
|
|
"type": "recognizing", |
|
|
|
|
|
"serviceId": serviceId, |
|
|
|
|
|
"text": text, |
|
|
|
|
|
"language": direction, |
|
|
|
|
|
"utteranceId": sessionId |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onFinalSourceText(sessionId: String, finalText: String) { |
|
|
|
|
|
plugin?.sendAstEvent([ |
|
|
|
|
|
"type": "recognized", |
|
|
|
|
|
"serviceId": serviceId, |
|
|
|
|
|
"text": finalText, |
|
|
|
|
|
"language": direction, |
|
|
|
|
|
"utteranceId": sessionId |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onPartialText(sessionId: String, text: String) { |
|
|
|
|
|
plugin?.sendAstEvent([ |
|
|
|
|
|
"type": "translatedInterim", |
|
|
|
|
|
"serviceId": serviceId, |
|
|
|
|
|
"translatedText": text, |
|
|
|
|
|
"targetLanguage": targetLanguage, |
|
|
|
|
|
"utteranceId": sessionId |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onPartialAudio(sessionId: String, data: Data) { |
|
|
|
|
|
let isRightChannel = (serviceId == "A") |
|
|
|
|
|
if data.count > 0 { |
|
|
|
|
|
if isRightChannel { |
|
|
|
|
|
BleService.shared.writeExternalRightAudioData(data) |
|
|
|
|
|
} else { |
|
|
|
|
|
BleService.shared.writeExternalLeftAudioData(data) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onSessionFinished(sessionId: String, finalText: String, finalAudio: Data) { |
|
|
|
|
|
plugin?.sendAstEvent([ |
|
|
|
|
|
"type": "translated", |
|
|
|
|
|
"serviceId": serviceId, |
|
|
|
|
|
"translatedText": finalText, |
|
|
|
|
|
"targetLanguage": targetLanguage, |
|
|
|
|
|
"utteranceId": sessionId |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onSessionError(sessionId: String, code: Int, message: String) { |
|
|
|
|
|
plugin?.sendAstEvent([ |
|
|
|
|
|
"type": "error", |
|
|
|
|
|
"serviceId": serviceId, |
|
|
|
|
|
"direction": direction, |
|
|
|
|
|
"component": "aliyunAst", |
|
|
|
|
|
"error": message, |
|
|
|
|
|
"code": code |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func onFinalTranslatedText(sessionId: String, finalText: String) { |
|
|
|
|
|
plugin?.sendAstEvent([ |
|
|
|
|
|
"type": "translated", |
|
|
|
|
|
"serviceId": serviceId, |
|
|
|
|
|
"translatedText": finalText, |
|
|
|
|
|
"targetLanguage": targetLanguage, |
|
|
|
|
|
"utteranceId": sessionId |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// MARK: - TTS 事件监听实现 |
|
|
// MARK: - TTS 事件监听实现 |
|
|
extension AzureSpeechPlugin: TtsEventListener { |
|
|
extension AzureSpeechPlugin: TtsEventListener { |
|
|
// 使用@objc特性为方法提供一个不同的Objective-C选择器名称 |
|
|
// 使用@objc特性为方法提供一个不同的Objective-C选择器名称 |
|
|
|