<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
原生提供功能,Dart module 通過 method channel 非同步呼叫
Flutter 官方的做法,就是自動註冊外掛,
很方便
手動註冊,體現本文的不同
外掛是 AudioRecorderPlugin
class MainActivity: FlutterActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) flutterEngine!!.plugins.add(AudioRecorderPlugin()) } }
主要是訊息回撥
下文依次是,
注意,這裡的錄音許可權包含兩個,麥克風的許可權,和儲存許可權
@Override public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { switch (call.method) { case "start": Log.d(LOG_TAG, "Start"); Log.d(LOG_TAG, "11111____"); String path = call.argument("path"); mExtension = call.argument("extension"); startTime = Calendar.getInstance().getTime(); if (path != null) { mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + path; } else { Log.d(LOG_TAG, "11111____222"); String fileName = String.valueOf(startTime.getTime()); mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + fileName + mExtension; } Log.d(LOG_TAG, mFilePath); startRecording(); isRecording = true; result.success(null); break; case "stop": Log.d(LOG_TAG, "Stop"); stopRecording(); long duration = Calendar.getInstance().getTime().getTime() - startTime.getTime(); Log.d(LOG_TAG, "Duration : " + String.valueOf(duration)); isRecording = false; HashMap<String, Object> recordingResult = new HashMap<>(); recordingResult.put("duration", duration); recordingResult.put("path", mFilePath); recordingResult.put("audioOutputFormat", mExtension); result.success(recordingResult); break; case "isRecording": Log.d(LOG_TAG, "Get isRecording"); result.success(isRecording); break; case "hasPermissions": Log.d(LOG_TAG, "Get hasPermissions"); Context context = _flutterBinding.getApplicationContext(); PackageManager pm = context.getPackageManager(); int hasStoragePerm = pm.checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context.getPackageName()); int hasRecordPerm = pm.checkPermission(Manifest.permission.RECORD_AUDIO, context.getPackageName()); boolean hasPermissions = hasStoragePerm == PackageManager.PERMISSION_GRANTED && hasRecordPerm == PackageManager.PERMISSION_GRANTED; result.success(hasPermissions); break; default: result.notImplemented(); break; } }
使用 wav
的封裝格式,用 AudioRecord
;
其他封裝格式,用 MediaRecorder
上面兩個播放器,有開始錄音和結束錄音功能;
暫停錄音和恢復錄音,則多次開始和結束,再把檔案拼接在一起
建立 MethodChannel
, 非同步呼叫上面的原生功能
class AudioRecorder { static const MethodChannel _channel = const MethodChannel('audio_recorder'); static LocalFileSystem fs = LocalFileSystem(); static Future start(String path, AudioOutputFormat audioOutputFormat) async { String extension; if (path != null) { if (audioOutputFormat != null) { if (_convertStringInAudioOutputFormat(p.extension(path)) != audioOutputFormat) { extension = _convertAudioOutputFormatInString(audioOutputFormat); path += extension; } else { extension = p.extension(path); } } else { if (_isAudioOutputFormat(p.extension(path))) { extension = p.extension(path); } else { extension = ".m4a"; // default value path += extension; } } File file = fs.file(path); if (await file.exists()) { throw new Exception("A file already exists at the path :" + path); } else if (!await file.parent.exists()) { throw new Exception("The specified parent directory does not exist"); } } else { extension = ".m4a"; // default value } return _channel .invokeMethod('start', {"path": path, "extension": extension}); } static Future<Recording?> stop() async { // 把原生帶出來的資訊,放入字典中 Map<String, dynamic> response = Map.from(await _channel.invokeMethod('stop')); if (response != null) { int duration = response['duration']; String fmt = response['audioOutputFormat']; AudioOutputFormat? outputFmt = _convertStringInAudioOutputFormat(fmt); if (fmt != null && outputFmt != null) { Recording recording = new Recording( new Duration(milliseconds: duration), response['path'], outputFmt, response['audioOutputFormat']); return recording; } } else { return null; } }
這裡的外掛名, 為 SwiftAudioRecorderPlugin
public class SwiftAudioRecorderPlugin: NSObject, FlutterPlugin { var isRecording = false var hasPermissions = false var mExtension = "" var mPath = "" var startTime: Date! var audioRecorder: AVAudioRecorder? public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "audio_recorder", binaryMessenger: registrar.messenger()) let instance = SwiftAudioRecorderPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "start": print("start") let dic = call.arguments as! [String : Any] mExtension = dic["extension"] as? String ?? "" mPath = dic["path"] as? String ?? "" startTime = Date() let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] if mPath == "" { mPath = documentsPath + "/" + String(Int(startTime.timeIntervalSince1970)) + ".m4a" } else{ mPath = documentsPath + "/" + mPath } print("path: " + mPath) let settings = [ AVFormatIDKey: getOutputFormatFromString(mExtension), AVSampleRateKey: 12000, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] do { try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, options: AVAudioSession.CategoryOptions.defaultToSpeaker) try AVAudioSession.sharedInstance().setActive(true) let recorder = try AVAudioRecorder(url: URL(string: mPath)!, settings: settings) recorder.delegate = self recorder.record() audioRecorder = recorder } catch { print("fail") result(FlutterError(code: "", message: "Failed to record", details: nil)) } isRecording = true result(nil) case "pause": audioRecorder?.pause() result(nil) case "resume": audioRecorder?.record() result(nil) case "stop": print("stop") audioRecorder?.stop() audioRecorder = nil let duration = Int(Date().timeIntervalSince(startTime as Date) * 1000) isRecording = false var recordingResult = [String : Any]() recordingResult["duration"] = duration recordingResult["path"] = mPath recordingResult["audioOutputFormat"] = mExtension result(recordingResult) case "isRecording": print("isRecording") result(isRecording) case "hasPermissions": print("hasPermissions") switch AVAudioSession.sharedInstance().recordPermission{ case AVAudioSession.RecordPermission.granted: print("granted") hasPermissions = true case AVAudioSession.RecordPermission.denied: print("denied") hasPermissions = false case AVAudioSession.RecordPermission.undetermined: print("undetermined") AVAudioSession.sharedInstance().requestRecordPermission() { [unowned self] allowed in DispatchQueue.main.async { if allowed { self.hasPermissions = true } else { self.hasPermissions = false } } } default:() } result(hasPermissions) default: result(FlutterMethodNotImplemented) } } }
邏輯與安卓外掛類似,
因為 iOS 的 AVAudioRecorder
對 pause
和 resume
操作,支援友好,
所以增添了暫停和恢復錄音功能
iOS 端的許可權比安卓許可權,少一個
僅需要錄音麥克風許可權
public class SwiftAudioRecorderPlugin: NSObject, FlutterPlugin { var isRecording = false var hasPermissions = false var mExtension = "" var mPath = "" var startTime: Date! var audioRecorder: AVAudioRecorder? public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "audio_recorder", binaryMessenger: registrar.messenger()) let instance = SwiftAudioRecorderPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "start": print("start") let dic = call.arguments as! [String : Any] mExtension = dic["extension"] as? String ?? "" mPath = dic["path"] as? String ?? "" startTime = Date() let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] if mPath == "" { mPath = documentsPath + "/" + String(Int(startTime.timeIntervalSince1970)) + ".m4a" } else{ mPath = documentsPath + "/" + mPath } print("path: " + mPath) let settings = [ AVFormatIDKey: getOutputFormatFromString(mExtension), AVSampleRateKey: 12000, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] do { try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, options: AVAudioSession.CategoryOptions.defaultToSpeaker) try AVAudioSession.sharedInstance().setActive(true) let recorder = try AVAudioRecorder(url: URL(string: mPath)!, settings: settings) recorder.delegate = self recorder.record() audioRecorder = recorder } catch { print("fail") result(FlutterError(code: "", message: "Failed to record", details: nil)) } isRecording = true result(nil) case "pause": audioRecorder?.pause() result(nil) case "resume": audioRecorder?.record() result(nil) case "stop": print("stop") audioRecorder?.stop() audioRecorder = nil let duration = Int(Date().timeIntervalSince(startTime as Date) * 1000) isRecording = false var recordingResult = [String : Any]() recordingResult["duration"] = duration recordingResult["path"] = mPath recordingResult["audioOutputFormat"] = mExtension result(recordingResult) case "isRecording": print("isRecording") result(isRecording) case "hasPermissions": print("hasPermissions") switch AVAudioSession.sharedInstance().recordPermission{ case AVAudioSession.RecordPermission.granted: print("granted") hasPermissions = true case AVAudioSession.RecordPermission.denied: print("denied") hasPermissions = false case AVAudioSession.RecordPermission.undetermined: print("undetermined") AVAudioSession.sharedInstance().requestRecordPermission() { [unowned self] allowed in DispatchQueue.main.async { if allowed { self.hasPermissions = true } else { self.hasPermissions = false } } } default:() } result(hasPermissions) default: result(FlutterMethodNotImplemented) } } }
通過判斷平臺,Platform.isIOS
,
給 iOS 裝置,增加完善的功能
@override Widget build(BuildContext context) { final VoidCallback tapFirst; if (Platform.isAndroid && name == kEnd) { tapFirst = _audioEnd; } else { tapFirst = _audioGoOn; } List<Widget> views = [ ElevatedButton( child: Text( name, style: Theme.of(context).textTheme.headline4, ), onPressed: tapFirst, ) ]; if (Platform.isIOS && name != kStarted) { views.add(SizedBox(height: 80)); views.add(ElevatedButton( child: Text( kEnd, style: Theme.of(context).textTheme.headline4, ), onPressed: _audioEnd, )); } return Scaffold( appBar: AppBar( // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. title: Text(widget.title), ), body: Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. child: Column( mainAxisAlignment: MainAxisAlignment.center, children: views, ), ), // This trailing comma makes auto-formatting nicer for build methods. ); }
到此這篇關於Android使用Flutter實現錄音外掛的文章就介紹到這了,更多相關Android Flutter錄音內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45