经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Flutter » 查看文章
Android使用Flutter实现录音插件
来源:jb51  时间:2022/8/1 19:09:56  对本文有异议

原生提供功能,Dart module 通过 method channel 异步调用

安卓部分

手动注册

Flutter 官方的做法,就是自动注册插件,

很方便

手动注册,体现本文的不同

插件是 AudioRecorderPlugin

  1. class MainActivity: FlutterActivity() {
  2. override fun onCreate(savedInstanceState: Bundle?) {
  3. super.onCreate(savedInstanceState)
  4. flutterEngine!!.plugins.add(AudioRecorderPlugin())
  5. }
  6. }

Android和Dart的通讯

主要是消息回调

下文依次是,

  • 开始录音
  • 结束录音
  • 正在录音
  • 是否有录音权限

注意,这里的录音权限包含两个,麦克风的权限,和存储权限

  1. @Override
  2. public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
  3. switch (call.method) {
  4. case "start":
  5. Log.d(LOG_TAG, "Start");
  6. Log.d(LOG_TAG, "11111____");
  7. String path = call.argument("path");
  8. mExtension = call.argument("extension");
  9. startTime = Calendar.getInstance().getTime();
  10. if (path != null) {
  11. mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + path;
  12. } else {
  13. Log.d(LOG_TAG, "11111____222");
  14. String fileName = String.valueOf(startTime.getTime());
  15. mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + fileName + mExtension;
  16. }
  17. Log.d(LOG_TAG, mFilePath);
  18. startRecording();
  19. isRecording = true;
  20. result.success(null);
  21. break;
  22. case "stop":
  23. Log.d(LOG_TAG, "Stop");
  24. stopRecording();
  25. long duration = Calendar.getInstance().getTime().getTime() - startTime.getTime();
  26. Log.d(LOG_TAG, "Duration : " + String.valueOf(duration));
  27. isRecording = false;
  28. HashMap<String, Object> recordingResult = new HashMap<>();
  29. recordingResult.put("duration", duration);
  30. recordingResult.put("path", mFilePath);
  31. recordingResult.put("audioOutputFormat", mExtension);
  32. result.success(recordingResult);
  33. break;
  34. case "isRecording":
  35. Log.d(LOG_TAG, "Get isRecording");
  36. result.success(isRecording);
  37. break;
  38. case "hasPermissions":
  39. Log.d(LOG_TAG, "Get hasPermissions");
  40. Context context = _flutterBinding.getApplicationContext();
  41. PackageManager pm = context.getPackageManager();
  42. int hasStoragePerm = pm.checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context.getPackageName());
  43. int hasRecordPerm = pm.checkPermission(Manifest.permission.RECORD_AUDIO, context.getPackageName());
  44. boolean hasPermissions = hasStoragePerm == PackageManager.PERMISSION_GRANTED && hasRecordPerm == PackageManager.PERMISSION_GRANTED;
  45. result.success(hasPermissions);
  46. break;
  47. default:
  48. result.notImplemented();
  49. break;
  50. }
  51. }

安卓录音

使用 wav 的封装格式,用 AudioRecord;

其他封装格式,用 MediaRecorder

上面两个播放器,有开始录音和结束录音功能;

暂停录音和恢复录音,则多次开始和结束,再把文件拼接在一起

Dart module部分

建立 MethodChannel, 异步调用上面的原生功能

  1. class AudioRecorder {
  2. static const MethodChannel _channel = const MethodChannel('audio_recorder');
  3. static LocalFileSystem fs = LocalFileSystem();
  4. static Future start(String path, AudioOutputFormat audioOutputFormat) async {
  5. String extension;
  6. if (path != null) {
  7. if (audioOutputFormat != null) {
  8. if (_convertStringInAudioOutputFormat(p.extension(path)) !=
  9. audioOutputFormat) {
  10. extension = _convertAudioOutputFormatInString(audioOutputFormat);
  11. path += extension;
  12. } else {
  13. extension = p.extension(path);
  14. }
  15. } else {
  16. if (_isAudioOutputFormat(p.extension(path))) {
  17. extension = p.extension(path);
  18. } else {
  19. extension = ".m4a"; // default value
  20. path += extension;
  21. }
  22. }
  23. File file = fs.file(path);
  24. if (await file.exists()) {
  25. throw new Exception("A file already exists at the path :" + path);
  26. } else if (!await file.parent.exists()) {
  27. throw new Exception("The specified parent directory does not exist");
  28. }
  29. } else {
  30. extension = ".m4a"; // default value
  31. }
  32. return _channel
  33. .invokeMethod('start', {"path": path, "extension": extension});
  34. }
  35. static Future<Recording?> stop() async {
  36. // 把原生带出来的信息,放入字典中
  37. Map<String, dynamic> response =
  38. Map.from(await _channel.invokeMethod('stop'));
  39. if (response != null) {
  40. int duration = response['duration'];
  41. String fmt = response['audioOutputFormat'];
  42. AudioOutputFormat? outputFmt = _convertStringInAudioOutputFormat(fmt);
  43. if (fmt != null && outputFmt != null) {
  44. Recording recording = new Recording(
  45. new Duration(milliseconds: duration),
  46. response['path'],
  47. outputFmt,
  48. response['audioOutputFormat']);
  49. return recording;
  50. }
  51. } else {
  52. return null;
  53. }
  54. }

iOS部分

手动注册插件

这里的插件名, 为 SwiftAudioRecorderPlugin

  1. public class SwiftAudioRecorderPlugin: NSObject, FlutterPlugin {
  2. var isRecording = false
  3. var hasPermissions = false
  4. var mExtension = ""
  5. var mPath = ""
  6. var startTime: Date!
  7. var audioRecorder: AVAudioRecorder?
  8. public static func register(with registrar: FlutterPluginRegistrar) {
  9. let channel = FlutterMethodChannel(name: "audio_recorder", binaryMessenger: registrar.messenger())
  10. let instance = SwiftAudioRecorderPlugin()
  11. registrar.addMethodCallDelegate(instance, channel: channel)
  12. }
  13. public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
  14. switch call.method {
  15. case "start":
  16. print("start")
  17. let dic = call.arguments as! [String : Any]
  18. mExtension = dic["extension"] as? String ?? ""
  19. mPath = dic["path"] as? String ?? ""
  20. startTime = Date()
  21. let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
  22. if mPath == "" {
  23. mPath = documentsPath + "/" + String(Int(startTime.timeIntervalSince1970)) + ".m4a"
  24. }
  25. else{
  26. mPath = documentsPath + "/" + mPath
  27. }
  28. print("path: " + mPath)
  29. let settings = [
  30. AVFormatIDKey: getOutputFormatFromString(mExtension),
  31. AVSampleRateKey: 12000,
  32. AVNumberOfChannelsKey: 1,
  33. AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
  34. ]
  35. do {
  36. try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, options: AVAudioSession.CategoryOptions.defaultToSpeaker)
  37. try AVAudioSession.sharedInstance().setActive(true)
  38.  
  39. let recorder = try AVAudioRecorder(url: URL(string: mPath)!, settings: settings)
  40. recorder.delegate = self
  41. recorder.record()
  42. audioRecorder = recorder
  43. } catch {
  44. print("fail")
  45. result(FlutterError(code: "", message: "Failed to record", details: nil))
  46. }
  47. isRecording = true
  48. result(nil)
  49. case "pause":
  50. audioRecorder?.pause()
  51. result(nil)
  52. case "resume":
  53. audioRecorder?.record()
  54. result(nil)
  55. case "stop":
  56. print("stop")
  57. audioRecorder?.stop()
  58. audioRecorder = nil
  59. let duration = Int(Date().timeIntervalSince(startTime as Date) * 1000)
  60. isRecording = false
  61. var recordingResult = [String : Any]()
  62. recordingResult["duration"] = duration
  63. recordingResult["path"] = mPath
  64. recordingResult["audioOutputFormat"] = mExtension
  65. result(recordingResult)
  66. case "isRecording":
  67. print("isRecording")
  68. result(isRecording)
  69. case "hasPermissions":
  70. print("hasPermissions")
  71. switch AVAudioSession.sharedInstance().recordPermission{
  72. case AVAudioSession.RecordPermission.granted:
  73. print("granted")
  74. hasPermissions = true
  75. case AVAudioSession.RecordPermission.denied:
  76. print("denied")
  77. hasPermissions = false
  78. case AVAudioSession.RecordPermission.undetermined:
  79. print("undetermined")
  80. AVAudioSession.sharedInstance().requestRecordPermission() { [unowned self] allowed in
  81. DispatchQueue.main.async {
  82. if allowed {
  83. self.hasPermissions = true
  84. } else {
  85. self.hasPermissions = false
  86. }
  87. }
  88. }
  89. default:()
  90. }
  91. result(hasPermissions)
  92. default:
  93. result(FlutterMethodNotImplemented)
  94. }
  95. }
  96. }

iOS插件

逻辑与安卓插件类似,

因为 iOS 的 AVAudioRecorderpauseresume 操作,支持友好,

所以增添了暂停和恢复录音功能

iOS 端的权限比安卓权限,少一个

仅需要录音麦克风权限

  1. public class SwiftAudioRecorderPlugin: NSObject, FlutterPlugin {
  2. var isRecording = false
  3. var hasPermissions = false
  4. var mExtension = ""
  5. var mPath = ""
  6. var startTime: Date!
  7. var audioRecorder: AVAudioRecorder?
  8. public static func register(with registrar: FlutterPluginRegistrar) {
  9. let channel = FlutterMethodChannel(name: "audio_recorder", binaryMessenger: registrar.messenger())
  10. let instance = SwiftAudioRecorderPlugin()
  11. registrar.addMethodCallDelegate(instance, channel: channel)
  12. }
  13. public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
  14. switch call.method {
  15. case "start":
  16. print("start")
  17. let dic = call.arguments as! [String : Any]
  18. mExtension = dic["extension"] as? String ?? ""
  19. mPath = dic["path"] as? String ?? ""
  20. startTime = Date()
  21. let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
  22. if mPath == "" {
  23. mPath = documentsPath + "/" + String(Int(startTime.timeIntervalSince1970)) + ".m4a"
  24. }
  25. else{
  26. mPath = documentsPath + "/" + mPath
  27. }
  28. print("path: " + mPath)
  29. let settings = [
  30. AVFormatIDKey: getOutputFormatFromString(mExtension),
  31. AVSampleRateKey: 12000,
  32. AVNumberOfChannelsKey: 1,
  33. AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
  34. ]
  35. do {
  36. try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, options: AVAudioSession.CategoryOptions.defaultToSpeaker)
  37. try AVAudioSession.sharedInstance().setActive(true)
  38. let recorder = try AVAudioRecorder(url: URL(string: mPath)!, settings: settings)
  39. recorder.delegate = self
  40. recorder.record()
  41. audioRecorder = recorder
  42. } catch {
  43. print("fail")
  44. result(FlutterError(code: "", message: "Failed to record", details: nil))
  45. }
  46. isRecording = true
  47. result(nil)
  48. case "pause":
  49. audioRecorder?.pause()
  50. result(nil)
  51. case "resume":
  52. audioRecorder?.record()
  53. result(nil)
  54. case "stop":
  55. print("stop")
  56. audioRecorder?.stop()
  57. audioRecorder = nil
  58. let duration = Int(Date().timeIntervalSince(startTime as Date) * 1000)
  59. isRecording = false
  60. var recordingResult = [String : Any]()
  61. recordingResult["duration"] = duration
  62. recordingResult["path"] = mPath
  63. recordingResult["audioOutputFormat"] = mExtension
  64. result(recordingResult)
  65. case "isRecording":
  66. print("isRecording")
  67. result(isRecording)
  68. case "hasPermissions":
  69. print("hasPermissions")
  70. switch AVAudioSession.sharedInstance().recordPermission{
  71. case AVAudioSession.RecordPermission.granted:
  72. print("granted")
  73. hasPermissions = true
  74. case AVAudioSession.RecordPermission.denied:
  75. print("denied")
  76. hasPermissions = false
  77. case AVAudioSession.RecordPermission.undetermined:
  78. print("undetermined")
  79. AVAudioSession.sharedInstance().requestRecordPermission() { [unowned self] allowed in
  80. DispatchQueue.main.async {
  81. if allowed {
  82. self.hasPermissions = true
  83. } else {
  84. self.hasPermissions = false
  85. }
  86. }
  87. }
  88. default:()
  89. }
  90. result(hasPermissions)
  91. default:
  92. result(FlutterMethodNotImplemented)
  93. }
  94. }
  95. }

Dart调用部分

通过判断平台,Platform.isIOS,

给 iOS 设备,增加完善的功能

  1. @override
  2. Widget build(BuildContext context) {
  3. final VoidCallback tapFirst;
  4. if (Platform.isAndroid && name == kEnd) {
  5. tapFirst = _audioEnd;
  6. } else {
  7. tapFirst = _audioGoOn;
  8. }
  9. List<Widget> views = [
  10. ElevatedButton(
  11. child: Text(
  12. name,
  13. style: Theme.of(context).textTheme.headline4,
  14. ),
  15. onPressed: tapFirst,
  16. )
  17. ];
  18. if (Platform.isIOS && name != kStarted) {
  19. views.add(SizedBox(height: 80));
  20. views.add(ElevatedButton(
  21. child: Text(
  22. kEnd,
  23. style: Theme.of(context).textTheme.headline4,
  24. ),
  25. onPressed: _audioEnd,
  26. ));
  27. }
  28. return Scaffold(
  29. appBar: AppBar(
  30. // Here we take the value from the MyHomePage object that was created by
  31. // the App.build method, and use it to set our appbar title.
  32. title: Text(widget.title),
  33. ),
  34. body: Center(
  35. // Center is a layout widget. It takes a single child and positions it
  36. // in the middle of the parent.
  37. child: Column(
  38. mainAxisAlignment: MainAxisAlignment.center,
  39. children: views,
  40. ),
  41. ), // This trailing comma makes auto-formatting nicer for build methods.
  42. );
  43. }

github repo

到此这篇关于Android使用Flutter实现录音插件的文章就介绍到这了,更多相关Android Flutter录音内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号