Islands of Richness with Silverlight on an ASP.NET page

While I certainly think there are tons of interesting scenarios for a Silverlight application that takes over the full browser screen, there are also some interesting scenarios for putting Islands of richness on a predominately ASP.NET\HTML page.   This could be to display video, show some rich visualization or animation. 

In this walk through, I'll walk through using and customize the ASP.NET MediaPlayer control to give you an idea of how to add Video to an ASP.NET website. 

In case you just want to skip to the end... here is the completed version
(update:  you'll need the server controls for this project, those should be available in the next couple of weeks.. )

 

Part 1: The Basic Media Player

1. Create a new ASP.NET WebSite.  I called mine "MediaPlayerExample.

2. Create a new directory called "Media" and drop any WMV files you have in there

image

I am going to use this XBox trailer in the example, but feel free to use whatever you have. 

 

3.  Open Default.aspx, add ScriptManager and drag and drop the asp:MediaPlayer

image

4. using the smarttag on the right on the MediaPlayer control click on the Import Skin hyper link and load in the Expression skin from the Silverlight 2 SDK (note: this is coming out soon, stay toned)
C:\Program Files\Microsoft SDKs\Silverlight\v2.0\Libraries\Server\MediaPlayerSkins

image

This file contains the set of Xaml that controls the look and feel of the MediaPlayer..  Later we will show how to customize it to get exactly the look you want.  Feel free to play with the others and find one you like.

5. Go ahead and set the MediaSource to the WMV file you added to your Media direction, select autoplay.

image

6. Then in source view, we want make the control a bit bigger, so set it to 640x480

     <asp:MediaPlayer ID="MediaPlayer1" runat="server" 
        AutoPlay="True" 
        Height="480" Width="640" 
        MediaSkinSource="~/Expression.xaml" 
        MediaSource="~/Media/sl1.wmv"  />

 

7. Hit F5, check out the fully working MediaPlayer... full DVD like controls, click to pause, double click for fullscreen, timeline scrubbing, buffering indicator, volume control... very complete!

image

 

Part 2: Encoding, Defining Captions and Chapter Markers

OK, that was super easy... let's say you now want to do something a just a bit richer.  Maybe put some custom points in the move where the user can seek to easily.   For this, we will use Expression Encoder.

1. Open up Expression Encoder and Import your movie

2. Under the Output tab, change the Directory to be the Media directory in your website and turn off Sub-folder by job ID. This makes it much quicker to do round trips during active development. 

image

3. Setting chapter markers

Play the video (or just scrub across the timeline) and add Markers to interesting points in the video using the Markers section under the Metadata tab. Be sure to select the that you want a thumb nail, we will be using that later.

image

4. Setting the captions

You may want text captions to show up in the video stream during certain sections.  The default skins support a particular script command for this called "caption".  You will find this section right below Markers in the Metadata tab.

image

5. Hit encode

6. Back in VS we need to add the chapter markers

First, refresh the project in Solution Explorer to make the thumbnails show up

image

then in the property grid for MediaPlayer click on chapters

image

Then simply set up the chapter entries. Notice the name of the image can tell you what position in the stream to use. 

image

 

7. Hit F5

image

Notice the captions are showing up... they are being pulled right out of the media stream... As users fast-forward and re-wind, these captions will stay in sync with the video. 

In the upper left corner, notice there is a way to show the chapters.  Check out the images we are using from the thumbnails they are screens expression encoder pulled right from the video.  Click on one of them to go to this point.

 

Part 3: Customizing the Media Player Skin

OK, it is very cool to see what you can do out of the box, but what if I wanted to customize the skin for the media player.  As I mentioned above, this is all defined in Xaml, so it is easy to edit that in VS or better yet, why not open it in Expression Blend and see what we can do!

 

1. In VS, open the Expression.xaml file in the root of the project.  See that it is all just Xaml

2. Right click on Expression.xaml in the solution explorer and select "Edit in Blend"

image

3. Let's add a little spice to the CaptionArea.  In blend, Right click and "View Xaml". Drill down into the xaml until you find CaptionArea

 <Canvas x:Name="CaptionArea"

In the Properties area, have some fun... I picked I nice Blue-to-Green graident brush to be the foreground which proves I will never be a designer.

image

Save and go back to VS.. We get notified that the skin has been changed... It is excellent that VS and Expression share the same xaml file format. 

image

 

5.  In VS, hit F5

Notice the, ahh, cool, change to the caption area

image

What I showed in this step is how to customize the xaml for the media player.  You can now really go wild with this and change the look completely, just remember to have all the same elements with the same x:Name and you will be set! 

 

Part 4: Handing custom script commands in Ajax to show custom data

In part 2, I introduced Script Commands in Expression Encoder with the caption command.  In this section I will show how to use your own custom script command to do something interesting on the client when a certain part of the movie is being played. 

 

1. Add some xaml to the Expression.xaml skin file to show the price.  In VS (or blend) open Expression.xaml and find the ChapterToggleButton canvas.  Right above that add the following Xaml

 

    <Canvas x:Name="Pricing" Canvas.Left="16.5703" Canvas.Top="52.1174" Opacity="0">
      <Rectangle Stroke="Black" Width="607.037" Height="114.333" RadiusX="5" RadiusY="5">
        <Rectangle.Fill>
          <RadialGradientBrush GradientOrigin="0.75,0.25">
            <GradientStop Color="Yellow" Offset="0.0" />
            <GradientStop Color="White" Offset="1.0" />
          </RadialGradientBrush>
        </Rectangle.Fill>
      </Rectangle>
      <TextBlock Canvas.Left="5" Canvas.Top="5">Price: </TextBlock>
      <TextBlock x:Name="Pricing_Text" Canvas.Left="5" Canvas.Top="25"></TextBlock>
    </Canvas>
 Right above the ChapterArea_Show Storyboard, add the following Xaml
     <Storyboard x:Name="Pricing_Show">
      <DoubleAnimation Storyboard.TargetName="Pricing" Storyboard.TargetProperty="(Opacity)" To="1" Duration="0:0:00.55" />
    </Storyboard>
    <Storyboard x:Name="Pricing_Hide" >
      <DoubleAnimation Storyboard.TargetName="Pricing" Storyboard.TargetProperty="(Opacity)" To="0" Duration="0:0:00.35" />
    </Storyboard>

2. Now we need to add some markers to the video stream to make these show up. In Expression Encoder add a couple of new Script Commands.  Make the type be animate and the command be Price_Show and Price_Hide.

image

Encode and flip back to VS

3. In VS, open default.aspx and add the following JavaScript.  What we are doing here is writing some Ajax code to get a hold of the MediaPlayer element and defining a couple of events.  We want the _onMarkerReached event to be called each time one of the markers in the video is reached.  It will then parse out the commands and call the appropriate method.  So far, there is just one custom command: animate.  When it is called, we call the _animate function which begins the animations we defined in xaml above. 

     <script type="text/javascript">
    var player;
    
    function pageLoad() {
        player = $find('MediaPlayer1');
        player.add_pluginLoaded(_loaded);
    }
    
    function _loaded() {
        Sys.Debug.trace("loaded");
    }
    
    function _animate(e) {
        Sys.Debug.trace(e);
        if (!e || e === 'undefined') return;
        
        // Start the animation. 
        var el = player.get_element().content.root.findname(e);
        if (el) {
            Sys.Debug.trace(el);
            el.begin();
        }
    }
    
    function _onMarkerReached(sender, args) {
        var marker = args.get_marker();
        if (marker.type === 'animate')
            _animate(marker.text);
    }
</script>

 

4. Next we need to write up the _onMarkerReached event so the MediaPlayer control calls it.  

     <asp:MediaPlayer ID="MediaPlayer1" runat="server" 
        AutoPlay="True" 
        Height="480" MediaSkinSource="~/Expression.xaml" 
        MediaSource="~/Media/sl1.wmv" Width="640" 
        OnClientMarkerReached="_onMarkerReached">

5. Just to get a better look at what is going on, let's add a TextArea to use for Debugging output. Drop this anywhere on the page.

     <p />
    <textarea name="TraceConsole" id="TraceConsole" 
        cols="40" rows="5"></textarea>

6. F5 to run it.  Notice we get the custom xaml for the price to come up at the right time, and the debug window is showing us all the events that are bubbling through.  If you miss it your can just rewind and it will show up again!  That is the beauty of making it event based on the context of the video. 

image

 

7. We can now look up the price dynamically to get up to the minute price information by calling a web service.   Luckily this is very easy to do in with ASP.NET AJAX.  

a. Add Item, then select Ajax-Enabled WCF service

image 

b. Add the following method to the PriceService.cs file

 [OperationContract]
public string GetPrice()
{
    Random r = new Random((int)DateTime.Now.Ticks);
    return String.Format("{0:C}", r.NextDouble()*100);
}

c. Add the following to ScriptManager in default.aspx

     <asp:ScriptManager runat="server" ID="sm">
        <Services>
            <asp:ServiceReference Path="~/PriceService.svc" />
        </Services>
    </asp:ScriptManager>

d. Change _animate() to include an async call to the web service and in the response to that call, reach into the Xaml DOM and set the Pricing_Text field. 

 function _animate(e) {
    Sys.Debug.trace(e);
    if (!e || e === 'undefined') return;
    
    // Start the animation. 
    var el = player.get_element().content.root.findname(e);
    if (el) {
        Sys.Debug.trace(el);
        PriceService.GetPrice(_setPrice)
        el.begin();
    }
}
function _setPrice (price) {
        var el = player.get_element().content.root.findname('Pricing_Text');
        el.Text = price;        
}

 

e.  Hit F5 to run it and see the price gets set... it is different every time you replay the video because it is getting the up to the minute price.  You can use FireBug or WebDevHelper to trace the Ajax calls to see what is happening under the covers.  But it is basically an Async JSON call going out over XmlHttp. 

image

 

What I showed you in this part was some very cool client side Ajax interactions with the media.  I showed you how to add markers to the video stream that would cause events to happen in the client. I also showed how to respond to those events by calling a web service to get data from the server. 

 

Part 5: Using Script Commands to localize the captions

Ok, one more cool script command to show.. What if we wanted to captions to be localized based on the culture of the client.    That is easy to do with a script command.   

1. Go back to expression encoder and change all the captions to be a caption ID rather than the hard coded English string we saw earlier.    My example looks like this:

image

2. Encode that and go back to VS

3. open Default.aspx and at the top in the page directive set the UICulture="auto" flag to tell ASP.NET we are going to have localized content. 

 <%@ Page Language="C#" AutoEventWireup="true" UICulture="auto" 

4. Create a javascipt file named MediaPlayerResources.en-us for the English strings and  MediaPlayerResources.fr-fr to hold the french resource strings in the root of the project

MediaPlayerResources.en-us.js

 /// <reference name="MicrosoftAjax.js"/>

Type.registerNamespace("Resources");

Resources.MediaPlayer = {
    caption1: "Start machine configuration.",
    caption2: "Case color.",
    caption3: "Black is the best case color to choose.",
    caption4: "HDMI socket pre-configured.",
    caption4: "Optional hard disk drive."
}

 

MediaPlayerResources.fr-fr.js

 /// <reference name="MicrosoftAjax.js"/>

Type.registerNamespace("Resources");

Resources.MediaPlayer = {
    caption1: "Commencer la configuration.",
    caption2: "Couleur.",
    caption3: "Le noir est la meilleure couleur.",
    caption4: "Prise HDMI préconfigurée.",
    caption4: "Disque dur en option."
}

5. add a reference to these in the ScriptManager

     <asp:ScriptManager runat="server" ID="sm">
        <Services>
            <asp:ServiceReference Path="~/PriceService.svc" />
        </Services>
        <Scripts>
            <asp:ScriptReference Path="~/mediaplayerresources.js" ResourceUICultures="en-us,fr-fr,de-de" />
        </Scripts>
    </asp:ScriptManager>

 

6. Modify onMarkerReached to pull out the localized strings from the resource files

 function _onMarkerReached(sender, args) {
    var marker = args.get_marker();
    switch (marker.type)
    {
        case 'animate': _animate(marker.text); break;
        case 'caption': player.set_caption(Resources.MediaPlayer[marker.text]); break;
    }
    
}

7. F5 to run it...  inIE, Under tools, Internet Options, Languages change the order so that fr-fr is first.

image

Notice the localized text!

image

 

Part 6: Using Script Commands to start an interstitial Ad

I have already shown how cool script commands can be.  In this, last section I will show you one more very cool thing to do with Script commands.

1. Back in Encoder, add another ScriptCommand for Ad and have the command be the name of another WMV file.  This is the file we want to be an ad..
image

You should have Bear.wmv on your windows vista machine, but if you don't you can get it here

Encode

Add the bear.wmv file to your media directory. 

2. Back in VS, in default.aspx we need add some logic to handle the new ad command we just created.  In this case I cam going to create a some new Xaml on the fly to create a Video in Video window to display the ad.  Of course we also pause the main video so all the attention is on the ad. 

 function _onMarkerReached(sender, args) {
    var marker = args.get_marker();
    switch (marker.type)
    {
        case 'animate': _animate(marker.text); break;
        case 'caption': player.set_caption(Resources.MediaPlayer[marker.text]); break;
        case 'ad':
            player.pause();
            var host = player.get_element();
            var x = host.content.createFromXaml("<MediaElement Source='media/" + args.get_marker().text 
                + "' AutoPlay='true' MediaEnded='_adEnded' MediaFailed='_adError' Canvas.Top='60' Canvas.Left='80' Width='160' Height='120'/>");
            host.content.root.children.add(x);
        }        
}

 

3. We need add a couple of event handlers to start the main player again. 

 function _adEnded(sender, args)  
    {
        sender.getParent().children.remove(sender);
        player.play();
    }
    function _adError(sender, args)
    {
        Sys.Debug.trace("Failed to Load Ad");
        _adEnded(sender);
    }

4. F5.. Notice the ad comes up, plays through and then the video picks up again.

image

In this section, I showed how to use Script Commands to add a dynamically add new Xaml content to the player.  In this case an Ad.