2025-08-21 10:50:38 +08:00
|
|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
|
|
import 'package:loopin/IM/im_core.dart';
|
|
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
|
import 'package:record/record.dart';
|
|
|
|
|
|
|
|
|
|
class VoiceService {
|
|
|
|
|
static final VoiceService _instance = VoiceService._internal();
|
|
|
|
|
factory VoiceService() => _instance;
|
|
|
|
|
VoiceService._internal();
|
|
|
|
|
|
|
|
|
|
final AudioRecorder _recorder = AudioRecorder();
|
2025-09-22 14:41:47 +08:00
|
|
|
|
|
2025-08-21 10:50:38 +08:00
|
|
|
|
String? _voiceFilePath;
|
|
|
|
|
DateTime? _startTime;
|
|
|
|
|
|
|
|
|
|
/// 开始录音
|
|
|
|
|
Future<bool> startRecording() async {
|
|
|
|
|
if (await _recorder.hasPermission()) {
|
2025-09-22 14:41:47 +08:00
|
|
|
|
// final dir = await getTemporaryDirectory(); // 临时目录
|
|
|
|
|
final dir = await getApplicationDocumentsDirectory(); // 临时目录在ios不行
|
2025-08-21 10:50:38 +08:00
|
|
|
|
final filePath = '${dir.path}/${DateTime.now().millisecondsSinceEpoch}.m4a';
|
|
|
|
|
_voiceFilePath = filePath;
|
|
|
|
|
_startTime = DateTime.now();
|
|
|
|
|
|
|
|
|
|
await _recorder.start(
|
|
|
|
|
const RecordConfig(
|
|
|
|
|
encoder: AudioEncoder.aacLc,
|
|
|
|
|
bitRate: 128000,
|
|
|
|
|
sampleRate: 44100,
|
|
|
|
|
),
|
|
|
|
|
path: filePath,
|
|
|
|
|
);
|
|
|
|
|
logger.i('开始录音,文件路径: $filePath');
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
logger.e("没有录音权限");
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 停止录音并返回文件路径
|
|
|
|
|
Future<Map<String, dynamic>?> stopRecording() async {
|
|
|
|
|
await _recorder.stop();
|
|
|
|
|
|
|
|
|
|
if (_voiceFilePath != null && File(_voiceFilePath!).existsSync() && _startTime != null) {
|
|
|
|
|
final duration = DateTime.now().difference(_startTime!);
|
|
|
|
|
final durationSeconds = duration.inSeconds;
|
|
|
|
|
if (durationSeconds < 1 || durationSeconds > 60) {
|
|
|
|
|
logger.w('录音时长不在允许范围(1-60秒),删除文件: $_voiceFilePath,时长: $durationSeconds 秒');
|
|
|
|
|
try {
|
|
|
|
|
await File(_voiceFilePath!).delete();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
logger.e('删除录音文件失败: $e');
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
logger.i('录音完成: $_voiceFilePath,时长: ${duration.inSeconds}秒');
|
|
|
|
|
return {
|
|
|
|
|
'path': _voiceFilePath,
|
|
|
|
|
'duration': duration.inMilliseconds,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
logger.e("没有录到音频文件");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 取消录音(删除文件)
|
|
|
|
|
Future<void> cancelRecording() async {
|
|
|
|
|
await _recorder.stop();
|
|
|
|
|
if (_voiceFilePath != null) {
|
|
|
|
|
final file = File(_voiceFilePath!);
|
|
|
|
|
if (await file.exists()) {
|
|
|
|
|
await file.delete();
|
|
|
|
|
logger.i('录音已取消并删除');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_voiceFilePath = null;
|
|
|
|
|
}
|
|
|
|
|
}
|