NK Admin: switching songs stops the previous one; player bar shows edited details
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
cf645071aa
commit
f801f91edf
@ -13,19 +13,42 @@ class AdminPlayer {
|
|||||||
final audio = AudioPlayer();
|
final audio = AudioPlayer();
|
||||||
final current = ValueNotifier<AdminTrack?>(null);
|
final current = ValueNotifier<AdminTrack?>(null);
|
||||||
|
|
||||||
|
/// Bumped on every switch, so only the latest click starts playing.
|
||||||
|
int _request = 0;
|
||||||
|
|
||||||
Future<void> toggle(AdminTrack track) async {
|
Future<void> toggle(AdminTrack track) async {
|
||||||
if (current.value?.id == track.id) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
final url = track.audioUrl;
|
final url = track.audioUrl;
|
||||||
if (url == null) return;
|
if (url == null) return;
|
||||||
|
final request = ++_request;
|
||||||
current.value = track;
|
current.value = track;
|
||||||
|
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);
|
await audio.setUrl(url);
|
||||||
await audio.play();
|
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<AdminTrack> tracks) {
|
||||||
|
final id = current.value?.id;
|
||||||
|
for (final t in tracks) {
|
||||||
|
if (t.id == id) current.value = t;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
|
_request++;
|
||||||
await audio.stop();
|
await audio.stop();
|
||||||
current.value = null;
|
current.value = null;
|
||||||
}
|
}
|
||||||
@ -51,7 +74,11 @@ class PlayButton extends StatelessWidget {
|
|||||||
const [ProcessingState.loading, ProcessingState.buffering].contains(snap.data?.processingState);
|
const [ProcessingState.loading, ProcessingState.buffering].contains(snap.data?.processingState);
|
||||||
return IconButton(
|
return IconButton(
|
||||||
tooltip: track.audioKey == null ? 'No audio file' : (playing ? 'Pause' : 'Play'),
|
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
|
icon: loading && playing
|
||||||
? const SizedBox.square(dimension: 20, child: CircularProgressIndicator(strokeWidth: 2))
|
? const SizedBox.square(dimension: 20, child: CircularProgressIndicator(strokeWidth: 2))
|
||||||
: Icon(playing ? Icons.pause_circle_filled : Icons.play_circle_fill,
|
: Icon(playing ? Icons.pause_circle_filled : Icons.play_circle_fill,
|
||||||
|
|||||||
@ -49,6 +49,7 @@ class _SongsScreenState extends State<SongsScreen> {
|
|||||||
page: _page,
|
page: _page,
|
||||||
pageSize: _pageSize,
|
pageSize: _pageSize,
|
||||||
);
|
);
|
||||||
|
AdminPlayer.instance.refresh(page.items);
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user