Silverlight本身提供了多媒体播放控件,但并没有封装好,可以直接使用的控件。在网上搜索了一些,都不是很适用,有些过于复杂要引用一大堆dll,感觉很臃肿,有些样式风格不适合。silverlight只提供了MediaElement,并不像以前html那样现成的直接使用那么方便,所以就自己封装一下,做一个满足基本功能的简单播放器。通过本篇随笔认识一下Blend强大的修改控件样式魔力,和实现一个简单的播放器。
功能点:
1、播放、暂停及显示当前播放状态
2、实时显示已播放时间
3、播放进度条,并能拖动播放位置
4、全屏按钮及双击播放画面入或退出全屏
5、调整音量
6、播放列表
播放器的基本功能点就是需求,将需求分解,罗列出实现难点和功能要点,评估工作量及风险。
一、认识MediaElement控件
使用到的重要属性:
public MediaElementState CurrentState { get; }
MediaElement 的当前状态。状态可以为下列值之一(如在 MediaElementState 枚举中所定义):Buffering、Closed、Opening、Paused、Playing 或 Stopped。
默认值为 Closed。
public bool AutoPlay { get; set; }
如果自动播放,则为 true;否则为 false。默认值为 true。如果设置 Source 属性前将此属性设置为 true,则设置Source属性时自动播放视频。
public Uri Source { get; set; }
获取或设置 MediaElement 上的媒体来源。即指定一个视频的统一资源标识符 (URI) 字符串。
public double Volume { get; set; }
获取或设置媒体的音量大小。
使用到的重要事件(非运行代码):
//当媒体流已被验证和打开且已读取文件头时发生。在该自定义控件中主要通过该事件获取视频的总时长。 public event RoutedEventHandler MediaOpened void mediaElement_MediaOpened(object sender, RoutedEventArgs e) { this.playTools.TotaPlayTime = (int)this.mediaElement.NaturalDuration.TimeSpan.TotalSeconds; } //当 MediaElement 不再播放音频或视频时发生。在该自定义控件中主要通过该事件设置MediaElement为Stop,并判断是否循环播放而进行继续循环播放。 public event RoutedEventHandler MediaEnded void mediaElement_MediaEnded(object sender, RoutedEventArgs e) { this.mediaElement.Stop(); if (this.IsReplay) { this.mediaElement.Play(); } } //当 CurrentState 属性的值更改时发生。在该自定义控件中主要通过该事件显示当前视频播放状态信息。 public event RoutedEventHandler CurrentStateChanged if (this.mediaElement.CurrentState == MediaElementState.Buffering) { this.playTools.CurrentMessage = this.mediaElement.CurrentState + " " + Math.Round(this.mediaElement.BufferingProgress * 100, 0).ToString() + "%"; } //在存在与媒体 Source 关联的错误时发生。MediaFailed 事件可在下列条件下发生:1、未找到文件。2、无效的(无法识别的或不支持的)媒体格式。3、播放期间未知的媒体错误。 //在该自定义控件中主要通过该事件显示错误信息。 public event EventHandler<ExceptionRoutedEventArgs> MediaFailed void mediaElement_MediaFailed(object sender, ExceptionRoutedEventArgs e) { this.playTools.CurrentMessage = e.ErrorException.Message; } //该事件是播放时发生,用于获取当前已播放时间 CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering); void CompositionTarget_Rendering(object sender, EventArgs e) { int currentTime = (int)this.mediaElement.Position.TotalSeconds; this.playTools.CurrentPlayTime = currentTime; }
其他支持的媒体格式、协议和日志字段,请查看帮助文档,更详细更清楚。
二、分解播放器元素
可以将播放器分为视频显示区和控制条两部分。
视频显示区比较简单,就只有一个MediaElement控件就可以了。
控制条部分较为复杂,我们再进一步分解,并分析各个按钮的功能特性,选择合适的控件修改其样式。
1、实时播放进度条。获取视频总时长和当前播放时长,可以计算出进度百分比,在silverlight控件中,ProgressBar可以通过设置Value属性显示进度,所以ProgressBar 控件很适合作为播放进度的显示,只需要修改一下样式就可以了,不需要编写其他代码来实现;
2、播放、暂停按钮。这是一个互斥的操作,同一时间只有一种状态,在silverlight控件中CheckBox和ToggleButton都有这样的特性,每点击一次都切换一种状态,所以选择其中一个控件都可以。本人更倾向于ToggleButton,可能是因为比CheckBox的名字更容易于理解,不过都无所谓。
3、停止按钮。只有一种状态,所以用Button控件就可以了,不作多解释。
4、当前时间、总时长、播放状态、视频名称 。使用TextBlock就可以了,不作多解释。
5、播放列表按钮。点击显示视频列表,再点击隐藏,有两个状态进行切换,所以选择ToggleButton控件。
6、全屏按钮。点击全屏,再点击退出全屏,两个状态进行切换,所以选择ToggleButton控件。
7、音量调整按钮。是最复杂的一个按钮,点击小喇叭图标,弹出调整阀值条,点击播放器的其他地方,弹出消失。在silverlight中,点击应用的其他地方,会触发事件的控件,可以想一下有哪些,其中ComboBox就是在下拉下来的时候,点击界面其他地方,会自动收起。可以上下来回拖动调整音量大小,silverlight中Silder控件具有上下拖动的特性,使用它只需要调整样式就可以了。所以使用ComboBox和Silder组合成音量调整按钮。
三、设计控件样式
修改控件原本的样式,改变成符合实际需求的样式,是silverlight的强大优势之一。
下面看看ProcessBar怎么样从传统的样子改变成实时播放进度条
从工具箱将ProcessBar拖入工作区,创建一个新的样式,选择Eidt a Copy ,输入progressBarStyle键名;
1、修改ProgressBarRootGradient红色框选中属性值,如下图
效果如下:
2、编辑ProgressBarIndicator,将其类型改为Border类型,接着添加Grid,再添加Rectangle和Ellipse元素。将Grid分成两列,第一列自适应,第二列固定宽度为10px。Rectangle放在第一列,作用高亮显示已播放进度,Ellipse放在第二列,作用是显示为当前播放的进度点。如下图
设置DeterminateRoot属性:
设置ProgressBarIndicator属性:
设置Grid属性:
设置Rectangle属性:
设置Ellipse属性:
效果图:
3、修改ProgressBarTrack属性,如下图所示:
效果图:
整个播放实时进度条的样式已经完成,其中最关键的就是第2步骤,巧妙地运用ProcessBar控件通过改变ProgressBarIndicator的Width属性,显示进度变化,所以利用这个原理添加Grid、Rectangle、Ellipse元素,乔装成符合自己实际需要的控件。
接下来设计播放、暂停按钮的样式,看看ToggleButton按钮如何转变的。
从工具箱将ToggleButton拖入工作区,创建一个新的样式,选择Create Empty ,输入PlayPuseTemplate键名;
我们创建的是一个空的模板,所以里面什么元素都没有,那么添加我们所需要的元素,如下图:
设置第一个Rectangle属性:
设置borderPlay属性(<Border x:Name="borderPlay" CornerRadius="3">),使用钢笔工具绘制IconPlay形状
设置borderPause属性,和borderPlay大同小异,不多作说明了。唯一不同的是默认是隐藏的。
设置第二个Rectangle属性:
效果图:
接下来还需要添加鼠标进入时的过度效果,修改在MouseOver状态的样式(高亮按钮颜色)和Checked选中状态时的样式(暂停按钮显示,播放按钮隐藏)。
整个播放、暂停按钮的样式就完成了。播放列表按钮、全屏按钮、停止按钮都可以依葫芦画瓢,就不一步一步写出来了。
接下来设计最复杂的音量调整按钮的样式。看看怎样从传统的ComboBox+Silder 转变成符合实际需要的样式。
从工具箱将Slider拖入工作区,调整高度固定为80px,设置Orientation="Vertical".如图所示:
创建一个新的样式,选择Edit a Copy ,输入SliderStyle键名。确定后,如下图所示:
有HorizontalTemplate和VerticalTemplate两种类型,我们这里只需修改VerticalTemplate。将Rectangle宽度设为4;右键-》Edit Template -》Edit a Copy;确定后,将除Background元素外,其他元素删除,如下图所示:
整个调整声音的滑块样式就完成了,接着设计音量按钮的样式。
从工具箱将ComboBox拖入工作区,调整宽高固定为22px,创建一个新的样式,选择Edit a Copy ,输入CmbVolmeStyle键名。确定后,如下图所示:
注意框框里的元素,接下来我们要对DropDownToggle进行修改,右键-》Edit Template -》Edit Current,将Grid包含的元素contentPresenter外,其他全部删除。结果如下图所示:
接着将BtnArrow删除,添加Canvas 命名为 IconVolume ,在其里面再添加Canvas,用于绘制小喇叭的样式,使用钢笔工具绘制(画图需要耐性和反复修改);如图所示:
接着将ContentPresenter元素删除,因为我们不需要设置选中的内容。跟着添加两个Rectangle,目的是作美化用。如图所示:
紧接着删除DisabledVisualElement、FocusVisualElement、ScrollViewer。清空PopupBorder的边框色和背景色。如下图所示:
从工具箱将Slider拖入到PopupBorder,调整高度固定为80px,设置Orientation="Vertical",Value="5", Margin="0,0,0,-6",并应用SliderStyle样式。如图所示:
整个音量调整按钮的完成了,其他按钮的样式依葫芦画瓢就可以了。是否有点像周星驰电影007里面说的,它表面上是一个吹风机,其实它是一个刮胡刀。
四、组合成播放器界面
新建PlayTools用户控件,将实时播放进度条(ProgressBar)、播放、暂停按钮(ToggleButton)、停止按钮(Button)\当前时间、总时长、播放状态、视频名称 (TextBlock)、播放列表按钮(ToggleButton)、全屏按钮(ToggleButton)、音量调整按钮(ComboBox)添加到Grid里,并应用样式,结果如下图所示:
新建Player用户控件,将MediaElement、PlayTools、PlayListBox(没有详细说明,有需要请看源代码) 添加到Grid里,结果如下图所示:
五、关键代码及注意点
MediaElement控件本身没提供实时播放进度的事件,所以是通过注册CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);来实时获取当前已播放时间
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering); void CompositionTarget_Rendering(object sender, EventArgs e) { int currentTime = (int)this.mediaElement.Position.TotalSeconds; this.playTools.CurrentPlayTime = currentTime; }
六、特别处理及说明
双击全屏,由于silverlight4.0版本还没有鼠标双击的事件,所以需要模拟实现双击操作,原理是记录连续点击的两次时间差为300毫秒内,则判定为双击。
void mediaElement_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) { if (mouseClickList.Count == 0) { mouseClickList.Add(DateTime.Now); } else { if (mouseClickList.Count == 1) { if (DateTime.Now.Subtract(mouseClickList[0]).TotalMilliseconds <= 300) { this.playTools.IsScreenFull = true; playTools_ClickFullScreenButton(true); } mouseClickList.Clear(); } } }
全屏效果的实现,这里的实现使用了VideoBrush来渲染,就是所看到的全屏,其实是视频笔刷,后面才是真正的视频播放,只是把它投影到全屏控件上了。
this.FullPopup = new Popup(); double width = Application.Current.Host.Content.ActualWidth; double height = Application.Current.Host.Content.ActualHeight; VideoFullPlayer videoFullPlayer = new VideoFullPlayer(); videoFullPlayer.FullScreenChange += new Action<bool>(videoFullPlayer_FullScreenChange); videoFullPlayer.Width = width; videoFullPlayer.Height = height; videoFullPlayer.SetVideoBrush(this.mediaElement); PlayTools playTools = this.playTools; this.LayoutRoot.Children.Remove(this.playTools); videoFullPlayer.CurrentPlayTools = playTools; FullPopup.Child = videoFullPlayer; FullPopup.IsOpen = true;
七、演示示例
八、源码下载
如需源代码,请猛点击下载
准备放假咯,明年再见!!