Audio playback architecture
AudioPlayerService is an instance facade over the static XNA MediaPlayer and a per-instance MediaLibrary.
State
private List<Track> _playlist;
private List<Song> _songs;
private int _currentIndex;
private bool _isShuffled;
private Random _random;
private bool _isPlaying;
private MediaLibrary _mediaLibrary;
private bool _disposed;
Construction initializes empty track and song lists, index -1, shuffle/playing false, and subscribes:
MediaPlayer.MediaStateChanged→OnMediaStateChangedMediaPlayer.ActiveSongChanged→OnActiveSongChanged
Loading tracks
LoadSongsFromMusicLibrary() clears state and enumerates songs sorted by artist then name. Each item becomes a new Track with defensive property reads, while the corresponding XNA Song is appended to _songs at the same index.
The method contains a reflection-based attempt to access album art:
- inspect
song.Albumfor anArtproperty; - invoke
GetImage; - create a
BitmapImageand set its source.
The resulting bitmap is not retained, so this work has no visible output.
If at least one item exists, _currentIndex becomes zero, but playback does not start.
Control flow
The service's _isPlaying mirrors requested state and is also synchronized from MediaPlayer.State on media state changes.
Events
| Event | Raised when |
|---|---|
TrackChanged | Play() begins successfully, or XNA reports a recognized active song |
PlayStateChanged | Play/Pause/Resume/Stop or XNA media state changes |
ProgressChanged | Media state changes and explicit UpdateProgress() while playing |
PlaylistEnded | Declared, but never raised |
Events are nullable Action delegates, invoked after null checks.
Index behavior
Next
- sequential: increment, wrap to zero;
- shuffle:
_random.Next(0, count).
Previous
- restart active index when position is greater than three seconds;
- otherwise decrement and wrap to the last index.
Active-song synchronization
OnActiveSongChanged linearly searches the explicitly mapped _songs list by object identity against MediaPlayer.Queue.ActiveSong. If found and within playlist bounds, it assigns that mapped index to _currentIndex and raises events.
Sorted playlist mapping
LoadSongsFromMusicLibrary() builds _playlist and _songs together during the same sorted enumeration. The two lists therefore share one index space:
Index
├── _playlist[index] copied metadata used by CurrentTrack and scrobbling
└── _songs[index] original XNA Song passed to MediaPlayer.Play
Play() uses _songs[_currentIndex], and active-song changes resolve identity against _songs. This keeps playback, displayed metadata, and scrobbling aligned even when the original MediaLibrary.Songs order differs from the sorted display order.
Both lists are cleared and rebuilt together. Playlist exposes the mutable track list, so callers must not add, remove, or reorder its entries independently; current callers only bind it for display.
Threading
Play() dispatches its work through Deployment.Current.Dispatcher.BeginInvoke. Other control methods call MediaPlayer directly and raise events synchronously. Consumers are currently UI event handlers, but future background calls should normalize thread affinity.
Disposal
Dispose() removes static XNA subscriptions once. It does not stop playback or dispose MediaLibrary. The owning page currently never invokes it.
A robust page lifetime would stop its timer, detach page delegates, and dispose the service when permanently leaving the page, while accounting for the desire to continue audio across temporary navigation.
Public derived properties
CurrentTrackvalidates the index each read.PositionproxiesMediaPlayer.PlayPosition.Durationcomes from the copied activeTrack.Playlistexposes the mutable list itself.GetTracksByArtistperforms case-insensitive equality and suppresses per-item exceptions.