Skip to content
Client v5: BLE, BLE Hosting, HTTP, Jobs - Linux, MacOS, & Blazor Support! Full AOT, RX on BLE only & MANY other features! Power up!

Getting Started

GitHubGitHub stars for shinyorg/music
DownloadsNuGet downloads for Shiny.Music
Frameworks
.NET
.NET MAUI
Operating Systems
Android
iOS

Shiny.Music provides a unified API for accessing the device music library on Android and iOS. It supports permission management, querying track metadata, playing music files, and copying tracks (where platform restrictions allow).

  • Request and check music library permissions on both platforms
  • Query all music tracks or search by title, artist, or album
  • Resolve tracks and playlists by identifier — GetTrackByIdAsync, GetTracksByIdsAsync, and GetPlaylistByIdAsync for rehydrating saved queues, favorites, or a resumed session
  • Browse by genre, year, or decade — each with track counts
  • Browse playlists and their tracks
  • Filter tracks by any combination of genre, year, decade, and search query via MusicFilter
  • Cross-dimensional browsing — genres by decade, years by genre, etc.
  • Play, pause, resume, stop, and seek within tracks
  • Duck the currently playing music for announcements or TTS via IMusicPlayer.Duck(DuckOptions?) — an IAsyncDisposable scope that restores full volume when disposed, with configurable duck Level and fade durations
  • Stream Apple Music subscription tracks via MPMusicPlayerController on iOS
  • Check for active streaming subscriptions
  • Fetch lyrics — plain text and synchronized LRC format via LRCLIB
  • Retrieve album artwork as cached file paths
  • Copy music files to app storage (non-DRM content only on iOS)
  • Event-driven playback state changes and completion notifications
  • Manage custom playlists — create, remove, and add tracks via IMediaLibrary
  • Track play counts per song via MusicMetadata.PlayCount
  • Expose the library and player to an LLM agent as Microsoft.Extensions.AI tools via the optional Shiny.Music.Extensions.AI package
FeatureAndroidiOS
Permission Request
Query Tracks
Search Tracks
Browse by Genre
Browse by Year / Decade
Browse Playlists
Filter by Genre + Year/Decade
Local Playback✅ (AVAudioPlayer)
Streaming Playback✅ (MPMusicPlayerController via StoreId)
Ducking✅ (exact level + fades)✅ (AVAudioSession DuckOthers)
Lyrics (Plain + Synced LRC)✅ (LRCLIB)✅ (LRCLIB + MPMediaItem.Lyrics)
Album Art✅ (MediaStore)✅ (MPMediaItem.Artwork, cached to file)
Copy Track✅ (non-DRM only)
Streaming Subscription Check❌ (always false)✅ (MusicKit MusicSubscription)
Explicit Flag✅ (via MPMediaItem.IsExplicitItem)
Custom Playlists✅ (local JSON)✅ (local JSON)
Play Count Tracking✅ (local store)✅ (MPMediaItem.PlayCount)
Changed files
  • MyApp/
Platforms/Android/AndroidManifest.xml
AndroidManifest.xml
1<?xml version="1.0" encoding="utf-8"?>2<manifest xmlns:android="http://schemas.android.com/apk/res/android">3	<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true">4	</application>5	<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>6	<uses-permission android:name="android.permission.BATTERY_STATS" />7	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />8	<uses-permission android:name="android.permission.INTERNET" />9	<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />10	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />11</manifest>

To let an LLM chat agent search, browse, play, and manage playlists through natural language, add the companion package:

Terminal window
dotnet add package Shiny.Music.Extensions.AI

See AI Tools for how to register and use it. It depends only on Microsoft.Extensions.AI.Abstractions and is AOT-compatible.

public class MyPage
{
readonly IMediaLibrary _library;
readonly IMusicPlayer _player;
readonly ILyricsProvider _lyrics;
public MyPage(IMediaLibrary library, IMusicPlayer player, ILyricsProvider lyrics)
{
_library = library;
_player = player;
_lyrics = lyrics;
}
async Task PlayFirstTrack()
{
// 1. Request permission
var status = await _library.RequestPermissionAsync();
if (status != PermissionStatus.Granted)
return;
// 2. Get all tracks
var tracks = await _library.GetAllTracksAsync();
if (tracks.Count == 0)
return;
// 3. Play the first track
await _player.PlayAsync(tracks[0]);
// 4. Duck the music while an announcement plays, then restore volume
await using (_player.Duck())
{
// play a TTS prompt or alert sound over the lowered music here
}
// 5. Get album artwork
var artPath = await _library.GetAlbumArtPathAsync(tracks[0].Id);
// 6. Fetch lyrics (plain or synced LRC)
var lyrics = await _lyrics.GetLyricsAsync(tracks[0]);
// 7. Listen for completion
_player.PlaybackCompleted += (s, e) =>
{
Console.WriteLine("Track finished!");
};
}
async Task BrowseAndFilter()
{
// Browse genres with track counts
var genres = await _library.GetGenresAsync();
foreach (var g in genres)
Console.WriteLine($"{g.Value} ({g.Count} tracks)");
// Browse decades
var decades = await _library.GetDecadesAsync();
foreach (var d in decades)
Console.WriteLine($"{d.Value}s ({d.Count} tracks)");
// Get rock tracks from the 1990s
var tracks = await _library.GetTracksAsync(new MusicFilter
{
Genre = "Rock",
Decade = 1990
});
// Get genres within the 2000s
var genresIn2000s = await _library.GetGenresAsync(new MusicFilter { Decade = 2000 });
// Browse playlists
var playlists = await _library.GetPlaylistsAsync();
foreach (var p in playlists)
Console.WriteLine($"{p.Name} ({p.SongCount} songs)");
// Get tracks in a playlist
var playlistTracks = await _library.GetPlaylistTracksAsync(playlists[0].Id);
}
}
claude plugin marketplace add shinyorg/skills
claude plugin install shiny-client@shiny
copilot plugin marketplace add https://github.com/shinyorg/skills
copilot plugin install shiny-client@shiny
View shiny-client Plugin