3. Audio Agent
Although you can write applications that play
audio , you also might want that audio
to continue regardless of whether your application is running. In
addition to continuing to play the audio, you might want to integrate
with the Universal Volume Control (which enables the user to move to
pause audio as well as move to the previous and next tracks). You can
see the Universal Volume Control (UVC) in Figure 5.
FIGURE 5 The Universal Volume Control in action
To be able to have audio
play in the background, you need to have an audio agent. There are two
types of audio agents: audio playback agents and audio streaming agents.
For this example, we’ll focus on adding an audio playback agent because
that is a much more common case.
Background audio works by your application
having an audio agent associated with it. This audio agent is solely
responsible for handing requests to change the audio in any way (for
example, move tracks, pause, and so on). Your application can have
controls that request those changes, but it is ultimately the agent that
does the work so that the same agent code is used when the user changes
the audio (again, moving tracks, pausing, and so on) through the
phone’s built-in controls (like the UVC).
To get started, you need to add a new Audio
Playback Agent project to your project , as shown in Figure 6.
FIGURE 6 Adding an audio agent to your project
This new project not only creates a project for the audio agent, but also modifies your WMAppManifest.xml
file (similar to what the Scheduled Task Agent project did earlier):
<Deployment ...>
<App ...>
...
<Tasks>
<DefaultTask Name="_default"
NavigationPage="MainPage.xaml" />
<ExtendedTask Name="BackgroundTask">
<BackgroundServiceAgent Specifier="AudioPlayerAgent"
Name="MyAudioAgent"
Source="MyAudioAgent"
Type="MyAudioAgent.AudioPlayer" />
</ExtendedTask>
</Tasks>
...
</App>
</Deployment>
The BackgroundServiceAgent
element added here specifies that the specifier of the agent is an AudioPlayerAgent
, which tells the operating system that the AudioPlayer
class is responsible for playing audio if the application decides to
play background audio. Before you can use the new audio agent, you will
need to add a reference to the audio agent project from your main phone
application project.
Unlike the prior agent types, you do not need to register this type of agent with the ScheduledActionService
. Instead, you will simply use the BackgroundAudioPlayer
class. This class has a static property called Instance
that returns the singleton background player object. You can use this
class to tell the background audio to start, skip, or stop (or even
seek). For instance, to implement a button in your application that can
play or pause the current audio, you could do the following:
private void playButton_Click(object sender, RoutedEventArgs e)
{
// Tell the agent to play or pause
if (BackgroundAudioPlayer.Instance.PlayerState !=
PlayState.Playing)
{
BackgroundAudioPlayer.Instance.Play();
}
else
{
BackgroundAudioPlayer.Instance.Pause();
}
}
What is important to see here is that all your
application’s code should do is send commands to the background audio
player (which is controlled by the agent). When you tell the BackgroundAudioPlayer
class to “play,” it then routes that command to the agent where all the
hard work is done. So, let’s look at the skeleton of the audio agent
the new project created for you:
public class AudioPlayer : AudioPlayerAgent
{
protected override void OnPlayStateChanged(
BackgroundAudioPlayer player,
AudioTrack track,
PlayState playState)
{
//TODO: Add code to handle play state changes
NotifyComplete();
}
protected override void OnUserAction(
BackgroundAudioPlayer player,
AudioTrack track,
UserAction action,
object param)
{
//TODO: Add code to handle user actions
// through the application and system-provided UI
NotifyComplete();
}
protected override void OnError(
BackgroundAudioPlayer player,
AudioTrack track,
Exception error,
bool isFatal)
{
//TODO: Add code to handle error conditions
NotifyComplete();
}
protected override void OnCancel()
{
}
}
The agent
class contains four different methods that are called in different cases while background audio is playing. Let’s start with OnUserAction
:
protected override void OnUserAction(
BackgroundAudioPlayer player,
AudioTrack track,
UserAction action,
object param)
{
// May be initiated from the app or the UVC
switch (action)
{
case UserAction.Play:
{
PlayCurrentTrack();
break;
}
case UserAction.Pause:
{
player.Pause();
break;
}
case UserAction.SkipNext:
{
MoveToNextSong();
break;
}
case UserAction.SkipPrevious:
{
MoveToPreviousSong();
break;
}
}
NotifyComplete();
}
This method is called whenever the user makes a
request to manipulate the currently playing music (including the first
request to “play” a song). It passes in the current instance of the BackgroundAudioPlayer
so you can manipulate the song to be played directly (as is shown when the UserAction
is Pause
).
For playing and moving tracks, the list of tracks is owned by the audio
agent (not the background audio player). This means you will typically
need your audio agent to keep track of the list of tracks and the
current position in that list. Any state you need to keep around needs
to be static as the specific instance of the audio
agent should be stateless (because it can be destroyed or
garbage-collected if necessary). So, for this example you need to store
both the list of tracks and the track counter as static data:
public class AudioPlayer : AudioPlayerAgent
{
// Song Counter
static int _currentSong = 0;
// Songs
static SongList _songList = new SongList();
// ...
}
This enables you to keep the list of songs the
user has requested (it can be stored in isolated storage or another
mechanism for sharing between the audio agent and the application). Then
the audio agent can implement the PlayCurrentTrack
(which this example calls when a user requests to begin playing), like so:
void PlayCurrentTrack()
{
// Retrieve a Song object that contains the song to play
var song = _songList.Songs.ElementAt(_currentSong);
// Build Audio Track
var track = new AudioTrack(song.Path,
song.Name,
song.Artist,
song.Album,
song.Art);
// Instruct the player that this is the current track to play
BackgroundAudioPlayer.Instance.Track = track;
}
This simply returns the current song in the list as a custom type (called Song
in this example) and builds a new AudioTrack
object from the Song
object (by setting a URI to the path to the song as well as descriptive
properties for the song name, artist name, album name, and URI to an
image for the album art).
After a track has been set, the audio will not
automatically start playing. Setting the track simply tells the
background audio to attempt to find the audio to play. When it finds the
audio, it will change its play state. Luckily, one of the audio agent’s
generated methods is called when the play state changes:
protected override void OnPlayStateChanged(
BackgroundAudioPlayer player,
AudioTrack track,
PlayState playState)
{
switch (playState)
{
// Track has been selected and is ready to be played
// (includes starting to download if audio is remote)
case PlayState.TrackReady:
{
player.Play();
break;
}
case PlayState.TrackEnded:
{
MoveToNextSong();
break;
}
}
NotifyComplete();
}
The PlayState
enumeration has a number of values, but in your case you most want to pay attention to two states:
• TrackReady: This indicates that the BackgroundAudioPlayer
has located and loaded the audio and is ready to actually play.
• TrackEnded: This indicates that a track just ended.
When this method is called with TrackReady
,
that’s your cue to go ahead and play the audio that has been readied.
This is what actually causes the audio to start. You can also handle
other states (for instance, TrackEnded
) to determine what to do next. In this example the code simply moves the track to the next item, eventually calling PlayCurrentTrack
to load the next track. No matter what happens at this point, you should call NotifyComplete
or Abort
(like the earlier scheduled task agent example explained).
So far, you’ve seen how to play the first track,
but what about changing tracks? Earlier in this section, when the user
requested to go to the next or previous track, you called methods that
would do that work, but what do those methods actually do? They do the
simple work of changing the current track number and telling the audio
agent to play the current track after the counter has changed:
private void MoveToPreviousSong()
{
if (--_currentSong < 0)
{
_currentSong = _songList.Songs.Count() - 1;
}
PlayCurrentTrack();
}
private void MoveToNextSong()
{
if (++_currentSong >= _songList.Songs.Count())
{
_currentSong = 0;
}
PlayCurrentTrack();
}
The work of determining what to set the _currentSong
value to is trivial code after that track number has changed. It calls PlayCurrentTrack
, which you saw earlier as simply setting the BackgroundAudioPlayer
’s Track
property to the new track.
Back in the main application, you can use the BackgroundAudioPlayer
class to retrieve the current state of background audio. For example,
to show the currently playing song, you might check the current Track
:
public partial class MainPage : PhoneApplicationPage
{
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (BackgroundAudioPlayer.Instance.Track != null)
{
currentSong.Text = BackgroundAudioPlayer.Instance.Track.Title;
}
}
// ...
}
When the page is launched, it simply tests
whether there is a current track, and if so, it sets the title in the
user interface to indicate to the user what the current song is. But
because the audio agent is controlling the state of the background
audio, you might need to know about the change to the PlayState
as well. This enables your application to show the current track and enable/disable buttons as necessary:
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
BackgroundAudioPlayer.Instance.PlayStateChanged +=
Instance_PlayStateChanged;
}
void Instance_PlayStateChanged(object sender, EventArgs e)
{
if (BackgroundAudioPlayer.Instance.Track != null)
{
currentSong.Text = BackgroundAudioPlayer.Instance.Track.Title;
}
}
// ...
}
When the event is fired, the play state has
changed and (as this example shows) you can change the current audio
track if it is valid.
This should give you a general feel for the
nature of how audio agents work. The specifics of sharing data between
the agent and the application will greatly vary depending on how you
want to use background audio, but this way you can allow the user to
control the audio without it necessarily being part of the phone’s media
library.