Skip to main content

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 → OnMediaStateChanged
  • MediaPlayer.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:

  1. inspect song.Album for an Art property;
  2. invoke GetImage;
  3. create a BitmapImage and 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​

EventRaised when
TrackChangedPlay() begins successfully, or XNA reports a recognized active song
PlayStateChangedPlay/Pause/Resume/Stop or XNA media state changes
ProgressChangedMedia state changes and explicit UpdateProgress() while playing
PlaylistEndedDeclared, 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​

  • CurrentTrack validates the index each read.
  • Position proxies MediaPlayer.PlayPosition.
  • Duration comes from the copied active Track.
  • Playlist exposes the mutable list itself.
  • GetTracksByArtist performs case-insensitive equality and suppresses per-item exceptions.