10 changed files with 1417 additions and 1563 deletions
@ -0,0 +1,194 @@ |
|||
package com.example.deep_voice |
|||
|
|||
import android.util.Log |
|||
import com.microsoft.cognitiveservices.speech.* |
|||
import com.microsoft.cognitiveservices.speech.util.EventHandler |
|||
import java.util.concurrent.ExecutionException |
|||
import java.util.function.Consumer |
|||
|
|||
class SpeechRecognitionHelper { |
|||
private var recognizer: SpeechRecognizer? = null |
|||
private val TAG = "SpeechRecognitionHelper" |
|||
private var isContinuousRecognitionActive = false |
|||
|
|||
// 初始化 SDK |
|||
fun initialize(subscriptionKey: String, serviceRegion: String) { |
|||
try { |
|||
val config = SpeechConfig.fromSubscription(subscriptionKey, serviceRegion) |
|||
// 设置识别语言,例如中文 |
|||
config.speechRecognitionLanguage = "zh-CN" |
|||
recognizer = SpeechRecognizer(config) |
|||
Log.d(TAG, "Speech SDK initialized successfully") |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "初始化失败: ${e.message}") |
|||
} |
|||
} |
|||
|
|||
// 开始一次性语音识别 |
|||
fun recognizeOnce(callback: RecognizeCallback) { |
|||
if (recognizer == null) { |
|||
callback.onError("SpeechRecognizer 未初始化") |
|||
return |
|||
} |
|||
|
|||
try { |
|||
// 使用同步方式调用,避免 CompletableFuture 的兼容性问题 |
|||
val result = recognizer?.recognizeOnceAsync()?.get() |
|||
|
|||
if (result != null) { |
|||
when (result.reason) { |
|||
ResultReason.RecognizedSpeech -> { |
|||
callback.onResult(result.text) |
|||
} |
|||
else -> { |
|||
callback.onError("识别失败,原因: ${result.reason}") |
|||
} |
|||
} |
|||
} else { |
|||
callback.onError("识别结果为空") |
|||
} |
|||
} catch (e: Exception) { |
|||
when (e) { |
|||
is InterruptedException, is ExecutionException -> { |
|||
Log.e(TAG, "识别异常: ${e.message}") |
|||
callback.onError("识别异常: ${e.message}") |
|||
} |
|||
else -> { |
|||
Log.e(TAG, "未知异常: ${e.message}") |
|||
callback.onError("未知异常: ${e.message}") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 开始连续语音识别 |
|||
fun startContinuousRecognition(callback: ContinuousRecognizeCallback) { |
|||
if (recognizer == null) { |
|||
callback.onError("SpeechRecognizer 未初始化") |
|||
return |
|||
} |
|||
|
|||
if (isContinuousRecognitionActive) { |
|||
callback.onError("连续识别已经在进行中") |
|||
return |
|||
} |
|||
|
|||
try { |
|||
// 设置识别事件处理 |
|||
recognizer?.let { recognizer -> |
|||
// 设置识别事件处理 |
|||
recognizer.recognized.addEventListener( |
|||
EventHandler<SpeechRecognitionEventArgs> { _, event -> |
|||
if (event.result.reason == ResultReason.RecognizedSpeech) { |
|||
callback.onResult(event.result.text) |
|||
} |
|||
} |
|||
) |
|||
|
|||
// 设置识别中事件处理(实时反馈) |
|||
recognizer.recognizing.addEventListener( |
|||
EventHandler<SpeechRecognitionEventArgs> { _, event -> |
|||
if (event.result.reason == ResultReason.RecognizingSpeech) { |
|||
callback.onRecognizing(event.result.text) |
|||
} |
|||
} |
|||
) |
|||
|
|||
// 设置会话开始事件处理 |
|||
recognizer.sessionStarted.addEventListener( |
|||
EventHandler<SessionEventArgs> { _, _ -> |
|||
callback.onSessionStarted() |
|||
} |
|||
) |
|||
|
|||
// 设置会话结束事件处理 |
|||
recognizer.sessionStopped.addEventListener( |
|||
EventHandler<SessionEventArgs> { _, _ -> |
|||
isContinuousRecognitionActive = false |
|||
callback.onSessionStopped() |
|||
} |
|||
) |
|||
|
|||
// 设置取消事件处理 |
|||
recognizer.canceled.addEventListener( |
|||
EventHandler<SpeechRecognitionCanceledEventArgs> { _, event -> |
|||
val reason = event.reason |
|||
val errorDetails = if (reason == CancellationReason.Error) event.errorDetails else "" |
|||
callback.onCanceled(reason.toString(), errorDetails) |
|||
isContinuousRecognitionActive = false |
|||
} |
|||
) |
|||
|
|||
// 开始连续识别 |
|||
recognizer.startContinuousRecognitionAsync().get() |
|||
isContinuousRecognitionActive = true |
|||
Log.d(TAG, "连续识别已开始") |
|||
} |
|||
|
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "开始连续识别失败: ${e.message}") |
|||
callback.onError("开始连续识别失败: ${e.message}") |
|||
isContinuousRecognitionActive = false |
|||
} |
|||
} |
|||
|
|||
// 停止连续语音识别 |
|||
fun stopContinuousRecognition(callback: ContinuousRecognizeCallback) { |
|||
if (recognizer == null) { |
|||
callback.onError("SpeechRecognizer 未初始化") |
|||
return |
|||
} |
|||
|
|||
if (!isContinuousRecognitionActive) { |
|||
callback.onError("连续识别未在进行中") |
|||
return |
|||
} |
|||
|
|||
try { |
|||
// 停止连续识别 |
|||
recognizer?.stopContinuousRecognitionAsync()?.get() |
|||
isContinuousRecognitionActive = false |
|||
Log.d(TAG, "连续识别已停止") |
|||
callback.onSessionStopped() |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "停止连续识别失败: ${e.message}") |
|||
callback.onError("停止连续识别失败: ${e.message}") |
|||
} |
|||
} |
|||
|
|||
// 检查连续识别是否活跃 |
|||
fun isContinuousRecognitionActive(): Boolean { |
|||
return isContinuousRecognitionActive |
|||
} |
|||
|
|||
// 释放资源 |
|||
fun dispose() { |
|||
try { |
|||
if (isContinuousRecognitionActive) { |
|||
recognizer?.stopContinuousRecognitionAsync()?.get() |
|||
isContinuousRecognitionActive = false |
|||
} |
|||
recognizer?.close() |
|||
recognizer = null |
|||
Log.d(TAG, "语音识别资源已释放") |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "释放资源失败: ${e.message}") |
|||
} |
|||
} |
|||
|
|||
// 一次性识别回调接口 |
|||
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) |
|||
} |
|||
} |
|||
@ -1,493 +0,0 @@ |
|||
package com.example.deep_voice |
|||
|
|||
import android.app.Activity |
|||
import android.content.Context |
|||
import android.util.Log |
|||
import com.bytedance.speech.speechengine.SpeechEngine |
|||
import com.bytedance.speech.speechengine.SpeechEngineDefines |
|||
import com.bytedance.speech.speechengine.SpeechEngineGenerator |
|||
import io.flutter.plugin.common.BinaryMessenger |
|||
import io.flutter.plugin.common.EventChannel |
|||
import io.flutter.plugin.common.MethodChannel |
|||
import io.flutter.plugin.common.MethodChannel.Result |
|||
import org.json.JSONObject |
|||
import java.io.File |
|||
|
|||
|
|||
|
|||
/** |
|||
* 火山语音识别桥接类 |
|||
* 负责与火山语音识别 SDK 交互,并将结果通过 MethodChannel 和 EventChannel 传递给 Flutter 层 |
|||
*/ |
|||
class VolcSpeechBridge( |
|||
private val activity: Activity, |
|||
messenger: BinaryMessenger |
|||
) : SpeechEngine.SpeechListener { |
|||
|
|||
companion object { |
|||
private const val TAG = "VolcSpeechBridge" |
|||
private const val CHANNEL_NAME = "com.example.deep_voice/speech_recognition" |
|||
private const val EVENT_CHANNEL_NAME = "com.example.deep_voice/speech_recognition_events" |
|||
} |
|||
|
|||
private var engine: SpeechEngine? = null |
|||
private var engineHandler: Long = 0 |
|||
private var methodResult: Result? = null |
|||
private var eventSink: EventChannel.EventSink? = null |
|||
private var isInitialized = false |
|||
|
|||
// 初始化 MethodChannel 和 EventChannel |
|||
init { |
|||
// 设置 MethodChannel 处理方法调用 |
|||
MethodChannel(messenger, CHANNEL_NAME).setMethodCallHandler { call, result -> |
|||
when (call.method) { |
|||
"initialize" -> { |
|||
val appKey = call.argument<String>("appKey") ?: "" |
|||
val accessKey = call.argument<String>("accessKey") ?: "" |
|||
val cluster = call.argument<String>("cluster") ?: "volcano_asr" |
|||
initialize(appKey, accessKey, cluster, result) |
|||
} |
|||
"startRecognition" -> { |
|||
startRecognition(result) |
|||
} |
|||
"stopRecognition" -> { |
|||
stopRecognition(result) |
|||
} |
|||
"isInitialized" -> { |
|||
result.success(isInitialized) |
|||
} |
|||
"checkEngineCapabilities" -> { |
|||
checkEngineCapabilities(result) |
|||
} |
|||
else -> { |
|||
result.notImplemented() |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 设置 EventChannel 处理事件流 |
|||
EventChannel(messenger, EVENT_CHANNEL_NAME).setStreamHandler(object : EventChannel.StreamHandler { |
|||
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { |
|||
eventSink = events |
|||
} |
|||
|
|||
override fun onCancel(arguments: Any?) { |
|||
eventSink = null |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 初始化火山语音识别引擎 |
|||
*/ |
|||
fun initialize(appKey: String, accessKey: String, cluster: String, result: Result) { |
|||
methodResult = result |
|||
try { |
|||
Log.i(TAG, "Initializing Volcengine Speech Engine") |
|||
|
|||
// 前置操作:环境依赖 |
|||
SpeechEngineGenerator.PrepareEnvironment(activity.applicationContext, activity.application) |
|||
|
|||
// 创建引擎实例 |
|||
engine = SpeechEngineGenerator.getInstance() |
|||
engineHandler = engine?.createEngine() ?: 0 |
|||
|
|||
if (engineHandler == 0L) { |
|||
Log.e(TAG, "Failed to create engine instance") |
|||
result.error("CREATE_ERROR", "Failed to create engine instance", null) |
|||
return |
|||
} |
|||
|
|||
// 设置引擎类型 |
|||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_ENGINE_NAME_STRING, SpeechEngineDefines.ASR_ENGINE) |
|||
|
|||
// 设置用户ID(必填) |
|||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_UID_STRING, "deep_voice_user") |
|||
|
|||
// 设置设备ID(选填) |
|||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_DEVICE_ID_STRING, "deep_voice_device") |
|||
|
|||
// 设置日志级别 |
|||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_LOG_LEVEL_STRING, SpeechEngineDefines.LOG_LEVEL_WARN) |
|||
|
|||
// 禁用日志文件,避免权限问题 |
|||
engine?.setOptionBoolean(engineHandler, "enable_log_file", false) |
|||
|
|||
// 设置应用 ID 和访问密钥 |
|||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_APP_ID_STRING, appKey) |
|||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_APP_TOKEN_STRING, "Bearer;$accessKey") |
|||
|
|||
// 设置网络参数 |
|||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_ASR_ADDRESS_STRING, "wss://openspeech.bytedance.com") |
|||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_ASR_URI_STRING, "/api/v2/asr") |
|||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_ASR_CLUSTER_STRING, cluster) |
|||
|
|||
// 设置音频来源为内置录音机 |
|||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_RECORDER_TYPE_STRING, SpeechEngineDefines.RECORDER_TYPE_RECORDER) |
|||
|
|||
// 启用音量获取 |
|||
engine?.setOptionBoolean(engineHandler, SpeechEngineDefines.PARAMS_KEY_ENABLE_GET_VOLUME_BOOL, true) |
|||
|
|||
// 设置最大录音时长(60秒) |
|||
engine?.setOptionInt(engineHandler, SpeechEngineDefines.PARAMS_KEY_VAD_MAX_SPEECH_DURATION_INT, 60000) |
|||
|
|||
// 控制识别效果 |
|||
engine?.setOptionBoolean(engineHandler, SpeechEngineDefines.PARAMS_KEY_ASR_SHOW_NLU_PUNC_BOOL, true) // 返回标点符号 |
|||
engine?.setOptionBoolean(engineHandler, SpeechEngineDefines.PARAMS_KEY_ASR_AUTO_STOP_BOOL, true) // 开启自动判停 |
|||
|
|||
// 设置结果返回形式为增量返回 |
|||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_ASR_RESULT_TYPE_STRING, SpeechEngineDefines.ASR_RESULT_TYPE_SINGLE) |
|||
|
|||
// 设置上下文 |
|||
engine?.setContext(activity.applicationContext) |
|||
|
|||
// 设置监听器 |
|||
engine?.setListener(this) |
|||
|
|||
// 初始化引擎 |
|||
Log.i(TAG, "Calling initEngine()") |
|||
val ret = engine?.initEngine(engineHandler) |
|||
Log.i(TAG, "initEngine() returned: $ret") |
|||
|
|||
if (ret == SpeechEngineDefines.ERR_NO_ERROR) { |
|||
isInitialized = true |
|||
Log.i(TAG, "Volcengine Speech Engine initialized successfully") |
|||
result.success("Engine initialized") |
|||
|
|||
// 发送状态更新到 Flutter |
|||
sendEvent("status", "initialized") |
|||
} else { |
|||
Log.e(TAG, "Engine initialization failed with error code: $ret") |
|||
result.error("INIT_ERROR", "Engine initialization failed with error code: $ret", null) |
|||
} |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "Initialization failed", e) |
|||
result.error("INIT_ERROR", e.message, null) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 启动语音识别 |
|||
*/ |
|||
fun startRecognition(result: Result) { |
|||
try { |
|||
if (!isInitialized) { |
|||
Log.e(TAG, "Cannot start recognition: Engine not initialized") |
|||
result.error("NOT_INITIALIZED", "Speech engine is not initialized", null) |
|||
return |
|||
} |
|||
|
|||
Log.i(TAG, "Starting speech recognition") |
|||
|
|||
// 先调用同步停止,避免SDK内部异步线程带来的问题 |
|||
engine?.sendDirective(engineHandler, SpeechEngineDefines.DIRECTIVE_SYNC_STOP_ENGINE, "") |
|||
|
|||
// 启动引擎 |
|||
val ret = engine?.sendDirective(engineHandler, SpeechEngineDefines.DIRECTIVE_START_ENGINE, "") |
|||
Log.d(TAG, "Start engine directive returned: $ret") |
|||
|
|||
if (ret == SpeechEngineDefines.ERR_NO_ERROR) { |
|||
Log.i(TAG, "Successfully started recognition") |
|||
result.success("Recognition started") |
|||
// 状态会通过回调更新 |
|||
} else { |
|||
Log.e(TAG, "Start recognition failed with error code: $ret") |
|||
result.error("START_ERROR", "Start recognition failed with error code: $ret", null) |
|||
} |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "Start recognition failed", e) |
|||
result.error("START_ERROR", e.message, null) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 停止语音识别 |
|||
*/ |
|||
fun stopRecognition(result: Result) { |
|||
try { |
|||
if (!isInitialized) { |
|||
Log.e(TAG, "Cannot stop recognition: Engine not initialized") |
|||
result.error("NOT_INITIALIZED", "Speech engine is not initialized", null) |
|||
return |
|||
} |
|||
|
|||
Log.i(TAG, "Stopping speech recognition") |
|||
|
|||
// 先发送音频输入完成指令 |
|||
val finishRet = engine?.sendDirective(engineHandler, SpeechEngineDefines.DIRECTIVE_FINISH_TALKING, "") |
|||
Log.d(TAG, "Finish talking directive returned: $finishRet") |
|||
|
|||
// 然后停止引擎 |
|||
val stopRet = engine?.sendDirective(engineHandler, SpeechEngineDefines.DIRECTIVE_STOP_ENGINE, "") |
|||
Log.d(TAG, "Stop engine directive returned: $stopRet") |
|||
|
|||
if (finishRet == SpeechEngineDefines.ERR_NO_ERROR || stopRet == SpeechEngineDefines.ERR_NO_ERROR) { |
|||
Log.i(TAG, "Successfully stopped recognition") |
|||
result.success("Recognition stopped") |
|||
// 状态会通过回调更新 |
|||
} else { |
|||
Log.e(TAG, "Stop recognition failed with error codes: finish=$finishRet, stop=$stopRet") |
|||
result.error("STOP_ERROR", "Stop recognition failed", null) |
|||
} |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "Stop recognition failed", e) |
|||
result.error("STOP_ERROR", e.message, null) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 释放资源 |
|||
*/ |
|||
fun dispose() { |
|||
try { |
|||
if (engineHandler != 0L) { |
|||
engine?.destroyEngine(engineHandler) |
|||
engineHandler = 0 |
|||
} |
|||
engine = null |
|||
isInitialized = false |
|||
Log.i(TAG, "Volcengine Speech Engine released") |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "Error releasing engine", e) |
|||
} |
|||
} |
|||
|
|||
// ------------------ |
|||
// 发送事件到 Flutter |
|||
// ------------------ |
|||
|
|||
private fun sendEvent(eventName: String, data: Any) { |
|||
try { |
|||
val eventData = JSONObject().apply { |
|||
put("event", eventName) |
|||
put("data", data) |
|||
} |
|||
|
|||
eventSink?.success(eventData.toString()) |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "Error sending event", e) |
|||
} |
|||
} |
|||
|
|||
// ------------------ |
|||
// SpeechListener 接口回调 |
|||
// ------------------ |
|||
|
|||
/** |
|||
* 处理语音消息回调 |
|||
* 这是 SpeechListener 接口的必须实现方法 |
|||
*/ |
|||
override fun onSpeechMessage(messageType: Int, messageData: ByteArray?, messageLength: Int) { |
|||
try { |
|||
Log.d(TAG, "Received speech message type: $messageType, length: $messageLength") |
|||
|
|||
if (messageData != null) { |
|||
val dataString = try { |
|||
String(messageData, 0, messageLength) |
|||
} catch (e: Exception) { |
|||
"Unable to convert to string: ${e.message}" |
|||
} |
|||
Log.d(TAG, "Message data: $dataString") |
|||
} |
|||
|
|||
when (messageType) { |
|||
// 引擎启动成功 |
|||
SpeechEngineDefines.MESSAGE_TYPE_ENGINE_START -> { |
|||
Log.i(TAG, "Engine started successfully") |
|||
sendEvent("status", "listening") |
|||
} |
|||
|
|||
// 引擎关闭 |
|||
SpeechEngineDefines.MESSAGE_TYPE_ENGINE_STOP -> { |
|||
Log.i(TAG, "Engine stopped") |
|||
sendEvent("status", "idle") |
|||
} |
|||
|
|||
// 错误信息 |
|||
SpeechEngineDefines.MESSAGE_TYPE_ENGINE_ERROR -> { |
|||
messageData?.let { |
|||
val errorJson = String(it, 0, messageLength) |
|||
Log.e(TAG, "Engine error: $errorJson") |
|||
|
|||
try { |
|||
val jsonObject = JSONObject(errorJson) |
|||
val errorCode = jsonObject.optInt("err_code", -1) |
|||
val errorMsg = jsonObject.optString("err_msg", "Unknown error") |
|||
val reqId = jsonObject.optString("req_id", "") |
|||
|
|||
Log.e(TAG, "Error details: code=$errorCode, message=$errorMsg, reqId=$reqId") |
|||
|
|||
// 通知 Flutter 识别出错 |
|||
val errorData = JSONObject() |
|||
errorData.put("code", errorCode) |
|||
errorData.put("message", errorMsg) |
|||
errorData.put("reqId", reqId) |
|||
|
|||
sendEvent("error", errorData.toString()) |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "Error parsing error JSON", e) |
|||
sendEvent("error", "Error code: Unknown") |
|||
} |
|||
} |
|||
} |
|||
|
|||
// VAD 状态变化消息 |
|||
SpeechEngineDefines.MESSAGE_TYPE_VAD_STATE -> { |
|||
val state = messageData?.let { String(it, 0, messageLength).toIntOrNull() } ?: -1 |
|||
Log.i(TAG, "VAD state changed: $state") |
|||
|
|||
val vadState = when (state) { |
|||
0 -> "silence" |
|||
1 -> "speech" |
|||
else -> "unknown" |
|||
} |
|||
|
|||
sendEvent("vad", vadState) |
|||
} |
|||
|
|||
// 中间识别结果 |
|||
SpeechEngineDefines.MESSAGE_TYPE_PARTIAL_RESULT -> { |
|||
messageData?.let { |
|||
val resultJson = String(it, 0, messageLength) |
|||
Log.d(TAG, "Partial recognition result: $resultJson") |
|||
|
|||
try { |
|||
val jsonObject = JSONObject(resultJson) |
|||
|
|||
if (jsonObject.has("result")) { |
|||
val resultArray = jsonObject.getJSONArray("result") |
|||
if (resultArray.length() > 0) { |
|||
val result = resultArray.getJSONObject(0) |
|||
val text = result.optString("text", "") |
|||
|
|||
if (text.isNotEmpty()) { |
|||
Log.i(TAG, "Partial recognition text: $text") |
|||
|
|||
// 发送识别结果到 Flutter |
|||
val resultData = JSONObject() |
|||
resultData.put("text", text) |
|||
resultData.put("isFinal", false) |
|||
|
|||
sendEvent("result", resultData.toString()) |
|||
} |
|||
} |
|||
} |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "Error parsing partial result JSON", e) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 最终识别结果 |
|||
SpeechEngineDefines.MESSAGE_TYPE_FINAL_RESULT -> { |
|||
messageData?.let { |
|||
val resultJson = String(it, 0, messageLength) |
|||
Log.d(TAG, "Final recognition result: $resultJson") |
|||
|
|||
try { |
|||
val jsonObject = JSONObject(resultJson) |
|||
|
|||
if (jsonObject.has("result")) { |
|||
val resultArray = jsonObject.getJSONArray("result") |
|||
if (resultArray.length() > 0) { |
|||
val result = resultArray.getJSONObject(0) |
|||
val text = result.optString("text", "") |
|||
|
|||
if (text.isNotEmpty()) { |
|||
Log.i(TAG, "Final recognition text: $text") |
|||
|
|||
// 发送识别结果到 Flutter |
|||
val resultData = JSONObject() |
|||
resultData.put("text", text) |
|||
resultData.put("isFinal", true) |
|||
|
|||
sendEvent("result", resultData.toString()) |
|||
} |
|||
} |
|||
} |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "Error parsing final result JSON", e) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 当前音量 |
|||
SpeechEngineDefines.MESSAGE_TYPE_VOLUME -> { |
|||
val volume = messageData?.let { String(it, 0, messageLength).toFloatOrNull() } ?: 0f |
|||
// 将 [0-1] 的音量值转换为 [0-100] 的整数 |
|||
val volumeInt = (volume * 100).toInt().coerceIn(0, 100) |
|||
Log.d(TAG, "Volume level: $volume (normalized: $volumeInt)") |
|||
|
|||
// 发送音量变化事件到 Flutter |
|||
sendEvent("volume", volumeInt) |
|||
} |
|||
|
|||
else -> { |
|||
Log.d(TAG, "Received unknown message type: $messageType") |
|||
// 尝试解析未知消息类型的数据 |
|||
messageData?.let { |
|||
try { |
|||
val dataString = String(it, 0, messageLength) |
|||
Log.d(TAG, "Unknown message data: $dataString") |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "Error parsing unknown message data", e) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "Error processing speech message", e) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 检查引擎能力和支持的指令 |
|||
*/ |
|||
fun checkEngineCapabilities(result: Result) { |
|||
try { |
|||
Log.i(TAG, "Checking engine capabilities") |
|||
|
|||
if (engine == null) { |
|||
Log.e(TAG, "Engine is null, cannot check capabilities") |
|||
result.error("ENGINE_NULL", "Engine is null", null) |
|||
return |
|||
} |
|||
|
|||
// 获取引擎版本信息 |
|||
val version = engine?.getVersion() ?: "Unknown" |
|||
Log.i(TAG, "Engine version: $version") |
|||
|
|||
// 创建能力信息对象 |
|||
val capabilities = JSONObject() |
|||
capabilities.put("version", version) |
|||
|
|||
// 列出支持的指令 |
|||
val supportedDirectives = JSONObject() |
|||
supportedDirectives.put("DIRECTIVE_START_ENGINE", SpeechEngineDefines.DIRECTIVE_START_ENGINE) |
|||
supportedDirectives.put("DIRECTIVE_STOP_ENGINE", SpeechEngineDefines.DIRECTIVE_STOP_ENGINE) |
|||
supportedDirectives.put("DIRECTIVE_FINISH_TALKING", SpeechEngineDefines.DIRECTIVE_FINISH_TALKING) |
|||
supportedDirectives.put("DIRECTIVE_SYNC_STOP_ENGINE", SpeechEngineDefines.DIRECTIVE_SYNC_STOP_ENGINE) |
|||
supportedDirectives.put("DIRECTIVE_UPDATE_ASR_HOTWORDS", SpeechEngineDefines.DIRECTIVE_UPDATE_ASR_HOTWORDS) |
|||
|
|||
capabilities.put("supported_directives", supportedDirectives) |
|||
|
|||
// 列出支持的消息类型 |
|||
val supportedMessageTypes = JSONObject() |
|||
supportedMessageTypes.put("MESSAGE_TYPE_ENGINE_START", SpeechEngineDefines.MESSAGE_TYPE_ENGINE_START) |
|||
supportedMessageTypes.put("MESSAGE_TYPE_ENGINE_STOP", SpeechEngineDefines.MESSAGE_TYPE_ENGINE_STOP) |
|||
supportedMessageTypes.put("MESSAGE_TYPE_ENGINE_ERROR", SpeechEngineDefines.MESSAGE_TYPE_ENGINE_ERROR) |
|||
supportedMessageTypes.put("MESSAGE_TYPE_PARTIAL_RESULT", SpeechEngineDefines.MESSAGE_TYPE_PARTIAL_RESULT) |
|||
supportedMessageTypes.put("MESSAGE_TYPE_FINAL_RESULT", SpeechEngineDefines.MESSAGE_TYPE_FINAL_RESULT) |
|||
supportedMessageTypes.put("MESSAGE_TYPE_VOLUME", SpeechEngineDefines.MESSAGE_TYPE_VOLUME) |
|||
supportedMessageTypes.put("MESSAGE_TYPE_VAD_STATE_CHANGED", SpeechEngineDefines.MESSAGE_TYPE_VAD_STATE) |
|||
|
|||
capabilities.put("supported_message_types", supportedMessageTypes) |
|||
|
|||
// 返回结果 |
|||
result.success(capabilities.toString()) |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "Error checking engine capabilities", e) |
|||
result.error("CHECK_ERROR", e.message, null) |
|||
} |
|||
} |
|||
} |
|||
@ -1,391 +1,281 @@ |
|||
import 'dart:async'; |
|||
import 'dart:convert'; |
|||
import 'package:flutter/services.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:flutter/foundation.dart'; |
|||
import 'package:flutter_dotenv/flutter_dotenv.dart'; |
|||
import 'dart:developer' as developer; |
|||
|
|||
/// 火山语音识别服务 |
|||
/// 负责与原生层的火山语音识别引擎交互 |
|||
class VoiceRecognitionService extends GetxService { |
|||
// 方法通道,用于调用原生方法 |
|||
static const MethodChannel _methodChannel = |
|||
MethodChannel('com.example.deep_voice/speech_recognition'); |
|||
|
|||
// 事件通道,用于接收原生层的事件 |
|||
static const EventChannel _eventChannel = |
|||
EventChannel('com.example.deep_voice/speech_recognition_events'); |
|||
|
|||
// 状态变量 |
|||
final isInitialized = false.obs; |
|||
final isListening = false.obs; |
|||
final isConnecting = true.obs; |
|||
|
|||
// 识别结果 |
|||
final recognizedText = ''.obs; |
|||
|
|||
// 回调函数 |
|||
Function(String)? onRecognitionResult; |
|||
Function(String)? onError; |
|||
Function()? onRecognitionComplete; |
|||
Function(String)? onConnectionStatusChanged; |
|||
Function(String)? onSentenceComplete; |
|||
|
|||
// 事件流订阅 |
|||
StreamSubscription? _eventSubscription; |
|||
|
|||
// 火山语音识别的 AppKey 和 AccessKey |
|||
late final String _appKey; |
|||
late final String _accessKey; |
|||
late final String _cluster; |
|||
/// 微软语音识别服务 |
|||
/// |
|||
/// 该服务提供了通过平台通道与 Android 上的 Microsoft Speech SDK 交互的接口 |
|||
class VoiceRecognitionService { |
|||
static const MethodChannel _channel = MethodChannel('com.example.deep_voice/speech_recognition'); |
|||
static const EventChannel _eventChannel = EventChannel('com.example.deep_voice/speech_recognition_events'); |
|||
|
|||
@override |
|||
void onInit() { |
|||
developer.log('VoiceRecognitionService: 初始化服务', name: 'VoiceRecognition'); |
|||
super.onInit(); |
|||
_loadCredentials(); |
|||
_setupEventListener(); |
|||
} |
|||
bool _isInitialized = false; |
|||
late final String _subscriptionKey; |
|||
late final String _serviceRegion; |
|||
|
|||
/// 加载火山语音识别的凭证 |
|||
void _loadCredentials() { |
|||
try { |
|||
_appKey = dotenv.env['VOLC_SPEECH_APP_KEY'] ?? ''; |
|||
_accessKey = dotenv.env['VOLC_SPEECH_ACCESS_KEY'] ?? ''; |
|||
_cluster = dotenv.env['VOLC_SPEECH_CLUSTER'] ?? 'volcano_asr'; |
|||
// 连续识别相关 |
|||
bool _isContinuousRecognitionActive = false; |
|||
StreamController<RecognitionEvent>? _eventStreamController; |
|||
StreamSubscription? _eventSubscription; |
|||
|
|||
developer.log('VoiceRecognitionService: 加载凭证 - AppKey: ${_appKey.isNotEmpty ? '已设置' : '未设置'}, AccessKey: ${_accessKey.isNotEmpty ? '已设置' : '未设置'}, Cluster: $_cluster', name: 'VoiceRecognition'); |
|||
// 公开的事件流 |
|||
Stream<RecognitionEvent>? _recognitionStream; |
|||
Stream<RecognitionEvent>? get recognitionStream => _recognitionStream; |
|||
|
|||
if (_appKey.isEmpty || _accessKey.isEmpty) { |
|||
developer.log('VoiceRecognitionService: 警告 - 火山语音识别凭证未设置,请在 .env 文件中设置 VOLC_SPEECH_APP_KEY 和 VOLC_SPEECH_ACCESS_KEY', name: 'VoiceRecognition'); |
|||
// 最新的识别结果 |
|||
String _latestRecognizedText = ''; |
|||
String get latestRecognizedText => _latestRecognizedText; |
|||
|
|||
VoiceRecognitionService() { |
|||
_loadConfig(); |
|||
} |
|||
} catch (e, stack) { |
|||
developer.log('VoiceRecognitionService: 加载火山语音识别凭证失败: $e', name: 'VoiceRecognition'); |
|||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|||
|
|||
/// 从环境变量加载配置 |
|||
void _loadConfig() { |
|||
_subscriptionKey = dotenv.env['AZURE_SPEECH_KEY'] ?? ''; |
|||
_serviceRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? ''; |
|||
|
|||
if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) { |
|||
throw Exception('未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION'); |
|||
} |
|||
} |
|||
|
|||
/// 设置事件监听器 |
|||
void _setupEventListener() { |
|||
/// 初始化微软语音识别 SDK |
|||
/// |
|||
/// 返回 true 表示初始化成功,否则抛出 PlatformException |
|||
Future<bool> initialize() async { |
|||
if (_isInitialized) return true; |
|||
|
|||
try { |
|||
developer.log('VoiceRecognitionService: 设置事件监听器', name: 'VoiceRecognition'); |
|||
_eventSubscription = _eventChannel.receiveBroadcastStream().listen( |
|||
(dynamic event) { |
|||
_handleEvent(event.toString()); |
|||
}, |
|||
onError: (dynamic error) { |
|||
developer.log('VoiceRecognitionService: 事件通道错误: $error', name: 'VoiceRecognition'); |
|||
if (onError != null) { |
|||
onError!('事件通道错误: $error'); |
|||
final bool result = await _channel.invokeMethod('initialize', { |
|||
'subscriptionKey': _subscriptionKey, |
|||
'serviceRegion': _serviceRegion, |
|||
}); |
|||
_isInitialized = result; |
|||
return result; |
|||
} on PlatformException catch (e) { |
|||
print('语音识别初始化失败: ${e.message}'); |
|||
_isInitialized = false; |
|||
throw e; |
|||
} |
|||
}, |
|||
); |
|||
} catch (e, stack) { |
|||
developer.log('VoiceRecognitionService: 设置事件监听器失败: $e', name: 'VoiceRecognition'); |
|||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|||
} |
|||
|
|||
/// 执行一次性语音识别 |
|||
/// |
|||
/// 返回识别的文本,如果识别失败则抛出 PlatformException |
|||
Future<String> recognizeSpeech() async { |
|||
if (!_isInitialized) { |
|||
throw Exception('语音识别服务未初始化,请先调用 initialize()'); |
|||
} |
|||
|
|||
/// 处理来自原生层的事件 |
|||
void _handleEvent(String eventJson) { |
|||
try { |
|||
developer.log('VoiceRecognitionService: 收到原始事件数据: $eventJson', name: 'VoiceRecognition'); |
|||
|
|||
final eventData = jsonDecode(eventJson); |
|||
final eventName = eventData['event']; |
|||
final data = eventData['data']; |
|||
|
|||
developer.log('VoiceRecognitionService: 收到事件: $eventName, 数据: $data', name: 'VoiceRecognition'); |
|||
|
|||
switch (eventName) { |
|||
case 'status': |
|||
_handleStatusEvent(data.toString()); |
|||
break; |
|||
case 'result': |
|||
_handleResultEvent(data.toString()); |
|||
break; |
|||
case 'error': |
|||
_handleErrorEvent(data.toString()); |
|||
break; |
|||
case 'vad': |
|||
_handleVadEvent(data.toString()); |
|||
break; |
|||
default: |
|||
developer.log('VoiceRecognitionService: 未知事件类型: $eventName', name: 'VoiceRecognition'); |
|||
break; |
|||
final String result = await _channel.invokeMethod('recognizeOnce'); |
|||
return result; |
|||
} on PlatformException catch (e) { |
|||
print('语音识别失败: ${e.message}'); |
|||
throw e; |
|||
} |
|||
} catch (e, stack) { |
|||
developer.log('VoiceRecognitionService: 处理事件失败: $e', name: 'VoiceRecognition'); |
|||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|||
} |
|||
} |
|||
|
|||
/// 处理状态事件 |
|||
void _handleStatusEvent(String status) { |
|||
developer.log('VoiceRecognitionService: 状态变更: $status', name: 'VoiceRecognition'); |
|||
|
|||
switch (status) { |
|||
case 'initialized': |
|||
isInitialized.value = true; |
|||
isConnecting.value = false; |
|||
developer.log('VoiceRecognitionService: 引擎初始化完成', name: 'VoiceRecognition'); |
|||
break; |
|||
case 'listening': |
|||
isListening.value = true; |
|||
developer.log('VoiceRecognitionService: 开始监听', name: 'VoiceRecognition'); |
|||
break; |
|||
case 'idle': |
|||
isListening.value = false; |
|||
developer.log('VoiceRecognitionService: 停止监听', name: 'VoiceRecognition'); |
|||
break; |
|||
/// 开始连续语音识别 |
|||
/// |
|||
/// 返回一个包含识别事件的流,如果开始失败则抛出 PlatformException |
|||
Future<Stream<RecognitionEvent>> startContinuousRecognition() async { |
|||
if (!_isInitialized) { |
|||
throw Exception('语音识别服务未初始化,请先调用 initialize()'); |
|||
} |
|||
|
|||
if (onConnectionStatusChanged != null) { |
|||
onConnectionStatusChanged!(status); |
|||
} |
|||
if (_isContinuousRecognitionActive) { |
|||
throw Exception('连续语音识别已经在进行中'); |
|||
} |
|||
|
|||
/// 处理识别结果事件 |
|||
void _handleResultEvent(String resultJson) { |
|||
try { |
|||
final resultData = jsonDecode(resultJson); |
|||
final text = resultData['text']; |
|||
final isFinal = resultData['isFinal']; |
|||
|
|||
developer.log('VoiceRecognitionService: 识别结果: $text, 是否最终结果: $isFinal', name: 'VoiceRecognition'); |
|||
// 创建事件流控制器 |
|||
_eventStreamController = StreamController<RecognitionEvent>.broadcast(); |
|||
|
|||
// 更新识别文本 |
|||
recognizedText.value = text; |
|||
// 设置事件监听 |
|||
_eventSubscription = _eventChannel |
|||
.receiveBroadcastStream() |
|||
.listen(_handleRecognitionEvent, onError: _handleRecognitionError); |
|||
|
|||
// 调用回调函数 |
|||
if (onRecognitionResult != null) { |
|||
onRecognitionResult!(text); |
|||
} |
|||
// 开始连续识别 |
|||
final bool result = await _channel.invokeMethod('startContinuousRecognition'); |
|||
_isContinuousRecognitionActive = result; |
|||
|
|||
// 如果是最终结果,调用完成回调 |
|||
if (isFinal && text.isNotEmpty) { |
|||
developer.log('VoiceRecognitionService: 句子识别完成: $text', name: 'VoiceRecognition'); |
|||
|
|||
if (onSentenceComplete != null) { |
|||
onSentenceComplete!(text); |
|||
} |
|||
// 设置公开的流 |
|||
_recognitionStream = _eventStreamController!.stream; |
|||
|
|||
if (onRecognitionComplete != null) { |
|||
onRecognitionComplete!(); |
|||
return _recognitionStream!; |
|||
} on PlatformException catch (e) { |
|||
print('开始连续语音识别失败: ${e.message}'); |
|||
_cleanupEventStream(); |
|||
throw e; |
|||
} |
|||
} |
|||
} catch (e, stack) { |
|||
developer.log('VoiceRecognitionService: 处理识别结果失败: $e', name: 'VoiceRecognition'); |
|||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|||
|
|||
/// 停止连续语音识别 |
|||
/// |
|||
/// 返回 true 表示停止成功,否则抛出 PlatformException |
|||
Future<bool> stopContinuousRecognition() async { |
|||
if (!_isInitialized) { |
|||
throw Exception('语音识别服务未初始化,请先调用 initialize()'); |
|||
} |
|||
|
|||
if (!_isContinuousRecognitionActive) { |
|||
return true; // 已经停止,直接返回成功 |
|||
} |
|||
|
|||
/// 处理错误事件 |
|||
void _handleErrorEvent(String errorJson) { |
|||
try { |
|||
final errorData = jsonDecode(errorJson); |
|||
final errorMessage = errorData['message']; |
|||
final errorCode = errorData['code'] ?? 'unknown'; |
|||
final bool result = await _channel.invokeMethod('stopContinuousRecognition'); |
|||
_isContinuousRecognitionActive = !result; |
|||
|
|||
developer.log('VoiceRecognitionService: 语音识别错误: 代码=$errorCode, 消息=$errorMessage', name: 'VoiceRecognition', error: errorMessage); |
|||
// 清理事件流 |
|||
_cleanupEventStream(); |
|||
|
|||
if (onError != null) { |
|||
onError!(errorMessage); |
|||
} |
|||
} catch (e, stack) { |
|||
developer.log('VoiceRecognitionService: 处理错误事件失败: $e', name: 'VoiceRecognition'); |
|||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|||
return result; |
|||
} on PlatformException catch (e) { |
|||
print('停止连续语音识别失败: ${e.message}'); |
|||
throw e; |
|||
} |
|||
} |
|||
|
|||
/// 处理 VAD 事件 |
|||
void _handleVadEvent(String vadState) { |
|||
// 可以根据需要处理 VAD 状态变化 |
|||
developer.log('VoiceRecognitionService: VAD 状态: $vadState', name: 'VoiceRecognition'); |
|||
/// 检查连续识别是否活跃 |
|||
bool isContinuousRecognitionActive() { |
|||
return _isContinuousRecognitionActive; |
|||
} |
|||
|
|||
/// 检查引擎能力和支持的指令 |
|||
Future<void> checkEngineCapabilities() async { |
|||
try { |
|||
developer.log('VoiceRecognitionService: 检查引擎能力', name: 'VoiceRecognition'); |
|||
/// 处理来自原生端的识别事件 |
|||
void _handleRecognitionEvent(dynamic event) { |
|||
if (event is! Map) return; |
|||
|
|||
// 检查是否已初始化 |
|||
if (!isInitialized.value) { |
|||
developer.log('VoiceRecognitionService: 引擎未初始化,尝试初始化', name: 'VoiceRecognition'); |
|||
await initialize(); |
|||
} |
|||
final Map<dynamic, dynamic> eventMap = event; |
|||
final String eventType = eventMap['eventType'] as String? ?? ''; |
|||
|
|||
// 调用原生方法检查引擎能力 |
|||
final result = await _methodChannel.invokeMethod<String>('checkEngineCapabilities'); |
|||
developer.log('VoiceRecognitionService: 引擎能力检查结果: $result', name: 'VoiceRecognition'); |
|||
|
|||
if (result != null) { |
|||
try { |
|||
final capabilities = jsonDecode(result); |
|||
developer.log('VoiceRecognitionService: 引擎版本: ${capabilities['version']}', name: 'VoiceRecognition'); |
|||
|
|||
if (capabilities['supported_params'] != null) { |
|||
developer.log('VoiceRecognitionService: 支持的参数: ${capabilities['supported_params']}', name: 'VoiceRecognition'); |
|||
} |
|||
|
|||
if (capabilities['supported_directives'] != null) { |
|||
developer.log('VoiceRecognitionService: 支持的指令: ${capabilities['supported_directives']}', name: 'VoiceRecognition'); |
|||
} |
|||
} catch (e) { |
|||
developer.log('VoiceRecognitionService: 解析能力结果失败: $e', name: 'VoiceRecognition'); |
|||
} |
|||
} |
|||
} catch (e, stack) { |
|||
developer.log('VoiceRecognitionService: 检查引擎能力失败: $e', name: 'VoiceRecognition', error: e); |
|||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|||
} |
|||
} |
|||
switch (eventType) { |
|||
case 'finalResult': |
|||
final String text = eventMap['text'] as String? ?? ''; |
|||
_latestRecognizedText = text; |
|||
_eventStreamController?.add(RecognitionEvent( |
|||
type: RecognitionEventType.finalResult, |
|||
text: text, |
|||
)); |
|||
break; |
|||
|
|||
/// 初始化语音识别引擎 |
|||
Future<void> initialize() async { |
|||
try { |
|||
developer.log('VoiceRecognitionService: 开始初始化语音引擎', name: 'VoiceRecognition'); |
|||
isConnecting.value = true; |
|||
case 'intermediateResult': |
|||
final String text = eventMap['text'] as String? ?? ''; |
|||
_eventStreamController?.add(RecognitionEvent( |
|||
type: RecognitionEventType.intermediateResult, |
|||
text: text, |
|||
)); |
|||
break; |
|||
|
|||
// 检查凭证 |
|||
if (_appKey.isEmpty || _accessKey.isEmpty) { |
|||
developer.log('VoiceRecognitionService: 凭证未设置,无法初始化', name: 'VoiceRecognition', error: '火山语音识别凭证未设置'); |
|||
throw '火山语音识别凭证未设置'; |
|||
} |
|||
case 'sessionStarted': |
|||
_eventStreamController?.add(RecognitionEvent( |
|||
type: RecognitionEventType.sessionStarted, |
|||
)); |
|||
break; |
|||
|
|||
// 添加详细日志 |
|||
developer.log('VoiceRecognitionService: 使用凭证 AppKey: $_appKey, AccessKey: ${_accessKey.substring(0, 4)}***, Cluster: $_cluster', name: 'VoiceRecognition'); |
|||
case 'sessionStopped': |
|||
_isContinuousRecognitionActive = false; |
|||
_eventStreamController?.add(RecognitionEvent( |
|||
type: RecognitionEventType.sessionStopped, |
|||
)); |
|||
break; |
|||
|
|||
// 调用原生方法初始化引擎 |
|||
try { |
|||
final result = await _methodChannel.invokeMethod<String>( |
|||
'initialize', |
|||
{ |
|||
'appKey': _appKey, |
|||
'accessKey': _accessKey, |
|||
'cluster': _cluster, |
|||
}, |
|||
); |
|||
|
|||
developer.log('VoiceRecognitionService: 初始化结果: $result', name: 'VoiceRecognition'); |
|||
|
|||
// 初始化成功后,检查引擎能力 |
|||
await checkEngineCapabilities(); |
|||
} catch (e) { |
|||
developer.log('VoiceRecognitionService: 初始化方法调用失败: $e', name: 'VoiceRecognition', error: e); |
|||
case 'canceled': |
|||
_isContinuousRecognitionActive = false; |
|||
final String reason = eventMap['reason'] as String? ?? ''; |
|||
final String errorDetails = eventMap['errorDetails'] as String? ?? ''; |
|||
_eventStreamController?.add(RecognitionEvent( |
|||
type: RecognitionEventType.canceled, |
|||
error: '$reason: $errorDetails', |
|||
)); |
|||
break; |
|||
|
|||
// 尝试检查引擎是否已初始化 |
|||
try { |
|||
final isEngineInitialized = await _methodChannel.invokeMethod<bool>('isInitialized') ?? false; |
|||
if (isEngineInitialized) { |
|||
developer.log('VoiceRecognitionService: 引擎已经初始化,忽略错误', name: 'VoiceRecognition'); |
|||
isInitialized.value = true; |
|||
isConnecting.value = false; |
|||
|
|||
// 引擎已初始化,检查引擎能力 |
|||
await checkEngineCapabilities(); |
|||
return; |
|||
case 'error': |
|||
final String error = eventMap['error'] as String? ?? ''; |
|||
_eventStreamController?.add(RecognitionEvent( |
|||
type: RecognitionEventType.error, |
|||
error: error, |
|||
)); |
|||
break; |
|||
} |
|||
} catch (checkError) { |
|||
developer.log('VoiceRecognitionService: 检查引擎初始化状态失败: $checkError', name: 'VoiceRecognition'); |
|||
} |
|||
|
|||
// 如果检查失败或引擎未初始化,则重新抛出异常 |
|||
rethrow; |
|||
/// 处理识别事件流错误 |
|||
void _handleRecognitionError(Object error) { |
|||
_eventStreamController?.addError(error); |
|||
_cleanupEventStream(); |
|||
} |
|||
|
|||
// 初始化成功后,状态会通过事件通道更新 |
|||
} catch (e, stack) { |
|||
isConnecting.value = false; |
|||
developer.log('VoiceRecognitionService: 初始化语音识别引擎失败: $e', name: 'VoiceRecognition', error: e); |
|||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|||
|
|||
String errorMessage = '初始化失败: $e'; |
|||
|
|||
// 提供更具体的错误信息 |
|||
if (e.toString().contains('error code: -7')) { |
|||
errorMessage = '初始化失败: 无法创建日志目录,请检查应用权限'; |
|||
developer.log('VoiceRecognitionService: 错误代码 -7: 无法创建日志目录', name: 'VoiceRecognition'); |
|||
} else if (e.toString().contains('INIT_ERROR')) { |
|||
errorMessage = '初始化失败: 引擎初始化错误,请检查网络连接和凭证'; |
|||
developer.log('VoiceRecognitionService: INIT_ERROR: 引擎初始化错误', name: 'VoiceRecognition'); |
|||
} |
|||
/// 清理事件流资源 |
|||
void _cleanupEventStream() { |
|||
_eventSubscription?.cancel(); |
|||
_eventSubscription = null; |
|||
|
|||
if (onError != null) { |
|||
onError!(errorMessage); |
|||
} |
|||
rethrow; |
|||
} |
|||
_eventStreamController?.close(); |
|||
_eventStreamController = null; |
|||
|
|||
_recognitionStream = null; |
|||
_isContinuousRecognitionActive = false; |
|||
} |
|||
|
|||
/// 开始语音识别 |
|||
Future<void> startRecognition() async { |
|||
/// 释放资源 |
|||
Future<void> dispose() async { |
|||
try { |
|||
developer.log('VoiceRecognitionService: 开始语音识别', name: 'VoiceRecognition'); |
|||
|
|||
// 检查是否已初始化 |
|||
if (!isInitialized.value) { |
|||
developer.log('VoiceRecognitionService: 引擎未初始化,尝试初始化', name: 'VoiceRecognition'); |
|||
await initialize(); |
|||
if (_isContinuousRecognitionActive) { |
|||
await stopContinuousRecognition(); |
|||
} |
|||
|
|||
developer.log('VoiceRecognitionService: 调用原生方法 startRecognition', name: 'VoiceRecognition'); |
|||
|
|||
// 调用原生方法开始识别 |
|||
final result = await _methodChannel.invokeMethod<String>('startRecognition'); |
|||
developer.log('VoiceRecognitionService: 开始识别结果: $result', name: 'VoiceRecognition'); |
|||
|
|||
// 状态会通过事件通道更新 |
|||
} catch (e, stack) { |
|||
developer.log('VoiceRecognitionService: 开始语音识别失败: $e', name: 'VoiceRecognition', error: e); |
|||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|||
|
|||
if (onError != null) { |
|||
onError!('开始识别失败: $e'); |
|||
await _channel.invokeMethod('dispose'); |
|||
_cleanupEventStream(); |
|||
_isInitialized = false; |
|||
} catch (e) { |
|||
print('释放语音识别资源失败: $e'); |
|||
} |
|||
rethrow; |
|||
} |
|||
} |
|||
|
|||
/// 停止语音识别 |
|||
Future<void> stopRecognition() async { |
|||
try { |
|||
developer.log('VoiceRecognitionService: 停止语音识别', name: 'VoiceRecognition'); |
|||
/// 识别事件类型 |
|||
enum RecognitionEventType { |
|||
/// 最终识别结果 |
|||
finalResult, |
|||
|
|||
// 检查是否已初始化 |
|||
if (!isInitialized.value) { |
|||
developer.log('VoiceRecognitionService: 引擎未初始化,无法停止识别', name: 'VoiceRecognition'); |
|||
return; |
|||
} |
|||
/// 中间识别结果(实时反馈) |
|||
intermediateResult, |
|||
|
|||
developer.log('VoiceRecognitionService: 调用原生方法 stopRecognition', name: 'VoiceRecognition'); |
|||
/// 会话开始 |
|||
sessionStarted, |
|||
|
|||
// 调用原生方法停止识别 |
|||
final result = await _methodChannel.invokeMethod<String>('stopRecognition'); |
|||
developer.log('VoiceRecognitionService: 停止识别结果: $result', name: 'VoiceRecognition'); |
|||
/// 会话结束 |
|||
sessionStopped, |
|||
|
|||
// 状态会通过事件通道更新 |
|||
} catch (e, stack) { |
|||
developer.log('VoiceRecognitionService: 停止语音识别失败: $e', name: 'VoiceRecognition', error: e); |
|||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|||
/// 识别取消 |
|||
canceled, |
|||
|
|||
if (onError != null) { |
|||
onError!('停止识别失败: $e'); |
|||
} |
|||
} |
|||
/// 识别错误 |
|||
error, |
|||
} |
|||
|
|||
@override |
|||
void onClose() { |
|||
developer.log('VoiceRecognitionService: 关闭服务', name: 'VoiceRecognition'); |
|||
/// 识别事件 |
|||
class RecognitionEvent { |
|||
/// 事件类型 |
|||
final RecognitionEventType type; |
|||
|
|||
// 取消事件订阅 |
|||
_eventSubscription?.cancel(); |
|||
/// 识别文本(仅在 finalResult 和 intermediateResult 类型中有效) |
|||
final String text; |
|||
|
|||
// 停止识别 |
|||
stopRecognition(); |
|||
/// 错误信息(仅在 error 和 canceled 类型中有效) |
|||
final String error; |
|||
|
|||
super.onClose(); |
|||
RecognitionEvent({ |
|||
required this.type, |
|||
this.text = '', |
|||
this.error = '', |
|||
}); |
|||
|
|||
@override |
|||
String toString() { |
|||
return 'RecognitionEvent{type: $type, text: $text, error: $error}'; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue