| Comments

In the release of Visual Studio 2010 Feature Pack 2 one feature that might be interesting to Silverlight developers is the introduction of support for the coded UI test framework and Silverlight 4 applications, specifically:

  • Use Microsoft Test Manager to capture and playback action recordings for Silverlight 4 applications
  • Create coded UI tests for Silverlight 4 applications with Visual Studio 2010 Premium or Ultimate

There are more features in the feature pack (see link above for more details) but also some videos to help you understand these features.  Here’s a video of the one of the examples:

Get Microsoft Silverlight

Other videos are:

Pretty cool feature that folks have been asking for. If you are an MSDN subscriber get the feature pack!

| Comments

I was spelunking around playing with Silverlight in Windows Phone 7 and specifically the CameraCaptureTask.  The “tasks” are APIs that allow you to interact with phone-specific functionality like the camera, picture picker, phone dialer, etc.  A whole list of the available tasks in the Microsoft.Phone.Tasks namespace can be found in the developer documentation.

I was basically creating a simple application that would allow you to choose (PhotoChooserTask) or take a picture (CameraCaptureTask) and then display the picture (and later post it online or something).  Here was my basic XAML structure:

   1: <Grid x:Name="LayoutRoot" Background="Transparent">
   2:     <Grid.RowDefinitions>
   3:         <RowDefinition Height="Auto"/>
   4:         <RowDefinition Height="*"/>
   5:     </Grid.RowDefinitions>
   7:     <!--TitlePanel contains the name of the application and page title-->
   8:     <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
   9:         <TextBlock x:Name="ApplicationTitle" Text="PICTURE POSTER" Style="{StaticResource PhoneTextNormalStyle}"/>
  10:         <TextBlock x:Name="PageTitle" Text="take and post" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
  11:     </StackPanel>
  13:     <!--ContentPanel - place additional content here-->
  14:     <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  15:         <Image Margin="8,8,8,159" x:Name="ChosenPicture" />
  16:         <Button Click="OnPostClicked" x:Name="PostPic" Content="POST" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,8,47" Width="199"/>
  17:         <TextBlock x:Name="PostedUri" TextWrapping="Wrap" VerticalAlignment="Bottom" Margin="8,0,8,20"/></Grid>
  18: </Grid>
  20: <!--Sample code showing usage of ApplicationBar-->
  21: <phone:PhoneApplicationPage.ApplicationBar>
  22:     <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
  23:         <shell:ApplicationBar.MenuItems>
  24:             <shell:ApplicationBarMenuItem Text="take picture" Click="OnMenuTakeClicked"/>
  25:             <shell:ApplicationBarMenuItem Text="choose picture" Click="OnMenuChooseClicked"/>
  26:             <shell:ApplicationBarMenuItem Text="save picture" Click="OnSavePictureClicked" />
  27:         </shell:ApplicationBar.MenuItems>
  28:     </shell:ApplicationBar>
  29: </phone:PhoneApplicationPage.ApplicationBar>

And the initial code to trigger the task from a MenuItem:

   1: private void OnMenuTakeClicked(object sender, EventArgs e)
   2: {
   3:     CameraCaptureTask cam = new CameraCaptureTask();
   4:     cam.Completed += new EventHandler<PhotoResult>(OnCameraCaptureCompleted);
   5:     cam.Show();
   6: }
   8: private void OnMenuChooseClicked(object sender, EventArgs e)
   9: {
  10:     PhotoChooserTask pix = new PhotoChooserTask();
  11:     pix.Completed += new EventHandler<PhotoResult>(OnCameraCaptureCompleted);
  12:     pix.ShowCamera = true;
  13:     pix.Show();
  14: }

As you can see it is pretty simple and brings up the OS-standard camera and/or photo chooser. 

NOTE: if you use the PhotoChooserTask you can also initiate taking a new picture from that task as well.

After the picture is chosen (from a new pic or from a picker) I put the item in the Image control in my XAML:

   1: void OnCameraCaptureCompleted(object sender, PhotoResult e)
   2: {
   3:     capturedImage = e.ChosenPhoto; // this is a member variable to store the last chosen pic
   5:     BitmapImage bmp = new BitmapImage();
   6:     bmp.SetSource(e.ChosenPhoto);
   8:     ChosenPicture.Source = bmp;
   9: }

During this, however, I found that no matter how I held the phone when I took the picture (portrait or landscape), the API always assumed landscape.  I tried looking at some of the device orientation data, but it wasn’t providing the right information at the time I needed it.  It turns out after some internal discussions that others were seeing this as well.  On the device I have (Samsung) the picture snapshot button is in a natural place if you were to hold it landscape.  However, in my current experience with my mobile devices (Android and iPhone) I actually take more pictures in portrait mode.

After some discussion with folks internally, one of our test leads for WP7 reminded everyone that the phone does provide the EXIF information for each picture taken.  One of the attributes of EXIF is orientation (or rotation).  Now all we needed was a method to read the EXIF data in .NET…enter ExifLib.  This is a cool Code Project article and source code download that does a great job providing a simple EXIF reading experience.

The ExifLib as it stood wouldn’t work with the Stream that is provided as a result of the CameraCaptureTask, so a slight modification (or in my case I just created an override) to the function was needed for the library.  Here’s the additional override I added:

   1: public static JpegInfo ReadJpeg(Stream FileStream, string FileName)
   2: {
   3:     DateTime now = DateTime.Now;
   4:     ExifReader reader = new ExifReader(FileStream);
   5:     reader.info.FileSize = (int)FileStream.Length;
   6:     reader.info.FileName = string.Format("{0}.jpg", FileName);
   7:     reader.info.LoadTime = (TimeSpan)(DateTime.Now - now);
   8:     return reader.info;
   9: }

Now with that in place, I could accomplish taking my picture and read the EXIF data and apply the appropriate transform based on the orientation data.  The first thing I had to do was to create a RotateTransform on my Image element as well as set the RenderTransformOrigin on the Image element:

   1: <Image Margin="8,8,8,159" x:Name="ChosenPicture" RenderTransformOrigin="0.5,0.5">
   2:     <Image.RenderTransform>
   3:         <RotateTransform x:Name="ImageRotate" />
   4:     </Image.RenderTransform>
   5: </Image>

Now in code in my completed handler for the task I modified it to look at the EXIF orientation data and apply the correct rotation to show the image:

   1: void OnCameraCaptureCompleted(object sender, PhotoResult e)
   2: {
   3:     capturedImage = e.ChosenPhoto;
   5:     BitmapImage bmp = new BitmapImage();
   6:     bmp.SetSource(e.ChosenPhoto);
   8:     ChosenPicture.Source = bmp;
  10:     // figure out the orientation from EXIF data
  11:     e.ChosenPhoto.Position = 0;
  12:     JpegInfo info = ExifReader.ReadJpeg(e.ChosenPhoto, e.OriginalFileName);
  13:     PostedUri.Text = info.Orientation.ToString();
  15:     switch (info.Orientation)
  16:     {
  17:         case ExifOrientation.TopLeft:
  18:         case ExifOrientation.Undefined:
  19:             ImageRotate.Angle = 0d;
  20:             break;
  21:         case ExifOrientation.TopRight:
  22:             ImageRotate.Angle = 90d;
  23:             break;
  24:         case ExifOrientation.BottomRight:
  25:             ImageRotate.Angle = 180d;
  26:             break;
  27:         case ExifOrientation.BottomLeft:
  28:             ImageRotate.Angle = 270d;
  29:             break;
  30:     }
  31: }

Now I’ve got my flexibility in my application and don’t have to worry about the orientation. 

Now of course this only helps for the display of the information.  If you were to use the libraries to save the image you’d still have the issue of an incorrect orientation on the picture.  Again, iterating with our test team internally (thanks Stefan!!!) here’s a modified view of the world.

First, instead of rotating the Image element, let’s just rotate the actual Pixels themselves:

   1: void OnCameraCaptureCompleted(object sender, PhotoResult e)
   2: {
   3:     // figure out the orientation from EXIF data
   4:     e.ChosenPhoto.Position = 0;
   5:     JpegInfo info = ExifReader.ReadJpeg(e.ChosenPhoto, e.OriginalFileName);
   7:     _width = info.Width;
   8:     _height = info.Height;
   9:     _orientation = info.Orientation;
  11:     PostedUri.Text = info.Orientation.ToString();
  13:     switch (info.Orientation)
  14:     {
  15:         case ExifOrientation.TopLeft:
  16:         case ExifOrientation.Undefined:
  17:             _angle = 0;
  18:             break;
  19:         case ExifOrientation.TopRight:
  20:             _angle = 90;
  21:             break;
  22:         case ExifOrientation.BottomRight:
  23:             _angle = 180;
  24:             break;
  25:         case ExifOrientation.BottomLeft:
  26:             _angle = 270;
  27:             break;
  28:     }
  30:     if (_angle > 0d)
  31:     {
  32:         capturedImage = RotateStream(e.ChosenPhoto, _angle);
  33:     }
  34:     else
  35:     {
  36:         capturedImage = e.ChosenPhoto;
  37:     }
  39:     BitmapImage bmp = new BitmapImage();
  40:     bmp.SetSource(capturedImage);
  42:     ChosenPicture.Source = bmp;           
  43: }
  45: private Stream RotateStream(Stream stream, int angle)
  46: {
  47:     stream.Position = 0;
  48:     if (angle % 90 != 0 || angle < 0) throw new ArgumentException();
  49:     if (angle % 360 == 0) return stream;
  51:     BitmapImage bitmap = new BitmapImage();
  52:     bitmap.SetSource(stream);
  53:     WriteableBitmap wbSource = new WriteableBitmap(bitmap);
  55:     WriteableBitmap wbTarget = null;
  56:     if (angle % 180 == 0)
  57:     {
  58:         wbTarget = new WriteableBitmap(wbSource.PixelWidth, wbSource.PixelHeight);
  59:     }
  60:     else
  61:     {
  62:         wbTarget = new WriteableBitmap(wbSource.PixelHeight, wbSource.PixelWidth);
  63:     }
  65:     for (int x = 0; x < wbSource.PixelWidth; x++)
  66:     {
  67:         for (int y = 0; y < wbSource.PixelHeight; y++)
  68:         {
  69:             switch (angle % 360)
  70:             {
  71:                 case 90:
  72:                     wbTarget.Pixels[(wbSource.PixelHeight - y - 1) + x * wbTarget.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
  73:                     break;
  74:                 case 180:
  75:                     wbTarget.Pixels[(wbSource.PixelWidth - x - 1) + (wbSource.PixelHeight - y - 1) * wbSource.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
  76:                     break;
  77:                 case 270:
  78:                     wbTarget.Pixels[y + (wbSource.PixelWidth - x - 1) * wbTarget.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
  79:                     break;
  80:             }
  81:         }
  82:     }
  83:     MemoryStream targetStream = new MemoryStream();
  84:     wbTarget.SaveJpeg(targetStream, wbTarget.PixelWidth, wbTarget.PixelHeight, 0, 100);
  85:     return targetStream;
  86: }

Notice here that OnCameraCaptureCompleted is different in that we first check the orientation and if needed rotate the pixels using the newly introduced RotateStream method.  The resulting stream is what we actually set on our Image element and no need for RotateTransform at this point.  I can then even have a menu item save the picture to the media library on the device:

   1: private void OnSavePictureClicked(object sender, EventArgs e)
   2: {
   3:     if (capturedImage != null)
   4:     {
   5:         capturedImage.Seek(0, 0); // necessary to initiate the stream correctly before save
   7:         MediaLibrary ml = new MediaLibrary();
   8:         try
   9:         {
  10:             Picture p = ml.SavePicture(Guid.NewGuid().ToString(), capturedImage);
  11:             PostedUri.Text += ":" + p.Name;
  12:         }
  13:         catch (Exception ex)
  14:         {
  15:             PostedUri.Text = ex.Message;
  16:         }
  17:     }
  18: }

The MediaLibrary is a part of Microsoft.Xna.Framework.Media and provides the easy functionality of saving to the device.  And upon sync (or sharing) my image is what I expected when I took the picture using the CameraCaptureTask.

Here is my final project sample (lots of debug code in there, but you should get the point): WindowsPhoneApplication63.zip (requires the Windows Phone Developer Tools)

Hope this helps!

| 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>
   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>
  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);
   5:     // start the download of the MP3
   6:     WebClient wc = new WebClient();
   8:     wc.OpenReadCompleted += ((s, args) =>
   9:         {
  10:             DownloadProgress.IsIndeterminate = false;
  12:             // once get the streams, put in isolated storage
  14:         });
  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;
   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:         }
  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:     }
  23:     RefreshIsoFiles();
  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;
   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.


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

Yesterday (7-Jul-2010), the Silverlight Media Framework v2 was released on Codeplex (yeah, it’s Open Source).  If you aren’t familiar with it, it is a Silverlight framework encapsulating the best practices for media playback for Silverlight applications.  It is both a framework and, in v2, they also provided compiled simple player XAPs that you can just drop in HTML.  If you are building apps, you’ll want to take a look at the framework in more detail, but if you want a solid media player experience grab the players too.

Since I use Windows Live Writer as my blogging tool, I wanted to share my workflow for embedding videos into blog posts (or other content that I can author using Live Writer).  First, grab the necessary tools:

Install the Dynamic Template add-in for Writer.  Next step is to put your ProgressiveDownloadPlayer.xap somewhere.  This doesn’t matter, as along as you know the URL to it.  I keep mine on my cloud storage share since I can re-use it in various places.  Once all those pieces are in place here is what I do.

Step 1 – Create the Dynamic Template

In order to create a template, you have to execute the Insert function for Dynamic Template to bring up the dialog.  Once there, choose to Edit Templates and give it a name.

Dynamic Template insert dialog

Then in the template code, copy this HTML:

   1: <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="640" height="480">
   2:   <param name="source" value="URL_TO_YOUR_PLAYER_XAP" />
   3:   <param name="background" value="white" />
   4:   <param name="enableHtmlAccess" value="true" />
   5:   <param name="minRuntimeVersion" value="4.0.50424.0" />
   6:   <param name="initParams" value="MediaUrl=<%= VideoUrl %>,AutoPlay=false,ThumbnailUrl=<%= ThumbnailUrl %>" />
   7:   <param name="autoUpgrade" value="true" />
   8:   <a href="http://go.microsoft.com/fwlink/?LinkID=149156&amp;v=4.0.50424.0" style="text-decoration:none">
   9:       <img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft Silverlight" style="border-style:none" />
  10:   </a>
  11: </object>

And in the settings use these in this screenshot:

Code variables

Of course, replace with the URL to your player location.  That’s it, that is your template.

Step 2 – Prepare your media

I’m assuming that you already have media to display, but if you don’t, use your favorite media encoding tool to create the media and have the URL.  Note that it doesn’t have to be an absolute URL, but just understand the paths of your web app if you plan on using a relative URL.  Personally I recommend using an absolute one always.

Also the snippet above, while not required, implements the ThumbnailUrl (thanks Kevin) parameter for the player.  You’ll need a URL to a thumbnail image for preview.

Step 3 – Execute the template

Now that you have the template, you can use it.  Rather than talk about it, here’s an embedded video (using the process) demonstrating the process :-)…

Get Microsoft Silverlight

Simple, huh? Now of course you cannot see the final result in Writer (not sure why actually but I think it has something to do with the rendering techniques they use in authoring mode (hence the white box in the vid), but you can see the end result!


Silverlight Media Framework is awesome.  Live Writer is awesome.  Awesome+Aweomse = Awesomely Awesome.  Kidding aside, if you have similar workflows to me, this should help you.  It has streamlined the content publishing process when I need it in a simple, but efficient way.

Hope this helps!

| Comments

I’m working on a little sample application for music management in Silverlight using WCF RIA Services and some other new Silverlight 4 features.  One thing that I wanted to add to the application was the ability to drag an audio file and either lookup the data and/or add a new album/artist/song to the library automatically.

Audio formats have a ‘tag’ format known as ID3.  It’s a format used for audio file metadata that is used in Windows Media Player, iTunes, and various hardware devices as well.  Over the years there has been an evolution of this format, with the older ID3v1 format basically taking up a header space with fixed character spaces for various things like Album, Artist, Title, Year, Track.  Over time though the ID3v2 format has been adapted more as it is more flexible for things like album art, and longer titles, etc.  There are various implementations of ID3 libraries for .NET that developers can choose from.  All of these implementations don’t take into account Silverlight unfortunately.

Silverlight can only reference Silverlight-compatible libraries.  Most of these libraries were targeted for the full .NET Framework and thus I can’t binary reference them.  Luckily most of them (except one) are Open Source so I could tinker.  I took the step of simply copying the files to a Silverlight project and recompiling.  This did not work 100% in a single task.  Most of the libraries had some form of Serialization attributes/constructors and almost all used some form of ASCII encoding for various string manipulation of byte arrays.

I settled on TagLib# as the library that was the easiest to modify for me.  I had to make the same changes I mentioned above to this library as well.  I created a new Silverlight 4 class library and compiled it as such.  One thing that TagLib# didn’t have was a stream input implementation.  Most of the libraries, in fact, assumed a local file path.  Luckily the library was written using a generic ‘File’ interface, so I just had to create my own StreamFileAbstraction.  I chose to do this within my project rather than the base library.  It was easy since the LocalFileAbstraction actually perfomed an Open on the file as it’s first task and set some public variables.  My abstraction basically just hands the stream already and ready to go.

Now, using the Silverlight 4 drop target feature, I created just a simple test harness to test my theory.  My XAML basically is this (pretty rudimentary just to test my theory):

   1: <Grid x:Name="LayoutRoot" Background="White">
   2:         <StackPanel>
   3:             <Border x:Name="DropZone" Width="700" Height="300" Background="Silver" CornerRadius="8" AllowDrop="True" Drop="DropZone_Drop">
   4:                 <TextBlock TextWrapping="Wrap" Text="drop here" FontSize="64" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="Gray"/>
   5:             </Border>
   6:             <Grid Height="255" Width="700">
   7:                 <Grid.ColumnDefinitions>
   8:                     <ColumnDefinition Width="111"/>
   9:                     <ColumnDefinition Width="*"/>
  10:                 </Grid.ColumnDefinitions>
  11:                 <Grid.RowDefinitions>
  12:                     <RowDefinition Height="Auto"/>
  13:                     <RowDefinition Height="Auto"/>
  14:                     <RowDefinition Height="Auto"/>
  15:                     <RowDefinition Height="Auto" />
  16:                     <RowDefinition Height="50*" />
  17:                 </Grid.RowDefinitions>
  18:                 <dataInput:Label Content="Artist" HorizontalAlignment="Right" VerticalAlignment="Top" FontWeight="Bold" Margin="4" />
  19:                 <dataInput:Label Content="Album" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Top" FontWeight="Bold" Margin="4" />
  20:                 <dataInput:Label Content="Title" Grid.Row="2" HorizontalAlignment="Right" VerticalAlignment="Top" FontWeight="Bold" Margin="4" />
  21:                 <dataInput:Label Grid.Column="1" HorizontalAlignment="Left" Name="Artist" Margin="4" />
  22:                 <dataInput:Label Grid.Column="1" Grid.Row="1" HorizontalAlignment="Left" Name="Album" Margin="4" />
  23:                 <dataInput:Label Grid.Column="1" Grid.Row="2" HorizontalAlignment="Left" VerticalAlignment="Top" Name="Title" Margin="4" />
  24:                 <Image Grid.Column="1" Grid.Row="4" Height="118" HorizontalAlignment="Left" Margin="4,2,0,0" Name="AlbumArt" Stretch="Fill" VerticalAlignment="Top" Width="118" />
  25:             </Grid>
  26:         </StackPanel>
  27:     </Grid>

Notice on the Border the AllowDrop=”True” attribute.  This tells Silverlight that the element can be used as a drop target (for a file from the file system).  The rendered UI looks like this:

Sample MP3 test UI

You may also notice the Drop attribute on the Border element that maps to the event handler DropZone_Drop.  This event handler basically gives us an event argument that represents the dropped objects on the surface (yes you can drop more than one).  The initial stub of this function looks like this:

   1: private void DropZone_Drop(object sender, DragEventArgs e)
   2: {
   3:     IDataObject drop = e.Data as IDataObject;
   5:     object data = drop.GetData(DataFormats.FileDrop);
   7:     FileInfo[] files = data as FileInfo[];
   9:     FileInfo file = files[0];
  10: }

I’m being a little verbose in the code to show each of the steps.  As you can see you get a FileInfo array and can pull items out of that.  For my sample I’m just assuming one item was dropped.  In the next steps I just need to get the Stream from the file and use my library.  Here is the full function (with a quick check to make sure it is a supported audio file):

   1: private void DropZone_Drop(object sender, DragEventArgs e)
   2: {
   3:     IDataObject drop = e.Data as IDataObject;
   5:     object data = drop.GetData(DataFormats.FileDrop);
   7:     FileInfo[] files = data as FileInfo[];
   9:     FileInfo file = files[0];
  11:     if (file.Extension.ToLower() != ".mp3" && file.Extension.ToLower() != ".wma")
  12:     {
  13:         MessageBox.Show("Must be an MP3 file");
  14:     }
  15:     else
  16:     {
  17:         Stream fileStream = file.OpenRead();
  19:         TagLib.File.IFileAbstraction fileAbstraction = new StreamFileAbstraction(fileStream, file.Name);
  21:         TagLib.File tagFile = TagLib.File.Create(fileAbstraction);
  23:         if (tagFile.Tag.TagTypes.HasFlag(TagLib.TagTypes.Id3v2))
  24:         {
  25:             Artist.Content = tagFile.Tag.FirstAlbumArtist;
  26:             Album.Content = string.IsNullOrEmpty(tagFile.Tag.Album) ? "NO ALBUM NAME" : tagFile.Tag.Album;
  27:             Title.Content = tagFile.Tag.Title;
  28:             if (tagFile.Tag.Pictures.Length > 0)
  29:             {
  30:                 IPicture pic = tagFile.Tag.Pictures[0];
  31:                 MemoryStream img = new MemoryStream(pic.Data.Data);
  32:                 BitmapImage bmp = new BitmapImage();
  33:                 bmp.SetSource(img);
  35:                 AlbumArt.Source = bmp;
  36:             }
  37:         }
  38:         else
  39:         {
  40:             MessageBox.Show("no id3v2 tag");
  41:         }
  42:     }

Once all the pieces are together you drag an audio file on the drop surface and the items will populate.  Here’s a quick video showing how it all works together.

Get Microsoft Silverlight

So this is just a start – and I’ve got only the tag reading working…didn’t bother looking at the other parts of the library so I know it isn’t fully ported for Silverlight.

What do you think?  Found a better implementation of ID3 tag reading?