init
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
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 '💪';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user