Metadata topic

Read the tags and the cover art of the playing file.

Tags

Keys keep the case the file stores them in, so look them up without case:

String? tag(Map<String, String> tags, String key) => tags.entries
    .where((e) => e.key.toLowerCase() == key.toLowerCase())
    .map((e) => e.value)
    .firstOrNull;

player.stream.metadata.listen((tags) {
  print('Now playing: ${tag(tags, 'title')}, ${tag(tags, 'artist')}');
});

final album = tag(player.state.metadata, 'album');

Common keys are title, artist, album, album_artist, date, track, disc, genre, comment and composer.

Cover art

Cover art flow: an embedded picture reaches stream.coverArt, an image in the folder appears only in state.tracks

coverArt carries the picture embedded in the file.

StreamBuilder<CoverArt?>(
  stream: player.stream.coverArt,
  builder: (context, snapshot) {
    final art = snapshot.data;
    if (art == null) return const Icon(Icons.music_note, size: 96);
    return Image(image: art.image, fit: BoxFit.cover);
  },
);

// Save it to disk
final art = player.state.coverArt;
if (art != null) {
  await File('${dir.path}/cover.${art.extension}').writeAsBytes(art.bytes);
}

bytes holds the image as stored, without re-encoding. mimeType and extension give its type, and extension is empty for an unknown type.

The stream emits on every file load, a reload of the same file included. It emits null for a file without a picture, so the UI clears the previous cover. It also emits null when open, openAll or openPlaylistFile return, so a new cover always follows a null.

Images in the folder

setCoverArtAuto loads an image next to the audio file as an extra track. It is off by default, so unrelated images stay out.

await player.setCoverArtAuto(Cover.exact); // same base name, or cover.jpg, folder.jpg and the like
await player.setCoverArtAuto(Cover.fuzzy); // also images whose name contains the base name
await player.setCoverArtAuto(Cover.all);   // every image in the folder
await player.setCoverArtAuto(Cover.no);    // off

That image never reaches stream.coverArt. Find it among the tracks and read the file yourself:

final path = player.state.tracks
    .where((t) => t.external && t.albumArt)
    .firstOrNull
    ?.externalFilename;

Classes

CoverArt
Cover-art payload extracted from the currently loaded file.