视频播放器
播放服务器端的视频
图2-4 自定义的Silverlight视频播放器
这个实例设计了1个自定义的Silverlight视频播放器(视频源文件放在服务器端),如图2-4所示,这个示例完全在Expression Blend 4中设计,视频放在服务器端的文件夹中,网上调试成功。示例设计的复杂一点,当然功能也强,如果减少功能程序也会简单。图2-4上方是视频播放控件 MediaElement(me);下方是需要播放的视频图片,点击可以选择视频源;中间有1个视频播放控制面板,Silverlight视频控件 MediaElement有边下载边播放的功能,图2-5中间有个播放进度条,同时显示下载进度和播放进度;进度条上方的2个文本分别显示下载缓冲比例和 播放器的当前状态,进度条下方的文本框显示视频播放的时间进度值;进度条上方右侧有1个三角形音量控制图标,点击就可以选择音量,喇叭图标是“静音”控制 图标,点击1次变化1次,静音时喇叭图标上出现1个红色“×”;
图2-5 自定义的Silverlight视频播放器控制板
当鼠标悬浮在进度条上时,先出现文本(Callout控件)显示,显示进度条当前值对应的视频时间,只要鼠标悬浮不动就会出现1个视频预览窗口(也是 MediaElement控件,名为preview),可以看到此时对应的视频画面(反复播放当前画面2秒,仅几帧);进度条下方有“暂停”、“播放”、 “停止”、“重播”和“全屏”等控制图标。下面说明设计过程。
1、界面设计
新建1个“Silverlight应用程序+网站”项目,名称为SilverlightMediaPlayer,设置界面对中:改变 “LayoutRoot”的布局类型为“ScrollViewer”,放入Canvas(canvas1),设置其背景和大小,设置 “UserControl”的Width和Height属性为自动。
2、me控件属性设置
“布局”中Width:448 Height:252,相当于16:9。
“媒体”中Stretch:UniformToFill。me控件放置在Canvas布局控件canvas1中,这对me的位置和尺寸变化很重要。
3、音量控制设置
图2-5右侧的音量控制图标表面看是三角形的,实际上它是1个矩形ProgressBar控件(命名为volume),但其左上角部分用1个和背景颜色(黑色)一致的Path(用钢笔工具手绘)覆盖,看到的是三角形。volume和Path组合在grid3中。
volume控件属性设置(“公共属性”栏目中):
Maximum:1 Minimum:0
volume控件的Value属性(默认值改设为0.6,原来是0.5)采用数据绑定设置:在【属性】面板的“公共属性”栏中选Value属性—鼠标左键 点击右侧的白色方块(高级选项),在弹出的菜单中选择“数据绑定”,这时出现“创建数据绑定”窗口,选择“元素属性”页面,左侧场景元素中选“me”控 件,右侧属性中选Value,点“确定”键完成设置。me控件的音量将跟随volume控件的Value变化。
喇叭图标由图形组合而成,最后组合在Grid控件(mute)中,构成静音/播音反复控制,如果处于静音喇叭图标上又多出红色“×”(组合在grid5中),程序如下。
- //音量控制
- private void volume_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- //获取ProgressBar控件对应的音量值
- this.me.Volume=e.GetPosition(this.volume).X/this.volume.ActualWidth;
- }
- //静音控制
- private void mute_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- this.me.IsMuted=!this.me.IsMuted;
- if (this.me.IsMuted)
- //如果是静音,显示红色×
- this.grid5.Visibility=Visibility.Visible;
- else
- this.grid5.Visibility=Visibility.Collapsed;
- }
4、进度条设置和预览窗口
MediaElement控件本身有边下载边播放的功能,这里使用图图2-5中间的进度条控件ProgressBar(名为progressbar)的进 度值显示视频下载进度,同时又在其中附加了1个矩形控件(名为vernier,红色)作为游标用于显示播放进度,这2个控件组合放置在Canvas中(名 为canvas2),canvas2的大小和progressbar一致,这样方便游标vernier的位置设置。当点击进度条时视频就会选择从点击处对 应的视频位置开始播放。另外有3个文本框分别显示下载的缓冲进度(textblock1)、播放器当前状态(textblock2)和视频播放时的时间进 度(textblock3)。
关于缓冲进度:当me控件的属性Source被赋值后,自动产生下载缓冲过程,下载缓冲时间属性BufferingTime默认值是00:00:05(5秒,在“媒体”属性栏目中),此值的大小会影响视频播放的平滑程度,当然和网络速度也有关,本实例中设置500ms:
this.me.BufferingTime=TimeSpan.FromMilliseconds(500);
但实际的缓冲值在0-1之间变化,乘以100后相当于百分比,1个100%应当对应1个下载缓冲时间,1个视频的下载可能要经过若干个这样的过程。而且当1个缓冲完成时自动产生BufferProgressChanged事件,缓冲的显示正是在此事件的程序中设计的。
缓冲变化会使视频文件下载进度变化,而此变化也会产生DownloadProgressChanged事件,可以在其中编写程序显示下载进度属性DownloadProgress的变化值(0-1之间)。
关于播放器状态,MedaiElement控件在视频下载和播放时有如下状态:
Buffering、Closed、Opening、Paused、Playing 或 Stopped。默认值为 Closed,这些状态根据英文就能理解它的含义,不再解释。状态的显示也是在此BufferProgressChanged事件程序中设计的。
图2-4下方有5个图形图标,代表5个视频,当点击时就会产生下载播放过程,下面以点击左边的图标“机场”为例,看如何编写程序的。
- //播放"机场"视频
- private void image1_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- //定时器停止,定时器的作用后面介绍
- timer.Stop();
- //下载进度值回0
- this.progressbar.Value=0;
- //获取当前浏览位置定位(含网页文件名称),如:http://localhost:2277/Default.html
- uri=System.Windows.Browser.HtmlPage.Document.DocumentUri.ToString();
- //LastIndexOf("/"):搜寻定位中右边最后1个"/"的位置,其左侧是不含网页文件的定位 //形成服务器视频文件的位置信息
- videoname=uri.Substring(0,uri.LastIndexOf("/")+1)+"video/video1.wmv";
- //设置视频播放控件的视频源
- this.me.Source=new Uri(videoname,UriKind.RelativeOrAbsolute);
- //设置预览视频源,后面会介绍视频预览
- thisthis.preview.Source=this.me.Source;
- this.preview.Stop();
- //设置视频文件打开后的事件
- this.me.MediaOpened+=new RoutedEventHandler(meOpened);
- //设置下载进度变化事件
- this.me.DownloadProgressChanged+=new RoutedEventHandler(meDownload);
- //设置下载缓冲时间
- this.me.BufferingTime=TimeSpan.FromMilliseconds(500);
- //设置下载缓冲变化的事件
- this.me.BufferingProgressChanged+=new RoutedEventHandler(me_BufferingProgressChanged);
- }
- //下载进度显示
- private void meDownload(object sender, System.Windows.RoutedEventArgs e)
- {
- //DownloadProgress是下载进度,变化范围0-1
- thisthis.progressbar.Value=this.me.DownloadProgress*this.progressbar.Maximum;
- }
- //下载缓冲变化显示
- double bp=0;
- private void me_BufferingProgressChanged(object sender,RoutedEventArgs e){
- //BufferingProgress:缓冲变化值0-1
- bp=this.me.BufferingProgress*100;
- this.textblock1.Text="下载缓冲进度:"+bp.ToString()+"%";
- this.textblock2.Text="播放器当前状态:"+me.CurrentState.ToString();
- }
- //文件打开后定时器启动
- private void meOpened(object sender, System.Windows.RoutedEventArgs e)
- {
- timer.Start();
- }
- //当点击进度条时,选择视频播放位置
- private void progressbar_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- this.me.Pause();
- this.me.Position=TimeSpan.FromSeconds(currentlength);
- this.me.Play();
- }
- 在本机实际调试时,可能看不到缓冲变化和播放状态,但是如果放到服务器上调试就会很明显,本实例已经在网络服务器上调试验证。
- 以上的程序大多数代码在前面已经有了解释,还有视频预览preview和定时器timer没有涉及到。
- Preview也是1个MediaElement控件(和边框矩形设计一起组合在canvas2的grid4中),当视频已经下载并播放时,如果鼠标悬停在进度条上立刻显示视频对应的时间,如果鼠标停止不动一段时间(比如2秒),会自动弹出视频预览窗口,看到的是鼠标悬停处对应的视频,此预览只反复播放悬停处的几帧视频图像。鼠标离开或变化位置自动停止,这点和目前网上看到的有的视频播放器的效果是一样的。
- 这里用到时间控制,由定时器来完成,程序如下:
- private DispatcherTimer timer=new DispatcherTimer();
- //定义变量,用于视频源地址和文件位置信息变量
- string uri,videoname;
- public MainPage()
- {
- InitializeComponent();
- //Esc键提示关闭
- this.Esctext.Visibility=Visibility.Collapsed;
- //进度条当前位置显示文本框关闭
- this.cp.Visibility=Visibility.Collapsed;
- //预览窗口关闭
- this.grid4.Visibility=Visibility.Collapsed;
- timer.Interval=TimeSpan.FromMilliseconds(500);
- timer.Tick+=new EventHandler(timerarrive);
- //设置视频播放进度游标初始位置
- Canvas.SetLeft(this.vernier,0);
- Canvas.SetTop(this.vernier,0);
- }
- //定时器定时访问程序
- //视频时间总长度和视频进度条当前值变量
- double melength=0,currentvalue;
- //记忆鼠标悬停位置
- double movevalue=0;
- //悬停不动时间计数和预览时间计数
- double times=0,previewtimes=0;
- //鼠标移动
- bool move=false;
- private void timerarrive(object sender,EventArgs e){
- //获取视频时间总长度
- memelength=me.NaturalDuration.TimeSpan.TotalSeconds;
- //视频进度条当前值=视频进度条最大值*视频当前位置时间/视频时间总长度
- currentvalue=this.progressbar.ActualWidth*me.Position.TotalSeconds/melength;
- //设置视频进度游标位置
- Canvas.SetLeft(this.vernier,currentvalue);
- //获取视频当前位置时间小时值、分值、秒值
- int h=me.Position.Hours;
- int m=me.Position.Minutes;
- int s=me.Position.Seconds;
- //下面使用了C#的条件运算符<条件>?<满足条件时>:<不满足条件时>
- this.textblock3.Text="播放时间进度:"+(h<10? "0"+h.ToString():h.ToString())+":"
- +(m<10? "0"+m.ToString():m.ToString())+":"
- +(s<10? "0"+s.ToString():s.ToString());
- //预览视频处理
- if (times<4)
- //计数控制+1(位置不变的次数)
- times++;
- if (times==1){
- //取当前鼠标X位置
- movevalue=progressbarX;
- }
- //如果鼠标滑动在进度条且X坐标没有变化,且计数达4次
- if (move && movevalue==progressbarX){
- if (times==4){
- //开启预览窗口
- this.grid4.Visibility=Visibility.Visible;
- //如果预览窗口当前处于播放状态
- if (this.preview.CurrentState==MediaElementState.Playing){
- //预览时间控制计数
- previewtimes++;
- //达到预览时间
- if (previewtimes==4)
- //暂停预览
- this.preview.Pause();
- }else{
- //设置预览视频位置
- this.preview.Position=current;
- //启动视频预览,且设置预览时间控制计数回0
- this.preview.Play();
- previewtimes=0;
- }
- }
- }else{
- //关闭预览窗口,同时位置不变的次数计数回0
- this.grid4.Visibility=Visibility.Collapsed;
- times=0;
- }
- }
- 定时访问程序中用到的一些变量,如progressbarX、move等,和下列程序有关:
- //定义变量,用于当前位置时间值和鼠标X坐标
- double currentlength,progressbarX;
- //进度条鼠标悬停处的时间
- TimeSpan current;
- //鼠标悬停在进度条上方,进度条当前位置时间显示
- private void progressbar_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
- {
- //事件引发者是ProgressBar控件
- ProgressBar pb=sender as ProgressBar;
- //视频时间总长度
- memelength=me.NaturalDuration.TimeSpan.TotalSeconds;
- //进度条当前位置时间值 currentlength=melength*e.GetPosition(this.progressbar).X/this.progressbar.ActualWidth;
- int h=(int)currentlength/3600;
- int m=(int)((currentlength600)/60);
- int s=(int)((currentlength600)`);
- //设置视频进度游标位置
- Canvas.SetLeft(this.cp,e.GetPosition(this.progressbar).X-30);
- Canvas.SetLeft(this.grid4,e.GetPosition(this.progressbar).X-54);
- //当前位置显示使用Callout(cp)控件
- this.cp.Visibility=Visibility.Visible;
- this.cp.Content=(h<10? "0"+h.ToString():h.ToString())+":"
- +(m<10? "0"+m.ToString():m.ToString())+":"
- +(s<10? "0"+s.ToString():s.ToString());
- current=TimeSpan.Parse(this.cp.Content.ToString());
- move=true;
- progressbarX=e.GetPosition(this.progressbar).X;
- }
- //鼠标离开进度条
- private void progressbar_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
- {
- //关闭预览,关闭进度条当前位置时间显示
- this.grid4.Visibility=Visibility.Collapsed;
- this.cp.Visibility=Visibility.Collapsed;
- //停止鼠标移动
- move=false;
- //鼠标悬停不动时间计数置0,鼠标悬停位置回0
- times=0;
- movevalue=0;
- //预览播放停止,预览计数回0
- this.preview.Stop();
- previewtimes=0;
- }
- 5、播放控制
- 对视频的“暂停”、“播放”、“停止”、“重播”等控制图标采用了故事板动画,当鼠标悬停在这些图标上时颜色发生变化(本例变为绿色),程序比较简单:
- //暂停按钮控制
- private void pause_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
- {
- this.Storyboard1.Begin();
- }
- private void pause_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
- {
- this.Storyboard1.Stop();
- }
- private void pause_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- this.me.Pause();
- }
- //播放按钮控制
- private void play_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
- {
- this.Storyboard2.Begin();
- }
- private void play_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
- {
- this.Storyboard2.Stop();
- }
- private void play_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- this.me.Play();
- }
- //停止按钮控制
- private void stop_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
- {
- this.Storyboard3.Begin();
- }
- private void stop_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
- {
- this.Storyboard3.Stop();
- }
- private void stop_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- this.me.Stop();
- }
- //重播按钮控制
- private void replay_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
- {
- this.Storyboard4.Begin();
- }
- private void replay_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
- {
- this.Storyboard4.Stop();
- }
- private void replay_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- this.me.Stop();
- this.me.Play();
- }
- 6、全屏播放控制
- Silverlight本身有全屏播放的控制语句:
- Application.Current.Host.Content.IsFullScreen=!Application.Current.Host.Content.IsFullScreen;
- 但是,此语句控制的是当前显示界面的全屏显示,而我们只希望视频部分全屏播放,本实例中编程先将视频放到浏览器的“全屏”(浏览器菜单和工具条保留)大小(程序设计指定按Esc键恢复原始大小),然后再使用上述语句放大到全屏,此语句默认并提示按Esc取消全屏,这样当程序运行时第一次按Esc键实际恢复到浏览器“全屏”,第二次再按Esc键恢复到原始大小,程序如下。
- //全屏控制
- private void fullscreen_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
- {
- this.Storyboard5.Begin();
- }
- private void fullscreen_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
- {
- this.Storyboard5.Stop();
- }
- //定义变量,记忆初始值
- double canvas1W,canvas1H,meW,meH,meLeft,meTop;
- private void fullscreen_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- //视频播放时才能使用全屏操作
- if (this.me.CurrentState==MediaElementState.Playing){
- //记忆Canvas(me控件的容器)的原始大小
- canvas1W=this.canvas1.ActualWidth;
- canvas1H=this.canvas1.ActualHeight;
- //记忆me控件的原始大小,以及me控件的原始位置
- meW=this.me.ActualWidth;
- meH=this.me.ActualHeight;
- meLeft=Canvas.GetLeft(me);
- meTop=Canvas.GetTop(me);
- //布局控件放大到当前应用程序(在浏览器中)界面大小
- this.canvas1.Width=Application.Current.Host.Content.ActualWidth;
- this.canvas1.Height=Application.Current.Host.Content.ActualHeight;
- //设置me控件层次到上层(20是界面元素个数最大值,实际没有这么多)
- Canvas.SetZIndex(me,20);
- //视频控件也放大到当前应用程序界面大小
- thisthis.me.Width=this.canvas1.Width;
- thisthis.me.Height=this.canvas1.Height;
- //视频控件位置设置
- Canvas.SetLeft(me,0);
- Canvas.SetTop(me,0);
- //使用按Esc键提示,以及按Esc键提示框位置,并将提示框放置最上层
- this.Esctext.Visibility=Visibility.Visible;
- Canvas.SetLeft(Esctext,0);
- Canvas.SetTop(Esctext,0);
- Canvas.SetZIndex(Esctext,21);
- //设置按Esc键事件(自定义)
- Application.Current.RootVisual.KeyDown += new KeyEventHandler(ESC_Down);
- //使用Silverlight的全屏控制Application.Current.Host.Content.IsFullScreen=!Application.Current.Host.Content.IsFullScreen;
- }
- }
- private void ESC_Down(object sender, KeyEventArgs e)
- {
- //如果按下Esc键
- if (e.Key.ToString()=="Escape"){
- //Esc键提示框关闭,恢复布局原始大小
- this.Esctext.Visibility=Visibility.Collapsed;
- this.canvas1.Width=canvas1W;
- this.canvas1.Height=canvas1H;
- //恢复me控件原始大小,置于底层,并恢复原始位置
- this.me.Width=meW;
- this.me.Height=meH;
- Canvas.SetZIndex(me,0);
- Canvas.SetLeft(me,meLeft);
- Canvas.SetTop(me,meTop);
- }