Flutterのオーディオファイルのループ再生を比較してみた ※2023/10/24更新

# はじめに

BGMを流したくてaudioplayersを試したのですが、ループ再生したときに一瞬音が途切れるのが気になりました。
audioplayersのドキュメントを読むと、ループ再生で途切れることがあるのでjust_audioやocarinaもあるよ的なことが書かれていたのでそれらも試してみようというのが今回の目的。
各パッケージは以下のリンクから参照できます。
- [audioplayers](https://pub.dev/packages/audioplayers/)
- [just_audio](https://pub.dev/packages/just_audio/)
- [ocarina](https://pub.dev/packages/ocarina/)
    - 依存関係の問題で現在のAndroid Studioのバージョンでは利用が難しいかも。


今回のソースは[GitHubでも公開](https://github.com/project-dev/audioplayersample)しています。

# 更新情報
- 2023/10/2４ ファイルフォーマットによる違いにiOSを追記
- 2023/10/23 各フォーマットの再生についてとplay()メソッドの違いについて追記


# audioplayers

以下の実装でとりあえずループ再生できる

初期化
```dart
import 'package:audioplayers/audioplayers.dart' as audioplayers;
・・・
  final audioplayers.AudioPlayer _audioPlayer = audioplayers.AudioPlayer();
  audioplayers.AssetSource? _audioPlayerSource;
・・・
  await _audioPlayer.setReleaseMode(audioplayers.ReleaseMode.loop);
  _audioPlayerSource = audioplayers.AssetSource('SampleBGM.mp3');
```
setReleaseMode()メソッドでループ再生の設定をしています。
再生するオーディオファイルはAssetSourceオブジェクトを生成ておきます。

注意点は、audioplayersはassetsディレクトリに格納されている前提のため、assetsの下のパスを指定する必要があります。
assets/SampleBGM.mp3 で指定すると assets/assets/SampleBGM.mp3 になるので、上記のソースコードのように、
SampleBGM.mp3 のみを指定します。


再生
```dart
  _audioPlayer.play(_audioPlayerSource!);
```
再生したいAssetSourceオブジェクトをplayメソッドの引数に指定して再生します。
ループ再生は曲の末端から先頭に戻るときに少し間が空きます。


停止
```dart
  _audioPlayer.stop();
```


# just_audio

以下の実装でとりあえずループ再生できる

初期化
```dart
import 'package:just_audio/just_audio.dart' as just_audio;

・・・
final just_audio.AudioPlayer _justAudioPlayer = just_audio.AudioPlayer();
・・・
  await _justAudioPlayer.setLoopMode(just_audio.LoopMode.all);
  await _justAudioPlayer.setAsset('assets/SampleBGM.mp3');
```
setLoopMode()メソッドでループ再生の設定をしています。
再生するオーディオファイルはsetAsset()メソッドで指定します。
just_audioはアセットの指定はaudioplayersとは異なり、プロジェクトのアセットのパスを指定します。


再生
```dart
  _justAudioPlayer.play();
```
ループ再生はきれいにつながります。

停止
```dart
  _justAudioPlayer.stop();
```

# ocarina(試用断念)

こちらは2023/10/21時点で２年更新がない状態です。
ocarinaを導入すると以下のようなエラーが発生しました。
```
Launching lib\main.dart on A104SH in debug mode...
Running Gradle task 'assembleDebug'...

FAILURE: Build failed with an exception.

* What went wrong:
The Android Gradle plugin supports only Kotlin Gradle plugin version 1.5.20 and higher.
The following dependencies do not satisfy the required version:
project ':ocarina' -> org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.50

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1s
Exception: Gradle task assembleDebug failed with exit code 1
```
What went wrongを和訳してみると
```
Android Gradleプラグインは、Kotlin Gradleプラグインバージョン1.5.20以降のみをサポートしています。
以下の依存関係は必要なバージョンを満たしていません：
プロジェクト ':ocarina' -> org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.50
```
ということです。
2年間更新されていないだけあって、実質利用できないです。
GitHubのリポジトリも覗いてみましたが、動きなない状態です。

# audioplayersとjust_audioの違い

ライブラリが違うので、挙動の違いがあります。
現在までで触ってわかったことを記載します。

## play()メソッドの違い
オーディオソースの指定の仕方が違うため、挙動が変わる。
audioplayersは再生するオーディオソースをplay()メソッドの引数で指定する。
そのため、play()メソッドを呼び出すと必ず先頭からの再生になる。
stop()メソッドを呼び出した後に停止位置から再生したい場合はresum()メソッドを使用する。

just_audioはオーディオソースはsetAsset()メソッドで行う。
play()メソッドの初回は先頭から。
stop()メソッド停止後のplay()メソッドの呼び出しは停止位置からの再生になる。
そのため、先頭から再生したい場合はseek()メソッドで先頭に戻す必要がある。


# ファイルフォーマットによる違い

## audioplayers

Android
|フォーマット|再生|ループ動作|
|--|--|--|
|MP3|〇|途切れが発生する|
|M4A|〇|途切れが発生する|
|OGG|〇|途切れが発生する|
|WAV|〇|途切れが発生する|

iOS
|フォーマット|再生|ループ動作|
|--|--|--|
|MP3|〇|途切れが発生する|
|M4A|〇|途切れが発生する|
|OGG|×||
|WAV|〇|途切れが発生する|

AndroidとiOSでループした時の先頭の音の出方に違いがありました。
iOSはループ後の先頭の音が数ミリ秒鳴らない感じです。

## just_audio

Android
|フォーマット|再生|ループ動作|
|--|--|--|
|MP3|〇|きれいにループする|
|M4A|〇|ごくわずかな途切れが発生する|
|OGG|〇|きれいにループする|
|WAV|〇|きれいにループする|

iOS
|フォーマット|再生|ループ動作|
|--|--|--|
|MP3|〇|きれいにループする|
|M4A|〇|きれいにループする|
|OGG|×||
|WAV|〇|きれいにループする|


# 余談

audioplayersとjust_audioを一緒に利用した際に以下のエラーが発生しました。
```
ERROR:D8: Cannot fit requested classes in a single dex file (# methods: 69630 > 65536)
com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives: 
The number of method references in a .dex file cannot exceed 64K.
```

android/app/build.gradleのdefaultConfigにmultiDexEnabled trueを追加して対応しました。
```
android {
    ・・・
    defaultConfig {
        ・・・
        multiDexEnabled true
```

# 最後に

深く触ってないので語れることは少ないですが、きれいにループさせるの意であればjust_audioを使うのがよさそうと感じました。
また、m4aはAndroidだとごく僅かな途切れが発生するので、m4a以外を使用するのが良いと思います。
oggはAndroid専用といった結果になりました。
ループ再生させるなら、just_audioでmp3がベストなようです。