How to work with the user’s music library on Windows Phone
I am currently exploring a lot of APIs that I never used before in my Windows Phone apps as I am adding more features to my NFC Toolkit.
Like I always do, I create sample applications to explore what is possible and then integrate them into my main app.
One of those APIs is the MediaLibrary class on Windows Phone. You might think: there are tons of examples out there, why another post? Well, I agree, there are a lot of samples, but I want to cover a whole scenario.
In my sample, I am getting a list of all Albums stored in Library, start them playing, and display the current playing song as well as handle state managements of the playing song.
First, you will need to add the capabilities ID_CAP_MEDIALIB_AUDIO and ID_CAP_MEDIALIB_PLAYBACK to your app. If you will not do that, every call against the APIs will end up with an NotAuthorizedException.
For any access to the MediaLibrary and the MediaPlayer classes we are using the Namespace “Microsoft.Xna.Framework.Media”. Here our trouble begins already.
Xna is Microsoft’s framework to create games on Windows Phone 7. As Windows Phone 8 supports also native code, this is mainly used in 7 games. That’s why you might run in some TargetInvocationExceptions while you debug apps that use them.
To avoid this, we need to add a Framework Dispatcher that simulates a game loop for us. Add the following method to your App.xaml.cs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void StartGameLoop()
{
GameTimer gameTimer = new GameTimer();
gameTimer.UpdateInterval = TimeSpan.FromMilliseconds(33);
gameTimer.Update += delegate
{
try
{
FrameworkDispatcher.Update();
}
catch
{ }
};
gameTimer.Start();
FrameworkDispatcher.Update();
}
We are starting a new GameTimer and delgate it to our UI using the FrameworkDispatcher.Update() method. By adding it to App.xaml.cs and calling this method in Lauching and Activated event of our app, we have it running through our whole app and are done with this.
There are several methods to this, I found this the most easy version. I would pay credits, but don’t remember where I saw this – sorry.
Before we came to our first call against the MediaLibrary, we need to add a class for our List<T> or ObservableCollection<T>. I don’t know if this is the best practice in this case, but it made working with the List of Albums very easy for me:
1
2
3
4
5
6
7
8
9
10
11
public class AlbumFromStorage
{
public string AlbumName {get; set;}
public string AlbumArtist {get; set;}
public WriteableBitmap AlbumCover {get; set;}
public SongCollection AlbumSongs { get; set; }
}
To use this class, just add a new List<T>/ObservableCollection<T> to the Page class. Do the same for a MediaLibrary object and an AlbumCollection object:
1
2
3
public ObservableCollection<AlbumFromStorage> AlbumsList = new ObservableCollection<AlbumFromStorage>();
public MediaLibrary lib = new MediaLibrary();
public AlbumCollection albumslist;
Now we are prepared for fetching all local stored albums. Add the following code to your corresponding method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
albumslist = lib.Albums;
if (albumslist.Count != 0)
{
foreach (var item in albumslist)
{
AlbumsList.Add(new AlbumFromStorage()
{
AlbumArtist = item.Artist.ToString(),
AlbumName = item.Name,
AlbumCover = PictureDecoder.DecodeJpeg(item.GetThumbnail()),
AlbumSongs = item.Songs
});
}
MusicAlbumListBox.ItemsSource = AlbumsList;
}
else
{
await MessageBox.Show("It seems you have not stored any music on your phone.", "Sorry :(", MessageBoxButton.OK);
}
Let me explain the code. First, we are using our AlbumCollection object to asign the MediaLibrary.Albums property. This will give us a collection of all albums. Then we need to check first, if the count in the collection is not 0 (it will throw ugly exceptions if a user doesn’t have any music stored if you won’t to this).
Then we add these albums to our ObservableCollection<AlbumFromStorage> picking the interesting properties of each album for us.
As the thumbnail for the album cover is a Stream, we need to use a WriteableBitmap for calling the GetThumbnail() method. The last step adds our ObservableCollection<AlbumFromStorage> as ItemSource to our ListBox.
This will be the result (based on my current albums list):
Now all we need to do is to make a tapped album playing with Windows Phone’s media Player. Add the following code to the Listbox ItemTap Event:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (Microsoft.Xna.Framework.Media.MediaPlayer.State != MediaState.Playing)
{
//to delete the recent song (if there is any), just add this
MediaPlayer.Stop();
var selectedItem = this.MusicAlbumListBox.SelectedItem as AlbumFromStorage;
// play the SongCollection of the selected Album
MediaPlayer.Play(selectedItem.AlbumSongs);
}
else if (Microsoft.Xna.Framework.Media.MediaPlayer.State == MediaState.Playing)
{
MessageBox.Show("You are already playing music. Please stop the music first.");
//your own handling could be different here, like asking the user for the desired action and perform it then
}
As you can see, I use the MediaPlayer.State property to get the album playing. The album is a SongCollection that holds all songs of the album. I recommend you to stop the last played song (if there is any) first with the MediaPlayer.Stop() method before start playing the first album song with the MediaPlayer.Play() method. Otherwise, it may happen that the user hears a second from the old song.
Very important: you must handle the case that the user has already music playing. If not, your app is likely to not pass certification.
After we started playing the music, we naturally want to display the current song.
To achieve this, we need to add two events to our page constructor: MediaPlayer.ActiveSongChanged and MediaPlayer.MediaStateChanged.
Within the MediaPlayer_MediaStateChanged event, add the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
if (MediaPlayer.State == MediaState.Playing)
{
var activeSong = MediaPlayer.Queue.ActiveSong;
Dispatcher.BeginInvoke(() =>
{
MusicTileTitleBlock.Text = string.Format("playing:\n{0}\n{1}", activeSong.Name, activeSong.Artist);
});
}
else if (MediaPlayer.State == MediaState.Paused)
{
var activeSong = MediaPlayer.Queue.ActiveSong;
Dispatcher.BeginInvoke(() =>
{
MusicTitleBlock.Text = string.Format("paused:\n{0}\n{1}", activeSong.Name, activeSong.Artist);
});
}
else if (MediaPlayer.State == MediaState.Stopped)
{
Dispatcher.BeginInvoke(() =>
{
MusicTitleBlock.Text = "music stopped";
});
}
This way, we use the MediaState to display the the current state as well as the title and artist. As we are updating the UI thread, as Dispatcher is used to update the Text.
To change the title and the artist of the current playing song, use this code within the MediaPlayer_ActiveSongChanged event:
1
2
3
4
5
6
var activeSong = MediaPlayer.Queue.ActiveSong;
Dispatcher.BeginInvoke(() =>
{
MusicTileTitleBlock.Text = string.Format("{0}\n{1}", activeSong.Name, activeSong.Artist);
});
If you want to display also the album image, you will need to cache the image from the ItemTap event before, which would work fine with albums. If you choose Playlists instead of Albums, this will not work out well as you will not be able to get the image from MediaPlayer.Queue.ActiveSong.
I did a lot of research to get this feature(s) working the way they should. I took me several hours to figure everything out exactly, and I hope this post helps some of you out there to save some time on this.
With this post, you will be able to generate the whole experience your users deserve from the beginning to the end.
Until the next post, happy coding!
Comments powered by Disqus.