diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index b7d2b48c7..326fe0f55 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -34,7 +34,7 @@ android { applicationId = "com.example.deep_voice" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = 23 // Updated to meet record_android plugin requirements + minSdk = 24 // Updated to meet azure_speech_recognition_null_safety requirements targetSdk = 33 // 设置为 Android 13 (API 33),以匹配 FOREGROUND_SERVICE_MEDIA_PLAYBACK 权限的要求 versionCode = flutter.versionCode versionName = flutter.versionName diff --git a/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt b/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt new file mode 100644 index 000000000..537064614 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt @@ -0,0 +1,218 @@ +package com.example.deep_voice + +import android.util.Log +import com.microsoft.cognitiveservices.speech.* +import com.microsoft.cognitiveservices.speech.audio.* +import com.microsoft.cognitiveservices.speech.util.EventHandler +import java.util.concurrent.ExecutionException + +class AzureAsrHelper { + private var recognizer: SpeechRecognizer? = null + private var speechConfig: SpeechConfig? = null + private var audioConfig: AudioConfig? = null + private val TAG = "AzureAsrHelper" + private var isContinuousRecognitionActive = false + private var currentLanguage = "zh-CN" + private var subscriptionKey = "" + private var serviceRegion = "" + + // 初始化 SDK + fun initialize(subscriptionKey: String, serviceRegion: String, language: String = "zh-CN"): Boolean { + try { + this.subscriptionKey = subscriptionKey + this.serviceRegion = serviceRegion + this.currentLanguage = language + + // 释放之前的资源 + dispose() + + // 创建语音配置 + speechConfig = SpeechConfig.fromSubscription(subscriptionKey, serviceRegion) + speechConfig?.speechRecognitionLanguage = language + + // 创建音频配置 + audioConfig = AudioConfig.fromDefaultMicrophoneInput() + + // 创建识别器 + recognizer = SpeechRecognizer(speechConfig, audioConfig) + return true + } catch (e: Exception) { + Log.e(TAG, "初始化失败: ${e.message}") + return false + } + } + + // 开始一次性语音识别 + fun recognizeOnce(language: String? = null, callback: RecognizeCallback) { + if (recognizer == null) { + callback.onError("SpeechRecognizer 未初始化") + return + } + + // 如果指定了新的语言,需要重新初始化 + if (language != null && language != currentLanguage) { + initialize(subscriptionKey, serviceRegion, language) + } + + try { + val result = recognizer?.recognizeOnceAsync()?.get() + + if (result != null && result.reason == ResultReason.RecognizedSpeech) { + callback.onResult(result.text) + } else { + callback.onError("未能识别语音") + } + } catch (e: Exception) { + callback.onError("识别异常: ${e.message}") + } + } + + // 开始连续语音识别 + fun startContinuousRecognition(language: String? = null, callback: ContinuousRecognizeCallback): Boolean { + if (recognizer == null) { + callback.onError("SpeechRecognizer 未初始化") + return false + } + + // 如果已经在进行连续识别,先停止 + if (isContinuousRecognitionActive) { + stopContinuousRecognition(callback) + } + + // 如果指定了新的语言,需要重新初始化 + if (language != null && language != currentLanguage) { + initialize(subscriptionKey, serviceRegion, language) + } + + try { + // 先发送会话开始事件 + callback.onSessionStarted() + + // 重新创建识别器 + recognizer?.close() + recognizer = SpeechRecognizer(speechConfig, audioConfig) + + // 设置识别事件处理 + recognizer?.let { recognizer -> + // 最终识别结果 + recognizer.recognized.addEventListener( + EventHandler { _, event -> + if (event.result.reason == ResultReason.RecognizedSpeech) { + callback.onResult(event.result.text) + } + } + ) + + // 识别中事件 + recognizer.recognizing.addEventListener( + EventHandler { _, event -> + if (event.result.reason == ResultReason.RecognizingSpeech) { + callback.onRecognizing(event.result.text) + } + } + ) + + // 会话开始事件 + recognizer.sessionStarted.addEventListener( + EventHandler { _, _ -> + isContinuousRecognitionActive = true + callback.onSessionStarted() + } + ) + + // 会话结束事件 + recognizer.sessionStopped.addEventListener( + EventHandler { _, _ -> + isContinuousRecognitionActive = false + callback.onSessionStopped() + } + ) + + // 取消事件 + recognizer.canceled.addEventListener( + EventHandler { _, event -> + val errorDetails = if (event.reason == CancellationReason.Error) event.errorDetails else "" + callback.onCanceled(event.reason.toString(), errorDetails) + isContinuousRecognitionActive = false + } + ) + + // 开始连续识别 + recognizer.startContinuousRecognitionAsync().get() + isContinuousRecognitionActive = true + } + + return true + } catch (e: Exception) { + callback.onError("开始连续识别失败: ${e.message}") + isContinuousRecognitionActive = false + return false + } + } + + // 停止连续语音识别 + fun stopContinuousRecognition(callback: ContinuousRecognizeCallback): Boolean { + if (!isContinuousRecognitionActive || recognizer == null) { + return true + } + + try { + recognizer?.stopContinuousRecognitionAsync()?.get() + isContinuousRecognitionActive = false + callback.onSessionStopped() + return true + } catch (e: Exception) { + callback.onError("停止连续识别失败: ${e.message}") + isContinuousRecognitionActive = false + return false + } + } + + // 检查连续识别是否活跃 + fun isContinuousRecognitionActive(): Boolean { + return isContinuousRecognitionActive + } + + // 获取当前语言 + fun getCurrentLanguage(): String { + return currentLanguage + } + + // 释放资源 + fun dispose() { + try { + if (isContinuousRecognitionActive && recognizer != null) { + recognizer?.stopContinuousRecognitionAsync()?.get() + } + + recognizer?.close() + recognizer = null + + audioConfig?.close() + audioConfig = null + + speechConfig?.close() + speechConfig = null + + isContinuousRecognitionActive = false + } catch (e: Exception) { + // 忽略异常 + } + } + + // 一次性识别回调接口 + interface RecognizeCallback { + fun onResult(result: String) + fun onError(error: String) + } + + // 连续识别回调接口 + interface ContinuousRecognizeCallback { + fun onResult(result: String) + fun onRecognizing(recognizing: String) + fun onSessionStarted() + fun onSessionStopped() + fun onCanceled(reason: String, errorDetails: String) + fun onError(error: String) + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt b/android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt new file mode 100644 index 000000000..53ac72076 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt @@ -0,0 +1,279 @@ +package com.example.deep_voice + +import android.util.Log +import com.microsoft.cognitiveservices.speech.* +import com.microsoft.cognitiveservices.speech.audio.* +import java.util.concurrent.Future +import java.util.concurrent.Semaphore + +/** + * Microsoft Text-to-Speech Helper + * + * 该类封装了微软语音 SDK 的 TTS 功能,提供简单的接口供 Flutter 调用 + * 基于微软官方文档: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/how-to-speech-synthesis + */ +class AzureTtsHelper { + private val TAG = "AzureTtsHelper" + private var speechConfig: SpeechConfig? = null + private var synthesizer: SpeechSynthesizer? = null + private var isInitialized = false + + // 用于异步操作的信号量 + private val semaphore = Semaphore(0) + + // 当前语音设置 + private var currentVoiceName = "zh-CN-XiaoxiaoNeural" + private var currentSpeechRate = "0%" + private var currentPitch = "0%" + private var currentVolume = "100%" + + /** + * 初始化 TTS 引擎 + * + * @param subscriptionKey Azure 语音服务订阅密钥 + * @param serviceRegion Azure 语音服务区域 + * @param language 可选,默认语言,默认为 "zh-CN" + */ + fun initialize(subscriptionKey: String, serviceRegion: String, language: String = "zh-CN"): Boolean { + try { + // 创建语音配置 + speechConfig = SpeechConfig.fromSubscription(subscriptionKey, serviceRegion) + + // 设置语音合成输出格式为高质量音频 + speechConfig?.setSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm) + + // 设置默认语言 + speechConfig?.setSpeechSynthesisLanguage(language) + + // 设置默认语音 + speechConfig?.setSpeechSynthesisVoiceName(currentVoiceName) + + // 创建音频配置 - 使用默认扬声器 + val audioConfig = AudioConfig.fromDefaultSpeakerOutput() + + // 创建语音合成器 + synthesizer = SpeechSynthesizer(speechConfig, audioConfig) + + // 设置事件监听 + synthesizer?.SynthesisCompleted?.addEventListener { _, _ -> semaphore.release() } + synthesizer?.SynthesisCanceled?.addEventListener { _, _ -> semaphore.release() } + + isInitialized = true + Log.d(TAG, "TTS 引擎初始化成功") + return true + } catch (e: Exception) { + Log.e(TAG, "TTS 引擎初始化失败: ${e.message}") + e.printStackTrace() + return false + } + } + + /** + * 设置语音 + * + * @param voiceName 语音名称,例如 "zh-CN-XiaoxiaoNeural" + */ + fun setVoice(voiceName: String): Boolean { + if (!isInitialized) { + Log.e(TAG, "TTS 引擎尚未初始化") + return false + } + + try { + currentVoiceName = voiceName + speechConfig?.setSpeechSynthesisVoiceName(voiceName) + + // 重新创建合成器以应用新的语音设置 + val audioConfig = AudioConfig.fromDefaultSpeakerOutput() + synthesizer?.close() + synthesizer = SpeechSynthesizer(speechConfig, audioConfig) + + // 重新设置事件监听 + synthesizer?.SynthesisCompleted?.addEventListener { _, _ -> semaphore.release() } + synthesizer?.SynthesisCanceled?.addEventListener { _, _ -> semaphore.release() } + + Log.d(TAG, "已设置语音: $voiceName") + return true + } catch (e: Exception) { + Log.e(TAG, "设置语音失败: ${e.message}") + e.printStackTrace() + return false + } + } + + /** + * 设置语音合成参数 + * + * @param rate 语速,范围 -100 到 100,默认为 0 + * @param pitch 音调,范围 -100 到 100,默认为 0 + * @param volume 音量,范围 0 到 100,默认为 100 + */ + fun setSpeechParams(rate: Int = 0, pitch: Int = 0, volume: Int = 100): Boolean { + if (!isInitialized) { + Log.e(TAG, "TTS 引擎尚未初始化") + return false + } + + try { + currentSpeechRate = if (rate == 0) "0%" else if (rate < 0) "${(rate * 0.9).toInt()}%" else "$rate%" + currentPitch = if (pitch == 0) "0%" else "${(pitch * 0.5).toInt()}%" + currentVolume = "${volume.coerceIn(0, 100)}%" + + Log.d(TAG, "已设置语音参数: 语速=$currentSpeechRate, 音调=$currentPitch, 音量=$currentVolume") + return true + } catch (e: Exception) { + Log.e(TAG, "设置语音参数失败: ${e.message}") + e.printStackTrace() + return false + } + } + + /** + * 合成文本为语音并播放 + * + * @param text 要合成的文本 + * @param callback 回调接口,用于返回结果或错误 + */ + fun speakText(text: String, callback: TTSCallback) { + if (!isInitialized) { + callback.onError("TTS 引擎尚未初始化") + return + } + + try { + Log.d(TAG, "开始合成文本: $text") + + // 生成 SSML + val ssml = generateSsml(text) + + // 使用 SSML 合成语音 + speakSsml(ssml, callback) + } catch (e: Exception) { + Log.e(TAG, "语音合成异常: ${e.message}") + e.printStackTrace() + callback.onError("语音合成异常: ${e.message}") + } + } + + /** + * 生成 SSML 文本 + * + * @param text 要转换的文本 + * @return SSML 格式的文本 + */ + private fun generateSsml(text: String): String { + return """ + + + + $text + + + + """.trimIndent() + } + + /** + * 合成 SSML 为语音并播放 + * + * @param ssml SSML 格式的文本 + * @param callback 回调接口,用于返回结果或错误 + */ + fun speakSsml(ssml: String, callback: TTSCallback) { + if (!isInitialized) { + callback.onError("TTS 引擎尚未初始化") + return + } + + try { + Log.d(TAG, "开始合成 SSML") + + // 重置信号量 + semaphore.drainPermits() + + // 异步合成语音 + val task: Future = synthesizer!!.SpeakSsmlAsync(ssml) + + // 等待合成完成 + Thread { + try { + // 等待合成完成信号 + semaphore.acquire() + + // 获取结果 + val result = task.get() + + when (result.reason) { + ResultReason.SynthesizingAudioCompleted -> { + Log.d(TAG, "语音合成完成,音频长度: ${result.audioLength / 10000}ms,音频数据大小: ${result.audioData?.size ?: 0} 字节") + callback.onSuccess("语音合成完成") + } + ResultReason.Canceled -> { + val cancellation = SpeechSynthesisCancellationDetails.fromResult(result) + Log.e(TAG, "语音合成取消: ${cancellation.reason}, ${cancellation.errorDetails}") + callback.onError("语音合成取消: ${cancellation.reason}, ${cancellation.errorDetails}") + } + else -> { + Log.e(TAG, "语音合成失败: ${result.reason}") + callback.onError("语音合成失败: ${result.reason}") + } + } + + result.close() + } catch (e: Exception) { + Log.e(TAG, "处理语音合成结果异常: ${e.message}") + e.printStackTrace() + callback.onError("处理语音合成结果异常: ${e.message}") + } + }.start() + } catch (e: Exception) { + Log.e(TAG, "语音合成异常: ${e.message}") + e.printStackTrace() + callback.onError("语音合成异常: ${e.message}") + } + } + + /** + * 停止当前语音合成 + */ + fun stopSpeaking(): Boolean { + if (!isInitialized) { + Log.e(TAG, "TTS 引擎尚未初始化") + return false + } + + try { + synthesizer?.StopSpeakingAsync() + Log.d(TAG, "已停止语音合成") + return true + } catch (e: Exception) { + Log.e(TAG, "停止语音合成失败: ${e.message}") + e.printStackTrace() + return false + } + } + + /** + * 释放资源 + */ + fun dispose() { + try { + stopSpeaking() + synthesizer?.close() + speechConfig?.close() + isInitialized = false + Log.d(TAG, "TTS 引擎已释放") + } catch (e: Exception) { + Log.e(TAG, "释放 TTS 引擎失败: ${e.message}") + e.printStackTrace() + } + } + + /** + * TTS 回调接口 + */ + interface TTSCallback { + fun onSuccess(message: String) + fun onError(error: String) + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt b/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt index ae899a3d9..1df0fd09f 100644 --- a/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt @@ -17,77 +17,50 @@ import io.flutter.plugins.GeneratedPluginRegistrant class MainActivity: AudioServiceActivity() { private val AUDIO_PROCESSING_CHANNEL = "com.deep_voice.audio_processing" - // 移除 Azure TTS 相关通道 - // private val AZURE_TTS_CHANNEL = "com.deep_voice.azure_tts" - // private val AZURE_TTS_EVENT_CHANNEL = "com.deep_voice.azure_tts_events" + private val AZURE_ASR_CHANNEL = "com.deep_voice.azure_asr" + private val AZURE_ASR_EVENT_CHANNEL = "com.deep_voice.azure_asr_events" + private val AZURE_TTS_CHANNEL = "com.deep_voice.azure_tts" private val TAG = "MainActivity" - // 移除 Azure ASR 助手 - // private lateinit var azureAsrHelper: AzureAsrHelper - // 暂时注释掉AudioProcessingPlugin的实例化 - // private val audioProcessingPlugin: AudioProcessingPlugin by lazy { AudioProcessingPlugin() } - - // 移除 Azure TTS 助手 - // private lateinit var azureTtsHelper: AzureTtsHelper - - // 移除 Azure 事件接收器 - // private var azureAsrEventSink: EventChannel.EventSink? = null - // private var azureTtsEventSink: EventChannel.EventSink? = null + private lateinit var azureAsrHelper: AzureAsrHelper + private lateinit var azureTtsHelper: AzureTtsHelper + private var azureAsrEventSink: EventChannel.EventSink? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - // 移除 Azure ASR 助手初始化 - // azureAsrHelper = AzureAsrHelper() - - // 移除 Azure TTS 助手初始化 - // azureTtsHelper = AzureTtsHelper(applicationContext) - - // 暂时注释掉AudioProcessingPlugin的初始化 - // AudioProcessingPlugin.setApplicationContext(applicationContext) - // Log.i(TAG, "在MainActivity.onCreate中初始化AudioProcessingPlugin的applicationContext") + azureAsrHelper = AzureAsrHelper() + azureTtsHelper = AzureTtsHelper() } override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) - // 暂时注释掉AudioProcessingPlugin的初始化 - // AudioProcessingPlugin.setApplicationContext(applicationContext) - // Log.i(TAG, "在configureFlutterEngine中初始化AudioProcessingPlugin的applicationContext") - // 注册所有插件 GeneratedPluginRegistrant.registerWith(flutterEngine) - // 暂时注释掉AudioProcessingPlugin的注册,添加一个空的MethodCallHandler来处理调用 + // 音频处理通道 MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AUDIO_PROCESSING_CHANNEL).setMethodCallHandler { call, result -> // 为所有方法返回默认值或成功状态 when (call.method) { "isEchoCancellationAvailable" -> { - // 返回false表示不可用 result.success(false) } "isNoiseSuppressionAvailable" -> { - // 返回false表示不可用 result.success(false) } "enableEchoCancellation" -> { - // 返回true表示成功,但实际上不执行任何操作 result.success(true) } "disableEchoCancellation" -> { - // 返回true表示成功 result.success(true) } "enableNoiseSuppression" -> { - // 返回true表示成功,但实际上不执行任何操作 result.success(true) } "disableNoiseSuppression" -> { - // 返回true表示成功 result.success(true) } "getAudioSessionId" -> { - // 返回一个固定的音频会话ID result.success(1) } else -> { @@ -96,95 +69,235 @@ class MainActivity: AudioServiceActivity() { } } - // 移除 Azure ASR 方法通道 - - // 移除 Azure TTS 方法通道和相关代码 - /* - MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_TTS_CHANNEL).setMethodCallHandler { call, result -> + // Azure ASR 方法通道 + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_ASR_CHANNEL).setMethodCallHandler { call, result -> when (call.method) { "initialize" -> { val subscriptionKey = call.argument("subscriptionKey") val region = call.argument("region") + val language = call.argument("language") ?: "zh-CN" if (subscriptionKey == null || region == null) { result.error("INVALID_ARGUMENTS", "订阅密钥和区域不能为空", null) return@setMethodCallHandler } - val success = azureTtsHelper.initialize(subscriptionKey, region) - result.success(success) - } - "setVoice" -> { - val voiceName = call.argument("voiceName") - - if (voiceName == null) { - result.error("INVALID_ARGUMENTS", "语音名称不能为空", null) - return@setMethodCallHandler + try { + val success = azureAsrHelper.initialize(subscriptionKey, region, language) + result.success(success) + } catch (e: Exception) { + result.error("INITIALIZATION_ERROR", e.message, null) } + } + "recognizeOnce" -> { + val language = call.argument("language") - val success = azureTtsHelper.setVoice(voiceName) - result.success(success) + azureAsrHelper.recognizeOnce(language, object : AzureAsrHelper.RecognizeCallback { + override fun onResult(text: String) { + result.success(text) + } + + override fun onError(error: String) { + result.error("RECOGNITION_ERROR", error, null) + } + }) } - "synthesizeToSpeaker" -> { - val text = call.argument("text") + "startContinuousRecognition" -> { + val language = call.argument("language") - if (text == null) { - result.error("INVALID_ARGUMENTS", "文本不能为空", null) - return@setMethodCallHandler + // 确保事件通道已准备好 + if (!isEventChannelReady()) { + Log.w(TAG, "事件通道未准备好,等待...") + if (!waitForEventChannel()) { + result.error("EVENT_CHANNEL_NOT_READY", "事件通道未准备好,无法开始连续识别", null) + return@setMethodCallHandler + } } - azureTtsHelper.synthesizeToSpeaker(text, result) + val success = azureAsrHelper.startContinuousRecognition(language, object : AzureAsrHelper.ContinuousRecognizeCallback { + override fun onResult(text: String) { + sendAsrEvent(mapOf("type" to "result", "text" to text)) + } + + override fun onRecognizing(recognizing: String) { + sendAsrEvent(mapOf("type" to "recognizing", "text" to recognizing)) + } + + 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)) + } + }) + result.success(success) } - "synthesizeToFile" -> { - val text = call.argument("text") - val filePath = call.argument("filePath") - - if (text == null || filePath == null) { - result.error("INVALID_ARGUMENTS", "文本和文件路径不能为空", null) - return@setMethodCallHandler + "stopContinuousRecognition" -> { + try { + if (!azureAsrHelper.isContinuousRecognitionActive()) { + result.success(true) + return@setMethodCallHandler + } + + val success = azureAsrHelper.stopContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback { + override fun onResult(text: String) {} + override fun onRecognizing(recognizing: String) {} + override fun onSessionStarted() {} + override fun onSessionStopped() {} + override fun onCanceled(reason: String, errorDetails: String) {} + override fun onError(error: String) { + result.error("STOP_ERROR", error, null) + } + }) + result.success(success) + } catch (e: Exception) { + result.error("STOP_ERROR", e.message, null) } - - azureTtsHelper.synthesizeToFile(text, filePath, result) + } + "isContinuousRecognitionActive" -> { + result.success(azureAsrHelper.isContinuousRecognitionActive()) + } + "dispose" -> { + azureAsrHelper.dispose() + result.success(true) } else -> { result.notImplemented() } } } - */ - // 移除 Azure ASR 事件通道 - - // 移除 Azure TTS 事件通道 - /* - EventChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_TTS_EVENT_CHANNEL).setStreamHandler( + // Azure ASR 事件通道 + EventChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_ASR_EVENT_CHANNEL).setStreamHandler( object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { - azureTtsEventSink = events - azureTtsHelper.setEventSink(events) + Log.d(TAG, "ASR事件通道开始监听") + azureAsrEventSink = events + + // 通知 Flutter 端事件通道已准备好 + events?.success(mapOf("type" to "channelReady")) } override fun onCancel(arguments: Any?) { - azureTtsEventSink = null - azureTtsHelper.setEventSink(null) + Log.d(TAG, "ASR事件通道取消监听") + azureAsrEventSink = null } } ) - */ + + // Azure TTS 方法通道 + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_TTS_CHANNEL).setMethodCallHandler { call, result -> + when (call.method) { + "initialize" -> { + val subscriptionKey = call.argument("subscriptionKey") + val region = call.argument("serviceRegion") + val language = call.argument("language") ?: "zh-CN" + + if (subscriptionKey == null || region == null) { + result.error("INVALID_ARGUMENTS", "订阅密钥和区域不能为空", null) + return@setMethodCallHandler + } + + try { + val success = azureTtsHelper.initialize(subscriptionKey, region, language) + result.success(success) + } catch (e: Exception) { + result.error("INITIALIZATION_ERROR", e.message, null) + } + } + "setVoice" -> { + val voiceName = call.argument("voiceName") ?: return@setMethodCallHandler result.error("INVALID_ARGUMENTS", "语音名称不能为空", null) + result.success(azureTtsHelper.setVoice(voiceName)) + } + "setSpeechParams" -> { + val rate = call.argument("rate") ?: 0 + val pitch = call.argument("pitch") ?: 0 + val volume = call.argument("volume") ?: 100 + result.success(azureTtsHelper.setSpeechParams(rate, pitch, volume)) + } + "speakText" -> { + val text = call.argument("text") ?: return@setMethodCallHandler result.error("INVALID_ARGUMENTS", "文本不能为空", null) + + azureTtsHelper.speakText(text, object : AzureTtsHelper.TTSCallback { + override fun onSuccess(message: String) { + runOnUiThread { result.success(message) } + } + + override fun onError(error: String) { + runOnUiThread { result.error("SPEAK_ERROR", error, null) } + } + }) + } + "speakSsml" -> { + val ssml = call.argument("ssml") ?: return@setMethodCallHandler result.error("INVALID_ARGUMENTS", "SSML不能为空", null) + + azureTtsHelper.speakSsml(ssml, object : AzureTtsHelper.TTSCallback { + override fun onSuccess(message: String) { + runOnUiThread { result.success(message) } + } + + override fun onError(error: String) { + runOnUiThread { result.error("SPEAK_ERROR", error, null) } + } + }) + } + "stopSpeaking" -> { + result.success(azureTtsHelper.stopSpeaking()) + } + "dispose" -> { + azureTtsHelper.dispose() + result.success(true) + } + else -> { + result.notImplemented() + } + } + } } - // 移除 ASR 事件发送方法 + // ASR 事件发送方法 + fun sendAsrEvent(event: Map) { + if (azureAsrEventSink == null) return + + runOnUiThread { + azureAsrEventSink?.success(event) + } + } - // 移除 TTS 事件发送方法 - /* - fun sendTtsEvent(event: Map) { - azureTtsEventSink?.success(event) + // 检查事件通道是否已准备好 + fun isEventChannelReady(): Boolean { + return azureAsrEventSink != null + } + + // 等待事件通道准备好 + fun waitForEventChannel(timeoutMs: Long = 5000): Boolean { + val startTime = System.currentTimeMillis() + while (azureAsrEventSink == null) { + if (System.currentTimeMillis() - startTime > timeoutMs) { + Log.e(TAG, "等待事件通道超时") + return false + } + Thread.sleep(100) + } + return true } - */ override fun onDestroy() { - // 移除 Azure TTS 资源释放 - // azureTtsHelper.dispose() + azureAsrHelper.dispose() + azureTtsHelper.dispose() super.onDestroy() } }