Flutterで音声の録音・再生を実装する方法

こんにちは、株式会社Pentagonの小寺です。

今回はマイクを使用して録音・再生を行う方法を説明します。

【こんな人に読んで欲しい】

flutterを用いてマイクで録音しようと考えている方
flutterを用いて音声を再生しようと考えている方

【この記事を読むメリット】

マイクを使用して録音・再生する方法がわかる

目次

使用するパッケージ

今回は録音をするためにrecord、再生するためにjust_audioの2種類を使用します。
また、録音したデータの保存先パスを取得する必要があるためpath_providerのパッケージを使用します。

https://pub.dev/packages/record
https://pub.dev/packages/just_audio
https://pub.dev/packages/path_provider

Androidの設定

android/src/main/AndroidManifest.xmlファイル内で、端末のマイク機能を使用するために以下のコードを追記してください。

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

iOSの設定

iOSでもAndroidと同様に端末のマイク機能を使用するための設定を行います。
ios/Runner/info.plistに以下のコードを追記してください。

<key>NSMicrophoneUsageDescription</key>
<string>このアプリではマイクを使用します</string>

内にある文章は実際にアプリで端末機能の使用許可を求める際に表示されるポップアップの文言です。必要に応じて文言を変更してください。

マイクで録音する

今回作成したマイクを使用して録音するためのコードを記載します。

audio_record_state.dart

class AudioRecordState with _$AudioRecordState {
  const factory AudioRecordState({
    @Default(false) bool isRecording,
  }) = _AudioRecordState;
}

audio_record_stateではisRecordingというbool値で録音中かどうかを判断しています。

audio_record_service.dart

final audioRecordServiceProvider =
    StateNotifierProvider<AudioRecordService, AudioRecordState>((ref) {
  return AudioRecordService();
});

class AudioRecordService extends StateNotifier<AudioRecordState> {
  AudioRecordService() : super(const AudioRecordState());

  final _recorder = Record();

  Future<bool> get _hasPermission async {
    return _recorder.hasPermission();
  }

  Future<void> startRecording() async {
    if (!(await _hasPermission)) {
      return;
    }
    await _checkEncoder();
    final filePath = await getLocalFilePath();
    state = state.copyWith(isRecording: true);
    await _recorder.start(
      path: filePath,
    );
  }

  Future<void> stopRecording() async {
    await _recorder.stop();
    state = state.copyWith(isRecording: false);
  }

  Future<void> _checkEncoder() async {
    final isSupported = await _recorder.isEncoderSupported(
      AudioEncoder.aacLc,
    );
    if (kDebugMode) {
      print('${AudioEncoder.aacLc.name} supported: $isSupported');
    }
  }
}

typedef LocalFilePath = String;

// FIXME: プロダクトに入れる場合は可変にするなどの修正が必要
Future<LocalFilePath> getLocalFilePath() async {
  // 録音ファイル保存先取得
  final directory = await getApplicationDocumentsDirectory();
  final filePath = '${directory.path}/audio_sample.m4a';
  return filePath;
}

audio_record_service内では録音するための機能を実装しています。
_hasPermissionでは端末のマイク機能を使用する権限があるかの確認をしています。
startRecording()で録音を行う機能を実装しています。
_hasPermissionで録音する権限がなければstartRecordingで何も返さず、権限があればファイルのパスを生成し、そのパス内に録音を保存します。
stopRecording()で、録音を終了し、isRecordingをfalseにします。

再生する

次に作成した再生するためのコードを記載します。

audio_play_state.dart

class AudioPlayState with _$AudioPlayState {
  const factory AudioPlayState({
    @Default(false) bool isPlaying,
  }) = _AudioPlayState;
}

audio_play_stateではisPlayingというbool値で再生中かどうかを判断しています。

audio_play_service.dart

final audioPlayServiceProvider =
    StateNotifierProvider<AudioPlayService, AudioPlayState>((ref) {
  return AudioPlayService();
});

class AudioPlayService extends StateNotifier<AudioPlayState> {
  AudioPlayService() : super(const AudioPlayState()) {
    _listenPlayer();
  }

  final _player = AudioPlayer();

  Future<void> startPlaying() async {
    state = state.copyWith(isPlaying: true);
    await _setUpAudioFile();
    await _player.play();
  }

  Future<void> pausePlaying() async {
    await _player.pause();
    state = state.copyWith(isPlaying: false);
  }

  void _listenPlayer() {
    _player.playbackEventStream.listen((event) {
      switch (event.processingState) {
        case ProcessingState.completed:
          state = state.copyWith(isPlaying: false);
          break;
      }
    });
  }

  Future<void> _setUpAudioFile() async {
    final filePath = await getLocalFilePath();
    await _player.setFilePath(filePath);
  }
}

audio_play_service内では録音したファイルを再生するための機能を実装しています。
startPlaying()内にある_setUpAudioFile()で録音したファイルのパスをセットします。その後録音した内容を再生します。
pausePlaying()は再生を一時停止する機能です。
_listenPlayer()は音声再生が終了した際にisPlayingをfalseにしています。

注意点

音声まわりは端末のマイク機能を使用するため、権限がないと録音ができないエラーが発生します。エラーが発生した際はAndroidとiOSの設定ができているのか、_hasPermissionでprint()し、trueが返ってきているか等を確認してください。また、録音する際にfilePathが生成されfilePath内に録音内容が入っているか、再生する際にfilePathがセットできているか確認するようにしてください。

まとめ

ポイントとしては、録音時にファイルパスを生成し、そのファイルパスに録音をすることが重要です。再生する際は生成したファイルパスをセットすることで再生が可能です。
また、今回の実装ではiOSとAndroidのみの実装になっていますがwebでも録音・再生を実装することが可能なので使用するパッケージにあるURLを参考にコードを実行してみてください。
今回作成しているAudioRecordServiceやAudioPlayServiceはクラスの再利用性が高く、プロジェクトに組み込むのが容易ですので録音や再生の機能を使用する際はぜひ使用してください。

おまけ

Flutterに関する知識を深めたい方には、『Flutterの特徴・メリット・デメリットを徹底解説』という記事がおすすめです。

この記事では、Flutter アプリ開発の基本から、Flutter とは何か、そして実際のFlutter アプリ 事例を通じて、その将来性やメリット、デメリットまで詳しく解説しています。
Flutterを使ったアプリ開発に興味がある方、またはその潜在的な可能性を理解したい方にとって、必見の内容となっています。

ぜひ一度ご覧ください。

採用情報はこちら
目次