WPF的媒体播放器控件,几乎可以播放任何媒体






4.67/5 (3投票s)
一款基于 MPV 播放器的 WPF 媒体播放器控件,可以播放几乎任何媒体文件。
简介,问题
当 WPF 出现时,它在媒体方面是革命性的。您可以轻松地将视频文件嵌入到您的应用程序中,因为它带有一个 MediaPlayer
控件。这个媒体播放器控件依赖于 DirectX 和 Windows Media Player 基础架构来提供编解码器。这意味着理论上(并且在实践中,如果您运气好的话),您可以播放任何您有对应编解码器的视频文件。
不幸的是,当前流行和标准的(H.264、H.265、WebM)编解码器可能并未安装在任何系统上,所以如果您想将媒体文件与您的应用程序一起发布,您也必须安装编解码器,这在某些情况下可能不是理想的选择。
假设您必须在应用程序中包含一个视频,但希望它有多个音频轨道和字幕。对于 MKV 或 WEBM 容器来说这不是问题,但遗憾的是 WPF 不支持多音频流和字幕处理,所以您需要从多个文件中自己创建一个可以执行相同操作的字幕加载/显示和音频播放引擎。
我的解决方案
幸运的是,我们有多种选择来解决简介中提到的问题,每种选择都有不同的成本。
一个好的起点是使用 FFmpeg 实现一个播放器。
FFmpeg 是领先的多媒体框架,能够解码、编码、转码、混流、解混流、流式传输、过滤和播放人类和机器创建的几乎所有内容。它支持最晦涩的古老格式到最前沿的格式。无论它们是由某个标准委员会、社区还是公司设计的。
它具有高度的可移植性,可以在 Linux、OS-X 和 Windows 上运行,并且极易配置。然而,这种方法的最大缺点是您需要用 C/C++ 实现大部分播放器,并且需要自己解决渲染问题,因为 FFmpeg “只是”一个编解码器。
然而,已经有一些视频播放器使用 FFmpeg 进行视频解码并支持嵌入。
其中一个播放器是 MPV,它是 Mplayer 的一个现代且积极开发的衍生版本,而 Mplayer 是最早的 FFmpeg 视频播放器之一。
MPV 播放器提供了一个简单的 C 风格 API,可以将所有必需的代码编译成一个单一的 DLL 文件,足以与您的应用程序一起分发。DLL 文件中的函数可以通过 Platform invoke 访问,因此在 C# 应用程序中易于使用。
获取 MPV
要开始,您需要获取 mpv-1.dll 文件。这是播放器的可嵌入版本,可以从 此链接 下载。
构建的目标是 X64 系统,所以如果您的应用程序仍然需要运行在 X86 上,那么您需要手动编译。另外值得一提的是,为了定制和代码控制的目的,您可能希望为您的发布版本编译自己的版本。
在 Windows 上从源码编译 MPV 是一个巨大的挑战,因为它依赖于许多库。但是,您可以下载他们用于编译 Windows 版本的官方构建环境,该环境位于以下 GIT 仓库:https://github.com/shinchiro/mpv-winbuild-cmake。
它附带有关如何设置开发机的便捷说明。或者,如果您真的下定决心,您可以在 此文档 的帮助下从头开始编译。
实现 & MPV 的 API
对于平台调用部分,我没有重新发明轮子。我重用了 Aurel Hudec 出色的 Mpv.NET 库项目。对于平台调用部分,它使用委托和动态 DLL 加载,因此您可以为 Any CPU 编译您的应用程序,只要您为 X86 和 X64 DLL 文件提供正确的路径。
MPV 在应用程序的独立线程中运行。这意味着某些调用不是即时执行的。例如,在加载文件后,您无法直接获取流的数量,因为播放器必须对其进行处理。我通过在 API 调用之间添加一些延迟来解决了我的控件问题。
MPV 的客户端 API 非常直接。您初始化播放器,然后就可以通过 API 调用向播放器发送 命令。您可以通过另一个调用来获取和设置各种播放器 属性,还有另一个调用用于获取和设置播放器 选项。
您还可以观察属性变化。在我控件的当前实现中,唯一观察到的属性是文件中用于更新滑块的位置。
Mpv.NET 提供了 MpvPlayer
类,它提供了基本功能,但您可以访问所有底层的 API 方法作为方便的 C# 包装器。通过使用它,您可以扩展播放器功能。
我在我的控件中使用了 API 调用接口来实现章节、字幕和音轨选择。
控件
我将控件分两部分实现。MpvPlayer
是播放器控制器,MpvDisplay
负责显示视频。这种两部分解决方案允许灵活的应用布局创建。这两个控件都直接继承自 WPF 的 Control
类,因此它们是自定义控件。我选择这种实现策略,是因为我想只公开对于给定控件上下文真正相关的属性。
MpvDisplay
控件是对 WindowsFormsHost
元素的包装,该元素托管一个标准的 Windows Forms Panel
控件。这是必需的,因为需要一个句柄。这个句柄指定了 MPV 将渲染视频的表面。使用 WindowsFormsHost
是必要的,因为 WPF 控件没有句柄,只有窗口才有句柄。如果我们为 MPV 指定了窗口句柄,它就会用视频覆盖整个窗口内容。
MpvPlayer
控件是 MPV 的控制器。它支持以下功能
- 播放/暂停
- 停止
- 音量调节
- 跳转
- 章节选择(如果文件有章节。特别是 MKV 和 MP4 文件)
- 音轨选择
- 字幕轨道选择
此控件仅在您将 MpvDisplay
控件绑定到其 Display
依赖项属性时才有效。它使用了来自 https://materialdesignicons.com/ 网站的图标。
Using the Code
演示应用程序展示了该控件的易用性。主窗口的 XAML 部分如下所示:
<Window
x:Class="Mpv.Net.WpfTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mpv="clr-namespace:Mpv.Net.Wpf;assembly=Mpv.Net.Wpf"
Title="MainWindow"
Closing="Window_Closing"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Main menu for file selection -->
<Menu>
<MenuItem Header="File">
<MenuItem
Click="MenuItem_Click"
Header="Load file..." />
</MenuItem>
</Menu>
<!-- Mpv controls. Display is the video output -->
<mpv:MpvDisplay
x:Name="Display"
Grid.Row="1" />
<!-- Player controller. Main interaction -->
<mpv:MpvPlayer
x:Name="Player"
Grid.Row="2"
Display="{Binding ElementName=Display}" />
</Grid>
</Window>
代码隐藏文件如下:
using Microsoft.Win32;
using System.Threading.Tasks;
using System.Windows;
using System.ComponentModel;
namespace Mpv.Net.WpfTestApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
//Show a file select dialog and load the selected file
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
var dialog = new OpenFileDialog();
if (dialog.ShowDialog() == true)
{
Player.LoadFile(dialog.FileName);
}
}
// When closing the window stop played video
// and Dispose native dll resources
// A wait is necessary after stop, because
// it doesn't happen instantaneously
private async void Window_Closing(object sender, CancelEventArgs e)
{
if (Player != null)
{
Player.Stop();
await Task.Delay(1000);
Player.Dispose();
}
}
}
}
行动呼吁
该控件目前是概念验证,而非用于生产的现成解决方案,但它具有巨大的潜力。因此,如果您对代码感兴趣或想为项目做出贡献,可以从我的 github 存储库获取代码,该存储库位于 https://github.com/webmaster442/Mpv.Net.Wpf。
关注点
历史
- 2020 年 2 月 18 日:初始版本