diag(flutter): surface player errors + state transitions

Tap→audio measured at ~370ms — that path's fast. Real symptom: a few
seconds of audio, then silence with no event surfaced to Flutter.
ExoPlayer is failing/completing somewhere and we have no log to act
on.

Three changes, all log-only:
- Add onError to playbackEventStream so stream failures (404, range-
  request bugs, decoder errors, network drops) print instead of
  silently halting playback.
- Subscribe to playerStateStream and log playing + processingState
  on every transition. Silent stops will now show as a state shift
  to completed / idle / buffering with no resumption.
- Subscribe to processingStateStream separately to catch fine-grained
  state transitions ExoPlayer reports between source advances.

After hot-restart, tap-then-go-quiet should produce a sequence we
can read — most likely either "processingState=completed" partway
through (server returning premature EOS or wrong Content-Length) or
a thrown error from ExoPlayer's source-loading path.
This commit is contained in:
2026-05-11 19:43:37 -04:00
parent e8a515dac4
commit 2d5f0691c2
+19 -1
View File
@@ -12,7 +12,16 @@ import 'album_cover_cache.dart';
class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
MinstrelAudioHandler() {
_player.playbackEventStream.listen(_broadcastState);
_player.playbackEventStream.listen(
_broadcastState,
// ExoPlayer surfaces stream errors (404, premature EOS, decoder
// failure, network drop) here. Without an error sink, the
// player just goes quiet — exactly the "starts then stops"
// symptom we hit.
onError: (Object e, StackTrace st) {
debugPrint('audio_handler: playbackEventStream error: $e\n$st');
},
);
_player.currentIndexStream.listen(_onCurrentIndexChanged);
// Re-broadcast on shuffle/repeat changes so the PlaybackState's
// shuffleMode + repeatMode fields stay current for UI subscribers.
@@ -24,6 +33,15 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
// the index and the eviction loop can't reclaim them.
_player.bufferedPositionStream
.listen((_) => unawaited(_maybeRegisterStreamCache()));
// Diagnostic: log every player-state transition so silent stops
// surface as something we can read in the log.
_player.playerStateStream.listen((s) {
debugPrint(
'audio_handler: state playing=${s.playing} processing=${s.processingState}');
});
_player.processingStateStream.listen((ps) {
debugPrint('audio_handler: processingState=$ps');
});
}
final AudioPlayer _player = AudioPlayer();