原文:快速构建Windows 8风格应用21-构建简单媒体播放器

本篇博文主要介绍如何构建一个简单的媒体播放器。

《快速构建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控件声明了MediaOpenedMediaEndedCurrentStateChangedMediaFailed 事件。

最后设置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与媒体同步,并将DispatcherTimerInterval属性值设置为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上的状态更改。

最后,PointerPressedEventPointerCaptureLostEvent 事件处理程序处理与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#代码中处理显示指针位置更改和ValueChangedCurrentStateChangedMediaEnded 事件的事件处理程序。

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 对象。

快速构建Windows 8风格应用21-构建简单媒体播放器的更多相关文章

  1. 快速构建Windows 8风格应用15-ShareContract构建

    原文:快速构建Windows 8风格应用15-ShareContract构建 本篇博文主要介绍共享数据包.如何构建共享源.如何构建共享目标.DataTransferManager类. 共享数据包 Da ...

  2. 快速构建Windows 8风格应用13-SearchContract构建

    原文:快速构建Windows 8风格应用13-SearchContract构建 本篇博文主要介绍如何在应用中构建SearchContract,相应的原理已经在博文<快速构建Windows 8风格 ...

  3. 快速构建Windows 8风格应用32-构建辅助磁贴

    原文:快速构建Windows 8风格应用32-构建辅助磁贴 引言 Windows Phone中,我们开发者可能会开发的一个功能点是将数据列表中某一项"Pin To Start(固定到开始屏幕 ...

  4. 快速构建Windows 8风格应用17-布局控件

    原文:快速构建Windows 8风格应用17-布局控件 本篇博文主要介绍三种常用的布局控件:Canvas.Grid.StackPanel. Panel类是开发Windows 8 Store应用中一个重 ...

  5. 快速构建Windows 8风格应用14-ShareContract概述及原理

    原文:快速构建Windows 8风格应用14-ShareContract概述及原理 本篇博文主要介绍Share Contract概述.Share Contract实现原理.实现Share Contra ...

  6. 快速构建Windows 8风格应用9-竖直视图

    原文:快速构建Windows 8风格应用9-竖直视图 本篇博文主要介绍竖直视图概览.关于竖直视图设计.如何构建竖直视图 竖直视图概览 Windows 8为了支持旋转的设备提供了竖屏视图,我们开发的应用 ...

  7. 快速构建Windows 8风格应用10-设备方向

    原文:快速构建Windows 8风格应用10-设备方向 本篇博文主要介绍常用支持Windows 8操作系统设备的方向.如何获取当前设备方向.DisplayProperties类. 常用支持Window ...

  8. 快速构建Windows 8风格应用11-语义缩放

    原文:快速构建Windows 8风格应用11-语义缩放 本篇博文主要介绍为什么需要语义缩放.什么是语义缩放.如何构建语义缩放. 为什么需要语义缩放 如果用过Windows 8系统的开发者都知道在Win ...

  9. 快速构建Windows 8风格应用12-SearchContract概述及原理

    原文:快速构建Windows 8风格应用12-SearchContract概述及原理 本篇博文主要介绍Search Contract概述.Search Contract面板结构剖析.Search Co ...

随机推荐

  1. Lichee(三) Android4.0该产品的目标文件夹,Lichee链接---extract-bsp

    由<Lichee() 在sun4i_crane平台下的编译>介绍了编译lichee的基本情况,我们终于得到了编译后的结果例如以下: out/ ├── android │   ├── bIm ...

  2. SQL Server 作业监控

    原文:SQL Server 作业监控 在讲解SQLServer Agent Jobs之前,先要讲解msdb. Msdb是SQLServer的系统数据库之一,用于存储SQLServer的配置.元数据等信 ...

  3. gradle中使用cobertura做代码覆盖(转)

    gradle很好用,但是默认是没有代码覆盖功能的,只好自己写.曾经在网上找到过别人的一段脚本,虽然也能用,但是有一些不爽的地方,一个原因是它不支持对层级工程中全部代码的覆盖,另一个原因是它用替换bui ...

  4. 项目构建之maven篇:3.m2eclipse使用

    m2eclipse的安装 略 设置maven文件夹 设置用户个性化的maven配置 导入mavenproject 源码下载 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkb ...

  5. oracle的分页查询碰到的一个小问题

    订单表.与订单信息表(多个订单信息列有同一个订单id) 查出全部订单以及其信息并依照订单分页 select * from( select a. * , (DENSE_RANK() OVER(ORDER ...

  6. Struts2_1_struts2建立一个执行环境

    1)最低需要进口jar包: commons-fileupload-1.2.1.jar.commons-logging-1.0.4.jar. freemarker-2.3.15.jar.ognl-2.7 ...

  7. NSIS:检查某注册表键是否存在

    原文NSIS:检查某注册表键是否存在 ;定义注册表主键!define HKEY_CLASSES_ROOT           0x80000000!define HKEY_CURRENT_USER   ...

  8. Git Config(转)

    一.Git已经在你的系统中了,你会做一些事情来客户化你的Git环境.   你只需要做这些设置一次:即使你升级了,他们也会绑定到你的环境中.你也可以在任何时刻通过运行命令来重新更改这些设置.      ...

  9. SQLServer表变量对IO及内存影响测试

    原文:SQLServer表变量对IO及内存影响测试 1. 测试创建表变量对IO的影响 测试创建表变量前后,tempdb的空间大小,目前使用sp_spaceused得到大小,也可以使用视图sys.dm_ ...

  10. 应用ExcelPackage导出Excel

    前阵子工作需要,要实现从数据库中导出数据到Excel.老套路 先去百度上查阅资料,发现了以下几种方法: 1:将DataGrid控件中的数据导出Excel 2:将dataview导出excel 3:从网 ...