From 2df35e6227c200798c0805ce1f5a2009e73cd5fb Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 14 May 2026 13:16:20 -0400 Subject: [PATCH] fix(player): seed full-player display state from current mediaItem on mount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regression from v2026.05.13.2's load-then-swap rewrite. _displayedMedia only got populated by the ref.listen callback on mediaItem changes, but ref.listen doesn't fire on initial subscription — it only fires on transitions after the listener is registered. So opening the full player while a track was already playing left _displayedMedia null and the screen rendered "Nothing playing." even though the mini bar showed a live track. initState now reads the current mediaItem synchronously and seeds _displayedMedia immediately (and _displayedDominant from the color provider's cached value when available). A post-frame _scheduleSwap(current) runs to ensure the cover bytes are decoded and dominant color resolved when the user opens the player to a track whose album hasn't yet been color-extracted in this session. --- .../lib/player/now_playing_screen.dart | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/flutter_client/lib/player/now_playing_screen.dart b/flutter_client/lib/player/now_playing_screen.dart index 4396cff3..818d3ea7 100644 --- a/flutter_client/lib/player/now_playing_screen.dart +++ b/flutter_client/lib/player/now_playing_screen.dart @@ -62,6 +62,39 @@ class _NowPlayingScreenState extends ConsumerState { /// a track whose cover hadn't finished decoding yet. String? _pendingPreloadId; + @override + void initState() { + super.initState(); + // Seed displayed state from the current mediaItem if a track is + // already playing when the full player is opened. ref.listen + // below only fires on CHANGES after subscription, so without + // this seed the screen sits on "Nothing playing." even though + // the mini bar shows a live track — the listener never gets a + // mediaItem emission because the stream's current value hasn't + // changed. + final current = ref.read(mediaItemProvider).value; + if (current == null) return; + _displayedMedia = current; + // Best-effort synchronous color seed: if albumColorProvider has + // already extracted this album's dominant color earlier in the + // session, pull it now so the backdrop renders correctly on + // first frame. Otherwise the post-frame _scheduleSwap below + // resolves it asynchronously and AnimatedContainer tweens. + final albumId = current.extras?['album_id'] as String?; + if (albumId != null && albumId.isNotEmpty) { + _displayedDominant = + ref.read(albumColorProvider(albumId)).asData?.value; + } + // Kick the preload pipeline post-frame so the cover bytes are + // decoded + the dominant color is resolved even when the user + // opens the player to a track that hasn't yet flowed through + // ref.listen. + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + _scheduleSwap(current); + }); + } + /// Preload the new track's cover bytes + dominant color, then /// atomically flip _displayedMedia / _displayedDominant. Until both /// resolve, the screen continues to render the previous track —