Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Silverlight Video Player Carousel Control

0.00/5 (No votes)
22 Oct 2009 1  
VideoCarousel is a Silverlight V2 control that can play media in an interactive carousel.

Screen Capture 1

Introduction

A few days ago, I posted an article about creating a Silverlight Carousel control, which you can read here. In this article, I'll create a new carousel control which can be used to play video or audio. I named it VideoCarousel. The VideoCarousel is very similar to the Carousel except it can play video or audio. If you have tried the Carousel control, maybe you already know the Carousel has a Panel to display the user selected picture (with title). In VideoCarousel, the Panel is replaced by a video player control. If you want to know how the Carousel works, you may need to read this article.

You may use the VideoCarousel control as a video player in your web page. It supports dynamic add/remove of media (video or audio) info to its collection. The media info can include a picture (you can capture a frame from the film), a URI where the media is located, a title, the name of the director, the name of the main actors, and the name of the producer.

When the user moves the mouse over a CarouselItem, the media info will appear. When the user selects a CarouselItem, the selected video picture will appear in the selected item panel and the media will be auto buffered. If the media is ready, the user can play it, or if you set the AutoPlay property to true, the media will auto play. The carousel menu auto-disappears when the media is playing.

Display media info when mouse over:

Display media info when mouse over

The carousel hides when a video is playing:

The carousel hide when video playing

Background

I have created a Silverlight V2 control named Carousel which can display a collection of pictures in an interactive carousel. I liked the control, so I made some upgrades for it, so now it can be used as a video player.

Key Features

  • Silverlight V2 control, all written in C#.
  • Very easy to use.
  • Auto display a loading animation when the media is downloading.
  • Can play video in full screen mode.
  • Has a very attractive carousel as the video menu.

Using the Control

Using the VideoCarousel control is very simple. In your Silverlight application, add a reference to the assembly (the name of the assembly is Cokkiy.Display.VideoCarousel). Then, in your page's XAML file, do the following:

In the beginning of the page, add an xmlns reference:

<UserControl x:Class="VideoCarouselTest.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:display="clr-namespace:Cokkiy.Display;assembly=Cokkiy.Display.VideoCarousel"
   >

Then, place the VideoCarousel control inside your layout control, such as a Grid or StackPanel:

<Grid x:Name="LayoutRoot" Background="White">        
  <display:VideoCarousel x:Name="carousel" 
    TurnDirection="Counterclockwise" Padding="5,5,5,5" >

Also, you can set its background like this:

 <display:VideoCarousel.Background>
          <LinearGradientBrush EndPoint="0.5,0" StartPoint="0.5,1">
              <GradientStop Color="#FF000000"/>
              <GradientStop Color="#FFFFFFFF" Offset="1"/>
           </LinearGradientBrush>
 </display:VideoCarousel.Background>

If you want to set the media info static in your XAML file, you can do the following:

<display:VideoCarousel.ItemSources>
    <display:ItemSource Title="Sample Video" 
              Director="Unknown" Actors="Unknown" 
              Producer="Microsoft" 
              ImageSource="Images/01.jpg" 
              MediaSource="Videos/SampleVideo.wmv"/>
          <display:ItemSource Title="Sample" ImageSource="Images/02.jpg" 
                     MediaSource="Videos/Sample.wmv"/>
          <display:ItemSource Title="SL VC1 Video" ImageSource="Images/03.jpg" 
                    MediaSource="Videos/sl.wmv"/>
          <display:ItemSource Title="silverlight" ImageSource="Images/04.jpg" 
                     MediaSource="Videos/silverlight.wmv"/>
          <display:ItemSource Title="SampleVideo" ImageSource="Images/05.jpg" 
                       MediaSource="Videos/SampleVideo.wmv"/>
          <display:ItemSource Title="Sample" ImageSource="Images/06.jpg" 
                       MediaSource="Videos/Sample.wmv"/>
          <display:ItemSource Title="sl" ImageSource="Images/07.jpg" 
                       MediaSource="Videos/sl.wmv"/>
          <display:ItemSource Title="silverlight" ImageSource="Images/08.jpg" 
                      MediaSource="Videos/silverlight.wmv"/>
          <display:ItemSource Title="SampleVideo" ImageSource="Images/09.jpg" 
                      MediaSource="Videos/SampleVideo.wmv"/>
          <display:ItemSource Title="silverlight" ImageSource="Images/10.jpg" 
                       MediaSource="silverlight.wmv"/>
    <display:ItemSource Title="Error created" ImageSource="Images/11.jpg"/>
</display:VideoCarousel.ItemSources>

In the end, close the tag:

   </display:VideoCarousel>
</Grid>

But in most situations, you want to dynamically add media info into the collection based on user selection or the video just watched. You can subscribe to the SelectedItemChanged or CurrentMediaStateChnaged event. The SelectedItemChanged event occurs when the user selects an item, and the CurrentMediaStateChnaged event occurs when the media is playing, paused, or stopped. The following code adds a film to the control:

carousel.ItemSources.Add(
     new Uri("Images/25.jpg", UriKind.Relative), // a picture from the film
     new Uri("Videos/sl.wmv",UriKind.Relative), // the media Uri addresss
     "Dynamic added SL VC1 Video", // Film title
     "Jo Coco", // the director
     "Tom Crausel", // the main actor
     "the Disney Product"); // the producer

You also want to remove the media the user just watched when the media ends.

void carousel_CurrentMediaStateChnaged(object sender, MediaStateChangedEventArgs e)
{
      if (e.MediaState == MediaState.Ended)
      {
          // remove the media user just watched
          carousel.ItemSources.Remove(e.Title);
      }
}

Some times, you may want the Carousel part not to auto turn. You can simply set AutoTurn to false to get this effect. When the Carousel not auto turning, a button will appear on each side of the Carousel, and the user can use the buttons to turn left or right.

videoCarousel4.JPG

In the XAML file, you can do this:

<display:VideoCarousel x:Name="carousel" AutoTurn="False" 
     TurnDirection="Counterclockwise" Padding="5,5,5,5" >

Or you can set it in code.

How it Works

Here, I won't repeat how the Carousel works (you can read how it works here). The main point here is the video player. The video player is a UserControl. Here is the XAML (note that here I have omitted the control template code):

<UserControl x:Class="Cokkiy.Display.VideoPlayer"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
  xmlns:mwc="clr-namespace:Microsoft.Windows.Controls;assembly=Cokkiy.Common.Controls"
  xmlns:cc="clr-namespace:Cokkiy.Common.Controls;assembly=Cokkiy.Common.Controls"
  xmlns:resources="clr-namespace:Cokkiy.Display.Resources"
  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
  >
  <Grid x:Name="LayoutRoot" 
         HorizontalAlignment="Stretch" 
         VerticalAlignment="Stretch" 
         MouseLeave="LayoutRoot_MouseLeave" 
         MouseMove="LayoutRoot_MouseMove" 
         Opacity="1" Background="Transparent">
     <vsm:VisualStateManager.VisualStateGroups>
    <vsm:VisualStateGroup x:Name="PositionStates">
        <vsm:VisualState x:Name="SetPositionState">
                  <Storyboard>
                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
                        Duration="00:00:00.0010000" 
                        Storyboard.TargetName="positionSlider" 
                        Storyboard.TargetProperty="(UIElement.Opacity)">
                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
                </DoubleAnimationUsingKeyFrames>
                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
                           Duration="00:00:00.0010000" 
                             Storyboard.TargetName="_base" 
                             Storyboard.TargetProperty="(UIElement.Opacity)">
                 <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
                         Duration="00:00:00.0010000" 
                           Storyboard.TargetName="position" 
                                Storyboard.TargetProperty="(UIElement.Opacity)">
                 <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                </DoubleAnimationUsingKeyFrames>
                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
                         Duration="00:00:00.0010000" 
                         Storyboard.TargetName="bufferPosition" 
                         Storyboard.TargetProperty="(UIElement.Opacity)">
                 <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>
            </vsm:VisualState>
        <vsm:VisualState x:Name="ShowPositionState"/>
     </vsm:VisualStateGroup>
     <vsm:VisualStateGroup x:Name="ControlStates">
      <vsm:VisualStateGroup.Transitions>
        <vsm:VisualTransition GeneratedDuration="00:00:02" 
               To="ShowControlPanelState"/>
        <vsm:VisualTransition From="ShowControlPanelState" 
               GeneratedDuration="00:00:02"/>
      </vsm:VisualStateGroup.Transitions>
      <vsm:VisualState x:Name="ShowControlPanelState">
         <Storyboard>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
                     Storyboard.TargetName="controlBorder" 
                     Storyboard.TargetProperty="(UIElement.Opacity)">
                <SplineDoubleKeyFrame KeyTime="00:00:01" Value="0.8"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
      </vsm:VisualState>
      <vsm:VisualState x:Name="HideControlPanelState"/>
     </vsm:VisualStateGroup>
     </vsm:VisualStateManager.VisualStateGroups>
     <Image x:Name="frameImage" 
         HorizontalAlignment="Center" VerticalAlignment="Center" 
         Stretch="Uniform" />
     <Grid x:Name="mediaBackground" 
               Background="Black" Visibility="Collapsed">
          <StackPanel MouseLeftButtonUp="mediaPlace_MouseLeftButtonUp" 
                 x:Name="mediaPlace" 
                 VerticalAlignment="Center" 
                 HorizontalAlignment="Center">
              <MediaElement AutoPlay="False" x:Name="mediaPlayer"/>
          </StackPanel>
     </Grid>
     <Border CornerRadius="30,30,0,30" 
                  VerticalAlignment="Bottom" Background="Black" 
                  x:Name="controlBorder" Opacity="0">
         <Grid x:Name="controlGrid" Opacity="0.7" 
                   HorizontalAlignment="Stretch" 
                   VerticalAlignment="Bottom" 
                   Background="Transparent">
             <Grid.ColumnDefinitions>
                 <ColumnDefinition Width="Auto"/>
                 <ColumnDefinition Width="*"/>
             </Grid.ColumnDefinitions>
             <cc:PlayPauseButton x:Name="playPauseButton" 
                 IsEnabled="False" Width="60" Height="60" 
                 Click="playPauseButton_Click"/>
             <StackPanel Grid.Column="1">
                 <TextBlock x:Name="titleTextBlock" 
                        HorizontalAlignment="Center" 
                        VerticalAlignment="Center" 
                        FontSize="16" Foreground="White" 
                        Text="Video Tile" TextWrapping="Wrap"/>
                 <mwc:DockPanel LastChildFill="True">
                    <Button Margin="0,0,5,0" 
                       mwc:DockPanel.Dock="Left" x:Name="stopButton" 
                       Click="stopButton_Click" 
                       Style="{StaticResource StopButtonStyle}"/>
                    <StackPanel Orientation="Horizontal" 
                            mwc:DockPanel.Dock="Right">
                       <Slider Width="100" Maximum="1" 
                          Value="0.5" LargeChange="0.05" SmallChange="0.01" 
                          Style="{StaticResource EllipseSliderStyle}" 
                          x:Name="volumeSlider" 
                          ValueChanged="volumeSlider_ValueChanged"/>
                       <ToggleButton Margin="0,0,5,0" VerticalAlignment="Center" 
                                 Style="{StaticResource muteToggleButtonStyle}" 
                                 x:Name="muteButton" 
                                 Checked="muteButton_Checked" 
                                 Unchecked="muteButton_Unchecked"/>
                    </StackPanel>
                    <!--Position TraackBar and Text-->
                    <StackPanel x:Name="positionPanel" 
                              MouseEnter="positionPanel_MouseEnter" 
                              MouseLeave="positionPanel_MouseLeave" 
                              HorizontalAlignment="Stretch">
                        <Grid>       
                           <Rectangle Fill="#FF7C7A7A" 
                                   Height="5" VerticalAlignment="Center" 
                                  x:Name="_base" 
                                          SizeChanged="_base_SizeChanged" 
                                               HorizontalAlignment="Stretch"/>
                           <Rectangle Fill="#FFD7F90E" x:Name="bufferPosition" 
                                       VerticalAlignment="Center" 
                                       HorizontalAlignment="Left" 
                                       Height="5" Width="0"/>
                           <Rectangle Fill="#FFFAFAFB" 
                              Height="5" VerticalAlignment="Center" 
                              HorizontalAlignment="Left" 
                              Width="0" x:Name="position"/>
                           <Slider Maximum="1" 
                                LargeChange="0.05" SmallChange="0.01" 
                                mwc:DockPanel.Dock="Right" 
                                Style="{StaticResource EllipseSliderStyle}" 
                                IsEnabled="False" x:Name="positionSlider" 
                                ValueChanged="positionSlider_ValueChanged" 
                                Opacity="0"/>
                         </Grid>
                         <mwc:DockPanel LastChildFill="False">
                             <TextBlock Foreground="White" 
                                     mwc:DockPanel.Dock="Right" 
                                     x:Name="mediaDurantionTextBlock" 
                                     Text="unknown"/>
                             <TextBlock Foreground="White" 
                                     mwc:DockPanel.Dock="Right" 
                                     Text="/"/>
                             <TextBlock Foreground="White" 
                                     mwc:DockPanel.Dock="Right" 
                                     x:Name="mediaPositionTextBlock" 
                                     Text="00:00:00"/>                                
                         </mwc:DockPanel>
                    </StackPanel>
                  </mwc:DockPanel>
              </StackPanel>
          </Grid>
      </Border>
      <Grid HorizontalAlignment="Center" x:Name="loadingInfo" 
           VerticalAlignment="Center" 
           Visibility="Collapsed">
      <-- Omit the loading animation control -->
   </Grid>
</Grid>

The main part of the video player is a control panel which controls the MediaElement to play, pause, or stop the media and indicate the position of the media. The visual state controls appear or disappear on the control panel. There are no more interesting things to say about this XAML file.

But in code, something needs to be said. The first is how we track the current position in the media. If you have been studying the MediaElement control, you will notice that there is no notification when the playing position changes. So, I created a Timer object to track the position at specified intervals.

Create a global Timer object:

/// <summary>
/// A timer object to set the current position
/// </summary>
private Timer setPositionTimer;

When the media plays, start the timer.

 /// <summary>
///  Play the media
/// </summary>
private void PlayMedia()
{
     mediaPlayer.Play();
         
     // omit other code here

     // if can seek, show play position
     if (mediaPlayer.CanSeek)
     {
         if (setPositionTimer == null)
         {
               setPositionTimer = new Timer(this.UpdatePosition, null, 0, 500);
         }
     }
}

The TimerCallback function is called by the Timer object. It uses an anonymous delegate to update the position of the track bar.

// Update the play position
private void UpdatePosition(object state)
{
    positionSlider.Dispatcher.BeginInvoke(
          delegate()
          {
               double value = mediaPlayer.Position.TotalSeconds
                             / mediaPlayer.NaturalDuration.TimeSpan.TotalSeconds;
               position.Width = value * _base.ActualWidth;

               mediaPositionTextBlock.Text = 
                 mediaPlayer.Position.TotalMinutes.ToString("F2"); ;
          });
}

The second interesting thing is when the user moves the mouse over the player, the control panel will appear and then disappear if the user holds the mouse still.

When the user moves the mouse, we use the VisualState to make the control panel appear. And, we start the down-count to hide the control panel.

// Mouse move in the layout root panel
private void LayoutRoot_MouseMove(object sender, MouseEventArgs e)
{
  // If mouse move, we show the control panel, then wait a minute 
  // we hide the control panel
  VisualStateManager.GoToState(this, "ShowControlPanelState", true);
  this.Cursor = null;
  HideControlPanel();           
}

The HideControlPanel uses a DispatcherTimer object. Here, we use the DispatcherTimer object because we want to directly update the Visual.

/// <summary>
/// A timer when it's occured, the control panel will be hide
/// </summary>
private DispatcherTimer hideControlPanelTimer;

/// <summary>
/// Hide the control panel when a time out
/// </summary>
private void HideControlPanel()
{
      if (hideControlPanelTimer == null)
      {
          hideControlPanelTimer = new DispatcherTimer();
          hideControlPanelTimer.Interval = TimeSpan.FromSeconds(10);
      }
      hideControlPanelTimer.Stop();
      hideControlPanelTimer.Tick += delegate(object s, EventArgs args)
      {
           // when the time out, hide the control panel
           VisualStateManager.GoToState(this, "HideControlPanelState", true);
           this.Cursor = Cursors.None;
           hideControlPanelTimer.Stop();
      };
     hideControlPanelTimer.Start();
}

I have said so much about the VideoCarousel control. If you have any problems or advice, please contact me.

History

  • 26 November, 2008: Initial post.
  • 27 November, 2008: Updated the source code. Fixed a bug when the control size changes, the player panel can't resize itself. Added two events: CurrentMediaStateChnaged and SelectedItemChanged.
  • 27 November, 2008: Updated article.
  • 27 November, 2008: Updated source code. Fixed a bug when setting AutoTurn to false, the turning button did not appear.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here