Skip to content

What works where

Melih Ercan edited this page Sep 23, 2026 · 6 revisions

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.

Legend

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.

Platforms

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+

The core

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.

Calls

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.

Media features

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.

Known limits and open issues

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

The wider W3C surface

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.

What is verified by machine, and what by a person

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.

Where next

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

Clone this wiki locally