controller.dart 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import 'dart:io';
  2. import 'dart:typed_data';
  3. import 'package:electronic_assistant/base/base_controller.dart';
  4. import 'package:electronic_assistant/data/repositories/talk_repository.dart';
  5. import 'package:electronic_assistant/dialog/alert_dialog.dart';
  6. import 'package:electronic_assistant/module/record/constants.dart';
  7. import 'package:electronic_assistant/module/talk/view.dart';
  8. import 'package:electronic_assistant/utils/http_handler.dart';
  9. import 'package:electronic_assistant/utils/mmkv_util.dart';
  10. import 'package:electronic_assistant/utils/toast_util.dart';
  11. import 'package:get/get.dart';
  12. import 'package:path_provider/path_provider.dart';
  13. import 'package:record/record.dart';
  14. import 'package:uuid/uuid.dart';
  15. import '../../utils/pcm_wav_converter.dart';
  16. import '../../widget/frame_animation_view.dart';
  17. class RecordController extends BaseController {
  18. static const String keyLastRecordId = "last_record_id";
  19. final FrameAnimationController frameAnimationController =
  20. FrameAnimationController(autoPlay: false);
  21. final Rx<RecordStatus> currentStatus = RecordStatus.pending.obs;
  22. final RxDouble currentDuration = 0.0.obs;
  23. final AudioRecorder record = AudioRecorder();
  24. final RecordConfig recordConfig = const RecordConfig(
  25. encoder: AudioEncoder.pcm16bits,
  26. bitRate: 128000,
  27. sampleRate: 44100,
  28. numChannels: 2);
  29. late final String lastRecordId;
  30. @override
  31. void onInit() {
  32. super.onInit();
  33. _initLastRecordId();
  34. _initLastRecordStatus();
  35. }
  36. void _initLastRecordId() {
  37. String? lastRecordId = KVUtil.getString(keyLastRecordId, null);
  38. if (lastRecordId == null || lastRecordId.isEmpty) {
  39. this.lastRecordId = const Uuid().v4();
  40. KVUtil.putString(keyLastRecordId, this.lastRecordId);
  41. } else {
  42. this.lastRecordId = lastRecordId;
  43. }
  44. }
  45. Future<void> _initLastRecordStatus() async {
  46. var currentRecordFile = await _getCurrentRecordFile();
  47. var fileLength = currentRecordFile.lengthSync();
  48. if (currentRecordFile.existsSync() && fileLength > 0) {
  49. _changeRecordStatus(RecordStatus.paused);
  50. currentDuration.value = _getPcmDuration(
  51. fileLength, recordConfig.sampleRate, 16, recordConfig.numChannels);
  52. }
  53. }
  54. void addShortcut() {}
  55. void onBackClick() {
  56. if (currentStatus.value == RecordStatus.pending ||
  57. currentStatus.value == RecordStatus.paused) {
  58. Get.back();
  59. } else {
  60. EAAlertDialog.show(
  61. title: "是否保存当前录音?",
  62. confirmText: "确定",
  63. cancelText: "取消",
  64. confirmOnTap: () {
  65. _saveCurrentRecord();
  66. EAAlertDialog.dismiss();
  67. },
  68. cancelOnTap: () {
  69. EAAlertDialog.dismiss();
  70. _stopRecord().then((_) => Get.back());
  71. },
  72. );
  73. }
  74. }
  75. void onActionClick() {
  76. RecordStatus nextStatus = currentStatus.value.nextStatus;
  77. if (nextStatus == RecordStatus.recording) {
  78. _startOrContinueRecord();
  79. } else {
  80. _stopRecord();
  81. }
  82. }
  83. void onCancelClick() {
  84. if (currentStatus.value == RecordStatus.pending) {
  85. return;
  86. }
  87. EAAlertDialog.show(
  88. title: "是否删除当前录音?",
  89. confirmText: "删除",
  90. cancelText: "取消",
  91. confirmOnTap: () {
  92. _deleteCurrentRecord();
  93. EAAlertDialog.dismiss();
  94. },
  95. cancelOnTap: () {
  96. EAAlertDialog.dismiss();
  97. },
  98. );
  99. }
  100. void onSaveClick() {
  101. if (currentStatus.value == RecordStatus.pending) {
  102. return;
  103. }
  104. _saveCurrentRecord();
  105. }
  106. Future<void> _startOrContinueRecord() async {
  107. bool hasPermission = await record.hasPermission();
  108. if (!hasPermission) {
  109. _onRecordPermissionDenied();
  110. return;
  111. }
  112. File targetFile = await _getCurrentRecordFile();
  113. Stream<Uint8List> recordStream = await record.startStream(recordConfig);
  114. _changeRecordStatus(RecordStatus.recording);
  115. recordStream.listen((data) async {
  116. targetFile.writeAsBytesSync(data, mode: FileMode.append);
  117. currentDuration.value = currentDuration.value +
  118. _getPcmDuration(data.length, recordConfig.sampleRate, 16,
  119. recordConfig.numChannels);
  120. }, onDone: () {
  121. _changeRecordStatus(RecordStatus.paused);
  122. }, onError: (error) {
  123. _changeRecordStatus(RecordStatus.paused);
  124. });
  125. }
  126. _onRecordPermissionDenied() {}
  127. Future<void> _stopRecord() {
  128. return record.stop().then((_) => _changeRecordStatus(RecordStatus.paused));
  129. }
  130. Future<File> _getCurrentRecordFile() async {
  131. Directory documentDir = await getApplicationDocumentsDirectory();
  132. File file = File("${documentDir.path}/.atmob/record/$lastRecordId");
  133. if (!file.existsSync()) {
  134. file.createSync(recursive: true);
  135. }
  136. return file;
  137. }
  138. double _getPcmDuration(
  139. int fileSize, int sampleRate, int bitDepth, int channels) {
  140. final bytesPerSecond = sampleRate * (bitDepth / 8) * channels;
  141. final durationInSeconds = fileSize / bytesPerSecond;
  142. return durationInSeconds;
  143. }
  144. Future<void> _deleteCurrentRecord() async {
  145. await _stopRecord();
  146. _getCurrentRecordFile().then((file) {
  147. if (file.existsSync()) {
  148. file.deleteSync();
  149. }
  150. }).then((_) {
  151. currentDuration.value = 0;
  152. _changeRecordStatus(RecordStatus.pending);
  153. });
  154. }
  155. Future<void> _saveCurrentRecord() async {
  156. await _stopRecord();
  157. talkRepository
  158. .talkCreate(lastRecordId, currentDuration.value.toInt())
  159. .then((talkInfo) async {
  160. File pcmFile = await _getCurrentRecordFile();
  161. if (pcmFile.existsSync()) {
  162. File wavFile = await getRecordFile(talkInfo.id);
  163. PcmWavConverter.convert(pcmFile, wavFile, recordConfig.sampleRate,
  164. recordConfig.numChannels, 16);
  165. pcmFile.delete();
  166. Get.back();
  167. TalkPage.start(talkInfo);
  168. } else {
  169. throw Exception("pcm file not found");
  170. }
  171. }).catchError((error) {
  172. if (error is ServerErrorException) {
  173. ToastUtil.showToast("${error.message}");
  174. } else {
  175. ToastUtil.showToast("保存失败, 请重试");
  176. }
  177. });
  178. }
  179. void _changeRecordStatus(RecordStatus status) {
  180. currentStatus.value = status;
  181. status == RecordStatus.recording ? frameAnimationController.play() : null;
  182. }
  183. static Future<File> getRecordFile(String talkId) async {
  184. Directory documentDir = await getApplicationDocumentsDirectory();
  185. return File("${documentDir.path}/.atmob/record/$talkId.wav");
  186. }
  187. }