Silverlight 高级视图模型风格视频播放器






4.91/5 (32投票s)
功能齐全的 Silverlight 视图模型风格视频播放器。
一个高级视频播放器
实时示例: http://silverlight.adefwebserver.com/
这是本系列文章 Silverlight 4 视频播放器 的第二部分。那篇文章侧重于视图模型样式 (模型-视图-视图模型) 模式,以及它如何支持设计师/开发者的协作和工作流程。但是,当其他人尝试将该项目用于实际网站时,他们发现需要视频播放器通常提供的全部控件。我决定利用这个机会深入研究视图模型样式,并演示使用Microsoft Expression Blend 4 (或更高版本) 时它的真正简单性。这一次我们将涵盖“棘手的部分”。然而,实现这一点将令人惊讶地容易。
本项目创建一个完全“可设计”的 Silverlight 视频播放器。这不应与“可换肤”视频播放器混淆。可换肤视频播放器允许您更改视频播放器现有控件的外观和感觉。可设计播放器允许设计师使用任何控件集来实现视频播放器。例如,设计师可以将所有按钮替换为用户旋转到不同位置的单个旋钮,或者他们可以更改用户用于从可用视频列表中选择的方法。这可以在 Expression Blend 中完成,而无需编写任何代码。
2010 年 5 月 16 日更新: Victor Gaudioso 提供了一个示例代码,演示了如何快进和快退视频。他的代码已改编用于此项目。他的原始文章可以在这里找到: http://victorgaudioso.wordpress.com/2010/05/15/new-silverlight-video-tutorial-how-to-create-fast-forward-for-the-mediaelement/
其他下载
您还需要下载并安装Silverlight Toolkit: http://silverlight.codeplex.com/
视图模型样式 (概述)
视图模型样式模式允许程序员创建一个绝对没有 UI 的应用程序。程序员只创建一个视图模型和一个模型。没有任何编程能力的设计师,然后可以从一个空白页面开始,完全在Microsoft Expression Blend 4 (或更高版本) 中创建视图(UI)。
是的,您也可以将代码放在代码隐藏中 (例如,您将一个按钮放在页面上,然后双击它,它就会在代码隐藏中连接一个事件处理程序),但那样设计师只能使用一个按钮来完成该任务。他们不能将元素更改为旋钮。最重要的是,设计师需要了解一点编码,因为他们将直接与代码交互。使用视图模型样式 (或在 Expression Blend 模板中现在称为数据驱动应用程序),基本上
- 程序员创建由属性、集合和命令组成的代码。
- 设计师使用 Expression Blend,无需编写任何代码即可创建完整的 UI。
模型是数据所在的位置,通常是 Web 服务。视图是 UI,或“前端”。视图模型是“魔术”发生的地方。它由
- 属性 – 一种东西。这可以是一个字符串或一个对象。实现了INotifyPropertyChanged,因此任何绑定到它的元素在它发生更改时都会自动收到通知。
- 集合 – 一种东西的集合。这是ObservableCollection类型,因此任何绑定到它的元素在它发生更改时都会自动收到通知。
- 命令 – 可以引发的事件。另外,可以传递一个 Object 类型的参数。这实现了ICommand。
如果您是视图模型样式的新手,建议您阅读 Silverlight 视图模型样式:一个(过于)简化的解释 以获得介绍。
入门解决方案
我们将从 **MVVMVideoPlayer_Starter.zip** 文件中包含的解决方案开始。运行项目时,Web 项目中的 Web 服务将检测“Video”文件夹中有哪些视频,并将它们传递给 Silverlight 应用程序。
- 视频列表将在组合框下拉列表中显示。
- 进度条将显示当前进度。
- 视频的确切位置将显示在视频上方。
- 视频的当前时间和总时间也将显示。
- 播放按钮将播放视频
- 停止按钮将停止视频。
这就是 Silverlight 4 视频播放器 文章所涵盖的内容。要制作功能齐全的视频播放器,我们需要添加以下功能
- 音量控制 - 用户需要能够更改视频的音量。
- 暂停按钮 - 用户需要能够暂停视频,然后通过再次单击暂停按钮或单击播放按钮来恢复播放。
- “搜寻”控件 - 用户应该能够在视频播放时轻松地向前和向后跳动。
- “视频缓冲”通知 - 当视频加载时,需要出现一个通知,并指示视频开始播放还需要多长时间。
- 全屏视频 - 观看视频时,最好有一个选项,单击按钮即可全屏观看视频。您需要能够无缝地来回切换。另外,只有视频应该是全屏的,而不是整个 Silverlight 应用程序。
我们将仅通过向视图模型添加代码来实现所有这些功能。模型根本没有改变。代码已包含在 **MVVMVideoPlayer_Starter.zip** 文件中 (将在本文末尾详细介绍)。现在,我们将从设计器用来实现此附加功能的步骤开始。首先,设计器在 Expression Blend 中打开项目...
音量控制
此控件实际上执行两项操作
- 指示当前音量
- 允许更改音量
在Expression Blend中,在Assets中,获取一个Slider控件。
将其拖放到Objects and Timeline窗口中的[StackPanel]中。
按键盘上的“V”键切换到Selection工具。然后,在设计表面上,使用鼠标稍微加宽滑块。
在Slider的Properties中,将左侧Margin设置为5
将Maximum级别设置为1,将Minimum设置为0 (Media Element上的音量实际上仅从 0 到 1。例如,它从 0.5 开始。)
单击Value旁边的Advanced options框 (在Common Properties中)。
选择数据绑定...。
- 选择Element Property选项卡
- 在Scene elements部分选择mediaElement
- 在Properties部分选择Volume
- 为Binding direction选择TwoWay
- 单击 **确定** 按钮
按F5构建并运行项目。 Web 浏览器将打开,您现在可以控制音量了。
暂停按钮
同样,眼见不一定为实
- 单击Pause时,视频应停止
- 如果视频已暂停,再次单击Pause,它应从中断处继续
- 如果视频已暂停,单击Play,它仍应从中断处继续
在Assets中,获取一个Button控件。
将其拖放到Objects and Timeline窗口中PlayButton和StopButton之间。右键单击它并将其重命名为“PauseButton”。
在Properties中,将Content设置为“Pause”,并将左侧Margin设置为5。
在Assets中,获取一个InvokeCommand行为。
将其拖放到Objects and Timeline窗口中**PauseButton**下方 (请记住您在之前的步骤中已将其重命名)。
在行为的Properties中,选择Data bind旁边的Command。
在Data Context窗口中选择PauseVideoCommand,然后单击OK按钮。
按F5构建并运行项目。 Web 浏览器将打开,您现在可以Pause视频了。
搜寻控件
我们希望提供在视频播放时向前和向后跳动到特定位置的功能。此功能可以使用任何控件来实现。例如,您可以使用Slider控件或自定义旋钮控件。在本例中,我们将仅使用ProgressBar控件。ProgressBar控件仍将显示进度,但当您单击控件时,它将导航到视频中的该部分。
在Assets中,获取一个InvokeCommandAction行为。
将其拖放到Objects and Timeline窗口中**mediaElement**下方。右键单击它并将其重命名为“SetSeekControl”。
在行为的Properties中
- 将EventName设置为MediaOpened (当视频设置为MediaElement的源,并且它找到了视频并准备好播放它时,此事件会自动触发)。
- 单击Command旁边的Data bind按钮。
将其绑定到SetSeekControlCommand。
单击CommandParameter旁边的Advanced options框。
- 选择Element Property选项卡
- 在Scene elements部分选择progressBar
- 在Properties部分选择ProgressBar
- 单击 **确定** 按钮
按F5构建并运行项目。 Web 浏览器将打开,您现在可以通过单击ProgressBar控件来跳到视频的各个部分了。
视频缓冲通知
当MediaElement播放视频时,它会加载视频“提前”播放的部分。如果它无法足够快地加载视频,它将停止并“缓冲”视频,然后在缓冲完成后 (缓冲达到 100% 时) 恢复播放。我们希望缓冲通知执行以下功能
- 视频正在缓冲时显示
- 视频未缓冲时消失
- 实时显示缓冲百分比,在缓冲过程中
在 Assets 中,搜索BusyIndicator。
将其拖到设计表面,并将其放置在MediaElement上。
在BusyIndicator的Properties中
- 清除BusyContent
- 清除Content
将BusyContent绑定到MediaBufferingTimeProperty (这将显示缓冲进度)。
将IsBusy属性绑定到MediaBufferingProperty (这将控制缓冲框何时显示)。
您可以通过它们周围的金色框知道您已绑定属性。
全屏视频
对于全屏视频功能,我们希望实现以下目标
- 处于全屏模式时,我们只想看到视频,而不是其他控件
- 来回切换时,我们希望视频无缝地继续播放
- 如果我们单击视频或全屏按钮,我们希望视频进入全屏模式
将 Silverlight 应用程序切换到全屏模式非常简单。仅切换 Silverlight 应用程序的一个元素涉及一些额外的步骤。我们需要创建一个Grid,然后指示ViewModel在进入全屏模式时使用此Grid。ViewModel将自动创建一个VideoBrush并将其源设置为MediaElement,以便视频在两种模式下保持同步。
在Tools栏的Panel部分,选择Grid。
双击Grid将其插入到设计表面。
在Grid的Properties中
- 将Width和Height设置为Auto
- 将RowSpan设置为2
- 将HorizontalAlignment和VerticalAlignment设置为Stretch
将一个InvokeCommand行为从Objects and Timeline窗口拖放到MediaElement下方,并将其重命名为“FullScreen”。
在行为的Properties中,将Command绑定到SetFullScreenCommand。
将CommandParameter绑定到[Grid]。
这将启用单击视频时进入全屏模式。现在要触发返回正常模式...
将一个InvokeCommand行为从Objects and Timeline窗口中的LayoutRoot拖放到下方。
在行为的Properties中
- 将EventName设置为SizeChanged
- 将Command绑定到ExitFullScreenCommand
- 将Grid绑定到CommandParameter
按F5构建并运行项目。当您单击视频时,它将切换到全屏模式。
返回Expression Blend并执行以下操作来创建Full Screen按钮
- 将一个按钮放在Stop按钮旁边,并将其内容设置为“Full Screen”
- 将左侧Margin设置为5
- 将一个InvokeCommand行为从它下方拖放。
- 在行为的Properties中,将Command绑定到SetFullScreenCommand
- 将CommandParameter绑定到[Grid]
代码
- 设计师可以跳到结尾,这里没什么可看的 :) -
让我们看看用于实现每个部分的相应代码...
卷
没有代码!Slider控件直接绑定到MediaElement的Volume属性。我知道……哇!
Pause
在ViewModel的构造函数中,所有ICommands都已设置好,包括PauseVideoCommand
public MainViewModel() { // Set the command property MediaOpenedCommand = new DelegateCommand(MediaOpened, CanMediaOpened); PlayVideoCommand = new DelegateCommand(PlayVideo, CanPlayVideo); StopVideoCommand = new DelegateCommand(StopVideo, CanStopVideo); PauseVideoCommand = new DelegateCommand(PauseVideo, CanPauseVideo); SetSeekControlCommand = new DelegateCommand(SetSeekControl, CanSetSeekControl); SetVideoCommand = new DelegateCommand(SetVideo, CanSetVideo); SetFullScreenCommand = new DelegateCommand(SetFullScreen, CanSetFullScreen); ExitFullScreenCommand = new DelegateCommand(ExitFullScreen, CanExitFullScreen); // Call the Model to get the collection of Videos GetListOfVideos(); }
其余的实现如下。基本上,它只是调用MediaElement上的Pause()。其余代码处理是否可以暂停,是否已暂停,以及如果已暂停,是否应改为播放。
#region PauseVideoCommand public ICommand PauseVideoCommand { get; set; } public void PauseVideo(object param) { // We only want to Pause if the media is Playing if (MyMediaElement.CurrentState == MediaElementState.Playing) { // If we can Pause the Video, Pause it if (MyMediaElement.CanPause) { // Pause Video MyMediaElement.Pause(); } else { // We can't pause the Video so Stop it MyMediaElement.Stop(); } if (progressTimer.IsEnabled) { progressTimer.Stop(); } } else { // The Media is not Playing so we are Paused // Play Video MyMediaElement.Play(); progressTimer.Start(); } } private bool CanPauseVideo(object param) { bool CanPause = false; if (MyMediaElement != null) { // Only allow this Command if paused or Playing if ((MyMediaElement.CurrentState == MediaElementState.Paused) || (MyMediaElement.CurrentState == MediaElementState.Playing)) { CanPause = true; } } return CanPause; } #endregion
Seek
这个有点意思,基本上,你首先传入一个FrameworkElement,它将用于执行 Seek。鼠标事件会附加到这个 FrameworkElement 上,这样当有人单击该元素时就会引发一个事件。当事件被引发时,元素的宽度将用于确定用户在元素上的哪个位置单击了。该值用于确定在视频中导航到哪个位置。
#region SetSeekControlCommand public ICommand SetSeekControlCommand { get; set; } public void SetSeekControl(object param) { // Hook Events into the Seek Control SeekControl = (FrameworkElement)param; SeekControl.MouseLeftButtonDown += new MouseButtonEventHandler(SeekControl_MouseLeftButtonDown); } private bool CanSetSeekControl(object param) { return true; } private void SeekControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { double position = e.GetPosition(SeekControl).X; double percent = position / SeekControl.ActualWidth; Seek(percent); } private void Seek(double percentComplete) { TimeSpan duration = MyMediaElement.NaturalDuration.TimeSpan; int newPosition = (int)(duration.TotalSeconds * percentComplete); MyMediaElement.Position = new TimeSpan(0, 0, newPosition); SetCurrentPosition(); } #endregion
视频缓冲
使用DispatcherTimer每秒检查一次视频进度,并更新属性,包括缓冲属性。
#region Time display private void ProgressTimer_Tick(object sender, EventArgs e) { SetCurrentPosition(); } private void SetCurrentPosition() { // If the Media play is complete stop the media if (CurrentPositionProperty > 0) { if (CurrentPositionProperty >= TotalDurationProperty) { // If in full screen mode - exit full screen mode var content = Application.Current.Host.Content; if (content.IsFullScreen) { content.IsFullScreen = false; } CurrentPositionProperty = 0; StopVideo(null); } } // Update the time text e.g. 01:50 / 03:30 CurrentProgressProperty = string.Format( "{0}:{1} / {2}:{3}", Math.Floor(MyMediaElement.Position.TotalMinutes).ToString("00"), MyMediaElement.Position.Seconds.ToString("00"), Math.Floor(MyMediaElement.NaturalDuration.TimeSpan.TotalMinutes).ToString("00"), MyMediaElement.NaturalDuration.TimeSpan.Seconds.ToString("00")); CurrentPositionProperty = MyMediaElement.Position.TotalSeconds; TotalDurationProperty = MyMediaElement.NaturalDuration.TimeSpan.TotalSeconds; MediaBufferingProperty = (MyMediaElement.CurrentState == MediaElementState.Buffering); MediaBufferingTimeProperty = String.Format("Buffering {0} %", (MyMediaElement.BufferingProgress * 100).ToString("##")); } #endregion
注意: 回过头来看,完全有可能在不使用DispatcherTimer的情况下实现整个示例。MediaElement上的InvokeAction行为可能就是更新属性所需的一切……好吧,也许下次吧。
全屏视频
设计师必须指定一个Grid用于全屏模式。此Grid必须设置为拉伸并填充整个屏幕,遮挡应用程序的其余部分 (所以是的,在全屏模式下,应用程序的其余部分实际上仍然在Grid后面)。
代码动态地将一个VideoBrush放入Grid中,并将其源设置为MediaElement,以便视频保持同步。可以在Grid中放置其他元素,例如,您可以在鼠标移动时显示播放器控件。
#region SetFullScreenCommand public ICommand SetFullScreenCommand { get; set; } public void SetFullScreen(object param) { if (MyMediaElement != null) { // Put application in full screen mode var content = Application.Current.Host.Content; content.IsFullScreen = true; // Set the Video Brush to the content of the MediaElement VideoBrush objVideoBrush = new VideoBrush(); objVideoBrush.SetSource(MyMediaElement); objVideoBrush.Stretch = Stretch.UniformToFill; // A Grid to show in full screen needs to be passed as the parameter // Set the background content of that panel to the VideoBrush // Note: Other elements and controls can be placed on this Grid // It does not have to be blank Grid objGrid = (Grid)param; objGrid.Visibility = Visibility.Visible; objGrid.Background = objVideoBrush; } } private bool CanSetFullScreen(object param) { // Only allow full screen if not in full screen var content = Application.Current.Host.Content; return (!content.IsFullScreen); } #endregion
这是退出全屏时的代码
#region ExitFullScreenCommand public ICommand ExitFullScreenCommand { get; set; } public void ExitFullScreen(object param) { // A Panel to show in full screen needs to be passed as the parameter // Set this element to invisible Panel objPanel = (Panel)param; objPanel.Visibility = Visibility.Collapsed; } private bool CanExitFullScreen(object param) { // Only allow exit full screen if not in full screen // This may seem odd, but this command is being called by a SizeChanged event // If we ARE in full screen then we were actually just recently NOT in full screen // We only want to fire the ExitFullScreen if we were actually just In full screen // but NOT in full screen any more var content = Application.Current.Host.Content; return (!content.IsFullScreen); } #endregion
请“粉饰”此视频播放器!
您是设计师,还是想成为设计师?下载此项目,设计一个很酷的设计。代码是开源的,因此您可以重做应用程序,并发布教程或博客来展示您创建的内容。要了解如何使用 Expression Blend,您只需转到