| Comments

In talking with a friend about some Windows Phone 7 and Silverlight stuff recently.  He was giving me some great feedback about a few things (all of which I’ve passed along).  One of the things was what I felt was a common task that might exist in the mobile space but admittedly isn’t as clear if you are just coming to WP7 development.  The scenario is that of downloading media files and storing them for later playback.

WP7 does not have a storage mechanism like SQLLite on the device, but since it is Silverlight, you do have Isolated Storage you can use leveraging the same .NET class libraries from the full framework.  Here’s a sample app that demonstrates downloading an MP3 and storing for later playback on Windows Phone 7.

The Scenario

First, to make the scenario clear, say you are building a specific app for your brand’s media archives (audio and/or video).  You want to enable the user to selectively (or automatically) download the media to their device.  You want the user to be able to playback the downloaded media while offline later.  The media in this scenario is an MP3 file, a common audio format.

The Pieces

In order to do this we’ll assume the following:

  • You have an absolute URI to the MP3 file.
  • You have some type of UI to display to the user a list of media for your application
  • Some type of UI to control the playback (play/stop/etc.)

For demonstration purposes my UI will not be very ‘user friendly’ as it is meant to be diagnostic in explaining the task.  I will have a ListBox that I’ll use to bind to the list of downloaded items, a TextBox to give an option to download an MP3 file, and three (3) buttons: Download (to fetch the file and store for later), Play and Stop.  Here is the XAML I’ve used starting with the blank Windows Phone Application using the Windows Phone Developer Tools available to developers.

   1: <!--LayoutRoot is the root grid where all page content is placed-->
   2: <Grid x:Name="LayoutRoot" Background="Transparent">
   3:     <Grid.RowDefinitions>
   4:         <RowDefinition Height="Auto"/>
   5:         <RowDefinition Height="*"/>
   6:     </Grid.RowDefinitions>
   7:  
   8:     <!--TitlePanel contains the name of the application and page title-->
   9:     <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,9,0,40">
  10:         <TextBlock x:Name="ApplicationTitle" Text="AUDIO SAMPLES" Style="{StaticResource PhoneTextNormalStyle}"/>
  11:         <TextBlock x:Name="PageTitle" Text="MP3" Margin="9,-8,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
  12:     </StackPanel>
  13:  
  14:     <!--ContentPanel - place additional content here-->
  15:     <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  16:         <TextBlock Height="67" HorizontalAlignment="Left" Margin="6,6,0,0" Name="textBlock1" Text="Showing how to store and playback MP3 files on the device." VerticalAlignment="Top" TextWrapping="Wrap" />
  17:         <ListBox Height="150" HorizontalAlignment="Left" Margin="6,79,0,0" Name="listBox1" VerticalAlignment="Top" Width="444" />
  18:         <ProgressBar x:Name="DownloadProgress" IsIndeterminate="False" Style="{StaticResource PerformanceProgressBar}" Margin="6,-60,0,0"/>
  19:         <TextBlock Height="30" HorizontalAlignment="Left" Margin="12,298,0,0" Name="textBlock2" Text="URL to download" VerticalAlignment="Top" Width="175" />
  20:         <TextBox Height="72" HorizontalAlignment="Left" Margin="0,334,0,0" Name="mpsUri" VerticalAlignment="Top" Width="460" Text="http://files.sparklingclient.com/099_2010.07.09_WP7_Phones_In_The_Wild.mp3" />
  21:         <Button Content="DOWNLOAD" Height="72" HorizontalAlignment="Left" Margin="12,399,0,0" Name="button1" VerticalAlignment="Top" Width="438" Click="button1_Click" />
  22:         <Button Content="PLAY" Height="72" HorizontalAlignment="Left" Margin="12,477,0,0" Name="button2" VerticalAlignment="Top" Width="220" Click="button2_Click" />
  23:         <MediaElement Height="43" HorizontalAlignment="Left" Margin="6,555,0,0" Name="mediaPlayback" VerticalAlignment="Top" Width="438" />
  24:         <Button Content="STOP" Height="72" HorizontalAlignment="Left" Margin="230,477,0,0" Name="button3" VerticalAlignment="Top" Width="220" Click="button3_Click" />
  25:     </Grid>
  26: </Grid>

You can see that I’m not using any advanced pattern development here for two reasons: 1) it doesn’t need it and 2) I want to focus on specific tasks to isolate this learning.  Therefore if you don’t like Click handlers in your Button XAML, then you can flog me later :-).

Downloading the media

The first step in my sample is to download some media.  I’ve chosen a favorite podcast, the Sparkling Client as my sample file.  I hope they don’t mind.  In fact you may want to pick a small MP3 file to test on one of your servers so you don’t have to wait a while for the download to complete.

SIDE NOTE: I like how Sparkling Client has become more of short review of the times with regard to Silverlight.  Good to see some perspectives (and rumors).  It’s like a live version of Dave Campbell’s Silverlight Cream.

Because typing long things in the emulator is, well, annoying, I’ve prepopulated the URI TextBox (mpsUrl) with a specific URL.  Feel free to change this or enter a new MP3 URI in the emulator.  The next step is to execute the download.  I’m choosing to use WebClient.OpenReadAsync in this regard.  It’s simple and gets the job done.  Admittedly I wish that there was an OpenReadDownloadProgress argument available (as there is for DownloadStringAsync) but there is not. 

Because there is no actual progress value provided for me, I do want to provide the user some feedback that something is happening.  To do this in my sample I am using Jeff Wilcox’s “High Performance ProgressBar” for Windows Phone 7.  I’m not going to go into the details of why and how to hook it up – read his post.  I followed the exact same instructions. 

NOTE: When using the progress bar, don’t (only) set Visibility to collapsed if that is one of your mechanisms for displaying/hiding it, but be sure to set IsIndeterminate to false when not using it.

When I start the download, I start the ProgressBar to show the user some type of feedback that something is happening.  Here’s the code for the function:

   1: private void button1_Click(object sender, RoutedEventArgs e)
   2: {
   3:     string fileName = System.IO.Path.GetFileName(mpsUri.Text);
   4:  
   5:     // start the download of the MP3
   6:     WebClient wc = new WebClient();
   7:  
   8:     wc.OpenReadCompleted += ((s, args) =>
   9:         {
  10:             DownloadProgress.IsIndeterminate = false;
  11:  
  12:             // once get the streams, put in isolated storage
  13:  
  14:         });
  15:  
  16:     wc.OpenReadAsync(new System.Uri(mpsUri.Text, System.UriKind.RelativeOrAbsolute));
  17:     DownloadProgress.IsIndeterminate = true;
  18: }

And here’s a quick screenshot of what it looks like running:

Windows Phone 7 progress bar

Simple enough…let’s store the downloaded bits now.

Storing the media to IsolatedStorage

The result of OpenReadAsync is a Stream.  Using IsolatedStorage and specifically IsolatedStorageFileStream, I can write out those bits to a file that is stored in my device’s storage location.  Normally in the browser Silverlight world I would have to calculate the amount of storage needed, see if it is available and, if not, request a quota increase to the user.  I don’t have to do that in the phone world.  I can just begin to write out the data.  Ideally, however, I should check for available space since it is entirely possible the user has used all their storage.  This sample does not accommodate that logic.

In my OpenReadCompleted event I add the following logic:

   1: // snipped
   2: wc.OpenReadCompleted += ((s, args) =>
   3: {
   4:     DownloadProgress.IsIndeterminate = false;
   5:  
   6:     // once get the streams, put in isolated storage
   7:     using (var store = IsolatedStorageFile.GetUserStoreForApplication())
   8:     {
   9:         if (store.FileExists(fileName))
  10:         {
  11:             store.DeleteFile(fileName);
  12:         }
  13:  
  14:         using (var fs = new IsolatedStorageFileStream(fileName, FileMode.Create, store))
  15:         {
  16:             byte[] bytesInStream = new byte[args.Result.Length];
  17:             args.Result.Read(bytesInStream, 0, (int)bytesInStream.Length);
  18:             fs.Write(bytesInStream, 0, bytesInStream.Length);
  19:             fs.Flush();
  20:         }
  21:     }
  22:  
  23:     RefreshIsoFiles();
  24:  
  25: });
  26: // snipped

You can see that I’m writing out the file stream to a file using the same name as the MP3 file literally (which may not make sense to the user, so again this is one of those ‘polish’ areas you’d want to make better and perhaps organize the files in IsolatedStorage better).

The last step you see is a call to RefreshIsoFiles.  This is a function that I also call when the first user interface page is loaded.  It traverses the IsolatedStorage for the app to display the already stored media in the ListBox in our XAML:

   1: private void RefreshIsoFiles()
   2: {
   3:     string[] fileList;
   4:  
   5:     using (var store = IsolatedStorageFile.GetUserStoreForApplication())
   6:     {
   7:         fileList = store.GetFileNames();
   8:     }
   9:     listBox1.ItemsSource = fileList;
  10: }

Now we have our data downloaded and ready for playback.

Playing back the stored media

So great, now you have a stored MP3 file and you want to play it back.  Remember the XAML above and that I have a MediaElement there.  MediaElement is so simple at playing back media files from a URI.  Simply set the source of MediaElement and you can then call Play() and other functions. 

UPDATE: Well, you learn something new always.  Corrado pointed out below in comments that in WP7 you can SetSource directly to an IsolatedStorageFileStream...so while the following is interesting, it doesn't appear to be required in WP7 :-).

The challenge is that we now have our media in IsolatedStorage and there isn’t a URI scheme for IsolatedStorage that is predictable to the developer.  What we are left with is opening the media as a Stream and feeding that to the MediaElement.  This introduces MediaStreamSource.  If you aren’t familiar with this API, you’re probably not alone.  This is the API that enables a few scenarios, namely Smooth Streaming playback for Silverlight.  It is an extensible API so that you could wrap your own decode logic, etc. as needed.  Now given that MP3 is a common format you’d think it would be simple to do this…but there isn’t one built-in method for these various different codecs.

When MediaStreamSource was introduced, the program manager on that feature had written some helper files as code samples for developers to use.  One of them was an MP3 MediaStreamSource helper.  I wrote about them and where you can get them: MediaStreamSource for Silverlight.  Here’s where some awesome code re-use comes in to play.

I downloaded the ManagedMediaHelpers project and built the Mp3MediaStreamSource project (which also builds MediaHelpers).  In my WP7 project I simply added a reference to these in my project.  I was able to use these Silverlight binaries directly in my WP7 project! (I’ve included the compiled binaries in this sample for convenience but you can also see the source link in the article above.)

Now I need to read the media from IsolatedStorage as a stream, feed that Stream into my MediaStreamSource, and set that as the source for my MediaElement.  Here’s the relevant code on the Play button on my sample:

   1: private void button2_Click(object sender, RoutedEventArgs e)
   2: {
   3:     if (listBox1.SelectedItem == null)
   4:     {
   5:         MessageBox.Show("choose an item to play back!");
   6:     }
   7:     else
   8:     {
   9:         using (var store = IsolatedStorageFile.GetUserStoreForApplication())
  10:         {
  11:             audio = store.OpenFile(listBox1.SelectedItem.ToString(), FileMode.Open);
  12:             // play it back as a MSS
  13:             mss = new Media.Mp3MediaStreamSource(audio);
  14:             mediaPlayback.SetSource(mss);
  15:         }
  16:     }
  17: }

The media now plays back on the phone.  My Stop button basically closes the stream (audio and mss are member variables of the project) and nulls out references.

Summary

This is I think what might be a common application scenario for WP7 (downloading media for playback later).  Hopefully over time our platform will improve to make some of this better (i.e., progress indication), but for now this should help those get started on this task.  The meat of the solution is in the MediaStreamSource implementation.  If you are working with MP3 format, then the sample code will help you greatly as it’s mostly done!  There are other implementations floating around for WAV and other things as well if you need them.

Hopefully this might nudge Chris along the right path and be a helpful tip to others as well.  If you have any feedback on the implementation or a better way of doing this, please share!  Here’s the solution bits to my sample in full: Mp3StoreandPlayback.zip.

Hope this helps!


This work is licensed under a Creative Commons Attribution By license.

| Comments

Suppose you use Firefox as your default web browser and you are a Silverlight developer using Visual Studio.  You may have been frustrated at times in being able to get the debugger to attach to your breakpoints.  You’ve triple-checked that you are in debug mode, that the Silverlight checkbox is marked in the hosting web application’s property pages and it still is not breaking for you.  You stare at the dreaded empty red circle in Visual Studio reading the tooltip of “No debug symbols have been loaded…” a thousand times.

But it works in Internet Explorer.

I’ve faced this a few times and always forget the tip.  I’m recording it for my own posterity, but hopefully it will help others as well.

Here’s how to ensure the VS debugger attaches to the Silverlight app for debugging:

  • In Firefox address bar type about:config
  • Read the warning, choose your preference to always remind you or not and accept
  • In the search bar of the config options now type: npctrl
  • You should then see the entry: dom.ipc.plugins.enabled.npctrl.dll
  • Change the value from true to false (simply double-clicking will change this for you)
  • Restart Firefox

And your debugging should come back to normal. It has frustrated me more than once. Hopefully it helps some others.

| Comments

One of the biggest discussions I started getting into when Windows Phone development was announced to the world was sparked with this single question I posed to our internal Windows Phone developer teams:

What is the use case for when you would want to use a Pano versus Pivot application layout?

I asked this in the context of an application for Yelp that I was writing.  Information was similar but not identical.  It was only similar in the sense that the data was all about user reviews and venues.  In my eyes I saw this like the fact that games are similar in that they are games, but their individual information (the play) is different.

I guess I was expecting a definitive or clear view on when to use either given my use case I presented.  I laughed as I collected numerous various viewpoints from all areas of the dev platform folks.  It was clear there was no clarity for me.  I actually have saved the most concise definition the discussion generated and had been wanting to blog about it for a while.

Today, Jaime released a bunch of videos from a ‘design days’ event that was held in Redmond for Windows Phone developers.  They are all great, but there was one that caught my eye: Windows Phone Design Days – Pivot and Pano.  In this video a few UX researchers walk through some of the key tenets of each control.  Here’s some of my mental notes:

Pivot

  • Application view manager
  • Filter same data on different views (the “inbox” is a great example of this)
  • Optimized for current screen size
  • Filter of data doesn’t have to be same view (agenda/day)
  • Related content is ok to pivot on as long as related content is truly related
  • Focused

Panorama

  • Horizontal broad canvas, not confined to current screen size
  • A ‘top layer’ view into underlying experiences/tasks
  • Exploratory in nature
  • Don’t use if you need an application bar
  • Don’t have a pano that takes the user to a pivot control constantly
  • Leverage things inherently interesting (use ‘about me’ type information)
  • Never place a pano *in* a pivot

There was some good information in this video (like don’t use pivot/pano for wizard-based UI) and one of the better descriptions/examples of answering my root question.  The other videos are great and I encourage you to take a look at them.

I’d encourage you to take a look at all of these videos to get a good sense on some of the user experience research done for Windows Phone developers and how you can learn to target a great experience in your app/game.

| Comments

I recently got a note about a nagging issue in using StringFormat in XAML binding expressions and how it doesn’t honor the current user’s culture settings.  This is true that there is an issue in that it doesn’t in WPF or Silverlight.  If you don’t know what I’m talking about, Silverlight introduced the ability to use StringFormat in data binding expressions (WPF has had this since 3.5 SP1) so you could do some formatting in-line in your binding.  Like this:

   1: <TextBlock Text="{Binding Path=CurrentDate, StringFormat=Current Timestamp is: \{0:G\}}" />

This would result in text that would be formatted directly using your string Formatter without the need for code-behind or any generic ValueConverter.  This is a very helpful feature for formatting UI values as well as in some cases replacing ValueConverters for simple tasks.

The problem is that StringFormat isn’t honoring the user’s culture settings.  Take for example this complete XAML:

   1: <StackPanel x:Name="FooContainer">
   2:  
   3:     <TextBlock x:Name="CultureInfo" />
   4:     <TextBlock x:Name="UICultureInfo" />
   5:  
   6:     <TextBlock Text="{Binding Path=CurrentDate, StringFormat=Current Timestamp is: \{0:G\}}" />
   7:  
   8:     <TextBlock x:Name="CostField" Text="{Binding Path=Cost, StringFormat=Cost is: \{0:c\}}" />
   9:  
  10:     <toolkit:GlobalCalendar  />
  11:  
  12: </StackPanel>

This is being bound to a simple object that exposes two properties for the purposes of demonstration: CurrentDate (DateTime) and Cost (double).  Using my standard US-English settings and regional preferences the output would be:

StringFormat with default culture

Now, let me tell my Silverlight app that I have a different culture information.  I can do this without having to force a language pack installation of sorts and completely change my machine.  Adding the culture/uiculture params to the <object> tag does the trick.  I’ll change it to “de-de” for German.  Here is the new output:

StringFormat with explicit culture

What?!  Even thought the settings recognize a different culture, StringFormat is not doing what I expect.  I would have expected a different date display for German settings (d.m.yyyy) and a different currency display instead of dollars.

Unfortunately this is an issue in StringFormat right now, but there is a simple workaround that if you are creating a localized app you can add to your code that shouldn’t affect your default language settings either.  In my constructor I add this line of code:

   1: this.Language = XmlLanguage.GetLanguage(Thread.CurrentThread.CurrentCulture.Name);

This tells the markup system to use the current culture settings as the UI language.  XmlLanguage is a part of the System.Windows.Markup namespace, so ensure you call that out explicitly or add a using statement.  Now refreshing my German settings sample I get:

StringFormat with explicit culture

as expected.  Changing (or removing the explicit setting of culture in my <object> tag) back to my default culture settings results in my US-English preferences being used and no need for me to change the XAML.

Hope this helps!

| Comments

August 1st is here (well okay, so I'm a little late)…time for an updated Windows 7 Smashing Magazine theme pack!

Smashing Magazine August 2010 Windows 7 Theme

The August themes seem to be continuing the focus on 'summer' things.  So here is your August 2010 Windows 7 Theme Packs for wallpapers – unfiltered and uncensored – about 35 wallpapers in all.

For details on these and to see past ones, visit the Smashing Magazine Windows 7 Theme information for the specifications I used for the theme pack as well as previous themes.  Want to participate and submit yours?  Join in!


This work is licensed under a Creative Commons Attribution By license.