feat(player): resume last session on launch (#54)

The #52 teardown clears the session when idle/dismissed, so the
headset / lock-screen play button had nothing to resume and the user
lost their place.

- track.dart: toJson() (round-trips fromJson)
- db.dart: CachedResumeState single-row snapshot, schema 9->10 + migration
- audio_handler.dart: queuedTracks getter
- player_provider.dart: restoreQueue() — configure + setQueueFromTracks
  + seek, no play (restores PAUSED)
- resume_controller.dart: restores last snapshot paused on launch;
  persists {source,index,position_ms,tracks} debounced on track
  change / pause and immediately on app teardown; never clobbers the
  saved snapshot when the queue is empty so a teardown stays
  recoverable; restore gated on auth + no active session
- app.dart: wired into postFrame

db.g.dart regenerated by CI per project convention. Deferred fast-follow:
media-button-when-fully-stopped re-init (needs a configure()-injected
callback; tracked on #54).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 15:57:58 -04:00
parent c80dc0b306
commit 41dd2892e3
6 changed files with 237 additions and 1 deletions
+6
View File
@@ -6,6 +6,7 @@ import 'cache/metadata_prefetcher.dart';
import 'cache/offline_provider.dart';
import 'cache/mutation_queue.dart';
import 'cache/prefetcher.dart';
import 'cache/resume_controller.dart';
import 'cache/sync_controller.dart';
import 'player/play_events_reporter.dart';
import 'shared/live_events_dispatcher.dart';
@@ -62,6 +63,11 @@ class _MinstrelAppState extends ConsumerState<MinstrelApp> {
// scoring, scrobbles, and system-playlist rotation, and is the
// path that carries the `source` tag for #415.
ref.read(playEventsReporterProvider);
// Resume-on-launch (#54): restores the last persisted session
// (paused) and persists queue/index/position on track change,
// pause, and app teardown. Pairs with the #52 idle teardown so a
// torn-down session is recoverable instead of lost.
ref.read(resumeControllerProvider);
// Offline marker (#427 S1): periodic /healthz reachability
// probe → offlineProvider. Read here to start the poller; S4
// gates system-playlist play + Shuffle-all on it.