Flutterメディア再生のすすめ

この記事は[レコチョク Advent Calendar 2021](https://qiita.com/advent-calendar/2021/recochoku)の1日目の記事となります。

https://qiita.com/advent-calendar/2021/recochoku

株式会社レコチョクでAndroidアプリ開発をしている木村です。
弊社では音楽に関する複数のサービスを提供しており、オーディオや動画などのメディア再生は中核を担う機能の一つです。  
  
今回はマルチプラットフォームフレームワークとして定番となっているFlutterを利用してメディアを再生する方法を調べてみました。  

- この記事では以下のプラットフォームを対象とします
  - Android
  - iOS
- 2021年11月時点の情報を元に構成しています

---

# Flutterで提供されているパッケージ
Flutterでは[pub.dev](https://pub.dev/) というサイトで多くのパッケージ（ライブラリ）が提供されており、これらを利用することで高速に開発を行えます。
pub.devで公開されているパッケージは3つのスコアで評価されており、これらの値がパッケージを選定する際の参考になります。

![score.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122574/519da590-2465-38f6-a43c-8f9b08160081.png)

- LIKES
  - LIKE（お気に入り）された数
- PUB POINTS
  - 品質に関する評価ポイント
- POPULARITY
  - パッケージの人気度

以下のリンクでは評価基準の詳細について解説されています。  

https://pub.dev/help/scoring

---

# 動画を再生する
動画再生を実現するためのパッケージをピックアップしてみました。  

## video_player
https://pub.dev/packages/video_player

|LIKES|PUB POINTS|POPULARITY|
|:---:|:---:|:---:|
|1200+|130|99%|

- Flutter公式の動画プレイヤーパッケージ
- 公式サイトに導入のためのドキュメントが掲載されています

https://flutter.dev/docs/cookbook/plugins/play-video

### プレイヤー実装
https://github.com/flutter/plugins/tree/master/packages/video_player/video_player

|OS|Player|
|:---:|:---:|
|Android|ExoPlayer|
|iOS|AVPlayer|

### 使い方
``` dart
// コントローラを初期化する
VideoPlayerController _controller = VideoPlayerController.network(<URL>)..initialize();

// VideoPlayerウィジェットにコントローラ渡すだけで表示が可能
Center(
  child: VideoPlayer(_controller),
);

// コントローラの制御周り
_controller.play();
_controller.pause();
_controller.value.isPlaying;
_controller.dispose();
```

## chewie
https://pub.dev/packages/chewie

|LIKES|PUB POINTS|POPULARITY|
|:---:|:---:|:---:|
|700+|120|99%|

- `video_player` をラップしてより簡単に再生を行うことができるようにしたパッケージ
- デフォルトで標準的なUIが実装されているため使い勝手がいい

### プレイヤー実装
https://github.com/brianegan/chewie

- `video_player` に準拠

### 使い方
``` dart
// VideoPlayerControllerを初期化する
VideoPlayerController _controller = VideoPlayerController.network(<URL>)..initialize();
// ChewieControllerを作成する
ChewieController = ChewieController(
  videoPlayerController: _videoPlayerController,
  autoPlay: true,
  looping: true,
);

// VideoPlayerウィジェットにコントローラ渡すだけで表示が可能
Center(
  child: Chewie(controller: _chewieController),
);

// コントローラの制御周り
_chewieController.play();
_chewieController.pause();
_chewieController.isPlaying;
_chewieController.dispose();
```

## ビデオ再生の調査結果
手っ取り早く動画プレイヤーを実装するなら `chewie` はかなり使いやすいと感じました。
ただ、UIを独自で実装したい場合は公式パッケージの `video_player` を使った方が自由度は高そうです。
いずれにせよ `video_player` を利用するため大きく使用感は変わらないかと思います。

---

# オーディオを再生する
オーディオ再生を実現するためのパッケージをピックアップしてみました。
動画と違って公式のオーディオプレイヤーが提供されていないためか選択肢は多い印象を受けました。

## just_audio
https://pub.dev/packages/just_audio

|LIKES|PUB POINTS|POPULARITY|
|:---:|:---:|:---:|
|1300+|130|99%|

- 機能が豊富なオーディオプレイヤー
- 今回ピックアップした中で一番スコアが良かったパッケージ
- 追加機能がプラグインとして別なパッケージで提供されているため、用途に合わせて組み合わせて利用できる

### プラグイン
- [audio_service](https://pub.dev/packages/audio_service)
  - バックグラウンド再生・Androidの通知領域のプレイヤー・iOSのコントロールセンターなどを実現できるプラグイン
- [just_audio_background](https://pub.dev/packages/just_audio_background)
  - audio_serviceに比べてシンプルにバックグラウンド再生やコントロールを行うことができるプラグイン
  - 2021年11月時点ではベータ版としての提供
- [audio_session](https://pub.dev/packages/audio_session)
  - 電話やゲームなど他のアプリの音声の通知などを行うプラグイン
    - AndroidのAudioFocus周りの制御などが実装されている
- [just_waveform](https://pub.dev/packages/just_waveform)
  - 再生中のオーディオの波形を視覚化するプラグイン

### プレイヤー実装
https://github.com/ryanheise/just_audio/tree/master/just_audio

|OS|Player|
|:---:|:---:|
|Android|ExoPlayer|
|iOS|AVQueuePlayer|

### 使い方
``` dart
// プレイヤーのインスタンスを作成
final _player = AudioPlayer();
// オーディオソースをセット
_player.setAudioSource(AudioSource.uri(Uri.parse(<URL>)));

// プレイヤー操作
_player.play();
_player.pause();
_player.seek(Duration(position);
_player.setSpeed(1.0);
_player.stop();
_player.dispose();
```

## audioplayers
https://pub.dev/packages/audioplayers

|LIKES|PUB POINTS|POPULARITY|
|:---:|:---:|:---:|
|1100+|130|99%|

- バックグラウンド再生するには前述のaudio_serviceプラグインを利用する必要がある
  - https://denis-korovitskii.medium.com/flutter-demo-audioplayers-on-background-via-audio-service-c95d65c90ae1
- 使い方がわかりやすく簡単に利用できる
- プレイヤーのクラス名が `just_audio` と同じなのが紛らわしい・・・

### 実装
https://github.com/bluefireteam/audioplayers

|OS|Player|
|:---:|:---:|
|Android|MediaPlayer|
|iOS|AVPlayer|

### 使い方
``` dart
// インスタンス作成
final _player = AudioPlayer();

// プレイヤー操作
_player.play(<URL>, position: Duration.zero);
_player.pause();

// コールバック
_player.onAudioPositionChanged.listen((Duration d) {});
_player.onPlayerStateChanged.listen((PlayerState state) {});
```

## flutter_sound
https://pub.dev/packages/flutter_sound

|LIKES|PUB POINTS|POPULARITY|
|:---:|:---:|:---:|
|600+|130|97%|

- オーディオの再生だけではなく録音も行うことができるパッケージ
- 使い勝手が独特で慣れないと使いづらい
- 利用時にiOSでビルドエラーが起きたためXcodeのファイル類を修正する必要があった

### 実装
https://github.com/canardoux/flutter_sound

|OS|Player|
|:---:|:---:|
|Android|MediaPlayer|
|iOS|AVAudioPlayer|

|OS|Recorder|
|:---:|:---:|
|Android|MediaRecorder|
|iOS|AVAudioRecorder|

### 使い方
``` dart
// インスタンス作成
final FlutterSoundPlayer player = FlutterSoundPlayer();

// プレイヤー操作
_boumData = await getAssetData('assets/samples.aac');
player.startPlayer(
  fromDataBuffer: _boumData,
  codec: Codec.aacADTS,
  whenFinished: () {
    setState(() {});
  }
);
player.stopPlayer();
```

## soundpool
https://pub.dev/packages/soundpool

|LIKES|PUB POINTS|POPULARITY|
|:---:|:---:|:---:|
|90+|130|94%|

- AndroidのSoundPoolを参考に効果音の再生を目的として作られたパッケージ
  - 繰り返し利用する短い効果音の再生に適している
  - 音楽ファイルの利用は推奨されていない

### 実装
https://github.com/ukasz123/soundpool

|OS|Player|
|:---:|:---:|
|Android|SoundPool|
|iOS|AVAudioPlayer|

### 使い方
``` dart
// インスタンス生成
Soundpool pool = Soundpool.fromOptions();

// リソースのロード
_soundId = await rootBundle.load("assets/beep.mp3").then((ByteData soundData) {
  return pool.load(soundData);
});

// 再生
pool.play(_soundId);
```

## その他のオーディオで考慮すべきポイント
今回試すことはできなかったのですがプレイヤー周りでは他にも考慮する点が多くあるため実際の導入時には対応方針の検討が必要です。

### 再生方式
- ストリーミング再生
  - URLを渡してネットワーク経由で再生する方法
  - 動画周りは特に様々な配信方式があるため利用するパッケージの対応状況に注意が必要
- アセット再生
  - アプリ内に音源を内包して再生する方法
  - アプリ容量が大きくなるためあまり大きなファイルは内包するべきではない
- ローカルファイル再生
  - 端末のアプリ外の領域にあるオーディオファイルを参照して再生する方法
  - 端末のストレージにアクセスすることからAndroid/iOSによる差が大きい
    - [path_provider](https://pub.dev/packages/path_provider) というパッケージを利用することでOS間の差異を考慮したファイルアクセスが実現できる
  - ストレージのアクセスについては権限の許可が必要だったりバージョンを重ねるごとにセキュリティ面で厳しくなるなど影響が出やすい部分なので実装する際は慎重に対応する必要がある

### バックグラウンド再生
オーディオプレイヤーアプリを作る場合は必須と言っていいほど重要な機能ですが、AndroidManifest.xmlやInfo.plistへの記述が必要なためOS毎の動作確認はしっかりする必要があります。
[audio_service](https://pub.dev/packages/audio_service)を使うことで `just_audio` と `audioplayers` ばバックグラウンド再生に対応できるようです。

## オーディオ再生の調査結果
好みの部分もあるかと思いますが簡単に使うだけなら `audioplayers` が組み込みやすく、
しっかりとプレイヤーを実装したい場合はプラグインも豊富な `just_audio` が良さそうです。
`flutter_sound` については録音機能は今回試しませんでしたがプレイヤーは若干使いにくい部分が目立ちました。 

---

# まとめ
今回は各パッケージで最低限の簡単な再生を試してみましたがいかがでしょうか？
Android/iOSそれぞれの内部実装は同じものを利用しているケースが多かったですがパッケージの使い勝手はそれぞれ特色がありました。
用途に合わせて利用する際の参考になれば幸いです。
  
紹介した各パッケージの簡単なサンプルをまとめました。  

https://github.com/misoca12/FlutterPlayMedia

---

最後まで読んでいただきありがとうございました。
明日の[レコチョク Advent Calendar 2021](https://qiita.com/advent-calendar/2021/recochoku)は2日目 `murketでのSassの変数・@mixinのご紹介` となります。お楽しみに！

---

この記事は[レコチョクのエンジニアブログの記事](https://techblog.recochoku.jp/7864)を転載したものとなります。

https://techblog.recochoku.jp/7864