From f801f91edfc6d4b61060f70d56898e0de00f991e Mon Sep 17 00:00:00 2001 From: metatroncubeswdev Date: Fri, 30 Oct 2026 11:46:22 -0400 Subject: [PATCH] NK Admin: switching songs stops the previous one; player bar shows edited details Co-Authored-By: Claude Opus 5.5 --- admin/lib/player.dart | 35 +++++++++++++++++++++++++---- admin/lib/screens/songs_screen.dart | 1 + 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/admin/lib/player.dart b/admin/lib/player.dart index 4817d71..2197d43 100644 --- a/admin/lib/player.dart +++ b/admin/lib/player.dart @@ -13,19 +13,42 @@ class AdminPlayer { final audio = AudioPlayer(); final current = ValueNotifier(null); + /// Bumped on every switch, so only the latest click starts playing. + int _request = 0; + Future toggle(AdminTrack track) async { if (current.value?.id == track.id) { - audio.playing ? await audio.pause() : await audio.play(); + // play() completes only when playback pauses, so it is not awaited. + audio.playing ? await audio.pause() : audio.play(); return; } final url = track.audioUrl; if (url == null) return; + final request = ++_request; current.value = track; - await audio.setUrl(url); - await audio.play(); + try { + // Stop first: loading a new file while one plays can be interrupted on the web, + // leaving the old song playing. + await audio.stop(); + await audio.setUrl(url); + if (request == _request) audio.play(); + } catch (_) { + if (request != _request) return; // a newer click replaced this load + current.value = null; + rethrow; + } + } + + /// Shows fresh details (after an edit) for the loaded song if it is in [tracks]. + void refresh(Iterable tracks) { + final id = current.value?.id; + for (final t in tracks) { + if (t.id == id) current.value = t; + } } Future close() async { + _request++; await audio.stop(); current.value = null; } @@ -51,7 +74,11 @@ class PlayButton extends StatelessWidget { const [ProcessingState.loading, ProcessingState.buffering].contains(snap.data?.processingState); return IconButton( tooltip: track.audioKey == null ? 'No audio file' : (playing ? 'Pause' : 'Play'), - onPressed: track.audioKey == null ? null : () => player.toggle(track), + onPressed: track.audioKey == null + ? null + : () => player.toggle(track).catchError((Object e) { + if (context.mounted) showMessage(context, 'Could not play "${track.title}": $e'); + }), icon: loading && playing ? const SizedBox.square(dimension: 20, child: CircularProgressIndicator(strokeWidth: 2)) : Icon(playing ? Icons.pause_circle_filled : Icons.play_circle_fill, diff --git a/admin/lib/screens/songs_screen.dart b/admin/lib/screens/songs_screen.dart index b687e42..94b7461 100644 --- a/admin/lib/screens/songs_screen.dart +++ b/admin/lib/screens/songs_screen.dart @@ -49,6 +49,7 @@ class _SongsScreenState extends State { page: _page, pageSize: _pageSize, ); + AdminPlayer.instance.refresh(page.items); return page; }