An application that wants to play music under Windows Phone 7 uses classes from the Microsoft.Xna.Framework.Media namespace. You’ll first need to access the music from the library, and for that you’ll need a new instance of MediaLibrary, the same class you use to access the photo library.
The MediaLibrary class defines several get-only properties that let you access the music library in several standard ways. These properties include:
Albums of type AlbumCollection, a collection of Album objects.
Songs of type SongCollection, a collection of Song objects.
Artists of type ArtistCollection, a collection of Artist objects.
Genres of type GenreCollection, a collection of Genre objects.
Each of these collections contains all the music in your library but arranged in different ways. (The presence of a property called Composer of type ComposerCollection would have simplified my program considerably.)
For my purposes I found the Albums property of MediaLibrary the most useful. The AlbumCollection class is a collection of items of type Album, and Album has the following get-only properties (among others):
If HasArt is true, you can call two methods, GetAlbumArt and GetThumbnail, both of which return Stream objects to access a bitmap with an image of the album cover. GetAlbumArt returns a bitmap of about 200-pixels square and GetThumbnail returns a bitmap of about 100-pixels square.
The SongCollection in an Album instance contains all the tracks on the album. (In the composer-centric tradition, the use of the word song
to describe these album tracks doesn’t make much sense if, for example,
a track is actually a movement of a symphony, but the performer-centric
prejudice of the XNA classes is something we’re forced to live with.)
The Song object has several get-only properties, among them:
For organizing the music library by composer and for data binding purposes, I realized that I’d need a couple new classes. My AlbumInfo class is basically a wrapper around the XNA Album class:
Example 1. Silverlight Project: MusicByComposer File: AlbumInfo.cs
using System; using System.Windows.Media.Imaging; using Microsoft.Xna.Framework.Media;
namespace MusicByComposer { public class AlbumInfo : IComparable<AlbumInfo> { BitmapImage albumArt; BitmapImage thumbnailArt;
public AlbumInfo(string shortAlbumName, Album album) { this.ShortAlbumName = shortAlbumName; this.Album = album; }
public string ShortAlbumName { protected set; get; }
public Album Album { protected set; get; }
public BitmapSource AlbumArt { get { if (albumArt == null && Album.HasArt) { BitmapImage bitmapImage = new BitmapImage(); bitmapImage.SetSource(Album.GetAlbumArt()); albumArt = bitmapImage; } return albumArt; } }
public BitmapSource ThumbnailArt { get { if (thumbnailArt == null && Album.HasArt) { BitmapImage bitmapImage = new BitmapImage(); bitmapImage.SetSource(Album.GetThumbnail()); thumbnailArt = bitmapImage; } return thumbnailArt; } }
public int CompareTo(AlbumInfo albumInfo) { return ShortAlbumName.CompareTo(albumInfo.ShortAlbumName); } } }
|
This AlbumInfo class has a property of type Album and adds three more properties: The ShortAlbumName
property is the name of the album with the composer or composers at the
beginning stripped off. (For example, “Mahler: Symphony No. 2” becomes
“Symphony No. 2”.) This property is used in the CompareTo
method at the bottom for sorting purposes. In the first of the two
screen shots of MusicByComposer, you’ll notice that the album names are
sorted.
The GetAlbumArt and GetThumbnail methods of Album return Stream objects. For binding purposes, I expose two public properties of type BitmapImage but the class only creates these objects when the properties are first accessed, and then caches them for subsequent accesses.
The next class is ComposerInfo, which consists of the composer’s name and a list of all the AlbumInfo objects containing music by that composer:
Example 2. Silverlight Project: MusicByComposer File: ComposerInfo.cs
using System; using System.Collections.Generic;
namespace MusicByComposer { public class ComposerInfo { public ComposerInfo(string composer, List<AlbumInfo> albums) { Composer = composer; albums.Sort(); Albums = albums; }
public string Composer { protected set; get; }
public IList<AlbumInfo> Albums { protected set; get; } } }
|
Notice that the List of AlbumInfo objects is sorted in the constructor.
The MusicPresenter
class is responsible for accessing the phone’s music library, obtaining
all the albums, analyzing the album titles for the presence of composer
names, and creating objects of type ComposerInfo and AlbumInfo. It does the main work in its instance constructor by storing the information in a dictionary with composer names used as keys that reference items of the type List<AlbumInfo>:
Example 3. Silverlight Project: MusicByComposer File: MusicPresenter.cs
using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Media;
namespace MusicByComposer { public class MusicPresenter { // Static constructor static MusicPresenter() { if (Current == null) Current = new MusicPresenter(); }
// Instance constructor public MusicPresenter() { // Make this class a singleton if (MusicPresenter.Current != null) { this.Composers = MusicPresenter.Current.Composers; return; }
MediaLibrary mediaLib = new MediaLibrary(); Dictionary<string, List<AlbumInfo>> albumsByComposer = new Dictionary<string, List<AlbumInfo>>();
foreach (Album album in mediaLib.Albums) { int indexOfColon = album.Name.IndexOf(':');
// Check for pathological cases if (indexOfColon != -1 && // Colon at beginning of album name (indexOfColon == 0 || // Colon at end of album name indexOfColon == album.Name.Length - 1 || // nothing before colon album.Name.Substring(0, indexOfColon).Trim().Length == 0 || // nothing after colon album.Name.Substring(indexOfColon + 1).Trim().Length == 0)) {
indexOfColon = -1; }
// Main logic for albums with composers if (indexOfColon != -1) { string[] albumComposers = album.Name.Substring(0, indexOfColon).Split(','); string shortAlbumName = album.Name.Substring(indexOfColon + 1).Trim(); bool atLeastOneEntry = false;
foreach (string composer in albumComposers) { string trimmedComposer = composer.Trim(); if (trimmedComposer.Length > 0) { atLeastOneEntry = true;
if (!albumsByComposer.ContainsKey(trimmedComposer)) albumsByComposer.Add(trimmedComposer, new List<AlbumInfo>());
albumsByComposer[trimmedComposer].Add( new AlbumInfo(shortAlbumName, album)); } }
// Another pathological case: Just commas before colon if (!atLeastOneEntry) { indexOfColon = -1; } }
// The "Other" category is for albums without composers if (indexOfColon == -1) { if (!albumsByComposer.ContainsKey("Other")) albumsByComposer.Add("Other", new List<AlbumInfo>());
albumsByComposer["Other"].Add(new AlbumInfo(album.Name, album)); } }
mediaLib.Dispose();
// Transfer Dictionary keys to List for sorting List<string> composerList = new List<string>();
foreach (string composer in albumsByComposer.Keys) composerList.Add(composer);
(composerList as List<string>).Sort();
// Construct Composers property Composers = new List<ComposerInfo>();
foreach (string composer in composerList) Composers.Add(new ComposerInfo(composer, albumsByComposer[composer]));
Current = this; }
public static MusicPresenter Current { protected set; get; }
public IList<ComposerInfo> Composers { private set; get; } } }
|
Only one instance
of this class is required by the program. The music library will not
change while the program is running, so there’s no reason for this
instance constructor to run again. For that reason, when the instance
constructor is finished, it sets the static Current property equal to the instance of MusicPresenter being created. This first instance will actually be created from the static constructor at the very top of the class, and result in setting the Composers property (down at the bottom), which consists of a list of ComposerInfo objects. If the constructor is called again, it merely transfers the existing Composers property to the new instance.
Why not make MusicPresenter a static class and simplify it somewhat? Because MusicPresenter
is used in data bindings in XAML files and an actual instance of a
class is required for those bindings. However, code also needs to access
the class and for that the static MusicPresenter.Current property is helpful.
This static constructor
executes when the program first accesses the class, of course, but also
when the program accesses the class again after it is revived from
tombstoning. In this case, re-creating the data from the MediaLibrary is certainly easier than saving it all in isolated storage.