Playback
The IMusicPlayer interface provides full playback control for music tracks from the device library.
On Apple platforms, all playback runs through MPMusicPlayerController.ApplicationMusicPlayer — the track’s properties only decide how it is enqueued:
- Local library tracks are looked up by their persistent ID (via
MPMediaQuery) and set as the player’s queue. - Streaming catalog tracks — those returned by
SearchCatalogAsyncwith aCatalogId— are enqueued by catalog id viaMPMusicPlayerStoreQueueDescriptor. An active Apple Music subscription is required; the track need not be in the user’s library.
Playing a Track
Section titled “Playing a Track”var tracks = await _library.GetAllTracksAsync();await _player.PlayAsync(tracks[0]);Calling PlayAsync stops any currently playing track, loads the new one, and begins playback immediately. On Apple platforms the player enqueues the track by its CatalogId (streaming catalog track) or, otherwise, by its persistent ID from the local library.
Pause, Resume, and Stop
Section titled “Pause, Resume, and Stop”// Pause the current track_player.Pause();
// Resume from where it was paused_player.Resume();
// Stop completely — releases the track_player.Stop();Pause()has no effect if the player is not in thePlayingstate.Resume()has no effect if the player is not in thePausedstate.Stop()returns the player to theStoppedstate and releases the current track.
Seeking
Section titled “Seeking”// Seek to 1 minute 30 seconds_player.Seek(TimeSpan.FromMinutes(1.5));Ducking
Section titled “Ducking”Ducking temporarily lowers the currently playing music so an announcement — a text-to-speech prompt, a navigation callout, an alert — can be heard clearly over top. Duck returns an IAsyncDisposable scope: the music stays ducked until the scope is disposed, at which point full volume is restored. Wrap the announcement in an await using block:
await using (_player.Duck(new DuckOptions { Level = 0.2 })){ await TextToSpeech.Default.SpeakAsync("Turn left in 200 meters");}// music ramps back to full volume hereIf nothing is playing, Duck returns a harmless no-op scope, so it is always safe to call. Check _player.IsDucked to know whether a duck is currently active.
DuckOptions
Section titled “DuckOptions”var options = new DuckOptions{ Level = 0.2, // target volume (0.0–1.0) while ducked FadeIn = TimeSpan.FromMilliseconds(200), // ramp-down when ducking starts FadeOut = TimeSpan.FromMilliseconds(200) // ramp-up when the scope is disposed};Passing null (or omitting the argument) uses these defaults — 20% level with 200ms fades.
One duck at a time
Section titled “One duck at a time”Only a single duck is ever active. While one is active, a further Duck call returns a harmless no-op scope — the existing duck is not superseded and keeps running until its own scope is disposed. Dispose the active scope to restore full volume. Enforcing a single duck avoids a superseded duck’s restore-fade racing the new duck on the shared volume, which could otherwise leave the music stuck at a lowered level.
Reading Playback State
Section titled “Reading Playback State”// Current state: Stopped, Playing, or PausedPlaybackState state = _player.State;
// Currently loaded track (null if stopped)MusicMetadata? track = _player.CurrentTrack;
// Current position and total durationTimeSpan position = _player.Position;TimeSpan duration = _player.Duration;Volume
Section titled “Volume”Volume is the device-wide system media volume, normalized 0.0–1.0, independent of any active duck. Reading works on every platform. Setting is Android-only — always guard with IsVolumeControlSupported:
// Read anywherefloat level = _player.Volume;
// Set only where supported (Android). On Apple the setter throws NotSupportedException.if (_player.IsVolumeControlSupported) _player.Volume = 0.5f;Setting Volume on iOS or Mac Catalyst throws NotSupportedException: Apple exposes no supported API to change the system volume (MPMusicPlayerController.Volume was deprecated in iOS 7 and is a no-op). On those platforms, let the user adjust volume with the hardware buttons or an MPVolumeView. IsVolumeControlSupported returns true on Android and false on Apple platforms.
Events
Section titled “Events”StateChanged
Section titled “StateChanged”Raised whenever the playback state transitions:
_player.StateChanged += (sender, newState) =>{ Console.WriteLine($"Playback state: {newState}"); // e.g., Playing → Paused, Paused → Playing, Playing → Stopped};PlaybackCompleted
Section titled “PlaybackCompleted”Raised when a track finishes playing naturally (i.e., reaches the end). This is not raised when you call Stop() manually.
_player.PlaybackCompleted += (sender, args) =>{ Console.WriteLine("Track finished — play the next one?");};VolumeChanged
Section titled “VolumeChanged”Raised when the device media volume changes — via the hardware volume buttons, Control Center, or a successful Volume set. The argument is the new volume normalized 0.0–1.0:
_player.VolumeChanged += (sender, volume) =>{ Console.WriteLine($"Volume is now {volume:P0}");};On Android this observes the system music-stream volume; on Apple platforms it comes from KVO on the audio session’s output volume.
Disposing
Section titled “Disposing”IMusicPlayer implements IDisposable. Call Dispose() to stop playback and release all platform resources:
_player.Dispose();If you register the player as a singleton in DI, it will be disposed when the app shuts down.
Platform Details
Section titled “Platform Details”Android
Section titled “Android”- Playback uses
Android.Media.MediaPlayerwith content URIs from MediaStore. - Audio attributes are set to
ContentType.MusicwithUsage.Media. - Seeking uses millisecond precision.
- Ducking lowers this player’s own track, honoring
DuckOptions.Level,FadeIn, andFadeOutexactly. - Volume reads and writes the system
STREAM_MUSIClevel viaAudioManager(IsVolumeControlSupportedistrue).VolumeChangedobserves system-volume changes through aContentObserver.
- All playback uses
MPMusicPlayerController.ApplicationMusicPlayer, which manages its own audio session. Seeking uses second precision. - Local library tracks: the
MPMediaItemis resolved by persistent ID viaMPMediaQueryand set as the player’s queue. This covers purchased/synced tracks as well as DRM-protected Apple Music subscription content that is in the library. - Catalog tracks (with
CatalogId, fromSearchCatalogAsync): the track is enqueued by its Apple Music catalog id viaMPMusicPlayerStoreQueueDescriptor— no library membership required. An active Apple Music subscription is required; gate playback onHasStreamingSubscriptionAsync. - Ducking activates
AVAudioSessionwithDuckOthers;Leveland the fade durations are advisory only, as the OS controls duck depth and ramp. Announcement audio played through the app’s audio session (anAVAudioPlayer, orAVSpeechSynthesizerwithUsesApplicationAudioSession = true) plays at full volume over the ducked music — only other out-of-process audio is ducked. - Volume reads from
AVAudioSession.OutputVolume(reliable), but setting is not supported (IsVolumeControlSupportedisfalse) — the setter throwsNotSupportedException, as Apple provides no supported API to change the system volume.VolumeChangedfires from KVO on the session’s output volume.


