Click here to Skip to main content
15,860,844 members
Articles / Desktop Programming / XAML

Silverlight Advanced View Model Style Video Player

Rate me:
Please Sign up or sign in to vote.
4.91/5 (35 votes)
16 May 2010Ms-PL12 min read 83.1K   4K   48   17
A full featured Silverlight View Model Style Video Player

imgFwdRwd.jpg

An Advanced Video player

Live example: http://silverlight.adefwebserver.com/

This is part 2 to the article Silverlight 4 Video Player. That article focused on the View Model Style (Model-View-ViewModel) pattern, and how it supports Designer / Developer collaboration and workflow. However, when others tried to use that project for a real website, they found they needed the full set of controls that a video player normally provides. I decided to use this as an opportunity to dig deeper into View Model Style and demonstrate how easy it truly is, when using Microsoft Expression Blend 4 (or higher). This time we will cover the "hard stuff". However, implementing this will be surprisingly easy.

This project creates 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 existing controls of the Video Player. A Designable player, allows a Designer to use ANY set of controls to implement the Video Player. For example, a Designer could replace all the buttons with a single dial that a user rotated into different positions, or they could change the method a user uses to select from the list of available videos. This can be done in Expression Blend without writing any code.

5/16/2010 Update: Victor Gaudioso has provided example code that demonstrates how to fast forward and rewind video. His code has been adapted for use in this project. His original article is found here: http://victorgaudioso.wordpress.com/2010/05/15/new-silverlight-video-tutorial-how-to-create-fast-forward-for-the-mediaelement/

Additional Downloads

You will also need to download and install the Silverlight Toolkit: http://silverlight.codeplex.com/

View Model Style (An overview)

Image 2

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).

Yes, you could just put your code in the code behind (for example, you place a button on a page and then double-click on it, and it wires up an event handler in the code behind), but then a Designer could only use a button for that task. They could not change the element to a Dial. Most importantly, the Designer would need to know a bit about coding because they would be directly interacting with the code. With View Model Style (or Data Driven Application as it is now referred to in the Expression Blend templates), basically:

  • The Programmer creates code that consist of Properties, Collections, and Commands.
  • The Designer uses Expression Blend, and creates the complete UI, without writing any code.

The Model is where the data goes, usually this is a web service. The View is the UI, or the "front end". The ViewModel is where the "magic" happens. It consists of:

  • 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.
  • Collections – A collection of something. This is of type ObservableCollection, so that any element bound to it, is automatically notified whenever it changes.
  • Commands – An event that can be raised. Also, one parameter of type Object can be passed. This implements ICommand.

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.

The Starter Solution

Image 3

We will start with the solution contained in the MVVMVideoPlayer_Starter.zip file. When you run the project, the web service in the web project, will detect what videos are in the "Video" folder, and pass them to the Silverlight application.

  • The list of videos will show in the combo box dropdown.
  • 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 Play button will play the video
  • The Stop button will stop the video.

This is what the Silverlight 4 Video Player article covered. To make a full featured video player, we need to add the following features:

  • Volume control - A user needs the ability to change the volume of the video.
  • Pause button - A user needs to be able to pause the video and resume play by clicking the pause button again, or by clicking the play button.
  • "Seek" control - A user should be able to easily skip forward and back through a video even while the video is playing.
  • "Video Buffering" notification - When the video is loading, a notification needs to appear, and indicate how much longer it will take for the video to start playing.
  • Full Screen Video - When viewing a video, it is nice to have the option to click a button and see the video full screen. You need to be able to switch back and forth seamlessly. Also, only the video should be full screen, not the entire Silverlight application.

We will implement the code for all of this by only adding code to the ViewModel. the Model is not changed at all. The code is already in the MVVMVideoPlayer_Starter.zip file (it will be covered in detail at the end of this article). For now, we will start with the steps the Designer would use to implement this additional functionality. To start, the Designer opens the project up in Expression Blend...

The Volume Control

Image 4

This control actually does two things:

  • Indicates what the current volume is
  • Allows the volume to be changed

Image 5

In Expression Blend, in Assets, get a Slider control.

Image 6

Drop it in the [StackPanel] in the Objects and Timeline window.

Image 7

Click the "V" key on the keyboard to switch to the Selection tool. Then on the design surface, use the mouse to widen the slider a bit.

Image 8

In the Properties for the Slider, set the left Margin to 5

Image 9

Set the Maximum level to 1 and Minimum to 0 (the volume on a Media Element actually goes only from 0 to 1. For example it starts at 0.5.)

Image 10

Click the Advanced options box next to Value (in Common Properties).

Image 11

Select Data Binding...

Image 12

  • Select the Element Property tab
  • Select mediaElement in the Scene elements section
  • Select Volume in the Properties section
  • Select TwoWay for Binding direction
  • Click the OK button

Image 13

Hit F5 to build and run the project. The web browser will open and you will now be able to control the volume.

The Pause Button

Image 14

Again, there is more than meets the eye:

  • When you click Pause the video should stop
  • If the video is paused and you click Pause again, it should continue from where it left off
  • If the video is paused and you click Play, it should still just continue from where it left off

Image 15

In Assets, get a Button control.

Image 16

Drop it between the PlayButton and the StopButton, in the Objects and Timeline window. Right-click on it and rename it to "PauseButton".

Image 17

In the Properties, set the Content to "Pause" and set the left Margin to 5.

Image 18

In the Assets, get an InvokeCommand behavior.

Image 19

Drop it under the PauseButton (remember you renamed it in the earlier step), in the Objects and Timeline window.

Image 20

In the Properties for the behavior, select Data bind next to Command.

Image 21

Select PauseVideoCommand in the Data Context window, and click the OK button.

Image 22

Hit F5 to build and run the project. The web browser will open and you will now be able to Pause the video.

The Seek Control

Image 23

We want to provide the ability to skip forward and backward while the video is playing. This functionality can be implemented using any control. For example, you could use a Slider control or a custom dial control. In this example, we will just use the ProgressBar control. The ProgressBar control will still display the progress, but when you click on the control, it will navigate to the section of the video.

Image 24

In the Assets, get an InvokeCommandAction behavior.

Drop it under the mediaElement in the Objects and Timeline window. Right-click on it and rename it to "SetSeekControl".

Image 25

In the Properties for the behavior:

  • Set the EventName to MediaOpened (this event is fired automatically when a video is set as a source for the MediaElement and it has found the video and is ready to play it).
  • Click the Data bind button next to Command.

Image 26

Bind it to SetSeekControlCommand.

Image 27

Click the Advanced options box next to CommandParameter.

Image 28

  • Select the Element Property tab
  • Select progressBar in the Scene elements section
  • Select ProgressBar in the Properties section
  • Click the OK button

Image 29

Hit F5 to build and run the project. The web browser will open and you will now be able to skip to parts of the video by clicking on the ProgressBar control.

Video Buffering Notification

Image 30

When the MediaElement is playing a video, it loads a bit of the video "ahead" of the part that is playing. If it is not able to load the video fast enough, it will stop and "buffer" the video and then resume playing after buffering is complete (when buffering has reached 100%). We want the buffering notification to perform the following functions:

  • Display when the video is being buffered
  • Disappear when the video is not being buffered
  • Display the percentage that it has buffered, in real time, as it is being buffered

Image 31

In Assets, search for the BusyIndicator.

Image 32

Drag it to the design surface and place it over the MediaElement.

Image 33

In the Properties for the BusyIndicator:

  • Clear BusyContent
  • Clear Content

Image 34

Bind BusyContent to MediaBufferingTimeProperty (this displays the buffering progress).

Image 35

Bind the IsBusy property to MediaBufferingProperty (this controls when the buffering box displays).

Image 36

You will know you have bound the properties because they will have a gold box around them.

Full Screen Video

Image 37

For the full screen video functionality we want the following things:

  • When in full screen mode, we want to only see the video, not the other controls
  • When we switch back and forth, we want the video to continue playing seamlessly
  • We want the video to go to full screen, if we click on the video, or the Full Screen button

Switching a Silverlight application to full screen mode is very simple. Switching only one element of a Silverlight application, involves a few extra steps. We need to create a Grid, then instruct the ViewModel to use this Grid when going into full screen mode. The ViewModel will automatically create a VideoBrush and set it's source to the MediaElement so that the video stays in sync in both modes.

Image 38

Ion the Panel section on the Tools bar, select Grid.

Image 39

Double-click on Grid to insert it onto the design surface.

Image 40

in the Properties for the Grid:

  • Set the Width and Height to Auto
  • Set the RowSpan to 2
  • Set HorizontalAlignment and VerticalAlignment to Stretch

Image 41

Drag and drop an InvokeCommand behavior under the MediaElement and rename it to "FullScreen".

Image 42

In the Properties for the behavior, bind the Command to the SetFullScreenCommand.

Image 43

Bind the CommandParameter to the [Grid].

This will enable full screen mode when you click on the video. Now to trigger a return to normal mode...

Image 44

Drag and drop an InvokeCommand behavior under LayoutRoot in the Objects and Timeline window.

Image 45

In the Properties for the behavior:

  • Set the EventName to SizeChanged
  • Bind the Command to ExitFullScreenCommand
  • Bind the Grid to CommandParameter

Hit F5 to build and run the project. When you click on the video it will switch to full screen mode.

Return to Expression Blend and perform the following functions to create a Full Screen button:

  • Place a button next to the Stop button and set it's content to "Full Screen"
  • Set the left Margin to 5
  • Drag and drop an InvokeCommand behavior under it
  • In the Properties for the behavior, bind the Command to the SetFullScreenCommand
  • Bind the CommandParameter to the [Grid]

The Code

- Designers can skip to the end, there is nothing to see here :) -

Let's look at the relevant code used to implement each section...

Volume

There is no code! The Slider control is bound directly to the Volume property of the MediaElement. I know... Wow!

Pause

In the constructor of the ViewModel, all the ICommands are set-up including the PauseVideoCommand:

public MainViewModel()
{
    // Set the command property
    MediaOpenedCommand = new DelegateCommand(MediaOpened, CanMediaOpened);
    PlayVideoCommand = new DelegateCommand(PlayVideo, CanPlayVideo);
    StopVideoCommand = new DelegateCommand(StopVideo, CanStopVideo);
    PauseVideoCommand = new DelegateCommand(PauseVideo, CanPauseVideo);
    SetSeekControlCommand = new DelegateCommand(SetSeekControl, CanSetSeekControl);
    SetVideoCommand = new DelegateCommand(SetVideo, CanSetVideo);
    SetFullScreenCommand = new DelegateCommand(SetFullScreen, CanSetFullScreen);
    ExitFullScreenCommand = new DelegateCommand(ExitFullScreen, CanExitFullScreen);

    // Call the Model to get the collection of Videos
    GetListOfVideos();
}

The rest of the implementation is below. Basically it simply calls Pause() on the MediaElement. The rest of the code is handling if it's ok to pause, if it can pause, and if it is paused already, should it play instead.

#region PauseVideoCommand
 public ICommand PauseVideoCommand { get; set; }
 public void PauseVideo(object param)
 {
     // We only want to Pause if the media is Playing
     if (MyMediaElement.CurrentState == MediaElementState.Playing)
     {
         // If we can Pause the Video, Pause it
         if (MyMediaElement.CanPause)
         {
             // Pause Video
             MyMediaElement.Pause();
         }
         else
         {
             // We can't pause the Video so Stop it
             MyMediaElement.Stop();
         }

         if (progressTimer.IsEnabled)
         {
             progressTimer.Stop();
         }
     }
     else
     {
         // The Media is not Playing so we are Paused
         // Play Video
         MyMediaElement.Play();
         progressTimer.Start();
     }

 }

 private bool CanPauseVideo(object param)
 {
     bool CanPause = false;

     if (MyMediaElement != null)
     {
         // Only allow this Command if paused or Playing
         if ((MyMediaElement.CurrentState == MediaElementState.Paused)
             || (MyMediaElement.CurrentState == MediaElementState.Playing))
         {
             CanPause = true;
         }
     }

     return CanPause;
 }
 #endregion

Seek

This one is a bit interesting, Basically you first pass in a FrameworkElement that will be used to perform the Seek. Mouse events are attached to this Framework element, so that an event is raised if a person clicks on the element. When the event is raised, the width of the element is used to determine where they clicked on the element. That value is used to determine where to navigate in the video.

#region SetSeekControlCommand
public ICommand SetSeekControlCommand { get; set; }
public void SetSeekControl(object param)
{
    // Hook Events into the Seek Control
    SeekControl = (FrameworkElement)param;
    SeekControl.MouseLeftButtonDown += new MouseButtonEventHandler(SeekControl_MouseLeftButtonDown);
}

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

private void SeekControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    double position = e.GetPosition(SeekControl).X;
    double percent = position / SeekControl.ActualWidth;
    Seek(percent);
}

private void Seek(double percentComplete)
{
    TimeSpan duration = MyMediaElement.NaturalDuration.TimeSpan;
    int newPosition = (int)(duration.TotalSeconds * percentComplete);
    MyMediaElement.Position = new TimeSpan(0, 0, newPosition);
    SetCurrentPosition();
}
#endregion

Video Buffering

A DispatcherTimer is used to check the progress of the Video each second, and update properties, including the buffering properties:

#region Time display
private void ProgressTimer_Tick(object sender, EventArgs e)
{
    SetCurrentPosition();
}

private void SetCurrentPosition()
{
    // If the Media play is complete stop the media
    if (CurrentPositionProperty > 0)
    {
        if (CurrentPositionProperty >= TotalDurationProperty)
        {
            // If in full screen mode - exit full screen mode
            var content = Application.Current.Host.Content;
            if (content.IsFullScreen)
            {
                content.IsFullScreen = false;
            }

            CurrentPositionProperty = 0;
            StopVideo(null);
        }
    }

    // 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;
    MediaBufferingProperty = (MyMediaElement.CurrentState == MediaElementState.Buffering);
    MediaBufferingTimeProperty = String.Format("Buffering {0} %", (MyMediaElement.BufferingProgress * 100).ToString("##"));
}
#endregion

Note: In hindsight, it may be possible to implement this entire example without using a DispatcherTimer. An InvokeAction behavior on the MediaElement may be all that is required to update the properties... oh well, perhaps next time.

Full Screen Video

The Designer must specify a Grid to be used for full screen mode. This Grid must be set to stretch and fill the entire screen, blocking out the rest of the application (so yes, the rest of the application is actually still behind the Grid in full screen mode).

The code dynamically places a VideoBrush in the Grid and sets it's source to the MediaElement so that the video will stay in sync. It is possible to put other elements in the Grid, so that you could, for example, show player controls when the mouse is moved.

#region SetFullScreenCommand
public ICommand SetFullScreenCommand { get; set; }
public void SetFullScreen(object param)
{
    if (MyMediaElement != null)
    {
        // Put application in full screen mode
        var content = Application.Current.Host.Content;
        content.IsFullScreen = true;

        // Set the Video Brush to the content of the MediaElement
        VideoBrush objVideoBrush = new VideoBrush();
        objVideoBrush.SetSource(MyMediaElement);
        objVideoBrush.Stretch = Stretch.UniformToFill;

        // A Grid to show in full screen needs to be passed as the parameter
        // Set the background content of that panel to the VideoBrush

        // Note: Other elements and controls can be placed on this Grid
        // It does not have to be blank
        Grid objGrid = (Grid)param;
        objGrid.Visibility = Visibility.Visible;
        objGrid.Background = objVideoBrush;
    }
}

private bool CanSetFullScreen(object param)
{
    // Only allow full screen if not in full screen
    var content = Application.Current.Host.Content;
    return (!content.IsFullScreen);
}
#endregion

This is the code when you exit full screen:

#region ExitFullScreenCommand
public ICommand ExitFullScreenCommand { get; set; }
public void ExitFullScreen(object param)
{
    // A Panel to show in full screen needs to be passed as the parameter
    // Set this element to invisible
    Panel objPanel = (Panel)param;
    objPanel.Visibility = Visibility.Collapsed;
}

private bool CanExitFullScreen(object param)
{
    // Only allow exit full screen if not in full screen
    // This may seem odd, but this command is being called by a SizeChanged event
    // If we ARE in full screen then we were actually just recently NOT in full screen
    // We only want to fire the ExitFullScreen if we were actually just In full screen
    // but NOT in full screen any more
    var content = Application.Current.Host.Content;
    return (!content.IsFullScreen);
}
#endregion

"Pimp" This Video Player, Please!

Are you a Designer, or do you want to be? Download this project and come up with a cool design. The code is Open Source so you can redo the application and post a tutorial or blog showing what you create. To learn how to use Expression Blend, all you have to do is go to:

http://www.microsoft.com/design/toolbox/

License

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


Written By
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
AiHelpWebsite.com,
LightSwitchHelpWebsite.com, and
HoloLensHelpWebsite.com.

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

He is the Author of:

Comments and Discussions

 
GeneralMy vote of 5 Pin
aofeng316-Apr-11 22:40
aofeng316-Apr-11 22:40 
GeneralMy vote of 5 Pin
aofeng316-Apr-11 22:34
aofeng316-Apr-11 22:34 
GeneralMy vote of 5 Pin
lapin.panda8-Nov-10 4:28
lapin.panda8-Nov-10 4:28 
GeneralMy vote of 4 Pin
RPNPSP2-Nov-10 2:48
RPNPSP2-Nov-10 2:48 
GeneralMy vote of 5 Pin
Rabid Woo20-Oct-10 12:07
Rabid Woo20-Oct-10 12:07 
GeneralIt is really cool .. Pin
Sushant Joshi8-Sep-10 3:44
Sushant Joshi8-Sep-10 3:44 
GeneralRe: It is really cool .. Pin
defwebserver8-Sep-10 3:52
defwebserver8-Sep-10 3:52 
GeneralGreat Article Pin
linuxjr15-May-10 22:10
professionallinuxjr15-May-10 22:10 
GeneralRe: Great Article Pin
defwebserver16-May-10 3:20
defwebserver16-May-10 3:20 
GeneralOuter streaming Pin
mikachi30-Apr-10 2:05
mikachi30-Apr-10 2:05 
GeneralRe: Outer streaming Pin
defwebserver30-Apr-10 2:50
defwebserver30-Apr-10 2:50 
GeneralCool article Pin
Josh Smith26-Apr-10 12:55
Josh Smith26-Apr-10 12:55 
GeneralRe: Cool article Pin
defwebserver26-Apr-10 12:59
defwebserver26-Apr-10 12:59 
GeneralRe: Cool article Pin
Rozis15-May-10 22:53
Rozis15-May-10 22:53 
GeneralRe: Cool article Pin
defwebserver16-May-10 3:19
defwebserver16-May-10 3:19 
GeneralGreat Article on showing idiot Designers how easy it can be to work alongside a Developer!!! Pin
Alan Beasley19-Apr-10 3:58
Alan Beasley19-Apr-10 3:58 
GeneralRe: Great Article on showing idiot Designers how easy it can be to work alongside a Developer!!! Pin
defwebserver19-Apr-10 4:04
defwebserver19-Apr-10 4:04 

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

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