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:
The carousel hides when a video is 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), new Uri("Videos/sl.wmv",UriKind.Relative), "Dynamic added SL VC1 Video", "Jo Coco", "Tom Crausel", "the Disney Product");
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)
{
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.
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>
-->
<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:
private Timer setPositionTimer;
When the media plays, start the timer.
private void PlayMedia()
{
mediaPlayer.Play();
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.
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.
private void LayoutRoot_MouseMove(object sender, MouseEventArgs e)
{
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
.
private DispatcherTimer hideControlPanelTimer;
private void HideControlPanel()
{
if (hideControlPanelTimer == null)
{
hideControlPanelTimer = new DispatcherTimer();
hideControlPanelTimer.Interval = TimeSpan.FromSeconds(10);
}
hideControlPanelTimer.Stop();
hideControlPanelTimer.Tick += delegate(object s, EventArgs args)
{
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.