diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index f28f0f64a..2a0b701cc 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -1,4 +1,8 @@
-
+repositories {
+ google()
+ mavenCentral()
+ maven { url = uri("https://artifact.bytedance.com/repository/Volcengine/") }
+}
plugins {
id("com.android.application")
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index f46ae0c52..9bb05f38c 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -9,11 +9,22 @@
+
+
+
+
+
+
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 c81f6a651..2cac0769c 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
@@ -1,17 +1,106 @@
package com.example.deep_voice
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.Environment
+import android.provider.Settings
+import android.util.Log
import com.ryanheise.audioservice.AudioServiceActivity
import io.flutter.embedding.engine.FlutterEngine
+import io.flutter.plugin.common.MethodChannel
class MainActivity: AudioServiceActivity() {
private var volcSpeechBridge: VolcSpeechBridge? = null
+ private val TAG = "MainActivity"
+ private val MANAGE_STORAGE_REQUEST_CODE = 2002
+ private lateinit var permissionChannel: MethodChannel
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ Log.i(TAG, "MainActivity onCreate")
+ }
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
+ // 设置权限通道 - 只处理Android 11+的MANAGE_EXTERNAL_STORAGE权限
+ permissionChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.deep_voice/permissions")
+ permissionChannel.setMethodCallHandler { call, result ->
+ when (call.method) {
+ "openAppSettings" -> {
+ openAppSettings()
+ result.success(true)
+ }
+ "requestManageExternalStorage" -> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ requestManageExternalStorage()
+ result.success(true)
+ } else {
+ result.success(false)
+ }
+ }
+ "isExternalStorageManager" -> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ result.success(Environment.isExternalStorageManager())
+ } else {
+ result.success(true) // 在Android 11以下版本,返回true
+ }
+ }
+ else -> result.notImplemented()
+ }
+ }
+
+ // 准备火山语音识别环境
+ try {
+ Log.i(TAG, "Preparing Volcano Speech Engine environment")
+ com.bytedance.speech.speechengine.SpeechEngineGenerator.PrepareEnvironment(applicationContext, application)
+ Log.i(TAG, "Volcano Speech Engine environment prepared successfully")
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to prepare Volcano Speech Engine environment", e)
+ }
+
// 初始化火山语音识别桥接类
volcSpeechBridge = VolcSpeechBridge(this, flutterEngine.dartExecutor.binaryMessenger)
}
+
+ private fun requestManageExternalStorage() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ try {
+ Log.i(TAG, "Requesting MANAGE_EXTERNAL_STORAGE permission")
+ val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
+ intent.data = Uri.parse("package:$packageName")
+ startActivityForResult(intent, MANAGE_STORAGE_REQUEST_CODE)
+ } catch (e: Exception) {
+ Log.e(TAG, "Error requesting MANAGE_EXTERNAL_STORAGE", e)
+ // 如果上面的Intent失败,尝试打开通用的存储设置页面
+ val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
+ startActivityForResult(intent, MANAGE_STORAGE_REQUEST_CODE)
+ }
+ }
+ }
+
+ private fun openAppSettings() {
+ val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ intent.data = Uri.parse("package:$packageName")
+ startActivity(intent)
+ }
+
+ // 只处理MANAGE_EXTERNAL_STORAGE权限的结果
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ if (requestCode == MANAGE_STORAGE_REQUEST_CODE) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ val granted = Environment.isExternalStorageManager()
+ Log.i(TAG, "MANAGE_EXTERNAL_STORAGE permission: ${if (granted) "granted" else "denied"}")
+
+ // 通知Flutter层权限状态变化
+ permissionChannel.invokeMethod("manageExternalStorageResult", granted)
+ }
+ } else {
+ super.onActivityResult(requestCode, resultCode, data)
+ }
+ }
override fun onDestroy() {
// 释放火山语音识别资源
diff --git a/android/app/src/main/kotlin/com/example/deep_voice/VolcSpeechBridge.kt b/android/app/src/main/kotlin/com/example/deep_voice/VolcSpeechBridge.kt
index 3319f6cba..26c2db086 100644
--- a/android/app/src/main/kotlin/com/example/deep_voice/VolcSpeechBridge.kt
+++ b/android/app/src/main/kotlin/com/example/deep_voice/VolcSpeechBridge.kt
@@ -11,6 +11,7 @@ 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
@@ -27,13 +28,10 @@ class VolcSpeechBridge(
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 const val DIRECTIVE_START_RECOGNITION = 1
- private const val DIRECTIVE_STOP_RECOGNITION = 2
}
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
@@ -46,7 +44,8 @@ class VolcSpeechBridge(
"initialize" -> {
val appKey = call.argument("appKey") ?: ""
val accessKey = call.argument("accessKey") ?: ""
- initialize(appKey, accessKey, result)
+ val cluster = call.argument("cluster") ?: "volcano_asr"
+ initialize(appKey, accessKey, cluster, result)
}
"startRecognition" -> {
startRecognition(result)
@@ -57,6 +56,9 @@ class VolcSpeechBridge(
"isInitialized" -> {
result.success(isInitialized)
}
+ "checkEngineCapabilities" -> {
+ checkEngineCapabilities(result)
+ }
else -> {
result.notImplemented()
}
@@ -78,32 +80,75 @@ class VolcSpeechBridge(
/**
* 初始化火山语音识别引擎
*/
- fun initialize(appKey: String, accessKey: String, result: Result) {
+ 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()
- engine?.createEngine()
+ engineHandler = engine?.createEngine() ?: 0
- // 设置监听器
- engine?.setListener(this)
+ 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("app_id", appKey)
- engine?.setOptionString("access_key", accessKey)
+ engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_APP_ID_STRING, appKey)
+ engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_APP_TOKEN_STRING, "Bearer;$accessKey")
- // 设置语音识别参数
- engine?.setOptionBoolean("enable_punctuation", true) // 启用标点符号
- engine?.setOptionBoolean("enable_intermediate_result", true) // 启用中间结果
- engine?.setOptionBoolean("enable_vad", true) // 启用语音活动检测
+ // 设置网络参数
+ 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("language", "zh")
+ // 设置音频来源为内置录音机
+ 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)
// 初始化引擎
- val ret = engine?.initEngine()
+ 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")
@@ -127,17 +172,24 @@ class VolcSpeechBridge(
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")
- val ret = engine?.sendDirective(DIRECTIVE_START_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")
- // 发送状态更新到 Flutter
- sendEvent("status", "listening")
+ // 状态会通过回调更新
} else {
Log.e(TAG, "Start recognition failed with error code: $ret")
result.error("START_ERROR", "Start recognition failed with error code: $ret", null)
@@ -154,20 +206,28 @@ class VolcSpeechBridge(
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 ret = engine?.sendDirective(DIRECTIVE_STOP_RECOGNITION, "")
- if (ret == SpeechEngineDefines.ERR_NO_ERROR) {
+ // 先发送音频输入完成指令
+ 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")
- // 发送状态更新到 Flutter
- sendEvent("status", "idle")
+ // 状态会通过回调更新
} else {
- Log.e(TAG, "Stop recognition failed with error code: $ret")
- result.error("STOP_ERROR", "Stop recognition failed with error code: $ret", null)
+ 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)
@@ -180,7 +240,10 @@ class VolcSpeechBridge(
*/
fun dispose() {
try {
- engine?.destroyEngine()
+ if (engineHandler != 0L) {
+ engine?.destroyEngine(engineHandler)
+ engineHandler = 0
+ }
engine = null
isInitialized = false
Log.i(TAG, "Volcengine Speech Engine released")
@@ -216,36 +279,60 @@ class VolcSpeechBridge(
*/
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) {
- // 初始化完成消息
- 1 -> {
- Log.i(TAG, "Engine initialized successfully")
- isInitialized = true
-
- // 通知 Flutter 初始化完成
- methodResult?.success("Engine initialized")
- methodResult = null
-
- // 发送状态更新到 Flutter
- sendEvent("status", "initialized")
+ // 引擎启动成功
+ SpeechEngineDefines.MESSAGE_TYPE_ENGINE_START -> {
+ Log.i(TAG, "Engine started successfully")
+ sendEvent("status", "listening")
}
- // 错误消息
- 2 -> {
- val errorCode = messageData?.let { String(it, 0, messageLength).toIntOrNull() } ?: -1
- Log.e(TAG, "Engine error: $errorCode")
-
- // 通知 Flutter 识别出错
- val errorData = JSONObject().apply {
- put("code", errorCode)
- put("message", "Error code: $errorCode")
+ // 引擎关闭
+ 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")
+ }
}
-
- sendEvent("error", errorData.toString())
}
// VAD 状态变化消息
- 3 -> {
+ SpeechEngineDefines.MESSAGE_TYPE_VAD_STATE -> {
val state = messageData?.let { String(it, 0, messageLength).toIntOrNull() } ?: -1
Log.i(TAG, "VAD state changed: $state")
@@ -258,36 +345,149 @@ class VolcSpeechBridge(
sendEvent("vad", vadState)
}
- // 识别结果消息
- 4 -> {
+ // 中间识别结果
+ 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)
- val text = jsonObject.optString("text", "")
- val isFinal = jsonObject.optBoolean("is_final", false)
-
- Log.i(TAG, "Recognition result: $text, isFinal: $isFinal")
- // 发送识别结果到 Flutter
- val resultData = JSONObject().apply {
- put("text", text)
- put("isFinal", isFinal)
+ 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)
- sendEvent("result", resultData.toString())
+ 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 result JSON", e)
+ 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)
+ }
+ }
}
\ No newline at end of file
diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml
new file mode 100644
index 000000000..2986285a0
--- /dev/null
+++ b/android/app/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+ 127.0.0.1
+ localhost
+
+
\ No newline at end of file
diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts
index c1c2fa423..20cc3a2da 100644
--- a/android/settings.gradle.kts
+++ b/android/settings.gradle.kts
@@ -25,13 +25,4 @@ plugins {
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
}
-dependencyResolutionManagement {
- repositories {
- google()
- mavenCentral()
- maven { url = uri("https://artifact.bytedance.com/repository/Volcengine/") }
- gradlePluginPortal()
- }
-}
-
include(":app")
diff --git a/lib/data/services/voice_recognition_service.dart b/lib/data/services/voice_recognition_service.dart
index 35024f5fb..c69d63d5e 100644
--- a/lib/data/services/voice_recognition_service.dart
+++ b/lib/data/services/voice_recognition_service.dart
@@ -4,6 +4,7 @@ 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;
/// 火山语音识别服务
/// 负责与原生层的火山语音识别引擎交互
@@ -37,9 +38,11 @@ class VoiceRecognitionService extends GetxService {
// 火山语音识别的 AppKey 和 AccessKey
late final String _appKey;
late final String _accessKey;
+ late final String _cluster;
@override
void onInit() {
+ developer.log('VoiceRecognitionService: 初始化服务', name: 'VoiceRecognition');
super.onInit();
_loadCredentials();
_setupEventListener();
@@ -50,41 +53,51 @@ class VoiceRecognitionService extends GetxService {
try {
_appKey = dotenv.env['VOLC_SPEECH_APP_KEY'] ?? '';
_accessKey = dotenv.env['VOLC_SPEECH_ACCESS_KEY'] ?? '';
+ _cluster = dotenv.env['VOLC_SPEECH_CLUSTER'] ?? 'volcano_asr';
+
+ developer.log('VoiceRecognitionService: 加载凭证 - AppKey: ${_appKey.isNotEmpty ? '已设置' : '未设置'}, AccessKey: ${_accessKey.isNotEmpty ? '已设置' : '未设置'}, Cluster: $_cluster', name: 'VoiceRecognition');
if (_appKey.isEmpty || _accessKey.isEmpty) {
- debugPrint('警告: 火山语音识别凭证未设置,请在 .env 文件中设置 VOLC_SPEECH_APP_KEY 和 VOLC_SPEECH_ACCESS_KEY');
+ developer.log('VoiceRecognitionService: 警告 - 火山语音识别凭证未设置,请在 .env 文件中设置 VOLC_SPEECH_APP_KEY 和 VOLC_SPEECH_ACCESS_KEY', name: 'VoiceRecognition');
}
- } catch (e) {
- debugPrint('加载火山语音识别凭证失败: $e');
+ } catch (e, stack) {
+ developer.log('VoiceRecognitionService: 加载火山语音识别凭证失败: $e', name: 'VoiceRecognition');
+ developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
}
}
/// 设置事件监听器
void _setupEventListener() {
try {
+ developer.log('VoiceRecognitionService: 设置事件监听器', name: 'VoiceRecognition');
_eventSubscription = _eventChannel.receiveBroadcastStream().listen(
(dynamic event) {
_handleEvent(event.toString());
},
onError: (dynamic error) {
- debugPrint('事件通道错误: $error');
+ developer.log('VoiceRecognitionService: 事件通道错误: $error', name: 'VoiceRecognition');
if (onError != null) {
onError!('事件通道错误: $error');
}
},
);
- } catch (e) {
- debugPrint('设置事件监听器失败: $e');
+ } catch (e, stack) {
+ developer.log('VoiceRecognitionService: 设置事件监听器失败: $e', name: 'VoiceRecognition');
+ developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
}
}
/// 处理来自原生层的事件
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());
@@ -98,24 +111,33 @@ class VoiceRecognitionService extends GetxService {
case 'vad':
_handleVadEvent(data.toString());
break;
+ default:
+ developer.log('VoiceRecognitionService: 未知事件类型: $eventName', name: 'VoiceRecognition');
+ break;
}
- } catch (e) {
- debugPrint('处理事件失败: $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;
}
@@ -131,6 +153,8 @@ class VoiceRecognitionService extends GetxService {
final text = resultData['text'];
final isFinal = resultData['isFinal'];
+ developer.log('VoiceRecognitionService: 识别结果: $text, 是否最终结果: $isFinal', name: 'VoiceRecognition');
+
// 更新识别文本
recognizedText.value = text;
@@ -141,6 +165,8 @@ class VoiceRecognitionService extends GetxService {
// 如果是最终结果,调用完成回调
if (isFinal && text.isNotEmpty) {
+ developer.log('VoiceRecognitionService: 句子识别完成: $text', name: 'VoiceRecognition');
+
if (onSentenceComplete != null) {
onSentenceComplete!(text);
}
@@ -149,8 +175,9 @@ class VoiceRecognitionService extends GetxService {
onRecognitionComplete!();
}
}
- } catch (e) {
- debugPrint('处理识别结果失败: $e');
+ } catch (e, stack) {
+ developer.log('VoiceRecognitionService: 处理识别结果失败: $e', name: 'VoiceRecognition');
+ developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
}
}
@@ -159,50 +186,134 @@ class VoiceRecognitionService extends GetxService {
try {
final errorData = jsonDecode(errorJson);
final errorMessage = errorData['message'];
+ final errorCode = errorData['code'] ?? 'unknown';
- debugPrint('语音识别错误: $errorMessage');
+ developer.log('VoiceRecognitionService: 语音识别错误: 代码=$errorCode, 消息=$errorMessage', name: 'VoiceRecognition', error: errorMessage);
if (onError != null) {
onError!(errorMessage);
}
- } catch (e) {
- debugPrint('处理错误事件失败: $e');
+ } catch (e, stack) {
+ developer.log('VoiceRecognitionService: 处理错误事件失败: $e', name: 'VoiceRecognition');
+ developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
}
}
/// 处理 VAD 事件
void _handleVadEvent(String vadState) {
// 可以根据需要处理 VAD 状态变化
- debugPrint('VAD 状态: $vadState');
+ developer.log('VoiceRecognitionService: VAD 状态: $vadState', name: 'VoiceRecognition');
+ }
+
+ /// 检查引擎能力和支持的指令
+ Future checkEngineCapabilities() async {
+ try {
+ developer.log('VoiceRecognitionService: 检查引擎能力', name: 'VoiceRecognition');
+
+ // 检查是否已初始化
+ if (!isInitialized.value) {
+ developer.log('VoiceRecognitionService: 引擎未初始化,尝试初始化', name: 'VoiceRecognition');
+ await initialize();
+ }
+
+ // 调用原生方法检查引擎能力
+ final result = await _methodChannel.invokeMethod('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');
+ }
}
/// 初始化语音识别引擎
Future initialize() async {
try {
+ developer.log('VoiceRecognitionService: 开始初始化语音引擎', name: 'VoiceRecognition');
isConnecting.value = true;
// 检查凭证
if (_appKey.isEmpty || _accessKey.isEmpty) {
+ developer.log('VoiceRecognitionService: 凭证未设置,无法初始化', name: 'VoiceRecognition', error: '火山语音识别凭证未设置');
throw '火山语音识别凭证未设置';
}
- // 调用原生方法初始化引擎
- final result = await _methodChannel.invokeMethod(
- 'initialize',
- {
- 'appKey': _appKey,
- 'accessKey': _accessKey,
- },
- );
+ // 添加详细日志
+ developer.log('VoiceRecognitionService: 使用凭证 AppKey: $_appKey, AccessKey: ${_accessKey.substring(0, 4)}***, Cluster: $_cluster', name: 'VoiceRecognition');
- debugPrint('初始化结果: $result');
+ // 调用原生方法初始化引擎
+ try {
+ final result = await _methodChannel.invokeMethod(
+ '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);
+
+ // 尝试检查引擎是否已初始化
+ try {
+ final isEngineInitialized = await _methodChannel.invokeMethod('isInitialized') ?? false;
+ if (isEngineInitialized) {
+ developer.log('VoiceRecognitionService: 引擎已经初始化,忽略错误', name: 'VoiceRecognition');
+ isInitialized.value = true;
+ isConnecting.value = false;
+
+ // 引擎已初始化,检查引擎能力
+ await checkEngineCapabilities();
+ return;
+ }
+ } catch (checkError) {
+ developer.log('VoiceRecognitionService: 检查引擎初始化状态失败: $checkError', name: 'VoiceRecognition');
+ }
+
+ // 如果检查失败或引擎未初始化,则重新抛出异常
+ rethrow;
+ }
// 初始化成功后,状态会通过事件通道更新
- } catch (e) {
+ } catch (e, stack) {
isConnecting.value = false;
- debugPrint('初始化语音识别引擎失败: $e');
+ 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');
+ }
+
if (onError != null) {
- onError!('初始化失败: $e');
+ onError!(errorMessage);
}
rethrow;
}
@@ -211,18 +322,25 @@ class VoiceRecognitionService extends GetxService {
/// 开始语音识别
Future startRecognition() async {
try {
+ developer.log('VoiceRecognitionService: 开始语音识别', name: 'VoiceRecognition');
+
// 检查是否已初始化
if (!isInitialized.value) {
+ developer.log('VoiceRecognitionService: 引擎未初始化,尝试初始化', name: 'VoiceRecognition');
await initialize();
}
+ developer.log('VoiceRecognitionService: 调用原生方法 startRecognition', name: 'VoiceRecognition');
+
// 调用原生方法开始识别
final result = await _methodChannel.invokeMethod('startRecognition');
- debugPrint('开始识别结果: $result');
+ developer.log('VoiceRecognitionService: 开始识别结果: $result', name: 'VoiceRecognition');
// 状态会通过事件通道更新
- } catch (e) {
- debugPrint('开始语音识别失败: $e');
+ } catch (e, stack) {
+ developer.log('VoiceRecognitionService: 开始语音识别失败: $e', name: 'VoiceRecognition', error: e);
+ developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
+
if (onError != null) {
onError!('开始识别失败: $e');
}
@@ -233,18 +351,25 @@ class VoiceRecognitionService extends GetxService {
/// 停止语音识别
Future stopRecognition() async {
try {
+ developer.log('VoiceRecognitionService: 停止语音识别', name: 'VoiceRecognition');
+
// 检查是否已初始化
if (!isInitialized.value) {
+ developer.log('VoiceRecognitionService: 引擎未初始化,无法停止识别', name: 'VoiceRecognition');
return;
}
+ developer.log('VoiceRecognitionService: 调用原生方法 stopRecognition', name: 'VoiceRecognition');
+
// 调用原生方法停止识别
final result = await _methodChannel.invokeMethod('stopRecognition');
- debugPrint('停止识别结果: $result');
+ developer.log('VoiceRecognitionService: 停止识别结果: $result', name: 'VoiceRecognition');
// 状态会通过事件通道更新
- } catch (e) {
- debugPrint('停止语音识别失败: $e');
+ } catch (e, stack) {
+ developer.log('VoiceRecognitionService: 停止语音识别失败: $e', name: 'VoiceRecognition', error: e);
+ developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
+
if (onError != null) {
onError!('停止识别失败: $e');
}
@@ -253,6 +378,8 @@ class VoiceRecognitionService extends GetxService {
@override
void onClose() {
+ developer.log('VoiceRecognitionService: 关闭服务', name: 'VoiceRecognition');
+
// 取消事件订阅
_eventSubscription?.cancel();
diff --git a/lib/modules/profile/views/profile_view.dart b/lib/modules/profile/views/profile_view.dart
index 1336c4c68..fd22bf44a 100644
--- a/lib/modules/profile/views/profile_view.dart
+++ b/lib/modules/profile/views/profile_view.dart
@@ -4,6 +4,7 @@ import '../controllers/profile_controller.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../../data/services/volcano_tts_service.dart';
import '../../../core/widgets/common_bottom_nav.dart';
+import '../../speech_demo/speech_demo_page.dart';
class ProfileView extends GetView {
const ProfileView({Key? key}) : super(key: key);
@@ -109,6 +110,15 @@ class ProfileView extends GetView {
},
),
const Divider(),
+ _buildMenuItem(
+ title: '语音识别测试',
+ icon: Icons.mic,
+ subtitle: '测试语音识别功能',
+ onTap: () {
+ Get.to(() => const SpeechDemoPage());
+ },
+ ),
+ const Divider(),
_buildMenuItem(
title: '关于',
icon: Icons.info_outline,
diff --git a/lib/modules/speech_demo/speech_demo_page.dart b/lib/modules/speech_demo/speech_demo_page.dart
index 473265d81..f10857d2f 100644
--- a/lib/modules/speech_demo/speech_demo_page.dart
+++ b/lib/modules/speech_demo/speech_demo_page.dart
@@ -1,56 +1,406 @@
+import 'dart:async';
+import 'dart:developer' as developer;
+import 'dart:io';
+
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:get/get.dart';
+import 'package:permission_handler/permission_handler.dart';
+import 'package:device_info_plus/device_info_plus.dart';
+import 'package:path_provider/path_provider.dart';
+
import '../../data/services/voice_recognition_service.dart';
class SpeechDemoPage extends StatefulWidget {
const SpeechDemoPage({Key? key}) : super(key: key);
@override
- _SpeechDemoPageState createState() => _SpeechDemoPageState();
+ State createState() => _SpeechDemoPageState();
}
class _SpeechDemoPageState extends State {
final VoiceRecognitionService _recognitionService = Get.find();
+ final RxBool _permissionsGranted = false.obs;
+ final RxBool _isInitializing = false.obs;
+ final RxBool _isRecognizing = false.obs;
+ final RxString _errorMessage = ''.obs;
+ final RxString _recognizedText = ''.obs;
+ final RxString _status = ''.obs;
+ // 添加原生权限通道 - 只用于处理Android 11+的MANAGE_EXTERNAL_STORAGE权限
+ static const MethodChannel _permissionChannel = MethodChannel('com.example.deep_voice/permissions');
+
@override
void initState() {
super.initState();
- _initializeEngine();
+ _setupPermissionListener();
+ _checkPermissions();
+ }
+
+ @override
+ void dispose() {
+ _recognitionService.stopRecognition();
+ super.dispose();
+ }
+
+ // 设置权限监听器 - 只监听MANAGE_EXTERNAL_STORAGE权限结果
+ void _setupPermissionListener() {
+ _permissionChannel.setMethodCallHandler((call) async {
+ if (call.method == 'manageExternalStorageResult') {
+ developer.log('Received manageExternalStorageResult from native: ${call.arguments}');
+
+ // 更新权限状态
+ await _checkPermissions();
+ }
+ return null;
+ });
}
+ // 检查所有必要的权限
+ Future _checkPermissions() async {
+ developer.log('Checking permissions');
+
+ try {
+ // 检查基本权限
+ final micStatus = await Permission.microphone.status;
+ final storageStatus = await Permission.storage.status;
+
+ developer.log('=== 详细权限状态 ===');
+ developer.log('麦克风权限: $micStatus');
+ developer.log('存储权限: $storageStatus');
+
+ // 对于Android 11+,检查MANAGE_EXTERNAL_STORAGE权限
+ bool manageStorageGranted = true;
+ if (Platform.isAndroid) {
+ if (await _isAndroid11OrHigher()) {
+ // 使用原生方法检查MANAGE_EXTERNAL_STORAGE权限
+ try {
+ manageStorageGranted = await _permissionChannel.invokeMethod('isExternalStorageManager') ?? false;
+ } catch (e) {
+ developer.log('Error checking MANAGE_EXTERNAL_STORAGE: $e');
+ manageStorageGranted = await Permission.manageExternalStorage.isGranted;
+ }
+ developer.log('管理所有文件权限: $manageStorageGranted');
+ }
+ }
+
+ // 记录其他权限状态
+ developer.log('读取外部存储权限: ${await Permission.audio.status}');
+ developer.log('写入外部存储权限: ${await Permission.photos.status}');
+ developer.log('=====================');
+
+ // 更新权限状态
+ final bool basicPermissionsGranted = micStatus.isGranted && storageStatus.isGranted;
+
+ // 修改逻辑:如果是Android 11+且已授予MANAGE_EXTERNAL_STORAGE权限,则忽略普通存储权限状态
+ bool effectiveStoragePermission = storageStatus.isGranted;
+ if (Platform.isAndroid && await _isAndroid11OrHigher() && manageStorageGranted) {
+ developer.log('已授予MANAGE_EXTERNAL_STORAGE权限,忽略普通存储权限状态');
+ effectiveStoragePermission = true;
+ }
+
+ _permissionsGranted.value = micStatus.isGranted && (effectiveStoragePermission || manageStorageGranted);
+
+ developer.log('所有权限已授予: ${_permissionsGranted.value}');
+
+ // 如果权限已授予,初始化引擎
+ if (_permissionsGranted.value && !_isInitializing.value) {
+ _initializeEngine();
+ } else if (!_permissionsGranted.value) {
+ _errorMessage.value = '需要麦克风和存储权限才能使用语音识别功能';
+ }
+ } catch (e) {
+ developer.log('检查权限时出错: $e');
+ _errorMessage.value = '检查权限时出错: $e';
+ }
+ }
+
+ // 检查是否为Android 11或更高版本
+ Future _isAndroid11OrHigher() async {
+ if (Platform.isAndroid) {
+ final androidInfo = await DeviceInfoPlugin().androidInfo;
+ return androidInfo.version.sdkInt >= 30; // Android 11 is API 30
+ }
+ return false;
+ }
+
+ // 记录详细的权限状态
+ Future _logPermissionDetails() async {
+ developer.log('=== 详细权限状态 ===');
+ developer.log('麦克风权限: ${await Permission.microphone.status}');
+ developer.log('存储权限: ${await Permission.storage.status}');
+
+ if (Platform.isAndroid) {
+ if (await _isAndroid11OrHigher()) {
+ bool manageStorageGranted = false;
+ try {
+ manageStorageGranted = await _permissionChannel.invokeMethod('isExternalStorageManager') ?? false;
+ } catch (e) {
+ developer.log('Error checking MANAGE_EXTERNAL_STORAGE: $e');
+ manageStorageGranted = await Permission.manageExternalStorage.isGranted;
+ }
+ developer.log('管理所有文件权限: $manageStorageGranted');
+ }
+ }
+
+ developer.log('读取外部存储权限: ${await Permission.audio.status}');
+ developer.log('写入外部存储权限: ${await Permission.photos.status}');
+ developer.log('=====================');
+ }
+
+ // 请求所有必要的权限
+ Future _requestPermissions() async {
+ developer.log('请求权限');
+
+ try {
+ // 对于Android 11+,优先请求MANAGE_EXTERNAL_STORAGE权限
+ if (Platform.isAndroid && await _isAndroid11OrHigher()) {
+ bool isManager = false;
+ try {
+ isManager = await _permissionChannel.invokeMethod('isExternalStorageManager') ?? false;
+ } catch (e) {
+ developer.log('检查MANAGE_EXTERNAL_STORAGE时出错: $e');
+ isManager = await Permission.manageExternalStorage.isGranted;
+ }
+
+ if (!isManager) {
+ developer.log('请求MANAGE_EXTERNAL_STORAGE权限');
+ // 使用原生方法请求MANAGE_EXTERNAL_STORAGE权限
+ await _permissionChannel.invokeMethod('requestManageExternalStorage');
+
+ // 等待一段时间,让用户有机会授予权限
+ await Future.delayed(const Duration(seconds: 1));
+
+ // 再次检查权限状态
+ try {
+ isManager = await _permissionChannel.invokeMethod('isExternalStorageManager') ?? false;
+ developer.log('MANAGE_EXTERNAL_STORAGE权限状态: $isManager');
+ } catch (e) {
+ developer.log('检查MANAGE_EXTERNAL_STORAGE时出错: $e');
+ }
+ }
+ }
+
+ // 请求基本权限
+ Map statuses = await [
+ Permission.microphone,
+ Permission.storage,
+ ].request();
+
+ // 记录结果
+ statuses.forEach((permission, status) {
+ developer.log('权限 $permission: $status');
+ });
+
+ // 检查权限状态
+ await _checkPermissions();
+ } catch (e) {
+ developer.log('请求权限时出错: $e');
+ _errorMessage.value = '请求权限时出错: $e';
+ }
+ }
+
+ // 打开应用设置
+ Future _openAppSettings() async {
+ developer.log('打开应用设置');
+ try {
+ // 使用permission_handler打开设置
+ final opened = await openAppSettings();
+ if (!opened) {
+ // 如果permission_handler失败,使用原生方法
+ await _permissionChannel.invokeMethod('openAppSettings');
+ }
+ } catch (e) {
+ developer.log('打开应用设置时出错: $e');
+ // 使用原生方法作为备选
+ try {
+ await _permissionChannel.invokeMethod('openAppSettings');
+ } catch (e2) {
+ developer.log('通过原生通道打开应用设置时出错: $e2');
+ }
+ }
+ }
+
+ // 初始化语音引擎
Future _initializeEngine() async {
+ if (_isInitializing.value) return;
+
+ _isInitializing.value = true;
+ _errorMessage.value = '';
+ _status.value = '正在初始化语音引擎...';
+
try {
+ // 再次检查权限状态
+ await _checkPermissions();
+
+ if (!_permissionsGranted.value) {
+ developer.log('权限未授予,无法初始化引擎');
+ _errorMessage.value = '权限未授予,无法初始化语音引擎';
+ _isInitializing.value = false;
+ return;
+ }
+
+ // 记录权限详情
+ await _logPermissionDetails();
+
+ // 测试存储权限
+ String? availableDir = await _getAvailableStorageDirectory();
+ if (availableDir != null) {
+ developer.log('找到可用的存储目录: $availableDir');
+ } else {
+ developer.log('警告: 未找到可用的存储目录,可能会影响语音引擎初始化');
+ }
+
+ // 初始化语音引擎
+ developer.log('初始化语音识别引擎');
await _recognitionService.initialize();
- } catch (e) {
- if (mounted) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(content: Text('初始化失败: $e')),
- );
+
+ developer.log('语音识别引擎初始化成功');
+ _status.value = '语音引擎初始化成功,可以开始识别';
+ _isInitializing.value = false;
+ } catch (e, stackTrace) {
+ developer.log('初始化语音识别引擎时出错: $e');
+ developer.log('堆栈跟踪: $stackTrace');
+
+ // 解析错误代码
+ String errorDetail = '未知错误';
+ if (e is PlatformException) {
+ developer.log('PlatformException 代码: ${e.code}, 消息: ${e.message}');
+
+ if (e.code == 'INIT_ERROR') {
+ if (e.message?.contains('-7') == true) {
+ errorDetail = '无法创建日志目录 (错误码: -7),可能是存储权限问题';
+ } else {
+ errorDetail = '初始化错误: ${e.message}';
+ }
+ }
}
+
+ _errorMessage.value = '初始化语音引擎失败: $errorDetail';
+ _status.value = '初始化失败';
+ _isInitializing.value = false;
+
+ // 显示错误提示和重试选项
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('初始化失败: $errorDetail'),
+ action: SnackBarAction(
+ label: '重试',
+ onPressed: _initializeEngine,
+ ),
+ duration: const Duration(seconds: 10),
+ ),
+ );
}
}
Future _startRecognition() async {
+ developer.log('开始识别');
+ _errorMessage.value = '';
+ _isRecognizing.value = true;
+ _status.value = '正在识别...';
+
try {
await _recognitionService.startRecognition();
+
+ // 设置监听器接收识别结果
+ _recognitionService.recognizedText.listen((text) {
+ _recognizedText.value = text;
+ });
+
} catch (e) {
- if (mounted) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(content: Text('启动识别失败: $e')),
- );
- }
+ developer.log('开始识别时出错: $e');
+ _errorMessage.value = '启动识别失败: $e';
+ _isRecognizing.value = false;
+ _status.value = '识别启动失败';
+
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('启动识别失败: $e'),
+ duration: const Duration(seconds: 3),
+ ),
+ );
}
}
Future _stopRecognition() async {
+ developer.log('停止识别');
+
try {
await _recognitionService.stopRecognition();
+ _isRecognizing.value = false;
+ _status.value = '识别已停止';
+
+ // 保存最终识别结果
+ _recognizedText.value = _recognitionService.recognizedText.value;
+
} catch (e) {
- if (mounted) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(content: Text('停止识别失败: $e')),
- );
+ developer.log('停止识别时出错: $e');
+ _errorMessage.value = '停止识别失败: $e';
+ _isRecognizing.value = false;
+ _status.value = '识别停止失败';
+
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('停止识别失败: $e'),
+ duration: const Duration(seconds: 3),
+ ),
+ );
+ }
+ }
+
+ // 获取可用的存储目录
+ Future _getAvailableStorageDirectory() async {
+ try {
+ List directoriesToTry = [];
+
+ // 应用专用外部存储目录
+ if (Platform.isAndroid) {
+ directoriesToTry.add(Directory('/storage/emulated/0/Android/data/com.example.deep_voice/files'));
+ }
+
+ // 应用内部存储目录
+ try {
+ final appDir = await getApplicationDocumentsDirectory();
+ directoriesToTry.add(appDir);
+ } catch (e) {
+ developer.log('获取应用内部存储目录失败: $e');
}
+
+ // 应用缓存目录
+ try {
+ final cacheDir = await getTemporaryDirectory();
+ directoriesToTry.add(cacheDir);
+ } catch (e) {
+ developer.log('获取应用缓存目录失败: $e');
+ }
+
+ // 尝试每个目录
+ for (var directory in directoriesToTry) {
+ try {
+ developer.log('测试目录可用性: ${directory.path}');
+
+ if (!await directory.exists()) {
+ await directory.create(recursive: true);
+ }
+
+ final testFile = File('${directory.path}/test_write.tmp');
+ await testFile.writeAsString('Test write at ${DateTime.now()}');
+ await testFile.readAsString(); // 验证可读
+ await testFile.delete();
+
+ developer.log('找到可用目录: ${directory.path}');
+ return directory.path;
+ } catch (e) {
+ developer.log('目录 ${directory.path} 不可用: $e');
+ }
+ }
+
+ developer.log('未找到可用的存储目录');
+ return null;
+ } catch (e) {
+ developer.log('获取可用存储目录时出错: $e');
+ return null;
}
}
@@ -58,76 +408,133 @@ class _SpeechDemoPageState extends State {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
- title: const Text('火山语音识别演示'),
+ title: const Text('语音识别演示'),
),
- body: Center(
+ body: Padding(
+ padding: const EdgeInsets.all(16.0),
child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
+ // 权限请求部分
+ Obx(() => !_permissionsGranted.value
+ ? Card(
+ color: Colors.amber.shade100,
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Text(
+ '需要麦克风和存储权限',
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 16,
+ ),
+ ),
+ const SizedBox(height: 8),
+ const Text(
+ '注意: 在Android 11及以上版本,您需要在设置中手动授予"管理所有文件"权限',
+ style: TextStyle(
+ fontSize: 14,
+ ),
+ ),
+ const SizedBox(height: 16),
+ Wrap(
+ spacing: 8.0,
+ runSpacing: 8.0,
+ alignment: WrapAlignment.center,
+ children: [
+ ElevatedButton(
+ onPressed: _requestPermissions,
+ child: const Text('授予权限'),
+ ),
+ ElevatedButton(
+ onPressed: _openAppSettings,
+ child: const Text('打开设置'),
+ ),
+ ElevatedButton(
+ onPressed: _logPermissionDetails,
+ child: const Text('检查权限'),
+ ),
+ ElevatedButton(
+ onPressed: _initializeEngine,
+ child: const Text('强制初始化'),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ )
+ : const SizedBox.shrink()),
+
// 状态显示
- Obx(() {
- String status = '准备中';
- if (_recognitionService.isConnecting.value) {
- status = '连接中...';
- } else if (_recognitionService.isInitialized.value) {
- status = _recognitionService.isListening.value ? '正在听...' : '就绪';
- }
- return Text('状态: $status', style: const TextStyle(fontSize: 18));
- }),
-
- const SizedBox(height: 20),
-
- // 识别结果显示
- Padding(
- padding: const EdgeInsets.all(16.0),
- child: Container(
- width: double.infinity,
- height: 200,
- decoration: BoxDecoration(
- border: Border.all(color: Colors.grey),
- borderRadius: BorderRadius.circular(8),
- ),
- padding: const EdgeInsets.all(16),
- child: Obx(() {
- return SingleChildScrollView(
+ Obx(() => _status.value.isNotEmpty
+ ? Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8.0),
+ child: Text(
+ '状态: ${_status.value}',
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ )
+ : const SizedBox.shrink()),
+
+ // 错误消息
+ Obx(() => _errorMessage.value.isNotEmpty
+ ? Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
- _recognitionService.recognizedText.value,
- style: const TextStyle(fontSize: 16),
+ '错误: ${_errorMessage.value}',
+ style: const TextStyle(
+ color: Colors.red,
+ fontWeight: FontWeight.bold,
+ ),
),
- );
- }),
+ )
+ : const SizedBox.shrink()),
+
+ // 识别结果显示区域
+ Expanded(
+ child: Card(
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: SingleChildScrollView(
+ child: Obx(() => Text(
+ _recognizedText.value.isEmpty
+ ? '识别结果将显示在这里...'
+ : _recognizedText.value,
+ style: const TextStyle(
+ fontSize: 18.0,
+ ),
+ )),
+ ),
+ ),
),
),
-
- const SizedBox(height: 20),
-
+
// 控制按钮
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Obx(() {
- return ElevatedButton(
- onPressed: _recognitionService.isInitialized.value && !_recognitionService.isListening.value
- ? _startRecognition
- : null,
- child: const Text('开始识别'),
- );
- }),
-
- const SizedBox(width: 20),
-
- Obx(() {
- return ElevatedButton(
- onPressed: _recognitionService.isListening.value
- ? _stopRecognition
- : null,
- style: ElevatedButton.styleFrom(
- backgroundColor: Colors.red,
- ),
- child: const Text('停止识别'),
- );
- }),
- ],
+ Padding(
+ padding: const EdgeInsets.symmetric(vertical: 16.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ Obx(() => ElevatedButton(
+ onPressed: _permissionsGranted.value && !_isRecognizing.value && !_isInitializing.value
+ ? _startRecognition
+ : null,
+ child: const Text('开始识别'),
+ )),
+ Obx(() => ElevatedButton(
+ onPressed: _isRecognizing.value ? _stopRecognition : null,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.red,
+ ),
+ child: const Text('停止识别'),
+ )),
+ ],
+ ),
),
],
),