fix(player): smooth mini bar cover swap across track change
Audio handler broadcasts MediaItem twice on every track change: once with artUri=null (the new track's bare metadata), then again with artUri pointing at the AlbumCoverCache file once the cover lands on disk. The mini player's cover element was rebuilding in place: previous track's image → slate placeholder → new track's image. That flash is the flicker reported on the v2026.05.13.0 build. AnimatedSwitcher around the cover (180ms crossfade) keyed by the artUri value makes the swap a smooth crossfade instead of a visible snap. The Hero parent stays — its tag is stable across track changes so the mini→full bar expansion animation keeps working unchanged.
This commit is contained in:
@@ -95,14 +95,25 @@ class _TrackInfo extends StatelessWidget {
|
|||||||
// artwork from this 48dp footprint to the full-screen size rather
|
// artwork from this 48dp footprint to the full-screen size rather
|
||||||
// than fade-cutting. Tag is stable per-route (not keyed by media.id)
|
// than fade-cutting. Tag is stable per-route (not keyed by media.id)
|
||||||
// so the transition works regardless of what's playing.
|
// so the transition works regardless of what's playing.
|
||||||
Widget cover;
|
//
|
||||||
|
// AnimatedSwitcher around the cover smooths the artUri null→file://
|
||||||
|
// swap that fires on every track change: audio_handler broadcasts
|
||||||
|
// the new MediaItem without artUri immediately, then re-broadcasts
|
||||||
|
// with artUri once AlbumCoverCache resolves the path. Without the
|
||||||
|
// crossfade the cover snaps from the previous track's image to a
|
||||||
|
// slate placeholder to the new image — visible as a flicker.
|
||||||
|
final Widget coverChild;
|
||||||
if (media.artUri != null) {
|
if (media.artUri != null) {
|
||||||
// CachedNetworkImageProvider for HTTPS art URIs so the mini bar
|
// CachedNetworkImageProvider for HTTPS art URIs so the mini bar
|
||||||
// hits the same disk cache the rest of the UI uses (ServerImage,
|
// hits the same disk cache the rest of the UI uses (ServerImage,
|
||||||
// discover thumbnails). FileImage stays for the file:// branch
|
// discover thumbnails). FileImage stays for the file:// branch
|
||||||
// populated by AlbumCoverCache — bytes are already on disk and a
|
// populated by AlbumCoverCache — bytes are already on disk and a
|
||||||
// second cache layer would only burn duplicate space.
|
// second cache layer would only burn duplicate space.
|
||||||
cover = Image(
|
coverChild = Image(
|
||||||
|
// Key includes the artUri so AnimatedSwitcher detects a swap;
|
||||||
|
// without this it treats successive Image widgets as the same
|
||||||
|
// tree node and skips the transition.
|
||||||
|
key: ValueKey('art-${media.artUri}'),
|
||||||
image: media.artUri!.isScheme('file')
|
image: media.artUri!.isScheme('file')
|
||||||
? FileImage(File.fromUri(media.artUri!)) as ImageProvider
|
? FileImage(File.fromUri(media.artUri!)) as ImageProvider
|
||||||
: CachedNetworkImageProvider(media.artUri.toString()),
|
: CachedNetworkImageProvider(media.artUri.toString()),
|
||||||
@@ -117,8 +128,19 @@ class _TrackInfo extends StatelessWidget {
|
|||||||
Container(width: 48, height: 48, color: fs.slate),
|
Container(width: 48, height: 48, color: fs.slate),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
cover = Container(width: 48, height: 48, color: fs.slate);
|
coverChild = Container(
|
||||||
|
key: const ValueKey('art-placeholder'),
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
color: fs.slate,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
final cover = AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 180),
|
||||||
|
switchInCurve: Curves.easeOut,
|
||||||
|
switchOutCurve: Curves.easeOut,
|
||||||
|
child: coverChild,
|
||||||
|
);
|
||||||
return Row(
|
return Row(
|
||||||
// Default centering vertically aligns the like/kebab buttons
|
// Default centering vertically aligns the like/kebab buttons
|
||||||
// against the album art (48dp) — visually they span the title +
|
// against the album art (48dp) — visually they span the title +
|
||||||
|
|||||||
Reference in New Issue
Block a user