controller.dart 6.1 KB

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