Skip to content

Querying Music

The IMediaLibrary interface provides methods to retrieve music metadata from the device library.

Returns every music track on the device:

var tracks = await _library.GetAllTracksAsync();
foreach (var track in tracks)
{
Console.WriteLine($"{track.Title} by {track.Artist} ({track.Duration:mm\\:ss})");
}

Search by title, artist, or album name:

var results = await _library.SearchTracksAsync("beethoven");
foreach (var track in results)
{
Console.WriteLine($"{track.Title} - {track.Album}");
}

The search is case-insensitive and matches partial strings against the title, artist, and album fields.

Returns all distinct genre names from the user’s music library with track counts, sorted alphabetically:

var genres = await _library.GetGenresAsync();
foreach (var genre in genres)
{
Console.WriteLine($"{genre.Value} ({genre.Count} tracks)");
}

Tracks with no genre are excluded from the results. On Android, genres are queried from MediaStore.Audio.Genres. On iOS, genres are enumerated via MPMediaQuery.GenresQuery.

You can optionally pass a MusicFilter to narrow results — for example, genres within a specific decade:

var rockGenresIn90s = await _library.GetGenresAsync(new MusicFilter { Decade = 1990 });

Returns all distinct release years with track counts, sorted in ascending order:

var years = await _library.GetYearsAsync();
foreach (var year in years)
{
Console.WriteLine($"{year.Value} ({year.Count} tracks)");
}

Tracks with no year metadata are excluded. Accepts an optional MusicFilter:

// Years that have Rock tracks
var rockYears = await _library.GetYearsAsync(new MusicFilter { Genre = "Rock" });

Returns all distinct decades with track counts, sorted in ascending order. Each decade is represented by its starting year (e.g., 1990 for the 1990s):

var decades = await _library.GetDecadesAsync();
foreach (var decade in decades)
{
Console.WriteLine($"{decade.Value}s ({decade.Count} tracks)");
}

Accepts an optional MusicFilter:

// Decades that have Jazz tracks
var jazzDecades = await _library.GetDecadesAsync(new MusicFilter { Genre = "Jazz" });

The MusicFilter class lets you combine multiple criteria to query tracks. All specified properties are combined with AND logic.

public class MusicFilter
{
public string? Genre { get; init; } // Case-insensitive genre match
public int? Year { get; init; } // Exact release year
public int? Decade { get; init; } // Decade start year (e.g., 1990)
public string? SearchQuery { get; init; } // Matches title, artist, or album
}
// Rock tracks from the 1990s
var tracks = await _library.GetTracksAsync(new MusicFilter
{
Genre = "Rock",
Decade = 1990
});
// Tracks from 2015 matching "love"
var tracks2 = await _library.GetTracksAsync(new MusicFilter
{
Year = 2015,
SearchQuery = "love"
});

The filter parameter on grouping methods enables powerful cross-dimensional queries:

// What genres exist in my 1980s music?
var genresIn80s = await _library.GetGenresAsync(new MusicFilter { Decade = 1980 });
// What decades have Rock tracks?
var rockDecades = await _library.GetDecadesAsync(new MusicFilter { Genre = "Rock" });
// What years have Jazz tracks?
var jazzYears = await _library.GetYearsAsync(new MusicFilter { Genre = "Jazz" });
// Genres in a specific year with a search term
var filtered = await _library.GetGenresAsync(new MusicFilter
{
Year = 2020,
SearchQuery = "remix"
});

All grouping methods (GetGenresAsync, GetYearsAsync, GetDecadesAsync) return IReadOnlyList<GroupedCount<T>>:

public record GroupedCount<T>(T Value, int Count);
  • GetGenresAsync returns GroupedCount<string> — the genre name and its track count
  • GetYearsAsync returns GroupedCount<int> — the year and its track count
  • GetDecadesAsync returns GroupedCount<int> — the decade start year and its track count

Returns all playlists from the device music library with their song counts, sorted alphabetically:

var playlists = await _library.GetPlaylistsAsync();
foreach (var playlist in playlists)
{
Console.WriteLine($"{playlist.Name} ({playlist.SongCount} songs)");
}

On Android, playlists are queried from MediaStore.Audio.Playlists. On iOS, playlists are queried via MPMediaQuery.PlaylistsQuery.

Returns all tracks in a specific playlist, in playlist order:

var playlists = await _library.GetPlaylistsAsync();
var tracks = await _library.GetPlaylistTracksAsync(playlists[0].Id);
foreach (var track in tracks)
{
Console.WriteLine($"{track.Title} by {track.Artist}");
}

The playlistId parameter is the platform-specific identifier from PlaylistInfo.Id.

Each playlist is represented by a PlaylistInfo record:

PropertyTypeDescription
IdstringPlatform-specific unique identifier. On Android, this is the MediaStore playlist row ID. On iOS, it is the persistent ID.
NamestringThe display name of the playlist.
SongCountintThe number of tracks in the playlist.

Each track is represented by a MusicMetadata record with the following properties:

PropertyTypeDescription
IdstringPlatform-specific unique identifier. On Android, this is the MediaStore row ID. On iOS, it is the MPMediaItem persistent ID.
Titlestring?The track title, or null if not available.
Artiststring?The artist or performer, or null if not available.
Albumstring?The album name, or null if not available.
Genrestring?The genre, or null if unavailable.
DurationTimeSpanThe playback duration.
AlbumArtUristring?URI to album artwork. Available on Android via MediaStore; null on iOS where artwork is accessed through MPMediaItem.Artwork.
IsExplicitbool?Whether the track is marked as explicit content. iOS only via MPMediaItem.IsExplicitItem; always null on Android.
ContentUristringURI used for playback and file operations. On Android, this is a content:// URI. On iOS, this is an ipod-library:// asset URL. Empty for DRM-protected Apple Music subscription tracks.
StoreIdstring?Apple Music catalog ID (from PlayParams.Id). Enables streaming playback via MPMusicPlayerController on iOS. Always null on Android.
Yearint?The release year of the track. On Android from MediaStore.Audio.Media.YEAR; on iOS from MPMediaItem.ReleaseDate. null if unavailable.

The ContentUri and StoreId properties determine what operations are available for a track:

var tracks = await _library.GetAllTracksAsync();
foreach (var track in tracks)
{
if (!string.IsNullOrEmpty(track.ContentUri))
{
// Locally synced or purchased track — full access
Console.WriteLine($"✅ {track.Title} - available for local playback and copy");
}
else if (!string.IsNullOrEmpty(track.StoreId))
{
// Apple Music subscription track with catalog ID — streaming playback only
Console.WriteLine($"🎧 {track.Title} - available for streaming playback (no copy)");
}
else
{
// No playback or copy available
Console.WriteLine($"⚠️ {track.Title} - not playable or copyable");
}
}
  • Tracks are queried from MediaStore.Audio.Media via ContentResolver.
  • Only items flagged as music (IsMusic != 0) are returned — ringtones, notifications, podcasts, and videos are excluded.
  • Results are sorted alphabetically by title.
  • Tracks are queried using MPMediaQuery from the MediaPlayer framework.
  • Only items with MPMediaType.Music are returned — podcasts, audiobooks, and movies are excluded.