!!!!! This is NOT an official libVLC project !!!!!
This project started because I needed libvlc_video_set_output_callbacks, the new video rendering
API from LibVLC 4.0.0. I believe it provides a cleaner solution to airspace issues in WPF and other
UI frameworks. While LibVLCSharp is very well-designed,
the v4 prerelease is not yet officially stable, and its interop layer is not fully automated. So this
project uses GitHub Actions + ClangSharpPInvokeGenerator to keep up-to-date P/Invoke bindings for VLC.
I leveraged AI for coding assistance. Most of the glue code and scripts were AI-generated, and I have
manually reviewed every single piece of code with fine-tuning on encapsulated implementation details.
A .NET binding for libVLC 4.x with two layers in one package:
- Raw interop (
LibVLCSharp.Core.Interop) — P/Invoke bindings auto-generated from the libvlc 4.x headers withClangSharpPInvokeGenerator, following the Csnake_casenames so the C documentation maps directly onto the API (similar toFFmpeg.AutoGen). - Managed API (
LibVLCSharp.Core) — a hand-written object-oriented layer over the interop:LibVLC,Media,MediaPlayer,MediaList,MediaListPlayer,Equalizer,Picture,RendererItem/RendererDiscoverer,MediaDiscoverer,Dialog, … withIDisposablelifetime, managedstring/enumparameters, and standard .NET events.
dotnet add package LibVLC4Sharp.Core --prereleasePackages are published as prerelease nightlies (
4.0.0-nightly.<date>) that track the libvlc 4.x headers, so--prerelease(or "Include prerelease" in your IDE) is required to install them.
using System;
using LibVLCSharp.Core;
// Resolve a native libvlc 4.x once, before any libvlc call (see "Native linking"):
LibVLC.Initialize();
// LibVLC.Use(LibVLCLinkMode.Path, @"C:\path\to\vlc-4.x\"); // or an explicit folder
using var vlc = new LibVLC("--no-video-title-show");
using var media = Media.FromPath(@"C:\video.mp4");
using var player = vlc.CreateMediaPlayer(media);
player.Playing += (s, e) => Console.WriteLine("playing");
player.TimeChanged += (s, e) => Console.WriteLine($"t = {e.TimeMs} ms"); // readonly-struct payload
player.EncounteredError += (s, e) => Console.WriteLine("error");
player.Play();
Console.ReadLine();using System;
using LibVLCSharp.Core.Interop;
using static LibVLCSharp.Core.Interop.libvlc;
LibVLC.Initialize();
string version = ((IntPtr)libvlc_get_version()).GetUtf8(); // byte* UTF-8 -> managed string
Console.WriteLine(version); // or: LibVLCSharp.Core.LibVLC.VersionA single package, LibVLC4Sharp.Core, multi-targeting netstandard2.0;netstandard2.1;net8.0.
The NuGet package id uses the
LibVLC4Sharpprefix (4 = LibVLC 4.0) to avoid the reservedLibVLCSharp.*prefix on nuget.org. The C# namespace remainsLibVLCSharp.Core.
All three classic linking strategies are covered from the one assembly via the static members on
LibVLCSharp.Core.LibVLC — call them once at startup, before any libvlc call (the choice is not
hot-swappable). A UI layer (e.g. WPF) configures this in its App startup.
LibVLC.Initialize(); // auto-discover (default)
LibVLC.Use(LibVLCLinkMode.Default); // OS / per-RID search
LibVLC.Use(LibVLCLinkMode.Path, @"C:\VLC\"); // explicit folder or file (= LibVLC.UsePath)
LibVLC.Use(LibVLCLinkMode.Static); // symbols in the main program (= LibVLC.UseStatic)| Mode | net7.0+ (resolver) | netstandard2.0 / 2.1 |
|---|---|---|
Default |
NativeLibrary default search |
OS loader resolves libvlc |
Path |
NativeLibrary.Load(path) |
pre-loads the binary (LoadLibrary/dlopen) |
Static |
NativeLibrary.GetMainProgramHandle() |
not supported (throws) |
On net7.0+ this uses NativeLibrary.SetDllImportResolver; on netstandard targets Static requires a
net7.0+ TFM. LibVLC.Initialize() auto-discovers libvlc in libvlc/<rid> and
runtimes/<rid>/native (loading libvlccore before libvlc); pass a directory to
Initialize(dir) to override.
This repo does not ship libvlc. Provide a LibVLC 4.x build yourself (the output-callbacks
API requires 4.x — the VideoLAN.LibVLC.Windows NuGet only carries 3.x). Drop libvlc.dll,
libvlccore.dll and the plugins/ folder into runtimes/win-x64/native/ (or any folder) and:
LibVLC.UsePath(@"C:\path\to\vlc-4.x\"); // directory containing libvlc.dll- Lifetime — every wrapper derives from
NativeReference(IDisposable+ finalizer, releases the native handle once). Each exposes apublic static implicit operator libvlc_*_t*so it passes straight into raw interop. - Strings —
char *parameters are managedstring(UTF-8 marshaled byUtf8Marshaler);char *return values staybyte*/sbyte*and are read with theIntPtr.GetUtf8()helper. - Enums — public PascalCase enums (no
Vlcprefix), e.g.State,Meta,TrackType, generated at compile time from the interop enums (never drift). - Events — standard
EventHandler(no payload) /EventHandler<TArgs>whereTArgsis a zero-allocationreadonly struct(e.g.TimeChangedEventArgs.TimeMs,MetaChangedEventArgs.Meta). Events live on the owning object (media.MetaChanged,player.TimeChanged, …) and attach the native callback on the first subscriber / detach on the last. - Callbacks — software/accelerated rendering and audio callbacks (
SetVideoCallbacks,SetOutputCallbacks,SetAudioCallbacks,WatchTime, …) take delegates that the wrapper keeps alive;Media.FromStream/FromCallbackswrap a managedStream/callbacks as input. - Dialogs —
Dialogroutes libvlc login/question/progress/error interactions to events.
The managed surface covers essentially the whole libvlc public API; the few intentionally
not wrapped (*_retain/*_hold, *_user_data, video_new_viewpoint) are handled internally
or replaced by idiomatic .NET equivalents.
| Package | Framework | Status |
|---|---|---|
LibVLC4Sharp.WPF |
net8.0-windows (AnyCPU) |
D3D9 and D3D11 output callbacks → D3DImage |
(An Avalonia VideoView is planned — see TODO.) The WPF VideoView (namespace LibVLCSharp.WPF)
references LibVLCSharp.Core and uses libvlc_video_set_output_callbacks. WPF can only composite a
Direct3D9 surface (via D3DImage), so both engines end up presenting through a host IDirect3D9Ex
device's shared surface — no HWND overlay, so the airspace problem is gone (you can freely overlay
WPF controls, use opacity, transforms, etc.). The engine is selected by the Engine property
(VideoEngine, default D3D9) and the output is created once on first use:
D3D9(default, widest compatibility) — libvlc renders into a Direct3D9 texture shared with the host D3D9Ex device. Based on VLC'sdoc/libvlc/d3d9_player.c.D3D11— we create theID3D11Deviceand hand its context to libvlc (enabling D3D11VA hardware decode); libvlc renders into a D3D11 texture that is bridged to the D3D9Ex surface via a shared handle. Based on VLC'sdoc/libvlc/d3d11_player.cpp.
The D3D9/D3D11 interop is hand-written (Interop/Direct3D9.cs, Interop/Direct3D11.cs, vtable-index
calls), so the package is AnyCPU with no extra dependencies.
// XAML: xmlns:vlc="clr-namespace:LibVLCSharp.WPF;assembly=LibVLCSharp.WPF"
// <vlc:VideoView x:Name="Video" Engine="D3D11" />
var view = new LibVLCSharp.WPF.VideoView { Engine = VideoEngine.D3D11 }; // set Engine before load
// ... add to your visual tree ...
view.Attach(player); // a managed MediaPlayer — call before play (or bind the MediaPlayer property)WPF rendering is implemented and compiles against the generated bindings, but still needs validation on a machine with a GPU + libvlc 4.x. The Avalonia renderer (D3D11 → Avalonia 11
CompositionDrawingSurface) is the next step.
- no
libvlc_printerr— C# has poor compatibility with C variadic arguments. - no
libvlc_role_Last— equivalent tolibvlc_role_Test. - libvlc logging (
LibVLC.SetLog) exposes structured fields (level/module/file/line/object); the printf message is not formatted (portableva_listformatting is unavailable).
The interop is generated offline and committed to the repo — no source generator runs the ClangSharp step at consumer build time (the enum/delegate alias generators below DO run at build).
Generation (tools/):
tools/fetch-headers.ps1— shallow + sparsegitcheckout ofvideolan/vlc'sinclude/vlcheaders intotools/.vlc/.tools/generate.ps1— runsfetch-headers.ps1(unless-SkipFetch), restores theClangSharpPInvokeGeneratordotnet tool, and runs it with thegenerate.rspresponse file to emitsrc/LibVLCSharp.Core/Generated/LibVLC.Interop.g.cs. The.rspcarries the libvlc-specific customizations:const char *parameters →string+ UTF-8 marshaling, callback-typedef params → the generated delegate type (notIntPtr), and[MarshalAs(U1)]on_Bool.tools/fetch-libvlc.ps1— downloads an official libvlc 4.x Windows nightly (libvlc.dll+libvlccore.dll+plugins/) so the sample can run; not part of binding generation.
# Regenerate locally (works on both pwsh 7+ and Windows PowerShell 5.1)
pwsh tools/generate.ps1
# or
powershell -ExecutionPolicy Bypass -File tools/generate.ps1Build: LibVLCSharp.Core compiles the generated interop, the hand-written managed wrappers, and
a few helpers under src/LibVLCSharp.Core/Utils/ (NativeTypeNameAttribute, the GetUtf8 string
helper, the Utf8Buffer UTF-8 marshaling, and the LibVLC partial that holds the native-linking
logic). Two Roslyn incremental source generators in LibVLCSharp.Core.Generator (referenced as
analyzers) emit, at compile time, PascalCase enum aliases and PascalCase delegate twins over
the interop types.
CI (.github/workflows):
check-vlc-update.ymlruns daily: regenerates the bindings from the latest libvlc headers and, if the generated interop actually changed, derives the version from the header macros (libvlc_version.h) asMAJOR.MINOR.REVISION-nightly.<UTC-date>, writes it intoDirectory.Build.props, commits the regenerated bindings, tagsv<version>, and invokes the publish workflow.nuget-publish.yml(reusable; also runs on av*tag push or manual dispatch): builds the whole solution (LibVLC4Sharp.slnx) in Release — the packable projects (LibVLC4Sharp.Core+LibVLC4Sharp.WPF) emit their.nupkg/.snupkgviaGeneratePackageOnBuild— and pushes every package undernupkgs/to nuget.org. Authentication uses NuGet Trusted Publishing (OIDC): the job runs in theproductionenvironment and exchanges a GitHub OIDC token for a short-lived API key viaNuGet/login— no long-livedNUGET_API_KEYsecret is needed.
tools/ fetch-headers.ps1, fetch-libvlc.ps1, generate.ps1, generate.rsp (offline generation + runtime fetch)
src/ LibVLCSharp.Core (interop + managed wrappers + native-linking loader)
LibVLCSharp.Core.Generator (enum + delegate alias source generators)
LibVLCSharp.WPF (D3D9/D3D11 + D3DImage VideoView, AnyCPU)
samples/ LibVLCSharp.WPF.Sample
- Validate the WPF
VideoView(both D3D9 and D3D11 engines) on a GPU + libvlc 4.x machine. - Implement the Avalonia
VideoViewrenderer (D3D11 output callbacks → Avalonia 11 compositor GPU interop).
All source code in this repository is licensed under the MIT License.
This library dynamically links against libVLC at runtime. libVLC is distributed under the GNU Lesser General Public License v2.1 (LGPLv2.1). This repository does NOT contain any libVLC binaries or source code; users must install VLC runtime separately. VLC source code: https://code.videolan.org/videolan/vlc