Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/CesarArellano/Music-Player-App/llms.txt

Use this file to discover all available pages before exploring further.

Musynth’s tag editor lets you fix or update the embedded metadata of any local audio file without leaving the app. Changes are written directly to the file using the music_query_selector plugin’s native JAudioTagger implementation. This page explains how to access the editor, what fields are available, and the read/write flow including permission handling.
Tag editing is Android-only. JAudioTagger is an Android-native implementation. The feature is not available on iOS, and all tag-editor UI entry points are guarded accordingly.

Accessing the tag editor

1

Long-press a song

Long-press any song tile — in the Songs tab, an album screen, an artist screen, the Favourites tab, search results, or the playing queue — to open MoreSongOptionsModal.
2

Tap 'Edit tags'

The context menu lists an Edit tags option that navigates to the tag editor screen, passing the song’s file path.
3

Make changes

Edit any combination of text fields and optionally pick a new artwork image from the gallery.
4

Save

Tapping save triggers the permission check and write flow described below.

TagData fields

TagEditorService works with a TagData value object. Every text field maps directly to a standard ID3 frame:
FieldTypeID3 equivalent
titleStringTIT2
albumStringTALB
artistStringTPE1
composerStringTCOM
genreStringTCON
yearStringTDRC / TYER
trackStringTRCK
artworkBytesUint8List?APIC (optional)
class TagData {
  const TagData({
    required this.title,
    required this.album,
    required this.artist,
    required this.composer,
    required this.genre,
    required this.year,
    required this.track,
    this.artworkBytes,   // null = keep existing artwork
  });

  final String title;
  final String album;
  final String artist;
  final String composer;
  final String genre;
  final String year;
  final String track;
  final Uint8List? artworkBytes;
}

Reading tags

Call TagEditorService.readTags(path) with the absolute file path of the audio file. The method delegates to MusicQuerySelector.readTags, which invokes JAudioTagger on the native side and returns a Map<String, dynamic>. The service maps each key to the corresponding TagData field, defaulting to an empty string for any missing field:
Future<TagData> readTags(String path) async {
  final map = await _selector.readTags(path);
  return TagData(
    title:    map['title']    as String? ?? '',
    album:    map['album']    as String? ?? '',
    artist:   map['artist']   as String? ?? '',
    composer: map['composer'] as String? ?? '',
    genre:    map['genre']    as String? ?? '',
    year:     map['year']     as String? ?? '',
    track:    map['track']    as String? ?? '',
    artworkBytes: map['artworkBytes'] as Uint8List?,
  );
}

Writing tags

Permission check

Before writing, call TagEditorService.ensureWritePermission(). On Android 11 and above, writing arbitrary audio files requires the MANAGE_EXTERNAL_STORAGE (“All files access”) permission. The method:
  1. Checks if the permission is already granted — returns true immediately if so.
  2. Calls Permission.manageExternalStorage.request(), which navigates the user to the system All files access screen.
  3. Returns true if granted, false if denied.
Future<bool> ensureWritePermission() async {
  if (await Permission.manageExternalStorage.isGranted) return true;
  final status = await Permission.manageExternalStorage.request();
  return status.isGranted;
}

Writing the tags

Once permission is confirmed, call TagEditorService.writeTags(path, data):
Future<void> writeTags(String path, TagData data) async {
  await _selector.writeTags(path, {
    'title':    data.title,
    'album':    data.album,
    'artist':   data.artist,
    'composer': data.composer,
    'genre':    data.genre,
    'year':     data.year,
    'track':    data.track,
    if (data.artworkBytes != null) 'artworkBytes': data.artworkBytes,
  });
}
artworkBytes is omitted from the map when null, signalling to the native layer that the existing artwork should be left untouched.

Full write flow

Future<void> saveTagEdits(String filePath, TagData updatedData) async {
  final service = TagEditorService();

  // 1. Ensure permission
  final hasPermission = await service.ensureWritePermission();
  if (!hasPermission) {
    // Show an error to the user and return early
    return;
  }

  // 2. Write the tags
  await service.writeTags(filePath, updatedData);

  // 3. Refresh the library so the new tags appear in lists
  //    and regenerate the artwork cache if artwork was changed
  await libraryCubit.getAllSongs(
    forceCreatingArtworks: updatedData.artworkBytes != null,
  );
}

Updating artwork

To change a track’s embedded artwork:
  1. Use the image_picker package to let the user select an image from the gallery.
  2. Read the file as bytes (File.readAsBytes()).
  3. Pass the bytes as TagData.artworkBytes.
  4. Call writeTags — JAudioTagger embeds the bytes as an APIC frame.
When artworkBytes is non-null, pass forceCreatingArtworks: true to getAllSongs() after saving so Musynth regenerates the cached .jpg thumbnail used throughout the app.

Refreshing the library after edits

After a successful tag write, call LibraryCubit.getAllSongs(forceCreatingArtworks: true) to reload the song list from MediaStore (which now reflects the updated tags) and regenerate the in-app artwork cache. Without this step, the old metadata and artwork will continue to display until the next cold start.
await libraryCubit.getAllSongs(forceCreatingArtworks: true);
The forceCreatingArtworks flag bypasses the “already ran once” guard in the artwork cache service, ensuring that even unchanged artwork is re-extracted so the cache stays consistent.

Build docs developers (and LLMs) love