Background
Because we received every participant’s tracks simultaneously, 50-person rooms were practically impossible on mobile and mid-range devices. The CPU and GPU couldn’t handle 50 decodes at once, and mobile devices shut down from overheating.
What made the problem harder was that the quality-decision logic was scattered inside the adapter. With no single place defining which resolution to use under which conditions, every round of tuning added more if statements, and regressions were frequent.
Approach
I started by cutting reception down to page units. I unified page state in a single place in the store, and added the modern setReceiverConstraints API to the adapter so that only participants on the current page are received at high resolution.
I split the quality policy out into a separate pure-function module. Three functions (computing receive resolution · send resolution · degraded resolution) take dpr · participant count · connection-quality as inputs and return a resolution bucket. The policy is decided in one place, the module, and the adapter only applies the result.
One testable function instead of scattered if statements. Tuning becomes a matter of changing the function’s inputs, and regressions get caught in tests.
Screen sharing runs outside the policy as a separate track (1080p / 10–15fps + contentHint). Background tabs pause sending and receiving, and network recovery is handled with navigator.onLine plus exponential-backoff reconnection.
I believed verification had to be observable from the outside. I wrote Puppeteer fake-camera bots and WebSocket bots myself, ran burst · steady · churn load in 6 · 17 · 25+ participant scenarios, and saved the simulcast layers, whether setReceiverConstraints was applied, and p95 metrics as JSON.
Results
- Restored mobile usability in 50-person rooms. SFU forwarding blocked for participants outside the current page.
- VP9 preferred + VP8 fallback. Dedicated 1080p · 10–15fps for screen sharing.
- 20+ new regression tests covering page state, participant activation, the quality adapter, and VP9 / p2p off / bridge channel guards.
- Load-bot artifacts make simulcast application and quality-policy results externally observable on every build.
What I Learned
The more branching an area has, like WebRTC, the more the answer was to treat policy as data and extract it into a function. The adapter becomes a thin layer that only applies the result of the policy, and tests only need to hit the policy function.
I was also reminded that verification has to be possible from the outside. Rather than trusting the SDK’s internal state, I generated real send/receive traffic with fake-camera bots and tied the resulting metrics to build artifacts — and quality-policy changes started showing up on a graph.