Back to Blog

rtcStats v2.0: Redesigned Tables, Negotiations & Sharper Analysis

rtcStats v2.0 redesigns the Connections, Streams and Devices tables, adds a Negotiations section and enriches every session summary for a sharper AI analysis.

Posted by

rtcStats v2.0: Redesigned Tables, Negotiations & Sharper Analysis

Time flies: rtcStats is already at v2.0.0! And this version goes even further into the details, to help you better diagnose the problems you run into.

This release is about reading a session faster and trusting what it tells you. The Connections, Streams, Channels, ICE Connectivity and Devices tables were redesigned so states and timing read at a glance, every negotiation now gets its own table, and the session summary carries a lot more facts, which gives the AI analysis far better material to work with.

A new major version, a cleaner view of every call. Let's dive in! 🤿

🚀 Major Improvements

> Connections, Streams, Channels and ICE Connectivity, Redesigned 📊

The tables of the Connections and Streams pages were the place where you had to squint the most. Each row now tells its own story: its state (such as Connected, Failed, Active, Stalled or Unused), when it started and how long it lasted. Each group of rows shares its own time axis with clock times, so you can line up what happened across connections without doing the math.

Order the Connections, Streams and Channels by Time, where each bar sits at its real start, or by Duration, where every bar starts at zero so you can compare their lengths. In Streams and Channels, Align by Peer start shows how long media took to start after its peer connection was created, which is exactly what you want when someone says "the video took ages to show up".

The redesigned Streams table with states, durations and a shared time axisThe redesigned Streams table with states, durations and a shared time axis

> A Devices Table Built Around Devices 🎙️

The Devices table used to follow the getUserMedia calls. It now follows the devices themselves. Rows are grouped by the origin that made the request, with one row per microphone, camera or screen track, its state (Used, Replaced, Not sent, Failed or Listed) and the connections that sent it.

A timeline shows the wait between each request and its grant, and how long each track was sent. Repeated identical failures and device listings fold into a single row, USB and default devices carry a badge, and long idle periods are folded on the time axis. The System details now sit above the table, which uses the full page width.

The redesigned Devices table, one row per track with its state and timelineThe redesigned Devices table, one row per track with its state and timeline

> Every Negotiation, Step by Step 🤝

Before media flows, the two sides agree on it through an offer/answer negotiation, and they negotiate again whenever the session changes: a track is added or removed, a data channel is first opened, or ICE is restarted. When that goes wrong, the answer is often hidden in the order and timing of the calls each side makes. Each connection's details on the Connections page now include a Negotiations section that lists every negotiation and renegotiation with its outcome: completed, failed, no answer, incomplete or rolled back.

For each signaling call, such as createOffer or setRemoteDescription, you get its result, how long it took, the time since the previous call, the media sections with their SSRCs, and the browser error when the call failed. A renegotiation that never got its answer is now one look away.

The Negotiations section of a connection, one table per negotiationThe Negotiations section of a connection, one table per negotiation

> Improved Analysis Accuracy 🎯

This release makes the scores and observations reflect better what happened on the call. Expect some of your numbers to move after the upgrade: sessions with audio cuts will score lower, and sessions barely touched by an audio or outgoing media issue will score higher.

Observations that scale with the impact (Paid Plans). Four observations covering audio and outgoing media used to be reported at a fixed severity, however much of the call they actually affected. That also pushed down the experience score of sessions that were barely impacted, and misled the AI analysis. Their severity now reflects the scale of what was detected, they carry the measured values it was graded on, and a signal too small to matter no longer raises them at all.

Echo detection you can trust. The two echo observations read the echo canceller's idle and not-yet-measured values as real echo, so they showed up on calls with no echo problem. They now rely only on actual measurements and are renamed Strong echo at the microphone and Low linear echo cancellation, to say what they detect.

A MOS that hears the silence. With the rtcStats Advanced audio MOS (Settings > Parameters), incoming audio the listener could not hear now scores the minimum MOS of 1: an audio interruption, no audio released by the jitter buffer, or no audio energy at all. A shorter interruption lowers the MOS in proportion to its duration. Sessions with audio cuts now score what the listener actually heard, so their MOS, audio and experience scores can go down.

On the Streams timeline, the periods where a stream's MOS sits at its minimum show as a hatched Bad segment, with their duration in the tooltip next to Poor, and they count toward the stream's "Mostly poor quality" state.

🛠️ Other Enhancements

> Richer Summaries, Sharper AI Analysis 🧠

Every session now carries the rtcstats-features catalog: the client, connection and track facts, computed the same way rtcstats-server computes them. Dumps uploaded straight to rtcStats get the same facts as the ones collected through rtcstats-server, on every plan and on every surface (dashboard, share and embed viewers, REST, MCP and POST /analyze).

These facts enrich the session summary, and the AI summary prompt was rewritten to use them (Paid Plans). It reads only the facts it needs, follows the media path in each direction, names the origin of a problem only when the evidence supports it, and grades every score on the same five-level scale: excellent, good, acceptable, poor, bad. The engine also gains 2 new observations covering connectivity issues.

> Four New Showcase Sessions 🎬

The Showcase welcomes four new real-world sessions: a WhatsApp Web 1:1 video call, a Telegram Web 1:1 video call to mobile, a Slack huddle 1:1 video call, and Cloudflare's demo service with every call forced through TURN/TCP. A great way to see how the big names set up their WebRTC.

> Read-only Application Tokens 🔒 (Paid Plans)

When you hand a token to an agent, it rarely needs to spend credits or delete anything. You can now pick the scope of an application token when creating it under Settings > Applications: full access or read-only. A read-only token serves every read (sessions, quota, observations and the MCP tools), and is refused with 403 and the errorCode insufficient_scope on upload, analyze, enrich and DELETE /v1.0/sessions/{rtcstatsId}. The list of tokens shows the scope of each one.

> Observation Explanations, Pagination and Upload Limits in the API ⚙️ (Paid Plans)

We are noticing more interest in our MCP/API interfaces. Today, more often than not, the first "eyes" on a bug is an AI agent instead of a human. Towards that end, we're improving and enhancing our automation surface, becoming more AI friendly:

  • Observation explanations: GET /v1.0/observations/{type} and the MCP tool get_observation_explanation return the explanation of one observation type as Markdown sections, with its title, description, severities, tags and related types. No credit is spent and read-only tokens are accepted.
  • Pagination: GET /v1.0/sessions and list_sessions take limit (1 to 1000) and page, and every response carries totalCount, page, limit, totalPages and hasMore.
  • Upload limits: GET /v1.0/quota and get_quota now return maxFileSizeBytes and maxChunkSizeBytes, so a client knows the size it can upload and how to split it instead of discovering it through a 413.
  • More counters: media entries of streams gain concealmentEvents, packetsDiscarded, retransmittedPacketsSent and retransmissionRatio.

See the API documentation for the details.

> Observation Explanations, Right Where You Need Them 📚 (Paid Plans)

Every observation raised on your sessions now opens its explanation in place, in the app, right next to the observation itself. A reference from one explanation to another opens in the same popup, which closes with Escape and returns focus to where you opened it. The same explanations are available to your code and your agents through GET /v1.0/observations/{type} and get_observation_explanation, on paid plans and trials.

With the explanations now living in the product, the observation articles are no longer published in the public knowledge base. The rest of the knowledge base is unchanged.

🐞 Bug Fixes

  • Peer connections closed before their first statistics sample now appear in the session with their real lifetime, state and failure reason. One closed before it ever tried to connect no longer scores 0 or lowers the connectivity score.
  • Some rtcstats dumps captured after the collecting client reconnected were dated January 1st, 1970. They now carry their real date.
  • Upload size limits are measured once, on the bytes you send: a gzipped dump no longer fails with 413 after its last chunk, and dashboard uploads are now checked on the server too.
  • Some connections that never managed to connect in Chrome are now reported as failing to connect, not as dropped.
  • STUN and TURN servers configured with an IPv6 address show their transport and port instead of UNKNOWN.
  • data.fileSize and data.fileName now report the same values whatever path the dump took.
  • The session Overview Summary and Insight chart render correctly on narrower windows.

📦 Ecosystem Updates

> rtcstats-server (main) 🛠️

No new release in this window. Here is what landed on main:

  • The Docker image is fixed and hardened: it now starts, ships only the default configuration, and expects the real one at deploy time, so secrets are never baked into the image.
  • rtcstats-features gains a custom.js hook for your own client, connection and track features, stored with a custom_ prefix so they never collide with the standard ones.
  • rtcstats-shared 2.6.2 keeps the timestamp of stats reports that did not change between two samples, notably audio remote-inbound-rtp and remote-outbound-rtp.
  • The dump importer can split a dump by origin and collapses long repeats of signaling state changes.

> rtcstats-js v2.5.0 📡

A small release, available on npm:

  • Calls to HTMLMediaElement.setSinkId are now traced, so you can see which audio output device the application selected.

> API & MCP ⚙️ (Paid Plans)

v2.0.0 comes with two breaking changes to the public API and the MCP tools, effective with this release:

  • Sessions list: GET /v1.0/sessions and list_sessions now return the 100 newest sessions when called without limit, instead of up to 1000. Pass limit (up to 1000) and page to read more.
  • MCP authentication: the MCP server now requires your application token on every request, initialize and tools/list included. A missing or invalid token gets HTTP 401 with a WWW-Authenticate header, and a plan without API access gets 403, instead of a tool error after connecting. Configure the Authorization: Bearer header in your MCP client before connecting.

We're excited to see how you use the redesigned tables and the new Negotiations section to get to the bottom of your WebRTC calls! As always, the latest version of rtcstats-js is available on npm. Make sure you're up to date to get the best out of these new features.

Happy debugging! 💎