【Flutter】録音・再生アプリを作ってみた

Flutter

録音して再生するだけのシンプルなアプリの作成方法をメモします。使用したパッケージは「record」「audioplayers」「path_provider」になります。録音ファイルはローカルストレージに保存します。

ソースコードはGitHubに上げています。

完成品のイメージは次のとおりです。

方針

方針は次のとおりです。

  • マイクで音を拾う → 「record
  • 録音ファイルをローカルストレージに保存 → 「path_provider
  • ローカルストレージの録音ファイルを再生 → 「audioplayers

パッケージのインストール

pubspec.yamlに下記を追加して、「flutter pub get」を実行します。

dependencies:
  audioplayers: ^1.0.1
  record: ^4.4.0
  path_provider: ^2.0.11

そして、main.dartで以下をインポートして使用します。

import 'package:record/record.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:path_provider/path_provider.dart';

マイクとローカルストレージの使用権限を取得

Androidの権限を追加します。(iOSは省略します)

android/app/src/main/AndroidManifest.xmlに下記を追加します。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.sample_record_audioplayer">

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

    <application
           .........

録音の開始・停止

録音のパッケージは、以下のようにインスタンス化して使用します。

Record record = Record(); // 録音

録音中かどうかの状態をフラグで持たせました。

bool _recordingStatus = false; // 録音状態(true:録音中/false:停止中)

録音の開始と停止は次のように行いました。

// 録音開始
void _startRecording() async {
  // 権限確認
  if (await record.hasPermission()) {
    // 録音ファイルを指定
    final directory = await getApplicationDocumentsDirectory();
    String pathToWrite = directory.path;
    final localFile = pathToWrite + '/sample.m4a';

    // 録音開始
    await record.start(
      path: localFile,
      encoder: AudioEncoder.aacLc,
      bitRate: 128000,
      samplingRate: 44100,
    );
  }
}

// 録音停止
void _stopRecording() async {
  await record.stop();
}

少し説明すると、以下で権限確認を行っています。許可された場合、中身の処理に移ります。

if (await record.hasPermission()) {
  ....

更に、以下でローカルストレージへのアクセスを行い、ファイル’sample.m4a’を取得しています。

final directory = await getApplicationDocumentsDirectory();
String pathToWrite = directory.path;
final localFile = pathToWrite + '/sample.m4a';

録音の開始、停止は以下で行っています。

// 開始
await record.start() 
// 停止
await record.stop()

録音ファイルの再生・停止

再生のパッケージは、以下のようにインスタンス化して使用します。

AudioPlayer audioPlayer = AudioPlayer(); // 再生

再生中かどうかの状態をフラグで持たせました。

bool _playingStatus = false; // 再生状態(true:再生中/false:停止中)

録音ファイルの再生と停止(一時停止)は次のようにしました。

※再生中に一時停止し、再び再生する際に停止した位置から再生する仕様としています

// 再生開始
void _startPlaying() async {
  // 再生するファイルを指定
  final directory = await getApplicationDocumentsDirectory();
  String pathToWrite = directory.path;
  final localFile = pathToWrite + '/sample.m4a';

  // 再生開始
  await audioPlayer.play(DeviceFileSource(localFile));

  // 再生終了後、ステータス変更
  audioPlayer.onPlayerComplete.listen((event) {
    setState(() {
      _playingStatus = false;
    });
  });
}

// 再生一時停止
void _pausePlaying() async {
  await audioPlayer.pause();
}

録音のときと同様に、以下でローカルストレージへのアクセスを行い、ファイル’sample.m4a’を取得しています。

// 再生するファイルを指定
final directory = await getApplicationDocumentsDirectory();
String pathToWrite = directory.path;
final localFile = pathToWrite + '/sample.m4a';

録音ファイルの再生と停止(一時停止)は以下で行っています。

// 再生開始
await audioPlayer.play(DeviceFileSource(localFile));
// 再生一時停止
await audioPlayer.pause();

全体

StatefulWidgetのState<>部分の全体を以下に載せておきます。

class _MyHomePageState extends State<MyHomePage> {
  bool _recordingStatus = false; // 録音状態(true:録音中/false:停止中)
  bool _playingStatus = false; // 再生状態(true:再生中/false:停止中)
  Record record = Record();
  AudioPlayer audioPlayer = AudioPlayer();

  // 録音開始
  void _startRecording() async {
    // 権限確認
    if (await record.hasPermission()) {
      // 録音ファイルを指定
      final directory = await getApplicationDocumentsDirectory();
      String pathToWrite = directory.path;
      final localFile = pathToWrite + '/sample.m4a';

      // 録音開始
      await record.start(
        path: localFile,
        encoder: AudioEncoder.aacLc, // by default
        bitRate: 128000, // by default
        samplingRate: 44100, // by default
      );
    }
  }

  // 録音停止
  void _stopRecording() async {
    await record.stop();
  }

  // 再生開始
  void _startPlaying() async {
    // 再生するファイルを指定
    final directory = await getApplicationDocumentsDirectory();
    String pathToWrite = directory.path;
    final localFile = pathToWrite + '/sample.m4a';

    // 再生開始
    // AudioPlayer audioPlayer = AudioPlayer();
    await audioPlayer.play(DeviceFileSource(localFile));

    // 再生終了後、ステータス変更
    audioPlayer.onPlayerComplete.listen((event) {
      setState(() {
        _playingStatus = false;
      });
    });
  }

  // 再生一時停止
  void _pausePlaying() async {
    await audioPlayer.pause();
  }

  // 録音の開始停止
  void _recordingHandle() {
    // 再生中の場合は何もしない
    if (_playingStatus) {return;}

    setState(() {
      _recordingStatus = !_recordingStatus;
      if (_recordingStatus) {
        _startRecording();
      } else {
        _stopRecording();
      }
    });
  }

  // 再生の開始停止
  void _playingHandle() {
    setState(() {
      // 録音中の場合は録音停止
      if (_recordingStatus) {
        _recordingStatus = false;
        _stopRecording();
      }

      _playingStatus = !_playingStatus;
      if (_playingStatus) {
        _startPlaying();
      } else {
        _pausePlaying();
      }
    });
  }

  // ステータスメッセージ
  String _statusMessage() {
    String msg = '';

    if(_recordingStatus) {
      if (_playingStatus) {
        msg = '-'; // 録音○、再生○(発生しない)
      } else {
        msg = '録音中'; // 録音×、再生○
      }
    } else {
      if (_playingStatus) {
        msg = '再生中'; // 録音○、再生×
      } else {
        msg = '待機中'; // 録音×、再生×
      }
    }

    return msg;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        padding: const EdgeInsets.all(50.0),
        child: Column(
          children: <Widget>[
            // ステータスメッセージ
            Text(
              _statusMessage(),
              style: const TextStyle(
                color: Colors.blue,
                fontSize: 40.0,
                fontWeight: FontWeight.w600,
              ),
            ),
            // 縦スペース
            const SizedBox(height: 30,),
            // 2つのボタン
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                // 録音ボタン
                TextButton(
                  onPressed: _recordingHandle,
                  style: TextButton.styleFrom(
                    backgroundColor: Colors.lightBlue,
                  ),
                  child: Text(
                    _recordingStatus ? "停止" : '録音',
                    style: const TextStyle(color: Colors.white, fontSize: 20.0),
                  ),
                ),
                // 再生ボタン
                SizedBox(
                  height: 50, // ボタンのサイズ調整
                  child: ElevatedButton(
                    onPressed: () {_playingHandle();},
                    style: ElevatedButton.styleFrom(
                      shape: const CircleBorder(),
                      primary: Colors.lightBlue,
                    ),
                    child: _playingStatus ? const Icon(Icons.stop) : const Icon(Icons.play_arrow),
                  ),
                ),
              ],
            ),
          ],
        ));
  }
}

以上になります。

タイトルとURLをコピーしました