Files
ai_english/client/lib/features/speaking/services/speech_recognition_service.dart
2025-11-17 13:39:05 +08:00

316 lines
9.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:math';
import '../models/pronunciation_item.dart';
/// 语音识别和评分服务
class SpeechRecognitionService {
static final SpeechRecognitionService _instance = SpeechRecognitionService._internal();
factory SpeechRecognitionService() => _instance;
SpeechRecognitionService._internal();
bool _isListening = false;
StreamController<double>? _volumeController;
StreamController<String>? _recognitionController;
/// 获取音量流
Stream<double> get volumeStream => _volumeController?.stream ?? const Stream.empty();
/// 获取识别结果流
Stream<String> get recognitionStream => _recognitionController?.stream ?? const Stream.empty();
/// 是否正在监听
bool get isListening => _isListening;
/// 开始语音识别
Future<bool> startListening() async {
if (_isListening) return false;
try {
_isListening = true;
_volumeController = StreamController<double>.broadcast();
_recognitionController = StreamController<String>.broadcast();
// TODO: 实现真实的语音识别
// 这里应该调用语音识别API如Google Speech-to-Text、百度语音识别等
// 模拟音量变化
_simulateVolumeChanges();
return true;
} catch (e) {
_isListening = false;
return false;
}
}
/// 停止语音识别
Future<void> stopListening() async {
if (!_isListening) return;
_isListening = false;
await _volumeController?.close();
await _recognitionController?.close();
_volumeController = null;
_recognitionController = null;
}
/// 分析发音并评分
Future<PronunciationResult> analyzePronunciation(
String recordedText,
PronunciationItem targetItem,
) async {
// TODO: 实现真实的发音分析
// 这里应该调用发音评估API如科大讯飞语音评测、腾讯云语音评测等
// 模拟分析过程
await Future.delayed(const Duration(milliseconds: 1500));
return _simulateAnalysis(recordedText, targetItem);
}
/// 模拟音量变化
void _simulateVolumeChanges() {
Timer.periodic(const Duration(milliseconds: 100), (timer) {
if (!_isListening) {
timer.cancel();
return;
}
// 生成随机音量值
final volume = Random().nextDouble() * 0.8 + 0.1;
_volumeController?.add(volume);
});
}
/// 模拟发音分析
PronunciationResult _simulateAnalysis(String recordedText, PronunciationItem targetItem) {
final random = Random();
// 基础分数
double baseScore = 60.0 + random.nextDouble() * 30;
// 根据文本相似度调整分数
final similarity = _calculateSimilarity(recordedText.toLowerCase(), targetItem.text.toLowerCase());
final adjustedScore = baseScore + (similarity * 20);
// 确保分数在合理范围内
final finalScore = adjustedScore.clamp(0.0, 100.0);
return PronunciationResult(
score: finalScore,
accuracy: _getAccuracyLevel(finalScore),
feedback: _generateFeedback(finalScore, targetItem),
detailedAnalysis: _generateDetailedAnalysis(finalScore, targetItem),
suggestions: _generateSuggestions(finalScore, targetItem),
recognizedText: recordedText,
);
}
/// 计算文本相似度
double _calculateSimilarity(String text1, String text2) {
if (text1 == text2) return 1.0;
final words1 = text1.split(' ');
final words2 = text2.split(' ');
int matches = 0;
final maxLength = max(words1.length, words2.length);
for (int i = 0; i < min(words1.length, words2.length); i++) {
if (words1[i] == words2[i]) {
matches++;
}
}
return matches / maxLength;
}
/// 获取准确度等级
AccuracyLevel _getAccuracyLevel(double score) {
if (score >= 90) return AccuracyLevel.excellent;
if (score >= 80) return AccuracyLevel.good;
if (score >= 70) return AccuracyLevel.fair;
if (score >= 60) return AccuracyLevel.needsImprovement;
return AccuracyLevel.poor;
}
/// 生成反馈
String _generateFeedback(double score, PronunciationItem item) {
if (score >= 90) {
return '发音非常标准!语调和节奏都很自然。';
} else if (score >= 80) {
return '发音很好,只需要在个别音素上稍作调整。';
} else if (score >= 70) {
return '发音基本正确,建议多练习重音和语调。';
} else if (score >= 60) {
return '发音需要改进,请注意音素的准确性。';
} else {
return '发音需要大幅改进,建议从基础音素开始练习。';
}
}
/// 生成详细分析
Map<String, double> _generateDetailedAnalysis(double score, PronunciationItem item) {
final random = Random();
final baseVariation = (score - 70) / 30; // 基于总分的变化
return {
'音素准确度': (70 + baseVariation * 20 + random.nextDouble() * 10).clamp(0, 100),
'语调自然度': (65 + baseVariation * 25 + random.nextDouble() * 15).clamp(0, 100),
'语速适中度': (75 + baseVariation * 15 + random.nextDouble() * 10).clamp(0, 100),
'重音正确度': (70 + baseVariation * 20 + random.nextDouble() * 10).clamp(0, 100),
'流畅度': (60 + baseVariation * 30 + random.nextDouble() * 15).clamp(0, 100),
};
}
/// 生成改进建议
List<String> _generateSuggestions(double score, PronunciationItem item) {
List<String> suggestions = [];
if (score < 80) {
suggestions.addAll([
'多听标准发音,模仿语调变化',
'注意重音位置,突出重读音节',
]);
}
if (score < 70) {
suggestions.addAll([
'放慢语速,确保每个音素清晰',
'练习困难音素的发音方法',
]);
}
if (score < 60) {
suggestions.addAll([
'从基础音标开始系统学习',
'使用镜子观察口型变化',
]);
}
// 根据练习类型添加特定建议
switch (item.type) {
case PronunciationType.word:
suggestions.add('分解单词,逐个音节练习');
break;
case PronunciationType.sentence:
suggestions.add('注意句子的语调起伏和停顿');
break;
case PronunciationType.phrase:
suggestions.add('练习短语内部的连读现象');
break;
case PronunciationType.phoneme:
suggestions.add('重点练习该音素的舌位和口型');
break;
}
return suggestions;
}
/// 获取发音提示
List<String> getPronunciationTips(PronunciationItem item) {
return item.tips;
}
/// 播放标准发音
Future<void> playStandardAudio(String audioUrl) async {
// TODO: 实现音频播放功能
// 这里应该使用音频播放插件如audioplayers
// 模拟播放时间
await Future.delayed(const Duration(seconds: 2));
}
/// 检查麦克风权限
Future<bool> checkMicrophonePermission() async {
// TODO: 实现权限检查
// 这里应该使用权限插件如permission_handler
// 模拟权限检查
return true;
}
/// 请求麦克风权限
Future<bool> requestMicrophonePermission() async {
// TODO: 实现权限请求
// 这里应该使用权限插件如permission_handler
// 模拟权限请求
return true;
}
}
/// 发音分析结果
class PronunciationResult {
final double score;
final AccuracyLevel accuracy;
final String feedback;
final Map<String, double> detailedAnalysis;
final List<String> suggestions;
final String recognizedText;
PronunciationResult({
required this.score,
required this.accuracy,
required this.feedback,
required this.detailedAnalysis,
required this.suggestions,
required this.recognizedText,
});
}
/// 准确度等级
enum AccuracyLevel {
excellent,
good,
fair,
needsImprovement,
poor,
}
extension AccuracyLevelExtension on AccuracyLevel {
String get displayName {
switch (this) {
case AccuracyLevel.excellent:
return '优秀';
case AccuracyLevel.good:
return '良好';
case AccuracyLevel.fair:
return '一般';
case AccuracyLevel.needsImprovement:
return '需要改进';
case AccuracyLevel.poor:
return '较差';
}
}
String get description {
switch (this) {
case AccuracyLevel.excellent:
return '发音非常标准,语调自然';
case AccuracyLevel.good:
return '发音准确,略有改进空间';
case AccuracyLevel.fair:
return '发音基本正确,需要练习';
case AccuracyLevel.needsImprovement:
return '发音有明显问题,需要改进';
case AccuracyLevel.poor:
return '发音问题较多,需要系统练习';
}
}
String get emoji {
switch (this) {
case AccuracyLevel.excellent:
return '🌟';
case AccuracyLevel.good:
return '👍';
case AccuracyLevel.fair:
return '👌';
case AccuracyLevel.needsImprovement:
return '📈';
case AccuracyLevel.poor:
return '💪';
}
}
}