feat(player): surface playback errors via debounced SnackBar (#58)
_handlePlaybackError silently skipped a dead track (404 / decoder /
EOS / network drop) with only a debugPrint, hiding the signal that
tells a broken track from a flaky app.
- audio_handler: _playbackErrors broadcast stream; emit the failing
track title (mediaItem.value?.title — correctly mapped post-#49)
before the skip/pause
- playback_error_reporter (new): global scaffoldMessengerKey +
reporter that buffers, 2s-debounces, and coalesces bursts into one
SnackBar ("Couldn't play X — skipping" / "Skipped N unplayable
tracks")
- app.dart: scaffoldMessengerKey on MaterialApp.router + postFrame read
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -151,6 +151,14 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
||||
Stream<Duration> get positionStream => _player.positionStream;
|
||||
Duration get position => _player.position;
|
||||
|
||||
/// Broadcasts the title of a track that just failed to play (404,
|
||||
/// decoder failure, premature EOS, network drop) and was auto-skipped
|
||||
/// or paused by _handlePlaybackError. The app listens and surfaces a
|
||||
/// debounced/coalesced SnackBar so a silent skip isn't mysterious.
|
||||
/// App-lifetime singleton handler — intentionally never closed.
|
||||
final _playbackErrors = StreamController<String>.broadcast();
|
||||
Stream<String> get playbackErrorStream => _playbackErrors.stream;
|
||||
|
||||
void configure({
|
||||
required String baseUrl,
|
||||
required String? token,
|
||||
@@ -478,6 +486,12 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
||||
/// If we're at the last track, seekToNext is a no-op; the state
|
||||
/// drops to idle and _broadcastState reflects that.
|
||||
Future<void> _handlePlaybackError() async {
|
||||
// mediaItem is the correctly-mapped current track (post #49 logical
|
||||
// index), so it's the one that just failed — no player-index math.
|
||||
final failed = mediaItem.value?.title;
|
||||
if (failed != null && failed.isNotEmpty) {
|
||||
_playbackErrors.add(failed);
|
||||
}
|
||||
final currentIdx = _player.currentIndex;
|
||||
final queueLen = queue.value.length;
|
||||
if (currentIdx == null || currentIdx + 1 >= queueLen) {
|
||||
|
||||
Reference in New Issue
Block a user