《快速構建Windows 8風格應用20-MediaElement》博文中提到了如何使用MediaElement對象進行播放視頻的簡單功能,但是在實際應用中需要更復雜的功能,例如:控制視頻播放的控件、全屏模式、進度條等等其他功能。
本篇博文中示例使用應用程序中包含的媒體文件,當然我們也可以通過網絡或者本地[使用FileOpenPicker]進行加載某一媒體文件。
MSDN中關於媒體播放器的示例代碼下載地址:XAML media playback sample。
構建基本的MediaElement控件
首先我們創建一個MediaElement控件並添加到ContentControl對象中,這樣做的目的是為了啟用全盤模式功能。
XAML代碼如下:
<ContentControl x:Name="videoContainer" KeyUp="VideoContainer_KeyUp" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Height="400" Grid.Row="0" > <MediaElement Name="videoMediaElement" Source="Video/Azure_Tmobile_500k.wmv" MediaOpened="videoElement_MediaOpened" MediaEnded="videoMediaElement_MediaEnded" MediaFailed="videoMediaElement_MediaFailed" CurrentStateChanged="videoMediaElement_CurrentStateChanged" PosterSource="Media/1.png" AutoPlay="False" /> </ContentControl>
MediaElement控件的Source屬性指向要播放的音頻或視頻文件。,該屬性可以設置為應用中的某一文件的URI或網絡上的文件的 URI。當然我們也可以使用 SetSource 方法將源設置為使用 FileOpenPicker 對象從本地系統檢索的文件。
AutoPlay屬性指定是否在加載 MediaElement 後開始播放媒體文件,默認值為 true。
MediaElement控件聲明了MediaOpened、MediaEnded、CurrentStateChanged和 MediaFailed 事件。
最後設置PosterSource屬性值,PosterSource是一個圖像,它在媒體加載前為MediaElement控件提供視覺展示。
通常PosterSource在以下情況下顯示:
1)未設置有效的源,例如:未設置Source、Source設置為Null、或源無效;
2)加載媒體時;
3)“播放到”流式播放期間;
構建控制MediaElement播放控件
一般控制MediaElement播放包括播放,停止和暫停等功能。
常用控制MediaElement播放功能包括:
1)停止:調用 Stop 方法;
2)暫停:調用 Pause 方法;
3)快進:將MediaElement控件的DefaultPlaybackRate屬性的值設置為 2.0,我們可以調整此值,以提高或降低快進的速率。 然後,處理程序調用 Play 方法;
4)快退:將MediaElement控件的DefaultPlaybackRate屬性的值設置為 -2.0,我們可以調整此值,以提高或降低快退的速率。然後,處理程序調用 Play 方法;
5)播放:如果MediaElement控件的DefaultPlaybackRate屬性值不是 1.0,則將DefaultPlaybackRate 設置為 1.0。然後,處理程序調用 Play 方法。
6)靜音:在 true 和false 間切換 IsMuted 屬性;
7)音量增加、音量降低:如果IsMuted為true,則取消音量靜音,然後處理程序按 0.1 增加或降低Volume屬性。 注意:音量級別范圍從 0.0 到 1.0;
對應這些控制功能的XAML代碼可如下:
<StackPanel Orientation="Horizontal"> <Button Name="btnPlay" Click="btnPlay_Click" Style="{StaticResource transportStyle}" Content="Play" /> <Button Name="btnPause" Click="btnPause_Click" Style="{StaticResource transportStyle}" Content="Pause" /> <Button Name="btnStop" Click="btnStop_Click" Style="{StaticResource transportStyle}" Content="Stop" /> <Button Name="btnReverse" Click="btnReverse_Click" Style="{StaticResource transportStyle}" Content="Rewind" /> <Button Name="btnForward" Click="btnForward_Click" Style="{StaticResource transportStyle}" Content="Forward" /> <Button Name="btnMute" Click="btnMute_Click" Style="{StaticResource transportStyle}" Content="Mute" /> <Button Name="btnFullScreenToggle" Click="btnFullScreenToggle_Click" Style="{StaticResource transportStyle}" Content="Full" /> <ComboBox Name="cbAudioTracks" SelectionChanged="cbAudioTracks_SelectionChanged" Width="75" /> <Button Name="btnVolumeUp" Click="btnVolumeUp_Click" Style="{StaticResource transportStyle}" Content="-" /> <Button Name="btnVolumeDown" Click="btnVolumeDown_Click" Style="{StaticResource transportStyle}" Content="+" /> <TextBlock Name="txtVolume" FontSize="14" Text="{Binding Volume, ElementName=videoMediaElement}" VerticalAlignment="Center" HorizontalAlignment="Right" /> </StackPanel>
相應的C#代碼如下:
private void btnPlay_Click(object sender, RoutedEventArgs e) { if (videoMediaElement.DefaultPlaybackRate != 1) { videoMediaElement.DefaultPlaybackRate = 1.0; } videoMediaElement.Play(); } private void btnPause_Click(object sender, RoutedEventArgs e) { videoMediaElement.Pause(); } private void btnStop_Click(object sender, RoutedEventArgs e) { videoMediaElement.Stop(); } private void btnForward_Click(object sender, RoutedEventArgs e) { videoMediaElement.DefaultPlaybackRate = 2.0; videoMediaElement.Play(); } private void btnReverse_Click(object sender, RoutedEventArgs e) { videoMediaElement.DefaultPlaybackRate = -2; videoMediaElement.Play();; } private void btnVolumeDown_Click(object sender, RoutedEventArgs e) { if (videoMediaElement.IsMuted) { videoMediaElement.IsMuted = false; } if (videoMediaElement.Volume < 1) { videoMediaElement.Volume += .1; } } private void btnMute_Click(object sender, RoutedEventArgs e) { videoMediaElement.IsMuted = !videoMediaElement.IsMuted; } private void btnVolumeUp_Click(object sender, RoutedEventArgs e) { if (videoMediaElement.IsMuted) { videoMediaElement.IsMuted = false; } if (videoMediaElement.Volume > 0) { videoMediaElement.Volume -= .1; } }
構建MediaElement的全屏播放功能
啟用全屏視頻播放功能,需要將MediaElement的Width和Height設置為當前窗口的Windows.Bounds(使用的是Window.Current.Bounds.Width和 Window.Current.Bounds.Height)。
啟用全屏視頻播放功能步驟如下:
1)隱藏應用程序中的所有UI 元素;
2)將MediaElement的Width和Height設置為顯示的最大范圍,這會讓MediaElement控件的高度和寬度與窗口的對應尺寸一致。但是需要首先保存當前Height和Width,方便在應用退出全屏模式時將控件恢復為正確的大小。然後,可以將ContentControl和MediaElement的尺寸設置為當前窗口的Window.Bounds;
退出全屏視頻播放功能步驟如下:
1)偵聽鍵盤事件以檢測用戶希望何時退出全屏模式,通常我們使用Esc 鍵通來退出全屏模式。在ContentControl控件中添加KeyUp事件。在KeyUp事件處理程序中,若應用處於全屏模式並且按下的鍵為Windows.System.VirtualKey.Escape時退出全屏模式。實際應用中我們還應該添加Manipulation觸摸事件來處理觸摸手勢,進行退出全盤模式;
2)恢復 UI 元素的可見性;
3)將ContentControl和MediaElement的Width和Height恢復為其原來的尺寸;
C#代碼可如下:
private bool _isFullscreenToggle = false; public bool IsFullscreen { get { return _isFullscreenToggle; } set { _isFullscreenToggle = value; } } private Size _previousVideoContainerSize = new Size(); private void FullscreenToggle() { this.IsFullscreen = !this.IsFullscreen; if (this.IsFullscreen) { TransportControlsPanel.Visibility = Visibility.Collapsed; _previousVideoContainerSize.Width = videoContainer.ActualWidth; _previousVideoContainerSize.Height = videoContainer.ActualHeight; videoContainer.Width = Window.Current.Bounds.Width; videoContainer.Height = Window.Current.Bounds.Height; videoMediaElement.Width = Window.Current.Bounds.Width; videoMediaElement.Height = Window.Current.Bounds.Height; } else { TransportControlsPanel.Visibility = Visibility.Visible; videoContainer.Width = _previousVideoContainerSize.Width; videoContainer.Height = _previousVideoContainerSize.Height; videoMediaElement.Width = _previousVideoContainerSize.Width; videoMediaElement.Height = _previousVideoContainerSize.Height; } } private void btnFullScreenToggle_Click(object sender, RoutedEventArgs e) { FullscreenToggle(); } private void VideoContainer_KeyUp(object sender, KeyRoutedEventArgs e) { if (IsFullscreen && e.Key == Windows.System.VirtualKey.Escape) { FullscreenToggle(); } e.Handled = true; }
構建MediaElement的滑動進度條功能
通常我們使用Slider控件來顯示或更改視頻位置,大致思路為設置Slider控件並使用DispatcherTimer來保持滑塊與MediaElement.Position屬性的同步。
首先XAML中聲明Slider控件。
<SliderName="timelineSlider"Margin="10,0"Width="200"/>
注意:Slider控件的StepFrequency屬性定義了滑塊的刻度上步驟的頻率。這裡演示是基於MediaElement的NaturalDuration屬性的值來設置StepFrequency屬性。
如果我們想采用更高的精度可以使用設置為250 毫秒的最低頻率調節這些數字。
C#代碼中聲明以下代碼:
private double SliderFrequency(TimeSpan timevalue) { double stepfrequency = -1; double absvalue = (int)Math.Round( timevalue.TotalSeconds, MidpointRounding.AwayFromZero); stepfrequency = (int)(Math.Round(absvalue / 100)); if (timevalue.TotalMinutes >= 10 && timevalue.TotalMinutes < 30) { stepfrequency = 10; } else if (timevalue.TotalMinutes >= 30 && timevalue.TotalMinutes < 60) { stepfrequency = 30; } else if (timevalue.TotalHours >= 1) { stepfrequency = 60; } if (stepfrequency == 0) stepfrequency += 1; if (stepfrequency == 1) { stepfrequency = absvalue / 100; } return stepfrequency; }
我們需要DispatcherTimer來保持Slider與媒體同步,並將DispatcherTimer的Interval屬性值設置為Slider 的StepFrequency。對於每次計時器計時,Slider的Value屬性設置為MediaElement.Position。
另外我們需要在以下情況中關閉掉計時器:
1)MediaElement當前狀態為暫停或停止時;
2)滑塊的滑條移動時;
3)進度條不可見時,例如:全屏模式下。這裡需要注意的是進度條在推出全屏模式後重新開始計時;
下面C#代碼是如何創建和設置DispatcherTimer:
private DispatcherTimer _timer; private void SetupTimer() { _timer = new DispatcherTimer(); _timer.Interval = TimeSpan.FromSeconds(timelineSlider.StepFrequency); StartTimer(); } private void _timer_Tick(object sender, object e) { if (!_sliderpressed) { timelineSlider.Value = videoMediaElement.Position.TotalSeconds; } } private void StartTimer() { _timer.Tick += _timer_Tick; _timer.Start(); } private void StopTimer() { _timer.Stop(); _timer.Tick -= _timer_Tick; }
同時我們需要在頁面的Loaded事件和MediaOpened事件中觸發執行基本任務的處理程序。
當CurrentStateChanged和MediaEnded事件觸發時,進行處理MediaElement上的狀態更改。
在ValueChanged事件中處理Slider上的狀態更改。
最後,PointerPressedEvent 和 PointerCaptureLostEvent 事件處理程序處理與Slider的用戶交互。
C#代碼如下:
private void MainPage_Loaded(object sender, RoutedEventArgs e) { timelineSlider.ValueChanged += timelineSlider_ValueChanged; PointerEventHandler pointerpressedhandler = new PointerEventHandler(slider_PointerEntered); timelineSlider.AddHandler(Control.PointerPressedEvent, pointerpressedhandler, true); PointerEventHandler pointerreleasedhandler = new PointerEventHandler(slider_PointerCaptureLost); timelineSlider.AddHandler(Control.PointerCaptureLostEvent, pointerreleasedhandler, true); } void videoElement_MediaOpened(object sender, RoutedEventArgs e) { double absvalue = (int)Math.Round( videoMediaElement.NaturalDuration.TimeSpan.TotalSeconds, MidpointRounding.AwayFromZero); timelineSlider.Maximum = absvalue; timelineSlider.StepFrequency = SliderFrequency(videoMediaElement.NaturalDuration.TimeSpan); SetupTimer(); // Helper method to populate the combobox with audio tracks. PopulateAudioTracks(videoMediaElement, cbAudioTracks); }
最後需要在C#代碼中處理顯示指針位置更改和ValueChanged、CurrentStateChanged和 MediaEnded 事件的事件處理程序。
private bool _sliderpressed = false; void slider_PointerEntered(object sender, PointerRoutedEventArgs e) { _sliderpressed = true; } void slider_PointerCaptureLost(object sender, PointerRoutedEventArgs e) { videoMediaElement.Position = TimeSpan.FromSeconds(timelineSlider.Value); _sliderpressed = false; } void timelineSlider_ValueChanged(object sender, Windows.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e) { if (!_sliderpressed) { videoMediaElement.Position = TimeSpan.FromSeconds(e.NewValue); } } void videoMediaElement_CurrentStateChanged(object sender, RoutedEventArgs e) { if (videoMediaElement.CurrentState == MediaElementState.Playing) { if (_sliderpressed) { _timer.Stop(); } else { _timer.Start(); } } if (videoMediaElement.CurrentState == MediaElementState.Paused) { _timer.Stop(); } if (videoMediaElement.CurrentState == MediaElementState.Stopped) { _timer.Stop(); timelineSlider.Value = 0; } } void videoMediaElement_MediaEnded(object sender, RoutedEventArgs e) { StopTimer(); timelineSlider.Value = 0.0; } private void videoMediaElement_MediaFailed(object sender, ExceptionRoutedEventArgs e) { // get HRESULT from event args string hr = GetHresultFromErrorMessage(e); // Handle media failed event appropriately } private string GetHresultFromErrorMessage(ExceptionRoutedEventArgs e) { String hr = String.Empty; String token = "HRESULT - "; const int hrLength = 10; // eg "0xFFFFFFFF" int tokenPos = e.ErrorMessage.IndexOf(token, StringComparison.Ordinal); if (tokenPos != -1) { hr = e.ErrorMessage.Substring(tokenPos + token.Length, hrLength); } return hr; }
到此為止一個簡單的媒體播放器就基本完成了!運行效果圖如下:
注意:本博文介紹的媒體播放器在實際應用開發中,對於開發者是遠遠不夠理想的,我們需要在此基礎上進一步去優化。
最後引用MSDN中給到的性能注意事項:
音頻和視頻播放是非常耗費資源的操作。有關媒體應用性能的詳細信息,可參閱優化媒體資源。
1)盡可能顯示全屏視頻播放
XAML 框架可以在只呈現視頻時優化呈現的視頻,這種情況類似於全屏播放的情況。 此方法使用較少的電源,並且產生高於同時顯示其他元素情況下的頻率。若要優化媒體播放,請將 MediaElement 對象的大小設置為屏幕的寬度和高度,並且不顯示其他 XAML 元素。在全屏模式下覆蓋 MediaElement頂部的 XAML 元素可能有正當理由 — 例如,隱藏的字幕或臨時傳輸控件 — 但在不需要時可以隱藏這些元素。
2)延遲設置 MediaElement 源
XAML 框架盡可能長地延遲加載 DLL 和創建大型對象。MediaElement在源由 Source 屬性或 SetSource 方法初始設置時完成此操作。僅在用戶准備好播放媒體後設置源,可減少與 MediaElement關聯的絕大部分性能成本。
3)將視頻分辨率與設備分辨率匹配
解碼視頻主要使用內存和 GPU,因此應選擇與要在其上顯示的設備的分辨率接近的視頻格式。例如,解碼高清 (1080) 視頻,然後將其縮小至相當小的尺寸進行顯示會占有不必要的資源。許多應用不會將相同的視頻解碼為不同的分辨率,但如果可用,請使用接近顯示設備的分辨率的解碼。
4)請勿動態顯示 MediaElement 對象。
動畫和媒體播放都會耗費大量系統資源。 在合適的上下文中使用動畫可以創建完美動人的效果。 但是,出於性能方面考慮,請避免在使用 C++、C# 或 Visual Basic 的 Metro 風格應用中動態顯示 MediaElement 對象。
本文出自 “王祖康” 博客,請務必保留此出處http://wzk89.blog.51cto.com/1660752/1033384