import 'dart:async'; import 'dart:typed_data'; /// 语音识别服务接口 abstract class AstService { /// 支持的语言 List get supportedLanguages; /// 初始化语音识别服务 /// [supportedLanguages] 支持的语言列表 /// [provider] 提供商:azure / volcano / alibaba / iflytek Future initialize( {required List supportedLanguages, String provider = 'azure'}); /// 设置低音量阈值 void setLowVolumeThreshold(int threshold); /// 开始录音 /// /// 通话翻译模式专用:以立体声 WAV 形式同时记录 /// - 左声道:本端麦克风原音(来自 BLE 输入) /// - 右声道:对方语音的翻译音频(B 路 AST 合成的 PCM) Future enableRecord(String filePath); /// 暂停通话翻译录音 Future pauseRecord(); /// 继续通话翻译录音 Future resumeRecord(); /// 结束通话翻译录音 /// /// [isSave] 为 false 时丢弃当前文件 Future stopRecord(bool isSave); /// 开始连续翻译 Future startContinuousTranslation(); /// 停止连续翻译 Future stopContinuousTranslation(); /// 返回一个包含识别事件的流 Future> recognizeCallback(); /// 开始录音 Future path(String filePath); /// 通话翻译:是否本地播报对方(B)翻译后的音频。 /// true → 原生层把 B 路 E2E PCM 直接播出,同时启用播放期 gate 防回环 /// false → 原生层丢弃 B 路播放,gate 永不触发 /// 未实现的原生端应 no-op 并返回 true Future setBroadcastPeerTranslate(bool enabled); /// 把外部捕获的 PCM(杰理 runtime 解码后的 SCO 上下行)推给 AST 流水线。 /// [leg] 'A'/'uplink' = 本机麦克风;'B'/'downlink' = 对端通话音 /// [pcm] 16k mono 16bit PCM little-endian Future pushExternalAudio({required String leg, required Uint8List pcm}); /// AST 流水线 / E2E 服务合成的 TTS PCM 帧流。每帧包含 leg('A'|'B') + pcm。 /// 调用方负责把它转发给设备(如 [Jielihome.feedTranslatedAudio])。 Stream get astTtsFrames; /// 释放资源 Future dispose(); } /// AST 服务输出的 TTS PCM 帧(用于通话翻译双向回灌)。 class AstTtsFrame { /// 'A' = 己方→对端 译文(让对端听);'B' = 对端→己方 译文(让本机听) final String leg; final Uint8List pcm; /// 是否为当前 utterance 的最后一帧。 /// /// 仅当原生 AST 服务(azure / iflytek / volcano / alibaba 任一)确实发出段尾事件时为 true。 /// 段尾帧通常 pcm 为空字节,仅作信号传递,由下游(杰理 RCSP runtime)触发立即整段下发。 final bool isFinal; const AstTtsFrame({ required this.leg, required this.pcm, this.isFinal = false, }); } // 识别事件类型 enum ASTEventType { /// 最终识别结果 finalResult, /// 中间识别结果(实时反馈) intermediateResult, /// 翻译结果 translationResult, /// 翻译中(实时反馈) translationInterim, /// 会话开始 sessionStarted, /// 会话结束 sessionStopped, /// 识别取消 canceled, /// 识别错误 error, } // 识别事件 class ASTEvent { /// 事件类型 final ASTEventType type; /// 识别ID final String utteranceId; /// 服务ID(A/B) final String serviceId; /// 识别文本(仅在 finalResult 和 intermediateResult 类型中有效) final String text; /// 检测到的语言 final String detectedLanguage; /// 原始音频 final Uint8List? audio; /// 错误信息(仅在 error 和 canceled 类型中有效) final String error; ASTEvent({ required this.type, this.utteranceId = '', this.text = '', this.detectedLanguage = '', this.audio, this.error = '', this.serviceId = '', }); /// 创建最终结果事件的快捷构造函数 factory ASTEvent.finalResult({ required String text, String utteranceId = '', String detectedLanguage = '', String serviceId = '', }) { return ASTEvent( type: ASTEventType.finalResult, utteranceId: utteranceId, text: text, detectedLanguage: detectedLanguage, serviceId: serviceId, ); } /// 创建错误事件的快捷构造函数 factory ASTEvent.error(String errorMessage) { return ASTEvent( type: ASTEventType.error, error: errorMessage, ); } /// 检查是否为最终结果 bool get isFinalResult => type == ASTEventType.finalResult; /// 检查是否为错误 bool get isError => type == ASTEventType.error || type == ASTEventType.canceled; @override String toString() { return 'ASTEvent{type: $type, text: $text, detectedLanguage: $detectedLanguage, error: $error}'; } }