Skip to content

Releases

Melih Ercan edited this page Sep 21, 2026 · 9 revisions

Releases

Versions, what changed, how to upgrade, and what is inside the packages.

Current: 26.9.21

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

Versioning

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.

.NET 10 only

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.

Upgrading from 2.0.0

Three edits, and no source changes beyond them.

1. Remove three retired packages

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.

2. Blazor: the JavaScript moved

-<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.

3. MAUI: pin the MAUI versions

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" />

See Platform prerequisites.

Namespaces are preserved

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.

Mac Catalyst users should take this release specifically

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.

26.9.21 — 21 September 2026

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.

A call whose transport died never ended, on Android

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.

ICE restart, which worked on one platform out of five

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.

A failed transport is now noticed and recovered

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.

Windows can change its outgoing video during a call

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.

Fixed issues

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

Also in this release

  • WebRTC M153 (branch-heads/8010) on all four native platforms.
  • The demo app no longer carries a Xirsys credential in appsettings.json.

Known issue

#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.

Upgrading

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.

26.9.18 — 18 September 2026

A bug-fix release. Everyone on 26.9.16 or earlier should take it, on every platform.

The crash that was really one bug

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.

Fixed issues

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

Also in this release

  • OnTrack fires on Apple, which it never had. didAddReceiver is @optional in RTCPeerConnectionDelegate, 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 onTest-Device-Phase4.ps1 -NativeLog info. See Testing.

Upgrading

Change the version. There are no API changes and nothing to edit.

What changed since 2.0.0

Platform and tooling

  • .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.md is 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.

Fixes that affect every consumer

  • Mac Catalyst's WebRTC.framework is 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-public JsonSerializerOptions and 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.

Features

  • 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 IceRestart on 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 calls RestartIce() 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 through ILocalMediaStream — 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.

A test suite, where there was none

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.

Demo apps

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.

Earlier releases

2.0.0 — 10 February 2024

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

Before 2.0.0

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.

What is inside the packages

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.aarclasses.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.

Attribution

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 at WebRTCme.Bindings/Maui/WebRTCme.Bindings.Maui.iOS/WebRTC.xcframework/LICENSE.md.
  • mediasoup-client (ISC, © Iñaki Baz Castillo) — WebRTCme.Connection.MediaSoup is 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.

How a release is cut

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
Loading

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.yml before 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 match WebRTCme itself, so the first push of the main package is rejected unless WebRTCme is listed alongside the pattern.

Running manually defaults to a dry run that resolves and verifies the artifact without pushing.

Why the Apple slices come from a Mac

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.

Where next

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

Clone this wiki locally