Client v5: BLE, BLE Hosting, HTTP, Jobs - Linux, MacOS, & Blazor Support! Full AOT, RX on BLE only & MANY other features! Power up!
Getting Started
| GitHub | |
| Downloads |
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).
Features
Section titled “Features”- 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, andGetPlaylistByIdAsyncfor 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?)— anIAsyncDisposablescope that restores full volume when disposed, with configurable duckLeveland fade durations - Stream Apple Music subscription tracks via
MPMusicPlayerControlleron 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.AIpackage
Platform Support
Section titled “Platform Support”| Feature | Android | iOS |
|---|---|---|
| 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>Optional: AI Tools
Section titled “Optional: AI Tools”To let an LLM chat agent search, browse, play, and manage playlists through natural language, add the companion package:
dotnet add package Shiny.Music.Extensions.AISee AI Tools for how to register and use it. It depends only on Microsoft.Extensions.AI.Abstractions and is AOT-compatible.
Quick Example
Section titled “Quick Example”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); }}Samples
Section titled “Samples”AI Coding Assistant
Section titled “AI Coding Assistant”Step 1 — Add the marketplace:
claude plugin marketplace add shinyorg/skills Step 2 — Install the plugin:
claude plugin install shiny-client@shiny Step 1 — Add the marketplace:
copilot plugin marketplace add https://github.com/shinyorg/skills Step 2 — Install the plugin:
copilot plugin install shiny-client@shiny