Wednesday, April 16, 2008 6:57 AM
mtaulty
Silverlight 2 - Video, Markers, XML
I encountered a chap the other day who was building an application which did something like this;
- Play a video
- Load a set of times and thumbnail images from an XML file.
- Highlight the thumbnail images at the point where the video hits the right position.
I thought this was a great example of how easy it can be to build solutions with Silverlight 2 ( regardless of whether Expression Encoder can already do some/all of this for you automatically ).
Starting with a UI, I might have;
<UserControl x:Class="SilverlightApplication12.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition
Height="7*" />
<RowDefinition
Height="3*" />
</Grid.RowDefinitions>
<MediaElement
x:Name="media"
Source="{Binding VideoUri}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stretch="Uniform" />
<ListBox
x:Name="lstPictures"
ItemsSource="{Binding Images}"
Grid.Row="1"
>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Image
MaxWidth="96"
MaxHeight="96"
Margin="20"
Stretch="Fill"
Source="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
and then I might have an XML file for my video which looks something like;
<video url="videos/video.wmv">
<timeline>
<marker timeInSeconds="2"
image="images/img1.jpg"/>
<marker timeInSeconds="4"
image="images/img2.jpg"/>
<marker timeInSeconds="6"
image="images/img3.jpg"/>
</timeline>
</video>
I want to databind as much as possible so I write some code to produce a class that suits my needs;
public class VideoInfo
{
// TODO: Cache results of executing these queries. Maybe.
public Uri VideoUri
{
get
{
string url = (string)root.Attribute("url");
return (new Uri(url, UriKind.Relative));
}
}
public List<BitmapImage> Images
{
get
{
var query =
from i in root.DescendantsAndSelf("marker")
select new BitmapImage() {
UriSource = new Uri((string)i.Attribute("image"), UriKind.Relative)
};
return (query.ToList());
}
}
public void AddMarkersToMediaElement(MediaElement element)
{
var query =
from i in root.DescendantsAndSelf("marker")
select new TimelineMarker()
{
Time = new TimeSpan(0, 0, (int)i.Attribute("timeInSeconds")),
Text = string.Empty,
Type = string.Empty
};
foreach (var item in query)
{
element.Markers.Add(item);
}
}
public void LoadFromXml(Uri xmlUri)
{
WebClient client = new WebClient();
client.OpenReadCompleted += OnReadCompleted;
client.OpenReadAsync(xmlUri);
}
void OnReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
root = XElement.Load(XmlReader.Create(e.Result));
if (Loaded != null)
{
Loaded(this, null);
}
}
public event EventHandler Loaded;
private XElement root;
}
This just gives me a class that will load up the XML file and fire an event when it's loaded and then offers properties to my UI so that they can be databound. With that in place, I need very little code to make the application "function" such as;
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
this.Loaded += OnPageLoaded;
}
void OnPageLoaded(object sender, RoutedEventArgs e)
{
VideoInfo videoInfo = new VideoInfo();
videoInfo.Loaded += OnInfoLoaded;
videoInfo.LoadFromXml(
new Uri("video.xml", UriKind.Relative));
}
void OnInfoLoaded(object sender, EventArgs e)
{
VideoInfo videoInfo = (VideoInfo)sender;
this.DataContext = videoInfo;
media.MediaOpened += (s, a) =>
{
videoInfo.AddMarkersToMediaElement(media);
};
media.MarkerReached += (s, a) =>
{
lstPictures.SelectedIndex++;
};
}
}
and we're "in business" :-)