Building an Audio Track Listener for Windows Phone

Introduction

Windows Phone platform supports the ability to have tasks running in the background. This enables applications to play music in the background while the user is using another application in the foreground.

In this article, we will explore how to build a simple Windows Phone application that can play audio tracks in the foreground and background.

Basics

Before we gets hands on, here is a brief architectural overview of background audio. This is important since our application needs to operate both in the foreground as well as background.

Background audio application relies on the background agents feature, which was introduced in the recent versions of the Windows Phone platform.

On the Windows Phone platform, media is played through the Zune media queue. Interaction with the media queue happens through the background audio application, which can specify to set the current track, start/stop playback, pause, go forward and go reverse. The BackgroundAudioPlayer class can be used for this.

Because the Universal Volume Control directly supports interacting with the media queue, our audio application’s volume can also be controlled by the volume controls (without needing a specific volume control within the application).

A simple audio track listener would comprise of two components – (i) front end, which is responsible for the user interface for playback, and (ii) implementation of the AudioPlayerAgent class, which is designed to play audio in the background.

The architecture of an audio track listener application can be represented as below.

Architecture of an Audio Track Listener App
Architecture of an Audio Track Listener App

Now that we have seen the architecture, we can start developing a very simple application.

Hands On

Create a new empty Visual Studio solution called WPAudioPlayerDemo.

New Project
New Project

Add a new Windows Phone Audio Playback Agent project “WPAudioPlaybackAgent” to the solution.

Windows Phone Audio Playback Agent
Windows Phone Audio Playback Agent

Next, we create a new Windows Phone app project and add it to the solution. We will call it WPAudioPlayer.

For the purpose of the demo, you can add a few media files to the solution (In your actual application, you should query your media library and populate the media queue from there).

If you are copying the media, make sure you specify to copy it to the output directory.

Copy Media to the Output Directory
Copy Media to the Output Directory

We will now add a reference to the WPAudioPlaybackAgent project in our WPAudioPlayer project. Right click WPAudioPlayer  project in Solution Explorer and select “Add Reference…”.

Add Reference
Add Reference

The Reference Manager dialog opens. Select Solution from the left bar and select the WPAudioPlaybackAgent project and click OK.

Before we build the UI for our media player application, we will annotate the application manifest file with the background tasks for our application.

Open WMAppManifest.xml as code and add the highlighted blurb in your file.

<?xml version="1.0" encoding="utf-8"?>
 
<Deployment xmlns="http://schemas.microsoft.com/windowsphone/2012/deployment" AppPlatformVersion="8.0">
  <DefaultLanguage xmlns="" code="en-US"/>
  <App xmlns="" ProductID="{2b93fda2-fe28-461f-a433-597862f70c97}" Title="WPAudioPlayer" RuntimeType="Silverlight" Version="1.0.0.0" Genre="apps.normal"  Author="WPAudioPlayer author" Description="Sample description" Publisher="WPAudioPlayer" PublisherID="{b504c18a-5538-450f-81f2-8b93a5dec08b}">
    <IconPath IsRelative="true" IsResource="false">Assets\ApplicationIcon.png</IconPath>
    <Capabilities>
      <Capability Name="ID_CAP_NETWORKING"/>
      <Capability Name="ID_CAP_MEDIALIB_AUDIO"/>
      <Capability Name="ID_CAP_MEDIALIB_PLAYBACK"/>
      <Capability Name="ID_CAP_SENSORS"/>
      <Capability Name="ID_CAP_WEBBROWSERCOMPONENT"/>
    </Capabilities>
    <Tasks>
      <DefaultTask  Name ="_default" NavigationPage="MainPage.xaml"/>
      <ExtendedTask Name="BackgroundTask">
        <BackgroundServiceAgent Specifier="AudioPlayerAgent" Name="WPAudioPlaybackAgent" Source="WPAudioPlaybackAgent" Type="WPAudioPlaybackAgent.AudioPlayer" />
      </ExtendedTask>
    </Tasks>
    <Tokens>
      <PrimaryToken TokenID="WPAudioPlayerToken" TaskName="_default">
        <TemplateFlip>
          <SmallImageURI IsRelative="true" IsResource="false">Assets\Tiles\FlipCycleTileSmall.png</SmallImageURI>
          <Count>0</Count>
          <BackgroundImageURI IsRelative="true" IsResource="false">Assets\Tiles\FlipCycleTileMedium.png</BackgroundImageURI>
          <Title>WPAudioPlayer</Title>
          <BackContent></BackContent>
          <BackBackgroundImageURI></BackBackgroundImageURI>
          <BackTitle></BackTitle>
          <DeviceLockImageURI></DeviceLockImageURI>
          <HasLarge></HasLarge>
        </TemplateFlip>
      </PrimaryToken>
    </Tokens>
    <ScreenResolutions>
      <ScreenResolution Name="ID_RESOLUTION_WVGA"/>
      <ScreenResolution Name="ID_RESOLUTION_WXGA"/>
      <ScreenResolution Name="ID_RESOLUTION_HD720P"/>
    </ScreenResolutions>
  </App>
</Deployment>

Now, we will build the UI for our media player application.

The UI of our application:

Application UI
Application UI

The XAML for the above display is below:

<phone:PhoneApplicationPage
    x:Class="WPAudioPlayer.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">
 
    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
 
        <!-- LOCALIZATION NOTE:
            To localize the displayed strings copy their values to appropriately named
            keys in the app's neutral language resource file (AppResources.resx) then
            replace the hard-coded text value between the attributes' quotation marks
            with the binding clause whose path points to that string name.
 
            For example:
 
                Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}"
 
            This binding points to the template's string resource named "ApplicationTitle".
 
            Adding supported languages in the Project Properties tab will create a
            new resx file per language that can carry the translated values of your
            UI strings. The binding in these examples will cause the value of the
            attributes to be drawn from the .resx file that matches the
            CurrentUICulture of the app at run time.
         -->
 
        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
            <TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>
 
        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Button x:Name="buttonPlay" Content="Play" HorizontalAlignment="Left" Margin="82,314,0,0" VerticalAlignment="Top" Click="buttonPlay_Click" FontSize="15"/>
            <Button x:Name="buttonStop" Content="Stop" HorizontalAlignment="Left" Margin="247,314,0,0" VerticalAlignment="Top" Click="buttonStop_Click" FontSize="15"/>
            <Button x:Name="buttonForward" Content="FastForward" HorizontalAlignment="Left"  Margin="208,242,0,0" VerticalAlignment="Top" FontSize="15" Click="buttonForward_Click"/>
            <Button x:Name="buttonRewind" Content="Rewind" HorizontalAlignment="Left" Margin="123,242,0,0" VerticalAlignment="Top" FontSize="15" Click="buttonRewind_Click"/>
            <Button x:Name="buttonPause" Content="Pause" HorizontalAlignment="Left" Margin="157,314,0,0" VerticalAlignment="Top" Click="buttonPause_Click" FontSize="15"/>
            <Button x:Name="buttonSkipForward" Content="SkipForward" HorizontalAlignment="Left" Margin="323,242,0,0" VerticalAlignment="Top" FontSize="15" Click="buttonSkipForward_Click"/>
            <Button x:Name="buttonSkipPrevious" Content="Skip Previous" HorizontalAlignment="Left" Margin="0,242,0,0" VerticalAlignment="Top" FontSize="15" Width="139" Click="buttonSkipPrevious_Click"/>
            <TextBox x:Name="textBoxNowPlaying" HorizontalAlignment="Left" Height="72" Margin="154,106,0,0" TextWrapping="Wrap" Text="Not currently playing" VerticalAlignment="Top" Width="292"/>
            <TextBlock HorizontalAlignment="Left" Margin="39,131,0,0" TextWrapping="Wrap" Text="Now Playing" VerticalAlignment="Top"/>
 
        </Grid>
 
        <!--Uncomment to see an alignment grid to help ensure your controls are
            aligned on common boundaries.  The image has a top margin of -32px to
            account for the System Tray. Set this to 0 (or remove the margin altogether)
            if the System Tray is hidden.
 
            Before shipping remove this XAML and the image itself.-->
        <!--<Image Source="/Assets/AlignmentGrid.png" VerticalAlignment="Top" Height="800" Width="480" Margin="0,-32,0,0" Grid.Row="0" Grid.RowSpan="2" IsHitTestVisible="False" />-->
    </Grid>
 
</phone:PhoneApplicationPage>

Now, open up the AudioPlayer.cs file in the WPAudioPlaybackAgent project and make the following changes.

Include the following namespaces in your AudioPlayer.cs file.

using
System.Collections.Generic;

We will add a few variables to track the current track and a list for audio tracks.

public class AudioPlayer : AudioPlayerAgent
    {
        static int currentTrack;
        static List<AudioTrack> audioTracks = new List<AudioTrack>
        {
            new AudioTrack(new Uri("Test Music 01.mp3", UriKind.Relative), "Test Music 01", "Some Singer 1", "Some Singer 1", null),
            new AudioTrack(new Uri("Test Music 02.mp3", UriKind.Relative), "Test Music 02", "Some Singer 2", "Some Singer 2", null),
            new AudioTrack(new Uri("Test Music 03.mp3", UriKind.Relative), "Test Music 03", "Some Singer 3", "Some Singer 3", null)
        };
        /// <remarks>
        /// AudioPlayer instances can share the same process.
        /// Static fields can be used to share state between AudioPlayer instances
        /// or to communicate with the Audio Streaming agent.
        /// </remarks>
        static AudioPlayer()
    

Change the constructor of AudioPlayer class to be static.

Change:

public AudioPlayer()

To:

static AudioPlayer()

Make the following highlighted changes in OnUserAction method.

        protected override void OnUserAction(BackgroundAudioPlayer player, AudioTrack track, UserAction action, object param)
        {
            switch (action)
            {
                case UserAction.Play:
                    if (player.PlayerState != PlayState.Playing)
                    {
                        player.Track = audioTracks[currentTrack];
                        player.Play();
                    }
                    break;
                case UserAction.Stop:
                    player.Stop();
                    break;
                case UserAction.Pause:
                    player.Pause();
                    break;
                case UserAction.FastForward:
                    player.FastForward();
                    break;
                case UserAction.Rewind:
                    player.Rewind();
                    break;
                case UserAction.Seek:
                    player.Position = (TimeSpan)param;
                    break;
                case UserAction.SkipNext:
                    player.Track = GetNextTrack();
                    break;
                case UserAction.SkipPrevious:
                    AudioTrack previousTrack = GetPreviousTrack();
                    if (previousTrack != null)
                    {
                        player.Track = previousTrack;
                    }
                    break;
            }
 
            NotifyComplete();
        }

Change the GetNextTrack() method to use the next track after the current track.

        private AudioTrack GetNextTrack()
        {
 
            AudioTrack track = audioTracks[++currentTrack];
 
            // specify the track
 
            return track;
        }

Make similar changes to the GetPreviousTrack() method.

        private AudioTrack GetPreviousTrack()
        {
 
            AudioTrack track = audioTracks[--currentTrack];
 
            // specify the track
 
            return track;
        }

Now, open the codebehind for the MainPage.xaml (MainPage.xaml.cs) and add a method to save a music file to the isolated storage.

        private void SaveToIsoStore(string fileName)
        {
            IsolatedStorageFile isolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication();
            if (!isolatedStorageFile.FileExists(fileName))
            {
                StreamResourceInfo resource = Application.GetResourceStream(new Uri(fileName, UriKind.Relative));
 
                using (IsolatedStorageFileStream isolatedStorageFileStream = isolatedStorageFile.CreateFile(fileName))
                {
                    int chunkSize = 1024;
                    byte[] bytes = new byte[chunkSize];
                    int byteCount;
 
                    while ((byteCount = resource.Stream.Read(bytes, 0, chunkSize)) > 0)
                    {
                        isolatedStorageFileStream.Write(bytes, 0, byteCount);
                    }
                }
 
            }
 
 
        }

Now, we implement the methods for the click events on the buttons.

        private void buttonPlay_Click(object sender, RoutedEventArgs e)
        {
            if (BackgroundAudioPlayer.Instance.PlayerState != PlayState.Playing)
                BackgroundAudioPlayer.Instance.Play();
        }
        private void buttonSkipPrevious_Click(object sender, RoutedEventArgs e)
        {
            if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
                BackgroundAudioPlayer.Instance.SkipPrevious();
        }
 
        private void buttonRewind_Click(object sender, RoutedEventArgs e)
        {
            if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
                BackgroundAudioPlayer.Instance.Rewind();
        }
 
        private void buttonForward_Click(object sender, RoutedEventArgs e)
        {
            if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
                BackgroundAudioPlayer.Instance.FastForward();
        }
 
        private void buttonSkipForward_Click(object sender, RoutedEventArgs e)
        {
            if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
                BackgroundAudioPlayer.Instance.SkipNext();
        }
 
        private void buttonPause_Click(object sender, RoutedEventArgs e)
        {
            if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
                BackgroundAudioPlayer.Instance.Pause();
            textBoxNowPlaying.Text = "Paused";
        }
 
        private void buttonStop_Click(object sender, RoutedEventArgs e)
        {
            if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
                BackgroundAudioPlayer.Instance.Stop();
            textBoxNowPlaying.Text = "Playback stopped";
        }

Then, we implement the BackgroundPlayStateChanged event handler. We use this event to change the “Now playing” content.

        void BackgroundPlayStateChanged(object sender, EventArgs e)
        {
            if (BackgroundAudioPlayer.Instance.Track != null)
            {
                textBoxNowPlaying.Text = BackgroundAudioPlayer.Instance.Track.Title;
            }
        }

Next, update the constructor for MainPage class to call the SaveToIsoStore() API to store all the files we have. In addition, we will also wire the BackgroundPlayState changed event.

        public MainPage()
        {
            InitializeComponent();
            SaveToIsoStore("Test Music 01.mp3");
            SaveToIsoStore("Test Music 02.mp3");
            SaveToIsoStore("Test Music 03.mp3");
            BackgroundAudioPlayer.Instance.PlayStateChanged += new EventHandler(BackgroundPlayStateChanged);
            // Sample code to localize the ApplicationBar
            //BuildLocalizedApplicationBar();
        }

Finally, we update the OnNavigatedTo event to update the now playing text when the application is brought to the foreground (from background).

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
            {
                textBoxNowPlaying.Text = BackgroundAudioPlayer.Instance.Track.Title;
            }
        }

Our application is now code complete. If you are having trouble following along, you can download the sample code below.

When you run the application the first time, you will see the screen below.

Opening Screen
Opening Screen

When you click “Play”, you will see

‘Play’ Screen
‘Play’ Screen

When you click Skip Forward, the track changes to the next one.

‘Skip Forward’ Screen
‘Skip Forward’ Screen

When you click “Skip Previous”, it goes back to:

‘Skip Previous’ Screen
‘Skip Previous’ Screen

Your music player is now complete.

Summary

In this article, we learned how to build a simple music player application that can play audio tracks. I hope you have found this information useful.

About the author

Vipul Patel is a Program Manager currently working at Amazon Corporation. He has formerly worked at Microsoft in the Lync team and in the .NET team (in the Base Class libraries and the Debugging and Profiling team). He can be reached at vipul.patel@hotmail.com



Related Articles

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Learn How A Global Entertainment Company Saw a 448% ROI Every business today uses software to manage systems, deliver products, and empower employees to do their jobs. But software inevitably breaks, and when it does, businesses lose money -- in the form of dissatisfied customers, missed SLAs or lost productivity. PagerDuty, an operations performance platform, solves this problem by helping operations engineers and developers more effectively manage and resolve incidents across a company's global operations. …

  • Live Event Date: December 18, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT The Internet of Things (IoT) incorporates physical devices into business processes using predictive analytics. While it relies heavily on existing Internet technologies, it differs by including physical devices, specialized protocols, physical analytics, and a unique partner network. To capture the real business value of IoT, the industry must move beyond customized projects to general patterns and platforms. Check out this upcoming webcast …

Most Popular Programming Stories

More for Developers

RSS Feeds