-
-
Notifications
You must be signed in to change notification settings - Fork 55
What works where
An honest per-platform matrix, including the parts that are written but unverified. Nothing here is implied by silence: if something has never been run, this page says so.
Current as of 26.9.21. The engineering record behind it is doc/KnownGaps.md in the
repository, which keeps fixed items with what the fault looked like beforehand.
26.9.21 has known issues, fixed on master and not yet released. On iOS and Mac Catalyst,
only the first call after launch sends video, and the camera is never turned off. On MAUI, any
error dialog during a call hangs up the call. A join can occasionally never connect. See
Releases.
| yes | Implemented, and run on real hardware |
| compiles | Implemented, never executed on this platform. Treat as unverified |
| no | Not implemented — throws |
| n/a | Not applicable to this platform |
The distinction between yes and compiles is the whole point of this page. A lot of Apple code was written on reasoning alone and compiles perfectly.
| supported | |
|---|---|
| Blazor WebAssembly | yes |
| Blazor Server | no — the binding has an async path for it, but it has never been run and is not supported |
| Blazor Hybrid (MAUI BlazorWebView) | no — #28 |
| Android | yes — API 28+, arm64 verified |
| iOS | yes — 15.0+, real device verified |
| Mac Catalyst | yes — macOS 12+ |
| Windows | yes — 10 22H2+ |
| Blazor | Android | iOS | Mac Catalyst | Windows | |
|---|---|---|---|---|---|
| Peer connection, offer/answer/ICE | yes | yes | yes | yes | yes |
| Data channel | yes | yes | yes | yes | yes |
GetUserMedia — camera and microphone |
yes | yes | yes | yes | yes |
EnumerateDevices |
yes | yes | yes | yes | yes |
Camera selection (facingMode) |
yes | yes | yes | yes | yes |
OnDeviceChange |
yes | yes | no¹ | no¹ | yes |
| Send and receive statistics | yes | yes | yes | yes | yes |
¹ iOS and Mac Catalyst declare the event and never raise it. The signal is
AVCaptureDeviceWasConnectedNotification and nothing observes it. On the three that do work, note
that Android reports a camera as unavailable when another application opens it, which is more
often than a phone gains or loses hardware. Subscribe through
ILocalMediaStream.OnDeviceChange — IMediaDevices.OnDeviceChange is not reachable
from outside the library.
All five run the loopback negotiation in CI on every release — see Testing.
| Blazor | Android | iOS | Mac Catalyst | Windows | |
|---|---|---|---|---|---|
| Signaling — mesh / peer-to-peer | yes | yes | yes | yes | yes |
| MediaSoup — SFU | yes | yes | yes | compiles | compiles |
| Mute / unmute, both directions | yes | yes | yes | yes | yes |
| Voice activity (speaking) | yes | yes | yes | yes | yes |
| ICE restart, on request | yes | yes | yes | yes | yes |
| Recovering a failed transport by itself | yes | yes | yes | yes | yes |
ICE restart, and what it was before 26.9.21. Only Blazor restarted anything. The restart was
asked for by setting IceRestart on the offer options, and Blazor is the one binding that passes
those options to the platform - Android, iOS, Mac Catalyst and Windows discarded them, so the
"restart" was a plain re-offer carrying the old ICE credentials. The call renegotiated and nothing
restarted. Fixed by calling RestartIce(), which is the W3C route and does not go through the
options object; the loopback tier now checks on every platform that the username fragment changes.
Recovery is automatic since 26.9.21. A peer whose transport reaches Failed is restarted
without anyone pressing anything - the initiator tries three times, then reports the peer as lost.
Before this, Failed was not handled at all and a dead peer left a tile frozen on its last frame
while the call still looked connected. What is proved on every platform is the restart itself; that
a peer which has genuinely lost its network route comes back is checked by hand, because a loopback
has no route to lose.
The five-platform call. Blazor, Android, Mac Catalyst and iOS were up together in one mesh
room; Blazor was then dropped to free the PC's only camera and Windows took its place. Every
platform carried live video in both directions, lost=0 on every peer, RTT 1–6 ms on the LAN.
MediaSoup has been verified on Blazor, Android and iOS together in one call. Mac Catalyst and Windows compile against that path and have not been run on it.
| Blazor | Android | iOS | Mac Catalyst | Windows | |
|---|---|---|---|---|---|
| Screen sharing | yes | yes | yes² | yes | yes |
Recording (MediaRecorder) |
yes | no | no | no | no³ |
| Simulcast (encoding) | yes | no⁴ | compiles | compiles | compiles |
² iOS captures the app's own content, not the system screen. RPScreenRecorder is an
in-process recorder, so the far side sees your call UI rather than whatever else is on the phone.
A system-wide share needs a Broadcast Upload Extension — a second bundle with its own process, an
App Group, and WebRTC running inside it. That is an architectural change rather than an addition.
Mac Catalyst uses ScreenCaptureKit instead and does give the whole display, which is what
"share my screen" means on a Mac.
³ Windows throws NotSupportedException saying so: the interop ABI has no encoder surface for
recording. Android, iOS and Mac Catalyst throw NotImplementedException. Recording is Blazor
only.
⁴ Android cannot encode simulcast with this libwebrtc build, and this was traced rather than
guessed. The ladder asked for is correct, the ladder negotiated is correct — the sender reports
back both encodings, active, with the right scales — and the encoder ignores it anyway: r0
encodes the full 640x480 with its scale=2 unapplied, and r1 never encodes a single frame. The
same ladder works on Blazor.
Simulcast on iOS, Mac Catalyst and Windows is unverified, and deliberately marked so rather
than assumed from Blazor: the demo apps run with UseSimulcast: false for the SFU reason below, so
it is not a path anyone has exercised there. If you need it on one of those three, measure before
depending on it — and the send-side statistics will tell you straight away, since an unfunded layer
reports zero frames and zero bytes rather than looking throttled.
Screen sharing landed on all five platforms in September 2026. If you have read elsewhere in this repository that it works on two platforms, that note is stale.
| Where | What | |
|---|---|---|
| Frame rotation | Android | Rotating the device does not rotate the picture locally. Mitigated, not fixed, by the demo apps' portrait lock. Open, and pickable today |
| Simulcast collapses the SFU's estimate | MediaSoup, all platforms | Measured: 64–159 kbit/s at 289x240 with simulcast against 1781–1828 kbit/s at 578x480 without. It is mediasoup's congestion control, not this client. The demos set UseSimulcast: false
|
| libwebrtc aborts on the Android x86_64 emulator | Android emulator only | A native SIGABRT on libwebrtc's signalling thread, about two runs in three. Never once on an arm64 phone, and no user runs x86_64. The .aar is stripped, so the failing assertion is unreachable from here |
| Receiver-side stall signal | all | Does not fire. Measured, nothing to do about it here |
WebRTCme mirrors the W3C WebRTC 1.0 API, and not all of it is implemented. Counting what throws gives 38 members on Android and 33 on iOS, with Mac Catalyst mirroring iOS — but that count is the wrong measure.
Of the 41 that threw on Android before September 2026, only 16 were referenced anywhere in the connection or middleware layers, and only three sat on paths that actually run. Those three were where every real failure came from; they are fixed. The other 25 have never been reached by anything.
So the useful question is "does anything call it", not "how many are left". Still referenced
and still unimplemented, none of them on a live path: MediaRecorder, MediaStream.Create,
GetCapabilities, GetConstraints, and the data-channel properties BinaryType, Protocol and
BufferedAmountLowThreshold. On Blazor, Blob cannot produce a byte[] from a JS ArrayBuffer.
If you hit a NotImplementedException on a path that matters to you, that is worth an issue —
it means something moved from "nothing reaches it" to "something does", which is exactly the signal
that decides what gets filled in next.
| Automated, every release | The negotiation core on all five platforms; the package restoring and resolving its own slice per target framework; the mediasoup port's capability negotiation, H264 parameter handling and scalability-mode parsing; the signalling hub |
| Manual | That frames are drawn — the right way up, at the right size. Multi-party SFU behaviour against a real mediasoup. TURN relay paths. Recovery from a genuinely lost network route. Anything needing two physically separate networks |
A headless test can prove frames arrive; it cannot prove they are rendered. That is precisely the frame-rotation bug class in the table above, which is why it is still open and why the demo apps remain the rig for it. Testing sets out what each tier does and does not cover.
| Testing | How each of the claims above is checked |
| Troubleshooting | Symptoms and causes |
| Connection: MediaSoup | The simulcast measurements in full |
doc/KnownGaps.md |
The engineering record, including what each fault looked like before it was found |
Start here
The stack
- WebRTCnative
- Bindings
- The unified API
- Middleware
- Connection
- · Signaling (mesh)
- · MediaSoup (SFU)
- Demo apps
Reference
When it breaks