Friday, September 03, 2010    
Blog  
OpenLight Blog
Jan 2

Written by: Michael Washington
1/2/2010 10:03 AM 

image

Live example: http://www.adefwebserver.com/silverlight/SilverlightCaptioning/SilverlightDynamicMediaMarkers/

This is another one of my “this blog post is not really about what this blog post is about”. Yes, I will deliver on what the title promises, but creating closed captaining with Silverlight is actually very easy. The thing that took me so much time to put together, was implementing a “MVVM like” pattern and have a code behind that has no application logic and looks like this:

using System.Windows.Controls;
namespace SilverlightDynamicMediaMarkers
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
       }
    }
}

 

From what I understand, it means that each layer is de-coupled and independently testable. When you change a caption and restart the video, it picks up the changes through bindings to dependency properties that are automatically updated.

But, first…

Creating Closed Captioning with Silverlight 3.0

The Silverlight 3 MediaElement allows you to create a collection of TimelineMarkers like this:

 

TimelineMarker objTimelineMarker1 = new TimelineMarker();
objTimelineMarker1.Text = Caption1;
objTimelineMarker1.Time = new TimeSpan(0, 0, 5);
objTimelineMarker1.Type = "Start";
TimelineMarker objTimelineMarker2 = new TimelineMarker();
objTimelineMarker2.Text = "";
objTimelineMarker2.Time = new TimeSpan(0, 0, 10);
objTimelineMarker2.Type = "Stop";
TimelineMarkerCollection colTimelineMarkerCollection =
    new TimelineMarkerCollection();
colTimelineMarkerCollection.Add(objTimelineMarker1);
colTimelineMarkerCollection.Add(objTimelineMarker2);

 

And attach them to the media like this:

 

public void UpdateTimelineMarkers()
{
    // Clear existing Markers
    this.media.Markers.Clear();
    if (MediaTimelineMarkerCollection != null)
    {
        // Add Markers
        foreach (var Marker in MediaTimelineMarkerCollection)
        {
            TimelineMarker objTimelineMarker = new TimelineMarker();
            objTimelineMarker.Text = Marker.Text;
            objTimelineMarker.Time = Marker.Time;
            objTimelineMarker.Type = Marker.Type;
            this.media.Markers.Add(objTimelineMarker);
        }
    }
}

 

As the media is playing (this can be a video or even a Mp3 file) the MarkerReached Event will fire, passing an instance of the marker as an argument. It can then be used to display subtitles in a text box that is overlain on top of the video.

 

private void media_MarkerReached(object sender, 
    TimelineMarkerRoutedEventArgs e)
{
    ClosedCaption.Visibility = (e.Marker.Type == "Start") 
        ? Visibility.Visible : 
        Visibility.Collapsed;
    ClosedCaption.Text = e.Marker.Text.ToString();
} 

 

Note, that you could use the media markers for a lot of other things such as triggering a JavaScript method on the hosting .html page that causes other things on the page to change.

You can also create media markers and embed them in the media using a product such as Microsoft Expression Blend.

The Closed Captioning Program

image

Implementing closed captaining will take you about 5 minutes. However, my desire was to create a program that would allow you to enter captions on the fly and when you re-start the video, you would see them. Note, you have to stop, rewind a bit, and restart the video to get new captions to show up. You hover over the video to get the play control to show so you can stop the video.

I could have simply put a button on the page that would reload the video, but I wanted to use bindings to tie everything together in a “MVVM like” architecture

I say “MVVM like” because it is hard to get two people to agree on what MVVM is in Silverlight means because apparently Silverlight 3 does not allow “true MVVM” because it is missing things like commanding and triggers. There are frameworks available that get past these limitations.

The Captions

First, I created 3 TextBoxes to hold the Captions.

I set bindings on each of the controls:

 

<TextBox x:Name="txtCaption1" Grid.Column="2" Grid.Row="1" 
Text="{Binding Caption1, Mode=TwoWay, UpdateSourceTrigger=Default}" 
TextWrapping="Wrap"/>

 

The bindings work because I set a DataContext on the Parent object that they are contained in, to a class called ViewModel:

 

xmlns:local="clr-namespace:SilverlightDynamicMediaMarkers">
<Canvas x:Name="LayoutRoot">
<Canvas.DataContext>
  <local:ViewModel />
</Canvas.DataContext>

 

In the ViewModel class I created a ObservableCollection of TimeLineMarkers property and set the TimelineMarkers property to rebuild each time one of the captions is changed. This happens because the binding is set to TwoWay and the ViewModel class implements the INotifyPropertyChanged interface and the Caption properties trigger NotifyPropertyChanged.

 

#region Caption1
public string Caption1
{
    get
    {
        return this._Caption1;
    }
    set
    {
        if (value != this._Caption1)
        {
            this._Caption1 = value;
            NotifyPropertyChanged("Caption1");
            TimelineMarkers = BuildTimelineMarkers();
        }
    }
}
#endregion

 

The Video Player

The Blacklight Codeplex project has a great set of controls with full source code. I took the video player from that project and added a TextBlock over the video player to display the captions.

I now have a ViewModel class that is bound to the user interface. When you change a caption and move focus from the TextBox, the TimelineMarkers property will be rebuilt. You can set a break point in the code and see this for yourself.

Now, I need to bind the video player in the MediaPlayer control to the collection of TimelineMarkers in ViewModel. To do this, I created dependency property.

The UpdateTimelineMarkers() method in the MediaPlayer control, (listed above) gets the TimelineMarkers from the collection contained in the MediaTimelineMarkers property of the MediaPlayer control. Here is the code for that property:

 

public ObservableCollection<TimelineMarker> MediaTimelineMarkerCollection
{
    get 
    { 
        return 
        (ObservableCollection<TimelineMarker>)GetValue(MediaTimelineMarkerProperty); 
    }
    set { SetValue(MediaTimelineMarkerProperty, value); }
}

 

This property reads and sets the MediaTimelineMarkerProperty dependency property. here is the declaration for that property:

 

public static readonly DependencyProperty MediaTimelineMarkerProperty =
    DependencyProperty.Register("MediaTimelineMarkerCollection", 
    typeof(ObservableCollection<TimelineMarker>), typeof(MediaPlayer), 
    new PropertyMetadata(new PropertyChangedCallback(MediaTimelineMarker_Changed)));

 

This declaration contains a pointer to MediaTimelineMarker_Changed that will fire whenever it is changed. Here is the code for that method:

 

private static void MediaTimelineMarker_Changed(DependencyObject 
    dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
    MediaPlayer mediaPlayer = (MediaPlayer)dependencyObject;            
    mediaPlayer.MediaTimelineMarkerCollection = 
        (ObservableCollection<TimelineMarker>)eventArgs.NewValue;
}

 

This allows us to bind the TimelineMarkers collection in ViewModel, to the MediaTimelineMarkerCollection in the  MediaPlayer control, by using simple binding code (in MainPage.xaml) like this:

 

<controls:MediaPlayer x:Name="myMediaPlayer" 
MediaTimelineMarkerCollection="{Binding TimelineMarkers, Mode=TwoWay, UpdateSourceTrigger=Default}" 
MediaSource="Media/niceday.wmv"
Canvas.Left="27" Canvas.Top="32" Height="248" Width="365" />

 

MVVM The good and the Bad

A lot of time was lost in creating this program when I had a typo in my Dependency property declaration. Also, when you make a mistake with a binding in XAML you wont always get a compile-time error, you will get a run-time error.

However, consuming the video player and binding to it’s properties using declarative binding and collections that automatically update does require less code.

 

Download the code here:

http://www.adefwebserver.com/silverlight/SilverlightCaptioning/SilverlightDynamicMediaMarkers.zip

 

Get Microsoft Silverlight

Tags:

3 comment(s) so far...

Re: Closed Captioning with Silverlight (using MVVM)

Note: You can make your life easier and use:
Dependency Property Generator
www.kirupa.com/blend_silverlight/dependency_property_generator.htm

By Michael Washington on   1/3/2010 8:52 AM

Re: Closed Captioning with Silverlight (using MVVM)

What's the current state of closed captions with Silverlight 3/4? We'd like to use it for stripping in captions in multiple languages. Does it use SAMI? There seems to be a dearth of information on this topic; the Mix 2010 schedule doesn't seem to include too much on captioning.

By Nicholas Bedworth on   3/15/2010 7:29 PM

Re: Closed Captioning with Silverlight (using MVVM)

@Nicholas Bedworth - You can use the method described in this blog to generate any text you desire. But the formats you describe are not available "out of the box" as far as I know.

By Michael Washington on   3/15/2010 7:37 PM

Your name:
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Add Comment   Cancel 
  
Copyright 2009 by OpenLightGroup.net   |  Privacy Statement  |  Terms Of Use