Skip to content
Introducing AI Conversations: Natural Language Interaction for Your Apps! Learn More

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 Apple platforms, playlists are queried via MPMediaQuery.PlaylistsQuery. Both include any custom playlists you’ve created (see below).

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 Apple platforms, it is the persistent ID. Custom playlists use a custom: prefix.
NamestringThe display name of the playlist.
SongCountintThe number of tracks in the playlist.

You can create and manage your own playlists through IMediaLibrary. Custom playlists are virtualized through the library — on Android, they are stored locally as JSON in app data since Android does not provide a writable playlist API for third-party apps. On Apple platforms, custom playlists are managed via MusicKit. In both cases, custom playlists appear alongside device playlists when calling GetPlaylistsAsync().

// Create a playlist
var playlist = await _library.CreatePlaylistAsync("Road Trip");
// Add tracks to it
await _library.AddTrackToPlaylistAsync(playlist.Id, track);
// Get tracks in the playlist
var tracks = await _library.GetPlaylistTracksAsync(playlist.Id);
// Remove a track
await _library.RemoveTrackFromPlaylistAsync(playlist.Id, track.Id);
// Remove the entire playlist
await _library.RemovePlaylistAsync(playlist.Id);

Adding a track that already exists in the playlist is a no-op. Custom playlist IDs use a custom: prefix to distinguish them from device playlists.

The PlayCount property on MusicMetadata tracks how many times a song has been played.

var tracks = await _library.GetAllTracksAsync();
foreach (var track in tracks.OrderByDescending(t => t.PlayCount).Take(10))
{
Console.WriteLine($"{track.Title}{track.PlayCount} plays");
}
AndroidApple Platforms
SourceLocally stored JSON fileMPMediaItem.PlayCount (system-tracked)
Incremented byIMusicPlayer.PlayAsync()The OS (any music app)
ScopeYour app onlyAll music playback on the device
Persists across installsNo — cleared when app is uninstalledYes — managed by the OS

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. Use GetAlbumArtPathAsync for cross-platform album art retrieval.
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?Track persistent ID for playback via MPMusicPlayerController on Apple platforms. Always null on Android.
Yearint?The release year of the track. On Android from MediaStore.Audio.Media.YEAR; on Apple platforms from MPMediaItem.ReleaseDate. null if unavailable.
PlayCountintNumber of times the track has been played. On Apple platforms from MPMediaItem.PlayCount; on Android tracked locally by the app. Default 0.

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.
  • Custom playlists and play counts are stored locally as JSON — Android does not provide writable playlist or play count APIs for third-party apps.
  • Tracks are queried using MPMediaQuery from the MediaPlayer framework.
  • Only items with MPMediaType.Music are returned — podcasts, audiobooks, and movies are excluded.
  • Custom playlists are managed via MusicKit.
  • Play counts come from MPMediaItem.PlayCount, which is tracked by the OS across all music apps.