Click here to Skip to main content
Click here to Skip to main content

Silverlight 4 Video Player

, 8 May 2011 Ms-PL
Rate this:
Please Sign up or sign in to vote.
An example of a Silverlight 4 View Model Style video player that is not just 'skinable' but fully 'designable'.

NOTE: This article is outdated due to changes in the latest version of RX Extensions.

Introduction

This project demonstrates an implementation of the View Model Style pattern to create a fully "designable" Silverlight video player. This is not to be confused with a "skinable" video player. A skinable video player allows you to change the look and feel of the buttons that control the player. A designable player allows a designer to use any set of controls to implement the video player.

The View Model Style pattern allows a programmer to create an application that has absolutely no UI. The programmer only creates a ViewModel and a Model. A designer with no programming ability at all is then able to start with a blank page and completely create the View (UI) in Microsoft Expression Blend 4 (or higher).

Note: to build the project, you may need to install the Silverlight RX Extensions from http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx.

The Power of View Model Style

This Silverlight project is not a full featured video player, but it actually works, and hopefully demonstrates a non-trivial example of a View Model Style Silverlight project. Certain functions such as the pause button, full screen support, and skip ahead were left out to simplify the example code as much as possible.

If you are new to View Model Style, it is suggested that you read Silverlight View Model Style: An (Overly) Simplified Explanation for an introduction.

View Model Style

image

Much has been written about the View Model Style pattern, and there are various interpretations. In this example, we will implement the pattern with as little code as possible. We will try to present it in the easiest possible way. The Model and the View of the View Model Style are very simple. The Model contains Web Services, and the View is the UI (that is created in Expression Blend with no coding). The only complex part is the ViewModel. In the ViewModel, all functionality will be implemented using:

  1. Properties - One of something. This could be a String or an Object. Implements INotifyPropertyChanged so that any element bound to it is automatically notified whenever it changes.
  2. Collections - A collection of something. This is of type ObservableCollection so that any element bound to it is automatically notified whenever it changes.
  3. Commands - An event that can be raised. Also, one parameter of type Object can be passed. This implements ICommand.

For the commands, we will only need to use the InvokeCommandAction behavior.

That's it. This is the "simplified" View Model Style.

A Silverlight View Model Style Video Player

Let's first start with the experience a designer would have in using the View Model Style pattern to completely redesign the video player.

When you download the attached code, you will need to place some .wmv videos in the Video folder.

When you run the project, the Web Service in the web project will detect what videos are in the folder and pass them to the Silverlight application.

  • The list of videos will show in the combo box dropdown.
  • You can select a video from the combo box and then click the Play button to play the video.
  • A progress bar will show the current progress.
  • The exact location of the video will display above the video.
  • The current and total time of the video will also display.
  • The Stop button will stop the video.

When you open the project in Expression Blend and open the MainView.xaml file, click on the Data tab...

You will see that there is a Data Context section. The Data Context is set to the ViewModel, and displays all the Properties, Collections, and Commands that the UI can interact with. The designer only needs to interact with these elements to implement their own video player.

The image above shows what is bound to what. However, note that while buttons are bound to, and raise ICommands, practically anything can raise an ICommand, it does not have to be a button. In the tutorial TreeView SelectedItemChanged Using View Model Style, a TreeView control is used to raise the ICommand.

Redesign the Video Player

We can delete the existing MainPage.xaml file...

... and create a new one.

If we click on LayoutRoot in the Objects and Timeline window...

... and click on the New button next to DataContext...

... and select MainViewModel and click OK:

When we click on the Data tab...

... we will see the Data Context has been set for the page.

Let me repeat this important point: We will not need to create a single line of code to implement the video player.

Create the UI

The one control that is required is the MediaElement. In Expression Blend:

  1. Click the Asset button
  2. Locate the MediaElement
  3. Drag it to the design surface

Make sure you set the AutoPlay to false by un-checking the box in the Properties for the MediaElement.

I went to the Alan Beasley article: http://www.codeproject.com/KB/expression/ArcadeButton.aspx and stole a button (he really does good work; when you click on the button, it actually animates and behaves like a real button).

I then created the "Masterpiece" you see above. Of course, anyone could do better. I just wanted to show how radically different the UI could be, yet still work without any code changes.

Wire-up the MediaElement - Learn to "Think View Model Style"

The UI is created, all we need to do is bind the UI elements to the ViewModel and the application is complete.

To understand how we bind the UI elements to the ViewModel, we need to learn a few things to "Think Like View Model Style". Basically, we:

  • Bind UI elements to Properties or Collections, and when the ViewModel puts something in those Properties or Collections, events will automatically be raised on the bound UI elements.
  • We can use the InvokeCommand behavior to respond to particular events and raise other events in the ViewModel.

In this application, we need to wire-up the MediaElement to the ViewModel. To do this, we have to:

  1. Bind the MediaElement to the SelectedVideoProperty (URI). This will cause the MediaElement to always play the video that the ViewModel sets to that property.
  2. Use an InvokeCommand behavior to raise the MediaOpenedCommand (ICommand). This behavior is configured to fire when the MediOpened event is raised on the MediaElement (this happens automatically when the MediaElement is ready to play the video). The behavior also passes a reference to the MediaElement as a parameter to the ViewModel (the ViewModel will use this reference to the MediaElement when other commands such as Start and Stop are called).

Let's walk through these steps:

In the Objects and Timeline window, select the MediaElement.

In the properties for the MediaElement, select the Advanced options for Source.

Select Data Binding...

Set it to SelectedVideoProperty and click OK.

You will know an element is bound because it will have a gold box around it.

In Assets, click and drag an InvokeCommand behavior...

... and drop it under the MediaElement in the Objects and Timeline window.

In the properties for the behavior, set MediaOpened for the EventName and click the Data bind button next to Command.

Select MediaOpenedCommand and click OK.

Select Advanced options for CommandParameter:

Select Data Binding...

Click the Element Property tab, and select MediaElement, and click OK.

That was the hard part. The rest is just binding the remaining Properties, Collections, and Commands.

Binding Properties and Collections

These are the remaining Properties and Collections that need to be bound.

For example, the ProgressBar control has its Value and Maximum properties set to CurrentPositionProperty and TotalDurationProperty, respectively.

The ListBox control is naturally bound to SilverlightVideoList, but the SelectedIndex for the ListBox is also bound to SelectedVideoInListProperty. The reason for this is that you cannot set the Selectedindex until after the list has been loaded.

The ViewModel fills the SilverlightVideoList, and only after it has filled that collection does it check to see if there are any items, and if there are, sets the Selectedindex value.

Binding Commands

These are the remaining Commands that need to be bound. To bind them, simply drop an InvokeCommand behavior on the control and set the properties to the respective commands.

For example, the ListBox uses an InvokeCommand behavior to call the SetVideoCommand when its SelectionChanged event is raised.

Its CommandParameter is bound to the SelectedItem of the ListBox.

That's about it. If you're a designer, you can skip the next section.

The Code

The website part of the code is simply a website with a Web Service that returns a list of any video that is in the Videos folder.

The Silverlight project consists of only a few files. The files that are important at this point are:

  • DelegateCommand.cs - A helper file that allows us to easily create ICommands (see: Silverlight View Model Style File Manager for a full explanation of this file).
  • SilverlightVideos.cs - This is the Model. It consists of a single Web Service method.
  • MainViewModel.cs - This is the ViewModel. This is where all the "magic" happens.

The Model

The Model for this application consists of a single web method in the SilverlightVideos.cs file. The "twist" is that we are using RX Extensions that call the Web Service and return an IObservable.

public static IObservable<IEvent<GetVideosCompletedEventArgs>> GetVideos()
{
  // Uses http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx
  // Also see: http://programmerpayback.com/2010/03/11/
  //           use-silverlight-reactive-extensions-rx-to-build-responsive-uis/
  // Also see: http://www.silverlightshow.net/items/
  //           Using-Reactive-Extensions-in-Silverlight-part-2-Web-Services.aspx 

  // Set up web service call
  WebServiceSoapClient objWebServiceSoapClient =
    new WebServiceSoapClient();

  // Get the base address of the website that launched the Silverlight Application
  EndpointAddress MyEndpointAddress = new
    EndpointAddress(GetBaseAddress());

  // Set that address
  objWebServiceSoapClient.Endpoint.Address = MyEndpointAddress;

  // Set up a Rx Observable that can be consumed by the ViewModel
  IObservable<IEvent<GetVideosCompletedEventArgs>> observable = 
    Observable.FromEvent<GetVideosCompletedEventArgs>(
    objWebServiceSoapClient, "GetVideosCompleted");
  objWebServiceSoapClient.GetVideosAsync();

  return observable;
}

The ViewModel

image

The ViewModel is where all the Properties, Collections, and Commands are created.

This is the constructor of the ViewModel:

public MainViewModel()
{
    // Set the command property
    MediaOpenedCommand = new DelegateCommand(MediaOpened, CanMediaOpened);
    PlayVideoCommand = new DelegateCommand(PlayVideo, CanPlayVideo);
    StopVideoCommand = new DelegateCommand(StopVideo, CanStopVideo);
    SetVideoCommand = new DelegateCommand(SetVideo, CanSetVideo);
    
    // Call the Model to get the collection of Videos
    GetListOfVideos();
}

It sets up the Commands by using the DeleagateCommand method (from the DelegateCommand.cs file) to create ICommands. Next, it calls the GetListOfVideos() method.

private void GetListOfVideos()
{
    // Call the Model to get the collection of Videos
    SilverlightVideos.GetVideos().Subscribe(p =>
    {
        if (p.EventArgs.Error == null)
        {
            // loop thru each item
            foreach (string Video in p.EventArgs.Result)
            {
                // Add to the SilverlightVideoList collection
                SilverlightVideoList.Add(Video);
            }

            // if we have any videos, set the selected item value to the first one
            if (SilverlightVideoList.Count > 0)
            {
                SelectedVideoInListProperty = 0;
            }
        }
    });
}

This method calls the GetVideos() method in the Model that fills the SilverlightVideoList collection. It also sets the SelectedVideoInListProperty property if there are videos in the collection.

The SilverlightVideoList collection is a simple ObservableCollection:

private ObservableCollection<string> _SilverlightVideoList = 
        new ObservableCollection<string>();
public ObservableCollection<string> SilverlightVideoList
{
    get { return _SilverlightVideoList; }
    private set
    {
        if (SilverlightVideoList == value)
        {
            return;
        }

        _SilverlightVideoList = value;
        this.NotifyPropertyChanged("SilverlightVideoList");
    }
}

When the SelectedVideoInListProperty property is set, the UI control that is bound to it (the listbox or the combo drop down box) should raise the SetVideoCommand command that sets the SelectedVideoProperty:

public ICommand SetVideoCommand { get; set; }
public void SetVideo(object param)
{
    // Set Video
    string tmpSelectedVideo = string.Format(@"{0}/{1}", 
                              GetBaseAddress(), (String)param);
    SelectedVideoProperty = new Uri(tmpSelectedVideo, 
                                    UriKind.RelativeOrAbsolute);

    // Stop Progress Timer
    progressTimer.Stop();
}

private bool CanSetVideo(object param)
{
    // only set video if the parameter is not null
    return (param != null);
}

The MediaElement is bound to the SelectedVideoProperty, and will automatically start loading the video. When it has opened the video, it will call the MediaOpenedCommand command that will:

  • Pass an instance of the MediaElement as a parameter so it can be stored in a private variable in the ViewModel (this will be used when the Start and Stop commands are raised).
  • Starts a DispatcherTimer that will fire each second and check the current position of the MediaElement (in the private variable) and update the CurrentPostionProperty.
public ICommand MediaOpenedCommand { get; set; }
public void MediaOpened(object param)
{
    // Play Video
    MediaElement parmMediaElement = (MediaElement)param;
    MyMediaElement = parmMediaElement;

    this.progressTimer = new DispatcherTimer();
    this.progressTimer.Interval = TimeSpan.FromSeconds(1);
    this.progressTimer.Tick += new EventHandler(this.ProgressTimer_Tick);

    SetCurrentPosition();
}

private bool CanMediaOpened(object param)
{
    return true;
}

The code for the Start and Stop Commands is:

#region PlayVideoCommand
public ICommand PlayVideoCommand { get; set; }
public void PlayVideo(object param)
{
    // Play Video
    MyMediaElement.Play();
    progressTimer.Start();
}

private bool CanPlayVideo(object param)
{
    bool CanPlay = false;
    // only allow Video to Play if it is not already Playing
    if (MyMediaElement != null)
    {
        if (MyMediaElement.CurrentState != MediaElementState.Playing)
        {
            CanPlay = true;
        }
    }
    return CanPlay;
}  
#endregion

#region StopVideoCommand
public ICommand StopVideoCommand { get; set; }
public void StopVideo(object param)
{
    // Stop Video
    MyMediaElement.Stop();
    progressTimer.Stop();
}

private bool CanStopVideo(object param)
{
    bool CanStop = false;
    // only allow Video to Stop if it is Playing
    if (MyMediaElement != null)
    {
        if (MyMediaElement.CurrentState == MediaElementState.Playing)
        {
            CanStop = true;
        }
    }
    return CanStop;
}  
#endregion

The SetCurrentPosition() method sets CurrentProgressProperty, CurrentPositionProperty, and TotalDurationProperty:

private void SetCurrentPosition()
{
    // Update the time text e.g. 01:50 / 03:30
    CurrentProgressProperty = string.Format(
        "{0}:{1} / {2}:{3}",
        Math.Floor(MyMediaElement.Position.TotalMinutes).ToString("00"),
        MyMediaElement.Position.Seconds.ToString("00"),
        Math.Floor(MyMediaElement.NaturalDuration.TimeSpan.TotalMinutes).ToString("00"),
        MyMediaElement.NaturalDuration.TimeSpan.Seconds.ToString("00"));

    CurrentPositionProperty = MyMediaElement.Position.TotalSeconds;
    TotalDurationProperty = MyMediaElement.NaturalDuration.TimeSpan.TotalSeconds;
}

Wrap Your Head Around View Model Style

It takes a bit of practice to wrap your head around View Model Style. However, it is not hard, and you will find you will use less code than you would normally use. The main thing you want to get used to is binding everything. If you find yourself needing to raise an event from the UI and you are stuck, you are usually not binding enough things. Let a change in a value in the ViewModel raise the event for you through bindings.

How Can You Learn More About Expression Blend?

To learn how to use Expression Blend, all you have to do is go to: http://www.microsoft.com/design/toolbox/. This site will give you the free training you need to master Expression Blend. It will also cover the design principles to make you a better designer.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

defwebserver
Software Developer (Senior) http://ADefWebserver.com
United States United States
Michael Washington is a Microsoft MVP. He is a ASP.NET and
C# programmer.
He is the founder of
LightSwitchHelpWebsite.com

He has a son, Zachary and resides in Los Angeles with his wife Valerie.

He is the Author of:
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 PinmemberChristian Amado28-Jul-12 6:13 
QuestionFix for Silverlight Version 1.0.10621.0 [modified] PinmemberMember 456543326-Sep-11 3:41 
AnswerRe: Fix for Silverlight Version 1.0.10621.0 Pinmvpdefwebserver26-Sep-11 4:26 
GeneralRe: Fix for Silverlight Version 1.0.10621.0 PinmemberMember 456543326-Sep-11 4:36 
GeneralDoes not work with new Rx Extensions drop PinmemberPaul Scholz8-May-11 8:54 
GeneralRe: Does not work with new Rx Extensions drop Pinmvpdefwebserver8-May-11 9:45 
GeneralRe: Does not work with new Rx Extensions drop PinmemberPaul Scholz8-May-11 10:08 
GeneralRe: Does not work with new Rx Extensions drop Pinmvpdefwebserver8-May-11 10:22 
GeneralRe: Does not work with new Rx Extensions drop PinmemberPaul Scholz8-May-11 10:41 
GeneralRe: Does not work with new Rx Extensions drop Pinmvpdefwebserver8-May-11 10:51 
GeneralRe: Does not work with new Rx Extensions drop PinmemberMember 456543326-Sep-11 3:46 
GeneralCan't get this to work Pinmemberdprentis6-May-11 4:26 
GeneralRe: Can't get this to work Pinmvpdefwebserver6-May-11 4:30 
GeneralMy vote of 5 Pinmemberaofeng316-Apr-11 23:49 
GeneralMy vote of 5. Pinmembermbcrump5-Feb-11 14:40 
GeneralRe: My vote of 5. Pinmvpdefwebserver5-Feb-11 15:08 
QuestionCan it does Live streaming from multicast IP or UDP protocol ? PinmemberRajan UNCC20-Apr-10 6:29 
GeneralMy vote of 1 PinmemberVickyC#12-Apr-10 6:38 
GeneralRe: My vote of 1 PinmemberAlan Beasley12-Apr-10 9:04 
GeneralRe: My vote of 1 Pinmemberdefwebserver12-Apr-10 9:24 
GeneralRe: My vote of 1 PinmemberDewey12-Apr-10 13:31 
GeneralRe: My vote of 1 Pinmemberdefwebserver12-Apr-10 13:36 
GeneralRe: My vote of 1 [modified] PinmemberVickyC#30-Jan-11 5:59 
GeneralRe: My vote of 1 Pinmvpdefwebserver30-Jan-11 6:14 
GeneralRe: My vote of 1 PinmemberVickyC#24-Oct-11 16:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 8 May 2011
Article Copyright 2010 by defwebserver
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid