Building a simple video player with WPF/E tutorial
I haven't had much original content on here in a while so I thought that should change by putting up some short tutorials on building stuff using "WPF/E". And the obvious one to start with is a really simple video player (RSVP for short).
For an introduction to WPF/E, XAML and the programming model (JS) please refer to this article on MSDN which talks about WPF/E from a key scenario, web media usage - "WPF/E" for Web Media Scenarios.
What I use/you will need to follow this at home:
- Visual Studio 2005 (with WPF/E templates installed)
- Expression Design
- Expression Blend
There are 3 steps for us to create this video player in WPF/E:
- Create the player.xaml file which will hold the XAML mark up that represents the video player. This will be created in Expression Design and outputted as WPF/E friendly XAML.
- Using Expression Blend, animate some of the elements to indicate an action by the user. Modify the XAML to be WPF/E friendly (Blend doesn't currently support WPF/E)
- Hook up a number of events in JavaScript to perform the play/pause and full-screen functionality on user driven mouse events
Create the Player.xaml file in Expression Design
Opening Expression Design, we want a document with the same dimensions as our video player (e.g. 573 x 320). We will use the tool to create a skin which we will program against in Javascript. We need to add a background which we will replace with a MediaElement and two collections of shapes to represent our buttons (Play and full screen).
In the centre of the document, drag on a rectangle with quite a large size and set it's Opacity to 40% (In the properties palette on the right, try searching for it) and fill to White. Now do the same with an ellipse and using the Polyline tool (click and hold the Fountain pen icon to reveal the polyline tool on the left to create a triangle shape.
Currently, these are all separate shapes and we want to group them together so we can refer to them logically as a single item in code. Right-click and Group. In the layers pane expand the Layer 1 and double-click on [Group] and rename to PlayButton. This will make things easier when we export to XAML.
Now repeat the same process for a Full Screen button. I used a small rectangle and a large on inside a container rectangle. Name it FullScreen
This is in Expression Design format and we need to get it in XAML to render with the WPF/E plug-in. To do this:
File > Export... > Save As > XAML (video.xaml file name) > Select WPF/E export option > Export.
We now have a XAML file to program against. But it's only shapes on a Canvas, we need to create some interactivity so that when the user clicks the play button it disappears from view and plays the video.
User Expression Blend to create animations
Expression Blend doesn't currently support WPF/E friendly XAML and so we need to workaround this and use the tool as if it were a normal Windows EXE application. Create a new project, stick it somewhere handy, and link (under Project) to the video.xaml file.
To animate the PlayButton we need to create a new TimeLine - call it something like PlayAnimation. Blend now flips to recording mode which you can tell by the red outline on the design surface:
To animate the button we select it in the Timeline/Object hierarchy and advance the timeline on 1 or 2 seconds.
And modify the Opactity property and set it to 0. When the play button is pressed you will see the PlayButton fade from view.
Now, the workaround kicks in, as Blend puts the animation as a Resource in the XAML. This isn't supported in WPF/e and to fix it we need to move the animation directly into the Storyboard.
We need to turn this:
<Canvas.Resources>
<Storyboard x:Key="PlayAnimation">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="PlayButton" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:01" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Canvas.Resources>
<Canvas.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource PlayAnimation}"/>
</EventTrigger>
</Canvas.Triggers>
into this:
<Canvas.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard x:Name="PlayAnimation">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="PlayButton" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:01" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Canvas.Triggers>
Items in bold are new text (x:Key isn't support, x:Name is) and the Storyboard itself is just copied down and the Resources element removed.
One last bit of XAML. The Animation will being as soon the parent canvas element is loaded. To stop this from happening, we can add a BeginTime property to the Storyboard and set its time to "01:00:00" which means it will fire a day after first loading. Instead, we will initiate the animation in Javascript code.
Write JavaScript code behind for events
I have used the Visual Studio web application plug-in and WPF/E template to create the container web project. John Rayner has details on installing and setting up your workstation for WPF/E
So, I have a WPFEJSApplication1 (or 1000 by now in my case :)) and I need to add the video.xaml file to the project and change the Default.html to reflect.
In the new aghost() declaration, replace:
plugin.xaml with video.xaml
400 with 573 for width
400 with 320 for height
One major part missing is the video. So, add a video to your project (I picked a large video with high res to show off full screen, any vanilla WMV9 video should do). In the video.xaml file we need to replace the black Path element which represents the background with a MediaElement:
<MediaElement x:Name="Media" Width="574" Height="321" Canvas.Left="-0.5" Canvas.Top="-0.5"
Source="T2_720.wmv" AutoPlay="false" />
(AutoPlay is set to false, default is true to stop the video playing automatically)
Now, open the eventhandlers.js file and clear the file of the placeholder code. To get this sample working we need to hook up our buttons to event handlers, and set up the Loaded property in our root Canvas element (this is where we will set up the event handlers).
In the video.xaml file we need to add the following property:
<Canvas xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" x:Name="Untitled1"
Loaded=root_Loaded
>
And in the eventhandlers.js file we create the method:
function root_Loaded(sender, args)
{
}
We need to grab the PlayButton and we do this using the findName(string) method to pull it out of the XAML object model. And we hook up it's mouse up event to a function we will define in a sec:
var button = sender.findName("PlayButton");
button.mouseLeftButtonUp = "javascript:handleMouseUp";
var fsBtn = sender.findName("FullScreen");
fsBtn.mouseLeftButtonUp = "javascript:leftMouseUpFSEvent";
The process is the same for invoking the Play() method on our video and Begin():
function handleMouseUp(sender, eventArgs) {
//Grab the Media Element and call the Play() method
var myMedia = sender.findName("Media");
myMedia.Play();
//Grab the animation and start it
var myAnimation = sender.findName("PlayAnimation");
myAnimation.Begin();
}
For the full screen we have to access the parent WPF/E control and set some properties and access it's height and width.
function leftMouseUpFSEvent(sender, eventArgs)
{
//Find the parent WPFE control using getElementById
var wpfeC = document.getElementById("wpfeControl1");
//Set the full screen property to true/false
wpfeC.fullScreen = !wpfeC.fullScreen;
//Grab the media element and set its height/width properties and make the FS button invisible
var media = sender.findName("Media");
media.Height = wpfeC.actualHeight;
media.Width = wpfeC.actualWidth;
var FS = sender.findName("FullScreen");
FS.Opacity = 0;
}
This is a functioning video player which will work cross-browser/cross-platform. I have hosted a version here for you to test (note: you must have the February CTP installed to view).
Really Simple Video Player in WPFE (Will take quite a while to download the 70 meg file which gives me a nice follow-on to async download) (and also available for download here).
Next up:
Adding more controls: pause, stop, volume, seeking, async progress.
Better JS using prototype class inheritance for buttons, etc.
Technorati tags: wpf/e, wpfe, microsoft expression
Comments
Anonymous
March 23, 2007
I've been messing around with my WPF/E video player and adding more functionality. You can look at myAnonymous
April 05, 2007
I am trying to animate a couple simple pics. IE7 keeps crashing with the following code... (more info after code block) <Canvas x:Name="LayoutRoot"> <Image x:Name="testTubes" Width="632" Height="294.933" Source="imagestesttubes.png" Stretch="Fill"/> <Image RenderTransformOrigin="0.5,0.5" x:Name="man" Width="12" Height="20.8" Canvas.Left="730" Canvas.Top="49" Source="imagesmanBodySystems.png" Stretch="Fill"> <Image.RenderTransform> <TransformGroup> <ScaleTransform ScaleX="1" ScaleY="1"/> <SkewTransform AngleX="0" AngleY="0"/> <RotateTransform Angle="0"/> <TranslateTransform X="0" Y="0"/> </TransformGroup> </Image.RenderTransform> <Image.Triggers> <EventTrigger RoutedEvent="FrameworkElement.Loaded"> <BeginStoryboard> <Storyboard x:Name="Timeline1"> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="man" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)"> <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="-268"/> <SplineDoubleKeyFrame KeyTime="00:00:01" Value="-279.5"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="man" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)"> <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="108"/> <SplineDoubleKeyFrame KeyTime="00:00:01" Value="88.067"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="man" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"> <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="1"/> <SplineDoubleKeyFrame KeyTime="00:00:01" Value="14.249"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="man" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"> <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="1"/> <SplineDoubleKeyFrame KeyTime="00:00:01" Value="14.249"/> </DoubleAnimationUsingKeyFrames> ****TROUBLE STARTS HERE ***** <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="man" Storyboard.TargetProperty="(UIElement.Visibility)"> <DiscreteDoubleKeyFrame KeyTime="00:00:00" Value="{Visibility.Hidden}"/> <DiscreteDoubleKeyFrame KeyTime="00:00:00.5000000" Value="{Visibility.Visible}"/> </DoubleAnimationUsingKeyFrames> *****TROUBLE ENDS HERE ***** </Storyboard> </BeginStoryboard> </EventTrigger> </Image.Triggers> </Image> </Canvas> (more) Blend generates this code: <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="man" Storyboard.TargetProperty="(UIElement.Visibility)"> <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Hidden}"/> <DiscreteObjectKeyFrame KeyTime="00:00:00.5000000" Value="{x:Static Visibility.Visible}"/> </ObjectAnimationUsingKeyFrames> Which appears not to be supported by wpf/e. What does work? I have tried a just about everything I can think of and nothing works. In fact the browser crashes evertime this code is executed. If the trouble section is omitted the code works (just not what I want to happen.) The idea is to have elements "hidden" until I want them visible in the view. LarryAnonymous
April 05, 2007
The comment has been removedAnonymous
April 15, 2007
At NAB this week in Las Vegas, we have announced the product name for "WPF/E" and that is Silverlight