Flutter-flutter_sound錄音與播放

語言: CN / TW / HK

highlight: a11y-dark

外掛介紹:

flutter_sound這個外掛可以實現iOS和Android平臺的錄音和播放功能。即可以播放本地音訊檔案,也可以播放遠端URL檔案。在這裡我講介紹這個外掛的用法以及碰到的一些常見問題如何解決。

  • flutter_sound支援多種錄音格式

  • flutter_sound支援多種播放格式

  • flutter_sound支援音訊振幅大小

外掛資訊:

外掛地址:http://github.com/ryanheise/audio_session

外掛版本:9.2.9

外掛使用前的準備工作

設定麥克風許可權描述

  • iOS:需要在info.plist檔案新增一下許可權

oc <key>NSMicrophoneUsageDescription</key> <string>描述你使用麥克風用來幹嘛</string>

截圖2022-04-23 下午3.09.07.png

注意:還需要在Podfile檔案中配置

```js post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config|

    config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
      '$(inherited)',
      ## dart: PermissionGroup.microphone
      'PERMISSION_MICROPHONE=1',
    ]
  end
end

end ```

還需要在iOS工程中增加libc++.tbd庫,具體路徑

截圖2022-05-14 17.05.47.png

  • Android:需要設定AndroidManifest.xml

oc <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

截圖2022-04-23 下午3.11.31.png

這裡還用了下面幾個外掛

許可權管理外掛 permission_handler

外掛資訊:permission_handler: ^9.2.0

外掛地址:http://pub.flutter-io.cn/packages/permission_handler

音訊硬體配置外掛 audio_session

外掛資訊:audio_session: ^0.1.6

外掛地址:http://github.com/ryanheise/audio_session

動畫外掛

外掛資訊:lottie: 1.2.1

外掛地址:http://pub.flutter-io.cn/packages/flutter_lottie

常用的方法

錄音常見的方法

初始化錄音物件

flutter FlutterSoundRecorder recorderModule = FlutterSoundRecorder();

開啟錄音

flutter Future<void> init() async { //開啟錄音 await recorderModule.openRecorder(); //設定訂閱計時器 await recorderModule .setSubscriptionDuration(const Duration(milliseconds: 10)); //初始化日期外掛 await initializeDateFormatting(); }

麥克風許可權

```flutter Future getPermissionStatus() async { Permission permission = Permission.microphone; //granted 通過,denied 被拒絕,permanentlyDenied 拒絕且不在提示 PermissionStatus status = await permission.status; if (status.isGranted) { return true; } else if (status.isDenied) { requestPermission(permission); } else if (status.isPermanentlyDenied) { openAppSettings(); } else if (status.isRestricted) { requestPermission(permission); } else {} return false; }

///申請許可權 void requestPermission(Permission permission) async { PermissionStatus status = await permission.request(); if (status.isPermanentlyDenied) { openAppSettings(); } } ```

開始錄音

```flutter /// 開始錄音 _startRecorder() async { try { //獲取麥克風許可權 await getPermissionStatus().then((value) async { if (!value) { return; } //使用者允許使用麥克風之後開始錄音 Directory tempDir = await getTemporaryDirectory(); var time = DateTime.now().millisecondsSinceEpoch; String path = '${tempDir.path}/$time${ext[Codec.aacADTS.index]}';

  //這裡我錄製的是aac格式的,還有其他格式
  await recorderModule.startRecorder(
    toFile: path,
    codec: Codec.aacADTS,
    bitRate: 8000,
    numChannels: 1,
    sampleRate: 8000,
  );

  /// 監聽錄音
  _recorderSubscription = recorderModule.onProgress!.listen((e) {
    var date = DateTime.fromMillisecondsSinceEpoch(
        e.duration.inMilliseconds,
        isUtc: true);
    var txt = DateFormat('mm:ss:SS', 'en_GB').format(date);
    //設定了最大錄音時長
    if (date.second >= _maxLength) {
      _stopRecorder();
      return;
    }
    setState(() {
      //更新錄音時長
      _recordText = txt.substring(1, 5);
    });
  });
  setState(() {
     //更新錄音狀態和錄音檔案路徑
    _state = RecordPlayState.recording;
    _path = path;
  });
});

} catch (err) { setState(() { _stopRecorder(); _state = RecordPlayState.record; _cancelRecorderSubscriptions(); }); } } ```

結束錄音

```flutter /// 結束錄音 _stopRecorder() async { try { await recorderModule.stopRecorder(); _cancelRecorderSubscriptions(); // _getDuration(); } catch (err) {} setState(() { _state = RecordPlayState.record; }); }

///銷燬錄音 void dispose() { super.dispose(); _cancelRecorderSubscriptions(); _releaseFlauto(); }

/// 取消錄音監聽 void _cancelRecorderSubscriptions() { if (_recorderSubscription != null) { _recorderSubscription!.cancel(); _recorderSubscription = null; } }

/// 釋放錄音 Future _releaseFlauto() async { try { await recorderModule.closeRecorder(); } catch (e) {} }

/// 判斷檔案是否存在 Future _fileExists(String path) async { return await File(path).exists(); } ```

播放常見的方法

初始化播放器

flutter FlutterSoundPlayer playerModule = FlutterSoundPlayer();

初始化操作

```flutter init() async { await playerModule.closePlayer(); await playerModule.openPlayer(); await playerModule .setSubscriptionDuration(const Duration(milliseconds: 10));

//這塊是設定音訊,暫時沒用到可以不用設定 final session = await AudioSession.instance; await session.configure(AudioSessionConfiguration( avAudioSessionCategory: AVAudioSessionCategory.playAndRecord, avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.allowBluetooth | AVAudioSessionCategoryOptions.defaultToSpeaker, avAudioSessionMode: AVAudioSessionMode.spokenAudio, avAudioSessionRouteSharingPolicy: AVAudioSessionRouteSharingPolicy.defaultPolicy, avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none, androidAudioAttributes: const AndroidAudioAttributes( contentType: AndroidAudioContentType.speech, flags: AndroidAudioFlags.none, usage: AndroidAudioUsage.voiceCommunication, ), androidAudioFocusGainType: AndroidAudioFocusGainType.gain, androidWillPauseWhenDucked: true, )); } ```

開始播放

```flutter ///開始播放,這裡做了一個播放狀態的回撥 void startPlayer(PlayStateBack callBack) async { try { if (path.contains('http')) { await playerModule.startPlayer( fromURI: path, codec: Codec.mp3, sampleRate: 44000, whenFinished: () { stopPlayer(); callBack(0); }); } else { //判斷檔案是否存在 if (await _fileExists(path)) { if (playerModule.isPlaying) { playerModule.stopPlayer(); } await playerModule.startPlayer( fromURI: path, codec: Codec.aacADTS, sampleRate: 44000, whenFinished: () { stopPlayer(); callBack(0); }); } else {} }

//監聽播放進度
_playerSubscription = playerModule.onProgress!.listen((e) {});
callBack(1);

} catch (err) { callBack(0); } } ```

結束播放

```flutter /// 結束播放 void stopPlayer() async { try { await playerModule.stopPlayer(); cancelPlayerSubscriptions(); } catch (err) {} }

/// 取消播放監聽 void cancelPlayerSubscriptions() { if (_playerSubscription != null) { _playerSubscription!.cancel(); _playerSubscription = null; } }

///獲取播放狀態 Future getPlayState() async { return await playerModule.getPlayerState(); }

/// 釋放播放器 void releaseFlauto() async { try { await playerModule.closePlayer(); } catch (e) { print(e); } }

/// 判斷檔案是否存在 Future _fileExists(String path) async { return await File(path).exists(); } ```

動畫實現

在進行錄音和播放的過程中難免使用到動畫,這裡我說下如何載入gif和動畫檔案

載入GIF動畫

flutter Visibility( visible: (item.playing.value == 1) ? true : false, child: Image.asset('assets/-comm/comm_audio_paly.gif', width: 20, height: 20,), replacement: const Image( image: AssetImage('assets/-comm/comm_audio_icon.png'), width: 20, height: 20, ), )

載入動畫檔案

flutter Lottie.asset('assets/-comm/record_audio_animation.json', height: 25, width: ScreenAdapter.screenWidth() -ScreenAdapter.width(160), animate: true)

上傳檔案

上傳音訊檔案

flutter var map = { "file": MultipartFile.fromBytes( await File.fromUri(Uri(path: path)).readAsBytes(), filename: "$fileName.mp3", contentType: MediaType.parse("audio/mp3")) };

總結

上面介紹瞭如何錄音,如何播放本地和遠端音訊檔案,以及如何實現動畫,在錄製完音訊檔案後如何上傳,這些都是我們平常使用這個功能會遇到的問題。在使用的過程中遇到的問題也有列出,希望對您有所幫助。 這裡有記錄整合到原生專案遇到的問題