65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight 视频播放器轮播控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.18/5 (17投票s)

2008年11月26日

Ms-PL

5分钟阅读

viewsIcon

132930

downloadIcon

3672

VideoCarousel 是一个 Silverlight V2 控件,可以在交互式轮播中播放媒体。

Screen Capture 1

引言

几天前,我发表了一篇关于创建 Silverlight Carousel 控件的文章,你可以在 这里 阅读。在这篇文章中,我将创建一个新的轮播控件,用于播放视频或音频。我给它命名为 VideoCarouselVideoCarouselCarousel 非常相似,只是它可以播放视频或音频。如果你试用过 Carousel 控件,你可能已经知道 Carousel 有一个 Panel 来显示用户选择的图片(带有标题)。在 VideoCarousel 中,Panel 被替换为视频播放器控件。如果你想了解 Carousel 是如何工作的,你可能需要阅读 这篇文章

你可以将 VideoCarousel 控件用作网页中的视频播放器。它支持动态添加/删除媒体(视频或音频)信息到其集合中。媒体信息可以包括一张图片(你可以从电影中捕捉帧)、媒体所在的位置 URI、标题、导演姓名、主演姓名和制片人姓名。

当用户将鼠标悬停在 CarouselItem 上时,媒体信息将出现。当用户选择一个 CarouselItem 时,所选视频的画面将出现在选定的项目面板中,并且媒体将自动缓冲。如果媒体已准备好,用户可以播放它,或者如果你将 AutoPlay 属性设置为 true,媒体将自动播放。当媒体正在播放时,轮播菜单会自动消失。

鼠标悬停时显示媒体信息

Display media info when mouse over

视频播放时轮播隐藏

The carousel hide when video playing

背景

我创建了一个名为 Carousel 的 Silverlight V2 控件,它可以以交互式轮播方式显示图片集合。我喜欢这个控件,所以对其进行了一些升级,现在它可以作为视频播放器使用。

主要特点

  • Silverlight V2 控件,全部用 C# 编写。
  • 非常易于使用。
  • 下载媒体时自动显示加载动画。
  • 可以全屏模式播放视频。
  • 拥有一个非常吸引人的轮播作为视频菜单。

使用控件

使用 VideoCarousel 控件非常简单。在你的 Silverlight 应用程序中,添加对程序集的引用(程序集名称为 *Cokkiy.Display.VideoCarousel*)。然后,在你的页面的 XAML 文件中,执行以下操作:

在页面开头,添加一个 xmlns 引用

<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"
   >

然后,将 VideoCarousel 控件放置在你的布局控件(如 GridStackPanel)内

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

你也可以像这样设置其背景

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

如果你想在 XAML 文件中静态设置媒体信息,你可以这样做:

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

最后,关闭标签

   </display:VideoCarousel>
</Grid>

但在大多数情况下,你希望根据用户选择或刚刚观看的视频动态地将媒体信息添加到集合中。你可以订阅 SelectedItemChangedCurrentMediaStateChnaged 事件。SelectedItemChanged 事件在用户选择一个项目时发生,而 CurrentMediaStateChnaged 事件在媒体播放、暂停或停止时发生。以下代码向控件添加一部电影:

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

你也可能希望在用户观看完视频时移除该媒体。

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

有时,你可能不希望 Carousel 部分自动旋转。你只需将 AutoTurn 设置为 false 即可达到此效果。当 Carousel 不自动旋转时,Carousel 的两侧会出现一个按钮,用户可以使用这些按钮向左或向右转动。

videoCarousel4.JPG

在 XAML 文件中,你可以这样做:

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

或者你可以在代码中设置。

工作原理

在这里,我将不再重复 Carousel 的工作原理(你可以 这里 阅读它的工作原理)。这里的重点是视频播放器。视频播放器是一个 UserControl。这是 XAML(请注意,此处我省略了控件模板代码):

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

视频播放器的主要部分是一个控件面板,它控制 MediaElement 播放、暂停或停止媒体,并指示媒体的位置。视觉状态控件在控件面板上出现或消失。这个 XAML 文件没有更多有趣的内容可以说明了。

但在代码方面,有些事情需要说明。首先是如何跟踪媒体的当前位置。如果你一直在研究 MediaElement 控件,你会注意到播放位置变化时没有通知。所以,我创建了一个 Timer 对象来在指定的时间间隔内跟踪位置。

创建一个全局 Timer 对象

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

媒体播放时,启动计时器。

 /// <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);
         }
     }
}

TimerCallback 函数由 Timer 对象调用。它使用匿名委托来更新滑块的位置。

// 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"); ;
          });
}

第二个有趣的事情是当用户将鼠标悬停在播放器上时,控件面板会出现,如果用户鼠标不动,则会消失。

当用户移动鼠标时,我们使用 VisualState 来使控件面板出现。然后,我们开始倒计时以隐藏控件面板。

// 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();           
}

HideControlPanel 使用一个 DispatcherTimer 对象。在这里,我们使用 DispatcherTimer 对象是因为我们希望直接更新 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();
}

我已经详细介绍了 VideoCarousel 控件。如果你有任何问题或建议,请联系我。

历史

  • 2008 年 11 月 26 日:初次发布。
  • 2008 年 11 月 27 日:更新了源代码。修复了控件大小改变时播放器面板无法自动调整大小的错误。添加了两个事件:CurrentMediaStateChnagedSelectedItemChanged
  • 2008 年 11 月 27 日:更新了文章。
  • 2008 年 11 月 27 日:更新了源代码。修复了当 AutoTurn 设置为 false 时,旋转按钮未出现的错误。
© . All rights reserved.