-
-
Notifications
You must be signed in to change notification settings - Fork 55
Bindings
Five projects that turn each platform's native WebRTC into C#. They are the only layer still split across several assemblies, because each binds something genuinely different — and one of them is not a binding at all.
You never reference a binding. They are compiled into the WebRTCme package with
PrivateAssets="all", which keeps them out of your dependency list and out of your code. This page
is for people changing them, or diagnosing something that went wrong beneath the API.
| Project | Binds | Native payload | Technique |
|---|---|---|---|
WebRTCme.Bindings.Blazor |
the browser's WebRTC | none | JSInterop |
Maui/…Maui.Android |
Google's Java SDK | Jars/libwebrtc.aar |
Java binding (AndroidLibrary) |
Maui/…Maui.iOS |
Google's ObjC SDK | WebRTC.xcframework |
ObjC binding (Objective Sharpie) |
Maui/…Maui.MacCatalyst |
Google's ObjC SDK | WebRTC.framework |
ObjC binding (Objective Sharpie) |
Maui/…Maui.Windows |
a C ABI over libwebrtc | native/win-x64/*.dll |
P/Invoke |
Everything except Blazor gets its native half from
WebRTCnative. All of them are committed to this repository, so an ordinary
dotnet build needs no downloads.
Worth being precise, because the layering only makes sense once this is clear. A binding exposes
the native SDK's own shape in C# — org.webrtc.PeerConnection as Java declared it, RTCPeerConnection
as Objective-C declared it. It does not implement the common API and knows nothing about it.
The mapping onto the common surface happens one layer up, in WebRTCme/Platforms/<Platform>/.
See The unified API.
The web already has WebRTC, so there is nothing to bind: the "binding" is a JSInterop wrapper that holds JavaScript object references and calls methods on them.
flowchart LR
cs["WebRTCme/Platforms/Blazor<br/>RTCPeerConnection.cs"]
ext["JsRuntimeExtensions<br/>CallJs · GetJsPropertyValue · AddEventListener"]
ref["JsObjectRef<br/>a handle to a JS object"]
js["wwwroot/JsInterop.js<br/>served as _content/WebRTCme/JsInterop.js"]
api["the browser's RTCPeerConnection"]
cs --> ext --> ref
ext --> js --> api
Three things about it are worth knowing:
The JavaScript ships from WebRTCme, not from this project. Only a Razor SDK project can pack
static web assets, and WebRTCme.csproj is the Razor SDK project, so wwwroot/JsInterop.js lives
there. That is also why the Blazor binding needs no NuGet package of its own. Consumers load it
from _content/WebRTCme/JsInterop.js — see Getting started.
A JS return value is a reference, not its contents. JsObjectRef is a handle; reading a
property means another interop call. Treating a returned object as though it had already been
marshalled is a mistake this codebase has made more than once.
Arguments are reshaped here, in JsRuntimeExtensions. The API models optional dictionary
members as nullable properties, and JSInterop serialises a null property as an explicit null
rather than omitting it — which the browser rejects, because null is not a member of an enum:
TypeError: Failed to construct 'RTCPeerConnection': Failed to read the 'bundlePolicy' property
from 'RTCConfiguration': The provided value 'null' is not a valid enum value of type
RTCBundlePolicy.
Until 26.9.15 every Blazor consumer had to reach into JSRuntime's non-public
JsonSerializerOptions by reflection to fix that, and nothing said so anywhere a consumer would
look. The binding now serialises its own model types through a JsonElement with
WhenWritingNull, so the host's settings do not matter. If you find yourself needing that
reflection workaround again, this has regressed — please report it.
libwebrtc.aar carries classes.jar and four libjingle_peerconnection_so.so, one per ABI. The
project is a net10.0-android library with the .aar as an AndroidLibrary, so the Android SDK
generates C# for the Java classes.
-
Transforms/Metadata.xml,EnumFields.xml,EnumMethods.xmlsteer that generation — renaming, hiding and un-mangling what the generator produces from Java. -
Additions/is where hand-written C# is added to a generated class, if it ever needs to be.
Java bindings are generous: almost everything public in the SDK arrives, so gaps at the API level are usually unmapped rather than unbound.
One packaging detail that cost real time: the Android SDK builds a second .aar for the binding
project itself, which re-emits the same four .so and nothing else — 23 MB of duplication, and two
.aar carrying the same .so in one lib/ folder. WebRTCme.csproj removes it after
_IncludeAarInNuGetPackage, because the SDK offers no property to say no.
Two projects, the same Objective-C SDK, two different packagings of it:
-
iOS takes
WebRTC.xcframework, which is flat and builds correctly on Windows. -
Mac Catalyst takes
WebRTC.framework, which must be a versioned bundle —Versions/Aplus symlinks — or macOS refuses to codesign an app embedding it. Git on Windows cannot check those symlinks out withoutcore.symlinks, so for a long time the framework's binary arrived as a 23-byte text file holding a path, and that is what shipped. It is stored flat here and reassembled bybuild-versioned-framework.sh, and CI builds that slice on a Mac.
ApiDefinitions.cs and StructsAndEnums.cs in each project are the binding definitions, generated
by Objective Sharpie from the framework's headers and then
edited. Regenerating them after a native refresh is described in
WebRTCme.Bindings/README_BuildBindings.txt; the headers need <WebRTC/xxx.h> rewritten to
"xxx.h" before Sharpie will read them.
IOS_BINDINGS_BUILD_PROBLEMS_FROM_WINDOWS.txt records what goes wrong doing this from a PC. Read
it before assuming a failure is yours.
Both slices compile on Windows. Only linking and deploying an app needs a Mac.
There is no Google-published Windows SDK to bind. WebRTCnative builds one instead:
WebRtcInterop.dll exposes a flat C ABI, and this project is a single 576-line Interop.cs
of P/Invoke declarations.
flowchart LR
plat["WebRTCme/Platforms/Windows<br/>lifetime, threading, events"]
interop["Interop.cs<br/>DllImport, one per header line"]
dll["WebRtcInterop.dll"]
lib["libwebrtc<br/>absorbed into the shim"]
plat --> interop --> dll --> lib
Interop.cs is deliberately mechanical: every entry corresponds to a line in
WebRtcInterop/include/Interop.h and nothing in it interprets, wraps or improves on anything.
Keeping it dumb is what makes it reviewable against the header. Lifetime, threading and turning
callbacks into events all happen a layer up.
Three rules govern everything above it, and breaking any of them produces a leak or a deadlock rather than an exception:
- A handle arriving through an out-parameter or a callback is owned by the receiver and needs exactly one matching release.
- A string arriving as a callback argument is borrowed for the duration of the call; a string
returned through an out-parameter must be freed with
StringFree. - No callback may call back in synchronously. Callbacks run on WebRTC's signalling thread, which every peer connection in the process shares, and the interop calls block on it. Feeding a candidate raised by one connection straight into another deadlocks that thread against itself. Copy, queue, and let an ordinary thread make the call.
The full contract is the Interop ABI page of the WebRTCnative wiki.
The native payload is a component build. native/win-x64/ holds WebRtcInterop.dll plus its
import closure — abseil, BoringSSL, protobuf, libc++ — and four Microsoft C++ runtime DLLs so the
binding works on a machine without the Visual C++ redistributable installed. That last group is a
deployment choice rather than a fact, and the folder's own README.md says so. webrtc.dll is
not used: the shim absorbs the WebRTC code it needs.
They land in the package at runtimes/win-x64/native/, not in lib/, because they are plain DLLs
rather than managed assemblies.
Everything builds with plain dotnet build on Windows, including the iOS and Mac Catalyst slices:
dotnet build WebRTCme.slnCI does exactly this on windows-latest, which is the only runner that can build all five target
frameworks in one job — net10.0-windows10.0.22621.0 builds nowhere else. The Apple package
slices are built on a Mac and merged in, for the codesigning reason above.
Before changing a binding project, read the three notes beside them, each of which records a failure that does not name its own cause:
-
WebRTCme.Bindings/README_BuildBindings.txt— building WebRTC by hand, and regenerating Sharpie definitions. Largely historical now that WebRTCnative does the building. -
WebRTCme.Bindings/README_NativeSdkVersions.txt— where each artifact came from. -
WebRTCme.Bindings/IOS_BINDINGS_BUILD_PROBLEMS_FROM_WINDOWS.txt— Apple bindings from a PC.
Less relevant than it sounds. A binding surface is only interesting where the API above it reaches
for something and finds nothing — an unbound method that nothing calls costs nobody anything. What
is actually unreachable per platform is in What works where, and the engineering
record is doc/KnownGaps.md.
Next layer up: The unified API.
Start here
The stack
- WebRTCnative
- Bindings
- The unified API
- Middleware
- Connection
- · Signaling (mesh)
- · MediaSoup (SFU)
- Demo apps
Reference
When it breaks