Some time ago, I wrote a popular article on how to create a scrolling Silverlight 1.x Playlist using Microsoft Expression Encoder output. Well, I finally found some time to revisit that application to see how I might upgrade it to Silverlight 2. As you are probably aware, Expression Encoder 2 Service Pack 1 is now out, and it ships with a handful of Player templates just for Silverlight 2. Among these new templates are two which already have built in scrolling playlists, and I thought I would test one out.
However, I already have all my videos encoded - including all the chapter point thumbnails, etc. So, I didn't want to start inside Encoder, have it build my project, and work from there like I did last time. This time, I created a new Silverlight 2.0 UserControl project in Visual Studio 2008, and worked for a week or two on the new look and feel for the site before I decided it was time to merge my project with the Expression Encoder template. I found Tim Heuer's blog entry on integrating these new templates very helpful, though I feel it is important to add that the template itself, meaning the look and feel of the player, is not stored in the DLLs. And, it does not matter which of the template projects you open and compile. Just referencing the DLLs and following Tim's instructions will always give you the standard Silverlight 2 player. In order to add the look and feel, you must copy (or merge) the contents of the Page.xaml file in that template into your own UserControl.
For my project, I wanted the player to be a separate UserControl, so I went to Project > New Item > Silverlight User Control, and called it MediPlayer.xaml. Next, I pasted everything from C:\Program Files\Microsoft Expression\Encoder 2\Templates\en\FrostedGallery\Source\Page.xaml into my new MediaPlayer.xaml file, then changed x:Class
at the very top to reflect my original namespace and class name. (If you forgot what it was, just undo your paste, make a note of it, and redo the paste.)
I also wanted my player control to be available on multiple pages, and I wanted it hidden most of the time, only to pop up in a pseudo modal 'Lightbox' format with a close button. To achieve this effect, I simply used these techniques described by Scot Guthrie in his excellent tutorial on creating a Silverlight Digg application. So now, I had my player, and I could show and hide it with the click of a button. Basically, my site showcases James Bond movies, and I have customized the Yet Another Carousel control so that you can click on the selected box art and read the synopsis, watch the trailer, and buy it on Amazon.com. My initial idea, therefore, was to create a new PlayListItem
programmatically, and add it to the player's PlayListCollection
in the onLeftMouseButtonUp
event of the button. What I found was that while this worked quite nicely, I ultimately ended up with a playlist slowly being generated by the user in the order in which the user clicked on the movies, making it harder to keep track of which item in the collection was which movie, but more importantly, I couldn't figure out how to take full advantage of all the chapter information and thumbnails I had created for the original project. I could create chapter item objects, and add them to my own PlayListCollection
object, but I could not bind that new object to my player since the Playlist
property is read only.
Reading more of Tim's blog, I saw that you could add some XML to the InitParams
of the control, but I have 24 videos, each with thumbnail paths for at least four chapter points, and I didn't need to start typing that all into a single line to understand what a nightmare that would become: not only would it be hard to read and maintain, but also it goes against the whole MVC separation of code, data, and presentation layers ethic we have all grown so attached to.
More Google searches led me to this solution which does allow you to move the creation of the parameters into the code-behind, but seems to require tinkering with the original template code, which I am not opposed to, but I'd already thought of a cleaner solution. What I wanted was a way to create an XML file containing my entire playlist in this format:
1: ="1.0"="utf-8"
2: <playList>
3: <playListItems>
4: <playListItem title="Dr No" description="Trailer"
mediaSource="ClientBin/01_dr_no.wmv"
adaptiveStreaming="False" thumbSource="ClientBin/01_dr_no_Thumb.jpg" >
5: <chapters>
6: <chapter position="29.176"
thumbnailSource="ClientBin/01_dr_no_MarkerThumb 00.00.29.1760677.jpg"
title="1" />
7: <chapter position="49.374"
thumbnailSource="ClientBin/01_dr_no_MarkerThumb 00.00.49.3748838.jpg"
title="2" />
8:
20: </chapters>
21: </playListItem>
22: </playListItems>
23: </playList>
and simply pass the file to my player. And, my solution turned out to be trivially easy: I created a new class that inherits from ExpressionMediaPlayer.MediaPlayer
, and added a new method that would accept my file:
using System;
using System.Diagnostics;
using System.Net;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Xml.Linq;
namespace Bond_Silverlight2
{
public class MI6MediaPlayer : ExpressionMediaPlayer.MediaPlayer
{
public MI6MediaPlayer(): base()
{
}
public void OnStartup(string xmlPlayList)
{
XDocument document = XDocument.Load(xmlPlayList);
try
{
Playlist.Clear();
Playlist.ParseXml(HtmlPage.Document.DocumentUri,
document.ToString());
}
catch (System.Xml.XmlException xe)
{
Debug.WriteLine("XML Parsing Error:" + xe.ToString());
}
catch (NullReferenceException)
{
}
}
}
}
This required some minor changes to the MediaPlayer.xaml file in order to make it use my version of the player.
First of all, I replaced the <expression:ExpressionPlayer>
tags with <Bond_Silverlight2:MI6MediaPlayer>
tags, and any static resource styles that had a target type of ExpressionPlayer:ExpressionPlayer
also needed to be replaced, and then everything used my new player. Obviously, there was another step - how to initialize and pass my XML playlist to my new player. First, I created my XML file in the format outlined above. It is important to note, that my video files and associated JPEG files are stored on the web server inside the Clientbin folder, rather than inside the xap file as resources or content. In the code-behind of my MediaPlayer.xaml (that is the user control I pasted the page.xaml into earlier, not the MI6MediaPlayer
class from the code above), I call the startup method using a link to my XML file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace Bond_Silverlight2
{
public partial class MediaPlayer : UserControl
{
public MediaPlayer()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MediaPlayer_Loaded);
}
void MediaPlayer_Loaded(object sender, RoutedEventArgs e)
{
Player1.OnStartup("Playlist.xml");
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Player1.Stop();
Visibility = System.Windows.Visibility.Collapsed;
}
}
}
The XML file ("Playlist.xml") is stored as content within the XAP file. This should be the default behavior if you created the XML file inside Visual Studio by using the Project > Add Item menu, but if you didn't, you can check by right clicking on the XML file, choosing Properties, and checking that the Build Action is "Content", and Copy to Output Directory is set to "Do Not Copy".
Now, when my player is first loaded into memory, my full playlists are immediately available.