liuyuqi-dellpc 2 years ago
parent
commit
8e9fdc776c

+ 19 - 16
lib/pages/home_page.dart

@@ -22,15 +22,6 @@ class _HomePageState extends State<HomePage> {
   @override
   Widget build(BuildContext context) {
     return Scaffold(
-      floatingActionButton: FloatingActionButton(
-        onPressed: () {},
-        child: InkWell(
-          child: const Icon(Icons.mic),
-          onTap: () {
-            showRecord(context);
-          },
-        ),
-      ),
       appBar: AppBar(
         title: const Text(
           "录音机App",
@@ -38,19 +29,31 @@ class _HomePageState extends State<HomePage> {
         ),
         centerTitle: true,
       ),
-      body: Column(
+      floatingActionButton: FloatingActionButton(
+        onPressed: () {
+          showRecord(context);
+        },
+        child: const Icon(Icons.mic),
+      ),
+      body: Stack(
         children: [
-          Expanded(
-            flex: 2,
-            child: RecordList(
-              records: records!,
-            ),
+          Center(child: Text("天问科技")),
+          Column(
+            children: [
+              Expanded(
+                flex: 2,
+                child: RecordList(
+                  records: records!,
+                ),
+              ),
+            ],
           ),
         ],
       ),
     );
   }
 
+  /// 回调函数
   _onFinish() {
     records!.clear();
     appDir!.list().listen((onData) {
@@ -62,7 +65,7 @@ class _HomePageState extends State<HomePage> {
     });
   }
 
-  //底部弹出录音按钮模态框
+  /// 底部弹出录音按钮模态框
   void showRecord(BuildContext context) {
     showModalBottomSheet<void>(
       context: context,

+ 276 - 0
lib/plugins/audio_recorder.dart

@@ -0,0 +1,276 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:file/local.dart';
+import 'package:flutter/services.dart';
+import 'package:path/path.dart' as p;
+
+/// Audio Recorder Plugin
+class FlutterAudioRecorder {
+
+  static const MethodChannel _channel = MethodChannel('flutter_audio_recorder2');
+  static const String DEFAULT_EXTENSION = '.m4a';
+  static LocalFileSystem fs = LocalFileSystem();
+
+  String? _path;
+  String? _extension;
+  Recording? _recording;
+  String? _sampleRate;
+
+  Future? _initRecorder;
+  Future? get initialized => _initRecorder;
+  Recording? get recording => _recording;
+  /// 构造方法
+  /// path audio文件路径
+  FlutterAudioRecorder(String path, {AudioFormat? audioFormat, String sampleRate = "16000"}) {
+    _initRecorder = _init(path, audioFormat, sampleRate);
+  }
+
+  /// 初始化 FlutterAudioRecorder 对象
+  Future _init(String? path, AudioFormat? audioFormat, String sampleRate) async {
+    String extension;
+    String extensionInPath;
+    if (path != null) {
+      // Extension(.xyz) of Path
+      extensionInPath = p.extension(path);
+      // Use AudioFormat
+      if (audioFormat != null) {
+        // .m4a != .m4a
+        if (_stringToAudioFormat(extensionInPath) != audioFormat) {
+          // use AudioOutputFormat
+          extension = _audioFormatToString(audioFormat);
+          path = p.withoutExtension(path) + extension;
+        } else {
+          extension = p.extension(path);
+        }
+      } else {
+        // Else, Use Extension that inferred from Path
+        // if extension in path is valid
+        if (_isValidAudioFormat(extensionInPath)) {
+          extension = extensionInPath;
+        } else {
+          extension = DEFAULT_EXTENSION; // default value
+          path += extension;
+        }
+      }
+      File file = fs.file(path);
+      if (await file.exists()) {
+        throw Exception("A file already exists at the path :" + path);
+      } else if (!await file.parent.exists()) {
+        throw Exception("The specified parent directory does not exist ${file.parent}");
+      }
+    } else {
+      extension = DEFAULT_EXTENSION; // default value
+    }
+    _path = path;
+    _extension = extension;
+    _sampleRate = sampleRate;
+
+    late Map<String, Object> response;
+    var result = await _channel.invokeMethod('init',
+        {"path": _path, "extension": _extension, "sampleRate": _sampleRate});
+
+    if (result != false) {
+      response = Map.from(result);
+    }
+
+    _recording = Recording()
+      ..status = _stringToRecordingStatus(response['status'] as String?)
+      ..metering = AudioMetering(
+          averagePower: -120, peakPower: -120, isMeteringEnabled: true);
+
+    return;
+  }
+
+  /// Request an initialized recording instance to be [started]
+  /// Once executed, audio recording will start working and
+  /// a file will be generated in user's file system
+  Future start() async {
+    return _channel.invokeMethod('start');
+  }
+
+  /// Request currently [Recording] recording to be [Paused]
+  /// Note: Use [current] to get latest state of recording after [pause]
+  Future pause() async {
+    return _channel.invokeMethod('pause');
+  }
+
+  /// Request currently [Paused] recording to continue
+  Future resume() async {
+    return _channel.invokeMethod('resume');
+  }
+
+  /// Request the recording to stop
+  /// Once its stopped, the recording file will be finalized
+  /// and will not be start, resume, pause anymore.
+  Future<Recording?> stop() async {
+    Map<String, Object> response;
+    var result = await _channel.invokeMethod('stop');
+
+    if (result != null) {
+      response = Map.from(result);
+      _responseToRecording(response);
+    }
+
+    return _recording;
+  }
+
+  /// Ask for current status of recording
+  /// Returns the result of current recording status
+  /// Metering level, Duration, Status...
+  Future<Recording?> current({int channel = 0}) async {
+    Map<String, Object> response;
+
+    var result = await _channel.invokeMethod('current', {"channel": channel});
+
+    if (result != null && _recording?.status != RecordingStatus.Stopped) {
+      response = Map.from(result);
+      _responseToRecording(response);
+    }
+
+    return _recording;
+  }
+
+  /// Returns the result of record permission
+  /// if not determined(app first launch),
+  /// this will ask user to whether grant the permission
+  static Future<bool?> get hasPermissions async {
+    bool? hasPermission = await _channel.invokeMethod('hasPermissions');
+    return hasPermission;
+  }
+
+  ///  util - response msg to recording object.
+  void _responseToRecording(Map<String, Object>? response) {
+    if (response == null) return;
+
+    _recording!.duration =
+    Duration(milliseconds: response['duration'] as int);
+    _recording!.path = response['path'] as String?;
+    _recording!.audioFormat =
+        _stringToAudioFormat(response['audioFormat'] as String?);
+    _recording!.extension = response['audioFormat'] as String?;
+    _recording!.metering = AudioMetering(
+        peakPower: response['peakPower'] as double?,
+        averagePower: response['averagePower'] as double?,
+        isMeteringEnabled: response['isMeteringEnabled'] as bool?);
+    _recording!.status =
+        _stringToRecordingStatus(response['status'] as String?);
+  }
+
+  /// util - verify if extension string is supported
+  static bool _isValidAudioFormat(String extension) {
+    switch (extension) {
+      case ".wav":
+      case ".mp4":
+      case ".aac":
+      case ".m4a":
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  /// util - Convert String to Enum
+  static AudioFormat? _stringToAudioFormat(String? extension) {
+    switch (extension) {
+      case ".wav":
+        return AudioFormat.WAV;
+      case ".mp4":
+      case ".aac":
+      case ".m4a":
+        return AudioFormat.AAC;
+      default:
+        return null;
+    }
+  }
+
+  /// Convert Enum to String
+  static String _audioFormatToString(AudioFormat format) {
+    switch (format) {
+      case AudioFormat.WAV:
+        return ".wav";
+      case AudioFormat.AAC:
+        return ".m4a";
+      default:
+        return ".m4a";
+    }
+  }
+
+  /// util - Convert String to Enum
+  static RecordingStatus _stringToRecordingStatus(String? status) {
+    switch (status) {
+      case "unset":
+        return RecordingStatus.Unset;
+      case "initialized":
+        return RecordingStatus.Initialized;
+      case "recording":
+        return RecordingStatus.Recording;
+      case "paused":
+        return RecordingStatus.Paused;
+      case "stopped":
+        return RecordingStatus.Stopped;
+      default:
+        return RecordingStatus.Unset;
+    }
+  }
+}
+
+/// Recording Object - represent a recording file
+class Recording {
+  /// File path
+  String? path;
+
+  /// Extension
+  String? extension;
+
+  /// Duration in milliseconds
+  Duration? duration;
+
+  /// Audio format
+  AudioFormat? audioFormat;
+
+  /// Metering
+  AudioMetering? metering;
+
+  /// Is currently recording
+  RecordingStatus? status;
+}
+
+/// Audio Metering Level - describe the metering level of microphone when recording
+class AudioMetering {
+  /// Represent peak level of given short duration
+  double? peakPower;
+
+  /// Represent average level of given short duration
+  double? averagePower;
+
+  /// Is metering enabled in system
+  bool? isMeteringEnabled;
+
+  AudioMetering({this.peakPower, this.averagePower, this.isMeteringEnabled});
+}
+
+/// Represent the status of a Recording
+enum RecordingStatus {
+  /// Recording not initialized
+  Unset,
+
+  /// Ready for start recording
+  Initialized,
+
+  /// Currently recording
+  Recording,
+
+  /// Currently Paused
+  Paused,
+
+  /// This specific recording Stopped, cannot be start again
+  Stopped,
+}
+
+/// Audio Format,
+/// WAV is lossless audio, recommended
+enum AudioFormat {
+  AAC,
+  WAV,
+}

+ 17 - 0
lib/plugins/generated_plugin_registrant.dart

@@ -0,0 +1,17 @@
+//
+// Generated file. Do not edit.
+//
+
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:audioplayers/web/audioplayers_web.dart';
+import 'package:fluttertoast/fluttertoast_web.dart';
+
+import 'package:flutter_web_plugins/flutter_web_plugins.dart';
+
+// ignore: public_member_api_docs
+void registerPlugins(Registrar registrar) {
+  AudioplayersPlugin.registerWith(registrar);
+  FluttertoastWebPlugin.registerWith(registrar);
+  registrar.registerMessageHandler();
+}

+ 1 - 1
lib/views/record_list.dart

@@ -183,7 +183,7 @@ class _Presso extends StatelessWidget {
   Widget build(BuildContext context) {
     return ButtonTheme(
       minWidth: 48.0,
-      child: RaisedButton(
+      child: ElevatedButton(
           child: Icon(
             ico,
             color: Colors.white,

+ 32 - 21
lib/views/recorder.dart

@@ -2,10 +2,12 @@ import 'dart:async';
 import 'dart:io';
 
 import 'package:flutter/material.dart';
+import 'package:flutter_audio_recorder/plugins/audio_recorder.dart';
 import 'package:fluttertoast/fluttertoast.dart';
 import 'package:path_provider/path_provider.dart';
 import 'package:permission_handler/permission_handler.dart';
 
+/// 录音模态框
 class RecorderView extends StatefulWidget {
   final Function save;
 
@@ -67,27 +69,27 @@ class _RecorderViewState extends State<RecorderView> {
               height: 20,
             ),
             Text(
-              (_current == null)
-                  ? "0:0:0:0"
-                  : _current!.duration.toString(),
+              (_current == null) ? "0:0:0:0" : _current!.duration.toString(),
               style: const TextStyle(color: Colors.black, fontSize: 20),
             ),
             const SizedBox(
               height: 20,
             ),
             stop == false
-                ? RaisedButton(
-                    color: Colors.orange,
+                ? ElevatedButton(
+                    style: ButtonStyle(
+                        shape: MaterialStateProperty.all(RoundedRectangleBorder(
+                          borderRadius: BorderRadius.circular(10),
+                        )),
+                        textStyle: MaterialStateProperty.all(
+                            const TextStyle(color: Colors.orange))),
                     onPressed: () async {
                       await _onRecordButtonPressed();
                       setState(() {});
                     },
-                    shape: RoundedRectangleBorder(
-                      borderRadius: BorderRadius.circular(10),
-                    ),
                     child: Column(
                       children: [
-                        Container(
+                        SizedBox(
                           width: 80,
                           height: 80,
                           child: Icon(
@@ -111,15 +113,19 @@ class _RecorderViewState extends State<RecorderView> {
                     child: Row(
                       mainAxisAlignment: MainAxisAlignment.spaceBetween,
                       children: [
-                        RaisedButton(
-                          color: colo,
+                        ElevatedButton(
+                          style: ButtonStyle(
+                              textStyle: MaterialStateProperty.all(
+                                  TextStyle(color: colo)),
+                              shape: MaterialStateProperty.all(
+                                RoundedRectangleBorder(
+                                  borderRadius: BorderRadius.circular(10),
+                                ),
+                              )),
                           onPressed: () async {
                             await _onRecordButtonPressed();
                             setState(() {});
                           },
-                          shape: RoundedRectangleBorder(
-                            borderRadius: BorderRadius.circular(10),
-                          ),
                           child: Container(
                             width: 80,
                             height: 80,
@@ -130,18 +136,23 @@ class _RecorderViewState extends State<RecorderView> {
                             ),
                           ),
                         ),
-                        RaisedButton(
-                          color: Colors.orange,
+                        ElevatedButton(
+                          style: ButtonStyle(
+                              shape: MaterialStateProperty.all(
+                                RoundedRectangleBorder(
+                                  borderRadius: BorderRadius.circular(10),
+                                ),
+                              ),
+                              textStyle: MaterialStateProperty.all(TextStyle(
+                                color: Colors.orange,
+                              ))),
                           onPressed: _currentStatus != RecordingStatus.Unset
                               ? _stop
                               : null,
-                          shape: RoundedRectangleBorder(
-                            borderRadius: BorderRadius.circular(10),
-                          ),
                           child: Container(
                             width: 80,
                             height: 80,
-                            child: Icon(
+                            child: const Icon(
                               Icons.stop,
                               color: Colors.white,
                               size: 50,
@@ -209,7 +220,7 @@ class _RecorderViewState extends State<RecorderView> {
       _current = recording!;
     });
 
-    const tick = const Duration(milliseconds: 50);
+    const tick = Duration(milliseconds: 50);
     Timer.periodic(tick, (Timer t) async {
       if (_currentStatus == RecordingStatus.Stopped) {
         t.cancel();

+ 14 - 0
pubspec.lock

@@ -156,6 +156,13 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.7.0"
+  mime:
+    dependency: transitive
+    description:
+      name: mime
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.1"
   path:
     dependency: transitive
     description:
@@ -247,6 +254,13 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "4.2.4"
+  share:
+    dependency: "direct main"
+    description:
+      name: share
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.4"
   sky_engine:
     dependency: transitive
     description: flutter

+ 2 - 2
pubspec.yaml

@@ -1,7 +1,7 @@
 name: flutter_audio_recorder
 description: A new Flutter application.
 publish_to: 'none' # Remove this line if you wish to publish to pub.dev
-version: 1.0.0+1
+version: 1.1.0+1
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
@@ -16,7 +16,7 @@ dependencies:
   audioplayers: ^0.20.1
 
 #  rflutter_alert: ^2.0.2
-#  share: ^2.0.4
+  share: ^2.0.4
 
 dev_dependencies:
   flutter_test: