fix(player): seed full-player display state from current mediaItem on mount
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.
This commit is contained in:
@@ -62,6 +62,39 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
|
|||||||
/// a track whose cover hadn't finished decoding yet.
|
/// a track whose cover hadn't finished decoding yet.
|
||||||
String? _pendingPreloadId;
|
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
|
/// Preload the new track's cover bytes + dominant color, then
|
||||||
/// atomically flip _displayedMedia / _displayedDominant. Until both
|
/// atomically flip _displayedMedia / _displayedDominant. Until both
|
||||||
/// resolve, the screen continues to render the previous track —
|
/// resolve, the screen continues to render the previous track —
|
||||||
|
|||||||
Reference in New Issue
Block a user