|
|
|
@ -15,384 +15,499 @@ import com.microsoft.cognitiveservices.speech.audio.* |
|
|
|
import com.deep_voice.speech.tts.TtsEvent |
|
|
|
import com.deep_voice.speech.tts.TtsEventListener |
|
|
|
import com.deep_voice.speech.tts.TtsEventType |
|
|
|
import com.yunqiinnovation.ble_service.BleService |
|
|
|
|
|
|
|
/** AzureSpeechPlugin */ |
|
|
|
class AzureSpeechPlugin: FlutterPlugin { |
|
|
|
private val tag = "AzureSpeechPlugin" |
|
|
|
private lateinit var context: Context |
|
|
|
private val mainHandler = Handler(Looper.getMainLooper()) |
|
|
|
|
|
|
|
// ASR相关 |
|
|
|
private lateinit var asrChannel : MethodChannel |
|
|
|
private lateinit var asrEventChannel: EventChannel |
|
|
|
private var asrEventSink: EventChannel.EventSink? = null |
|
|
|
private lateinit var azureAsrHelper: AzureAsrHelper |
|
|
|
|
|
|
|
// TTS相关 |
|
|
|
private lateinit var ttsChannel : MethodChannel |
|
|
|
private lateinit var ttsEventChannel: EventChannel |
|
|
|
private var ttsEventSink: EventChannel.EventSink? = null |
|
|
|
private lateinit var azureTtsHelper: AzureTtsHelper |
|
|
|
// 是否已添加TTS事件监听器 |
|
|
|
private var isTtsListenerAdded = false |
|
|
|
|
|
|
|
// ASR 事件发送方法 |
|
|
|
private fun sendAsrEvent(event: Map<String, Any>) { |
|
|
|
if (asrEventSink == null) { |
|
|
|
FileLogger.w(tag, "无法发送ASR事件:事件通道未准备好") |
|
|
|
return |
|
|
|
} |
|
|
|
class AzureSpeechPlugin : BleService.Callback, FlutterPlugin { |
|
|
|
private val tag = "AzureSpeechPlugin" |
|
|
|
private lateinit var context: Context |
|
|
|
private val mainHandler = Handler(Looper.getMainLooper()) |
|
|
|
|
|
|
|
// ASR相关 |
|
|
|
private lateinit var asrChannel: MethodChannel |
|
|
|
private lateinit var asrEventChannel: EventChannel |
|
|
|
private var asrEventSink: EventChannel.EventSink? = null |
|
|
|
private lateinit var azureAsrHelper: AzureAsrHelper |
|
|
|
|
|
|
|
// TTS相关 |
|
|
|
private lateinit var ttsChannel: MethodChannel |
|
|
|
private lateinit var ttsEventChannel: EventChannel |
|
|
|
private var ttsEventSink: EventChannel.EventSink? = null |
|
|
|
private lateinit var azureTtsHelper: AzureTtsHelper |
|
|
|
|
|
|
|
// 是否已添加TTS事件监听器 |
|
|
|
private var isTtsListenerAdded = false |
|
|
|
|
|
|
|
// 是否已注册BLE回调 |
|
|
|
private var isRegisteredToBle = false |
|
|
|
|
|
|
|
// ASR 事件发送方法 |
|
|
|
private fun sendAsrEvent(event: Map<String, Any>) { |
|
|
|
if (asrEventSink == null) { |
|
|
|
FileLogger.w(tag, "无法发送ASR事件:事件通道未准备好") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
mainHandler.post { |
|
|
|
try { |
|
|
|
asrEventSink?.success(event) |
|
|
|
} catch (e: Exception) { |
|
|
|
FileLogger.e(tag, "发送ASR事件失败: ${e.message}") |
|
|
|
} |
|
|
|
mainHandler.post { |
|
|
|
try { |
|
|
|
asrEventSink?.success(event) |
|
|
|
} catch (e: Exception) { |
|
|
|
FileLogger.e(tag, "发送ASR事件失败: ${e.message}") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// TTS 事件发送方法 |
|
|
|
private fun sendTtsEvent(event: Map<String, Any>) { |
|
|
|
if (ttsEventSink == null) { |
|
|
|
FileLogger.w(tag, "无法发送TTS事件:事件通道未准备好") |
|
|
|
return |
|
|
|
} |
|
|
|
// TTS 事件发送方法 |
|
|
|
private fun sendTtsEvent(event: Map<String, Any>) { |
|
|
|
if (ttsEventSink == null) { |
|
|
|
FileLogger.w(tag, "无法发送TTS事件:事件通道未准备好") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 确保在主线程上发送事件 |
|
|
|
mainHandler.post { |
|
|
|
try { |
|
|
|
ttsEventSink?.success(event) |
|
|
|
} catch (e: Exception) { |
|
|
|
FileLogger.e(tag, "发送TTS事件失败: ${e.message}") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { |
|
|
|
context = flutterPluginBinding.applicationContext |
|
|
|
|
|
|
|
// 初始化ASR通道 |
|
|
|
asrChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "azure_speech/asr") |
|
|
|
asrChannel.setMethodCallHandler(AsrMethodHandler()) |
|
|
|
|
|
|
|
// 初始化TTS通道 |
|
|
|
ttsChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "azure_speech/tts") |
|
|
|
ttsChannel.setMethodCallHandler(TtsMethodHandler()) |
|
|
|
|
|
|
|
// 初始化ASR事件通道 |
|
|
|
asrEventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "azure_speech/asr_events") |
|
|
|
asrEventChannel.setStreamHandler(object : EventChannel.StreamHandler { |
|
|
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { |
|
|
|
asrEventSink = events |
|
|
|
} |
|
|
|
|
|
|
|
override fun onCancel(arguments: Any?) { |
|
|
|
asrEventSink = null |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
// 初始化TTS事件通道 |
|
|
|
ttsEventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "azure_speech/tts_events") |
|
|
|
ttsEventChannel.setStreamHandler(object : EventChannel.StreamHandler { |
|
|
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { |
|
|
|
ttsEventSink = events |
|
|
|
setupTtsEventListener() // 在事件通道准备好时设置TTS事件监听器 |
|
|
|
} |
|
|
|
|
|
|
|
override fun onCancel(arguments: Any?) { |
|
|
|
ttsEventSink = null |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
// 初始化Azure语音服务 |
|
|
|
azureTtsHelper = AzureTtsHelper(context) |
|
|
|
azureAsrHelper = AzureAsrHelper(context) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 设置TTS事件监听器 |
|
|
|
*/ |
|
|
|
private fun setupTtsEventListener() { |
|
|
|
if (!isTtsListenerAdded) { |
|
|
|
// 添加TTS事件监听器 |
|
|
|
azureTtsHelper.addListener(object : TtsEventListener { |
|
|
|
override fun onEvent(event: TtsEvent) { |
|
|
|
// 将TTS事件转换为Flutter可理解的格式 |
|
|
|
val eventMap = when (event.type) { |
|
|
|
TtsEventType.SYNTHESIS_STARTED -> mapOf( |
|
|
|
"type" to "synthesis_started" |
|
|
|
) |
|
|
|
TtsEventType.SYNTHESIS_COMPLETED -> mapOf( |
|
|
|
"type" to "synthesis_completed" |
|
|
|
) |
|
|
|
TtsEventType.SYNTHESIS_CANCELED -> { |
|
|
|
val params = event.params |
|
|
|
mapOf( |
|
|
|
"type" to "synthesis_canceled", |
|
|
|
"reason" to (params["reason"] ?: ""), |
|
|
|
"errorDetails" to (params["errorDetails"] ?: "") |
|
|
|
) |
|
|
|
// 确保在主线程上发送事件 |
|
|
|
mainHandler.post { |
|
|
|
try { |
|
|
|
ttsEventSink?.success(event) |
|
|
|
} catch (e: Exception) { |
|
|
|
FileLogger.e(tag, "发送TTS事件失败: ${e.message}") |
|
|
|
} |
|
|
|
TtsEventType.ERROR -> { |
|
|
|
val params = event.params |
|
|
|
mapOf( |
|
|
|
"type" to "error", |
|
|
|
"errorCode" to (params["errorCode"] ?: "UNKNOWN_ERROR"), |
|
|
|
"errorMessage" to (params["errorMessage"] ?: "未知错误") |
|
|
|
) |
|
|
|
} |
|
|
|
else -> mapOf( |
|
|
|
"type" to event.type.toString().lowercase(), |
|
|
|
"params" to event.params |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
// 发送事件 |
|
|
|
sendTtsEvent(eventMap) |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
isTtsListenerAdded = true |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// ASR方法处理器 |
|
|
|
inner class AsrMethodHandler : MethodCallHandler { |
|
|
|
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { |
|
|
|
when (call.method) { |
|
|
|
"initialize" -> { |
|
|
|
val subscriptionKey = call.argument<String>("subscriptionKey") ?: "" |
|
|
|
val region = call.argument<String>("region") ?: "" |
|
|
|
val supportedLanguages = call.argument<List<String>>("supportedLanguages") ?: listOf("zh-CN") |
|
|
|
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false |
|
|
|
|
|
|
|
try { |
|
|
|
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") // |
|
|
|
// 选择音频源类型 |
|
|
|
val audioSourceType = if (useExternalAudio) { |
|
|
|
AzureAsrHelper.AudioSourceType.EXTERNAL |
|
|
|
} else { |
|
|
|
AzureAsrHelper.AudioSourceType.MICROPHONE |
|
|
|
|
|
|
|
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { |
|
|
|
context = flutterPluginBinding.applicationContext |
|
|
|
|
|
|
|
// 初始化ASR通道 |
|
|
|
asrChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "azure_speech/asr") |
|
|
|
asrChannel.setMethodCallHandler(AsrMethodHandler()) |
|
|
|
|
|
|
|
// 初始化TTS通道 |
|
|
|
ttsChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "azure_speech/tts") |
|
|
|
ttsChannel.setMethodCallHandler(TtsMethodHandler()) |
|
|
|
|
|
|
|
// 初始化ASR事件通道 |
|
|
|
asrEventChannel = |
|
|
|
EventChannel(flutterPluginBinding.binaryMessenger, "azure_speech/asr_events") |
|
|
|
asrEventChannel.setStreamHandler(object : EventChannel.StreamHandler { |
|
|
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { |
|
|
|
asrEventSink = events |
|
|
|
} |
|
|
|
|
|
|
|
val success = azureAsrHelper.initialize( |
|
|
|
subscriptionKey = subscriptionKey, |
|
|
|
region = region, |
|
|
|
supportedLanguages = supportedLanguages.toTypedArray(), |
|
|
|
audioSourceType = audioSourceType, |
|
|
|
) |
|
|
|
|
|
|
|
result.success(success) |
|
|
|
} catch (e: Exception) { |
|
|
|
result.error("INITIALIZATION_ERROR", e.message, null) |
|
|
|
} |
|
|
|
} |
|
|
|
"recognizeOnce" -> { |
|
|
|
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false |
|
|
|
try { |
|
|
|
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") // |
|
|
|
// 选择音频源类型 |
|
|
|
val audioSourceType = if (useExternalAudio) { |
|
|
|
AzureAsrHelper.AudioSourceType.EXTERNAL |
|
|
|
} else { |
|
|
|
AzureAsrHelper.AudioSourceType.MICROPHONE |
|
|
|
override fun onCancel(arguments: Any?) { |
|
|
|
asrEventSink = null |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
// 初始化TTS事件通道 |
|
|
|
ttsEventChannel = |
|
|
|
EventChannel(flutterPluginBinding.binaryMessenger, "azure_speech/tts_events") |
|
|
|
ttsEventChannel.setStreamHandler(object : EventChannel.StreamHandler { |
|
|
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { |
|
|
|
ttsEventSink = events |
|
|
|
setupTtsEventListener() // 在事件通道准备好时设置TTS事件监听器 |
|
|
|
} |
|
|
|
// 直接使用同步方式进行一次性识别 |
|
|
|
azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback { |
|
|
|
override fun onResult(text: String, detectedLanguage: String) { |
|
|
|
// 直接返回结果,不发送事件 |
|
|
|
result.success(mapOf( |
|
|
|
"text" to text, |
|
|
|
"detectedLanguage" to detectedLanguage |
|
|
|
)) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onError(error: String) { |
|
|
|
result.error("RECOGNITION_ERROR", error, null) |
|
|
|
} |
|
|
|
},audioSourceType) |
|
|
|
} catch (e: Exception) { |
|
|
|
result.error("RECOGNITION_ERROR", "识别过程异常: ${e.message}", null) |
|
|
|
} |
|
|
|
} |
|
|
|
"startContinuousRecognition" -> { |
|
|
|
val useExternalAudio = call.argument<Boolean>("audioSourceType") ?: false |
|
|
|
// 确保事件通道已准备好 |
|
|
|
if (asrEventSink == null) { |
|
|
|
result.error("EVENT_CHANNEL_NOT_READY", "事件通道未准备好,无法开始连续识别", null) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
try { |
|
|
|
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") // |
|
|
|
// 选择音频源类型 |
|
|
|
val audioSourceType = if (useExternalAudio) { |
|
|
|
AzureAsrHelper.AudioSourceType.EXTERNAL |
|
|
|
override fun onCancel(arguments: Any?) { |
|
|
|
ttsEventSink = null |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
// 初始化Azure语音服务 |
|
|
|
azureTtsHelper = AzureTtsHelper(context) |
|
|
|
azureAsrHelper = AzureAsrHelper(context) |
|
|
|
|
|
|
|
// 2. 初始化BleService并注册回调 |
|
|
|
if (BleService.initialize(context)) { |
|
|
|
|
|
|
|
if (BleService.addCallback(this)) { |
|
|
|
isRegisteredToBle = true |
|
|
|
FileLogger.d(tag, "已注册BLE回调") |
|
|
|
} else { |
|
|
|
AzureAsrHelper.AudioSourceType.MICROPHONE |
|
|
|
FileLogger.w(tag, "BLE回调注册失败") |
|
|
|
} |
|
|
|
|
|
|
|
val success = azureAsrHelper.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback { |
|
|
|
override fun onResult(text: String, detectedLanguage: String) { |
|
|
|
sendAsrEvent(mapOf( |
|
|
|
"type" to "result", |
|
|
|
"text" to text, |
|
|
|
"detectedLanguage" to detectedLanguage |
|
|
|
)) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onRecognizing(recognizing: String, detectedLanguage: String) { |
|
|
|
sendAsrEvent(mapOf( |
|
|
|
"type" to "recognizing", |
|
|
|
"text" to recognizing, |
|
|
|
"detectedLanguage" to detectedLanguage |
|
|
|
)) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onSessionStarted() { |
|
|
|
sendAsrEvent(mapOf("type" to "sessionStarted")) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onSessionStopped() { |
|
|
|
sendAsrEvent(mapOf("type" to "sessionStopped")) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onCanceled(reason: String, errorDetails: String) { |
|
|
|
sendAsrEvent(mapOf( |
|
|
|
"type" to "canceled", |
|
|
|
"reason" to reason, |
|
|
|
"errorDetails" to errorDetails |
|
|
|
)) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onError(error: String) { |
|
|
|
sendAsrEvent(mapOf( |
|
|
|
"type" to "error", |
|
|
|
"message" to error |
|
|
|
)) |
|
|
|
} |
|
|
|
},audioSourceType) |
|
|
|
result.success(success) |
|
|
|
} catch (e: Exception) { |
|
|
|
result.error("START_RECOGNITION_ERROR", e.message, null) |
|
|
|
} |
|
|
|
} |
|
|
|
"stopContinuousRecognition" -> { |
|
|
|
try { |
|
|
|
val success = azureAsrHelper.stopContinuousRecognition() |
|
|
|
result.success(success) |
|
|
|
} catch (e: Exception) { |
|
|
|
result.error("STOP_ERROR", e.message, null) |
|
|
|
} |
|
|
|
} |
|
|
|
"isContinuousRecognitionActive" -> { |
|
|
|
result.success(azureAsrHelper.isContinuousRecognitionActive()) |
|
|
|
} |
|
|
|
"dispose" -> { |
|
|
|
azureAsrHelper.dispose() |
|
|
|
result.success(true) |
|
|
|
} else { |
|
|
|
FileLogger.e(tag, "BLE服务初始化失败") |
|
|
|
|
|
|
|
} |
|
|
|
"pushAudioData" -> { |
|
|
|
val data = call.argument<ByteArray>("data") ?: return result.error("INVALID_ARGUMENTS", "音频数据不能为空", null) |
|
|
|
azureAsrHelper.pushAudioData(data) |
|
|
|
result.success(true) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 设置TTS事件监听器 |
|
|
|
*/ |
|
|
|
private fun setupTtsEventListener() { |
|
|
|
if (!isTtsListenerAdded) { |
|
|
|
// 添加TTS事件监听器 |
|
|
|
azureTtsHelper.addListener(object : TtsEventListener { |
|
|
|
override fun onEvent(event: TtsEvent) { |
|
|
|
// 将TTS事件转换为Flutter可理解的格式 |
|
|
|
val eventMap = when (event.type) { |
|
|
|
TtsEventType.SYNTHESIS_STARTED -> mapOf( |
|
|
|
"type" to "synthesis_started" |
|
|
|
) |
|
|
|
|
|
|
|
TtsEventType.SYNTHESIS_COMPLETED -> mapOf( |
|
|
|
"type" to "synthesis_completed" |
|
|
|
) |
|
|
|
|
|
|
|
TtsEventType.SYNTHESIS_CANCELED -> { |
|
|
|
val params = event.params |
|
|
|
mapOf( |
|
|
|
"type" to "synthesis_canceled", |
|
|
|
"reason" to (params["reason"] ?: ""), |
|
|
|
"errorDetails" to (params["errorDetails"] ?: "") |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
TtsEventType.ERROR -> { |
|
|
|
val params = event.params |
|
|
|
mapOf( |
|
|
|
"type" to "error", |
|
|
|
"errorCode" to (params["errorCode"] ?: "UNKNOWN_ERROR"), |
|
|
|
"errorMessage" to (params["errorMessage"] ?: "未知错误") |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
else -> mapOf( |
|
|
|
"type" to event.type.toString().lowercase(), |
|
|
|
"params" to event.params |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
// 发送事件 |
|
|
|
sendTtsEvent(eventMap) |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
isTtsListenerAdded = true |
|
|
|
} |
|
|
|
else -> { |
|
|
|
result.notImplemented() |
|
|
|
} |
|
|
|
|
|
|
|
// ASR方法处理器 |
|
|
|
inner class AsrMethodHandler : MethodCallHandler { |
|
|
|
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { |
|
|
|
when (call.method) { |
|
|
|
"initialize" -> { |
|
|
|
val subscriptionKey = call.argument<String>("subscriptionKey") ?: "" |
|
|
|
val region = call.argument<String>("region") ?: "" |
|
|
|
val supportedLanguages = |
|
|
|
call.argument<List<String>>("supportedLanguages") ?: listOf("zh-CN") |
|
|
|
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false |
|
|
|
|
|
|
|
try { |
|
|
|
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") // |
|
|
|
// 选择音频源类型 |
|
|
|
val audioSourceType = if (useExternalAudio) { |
|
|
|
AzureAsrHelper.AudioSourceType.EXTERNAL |
|
|
|
} else { |
|
|
|
AzureAsrHelper.AudioSourceType.MICROPHONE |
|
|
|
} |
|
|
|
|
|
|
|
val success = azureAsrHelper.initialize( |
|
|
|
subscriptionKey = subscriptionKey, |
|
|
|
region = region, |
|
|
|
supportedLanguages = supportedLanguages.toTypedArray(), |
|
|
|
audioSourceType = audioSourceType, |
|
|
|
) |
|
|
|
|
|
|
|
result.success(success) |
|
|
|
} catch (e: Exception) { |
|
|
|
result.error("INITIALIZATION_ERROR", e.message, null) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
"recognizeOnce" -> { |
|
|
|
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false |
|
|
|
try { |
|
|
|
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") // |
|
|
|
// 选择音频源类型 |
|
|
|
val audioSourceType = if (useExternalAudio) { |
|
|
|
AzureAsrHelper.AudioSourceType.EXTERNAL |
|
|
|
} else { |
|
|
|
AzureAsrHelper.AudioSourceType.MICROPHONE |
|
|
|
} |
|
|
|
// 直接使用同步方式进行一次性识别 |
|
|
|
azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback { |
|
|
|
override fun onResult(text: String, detectedLanguage: String) { |
|
|
|
// 直接返回结果,不发送事件 |
|
|
|
result.success( |
|
|
|
mapOf( |
|
|
|
"text" to text, |
|
|
|
"detectedLanguage" to detectedLanguage |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onError(error: String) { |
|
|
|
result.error("RECOGNITION_ERROR", error, null) |
|
|
|
} |
|
|
|
}, audioSourceType) |
|
|
|
} catch (e: Exception) { |
|
|
|
result.error("RECOGNITION_ERROR", "识别过程异常: ${e.message}", null) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
"startContinuousRecognition" -> { |
|
|
|
val useExternalAudio = call.argument<Boolean>("audioSourceType") ?: false |
|
|
|
// 确保事件通道已准备好 |
|
|
|
if (asrEventSink == null) { |
|
|
|
result.error( |
|
|
|
"EVENT_CHANNEL_NOT_READY", |
|
|
|
"事件通道未准备好,无法开始连续识别", |
|
|
|
null |
|
|
|
) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
try { |
|
|
|
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") // |
|
|
|
// 选择音频源类型 |
|
|
|
val audioSourceType = if (useExternalAudio) { |
|
|
|
AzureAsrHelper.AudioSourceType.EXTERNAL |
|
|
|
} else { |
|
|
|
AzureAsrHelper.AudioSourceType.MICROPHONE |
|
|
|
} |
|
|
|
|
|
|
|
val success = azureAsrHelper.startContinuousRecognition(object : |
|
|
|
AzureAsrHelper.ContinuousRecognizeCallback { |
|
|
|
override fun onResult(text: String, detectedLanguage: String) { |
|
|
|
sendAsrEvent( |
|
|
|
mapOf( |
|
|
|
"type" to "result", |
|
|
|
"text" to text, |
|
|
|
"detectedLanguage" to detectedLanguage |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onRecognizing( |
|
|
|
recognizing: String, |
|
|
|
detectedLanguage: String |
|
|
|
) { |
|
|
|
sendAsrEvent( |
|
|
|
mapOf( |
|
|
|
"type" to "recognizing", |
|
|
|
"text" to recognizing, |
|
|
|
"detectedLanguage" to detectedLanguage |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onSessionStarted() { |
|
|
|
sendAsrEvent(mapOf("type" to "sessionStarted")) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onSessionStopped() { |
|
|
|
sendAsrEvent(mapOf("type" to "sessionStopped")) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onCanceled(reason: String, errorDetails: String) { |
|
|
|
sendAsrEvent( |
|
|
|
mapOf( |
|
|
|
"type" to "canceled", |
|
|
|
"reason" to reason, |
|
|
|
"errorDetails" to errorDetails |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onError(error: String) { |
|
|
|
sendAsrEvent( |
|
|
|
mapOf( |
|
|
|
"type" to "error", |
|
|
|
"message" to error |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
}, audioSourceType) |
|
|
|
result.success(success) |
|
|
|
} catch (e: Exception) { |
|
|
|
result.error("START_RECOGNITION_ERROR", e.message, null) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
"stopContinuousRecognition" -> { |
|
|
|
try { |
|
|
|
val success = azureAsrHelper.stopContinuousRecognition() |
|
|
|
result.success(success) |
|
|
|
} catch (e: Exception) { |
|
|
|
result.error("STOP_ERROR", e.message, null) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
"isContinuousRecognitionActive" -> { |
|
|
|
result.success(azureAsrHelper.isContinuousRecognitionActive()) |
|
|
|
} |
|
|
|
|
|
|
|
"dispose" -> { |
|
|
|
azureAsrHelper.dispose() |
|
|
|
result.success(true) |
|
|
|
} |
|
|
|
|
|
|
|
"pushAudioData" -> { |
|
|
|
val data = call.argument<ByteArray>("data") ?: return result.error( |
|
|
|
"INVALID_ARGUMENTS", |
|
|
|
"音频数据不能为空", |
|
|
|
null |
|
|
|
) |
|
|
|
azureAsrHelper.pushAudioData(data) |
|
|
|
result.success(true) |
|
|
|
} |
|
|
|
|
|
|
|
"enableRecord" -> { |
|
|
|
azureAsrHelper.enableRecord() |
|
|
|
result.success(true) |
|
|
|
} |
|
|
|
|
|
|
|
"stopRecord" -> { |
|
|
|
azureAsrHelper.stopRecord() |
|
|
|
result.success(true) |
|
|
|
} |
|
|
|
|
|
|
|
else -> { |
|
|
|
result.notImplemented() |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// TTS方法处理器 |
|
|
|
inner class TtsMethodHandler : MethodCallHandler { |
|
|
|
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { |
|
|
|
when (call.method) { |
|
|
|
"initialize" -> { |
|
|
|
val subscriptionKey = call.argument<String>("subscriptionKey") ?: "" |
|
|
|
val region = call.argument<String>("region") ?: "" |
|
|
|
val useCustomAudioOutput = call.argument<Boolean>("useCustomAudioOutput") ?: false |
|
|
|
|
|
|
|
try { |
|
|
|
// 初始化TTS引擎(使用新的接口方法) |
|
|
|
val success = azureTtsHelper.initialize( |
|
|
|
ttsAppId = "", // Azure TTS不需要appId |
|
|
|
ttsAppToken = subscriptionKey, |
|
|
|
ttsResource = region, |
|
|
|
language = "zh-CN" |
|
|
|
) |
|
|
|
|
|
|
|
// 如果初始化成功且需要使用自定义音频输出,则设置自定义音频播放器 |
|
|
|
if (success && useCustomAudioOutput) { |
|
|
|
// 创建SimpleAudioPlayer实例 |
|
|
|
val customPlayer = SimpleAudioPlayer(context) |
|
|
|
|
|
|
|
// 获取并设置音频输出流 |
|
|
|
val outputStream = customPlayer.getAudioOutputStream() |
|
|
|
if (outputStream != null) { |
|
|
|
azureTtsHelper.setCustomAudioOutputStream(outputStream) |
|
|
|
} |
|
|
|
|
|
|
|
// TTS方法处理器 |
|
|
|
inner class TtsMethodHandler : MethodCallHandler { |
|
|
|
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { |
|
|
|
when (call.method) { |
|
|
|
"initialize" -> { |
|
|
|
val subscriptionKey = call.argument<String>("subscriptionKey") ?: "" |
|
|
|
val region = call.argument<String>("region") ?: "" |
|
|
|
val useCustomAudioOutput = |
|
|
|
call.argument<Boolean>("useCustomAudioOutput") ?: false |
|
|
|
|
|
|
|
try { |
|
|
|
// 初始化TTS引擎(使用新的接口方法) |
|
|
|
val success = azureTtsHelper.initialize( |
|
|
|
ttsAppId = "", // Azure TTS不需要appId |
|
|
|
ttsAppToken = subscriptionKey, |
|
|
|
ttsResource = region, |
|
|
|
language = "zh-CN" |
|
|
|
) |
|
|
|
|
|
|
|
// 如果初始化成功且需要使用自定义音频输出,则设置自定义音频播放器 |
|
|
|
if (success && useCustomAudioOutput) { |
|
|
|
// 创建SimpleAudioPlayer实例 |
|
|
|
val customPlayer = SimpleAudioPlayer(context) |
|
|
|
|
|
|
|
// 获取并设置音频输出流 |
|
|
|
val outputStream = customPlayer.getAudioOutputStream() |
|
|
|
if (outputStream != null) { |
|
|
|
azureTtsHelper.setCustomAudioOutputStream(outputStream) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 确保设置了TTS事件监听器 |
|
|
|
setupTtsEventListener() |
|
|
|
|
|
|
|
result.success(success) |
|
|
|
} catch (e: Exception) { |
|
|
|
result.error("INITIALIZATION_ERROR", "TTS初始化失败: ${e.message}", null) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
"setVoice" -> { |
|
|
|
val voiceName = call.argument<String>("voiceName") |
|
|
|
?: return result.error("INVALID_ARGUMENTS", "语音名称不能为空", null) |
|
|
|
result.success(azureTtsHelper.setVoice(voiceName)) |
|
|
|
} |
|
|
|
|
|
|
|
"speakText" -> { |
|
|
|
val text = call.argument<String>("text") ?: return result.error( |
|
|
|
"INVALID_ARGUMENTS", |
|
|
|
"文本不能为空", |
|
|
|
null |
|
|
|
) |
|
|
|
|
|
|
|
// 直接调用speakOnce方法 |
|
|
|
try { |
|
|
|
val success = azureTtsHelper.speakOnce(text) |
|
|
|
result.success(success) |
|
|
|
} catch (e: Exception) { |
|
|
|
result.error("SPEAK_ERROR", "语音合成失败: ${e.message}", null) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
"stopSpeaking" -> { |
|
|
|
// 直接调用stop方法 |
|
|
|
try { |
|
|
|
val success = azureTtsHelper.stop() |
|
|
|
result.success(success) |
|
|
|
} catch (e: Exception) { |
|
|
|
result.error("STOP_ERROR", "停止语音合成失败: ${e.message}", null) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
"isSpeaking" -> { |
|
|
|
result.success(azureTtsHelper.isSpeaking()) |
|
|
|
} |
|
|
|
|
|
|
|
"dispose" -> { |
|
|
|
azureTtsHelper.release() |
|
|
|
result.success(true) |
|
|
|
} |
|
|
|
|
|
|
|
else -> { |
|
|
|
result.notImplemented() |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 确保设置了TTS事件监听器 |
|
|
|
setupTtsEventListener() |
|
|
|
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { |
|
|
|
asrChannel.setMethodCallHandler(null) |
|
|
|
ttsChannel.setMethodCallHandler(null) |
|
|
|
asrEventChannel.setStreamHandler(null) |
|
|
|
ttsEventChannel.setStreamHandler(null) |
|
|
|
|
|
|
|
try { |
|
|
|
if (isRegisteredToBle) { |
|
|
|
if (BleService.removeCallback(this)) { |
|
|
|
isRegisteredToBle = false |
|
|
|
FileLogger.d(tag, "已取消BLE回调") |
|
|
|
} else { |
|
|
|
FileLogger.w(tag, "BLE回调取消失败") |
|
|
|
} |
|
|
|
} |
|
|
|
// 释放资源 |
|
|
|
azureTtsHelper.release() |
|
|
|
azureAsrHelper.dispose() |
|
|
|
|
|
|
|
result.success(success) |
|
|
|
} catch (e: Exception) { |
|
|
|
result.error("INITIALIZATION_ERROR", "TTS初始化失败: ${e.message}", null) |
|
|
|
} |
|
|
|
} |
|
|
|
"setVoice" -> { |
|
|
|
val voiceName = call.argument<String>("voiceName") ?: return result.error("INVALID_ARGUMENTS", "语音名称不能为空", null) |
|
|
|
result.success(azureTtsHelper.setVoice(voiceName)) |
|
|
|
} |
|
|
|
"speakText" -> { |
|
|
|
val text = call.argument<String>("text") ?: return result.error("INVALID_ARGUMENTS", "文本不能为空", null) |
|
|
|
|
|
|
|
// 直接调用speakOnce方法 |
|
|
|
try { |
|
|
|
val success = azureTtsHelper.speakOnce(text) |
|
|
|
result.success(success) |
|
|
|
} catch (e: Exception) { |
|
|
|
result.error("SPEAK_ERROR", "语音合成失败: ${e.message}", null) |
|
|
|
} |
|
|
|
} |
|
|
|
"stopSpeaking" -> { |
|
|
|
// 直接调用stop方法 |
|
|
|
try { |
|
|
|
val success = azureTtsHelper.stop() |
|
|
|
result.success(success) |
|
|
|
} catch (e: Exception) { |
|
|
|
result.error("STOP_ERROR", "停止语音合成失败: ${e.message}", null) |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
FileLogger.e(tag, "释放资源失败: ${e.message}") |
|
|
|
} |
|
|
|
"isSpeaking" -> { |
|
|
|
result.success(azureTtsHelper.isSpeaking()) |
|
|
|
} |
|
|
|
"dispose" -> { |
|
|
|
azureTtsHelper.release() |
|
|
|
result.success(true) |
|
|
|
} |
|
|
|
else -> { |
|
|
|
result.notImplemented() |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// BleService.Callback 接口实现 |
|
|
|
override fun onScanResult(devices: List<Map<String, Any>>) { |
|
|
|
// 不处理扫描结果 |
|
|
|
} |
|
|
|
|
|
|
|
override fun onConnectionStateChanged(state: Int) { |
|
|
|
} |
|
|
|
|
|
|
|
override fun onAudioDataReceived(data: ByteArray) { |
|
|
|
if (azureAsrHelper.audioSourceType == AzureAsrHelper.AudioSourceType.EXTERNAL) { |
|
|
|
azureAsrHelper.pushAudioData(data) |
|
|
|
} |
|
|
|
} |
|
|
|
// 可选:处理音频数据 |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 处理唤醒信号 |
|
|
|
* 在收到唤醒信号时启动语音识别 |
|
|
|
*/ |
|
|
|
override fun onWakeupSignalReceived() { |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { |
|
|
|
asrChannel.setMethodCallHandler(null) |
|
|
|
ttsChannel.setMethodCallHandler(null) |
|
|
|
asrEventChannel.setStreamHandler(null) |
|
|
|
ttsEventChannel.setStreamHandler(null) |
|
|
|
|
|
|
|
try { |
|
|
|
// 释放资源 |
|
|
|
azureTtsHelper.release() |
|
|
|
azureAsrHelper.dispose() |
|
|
|
} catch (e: Exception) { |
|
|
|
FileLogger.e(tag, "释放资源失败: ${e.message}") |
|
|
|
|
|
|
|
override fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>) { |
|
|
|
// 不处理设备信息 |
|
|
|
} |
|
|
|
} |
|
|
|
} |