「just_audio」についての技術調査
ドキュメント
サンプルの実行画面
ストリーミング再生、音量や速度と再生時間等を表示している。
initState()でしていること。
@override
void initState() {
super.initState();
// インスタンスをバインディングオブザーバー(イベント発生時、通知する)に登録する
WidgetsBinding.instance?.addObserver(this);
// 画面の向きを指定する
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Colors.black, // 上部のステータスバーの色
));
_init();
}
_init();
Future<void> _init() async {
// アプリのオーディオ関連のプラグインの使用を宣言。
final session = await AudioSession.instance;
// オーディオセッションを定義する。(ポッドキャストやオーディオブックなど、連続音声を再生向け) / other: AudioSessionConfiguration.music(音楽再生向け)
await session.configure(AudioSessionConfiguration.speech());
// 再生中のイベント(エラー)を取得
_player.playbackEventStream.listen((event) {},
onError: (Object e, StackTrace stackTrace) {
print('A stream error occurred: $e');
});
// オーディオファイルを取得する
try {
await _player.setAudioSource(AudioSource.uri(Uri.parse(
"https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3")));
} catch (e) {
print("Error loading audio source: $e");
}
}
- AudioSessionConfiguration.music:音楽プレーヤーアプリ向け
- AudioSessionConfiguration.speech:ポッドキャストおよびオーディオブックアプリなど連続再生向け
2つの違いは最適化の問題で、軽く聞いただけだと違いは感じなかった。
dispose()でしていること
@override
void dispose() {
// インスタンスをオブザーバー登録を削除する
WidgetsBinding.instance?.removeObserver(this);
// デコーダやバッファを解放し、他のアプリ(リソース)が活用できるようにする。
_player.dispose();
super.dispose();
}
didChangeAppLifecycleState()
/// システムがフォアグランド/バックグランドに遷移した時に呼び出される。
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// バックグラウンドで動作している。
if (state == AppLifecycleState.paused) {
_player.stop();
}
}
値が変更されたことを報告するコールバックのシグネチャ。
typedef ValueChanged<T> = void Function(T value);
知らなかったし、便利そうだから使い方理解しないと...
stop methodを使うとエラーを吐く
IconButton(
icon: const Icon(Icons.stop),
onPressed: () async => await _player.stop(),
)
エラー文
[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception: PlatformException(abort, Loading interrupted, null, null)
#0 AudioPlayer._setPlatformActive.checkInterruption (package:just_audio/just_audio.dart:1161:7)
#1 AudioPlayer._setPlatformActive.setPlatform (package:just_audio/just_audio.dart:1272:11)
<asynchronous suspension>
flutter: error
flutter: PlatformException(abort, Loading interrupted, null, null)
just_audio/just_audio.dart:1161:7 を覗く
/// 原文
// Checks if we were interrupted and aborts the current activation. If we
// are interrupted, there are two cases:
// 1. If we were activating the native platform, abort with an exception.
// 2. If we were activating the idle dummy, abort silently.
//
// We should call this after each awaited call since those are opportunities
// for other coroutines to run and interrupt this one.
/// 翻訳
中断されたかどうかをチェックし、現在の起動を中止する。
中断された場合、2つのケースがある。
1. ネイティブプラットフォームを起動していた場合、例外を発生させて中断する。
2. アイドルダミーを起動していた場合は、静かに終了します。
待機中の呼び出しは、他のコルーチンが実行される機会であるため、それぞれの呼び出しの後にこの関数を呼び出す必要があります。
他のコルーチンが実行され、このコルーチンを中断する機会となるからです。
原因
playerStateStream property
は、PlayerState class
のstreamを返すため、再生状態と処理状態を把握することができる。そのため、stop method
でリソースを解放すると処理状態を把握することができずエラーを吐く。
対策
playbackEventStream property
を使う。
playbackEventStream property
はPlaybackEvent class
のstreamを返すため、再生状態と現在の位置を把握することができる。そのため、stop method
でリソースを解放しても再生状態や現在地に関係しないため、エラーは吐かない。
playerStateStreamとplaybackEventStreamを比較
playerStateStreamを先に宣言する
_player.playerStateStream.listen((event) async {
print('playerStateStream' + ' / ' + event.processingState.toString());
});
_player.playbackEventStream.listen((event) async {
print('playbackEventStream' + ' / ' + event.processingState.toString());
});
flutter: playbackEventStream / ProcessingState.idle
flutter: playerStateStream / ProcessingState.idle
flutter: playbackEventStream / ProcessingState.idle
flutter: playbackEventStream / ProcessingState.loading
flutter: playerStateStream / ProcessingState.loading
flutter: playbackEventStream / ProcessingState.loading
flutter: playbackEventStream / ProcessingState.ready
flutter: playerStateStream / ProcessingState.ready
flutter: playbackEventStream / ProcessingState.ready
flutter: playbackEventStream / ProcessingState.ready
flutter: playbackEventStream / ProcessingState.ready
flutter: playerStateStream / ProcessingState.ready
flutter: playbackEventStream / ProcessingState.buffering
flutter: playerStateStream / ProcessingState.buffering
flutter: playbackEventStream / ProcessingState.ready
flutter: playerStateStream / ProcessingState.ready
flutter: playbackEventStream / ProcessingState.completed
flutter: playerStateStream / ProcessingState.completed
playbackEventStreamを先に宣言する
_player.playbackEventStream.listen((event) async {
print('playbackEventStream' + ' / ' + event.processingState.toString());
});
_player.playerStateStream.listen((event) async {
print('playerStateStream' + ' / ' + event.processingState.toString());
});
flutter: playbackEventStream / ProcessingState.idle
flutter: playerStateStream / ProcessingState.idle
flutter: playbackEventStream / ProcessingState.idle
flutter: playbackEventStream / ProcessingState.loading
flutter: playerStateStream / ProcessingState.loading
flutter: playbackEventStream / ProcessingState.loading
flutter: playbackEventStream / ProcessingState.ready
flutter: playerStateStream / ProcessingState.ready
flutter: playbackEventStream / ProcessingState.ready
flutter: playbackEventStream / ProcessingState.ready
flutter: playbackEventStream / ProcessingState.ready
flutter: playerStateStream / ProcessingState.ready
flutter: playbackEventStream / ProcessingState.buffering
flutter: playerStateStream / ProcessingState.buffering
flutter: playbackEventStream / ProcessingState.ready
flutter: playerStateStream / ProcessingState.ready
flutter: playbackEventStream / ProcessingState.completed
flutter: playerStateStream / ProcessingState.completed
結果
playbackEventStream -> playerStateStream の順に処理される。
- ProcessingState.idle
- ProcessingState.loading
- ProcessingState.ready
playbackEventStreamは、上記3つの状態を重複して検知する(ProcessingState.readyはplayerStateStreamでも重複する)。だが例外処理を実装せずstop methodを使える。
個人的な意見
再生可能や終了の検知はplaybackEventStreamを使っていく。理由はmethodに干渉せず、注意しなければいけない事項が少ないためである。
連続再生
連続再生するには、
を使う。実装
ByteData data = await rootBundle.load('assets/audio/cute.mp3');
_player.setAudioSource(ConcatenatingAudioSource(children: [
AudioSource.uri(Uri.dataFromBytes(Uint8List.view(data.buffer))),
AudioSource.uri(Uri.parse('https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3')),
])).catchError((error) {
print("Error loading audio source: $error");
});
setAudioSource
はAudioSource
しか受け付けない。そしてAudioSource
はUri
しか受け付けないため、アセットは ByteData -> buffer -> Uint8List
に変換しないといけない。
参考文献
setSpeed
で遅延再生は可能だが、倍速再生ができない。
ドキュメントに「1.0以上はstalls(失速)する可能性を注意して」と書いているが、1.1以上だとエラーを吐く。