Compare commits
3 Commits
aba8b44ab8
...
508244fac3
| Author | SHA1 | Date |
|---|---|---|
|
|
508244fac3 | |
|
|
75080d0c0d | |
|
|
e961996ec6 |
|
|
@ -40,14 +40,10 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
bool _isListening = false;
|
bool _isListening = false;
|
||||||
String _currentText = '';
|
String _currentText = '';
|
||||||
String _errorMessage = '';
|
String _errorMessage = '';
|
||||||
List<String> _recognitionHistory = [];
|
|
||||||
|
|
||||||
/// 本次录音开始前的文本内容(用于实时追加)
|
/// 本次录音开始前的文本内容(用于实时追加)
|
||||||
String _baseText = '';
|
String _baseText = '';
|
||||||
|
|
||||||
// 录音相关
|
|
||||||
final List<String> _realtimeResults = []; // 存储实时识别片段
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
@ -152,11 +148,9 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 清除历史记录
|
/// 清除内容
|
||||||
void _clearHistory() {
|
void _clearContent() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_recognitionHistory.clear();
|
|
||||||
_realtimeResults.clear();
|
|
||||||
_currentText = '';
|
_currentText = '';
|
||||||
_baseText = ''; // 也清空基础文本
|
_baseText = ''; // 也清空基础文本
|
||||||
_textController.clear();
|
_textController.clear();
|
||||||
|
|
@ -173,8 +167,8 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.clear_all),
|
icon: const Icon(Icons.clear_all),
|
||||||
onPressed: _clearHistory,
|
onPressed: _clearContent,
|
||||||
tooltip: '清除历史',
|
tooltip: '清除内容',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -189,9 +183,6 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
// 识别结果卡片
|
// 识别结果卡片
|
||||||
_buildRecognitionCard(),
|
_buildRecognitionCard(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// 历史记录
|
|
||||||
Expanded(child: _buildHistoryCard()),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -396,66 +387,6 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 构建历史记录卡片
|
|
||||||
Widget _buildHistoryCard() {
|
|
||||||
return Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'识别历史',
|
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Expanded(
|
|
||||||
child: _recognitionHistory.isEmpty
|
|
||||||
? Center(
|
|
||||||
child: Text(
|
|
||||||
'暂无识别历史',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey[600],
|
|
||||||
fontStyle: FontStyle.italic,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: ListView.builder(
|
|
||||||
itemCount: _recognitionHistory.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return Card(
|
|
||||||
margin: const EdgeInsets.only(bottom: 8.0),
|
|
||||||
child: ListTile(
|
|
||||||
leading: CircleAvatar(
|
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
|
||||||
child: Text(
|
|
||||||
'${index + 1}',
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: Text(_recognitionHistory[index]),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(Icons.copy),
|
|
||||||
onPressed: () {
|
|
||||||
// TODO: 实现复制到剪贴板功能
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('已复制到剪贴板'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 构建浮动操作按钮
|
/// 构建浮动操作按钮
|
||||||
Widget _buildFloatingActionButton() {
|
Widget _buildFloatingActionButton() {
|
||||||
if (!_isInitialized) {
|
if (!_isInitialized) {
|
||||||
|
|
@ -508,16 +439,6 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isListening) {
|
if (!isListening) {
|
||||||
// 录音结束后,确认当前文本并保存到历史记录
|
|
||||||
if (_currentText.isNotEmpty) {
|
|
||||||
setState(() {
|
|
||||||
_recognitionHistory.insert(0, _currentText);
|
|
||||||
// 限制历史记录数量
|
|
||||||
if (_recognitionHistory.length > 10) {
|
|
||||||
_recognitionHistory.removeLast();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 录音结束后,更新基础文本为下次录音做准备
|
// 录音结束后,更新基础文本为下次录音做准备
|
||||||
setState(() {
|
setState(() {
|
||||||
_baseText = _textController.text; // 保存当前文本作为下次的基础
|
_baseText = _textController.text; // 保存当前文本作为下次的基础
|
||||||
|
|
@ -527,7 +448,6 @@ class _SpeechRecognitionPageState extends State<SpeechRecognitionPage> {
|
||||||
// 开始录音时记录当前文本作为基础,清空当前识别文本
|
// 开始录音时记录当前文本作为基础,清空当前识别文本
|
||||||
setState(() {
|
setState(() {
|
||||||
_baseText = _textController.text; // 记录录音开始前的文本
|
_baseText = _textController.text; // 记录录音开始前的文本
|
||||||
_realtimeResults.clear();
|
|
||||||
_currentText = ''; // 清空当前识别文本
|
_currentText = ''; // 清空当前识别文本
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,14 @@ class SpeechRecognitionResult {
|
||||||
/// 识别出的文字内容
|
/// 识别出的文字内容
|
||||||
final String recognizedWords;
|
final String recognizedWords;
|
||||||
|
|
||||||
/// 识别置信度(0.0 到 1.0)
|
|
||||||
final double confidence;
|
|
||||||
|
|
||||||
/// 备选识别结果
|
|
||||||
final List<String> alternatives;
|
|
||||||
|
|
||||||
const SpeechRecognitionResult({
|
const SpeechRecognitionResult({
|
||||||
required this.recognizedWords,
|
required this.recognizedWords,
|
||||||
this.confidence = 0.0,
|
|
||||||
this.alternatives = const [],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 从 Map 创建 [SpeechRecognitionResult] 实例
|
/// 从 Map 创建 [SpeechRecognitionResult] 实例
|
||||||
factory SpeechRecognitionResult.fromMap(Map<String, dynamic> map) {
|
factory SpeechRecognitionResult.fromMap(Map<String, dynamic> map) {
|
||||||
return SpeechRecognitionResult(
|
return SpeechRecognitionResult(
|
||||||
recognizedWords: map['recognizedWords'] as String? ?? '',
|
recognizedWords: map['recognizedWords'] as String? ?? '',
|
||||||
confidence: (map['confidence'] as num?)?.toDouble() ?? 0.0,
|
|
||||||
alternatives: List<String>.from(map['alternatives'] as List? ?? []),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,31 +18,23 @@ class SpeechRecognitionResult {
|
||||||
Map<String, dynamic> toMap() {
|
Map<String, dynamic> toMap() {
|
||||||
return {
|
return {
|
||||||
'recognizedWords': recognizedWords,
|
'recognizedWords': recognizedWords,
|
||||||
'confidence': confidence,
|
|
||||||
'alternatives': alternatives,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SpeechRecognitionResult(recognizedWords: $recognizedWords, '
|
return 'SpeechRecognitionResult(recognizedWords: $recognizedWords)';
|
||||||
'confidence: $confidence, alternatives: $alternatives)';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
return other is SpeechRecognitionResult &&
|
return other is SpeechRecognitionResult &&
|
||||||
other.recognizedWords == recognizedWords &&
|
other.recognizedWords == recognizedWords;
|
||||||
other.confidence == confidence &&
|
|
||||||
other.alternatives.length == alternatives.length &&
|
|
||||||
other.alternatives.every((alt) => alternatives.contains(alt));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
return recognizedWords.hashCode ^
|
return recognizedWords.hashCode;
|
||||||
confidence.hashCode ^
|
|
||||||
alternatives.hashCode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,7 @@ class YxAsrService implements SpeechRecognitionService {
|
||||||
bool _isStartingRecording = false; // 防抖保护:防止重复启动录音
|
bool _isStartingRecording = false; // 防抖保护:防止重复启动录音
|
||||||
bool _isInitialized = false;
|
bool _isInitialized = false;
|
||||||
String _currentModelPath = '';
|
String _currentModelPath = '';
|
||||||
|
String _lastRecognizedText = ''; // 记录上次识别的文本,避免重复发送
|
||||||
|
|
||||||
// 识别速度配置
|
// 识别速度配置
|
||||||
RecognitionSpeed _recognitionSpeed = RecognitionSpeed.fast;
|
RecognitionSpeed _recognitionSpeed = RecognitionSpeed.fast;
|
||||||
|
|
@ -513,6 +514,7 @@ class YxAsrService implements SpeechRecognitionService {
|
||||||
await _startAudioRecording(_sampleRate.hz);
|
await _startAudioRecording(_sampleRate.hz);
|
||||||
|
|
||||||
_isListening = true;
|
_isListening = true;
|
||||||
|
_lastRecognizedText = ''; // 重置上次识别的文本
|
||||||
_statusController.add(true);
|
_statusController.add(true);
|
||||||
|
|
||||||
// 开始识别循环处理
|
// 开始识别循环处理
|
||||||
|
|
@ -740,13 +742,18 @@ class YxAsrService implements SpeechRecognitionService {
|
||||||
final result = _recognizer!.getResult(_stream!);
|
final result = _recognizer!.getResult(_stream!);
|
||||||
debugPrint('🔍 [YxAsr] 获取识别结果: "${result.text}"');
|
debugPrint('🔍 [YxAsr] 获取识别结果: "${result.text}"');
|
||||||
|
|
||||||
if (result.text.isNotEmpty && partialResults) {
|
// 只有当识别结果不为空、启用了部分结果、且与上次结果不同时才发送
|
||||||
|
if (result.text.isNotEmpty &&
|
||||||
|
partialResults &&
|
||||||
|
result.text != _lastRecognizedText) {
|
||||||
debugPrint('🎤 [YxAsr] 发送实时识别结果: ${result.text}');
|
debugPrint('🎤 [YxAsr] 发送实时识别结果: ${result.text}');
|
||||||
|
_lastRecognizedText = result.text; // 更新最后识别的文本
|
||||||
_sendResult(
|
_sendResult(
|
||||||
recognizedWords: result.text,
|
recognizedWords: result.text,
|
||||||
confidence: 0.8,
|
|
||||||
alternatives: [],
|
|
||||||
);
|
);
|
||||||
|
} else if (result.text.isNotEmpty &&
|
||||||
|
result.text == _lastRecognizedText) {
|
||||||
|
debugPrint('🔄 [YxAsr] 跳过重复识别结果: "${result.text}"');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 端点检测已禁用,由用户手动控制录音结束
|
// 端点检测已禁用,由用户手动控制录音结束
|
||||||
|
|
@ -762,14 +769,10 @@ class YxAsrService implements SpeechRecognitionService {
|
||||||
/// 发送识别结果到结果流
|
/// 发送识别结果到结果流
|
||||||
void _sendResult({
|
void _sendResult({
|
||||||
required String recognizedWords,
|
required String recognizedWords,
|
||||||
required double confidence,
|
|
||||||
required List<String> alternatives,
|
|
||||||
}) {
|
}) {
|
||||||
debugPrint('📤 [YxAsr] 发送识别结果: "$recognizedWords"');
|
debugPrint('📤 [YxAsr] 发送识别结果: "$recognizedWords"');
|
||||||
final result = SpeechRecognitionResult(
|
final result = SpeechRecognitionResult(
|
||||||
recognizedWords: recognizedWords,
|
recognizedWords: recognizedWords,
|
||||||
confidence: confidence,
|
|
||||||
alternatives: alternatives,
|
|
||||||
);
|
);
|
||||||
_resultController.add(result);
|
_resultController.add(result);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue