If you get the Windows Phone "Mango" update on your phone, and you are use to listen to music on your phone, you have for sure noticed some improvements in the music capabilities of the phone. Since the release of the OS7.0, the music hub is an important application that is able to integrate with the Zune marketplace and enable download and listen of music on the phone.
One of the beautiful capabilities of the hub is the ability of playing music also when the phone is doing other things. Once you started a playlist you can exit from the music hub and the music continue to play in background also if you lock the phone.
In the new OS7.5 these capabilities are improved because now you get a new set of controls in the lock screen that let you control the music without the need of unlocking the phone. But this is only the most visible change in the music capabilities. From the programming side you have much more opportunities of integrating with the music hub, feeding and controlling the player from inside your applications.
The role of Agents
Playing background audio is not simply feeding something with a playlist and then leave it free of managing the events occured during its lifetime. if so, it would be very limitative and it would be much simpler to load songs on the music hub and the let it manages them. In Windows Phone 7.5 we can instead directly manage the playlists and being notified about the commands sent by the user from the lock screen and from the audio player bar that usually opens when you press the volume switch.
As you can figure, these tasks can be accomplished only starting a background thread that runs also when the application exited. It is natural to understand the we have to do some work when there are other application running on the screen and also when the lock screen has been teared down.
From the OS7.5 the phone has the capability of running background agents and to control the tracks you have to play, you have to build a special kind of agent. There are two classes you can inherit from to start this work. The AudioPlayerAgent is made to be feed with tracks that you can stream from the network or from the Isolated Storage; it is the perfect point to create your own player to connect with a live service and so on. With the AudioStreamingAgent instead you can create your own stream on the fly or you can manipulate different formats not directly supported by the build-in player. For the sample of this article I will show the basic AudioPlayerAgent. First of all open Visual Studio and select File -> New -> Project
As you can see there are two project templates that are ready to start your work. Side by side with the generic Scheduled Task Agent there are an Audio Playback Agent and an Audio Streaming Agent. Select the first one as in the figure and then OK to ask to Visualo Studio of create the skeleton of the project. The project that is created by Visual Studio contains a single class called AudioPlayer.
The project you have created is a class library, so you have to add its reference to a Windows Phone Application. After you done this task your task is ready to be used. Under the hoods Visual Studio addedd a reference to your AudioPlayerAgent into the WMAppManifest.xaml. This reference simply add the class to the extended tasks that runs in background:
1: <Tasks>
2: <DefaultTask Name="_default" NavigationPage="MainPage.xaml" />
3: <ExtendedTask Name="BackgroundTask">
4: <BackgroundServiceAgent Specifier="AudioPlayerAgent" Name="AudioPlaybackAgent1" Source="AudioPlaybackAgent1" Type="AudioPlaybackAgent1.AudioPlayer" />
5: </ExtendedTask>
6: </Tasks>
Manage the playlist
Now that the Agent is ready to start, it is time to understand its internal work and to write the code to manage the playlist and feed the player. Look at the AudioPlayerAgent class:
1: public class AudioPlayerAgent : BackgroundAgent
2: {
3: protected virtual void OnError(
4: BackgroundAudioPlayer player,
5: AudioTrack track,
6: Exception error,
7: bool isFatal);
8: protected virtual void OnPlayStateChanged(
9: BackgroundAudioPlayer player,
10: AudioTrack track,
11: PlayState playState);
12: protected virtual void OnUserAction(
13: BackgroundAudioPlayer player,
14: AudioTrack track,
15: UserAction action,
16: object param);
17: }
There are two key method you have to handle. The OnUserAction method is made to notify the actions of the user on the playback controls. When the user hits a button on the lock screen this method is called and you are in charge of taking the right action on the current playing track or on the playlist. The method notifies about track-related actions like Play, Pause, Stop, Forward and Rewind but also on SkipNext and SkipPrevious that ask to change the currently playing track.
The OnPlayerStateChanged method instead, notifies about the progress of the playback and about the changes of its state. So, as an example, if the track is ended this method is called with PlayState.TrackEnded and you have to feed the player with the next track available. Mostly important is that you get notifications also about the actions you trigger in the OnUserAction method.
The AudioPlayer class created by the Visual Studio template already implements lot of the logic needed to make it work. The sole thing it miss is the tracks. So at the very start of the class add an array of AudioTrack instance and a _current property that identified the number of the current track running in this array:
1: private static volatile int _current = 0;
2:
3: private AudioTrack[] tracks = new AudioTrack[]
4: {
5: new AudioTrack(new Uri("Hard As Rock.mp3", UriKind.Relative),
6: "Hard as rock", "AC/DC", "Ballbreaker", new Uri("Hard As Rock.jpg", UriKind.Relative)),
7: new AudioTrack(new Uri("Run Around.mp3", UriKind.Relative),
8: "Run around", "Blues traveller", "Four", new Uri("Four.jpg", UriKind.Relative)),
9: };
In this sample I statically declare the array but you can obviously load it from every kind of media. It can be loaded from the network or generate it dinamically. Also you do not need to have a static array but you can also generate the next track at runtime on a random basis. Important to say is that the _current variable is declared static because the AudioPlayer class is not persistent but an instance is created every time an action is required.
The AudioTrack class represent a single track of the playlist. It is provided with properties for Title, Artist, Album, AlbumArt and so on. You can use these properties to provide information to the phone and it shows them whenever it is required. The template of the AudioPlayer class declares a GetNextTrack and a GetPreviousTrack methods. Implementing the body of these method let you feed the player with the right track.
1: private AudioTrack GetNextTrack()
2: {
3: AudioTrack track = this.tracks[_current];
4: if (++_current >= this.tracks.Length) _current = 0;
5: return track;
6: }
7:
8: private AudioTrack GetPreviousTrack()
9: {
10: AudioTrack track = this.tracks[_current];
11: if (--_current < 0) _current = this.tracks.Length - 1;
12: return track;
13: }
Starting the task
Adding this class to the WMAppManifest.xaml does not automatically start the playback. To make the last step you have to use the BackgroundAudioPlayer class. This class is designed to simulate the actions of an user and it has methods to Play, Pause, Stop and so on. So when you need to start the audio you have to call the Play method. After this action the phone loads the AudioPlayerAgent and starts the music. You can also use this class to control le lifetime of the track and playlist from your own application user interface:
1: private void bPlay_Click(object sender, RoutedEventArgs e)
2: {
3: if (BackgroundAudioPlayer.Instance.PlayerState != PlayState.Playing)
4: BackgroundAudioPlayer.Instance.Play();
5: }
6:
7: private void bPause_Click(object sender, RoutedEventArgs e)
8: {
9: if (BackgroundAudioPlayer.Instance.CanPause &&
10: BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
11: BackgroundAudioPlayer.Instance.Pause();
12: }
13:
14: private void bStop_Click(object sender, RoutedEventArgs e)
15: {
16: if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
17: BackgroundAudioPlayer.Instance.Stop();
18: }
19:
20: private void bNext_Click(object sender, RoutedEventArgs e)
21: {
22: if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
23: BackgroundAudioPlayer.Instance.SkipNext();
24: }
25:
26: private void bPrev_Click(object sender, RoutedEventArgs e)
27: {
28: if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
29: BackgroundAudioPlayer.Instance.SkipPrevious();
30: }
As you can see the PlayerState property is used to detect the current state of the player. Calling Play when the player is currntly playing will raise an exception so you have to carefully test the current state to avoid unwanter errors. The PlayerState is also useful if it is used in the PlayerStateChanged event that is notified when the value changes. In this snipped it is used to change the buttons IsEnabled property according with the state:
1: private void Instance_PlayStateChanged(object sender, EventArgs e)
2: {
3: PlayState state = BackgroundAudioPlayer.Instance.PlayerState;
4:
5: this.bPlay.IsEnabled = state != PlayState.Playing && state != PlayState.Unknown;
6: this.bPause.IsEnabled = state == PlayState.Playing && state != PlayState.Unknown;
7: this.bStop.IsEnabled = state == PlayState.Playing && state != PlayState.Unknown;
8: this.bNext.IsEnabled = state == PlayState.Playing && state != PlayState.Unknown;
9: this.bPrev.IsEnabled = state == PlayState.Playing && state != PlayState.Unknown;
10:
11: if (BackgroundAudioPlayer.Instance.Track != null)
12: {
13: this.txtTrack.Text = BackgroundAudioPlayer.Instance.Track.Title;
14: this.txtAuthor.Text = BackgroundAudioPlayer.Instance.Track.Artist;
15: this.txtAlbum.Text = BackgroundAudioPlayer.Instance.Track.Album;
16: this.txtDuration.Text = BackgroundAudioPlayer.Instance.Track.Duration.ToString();
17:
18: using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
19: {
20: string coverFile = Uri.UnescapeDataString(BackgroundAudioPlayer.Instance.Track.AlbumArt.OriginalString);
21:
22: if (file.FileExists(coverFile))
23: {
24: Stream stream = file.OpenFile(coverFile, FileMode.Open, FileAccess.Read);
25:
26: BitmapImage cover = new BitmapImage();
27: cover.SetSource(stream);
28: this.imgCover.Source = cover;
29: }
30: }
31: }
32: }
Finally you have access to the currently playing track. The BackgroundAudioPlayer instance exposes a TrackProperty that is the current AudioTrack provider by the background agent. In this code I use this information to display the AlbumArt and other properties on the page:
When to use Audio Agents
While I was working on the samples for this article I often ask myself about the usefulness of these features. I think they are a good entry point for provider that want to feed phones with music service that are alternative to the zune marketplace. With the ability of connect to the network to stream content and to save it to the marketplace I think it will be very easy to integrate some source to the phone. Also I think there are interesting opportunities in the AudioStreamingAgent due to its capability of playing audio that is generated on board of the phone. As for any other feature of this operating system ther will be someone ready to take advantage of them.