audio_recorder.dart 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:file/local.dart';
  4. import 'package:flutter/services.dart';
  5. import 'package:path/path.dart' as p;
  6. /// Audio Recorder Plugin
  7. class FlutterAudioRecorder {
  8. static const MethodChannel _channel = MethodChannel('flutter_audio_recorder2');
  9. static const String DEFAULT_EXTENSION = '.m4a';
  10. static LocalFileSystem fs = LocalFileSystem();
  11. String? _path;
  12. String? _extension;
  13. Recording? _recording;
  14. String? _sampleRate;
  15. Future? _initRecorder;
  16. Future? get initialized => _initRecorder;
  17. Recording? get recording => _recording;
  18. /// 构造方法
  19. /// path audio文件路径
  20. FlutterAudioRecorder(String path, {AudioFormat? audioFormat, String sampleRate = "16000"}) {
  21. _initRecorder = _init(path, audioFormat, sampleRate);
  22. }
  23. /// 初始化 FlutterAudioRecorder 对象
  24. Future _init(String? path, AudioFormat? audioFormat, String sampleRate) async {
  25. String extension;
  26. String extensionInPath;
  27. if (path != null) {
  28. // Extension(.xyz) of Path
  29. extensionInPath = p.extension(path);
  30. // Use AudioFormat
  31. if (audioFormat != null) {
  32. // .m4a != .m4a
  33. if (_stringToAudioFormat(extensionInPath) != audioFormat) {
  34. // use AudioOutputFormat
  35. extension = _audioFormatToString(audioFormat);
  36. path = p.withoutExtension(path) + extension;
  37. } else {
  38. extension = p.extension(path);
  39. }
  40. } else {
  41. // Else, Use Extension that inferred from Path
  42. // if extension in path is valid
  43. if (_isValidAudioFormat(extensionInPath)) {
  44. extension = extensionInPath;
  45. } else {
  46. extension = DEFAULT_EXTENSION; // default value
  47. path += extension;
  48. }
  49. }
  50. File file = fs.file(path);
  51. if (await file.exists()) {
  52. throw Exception("A file already exists at the path :" + path);
  53. } else if (!await file.parent.exists()) {
  54. throw Exception("The specified parent directory does not exist ${file.parent}");
  55. }
  56. } else {
  57. extension = DEFAULT_EXTENSION; // default value
  58. }
  59. _path = path;
  60. _extension = extension;
  61. _sampleRate = sampleRate;
  62. late Map<String, Object> response;
  63. var result = await _channel.invokeMethod('init',
  64. {"path": _path, "extension": _extension, "sampleRate": _sampleRate});
  65. if (result != false) {
  66. response = Map.from(result);
  67. }
  68. _recording = Recording()
  69. ..status = _stringToRecordingStatus(response['status'] as String?)
  70. ..metering = AudioMetering(
  71. averagePower: -120, peakPower: -120, isMeteringEnabled: true);
  72. return;
  73. }
  74. /// Request an initialized recording instance to be [started]
  75. /// Once executed, audio recording will start working and
  76. /// a file will be generated in user's file system
  77. Future start() async {
  78. return _channel.invokeMethod('start');
  79. }
  80. /// Request currently [Recording] recording to be [Paused]
  81. /// Note: Use [current] to get latest state of recording after [pause]
  82. Future pause() async {
  83. return _channel.invokeMethod('pause');
  84. }
  85. /// Request currently [Paused] recording to continue
  86. Future resume() async {
  87. return _channel.invokeMethod('resume');
  88. }
  89. /// Request the recording to stop
  90. /// Once its stopped, the recording file will be finalized
  91. /// and will not be start, resume, pause anymore.
  92. Future<Recording?> stop() async {
  93. Map<String, Object> response;
  94. var result = await _channel.invokeMethod('stop');
  95. if (result != null) {
  96. response = Map.from(result);
  97. _responseToRecording(response);
  98. }
  99. return _recording;
  100. }
  101. /// Ask for current status of recording
  102. /// Returns the result of current recording status
  103. /// Metering level, Duration, Status...
  104. Future<Recording?> current({int channel = 0}) async {
  105. Map<String, Object> response;
  106. var result = await _channel.invokeMethod('current', {"channel": channel});
  107. if (result != null && _recording?.status != RecordingStatus.Stopped) {
  108. response = Map.from(result);
  109. _responseToRecording(response);
  110. }
  111. return _recording;
  112. }
  113. /// Returns the result of record permission
  114. /// if not determined(app first launch),
  115. /// this will ask user to whether grant the permission
  116. static Future<bool?> get hasPermissions async {
  117. bool? hasPermission = await _channel.invokeMethod('hasPermissions');
  118. return hasPermission;
  119. }
  120. /// util - response msg to recording object.
  121. void _responseToRecording(Map<String, Object>? response) {
  122. if (response == null) return;
  123. _recording!.duration =
  124. Duration(milliseconds: response['duration'] as int);
  125. _recording!.path = response['path'] as String?;
  126. _recording!.audioFormat =
  127. _stringToAudioFormat(response['audioFormat'] as String?);
  128. _recording!.extension = response['audioFormat'] as String?;
  129. _recording!.metering = AudioMetering(
  130. peakPower: response['peakPower'] as double?,
  131. averagePower: response['averagePower'] as double?,
  132. isMeteringEnabled: response['isMeteringEnabled'] as bool?);
  133. _recording!.status =
  134. _stringToRecordingStatus(response['status'] as String?);
  135. }
  136. /// util - verify if extension string is supported
  137. static bool _isValidAudioFormat(String extension) {
  138. switch (extension) {
  139. case ".wav":
  140. case ".mp4":
  141. case ".aac":
  142. case ".m4a":
  143. return true;
  144. default:
  145. return false;
  146. }
  147. }
  148. /// util - Convert String to Enum
  149. static AudioFormat? _stringToAudioFormat(String? extension) {
  150. switch (extension) {
  151. case ".wav":
  152. return AudioFormat.WAV;
  153. case ".mp4":
  154. case ".aac":
  155. case ".m4a":
  156. return AudioFormat.AAC;
  157. default:
  158. return null;
  159. }
  160. }
  161. /// Convert Enum to String
  162. static String _audioFormatToString(AudioFormat format) {
  163. switch (format) {
  164. case AudioFormat.WAV:
  165. return ".wav";
  166. case AudioFormat.AAC:
  167. return ".m4a";
  168. default:
  169. return ".m4a";
  170. }
  171. }
  172. /// util - Convert String to Enum
  173. static RecordingStatus _stringToRecordingStatus(String? status) {
  174. switch (status) {
  175. case "unset":
  176. return RecordingStatus.Unset;
  177. case "initialized":
  178. return RecordingStatus.Initialized;
  179. case "recording":
  180. return RecordingStatus.Recording;
  181. case "paused":
  182. return RecordingStatus.Paused;
  183. case "stopped":
  184. return RecordingStatus.Stopped;
  185. default:
  186. return RecordingStatus.Unset;
  187. }
  188. }
  189. }
  190. /// Recording Object - represent a recording file
  191. class Recording {
  192. /// File path
  193. String? path;
  194. /// Extension
  195. String? extension;
  196. /// Duration in milliseconds
  197. Duration? duration;
  198. /// Audio format
  199. AudioFormat? audioFormat;
  200. /// Metering
  201. AudioMetering? metering;
  202. /// Is currently recording
  203. RecordingStatus? status;
  204. }
  205. /// Audio Metering Level - describe the metering level of microphone when recording
  206. class AudioMetering {
  207. /// Represent peak level of given short duration
  208. double? peakPower;
  209. /// Represent average level of given short duration
  210. double? averagePower;
  211. /// Is metering enabled in system
  212. bool? isMeteringEnabled;
  213. AudioMetering({this.peakPower, this.averagePower, this.isMeteringEnabled});
  214. }
  215. /// Represent the status of a Recording
  216. enum RecordingStatus {
  217. /// Recording not initialized
  218. Unset,
  219. /// Ready for start recording
  220. Initialized,
  221. /// Currently recording
  222. Recording,
  223. /// Currently Paused
  224. Paused,
  225. /// This specific recording Stopped, cannot be start again
  226. Stopped,
  227. }
  228. /// Audio Format,
  229. /// WAV is lossless audio, recommended
  230. enum AudioFormat {
  231. AAC,
  232. WAV,
  233. }