【Win 10 应用开发】在后台播放视频

时间:2021-04-02 17:18:33

从 1607 (14393)版本开始,MediaPlayer 类就可以在前台与后台之间无缝播放,你不必再考虑前台与后之间的通信,所以从 14393 开始,你就不需要再用 BackgroundMediaPlayer 类了。无论多媒体资源是前台还是后台播放,都由 MediaPlayer 类负责。与此同时,在 XAML 控件体系中,MediaElement 类也由 MediaPlayerElement 类替代。

这些内容老周就不多说了,因为 SDK 文档的更新日志上都有说明。不管是音频或是视频都统一到 MediaPlayer 类中,正因为如此,实现后台播放视频就显得很是 Easy 了。

不废话,下面咱们来个示例来说明说明。

这个示例简单,老周就不用 Page 了,UI 的根直接来个 Grid 吧。

<Grid
x:Class="App32.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App32"> <Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition />
</Grid.RowDefinitions> <Button Content="打开视频文件" Margin="10,15" Click="OnOpen"/>
<MediaPlayerElement Name="me" Grid.Row="1" Margin="8"/> </Grid>

由于这不是项目默认生成的 Page ,所以注意(看好上面的 x:Class ,它指向对应的类),与它对应的代码文件中的类必须继承 Grid 类,不是 Page 类,代码类的基类就得与 XAML 文档的根元素匹配。

    partial class MainView : Grid
{
public MainView()
{
InitializeComponent();
}
}

如果,你觉得有必要密封这个类(不让别人继承财产),就这样修饰。

    sealed partial class MainView : Grid
{
public MainView()
{
InitializeComponent();
}
}

按钮是干啥用的呢,这样,咱们待会儿运行后,点击按钮来打开一个视频文件,然后在 MediaPlayerElement 控件中播放(显示)。

        private async void OnOpen(object sender, RoutedEventArgs e)
{
FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add(".mp4");
picker.FileTypeFilter.Add(".mkv");
picker.FileTypeFilter.Add(".wmv"); StorageFile file = await picker.PickSingleFileAsync();
if (file == null) return; MediaSource source = MediaSource.CreateFromStorageFile(file);
me.Source = source;
// 开始播放
me.MediaPlayer.Play();
}

在文首老周说过,现在 UWP 应用的多媒体播放都统一由 MediaPlayer 大哥负责,因此,MediaPlayerElement 类会公开一个 MediaPlayer 属性,你可以从这个属性获取到关联的 MediaPlayer 类实例(这个类位于 Windows.Media.Playback 命名空间)。

因此你看到了,在上面最后的代码上,老周调用的 Play 方法是 MediaPlayer 类的,实际上视频是在 MediaPlayer 上管理的。MediaPlayerElement 只负责显示(或者 UI 操作,如暂停等)。

行了,代码这么搞就OK了,不过要想让应用能在后台继续播放,清单文件上的配置是少不了的。打开清单文件(默认是以清单设计器打开),切换到“功能”选项卡,勾选“背景媒体播放”。这是翻译问题,其实应该叫“后台媒体播放”才对,不管它,知道是啥意思就行了,别纠结这些无关紧要的事情。

【Win 10 应用开发】在后台播放视频

清单文件其实是 XML 文档,所以,如果你觉得直接点两下鼠标没法装逼的话,可以手动写的。

首先,你要引入一个 v3 版本的 UWP 专用命名空间。

xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"

然后,你才能进行声明,写到 Package / Capabilities 节点下面。

  <Capabilities>
<Capability Name="internetClient" />
<uap3:Capability Name="backgroundMediaPlayback" />
</Capabilities>

最后,保存一下文件就完事了。

现在运行,随便找个视频播放,然后把程序最小化,或切换到其他应用,然后再切换回去,你会发现,当程序进入后台后,视频仍然在播放。

【Win 10 应用开发】在后台播放视频

其实,上面的实现还不够完善。你想啊,应用移到后台,UI 是看不见的,成了多余的货了,所以,我们妨考虑,在应用扔到后台时销毁 UI,等应用回到前台时再创建 UI。当然了,我们这个例子,界面比较简单,内存占用基本看不出来区别,大约也就是 0.1 MB 的差异。不过为了演示,我们还是处理一下应用程序的这两个事件:

        public App()
{
this.InitializeComponent();
EnteredBackground += App_EnteredBackground;
LeavingBackground +=
App_LeavingBackground;
}

当应用发送到后台,会引发 EnteredBackground 事件,当应用回到前台,则发生 LeavingBackground 事件。

下面我们处理一下。

        private void App_LeavingBackground(object sender, LeavingBackgroundEventArgs e)
{
if(Window.Current.Content == null) //这里为什么要判断 null 不 null 呢?
{
CreateUI(); //重建界面
}
} private void App_EnteredBackground(object sender, EnteredBackgroundEventArgs e)
{
Window.Current.Content = null;//取消对 UI 实例的引用
GC.Collect(); //打扫卫生
}

进入后台后,把当前窗口的内容设为 null,可以解除对 XAML 对象的引用,然后扛一根大扫把来,调用GC成员打扫一下,清理内存。

当重新回到前台时,重建界面,这里为啥我要先看看窗口的内容是否为 null 呢?很简单啊,如果不为 null 说明界面创建好了,那就不必要重复创建了,没意义了。为啥会这样,因为有件事你得知道,当启动应用时,会执行 OnLaunched 方法,这个没疑问吧。然后呢,LeavingBackground 事件会发生一次。而我们在很多时候,OnLaunched 方法中已经建好界面了,正因为如此,在处理 LeavingBackground 事件时,一定要判断一下界面是否创建过了,如果已有界面实例,就不必再创建了。

顺便补一下,CreateUI 方法代码如下。

        void CreateUI()
{
MainView mv = Window.Current.Content as MainView;
if (mv == null)
mv = new MainView();
Window.Current.Content = mv;
}

然后在你运行实例后,又出现新问题了。还记得吧?我们在 XAML 中放了一个 MediaPlayerElement 控件,现在应用进入后台后界面会释放,所以 MediaPlayerElement 控件的实例也会被干掉,等到应用再回到前台,MediaPlayerElement 控件已经是新创建的实例了,不是刚才的那个实例了,这就造成:回到应用后,视频不再播放了

要解决这问题,我们只要想办法让 MediaPlayer 实例的生命周期与应用同步就好了,对,把它变成静态变量(static)。所以,我写了这么个类。

    class MediaPlayerHelper
{
static MediaPlayer _player; public static MediaPlayer CurrentPlayer
{
get
{
if (_player == null)
_player = new MediaPlayer();
return
_player;
}
}
}

这样就可以让它静态了,别忘了,如果 MediaPlayerElement 控件实例是新创建的,会关联地创建一个 MediaPlayer 实例。为了让每个新创建的 MediaPlayerElement 控件实例所关联的 MediaPlayer 实例相同,我们可以调用控件的 SetMediaPlayer 方法。

回到咱们前面那个 Grid 的子类代码中,在构造函数中加上这么一句:

        public MainView()
{
InitializeComponent();
me.SetMediaPlayer(MediaPlayerHelper.CurrentPlayer);
}

这样一来,不管咱们在应用生命周期内创建了多少个 MediaPlayerElement 控件实例,其关联的 MediaPlayer 对象都是我们上面公开的那个 CurrentPlayer 了,这确保了 MediaPlayer 实例的全局唯一性。

现在再次运行示例,在界面元素销毁后再次回到应用程序,视频仍能继续播放。

好了,今天的话题聊完了,该用饭了。