Chapter 29. Background Audio


In This Chapter

Using the background audio player with an audio player agent

Creating a UI for controlling the background audio player

The Universal Volume Control (UVC)

Responding to user actions in an audio player agent

Unit testing and the background audio player

Monitoring audio playback progress

Creating audio streaming agents

Using a custom audio streaming agent to play audio stored as assembly resources


Windows Phone allows you to create apps that respond to the Universal Volume Control (UVC) on the phone and, in conjunction with an onscreen menu, coordinate the playback of audio, even when your app is not running in the foreground.

This chapter begins with an overview of the background audio player and shows how it is used to control the playback of local or remote audio files. You look at how audio file information is represented by the AudioTrack class and examine the role of background audio agents, which are used to coordinate audio playback while your app is not running in the foreground.

The chapter then demonstrates how to build a UI that leverages the background audio agent for controlling playback while your app is in the foreground.

Finally, the chapter examines audio streaming agents and how they are used to provide custom streaming and decoding of audio files. You see how a custom audio streaming agent can be employed to play audio directly from an assembly resource, something that is not possible using the built-in streaming and decoding system of the phone.

Background Agent Recap

In Chapter 27, “Scheduled Actions,” you learned that Windows Phone supports the following three types of BackgroundAgents:

ScheduledTaskAgent (presented in Chapter 27)

AudioPlayerAgent

AudioStreamingAgent

Background agents are classes invoked by the OS at certain times to carry out activities in the background (from another process) while your app may not actually be running in the foreground.

This chapter examines the last two agent types: AudioPlayerAgent and AudioStreamingAgent.

Background Audio Overview

The following comprise the three main classes used for background audio, which are explored in this chapter:

BackgroundAudioPlayer

AudioPlayerAgent

AudioStreamingAgent

BackgroundAudioPlayer is a class that allows you to specify an audio file for playback and to carry out playback activities such as play, pause, fast-forward, and rewind. BackgroundAudioPlayer allows you to specify track information, such as the title of the track and its location. The built-in playback capabilities of the BackgroundAudioPlayer allow audio files to be streamed from either a remote server or locally from isolated storage.

Out of the box, BackgroundAudioPlayer supports playback formats such as MP3, WMA, and WAV. For a complete list of supported audio formats see http://bit.ly/pnnT8C. BackgroundAudioPlayer offers little in the way of coordinating the playback of a list of audio tracks. This is the task of the AudioPlayerAgent.

AudioPlayerAgent classes are specialized agents used to retrieve audio files and coordinate the playback of background audio via the BackgroundAudioPlayer. AudioPlayerAgent provides a number of method callbacks that are automatically called by the OS based on user actions.

An AudioStreamingAgent can be used to customize how audio data is procured by your BackgroundAudioPlayer. You can supplant the BackgroundAudioPlayer’s media sourcing capabilities by providing your own logic to download and decode an audio file.

Background Audio Player

The BackgroundAudioPlayer class implements the singleton pattern. The single instance of the BackgroundAudioPlayer is accessed in your foreground app via its static Instance property, as shown:

BackgroundAudioPlayer.Instance.Track
    = new AudioTrack(
             new Uri("http://example.com/Audio.mp3", UriKind.Absolute),
             "Track Name", "Artist", "Album", null);
BackgroundAudioPlayer.Instance.Play();

The behavior of the BackgroundAudioPlayer changes depending on whether it is being used in your foreground app or from an AudioPlayerAgent. When the BackgroundAudioPlayer is used in your foreground app, all calls are relayed to your AudioPlayerAgent. When used from an AudioPlayerAgent, the BackgroundAudioPlayer affects the playback of audio directly. Without an AudioPlayerAgent, the BackgroundAudioPlayer does nothing.

This dual behavior can be best understood by looking at the internal workings of the BackgroundAudioPlayer. The BackgroundAudioPlayer is actually a proxy that relies on a native implementation of an internal IAudioPlaybackManager interface. When the BackgroundAudioPlayer is used in your foreground app, the IAudioPlaybackManager relays all audio requests to the app’s AudioPlayerAgent. Conversely, when used from your AudioPlayerAgent, the IAudioPlaybackManager uses the native audio playback system instead.

BackgroundAudioPlayer contains several public methods that allow you to control the playback of audio (see Table 29.1).

Table 29.1. BackgroundAudioPlayer Audio Playback Related Methods

Image

BackgroundAudioPlayer contains several public audio playback related properties, which are shown in Table 29.2.

Table 29.2. BackgroundAudioPlayer Audio Playback Related Properties

Image

BackgroundAudioPlayer contains a principle event, PlayStateChanged, which is used to detect, for example, when a track begins playing. You see later in the chapter how this event can be used to hide and show UI elements based on the current state of the player.

Representing Audio Files with the AudioTrack Class

The AudioTrack class includes information about the location of an audio file, as well as track metadata such as the track’s title. Table 29.3 describes each of the AudioTrack properties.

Table 29.3. AudioTrack Properties

Image

AudioTrack implements IEditableObject to enable batching of property change requests. Once an AudioTrack object has been instantiated, it cannot be changed without putting it in edit mode first. AudioTrack uses an IAudioTrack backing field, which links the managed AudioTrack object to the native audio system via Com Interop. To make changes to an AudioTrack object, its BeginEdit method must be called. Once all changes have been made, EndEdit should be called. This commits the property changes to the OS via the native IAudioTrack object.

Creating a Custom Audio Player Agent

An audio player agent can be created by selecting the Windows Phone Audio Playback Agent project type from the Visual Studio New Project dialog (see Figure 29.1). This produces a new project that includes a class that derives from AudioPlayerAgent.

Image

Figure 29.1. Visual Studio Windows Phone agent projects

When you link to an Audio Player Agent project from your main project, a BackgroundServiceAgent task definition is added to your main project’s WMAppManifest.xml file, resembling the following:

<Tasks>
  <DefaultTask  Name="_default" NavigationPage="MainPage.xaml"/>
  <ExtendedTask Name="BackgroundTask">
      <BackgroundServiceAgent Specifier="AudioPlayerAgent"
           Name="<Unique name within the app>"
           Source="<Assembly>"
           Type="<Namespace.Class>" />
  </ExtendedTask>
</Tasks>

It is not necessary to use Visual Studio’s Audio Player Agent project template to create a custom background audio agent. You can create the class and add the task definition to the WMAppManifest.xml file manually if you so choose.

AudioPlayerAgent Sample

The custom audio player agent for this section includes a simple playlist, which includes both locally stored and remote audio files. The class is called BackgroundAudioPlayerAgent, and it is located in the WindowsPhone7Unleashed.BackgroundAudio project, within the BackgroundAudio solution in the downloadable sample code.

A static list of audio tracks defines the playlist, shown in the following excerpt:

static readonly List<AudioTrack> playList
    = new List<AudioTrack>
        {
            new AudioTrack(new Uri("AudioFiles/Audio01.wma",
                                    UriKind.Relative),
                              "Ringtone 1",              // title
                              "Windows Phone",           // artist
                              "Windows Phone Ringtones", // album
                              null),
            /* ... */

            /* A remote URI. */
            new AudioTrack(new Uri("http://example.com/track.mp3",
                                   UriKind.Absolute),
                              "Episode 29",
                              "Windows Phone Radio",
                              "Windows Phone Radio Podcast",
                              null)
        };


Tip

AudioPlayerAgents remain active during playback. This allows the creation of dynamic playlists. During playback, you could retrieve a playlist on a background thread from a remote server.


If providing playback of local media files, the files must be copied to isolated storage before they can be played by the background audio player. The IIsolatedStorageUtility is a custom interface that abstracts access to isolated storage. It is used by the BackgroundAudioPlayerAgent to detect whether the media files have already been written to isolated storage; if not, it is used to copy the sample files. The downloadable sample code contains all the code for the IIsolatedStorageUtility and its default implementation IsolatedStorageUtility. See the following excerpt:

internal static void CopyAudioToIsolatedStorage(
    IIsolatedStorageUtility isolatedStorageUtility)
{
    ArgumentValidator.AssertNotNull(
        isolatedStorageUtility, "isolatedStorageUtility");

    /* If a file exists, do not copy. */
    if (isolatedStorageUtility.FileExists("AudioFiles/Audio01.wma"))
    {
        return;
    }

    string[] files = new[] { "Audio01.wma", "Audio02.wma", "Audio03.wma" };

    var sourceToDestinations = from file in files
                        let path = "AudioFiles/" + file
                        select new KeyValuePair<string, string>(path, path);

    isolatedStorageUtility.CopyApplicationResourcesToIsolatedStorage(
                                                     sourceToDestinations);
}

The custom BackgroundAudioPlayerAgent keeps track of the current track number using a static int field. This is made possible because, unlike its ScheduledTaskAgent counterpart, the process for an AudioPlayerAgent is not terminated when NotifyComplete is called.

The track number field is used to select the correct track to play when the user requests the previous or next track, as shown:

void PlayNextTrack(BackgroundAudioPlayer player)
{
    if (++trackNumber >= playList.Count)
    {
        trackNumber = 0;
    }

    PlayTrack(player);
}

void PlayPreviousTrack(BackgroundAudioPlayer player)
{
    if (--trackNumber < 0)
    {
        trackNumber = playList.Count - 1;
    }

    PlayTrack(player);
}

A PlayTrack method is used to toggle the play state of the BackgroundAudioPlayer, as shown:

void PlayTrack(BackgroundAudioPlayer player)
{
    if (player.PlayerState == PlayState.Paused)
    {
        player.Play();
    }
    else
    {
        player.Track = playList[trackNumber];
    }
}

During playback, when the BackgroundAudioPlayer.Track property is set, the track does not begin immediate playback, but rather the BackgroundAudioPlayer is placed into a TrackReady state, and the AudioPlayerAgent.OnPlayStateChanged method is called. You see how this fits together in a moment.

AudioPlayerAgent Virtual Methods

AudioPlayerAgent has the following three virtual methods that can be overridden in your implementation:

OnPlayStateChanged

OnUserAction

OnError

The following sections explore each method in detail.

AudioPlayerAgent.OnPlayStateChanged

OnPlayStateChanged allows you to respond to changes in the playback state of an audio track. For example, it allows you to transition the play state of a track once it has completed downloading or to move to the next track when a track finishes, as shown in the following excerpt:

protected override void OnPlayStateChanged(
    BackgroundAudioPlayer player, AudioTrack track, PlayState playState)
{
    switch (playState)
    {
        case PlayState.TrackReady:
            /* The track to play is set in the PlayTrack method. */
            player.Play();
            break;

        case PlayState.TrackEnded:
            PlayNextTrack(player);
            break;
    }

    NotifyComplete();
}

Table 29.4 describes the PlayState enum values that can be handled in the OnPlayStateChanged method.

Table 29.4. PlayState Enum

Image

Note

The OnPlayStateChanged method is used to respond to playback state changes, but not to errors that occur during playback. Use the OnError to respond to such errors.


AudioPlayerAgent.OnUserAction

The name of the OnUserAction method is somewhat misleading. The task of the OnUserAction method is to handle BackgroundAudioPlayer actions that occur in your foreground app.

Two actions cause the AudioPlayerAgent.OnUserAction method to be called. The first action occurs when the user presses the UVC and an onscreen menu is displayed allowing the user to play, pause, and skip forward and backward between tracks (see Figure 29.2). User actions do not directly affect the BackgroundAudioPlayer, but rather your AudioPlayerAgent is responsible for manipulating the player via the OnUserAction method.

Image

Figure 29.2. Tapping the onscreen menu background launches your app.

The second action occurs when your app is in the foreground and it calls one of the playback related methods on the BackgroundAudioPlayer.Instance object, such as Play. This type of action may or may not have been directed by the user.


Tip

The volume-up and volume-down buttons and the play/pause button of the UVC can be simulated in the Windows Phone emulator using the F9, F10, and F11 keys, respectively.

While a function key simulates pressing the play/pause button, the play/pause button is not actually a required hardware button on Windows Phone devices and therefore may not be present on every device.


OnUserAction is passed a UserAction enum value that allows you to determine the behavior of the audio player agent. In some cases the player can be put into action immediately. See the following excerpt:

protected override void OnUserAction(
    BackgroundAudioPlayer player, AudioTrack track, UserAction action,
    object param)
{
    /* User actions can be initiated from the main application
     * or from the UVC (Universal Volume Control). */
    switch (action)
    {
        case UserAction.Play:
            PlayTrack(player);
            break;

        case UserAction.Pause:
            player.Pause();
            break;

        case UserAction.SkipPrevious:
            PlayPreviousTrack(player);
            break;

        case UserAction.SkipNext:
            PlayNextTrack(player);
            break;

        case UserAction.Stop:
            player.Stop();
            break;
    }

    NotifyComplete();
}

Table 29.5 lists the UserAction enum values that can be handled in the OnUserAction method.

Table 29.5. UserAction Enum

Image

Note

The param object argument, of the OnUserAction method, is used only with the action argument UserAction.Seek, in which case the param argument is a TimeSpan that indicates the requested track position.


AudioPlayerAgent.OnError

OnError is called when an Exception is raised during playback of an audio track. The method is generally called when the agent encounters a connection error when downloading an audio file from a remote server and offers the opportunity to transition to a different track, one that is potentially stored locally if the error is due to an absence of network connectivity.

In the following excerpt, you see that when an error is determined to be fatal, the Abort method is called, signaling to the OS that playback should be disabled. The method implementation is identical to the base class implementation and need not be implemented if you do not want to provide explicit error handling:

protected override void OnError(BackgroundAudioPlayer player,
                                AudioTrack track,
                                Exception error, bool isFatal)
{
    if (isFatal)
    {
        Abort();
    }
    else
    {
        NotifyComplete();
    }
}

Controlling Background Audio from Your Foreground App

Most apps employing background audio are going to want to provide an enhanced interface within the foreground app for controlling playback and displaying track information. This section looks at the MainPage and associated classes of the WindowsPhone7Unleashed.BackgroundAudio project. The page allows the user to play audio via the BackgroundAudioPlayer class. It provides application bar buttons for play and pause, stop, rewind, and forward, and several fields for displaying the current track information.


Note

It is a Windows Phone Marketplace certification requirement that when the user is already playing background music on the phone when your app is launched, your app must ask the user for consent to stop playing or to adjust the background music. This prompt must occur each time the app launches, unless there is an opt-in setting provided to the user, and the user has used this setting to opt-in.


A Testable BackgroundAudioPlayer

The BackgroundAudioPlayer class is inherently difficult to use in unit tests because of the cross process interaction with the AudioPlayerAgent and its dependence on native phone components. For this reason, I have provided a wrapper called BackgroundAudioPlayerProxy, which implements a custom IBackgroundAudioPlayer interface. In addition, there is a second implementation: MockBackgroundAudioPlayer, which allows you to test whether your code called the background audio player as expected; it allows you to test, for example, whether your code attempted to play a track.

The code for IBackgroundAudioPlayer and its associated class implementations is not shown in this chapter because it is lengthy, and the methods, properties, and events of the IBackgroundAudioPlayer coincide with those of the built-in BackgroundAudioPlayer. You can, of course, find the code in the downloadable sample code for the book.

MainPageViewModel

The viewmodel is declared as a field of the MainPage, as shown:

readonly MainPageViewModel viewModel = new MainPageViewModel(
                                          new BackgroundAudioPlayerProxy(),
                                          new IsolatedStorageUtility());

The MainPageViewModel class constructor accepts an IBackgroundAudioPlayer as well as an IIsolatedStorageUtility instance (see Listing 29.1). A subscription to the PlayStateChanged event of the background audio player allows the viewmodel to update various properties whenever the play state changes.

The viewmodel contains five commands for controlling the BackgroundAudioPlayer while the app is in the foreground, which provide for playing, pausing, stopping, and skipping between tracks.

The viewmodel’s Refresh method is called to update the viewmodel, which relies on the state of the BackgroundAudioPlayer. A Timer is used to monitor the progress of the track while it is being played.

Listing 29.1. MainPageViewModel Constructor


public MainPageViewModel(
    IBackgroundAudioPlayer backgroundAudioPlayer,
    IIsolatedStorageUtility isolatedStorageUtility)
{
    player = ArgumentValidator.AssertNotNull(
                backgroundAudioPlayer, "backgroundAudioPlayer");
    ArgumentValidator.AssertNotNull(
                isolatedStorageUtility, "isolatedStorageUtility");

    player.PlayStateChanged += HandlePlayStateChanged;
    BackgroundAudioPlayerAgent.CopyAudioToIsolatedStorage(isolatedStorageUtility);

    playCommand = new DelegateCommand(obj => player.Play());
    pauseCommand = new DelegateCommand(obj => player.Pause());
    stopCommand = new DelegateCommand(obj => player.Stop());
    previousTrackCommand = new DelegateCommand(obj => player.SkipPrevious());
    nextTrackCommand = new DelegateCommand(obj => player.SkipNext());

    Refresh(false);

    timer = new Timer(HandleTimerTick, null, 3000, 1000);
}


The viewmodel’s TrackArtist and TrackTitle properties reflect the Artist and Title properties of the current AudioTrack.

The viewmodel’s VisualState property, which is of type string, determines the visual state of the view and the visibility of various application bar buttons. The VisualState value is set to the PlayerState property of the BackgroundAudioPlayer, as shown:

void Refresh(bool setVisualState = true)
{
    switch (player.PlayerState)
    {
        case PlayState.Playing:
            CanPause = true;
            TrackArtist = player.Track.Artist;
            TrackTitle = player.Track.Title;
            break;
        case PlayState.Paused:
            CanPause = false;
            break;
    }

    BufferingProgress = player.BufferingProgress;

    if (setVisualState)
    {
        VisualState = player.PlayerState.ToString("G");
    }

    if (player.Error != null)
    {
        MessageService.ShowError("A problem occured:" + player.Error);
    }
}

When the viewmodel’s VisualState property changes, the view responds by refreshing the VisualStateManager state. This is performed from the MainPage constructor, as shown:

public MainPage()
{
    InitializeComponent();

    DataContext = viewModel;

    viewModel.PropertyChanged
        += (sender, args) =>
            {
                if (args.PropertyName == "VisualState")
                {
                    SetVisualState();
                }
            };
}

The view’s custom AppBar includes icon buttons bound to the viewmodel’s commands, as shown:

<u:AppBar>
    <u:AppBarIconButton
            Command="{Binding PreviousTrackCommand}"
            Text="Previous"
            IconUri="/Images/ApplicationBarIcons/Previous.png" />

    <!-- A toggle button is used rather than visual state to avoid
         repopulating the app bar when the user taps play or pause. -->
    <u:AppBarToggleButton
            x:Name="Button_Play"
            Command1="{Binding PlayCommand}"
            Text1="Play"
            Icon1Uri="/Images/ApplicationBarIcons/Play.png"
            Command2="{Binding PauseCommand}"
            Text2="Pause"
            Icon2Uri="/Images/ApplicationBarIcons/Pause.png"
            Toggled="{Binding CanPause}"/>

    <u:AppBarIconButton
            x:Name="Button_Stop"
            Command="{Binding StopCommand}"
            Text="Stop"
            IconUri="/Images/ApplicationBarIcons/Stop.png" />

    <u:AppBarIconButton
            Command="{Binding NextTrackCommand}"
            Text="Previous"
            IconUri="/Images/ApplicationBarIcons/Next.png" />
</u:AppBar>

The viewmodel’s ViewState property determines the visibility of each button. The XAML that defines the VisualStateGroups is not shown, but if you are interested, see the downloadable sample code.

Two TextBlock controls are used to display the viewmodel’s TrackArtist and TrackTitle properties, like so:

<TextBlock Text="{Binding TrackArtist}"
            Style="{StaticResource PhoneTextTitle3Style}"
            TextWrapping="Wrap" />
<TextBlock Text="{Binding TrackTitle}"
            Style="{StaticResource PhoneTextTitle2Style}"
            TextWrapping="Wrap" />

Whenever the track changes, these fields are updated according to the new track.

Monitoring Playback Progress

The viewmodel contains a Position property that reflects the progress of the current AudioTrack. Because the BackgroundAudioPlayer does not have facility to monitor the progress of a track directly, the viewmodel uses a Timer to periodically raise a property changed event for the Position property. The tick handler is shown in the following excerpt:

void HandleTimerTick(object state)
{
    if (player.PlayerState == PlayState.Playing)
    {
        OnPropertyChanged(() => Position);
    }
}

When a Position property change is detected in the view, it prompts a Slider control to reread the property. The Position get accessor calculates the position value, which is returned as a value between 0 and 1, as shown:

public double Position
{
    get
    {
        if (player.Track == null || player.Track.Duration.TotalSeconds < 1)
        {
            return 0;
        }
        double result = player.Position.TotalSeconds
                            / player.Track.Duration.TotalSeconds;
        return result;
    }
    set
    {
        if (player.Track != null)
        {
            double newSeconds = player.Track.Duration.TotalSeconds * value;
            TimeSpan newPosition = TimeSpan.FromSeconds(newSeconds);
            player.Position = newPosition;
        }
        OnPropertyChanged(() => Position);
    }
}

Raising a property changed event for the Position property causes the Slider, which is bound to the property, to be updated. The Slider is defined like so:

<Slider Value="{Binding Position, Mode=TwoWay}" Minimum="0" Maximum="1" />

Figure 29.3 shows the MainPage with the slider indicating the progress of the current track and the application bar icon buttons for controlling playback.

Image

Figure 29.3. A user interface to control the BackgroundAudioPlayer from a foreground app

There are numerous possibilities for extending an app such as this. For example, it could be extended to include a view for the playlist or an image for the album art—the sky’s the limit!

Audio Streaming Agents

Audio streaming agents allow you to customize how audio files are downloaded and decoded by your app, and they enable you to support live streaming scenarios not natively supported by the phone.

An audio streaming agent can be created in the same manner as an audio player agent, by using the “Windows Phone Audio Streaming Agent” Visual Studio project template (shown previously in Figure 29.1).

The Visual Studio project template produces a class that derives from AudioStreamingAgent. The agent must then be registered with your main projects WMAppManifest.xml file, which is done automatically when linking to an Audio Streaming Agent project. The XML definition can be seen alongside the AudioPlayerAgent, as shown:

<Tasks>
  <DefaultTask  Name="_default" NavigationPage="MainPage.xaml"/>
    <ExtendedTask Name="BackgroundTask">
        <BackgroundServiceAgent Specifier="AudioPlayerAgent"
            Name="<Unique name within the app>"
            Source="<Assembly>"
            Type="<Namespace.Class>" />
        <BackgroundServiceAgent Specifier="AudioStreamingAgent"
            Name="<Unique name within the app>"
            Source="<Assembly>"
            Type="<Namespace.Class>" />
    </ExtendedTask>
</Tasks>

Registering a custom AudioStreamingAgent does not disable the built-in streaming and decoding of the phone. The OS determines whether to use the built-in audio streaming or your custom AudioStreamingAgent, based on the AudioTrack’s Source property, each time a new AudioTrack is played. To have your custom AudioStreamingAgent called for a particular track, assign the Source property of the AudioTrack to null, as shown in the following excerpt:

new AudioTrack(null, /* Uri is null so that it is sent
                      * to the AudioStreamingAgent
                      * called AssemblyAudioStreamingAgent. */
                      "Assembly Track!",
                      "Acquired using AudioStreamingAgent",
                      "Unleashed",
                      null,
                      "AudioFiles/Audio04.mp3", // Tag parameter.
                      EnabledPlayerControls.All),

If the Uri is not null, the OS performs all streaming and decoding. By using this technique, you can use a combination of both the inbuilt streaming and decoding for some tracks, and custom streaming and decoding for others.

The AudioTrack class contains a Tag property, which can be used to pass information to an AudioStreamingAgent. Notice from the previous excerpt that this property is set to the relative path of an audio file resource. You see, in the next section, how this value is used to resolve an audio file resource from the current assembly.

AudioStreamingAgent contains a virtual method named OnBeginStreaming, which is overridden to handle streaming and decoding of an audio track. The method has two parameters: the AudioTrack and an AudioStreamer. The AudioStreamer allows you to attach your own MediaStreamSource. MediaStreamSource provides you with direct access to APIs for manipulating encoded elementary audio and video streams. It enables you to implement your own mechanism for processing the contents of a media file, enabling support beyond the native built-in media formats.

Creating a custom MediaStreamSource is a large topic and one that is outside the scope of this book. A good starting point for learning about the MediaStreamSource is the MSDN ManagedMediaHelpers project located at http://archive.msdn.microsoft.com/ManagedMediaHelpers.

While not covering MediaStreamSource in detail, this section does, however, look at applying a specialized MediaStreamSource from the ManagedMediaHelpers project to enable the playback of audio files that are located in an app’s assembly, something that is not supported by the phone’s built-in streaming and decoding system.

Using a MediaStreamSource to Play Back an Assembly Resource

The built-in streaming and decoding system on the phone only supports playback of local audio files when they are located in isolated storage. This section looks at enabling the direct playback of audio files located within an app assembly.

The code for this section resides in the AssemblyAudioStreamingAgent class of the WindowsPhone7Unleashed.BackgroundAudio project in the downloadable sample code. AssemblyAudioStreamingAgent is a subclass of AudioStreamingAgent and overrides the base type’s OnBeginStreaming method (see Listing 29.2). The custom IsolatedStorageUtility is used to retrieve the byte stream for an audio file whose location is specified by the AudioTrack.Tag property. An Mp3MediaStreamSource, from the ManagedMediaHelpers library, is created using the stream. The Mp3MediaStreamSource is then assigned to the AudioStreamer, which then relies on the Mp3MediaStreamSource to provide format-independent audio data.

Listing 29.2. AssemblyAudioStreamingAgent.OnBeginStreaming Method


protected override void OnBeginStreaming(AudioTrack track, AudioStreamer streamer)
{
    IsolatedStorageUtility utility = new IsolatedStorageUtility();
    Uri uri = new Uri(track.Tag, UriKind.Relative);
    Stream stream = utility.GetApplicationResourceStream(uri);
    Mp3MediaStreamSource mediaStreamSource
            = new Mp3MediaStreamSource(stream, stream.Length);
    streamer.SetSource(mediaStreamSource);

    /* Not to be called. */
    //NotifyComplete();
}



Note

Do not call NotifyComplete within the OnBeginStreaming method. Doing so causes the process running the AudioStreamingAgent to be terminated, which halts playback of the file.


Having the ability to provide your own custom streaming means that you can support other third-party services or retrieve files from, for example, a WCF service, which is not supported natively by the OS.

Listing 29.3 demonstrates how to use a WebRequest to manually stream an audio file from the Web.

Listing 29.3. OnBeginStreaming Method with WebRequest


protected override void OnBeginStreaming(AudioTrack track, AudioStreamer streamer)
{
    HttpWebRequest request
             = WebRequest.CreateHttp("http://localhost:4864/Audio.mp3");
    request.AllowReadStreamBuffering = true;
    request.BeginGetResponse(asyncResult =>
        {
            HttpWebResponse response
                = request.EndGetResponse(asyncResult) as HttpWebResponse;
            if (response != null)
            {
                Stream stream = response.GetResponseStream();
                mediaStreamSource = new Mp3MediaStreamSource(
                                            stream, response.ContentLength);
                streamer.SetSource(mediaStreamSource);
            }
    }, null);
}


While not something you will need every day, audio streaming agents provide the means to overcome any limitations imposed by the built-in streaming and decoding system on the phone.

Summary

This chapter began with an overview of the background audio player and showed how it is used to control the playback of local or remote audio files. You looked at how audio file information is represented by the AudioTrack class and examined the role of background audio agents, which are used to coordinate audio playback while your app is not running in the foreground. You saw that when used in the foreground, a BackgroundAudioPlayer forwards all calls to the registered AudioPlayerAgent. Conversely, when a BackgroundAudioPlayer is used in an AudioPlayerAgent, it directly affects playback on the device.

The chapter then demonstrated how to build a UI that leverages the background audio agent for controlling playback while your app is in the foreground.

Finally, the chapter examined audio streaming agents, and you saw how they are used to provide custom streaming and decoding of audio files. You also saw how a custom audio streaming agent can be employed to play audio directly from an assembly resource, something not possible using the built-in streaming and decoding system of the phone.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.213.27