原文:快速构建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. tornado的GET POST方法样品展示

    举例说明get和post该方法的用途: 一.演示样例用的GET方法: import tornado.ioloop import tornado.web class MainHandler(tornad ...

  2. js 性能优化整理之 高频优化

    mousemove 拖拽操作 var count = 0; elem.onmousemove = function(){ count++; // 当计数器为偶数的时候不执行mousemove if( ...

  3. 策略模式设计模式(Strategy)摘录

    23种子GOF设计模式一般分为三类:创建模式.结构模型.行为模式. 创建模式抽象的实例.一个系统独立于怎样创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将 ...

  4. cocos2d-x3.0 windows 环境配置

    cocos2d-x3.0 windows 环境配置 参考Oo泡泡糖oO的CSDN博文 :http://blog.csdn.net/u010296979/article/details/24273393 ...

  5. JavaScript之一: 闭包、执行环境、作用域链

    这是大虾的第一篇博文,大虾试图用最直白的语言去描述出所理解的东西,大虾是菜鸟,水平有限,有误的地方希望路过的朋友们务必指正,谢谢大家了. 从读书时代一路走来,大虾在学习的时候逐渐喜欢上了去追寻根源,这 ...

  6. openstack 网络架构 nova-network + neutron

    openstack网络架构(nova-network/neutron) openstack网络体系中,网络技术没有创新,但用到的技术点很庞杂,包含bridge.vlan.gre.vxlan.ovs.o ...

  7. poj3671Dining Cows(DP)

    主题链接: 啊哈哈,点我点我 题意: 给一个仅仅含有1.2的序列,如何变换n次使序列成为一个非递减的序列,而且使n最小. 思路: 这道题的数据范围是50000,则肯定承受不了n方的复杂度.所以 仅仅能 ...

  8. 自动注册 IIS6 的 MIME 类型

    原文 自动注册 IIS6 的 MIME 类型 由于IIS5和IIS6有很多的MIME类型没有设置,其中还包括了FLV(video/x-flv),上篇文章描述了制作<IIS6 自动安装>,而 ...

  9. git 配置多个SSH-Key(转)

    摘要 我们在日常工作中会遇到公司有个gitlab,还有些自己的一些项目放在github上.这样就导致我们要配置不同的ssh-key对应不同的环境. 目录[-] 1,生成一个公司用的SSH-Key 2, ...

  10. Display Database Image using MS SQL Server 2008 Reporting Services

    原文 Display Database Image using MS SQL Server 2008 Reporting Services With the new release of MS SQL ...