本篇博文主要介紹如何構建一個簡單的媒體播放器。
《快速構建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控件。
<Slider Name="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 對象。