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;
七、演示示例
八、源碼下載
如需源代碼,請猛點擊下載
准備放假咯,明年再見!!