-
-
Notifications
You must be signed in to change notification settings - Fork 55
Releases
Versions, what changed, how to upgrade, and what is inside the packages.
Two packages, always published together:
WebRTCme |
The API, the five bindings, and their native halves |
WebRTCme.Middleware |
The middleware and the connection layer. Depends on WebRTCme
|
Never mix versions. WebRTCme.Middleware depends on WebRTCme at exactly the version it was
built against, so shipping one without the other leaves a dependency nobody can resolve. That is
why they are published together and never separately.
Both carry all five target frameworks, so you reference the same package whatever you are building and NuGet picks the slice that matches:
net10.0 · net10.0-android · net10.0-ios · net10.0-maccatalyst · net10.0-windows10.0.22621.0
The version is the build date, written normalised. 26.9.21 is 21 September 2026.
Normalised matters: it is 26.9.9, never 26.09.09. NuGet strips leading zeros, so a padded
version becomes an unpadded one on the way in — and every place that builds a file name from
<Version> then stops matching. Writing it normalised in the first place avoids a whole class of
quiet breakage.
There is no semantic-versioning promise in this scheme. Read this page before upgrading across a gap.
The 2.0.0 line is the .NET 8 line, and it is frozen. There is no back-port, and there will not be one: the .NET 10 work is a whole-solution migration, not a target-framework switch.
If you are on .NET 8, stay on 2.0.0. If you are moving to .NET 10, take 26.9.21.
Three edits, and no source changes beyond them.
WebRTCme.Api, WebRTCme.Bindings and WebRTCme.Bindings.Blazor are no longer published. Their
assemblies are inside the WebRTCme package. Delete any PackageReference to them; referencing
WebRTCme alone replaces all three.
WebRTCme.Api went furthest: it is no longer a separate assembly at all, its types are compiled
into WebRTCme.dll. They kept the WebRTCme namespace, so using WebRTCme; and every type name
are unchanged. Only something bound to the assembly — a raw <Reference Include="WebRTCme.Api" />,
or a pre-compiled third-party library built against it — has to be rebuilt.
-<script src="_content/WebRTCme.Bindings.Blazor/JsInterop.js"></script>
+<script src="_content/WebRTCme/JsInterop.js"></script>It ships with the WebRTCme package now rather than the Blazor bindings package — which is why
that package no longer needs to exist.
New, and your restore fails without it:
<PackageReference Include="Microsoft.Maui.Controls" Version="10.0.101" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="10.0.101" />WebRTCme.Middleware, WebRTCme.Middleware.Blazor and WebRTCme.Middleware.Maui became one
assembly, but all three namespaces are kept, so existing using directives keep working.
MAUI XAML naming the assembly explicitly (assembly=WebRTCme.Middleware.Maui) is better written
assembly=WebRTCme.Middleware, though the XAML compiler resolves the old spelling anyway by
searching referenced assemblies.
Every package built before it carried a broken WebRTC.framework. Git cannot write symlinks on
a Windows checkout without core.symlinks, so the framework's binary arrived as a 23-byte text file
holding a path — and that is what was packed. The framework is stored flat now and reassembled on a
Mac, and the native binary is really in it.
A bug-fix release, and one of the bugs is the kind that only shows up after the call has already gone wrong. Everyone on 26.9.18 should take it. Android consumers especially.
The Android binding did not report RTCPeerConnectionState at all. It synthesised one from the
ICE connection state, and raised the event exactly once - on leaving Connected, at which moment
the state reads Disconnected. Every later transition raised nothing, so Failed and Closed
never arrived.
An application that watches the peer connection to decide a call is over therefore waited for ever on Android. A phone that lost its network sat showing "reconnecting" with no way out, while the same code on the other four platforms ended the call in seconds.
onConnectionChange on PeerConnection.Observer has been there for years and was already bound.
It is used now. iOS, Mac Catalyst, Windows and Blazor were always correct; this was Android alone.
Worth a moment before upgrading: if your handler ends the call on the first Failed, that arm
is reachable on Android for the first time. A transient failure that a restart could have recovered
will now hang up.
IRTCPeerConnection.RestartIce() threw NotImplementedException on Android, iOS and Mac Catalyst
and NotSupportedException on Windows. Only Blazor implemented it. All five work now: the three
native SDKs had the call and the bindings never made it, and Windows needed a new export in the
interop ABI.
The trap alongside it is worse, because nothing failed. RTCOfferOptions.IceRestart was read by
exactly one binding. Blazor passes the options object to the browser's createOffer, which
honours iceRestart. Android passed new MediaConstraints(), iOS and Mac Catalyst passed
new RTCMediaConstraints(null, null), and Windows ignored the parameter. So
await pc.CreateOffer(new RTCOfferOptions { IceRestart = true });produced a plain re-offer carrying the old ICE credentials on four platforms out of five. The call renegotiated, the peer answered, and nothing restarted. This library's own manual restart did exactly that, and was documented as verified everywhere because the verification had been done in a browser.
If you restart ICE, call RestartIce() and then offer. Do not rely on the offer option.
On the SignalingConnection path only. A long call could decay: after about half an hour a tile
froze on its last frame while both processes were alive and both ends still looked connected,
because nothing was watching the transports fail.
The recovery restarts ICE on Failed and not on Disconnected, which frequently recovers by
itself; from the initiator only, since both ends offering is glare for no gain; off the callback
thread, because creating an offer inline from libwebrtc's signalling thread deadlocks; three
attempts and then the call ends, since an unbounded reaction to Failed is an offer storm rather
than a recovery; and the counter resets on Connected. There is a reconnecting state to show while
it happens, and PeerReconnecting / PeerReconnected peer responses.
Driving IRTCPeerConnection yourself? None of this arrives for free. The pieces it is built
from are all public.
RTCRtpSender.GetParameters and SetParameters threw on Windows, because the interop ABI had
nothing behind them. It does now, so a Windows sender's maximum bitrate, scale-down factor and
maximum frame rate can be changed mid-call, as on the other four platforms. Measured on a live
call: 1280x720 at 30 fps and 2500 kbit/s down to 640x360 at 12 fps and 150 kbit/s in about ten
seconds, and back up when the cap is lifted.
Two more from the same corner. The Windows capture source now honours what the encoder asks of it, so a struggling machine or a thin link actually gets relief instead of being sent full-size frames regardless; a consequence worth expecting is that video now starts small and climbs over the first twenty seconds or so, where before it began at capture size whatever the link could carry. And the camera reports the format it really opened at rather than the one that was requested.
| issue | what |
|---|---|
| #46 | Windows: removing a frame sink deadlocked the UI thread against the capture thread. A page navigating away from a video tile could freeze the application outright |
| #47 | The MAUI Media tile ignored a changed Stream, Label or AudioMuted: every bindable property was registered under the wrong name, so the handler's mapper ran once and never again. Fixing it exposed renderers on Android and Apple that could not survive a replaced track, which are fixed too |
-
WebRTC M153 (
branch-heads/8010) on all four native platforms. - The demo app no longer carries a Xirsys credential in
appsettings.json.
#48 — on iOS and Mac Catalyst, a local camera
preview moved from one Media tile to another leaves the new tile black for eleven to thirteen
seconds before the picture appears. The call is unaffected throughout and the camera never stops
sending. It only bites an application that moves a camera track between tiles. Fixed after this
release.
Change the version. There are no API changes and nothing to edit. MAUI consumers still have to name
Microsoft.Maui.Controls 10.0.101 themselves, or restore fails with NU1605 - see
Platform prerequisites.
A bug-fix release. Everyone on 26.9.16 or earlier should take it, on every platform.
A peer connection's awaits resumed on libwebrtc's own signalling thread. The completions
behind CreateOffer, CreateAnswer, SetLocalDescription, SetRemoteDescription and GetStats
were built without TaskCreationOptions.RunContinuationsAsynchronously, so whatever an application
wrote after an await ran inside libwebrtc's callback, in the middle of the operation that raised
it. A Dispose() there — the ordinary way to end a call — closed the connection a few lines before
libwebrtc used state the close had just destroyed.
It surfaced differently per platform, which is why it was filed as several different bugs:
| platform | symptom |
|---|---|
| iOS, Mac Catalyst |
EXC_BAD_ACCESS at 0x30 in JsepTransportController::MaybeStartGathering, on a thread with no managed frames |
| Android | process abort, FORTIFY: pthread_mutex_lock called on a destroyed mutex
|
The fault was asynchronous, so it killed whatever unrelated work happened to be running — device enumeration, a later call, anything. Nothing to catch, and no stack pointing at the cause.
Windows was never affected: its implementation had carried the flag from the start.
| issue | what |
|---|---|
| #45 | Native object freed under libwebrtc, crashing on its own thread — the bug above |
| #35 |
AbstractMethodError on PeerConnection$Observer.onTrack, fatal on any Android call carrying media |
| #22 | Android senders, receivers and transceivers disposed by the next enumeration |
| #31 | Chat threw when there was nobody to send to |
| #30 | A missing camera or microphone failed the whole call instead of degrading |
| #17 | Blazor call and chat pages are IAsyncDisposable, so teardown completes |
| #9 | The signalling server address can be supplied at runtime |
| #42 |
Partly. The TURN choice is one configuration key and is actually read; only StunOnly and Xirsys are implemented, so this stays open |
-
OnTrackfires on Apple, which it never had.didAddReceiveris@optionalinRTCPeerConnectionDelegate, and an optional protocol member implemented in C# does not exist as far as the Objective-C runtime is concerned unless it carries its own[Export]. Until now only the deprecated Plan B callback ever fired, and the configuration is Unified Plan. - A remote track no longer needs a stream to be delivered on Apple, and tracks go out on their stream's msid rather than their own.
-
Device tests can turn libwebrtc's own logging on —
Test-Device-Phase4.ps1 -NativeLog info. See Testing.
Change the version. There are no API changes and nothing to edit.
- .NET 8 → .NET 10, MAUI 10.0.101, System.Reactive 7.0.0, CommunityToolkit.Maui 15.0.1.
-
Xamarin is gone from the repository entirely.
README_V1.mdis the only record of that era. -
Five packages became two. The API, the bindings and the Blazor bindings are folded into
WebRTCme; the three middleware assemblies became one.
-
Mac Catalyst's
WebRTC.frameworkis real, where every earlier package shipped a 23-byte text file in its place. -
Blazor no longer needs a reflection workaround. Until this line, a Blazor app had to reach
into
JSRuntime's non-publicJsonSerializerOptionsand tell it to omit nulls, or the first peer connection threw — and that was written down nowhere a consumer would look. The binding now shapes its own arguments. -
CreateDataChannel(label)works on every platform. It threw on Android, iOS and Mac Catalyst when called without options, which the W3C API says means "all defaults". Windows was the one platform that got it right, which is why it stayed hidden. -
Windows counts audio devices after a call. The audio device module used to stop enumerating
inputs once a call had happened, taking the cameras with it out of
EnumerateDevices.
-
Screen sharing on all five platforms — Android via
MediaProjection, Mac Catalyst via ScreenCaptureKit, iOS via ReplayKit (app content only), plus Blazor and Windows. It arrives as its own tile beside the camera rather than displacing it. - Mute / unmute, wired on both connection paths and propagated to peers.
- Voice activity — server-observed on the SFU path, computed locally peer-to-peer.
-
ICE restart, reachable, recovering a call whose network path died. Correction: in this
release it restarts ICE only on Blazor. The request was made by setting
IceRestarton the offer options, which the Android, iOS, Mac Catalyst and Windows bindings discard - so the offer carried the old ICE credentials and nothing restarted, while the call renegotiated and the request was answered. Found 2026-09-21 and fixed in the next release, which callsRestartIce()instead. The verification behind the original claim was done in a browser, which is why it passed. -
Send-side statistics,
GetOutgoingStatsAsync, which no API previously reached. -
OnDeviceChange, reachable at last throughILocalMediaStream— it was declared on five platform classes and subscribable from none. - Camera selection honours constraints, where the parameter was previously accepted and ignored.
- Backgrounding an Android app no longer kills the call; a peer that drops actually leaves the room; a local track whose device died is noticed and recovered.
114 unit and integration tests, plus four runtime tiers that exercise the published package on Windows, Android, iOS, Mac Catalyst and in a browser. See Testing.
Rebuilt on MudBlazor and the Syncfusion MAUI Toolkit, as siblings of each other. See Demo apps.
For the full engineering record — including what each fault looked like before it was found —
doc/KnownGaps.md in the repository.
The .NET 8 and MAUI release, and the end of the Xamarin era. Still the current release for .NET 8 consumers, and frozen — see .NET 10 only above.
Added
- .NET MAUI
- .NET 8
Dropped
- Xamarin
- .NET 5, 6 and 7
Special thanks and credits to Gøran Yri for his major contributions to the .NET MAUI porting.
MediaSoup worked on Blazor, Android and iOS in this release, verified with the three in one call. Mac Catalyst was compile-verified only, and screen sharing and mute were not wired for the SFU path. All four of those statements have since changed — see What works where.
Packages in 2.0.0, three of which no longer exist:
WebRTCme |
still published |
WebRTCme.Middleware |
still published |
WebRTCme.Api |
retired — compiled into WebRTCme.dll
|
WebRTCme.Bindings |
retired — inside the WebRTCme package |
WebRTCme.Bindings.Blazor |
retired — inside the WebRTCme package |
The Xamarin era, and the project's earlier history, is recorded in
README_V1.md — a stack of
dated notes going back to 2021. The Xamarin code itself has been removed from the repository; that
file is the only record of it.
The packages embed prebuilt native WebRTC, so an app shipping WebRTCme is redistributing Google's WebRTC and its dependencies. That carries attribution obligations, and they are yours to satisfy in whatever your app shows its users.
| Platform | Embedded |
|---|---|
| Android |
libwebrtc.aar — classes.jar and four libjingle_peerconnection_so.so
|
| iOS | WebRTC.xcframework |
| Mac Catalyst | WebRTC.framework |
| Windows |
WebRtcInterop.dll, plus abseil, BoringSSL, protobuf and libc++, plus four Microsoft C++ runtime DLLs |
| Blazor | nothing — the browser supplies WebRTC |
WebRTCme's own code is MIT. The embedded libraries are not: Google's WebRTC is BSD-3-Clause with
an additional patent grant, and its bundled dependencies carry their own terms (abseil and libc++
Apache-2.0, protobuf BSD-3-Clause, BoringSSL a mix including OpenSSL-derived terms). The Microsoft
C++ runtime DLLs on Windows are redistributed under Microsoft's redistributable terms, and they are
a deployment choice rather than a requirement — if your app is packaged as MSIX with the VC++
runtime as a framework dependency, or the runtime is a stated prerequisite, they can be removed.
Consult the upstream license texts for the exact wording; this table is a starting point for your own review, not legal advice.
NOTICE in the repository is the
attribution record, and it is what to reproduce. It covers:
- Google's WebRTC (BSD 3-Clause), with the copyright notice binary redistribution requires, and a pointer to its separate Additional IP Rights Grant (Patents).
-
The 22 components bundled inside the WebRTC binaries — abseil-cpp, BoringSSL, libvpx, libaom,
libyuv, opus, libsrtp, dav1d, protobuf,
libc++and the rest. The authoritative aggregate text is produced by the WebRTC build itself and committed atWebRTCme.Bindings/Maui/WebRTCme.Bindings.Maui.iOS/WebRTC.xcframework/LICENSE.md. -
mediasoup-client (ISC, © Iñaki Baz Castillo) —
WebRTCme.Connection.MediaSoupis a C# port of it, so this is a source-level derived work, not just a binary you carry. - The Microsoft Visual C++ runtime DLLs on Windows.
NOTICE ships inside both packages, at the package root, so it arrives with whatever you
restore — open the .nupkg or find it in your NuGet cache. It is also readable
in the repository.
Worth understanding, because it explains why a tag builds nothing.
flowchart LR
ci["ci.yml — manual<br/>Mac builds the Apple slices<br/>Windows packs and verifies"]
art["artifact: nupkg<br/>a publishable package"]
test["you download it<br/>and run the tiers against it"]
tag["git tag v26.9.16"]
pub["publish.yml<br/>pushes those exact bytes"]
nuget["nuget.org"]
ci --> art --> test --> tag --> pub --> nuget
art -. the same bytes .-> pub
publish.yml builds nothing. It publishes the exact .nupkg files a CI run produced, verified
and uploaded — the same bytes you downloaded and tested on a PC and a Mac.
That is the point of it. It used to rebuild and repack, which meant the package that went to nuget.org had never been tested by anyone: you could test one artifact and ship a different one built minutes later on a different runner. NuGet has no TestFlight and no staging slot, and a published version is immutable — so the CI artifact is the staging step, and publishing is only the promotion.
Two consequences:
-
Run
ci.ymlbefore tagging, not after. It is manual, so nothing verifies a commit automatically; a packaging fault sits undetected until somebody runs it. - Authentication is NuGet Trusted Publishing — no API key is stored in the repository. The
policy on nuget.org must name both ids:
WebRTCme.*does not matchWebRTCmeitself, so the first push of the main package is rejected unlessWebRTCmeis listed alongside the pattern.
Running manually defaults to a dry run that resolves and verifies the artifact without pushing.
Windows can neither check WebRTC.framework's symlinks out of git nor write them into a zip, and
macOS refuses to codesign an app embedding a framework that is not a versioned bundle. Packages
built entirely on Windows restored and compiled fine — so nothing failed until a release, or a
consumer, reached a device.
CI now builds the Apple slices on macOS and merges them in, and -RequireAppleNativeLayout fails
the build if they did not arrive. Everything else stays on Windows, which is the only host that can
build net10.0-windows10.0.22621.0.
doc/Packaging.md is the reference for the packaging itself, and worth reading before changing
anything there: dotnet pack drops payloads it does not recognise silently.
| Getting started | Installing 26.9.18 |
| Platform prerequisites | The three things that fail silently |
| What works where | What this release actually does, per platform |
| Testing | What is verified before a release goes out |
Start here
The stack
- WebRTCnative
- Bindings
- The unified API
- Middleware
- Connection
- · Signaling (mesh)
- · MediaSoup (SFU)
- Demo apps
Reference
When it breaks