在WPF程序中,我们可以使用 MediaElement 控件播放视频,只要我们的系统中拥有相应的解码器即可。
即便Windows系统中没有相关的解码器,我们也可以通过安装一些解码器补丁程序(如 K-Lite Basic ),来增强和补完系统的媒体解码器。

虽然播放格式的问题可以通过第三方插件解决,然而 MediaElement 有一个致命的缺点,那就是它仅支持从本地或是网络地址来加载媒体文件,并不能通过数据流加载媒体,因此在使用时有许多的限制。

好在我们可以通过使用第三方的媒体库 VlcLib 来解决这个问题,让我们的 WPF 或是 WinForm 程序实现从文件流、内存流中加载媒体文件,并且其自身就拥有丰富的解码器插件,因此并不需要第三方或是系统解码器的支持,即可播放绝大部分的视频格式!

首先,我们需要获取 VlcLib 媒体库

VlcLib 采用 LGPL 协议,如果没有修改过源码,可以将其用于闭源或商业程序。
我们要使用 VlcLib,只需要调用它的 libvlc.dlllibvlccore.dllplugins 文件夹。
VlcLib 分为x86与x64两个版本,你可以根据需求选择,或者将两个版本集合到一起。

在 nuget 上可以找到最新 3.0+ 版的 VlcLib

https://www.nuget.org/packages/VideoLAN.LibVLC.Windows/

我个人觉得 3.x 版不仅容量大,而且启动速度也比 2.x 版慢上许多,所以我推荐您使用 2.2.5.1 版。

旧版本 VlcLib 获取方式

1. 在官方网站下载旧版本的 VLC 播放器,提取里面的 libvlc.dlllibvlccore.dllplugins 文件夹即可!
https://www.videolan.org/vlc/releases/

2. 在官方资源站上可以找到所有的源码与VLC安装程序,按照上面的方法提取 VlcLib 库文件即可。
https://download.videolan.org/vlc/

其二,安装 VlcDotNet,部署 VlcLib

在 nuget 上安装 Vlc.DotNet.Core Vlc.DotNet.Core.Interops两个库文件,并根据你所编写的程序类型,选择安装Vlc.DotNet.FormsVlc.DotNet.Wpf库!

https://www.nuget.org/packages/Vlc.DotNet.Core/
https://www.nuget.org/packages/Vlc.DotNet.Core.Interops/
https://www.nuget.org/packages/Vlc.DotNet.Forms/
https://www.nuget.org/packages/Vlc.DotNet.Wpf/

将 VlcLib 的资源文件(libvlc.dlllibvlccore.dllplugins文件夹)丢入程序目录下的 \libvlc\win64\ 文件夹下,这个路径你可以自己定义!
如果你想包含x86版本的,还可以将x86类库放到 \libvlc\win32\ 文件夹下!

在工程项目中添加引用(nuget会自动引用):
Vlc.DotNet.Core
Vlc.DotNet.Core.Interops
Vlc.DotNet.Wpf 或是 Vlc.DotNet.Forms

至此,VLC的运行环境便已经搭好!

使用 VlcDotNet 播放视频

以下所有示例全部采用 WPF,WinForm 可直接使用工具栏中的Vlc控件,基本的操作流程应该是差不多的!

首先,我们在窗体主标签中添加对Vlc命名空间的引用:
<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="519" Width="693"

    xmlns:Vlc="clr-namespace:Vlc.DotNet.Wpf;assembly=Vlc.DotNet.Wpf"
>
...
之后可以在主容器内添加VLC控件:
<Grid Name="GridMain">
    <Vlc:VlcControl Name="vlcControl" Background="Transparent" />
</Grid>
然后,我们在窗体的 Loaded 事件中进行一些基本的设置:
Dim WithEvents MediaPlayer As Vlc.DotNet.Core.VlcMediaPlayer

Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
    ' 根据当前系统设置 VlcLib 的目录
    Dim vlcLibDirectory = New DirectoryInfo(Path.Combine(Environment.CurrentDirectory, "libvlc", If(IntPtr.Size = 4, "win32", "win64")))
    ' 不准备支持32位系统的话直接用这行代码
    'Dim vlcLibDirectory = New DirectoryInfo(Path.Combine(Environment.CurrentDirectory, "libvlc\win64"))

    ' 创建播放器实例
    Dim options = New String() {}
    vlcControl.SourceProvider.CreatePlayer(vlcLibDirectory, options)

    ' 从实例中获取播放器本体
    MediaPlayer = vlcControl.SourceProvider.MediaPlayer
End Sub
再之后,请在窗体内添加一个按钮,我们使用此按钮来加载一个视频:
Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button1.Click
    Dim mediaPath As String = "文件路径,可以是任何常规的视频媒体,即便Windows系统内没有安装过该解码器!"

    ' 音量控制
    MediaPlayer.Audio.Volume = 25

    ' 播放速度
    MediaPlayer.Rate = 3.5

    ' 自动重播
    Dim opts As String()
    If MediaPlayer.Manager.VlcVersionNumber.Major < 3 Then
        opts = {"input-repeat=-1"} '2.x版
    Else
        opts = {"input-repeat=65535"} '3.x版
    End If

    ' 播放本地文件(参数必须是FileInfo)
    MediaPlayer.SetMedia(New FileInfo(mediaPath), opts)

    ' 开始播放
    MediaPlayer.Play()
End Sub
如果一切正常,那么你窗体上的VLC播放器便会开始启动,并且会以25%音量、3.5倍速、循环播放你所设置的视频!

需要注意的是:
  • VLC3的循环需要设置参数为input-repeat=65535,而VLC2可以设置为input-repeat=-1
  • 音量控制似乎对 VLC2.2 以下版本无效?
  • 设置媒体文件时,如果是本地文件,则必须以 FileInfo 对象包裹,否则Vlc会把它当作网络路径来解析,导致程序出错!

除了解析本地媒体之外,VLC可以很容易地解析网络与流媒体!

解析网络路径的视频

' 其他代码参考上面,只需要在播放时添加一个网络URL即可
MediaPlayer.Play("http://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_h264.mov")

播放文件流、内存流视频

' 播放内存流(注意:必要时务必清除内存流)
Dim bs As Byte() = File.ReadAllBytes(mediaPath)
MediaStream = New MemoryStream(bs)
MediaStream.Position = 0 '内存流需重定向位置
MediaPlayer.Play(MediaStream, opts)

' 播放文件流(注意:必要时务必关闭文件流)
MediaStream = New FileStream(mediaPath, FileMode.Open)
MediaPlayer.Play(MediaStream, opts)

最后需要注意的事情

结束程序前,记得关闭 VLC 播放器

不管你是用 WPF 还是 WinForm,我们本质上都是在调用 VLC 为我们提供的播放器控件,所以当我们要关闭自己的程序前,一定要记得先关闭 VLC 的播放器控件,否则程序会出现报错、或者导致内存泄漏等问题!

WPF 程序在窗体的 Closing 事件中添加下面的代码即可:
Private Sub MainWindow_Closing(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles Me.Closing
    If vlcControl IsNot Nothing Then
        vlcControl.SourceProvider.Dispose()
        vlcControl.Dispose()
    End If
End Sub

如何获得进度条进度?如何得知视频即将播放完毕?

我将 MediaPlayer 写成 Dim WithEvents MediaPlayer As Vlc.DotNet.Core.VlcMediaPlayer,在VB中即可使用后期绑定来获取 MediaPlayer 的事件!
其中:
PositionChanged 可获得视频的播放进度,但是请注意,你无法获得 100% 这个进度,最多到9x%。
EndReached 在视频播放完毕时激活,但是它无法阻止视频停止,你也不能在播放完毕后重新定位视频位置。
Stopped 在强制停止、关闭媒体时发生。
其他事件请参考 Vlc.DotNet.Core.VlcMediaPlayer 对象。

如何在媒体播放完毕前暂停、或者重新调整内容的位置?

EndReached 无法阻止VLC对于媒体的自动关闭行为,因此我们需要使用VLC的内部命令 play-and-pause 来让VLC在播放完毕后自动暂停,然后在 MediaPlayer.Paused 的暂停事件中判断当前位置是否为媒体的最后一帧内容,再决定是否要重新设置媒体的播放位置并重新播放!

一个简易示例:
' 设置播放器命令参数
Dim opts As String() = {"play-and-pause"} '播放完毕后自动暂停,而不是立刻关闭媒体
MediaPlayer.SetMedia(New FileInfo(mediaPath), opts)
MediaPlayer.Play()

' 在 Paused 事件中重定位
Private Sub MediaPlayer_Paused(sender As Object, e As Vlc.DotNet.Core.VlcMediaPlayerPausedEventArgs) Handles MediaPlayer.Paused
    ' 在这里判断是否是最后一帧(我自己的判断方法也不完美,就不分享了)
    ' 重新定位并播放
    MediaPlayer.Position = 0
    MediaPlayer.SetPause(False)
End Sub
注意:此方法虽然可以让媒体自动重播,然而效率并不如 "input-repeat" 命令,因此不推荐使用此方法设置重播。

如何获得更多的媒体信息?

' 在获取媒体信息之前,必须要先设置过媒体
MediaPlayer.SetMedia(New FileInfo(mediaPath))

MediaInfo = MediaPlayer.GetMedia
MediaInfo.Parse() '开始解析媒体数据
MsgBox(MediaInfo.Duration.ToString) '总长度
MsgBox(MediaPlayer.Length) '媒体文件总长度,ms
MsgBox(MediaPlayer.Time) '当前播放时间
MediaInfo 可以获得一些内置信息,请自行参考 Vlc.DotNet.Core.VlcMedia

如何跳转播放位置

MediaPlayer.Time = 62 * 1000 '62s
MediaPlayer.Position = 0.12 '12%

如何切换播放、暂停视频?

MediaPlayer.SetPause(MediaPlayer.IsPlaying)

如何关闭当前的媒体并清除资源?

MediaPlayer.ResetMedia()

更多VLC内部命令行指令

https://wiki.videolan.org/VLC_command-line_help/
应用到 VlcLib 的时候记得去掉前面的 --

最后,再来贴一个完整的VLC重载流程

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="519" Width="693"
    xmlns:Vlc="clr-namespace:Vlc.DotNet.Wpf;assembly=Vlc.DotNet.Wpf"
>
    <Grid Name="GridMain">
        <Vlc:VlcControl Name="vlcControl" Background="Transparent" />
    </Grid>
</Window>
' 一个完整的重载流程
Imports System.IO

Dim WithEvents MediaPlayer As Vlc.DotNet.Core.VlcMediaPlayer
Dim WithEvents MediaInfo As Vlc.DotNet.Core.VlcMedia
Dim MediaStream As Stream
Dim MediaPath As String = ""


' 初始化控件
If vlcControl IsNot Nothing Then
    GridMain.Children.Remove(vlcControl) '从容器中移除
    vlcControl.Dispose()
    vlcControl = Nothing

    If MediaStream IsNot Nothing Then '关闭媒体流
        MediaStream.Dispose()
        MediaStream = Nothing
    End If

    'GC.Collect() 'GC回收,会拖慢速度
End If

vlcControl = New Vlc.DotNet.Wpf.VlcControl()
vlcControl.Background = Brushes.Transparent
GridMain.Children.Insert(0, vlcControl)    '重新插入容器

' 重建播放器
Dim vlcLibDirectory = New DirectoryInfo(Path.Combine(Environment.CurrentDirectory, "libvlc", If(IntPtr.Size = 4, "win32", "win64")))
Dim options = New String() {}
vlcControl.SourceProvider.CreatePlayer(vlcLibDirectory, options)

' 初始化变量
MediaPlayer = vlcControl.SourceProvider.MediaPlayer

' 播放
MediaPath = "媒体路径..."

MediaPlayer.Audio.Volume = 25
MediaPlayer.Rate = 3.5
Dim opts = If(
    MediaPlayer.Manager.VlcVersionNumber.Major < 3, 
    {"input-repeat=-1"}, 
    {"input-repeat=65535"}
)

MediaPlayer.SetMedia(New FileInfo(MediaPath), opts)
MediaInfo = MediaPlayer.GetMedia()
MediaInfo.Parse() '解析媒体信息

MediaPlayer.Play()