10 changed files with 740 additions and 3 deletions
@ -1,5 +1,22 @@ |
|||||
package com.example.deep_voice |
package com.example.deep_voice |
||||
|
|
||||
import com.ryanheise.audioservice.AudioServiceActivity |
import com.ryanheise.audioservice.AudioServiceActivity |
||||
|
import io.flutter.embedding.engine.FlutterEngine |
||||
|
|
||||
class MainActivity: AudioServiceActivity() |
class MainActivity: AudioServiceActivity() { |
||||
|
private var volcSpeechBridge: VolcSpeechBridge? = null |
||||
|
|
||||
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { |
||||
|
super.configureFlutterEngine(flutterEngine) |
||||
|
|
||||
|
// 初始化火山语音识别桥接类 |
||||
|
volcSpeechBridge = VolcSpeechBridge(this, flutterEngine.dartExecutor.binaryMessenger) |
||||
|
} |
||||
|
|
||||
|
override fun onDestroy() { |
||||
|
// 释放火山语音识别资源 |
||||
|
volcSpeechBridge?.dispose() |
||||
|
volcSpeechBridge = null |
||||
|
super.onDestroy() |
||||
|
} |
||||
|
} |
||||
|
|||||
@ -0,0 +1,293 @@ |
|||||
|
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 |
||||
|
|
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 火山语音识别桥接类 |
||||
|
* 负责与火山语音识别 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 const val DIRECTIVE_START_RECOGNITION = 1 |
||||
|
private const val DIRECTIVE_STOP_RECOGNITION = 2 |
||||
|
} |
||||
|
|
||||
|
private var engine: SpeechEngine? = null |
||||
|
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") ?: "" |
||||
|
initialize(appKey, accessKey, result) |
||||
|
} |
||||
|
"startRecognition" -> { |
||||
|
startRecognition(result) |
||||
|
} |
||||
|
"stopRecognition" -> { |
||||
|
stopRecognition(result) |
||||
|
} |
||||
|
"isInitialized" -> { |
||||
|
result.success(isInitialized) |
||||
|
} |
||||
|
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, result: Result) { |
||||
|
methodResult = result |
||||
|
try { |
||||
|
Log.i(TAG, "Initializing Volcengine Speech Engine") |
||||
|
|
||||
|
// 创建引擎实例 |
||||
|
engine = SpeechEngineGenerator.getInstance() |
||||
|
engine?.createEngine() |
||||
|
|
||||
|
// 设置监听器 |
||||
|
engine?.setListener(this) |
||||
|
|
||||
|
// 设置应用 ID 和访问密钥 |
||||
|
engine?.setOptionString("app_id", appKey) |
||||
|
engine?.setOptionString("access_key", accessKey) |
||||
|
|
||||
|
// 设置语音识别参数 |
||||
|
engine?.setOptionBoolean("enable_punctuation", true) // 启用标点符号 |
||||
|
engine?.setOptionBoolean("enable_intermediate_result", true) // 启用中间结果 |
||||
|
engine?.setOptionBoolean("enable_vad", true) // 启用语音活动检测 |
||||
|
|
||||
|
// 设置语言(普通话) |
||||
|
engine?.setOptionString("language", "zh") |
||||
|
|
||||
|
// 初始化引擎 |
||||
|
val ret = engine?.initEngine() |
||||
|
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) { |
||||
|
result.error("NOT_INITIALIZED", "Speech engine is not initialized", null) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
Log.i(TAG, "Starting speech recognition") |
||||
|
val ret = engine?.sendDirective(DIRECTIVE_START_RECOGNITION, "") |
||||
|
|
||||
|
if (ret == SpeechEngineDefines.ERR_NO_ERROR) { |
||||
|
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) |
||||
|
} |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "Start recognition failed", e) |
||||
|
result.error("START_ERROR", e.message, null) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 停止语音识别 |
||||
|
*/ |
||||
|
fun stopRecognition(result: Result) { |
||||
|
try { |
||||
|
if (!isInitialized) { |
||||
|
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) { |
||||
|
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) |
||||
|
} |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "Stop recognition failed", e) |
||||
|
result.error("STOP_ERROR", e.message, null) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 释放资源 |
||||
|
*/ |
||||
|
fun dispose() { |
||||
|
try { |
||||
|
engine?.destroyEngine() |
||||
|
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 { |
||||
|
when (messageType) { |
||||
|
// 初始化完成消息 |
||||
|
1 -> { |
||||
|
Log.i(TAG, "Engine initialized successfully") |
||||
|
isInitialized = true |
||||
|
|
||||
|
// 通知 Flutter 初始化完成 |
||||
|
methodResult?.success("Engine initialized") |
||||
|
methodResult = null |
||||
|
|
||||
|
// 发送状态更新到 Flutter |
||||
|
sendEvent("status", "initialized") |
||||
|
} |
||||
|
|
||||
|
// 错误消息 |
||||
|
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") |
||||
|
} |
||||
|
|
||||
|
sendEvent("error", errorData.toString()) |
||||
|
} |
||||
|
|
||||
|
// VAD 状态变化消息 |
||||
|
3 -> { |
||||
|
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) |
||||
|
} |
||||
|
|
||||
|
// 识别结果消息 |
||||
|
4 -> { |
||||
|
messageData?.let { |
||||
|
val resultJson = String(it, 0, messageLength) |
||||
|
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) |
||||
|
} |
||||
|
|
||||
|
sendEvent("result", resultData.toString()) |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "Error parsing result JSON", e) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
else -> { |
||||
|
Log.d(TAG, "Received unknown message type: $messageType") |
||||
|
} |
||||
|
} |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "Error processing speech message", e) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,264 @@ |
|||||
|
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'; |
||||
|
|
||||
|
/// 火山语音识别服务 |
||||
|
/// 负责与原生层的火山语音识别引擎交互 |
||||
|
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; |
||||
|
|
||||
|
@override |
||||
|
void onInit() { |
||||
|
super.onInit(); |
||||
|
_loadCredentials(); |
||||
|
_setupEventListener(); |
||||
|
} |
||||
|
|
||||
|
/// 加载火山语音识别的凭证 |
||||
|
void _loadCredentials() { |
||||
|
try { |
||||
|
_appKey = dotenv.env['VOLC_SPEECH_APP_KEY'] ?? ''; |
||||
|
_accessKey = dotenv.env['VOLC_SPEECH_ACCESS_KEY'] ?? ''; |
||||
|
|
||||
|
if (_appKey.isEmpty || _accessKey.isEmpty) { |
||||
|
debugPrint('警告: 火山语音识别凭证未设置,请在 .env 文件中设置 VOLC_SPEECH_APP_KEY 和 VOLC_SPEECH_ACCESS_KEY'); |
||||
|
} |
||||
|
} catch (e) { |
||||
|
debugPrint('加载火山语音识别凭证失败: $e'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 设置事件监听器 |
||||
|
void _setupEventListener() { |
||||
|
try { |
||||
|
_eventSubscription = _eventChannel.receiveBroadcastStream().listen( |
||||
|
(dynamic event) { |
||||
|
_handleEvent(event.toString()); |
||||
|
}, |
||||
|
onError: (dynamic error) { |
||||
|
debugPrint('事件通道错误: $error'); |
||||
|
if (onError != null) { |
||||
|
onError!('事件通道错误: $error'); |
||||
|
} |
||||
|
}, |
||||
|
); |
||||
|
} catch (e) { |
||||
|
debugPrint('设置事件监听器失败: $e'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 处理来自原生层的事件 |
||||
|
void _handleEvent(String eventJson) { |
||||
|
try { |
||||
|
final eventData = jsonDecode(eventJson); |
||||
|
final eventName = eventData['event']; |
||||
|
final data = eventData['data']; |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} catch (e) { |
||||
|
debugPrint('处理事件失败: $e'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 处理状态事件 |
||||
|
void _handleStatusEvent(String status) { |
||||
|
switch (status) { |
||||
|
case 'initialized': |
||||
|
isInitialized.value = true; |
||||
|
isConnecting.value = false; |
||||
|
break; |
||||
|
case 'listening': |
||||
|
isListening.value = true; |
||||
|
break; |
||||
|
case 'idle': |
||||
|
isListening.value = false; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (onConnectionStatusChanged != null) { |
||||
|
onConnectionStatusChanged!(status); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 处理识别结果事件 |
||||
|
void _handleResultEvent(String resultJson) { |
||||
|
try { |
||||
|
final resultData = jsonDecode(resultJson); |
||||
|
final text = resultData['text']; |
||||
|
final isFinal = resultData['isFinal']; |
||||
|
|
||||
|
// 更新识别文本 |
||||
|
recognizedText.value = text; |
||||
|
|
||||
|
// 调用回调函数 |
||||
|
if (onRecognitionResult != null) { |
||||
|
onRecognitionResult!(text); |
||||
|
} |
||||
|
|
||||
|
// 如果是最终结果,调用完成回调 |
||||
|
if (isFinal && text.isNotEmpty) { |
||||
|
if (onSentenceComplete != null) { |
||||
|
onSentenceComplete!(text); |
||||
|
} |
||||
|
|
||||
|
if (onRecognitionComplete != null) { |
||||
|
onRecognitionComplete!(); |
||||
|
} |
||||
|
} |
||||
|
} catch (e) { |
||||
|
debugPrint('处理识别结果失败: $e'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 处理错误事件 |
||||
|
void _handleErrorEvent(String errorJson) { |
||||
|
try { |
||||
|
final errorData = jsonDecode(errorJson); |
||||
|
final errorMessage = errorData['message']; |
||||
|
|
||||
|
debugPrint('语音识别错误: $errorMessage'); |
||||
|
|
||||
|
if (onError != null) { |
||||
|
onError!(errorMessage); |
||||
|
} |
||||
|
} catch (e) { |
||||
|
debugPrint('处理错误事件失败: $e'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 处理 VAD 事件 |
||||
|
void _handleVadEvent(String vadState) { |
||||
|
// 可以根据需要处理 VAD 状态变化 |
||||
|
debugPrint('VAD 状态: $vadState'); |
||||
|
} |
||||
|
|
||||
|
/// 初始化语音识别引擎 |
||||
|
Future<void> initialize() async { |
||||
|
try { |
||||
|
isConnecting.value = true; |
||||
|
|
||||
|
// 检查凭证 |
||||
|
if (_appKey.isEmpty || _accessKey.isEmpty) { |
||||
|
throw '火山语音识别凭证未设置'; |
||||
|
} |
||||
|
|
||||
|
// 调用原生方法初始化引擎 |
||||
|
final result = await _methodChannel.invokeMethod<String>( |
||||
|
'initialize', |
||||
|
{ |
||||
|
'appKey': _appKey, |
||||
|
'accessKey': _accessKey, |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
debugPrint('初始化结果: $result'); |
||||
|
|
||||
|
// 初始化成功后,状态会通过事件通道更新 |
||||
|
} catch (e) { |
||||
|
isConnecting.value = false; |
||||
|
debugPrint('初始化语音识别引擎失败: $e'); |
||||
|
if (onError != null) { |
||||
|
onError!('初始化失败: $e'); |
||||
|
} |
||||
|
rethrow; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 开始语音识别 |
||||
|
Future<void> startRecognition() async { |
||||
|
try { |
||||
|
// 检查是否已初始化 |
||||
|
if (!isInitialized.value) { |
||||
|
await initialize(); |
||||
|
} |
||||
|
|
||||
|
// 调用原生方法开始识别 |
||||
|
final result = await _methodChannel.invokeMethod<String>('startRecognition'); |
||||
|
debugPrint('开始识别结果: $result'); |
||||
|
|
||||
|
// 状态会通过事件通道更新 |
||||
|
} catch (e) { |
||||
|
debugPrint('开始语音识别失败: $e'); |
||||
|
if (onError != null) { |
||||
|
onError!('开始识别失败: $e'); |
||||
|
} |
||||
|
rethrow; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 停止语音识别 |
||||
|
Future<void> stopRecognition() async { |
||||
|
try { |
||||
|
// 检查是否已初始化 |
||||
|
if (!isInitialized.value) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 调用原生方法停止识别 |
||||
|
final result = await _methodChannel.invokeMethod<String>('stopRecognition'); |
||||
|
debugPrint('停止识别结果: $result'); |
||||
|
|
||||
|
// 状态会通过事件通道更新 |
||||
|
} catch (e) { |
||||
|
debugPrint('停止语音识别失败: $e'); |
||||
|
if (onError != null) { |
||||
|
onError!('停止识别失败: $e'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
void onClose() { |
||||
|
// 取消事件订阅 |
||||
|
_eventSubscription?.cancel(); |
||||
|
|
||||
|
// 停止识别 |
||||
|
stopRecognition(); |
||||
|
|
||||
|
super.onClose(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,137 @@ |
|||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import '../../data/services/voice_recognition_service.dart'; |
||||
|
|
||||
|
class SpeechDemoPage extends StatefulWidget { |
||||
|
const SpeechDemoPage({Key? key}) : super(key: key); |
||||
|
|
||||
|
@override |
||||
|
_SpeechDemoPageState createState() => _SpeechDemoPageState(); |
||||
|
} |
||||
|
|
||||
|
class _SpeechDemoPageState extends State<SpeechDemoPage> { |
||||
|
final VoiceRecognitionService _recognitionService = Get.find<VoiceRecognitionService>(); |
||||
|
|
||||
|
@override |
||||
|
void initState() { |
||||
|
super.initState(); |
||||
|
_initializeEngine(); |
||||
|
} |
||||
|
|
||||
|
Future<void> _initializeEngine() async { |
||||
|
try { |
||||
|
await _recognitionService.initialize(); |
||||
|
} catch (e) { |
||||
|
if (mounted) { |
||||
|
ScaffoldMessenger.of(context).showSnackBar( |
||||
|
SnackBar(content: Text('初始化失败: $e')), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Future<void> _startRecognition() async { |
||||
|
try { |
||||
|
await _recognitionService.startRecognition(); |
||||
|
} catch (e) { |
||||
|
if (mounted) { |
||||
|
ScaffoldMessenger.of(context).showSnackBar( |
||||
|
SnackBar(content: Text('启动识别失败: $e')), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Future<void> _stopRecognition() async { |
||||
|
try { |
||||
|
await _recognitionService.stopRecognition(); |
||||
|
} catch (e) { |
||||
|
if (mounted) { |
||||
|
ScaffoldMessenger.of(context).showSnackBar( |
||||
|
SnackBar(content: Text('停止识别失败: $e')), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Scaffold( |
||||
|
appBar: AppBar( |
||||
|
title: const Text('火山语音识别演示'), |
||||
|
), |
||||
|
body: Center( |
||||
|
child: Column( |
||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||
|
children: [ |
||||
|
// 状态显示 |
||||
|
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( |
||||
|
child: Text( |
||||
|
_recognitionService.recognizedText.value, |
||||
|
style: const TextStyle(fontSize: 16), |
||||
|
), |
||||
|
); |
||||
|
}), |
||||
|
), |
||||
|
), |
||||
|
|
||||
|
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('停止识别'), |
||||
|
); |
||||
|
}), |
||||
|
], |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue