January 2012

Volume 27 Number 01

Touch and Go - Playing Audio Files in Windows Phone

By Charles Petzold | January 2012

Charles PetzoldWhen I first read that the enhancements in Windows Phone OS 7.1 included a way for applications to play sound and music files in the background, I thought, “Don’t we have that already?”

It turns out I was correct, but only a little. It’s indeed possible for a Windows Phone OS 7.0 application to play a music file in the background, but only in a very special case. In all the other cases, any music file that your Windows Phone OS 7.0 application plays will stop when your application is moved to the background. Of course, for most applications, this behavior is entirely appropriate and probably exactly what you want.

But consider an application that delivers music to your phone apart from the phone’s normal music library. For such an application, it’s extremely desirable to continue playing while other applications occupy the foreground or when the screen times out and goes into a locked state. And even for those of us who don’t have a need to write such an application, this facility provides a fun entry point into exploring the new world of “background agents” introduced in Windows Phone OS 7.1.

In the next issue, I’ll show you how to write a Windows Phone program that plays music files in the background. But to provide a broader picture of audio facilities on Windows Phone, I want to begin in this column with the more standard ways to play audio files supported in Windows Phone OS 7.0 as well as version 7.1.

MediaElement and Its Sources

The most common way for a Silverlight program to play a music or sound file is with MediaElement. Nothing is simpler: MediaElement derives from FrameworkElement, so you can put it in the visual tree of a XAML file and just set the Source property to a URL:

<MediaElement Source="http://www.SomeWebSite.com/CoolSong.mp3" />

When the XAML file is loaded, the music file automatically starts playing. MediaElement supports MP3, WMA and WAV files. Details are documented at msdn.microsoft.com/library/windows/apps/ff462087(v=vs.105).aspx.

As an alternative to referencing a file over the Internet, you can embed a sound or music file in your application executable. Add the file to the program in Visual Studio and flag the Build Action as either Content or Resource. (Content is preferred and embeds the file in the XAP executable; with Resource, the file is embedded in the DLL for the program.) Set the Source property to a URL referencing the file name with a folder name if applicable:

<MediaElement Source="Music/LocalSong.wma" />

Although MediaElement can be very simple, there are numerous ways to make it more complicated. One way is to specify the audio file at run time, as I did in the MediaElementDemo program, which is part of the downloadable code for this article.

In this program, the MediaElement is still in the visual tree, but the Source property isn’t set, and AutoPlay is set to False. Media­ElementDemo lets you play the three movements of the Brahms Violin Concerto. (The files are from the Internet Archive at archive.org/­details/BrahmsViolinConcerto-Heifetz. It’s a 1939 performance with violinist Jascha Heifetz and Serge Koussevitzky conducting, originally available on Victor 78-rpm disks.) Three RadioButton elements have their Tag properties set to the sources of the three music files. For the first RadioButton, that’s the full URL of the music file on the Internet Archive Web site. For the second movement, I downloaded the music file (named 02Ii.Adagio.mp3) to my PC, created a folder named Music in the project in Visual Studio and added that file to the folder. The second RadioButton references that file with the name “Music/02Ii.Adagio.mp3.” When either of these two buttons is checked, the event handler obtains the Tag property and creates a Uri object out of it (specifying UriKind.Absolute for the Web reference and UriKind.Relative for the content) and sets that to the Source property of the MediaElement.

The second movement is a file of about 4.5MB, and obviously it increases the size of the executable by a considerable bulk. Adding files of this size to your executable is not recommended and done here only for demonstration!

If your application needs files of that size, a possible compromise is available: The application could download the file once over the Internet and save it to Isolated Storage. That’s what I’ve done for the third movement of the Violin Concerto. The third RadioButton (which is assigned a name of “isoStoreRadio­Button”) has its IsEnabled property initially set to false. Figure 1 shows the download process. In the page’s constructor, if the file isn’t in Isolated Storage, WebClient initiates a background transfer. When the transfer completes, the file is saved to Isolated Storage and the RadioButton is enabled.

Figure 1 Downloading a Web File to Isolated Storage

public MainPage()
{
  InitializeComponent();
  // ...
  // Check if file is in Isolated Storage; otherwise start downloading it
  using (IsolatedStorageFile isoStore =
    IsolatedStorageFile.GetUserStoreForApplication())
  {
    if (isoStore.FileExists(isoStoreRadioButton.Tag as string))
    {
      isoStoreRadioButton.IsEnabled = true;
    }
    else
    {
      WebClient webClient = new WebClient();
      webClient.OpenReadCompleted += OnWebClientOpenReadCompleted;
      webClient.OpenReadAsync(new Uri("http://www.archive.org/....mp3"));
    }
  }
  // ...
}
// When the music file is downloaded, save it to Isolated Storage
void OnWebClientOpenReadCompleted(object sender, 
  OpenReadCompletedEventArgs args)
{
  if (!args.Cancelled && args.Error == null)
  {
    Stream inpStream = args.Result;
    byte[] buffer = new byte[inpStream.Length];
    inpStream.Read(buffer, 0, buffer.Length);
    using (IsolatedStorageFile isoStore =
      IsolatedStorageFile.GetUserStoreForApplication())
    {
      string isoPathName = isoStoreRadioButton.Tag as string;
      string isoDirName = Path.GetDirectoryName(isoPathName);
      if (!isoStore.DirectoryExists(isoDirName))
      {
        isoStore.CreateDirectory(isoDirName);
      }
      using (IsolatedStorageFileStream isoStream =
        isoStore.CreateFile(isoPathName))
      {
        isoStream.Write(buffer, 0, buffer.Length);
        isoStoreRadioButton.IsEnabled = true;
      }
    }
  }
}

In some contexts on Windows Phone OS 7.1, you can define a URI with a prefix of “isostore” to reference a file in Isolated Storage, but this doesn’t work for MediaElement. Fortunately, Media­Element has a SetSource property that accepts a Stream object. Figure 2shows how the Checked handler for the RadioButton elements handles these differences.

Figure 2 Setting the Source on MediaElement

void OnRadioButtonChecked(object sender, RoutedEventArgs args)
{
  RadioButton radioButton = sender as RadioButton;
  string uriString = radioButton.Tag as string;
  // Save index for tombstoning
  radioButtonIndex = radioButtonPanel.Children.IndexOf(radioButton);
  if (radioButton == isoStoreRadioButton)
  {
    // Call SetSource on MediaElement using Isolated Storage stream.
    using (IsolatedStorageFile storage =
      IsolatedStorageFile.GetUserStoreForApplication())
    {
      using (Stream isoStream = storage.OpenFile(uriString, FileMode.Open))
      {
        mediaElement.SetSource(isoStream);
      }
    }
  }
  else
  {
    // Set Source property on MediaElement using URI
    Uri uri = new Uri(uriString, uriString.Contains(':')
      ? UriKind.Absolute : UriKind.Relative);
    mediaElement.Source = uri;
  }
}

Transports and Tombstones

Another way you can make MediaElement more difficult for yourself is by adding controls to pause and to move to the beginning or end of the file. Even more fun is a Slider control that lets you move to a particular point in the file, as shown in Figure 3.

The MediaElementDemo Program
Figure 3 The MediaElementDemo Program

The four ApplicationBar buttons are implemented very simply. Respectively, they set the Position property of MediaElement to zero, call the Play method of MediaElement, call the Pause method and set the Position property to the NaturalDuration property.

The tricky part is enabling and disabling the buttons. For that job, the CurrentStateChanged event of MediaElement is handled. When working out MediaElement logic, it’s helpful first to use Debug.WriteLine in the event handler to get a feel for how the CurrentState property changes as a music file is loaded, buffered, played, paused and ended.

On the phone, all music and sound files are played through a single piece of software and hardware called the Zune Media Queue. If you use the standard Music+Videos application on the phone to play a song or album from your music collection, that music will continue to play in the background when you leave that application and start up other applications—and even when you start the MediaElementDemo program. However, if you start one of the movements of the Brahms Violin Concerto playing, the background music will stop. Now MediaElementDemo is in control.

But if MediaElementDemo leaves the foreground—whether by the user pressing the Start button or letting the screen time out—the Brahms will stop, even if the program is not tombstoned.

In such a circumstance, what do you want to happen when the user returns to the program? If the answer is “Nothing,” you’re in luck! But if you want the music to start up again from where it left off, MediaElementDemo demonstrates how this can be done. In its OnNavigatedFrom override, the program saves the index of the movement currently playing, the state (probably Playing or Paused) and the position. In OnNavigatedTo, the program checks the RadioButton and sets the state and position in the MediaOpened handler.

MediaLibrary and MediaPlayer

I mentioned that prior to Windows Phone OS 7.1, a facility already existed to play certain music files on the phone in the background. The catch is that these music files must be part of the phone’s music library. Your program can play one of these songs, or it can play all the songs on an album or all the songs of a particular artist or genre, or all the songs in a playlist.

The classes to do this are members of the Microsoft.Xna.Framework.Media namespace. To use these XNA classes in a Silverlight project for the phone, you first need to add a reference to the Microsoft.Xna.Framework library. With Windows Phone OS 7.0, Visual Studio gave you a warning about doing this. That warning is gone with Windows Phone OS 7.1.

Any Silverlight program that uses XNA classes to play music must include a special class that implements IApplicationService and calls FrameworkDispatcher.Update every 30th of a second. You can give that class any name you want, but you’ll reference it in the App.xaml file in the ApplicationLifetimeObjects section:

<local:XnaFrameworkDispatcherService />

To play a song from the user’s music library, start by instantiating the MusicLibrary class. Properties named Artists, Albums, Genres and Playlists provide collections of objects of type Artist, Album, Genre and Playlist, and all these classes include a Songs property of type SongCollection that’s a collection of Song objects. (These collections are read-only; your application can’t add anything to the user’s music library or modify it in any way.)

To start playing something, use members of the static MediaPlayer class. The MediaPlayer.Play method accepts a Song object, a SongCollection or a SongCollection with an index to indicate the song to begin.

The PlayRandomSong program contains a button labeled “Play Random Song,” and when you tap that, the following code executes:

void OnButtonClick(object sender, RoutedEventArgs args)
{
    MediaLibrary mediaLib = new MediaLibrary();
    AlbumCollection albums = mediaLib.Albums;
    Album album = albums[random.Next(albums.Count)];
    SongCollection songs = mediaLib.Songs;
    Song song = songs[random.Next(songs.Count)];
    MediaPlayer.Play(song);
}

This code extracts a random album from your collection, a random song from that album and starts playing it. (The Windows Phone emulator contains an album with a few tiny song files, so this program runs on the emulator just fine.)

If you start a song playing with PlayRandomSong, you’ll find that you can navigate away from the program or even terminate the program and the song will continue playing. It’s exactly as if you played that song from the phone’s regular Music+Videos application—and if you start that application, you’ll see the album cover and the song’s title. Moreover, if you press the volume control button on the phone, you’ll see the song at the top of the screen and get access to buttons to pause or go to the beginning or end of the song.

Just as the phone’s Music+Videos application knows what song you’ve played with MediaPlayer.Play, your application can determine which song the phone’s Music+Videos application is currently playing. This information is available from the Queue property of MediaPlayer, which provides a MediaQueue object that indicates the song currently playing and a collection of songs if an album or playlist is playing. The PlayRandomSong program uses a timer to check the ActiveSong property of the queue and displays information about that song. Alternatively, you can set handlers for the ActiveSongChanged event of MediaPlayer.

Creating Song Objects

The PlayRandomSong program obtains a Song object from one of the properties or collections of MediaLibrary, but Song also has a static property named FromUri that creates a Song property based on a file not in your music library. This URI can reference a music file over the Internet or that’s part of the program’s XAP file. (It can’t reference a file in Isolated Storage.) You can then use MediaPlayer to play this Song object. (You can’t create your own SongCollection objects.)

The MediaPlayerDemo program shows how it’s done. This program let you play the Brahms Double Concerto (another 1939 recording from archive.org/details/BrahmsDoubleConcerto_339) with Heifetz again, Emanuel Feuermann on cello and Eugene Ormandy conducting. Because you can’t use MediaPlayer with Isolated Storage, both the first and last movements are Web references.

Another difference is that the Position property of MediaElement is both gettable and settable, while the PlayPosition property of MediaPlayer is only gettable. Consequently, the two ApplicationBar buttons that go to the beginning and end of the track aren’t applicable. Also, there’s apparently no way to get the duration of a Song object created in this way, so the Slider is irrelevant as well. I’ve also removed all the tombstoning logic from this program because it’s not possible to start up a track where you left off.

Because MediaPlayer plays a Song object obtained from the phone’s music library in the background, you might expect it also to play any Song object in the background. It does not. In this respect, MediaPlayer is just like MediaElement. The music stops as soon as you navigate away from the application. However, if you navigate away from the MediaPlayerDemo program and it’s not tombstoned—which often happens with Windows Phone OS 7.1—the music is only suspended. When you navigate back to the application, it picks up where it left off.

I think when you chalk up the pluses and minuses, the Silverlight MediaElement is a little ahead of the XNA MediaPlayer, but the auto­matic resumption of playback is a very nice MediaPlayer feature.

Streaming and Beyond

I’ve been discussing playing common audio and music files in WMA and MP3 formats. Windows Phone also allows a program to generate sound dynamically within the application. In the context of Windows Phone programming, this is known as “streaming.” I demonstrated one approach in the SpeakMemo program in my February 2011 UI Frontiers column (msdn.microsoft.com/magazine/gg598930) using the XNA DyanamicSoundEffectInstance class. You can also use the MediaStreamSource class in Silverlight to do something similar. This is how to implement electronic music synthesis on the phone. Again, however, these are only usable by foreground applications.

Beginning in Windows Phone OS 7.1, the concept of a “background agent” has been introduced, and you can use this for playing either music files or streaming audio while your program is suspended in the background.

In the next issue, I’ll discuss how this is done.


Charles Petzold is a longtime contributing editor to MSDN Magazine. His Web site is charlespetzold.com.

Thanks to the following technical expert for reviewing this article: Mark Hopkins


About the Author