Previous fixes layered AnimatedSwitcher fades on top of a race: the
audio_handler broadcasts MediaItem twice on every track change
(bare metadata first, then with artUri once AlbumCoverCache resolves)
and the image bytes themselves decode asynchronously after the
widget mounts. The fades just smeared the resulting placeholder
flash without addressing the underlying ordering.
Rewrite the decision process around "load first, then swap":
**Mini player** (rapid change is acceptable per operator preference):
- Drop AnimatedSwitcher entirely
- PlayerBar becomes stateful, holds the most-recent non-null artUri
- Builds the child MediaItem with artUri = currentArtUri ?? _lastArtUri,
so the previous cover stays visible across the null-artUri gap and
the new cover snaps in the moment its artUri arrives
**Full player** (operator wants the image fully loaded before any
visible change):
- Introduce _displayedMedia + _displayedDominant state
- ref.listen on mediaItemProvider schedules a preload for each new
track id (and for the artUri-bearing rebroadcast on the same id)
- _scheduleSwap awaits precacheImage on the file:// artUri AND
awaits albumColorProvider's future for the dominant color
- Only then setState flips _displayedMedia + _displayedDominant in
one frame — cover, title, gradient all advance atomically
- Drop the per-element AnimatedSwitcher wrappers; the backdrop
AnimatedContainer still tweens between successive dominant
colors so the gradient transition is smooth, not snap
Concurrency: rapid skips drop stale preload completions via
_pendingPreloadId. Decode/color failures fall through to the
previous dominant + the ServerImage/error-builder fallbacks.