← Back to Integrations with 3rd parties

React Native (react-native-webrtc)

Add rtcstats-js to a react-native-webrtc app by registering the WebRTC globals, then wrapping them via globalThis and connecting from your entry point.

react-native-webrtc brings the WebRTC API to React Native, exposing the same RTCPeerConnection, RTCRtpSender, RTCRtpTransceiver, mediaDevices, and MediaStreamTrack you know from the browser, backed by the native WebRTC libraries on iOS and Android.

Because that API surface matches the browser, rtcstats-js works in React Native with no changes to the library itself. The only work is app-side setup: rtcstats-js wraps the WebRTC APIs on whatever global object you hand it (defaulting to globalThis). React Native does not expose the WebRTC classes as globals, so you register them with registerGlobals(), then wrap and connect. React Native already aliases window to the global object, so no shim is needed; you only fill in screen and window dimensions if you want them reported.

A complete, runnable loopback app is in the repo at example/react-native. This guide walks through the same integration.

How it fits together

rtcstats-js instruments WebRTC by wrapping the global constructors and navigator.mediaDevices, then streams a trace over a WebSocket to rtcstats-server. Three things differ from the browser; only the first needs any action from you:

  • The WebRTC classes are module exports, not globals. react-native-webrtc ships registerGlobals() to install RTCPeerConnection, MediaStream, MediaStreamTrack, the RTCRtpSender/RTCRtpTransceiver classes, and navigator.mediaDevices on the global object. rtcstats-js wraps whatever object you hand it, so this is what it wraps.
  • The browser device metrics are missing. rtcstats-js reads a few values off window when the trace connects (screen, innerWidth/innerHeight, devicePixelRatio). React Native already aliases window to the global object but does not define these values, so they are simply omitted from the trace unless you fill them in from Dimensions.
  • There is no DOM. rtcstats-js walks <video> elements to record how a received video track is displayed. React Native renders video with <RTCView>, so that code path has nothing to do. rtcstats-js reads the DOM off the object you hand it (window.document?.…), and React Native has no document, so this is a clean no-op with no stub required.

None of this modifies rtcstats-js. It is all setup you run once at startup.

Before you start

You need:

  • A running rtcstats-server (local or deployed)
  • A react-native-webrtc app you can build on a device or emulator (it does not run in a web or Node test runner)

Step 1: install rtcstats-js

npm install @rtcstats/rtcstats-js

Step 2: register globals, wrap, and connect

Do the setup in your entry point (index.js), right after react-native-webrtc installs its globals and before the app mounts. Keep it inline rather than in a side-effect module: ES imports are hoisted above top-level statements, so an imported module cannot be sequenced after registerGlobals(), and the wrapping has to run after it.

// index.js (your app entry point)
import {AppRegistry, Dimensions, PixelRatio} from 'react-native';
import {registerGlobals} from 'react-native-webrtc';
import {wrapRTCStatsWithDefaultOptions} from '@rtcstats/rtcstats-js';
import App from './App';
import {name as appName} from './app.json';

const RTCSTATS_URL = 'ws://10.0.2.2:8080/my-app';

// 1. Expose react-native-webrtc as globals: RTCPeerConnection, MediaStream,
//    MediaStreamTrack, navigator.mediaDevices, and the RTCRtpSender /
//    RTCRtpTransceiver classes rtcstats-js wraps. If your app already calls
//    registerGlobals(), wrap right after that existing call.
registerGlobals();

// 1b. Optional: report the device's screen and window dimensions. React Native
//     aliases `window` to the global object but does not define these, so
//     rtcstats-js omits them from the trace unless you set them here. Delete this
//     block if you do not need them.
const {width, height} = Dimensions.get('window');
globalThis.screen = {availWidth: width, availHeight: height};
globalThis.devicePixelRatio = PixelRatio.get();
globalThis.innerWidth = width;
globalThis.innerHeight = height;

// 2. Wrap. wrapRTCStatsWithDefaultOptions builds the trace and wraps the now-global
//    WebRTC APIs on its target, which defaults to globalThis (the object
//    registerGlobals() installed onto). Wrap at startup, before any peer is
//    created; it returns the trace without connecting.
const trace = wrapRTCStatsWithDefaultOptions({getStatsInterval: 2000, log: console.warn});

// 3. Connect, as a separate step. This example connects here against a fixed URL;
//    a real integration keeps the wrap above but opens the connection later, once
//    it has the rtcstats-server URL for the session (which may carry a per-session
//    token) -- see the Vonage integration.
trace.connect(RTCSTATS_URL);

// 4. Register the app as usual.
AppRegistry.registerComponent(appName, () => App);

A couple of notes:

  • wrapRTCStatsWithDefaultOptions(options) is the one-call setup: it builds the trace and wraps RTCPeerConnection, getUserMedia/getDisplayMedia, and enumerateDevices on its target, which defaults to globalThis. On React Native that is the object registerGlobals() populated, so no second argument is needed (pass one, e.g. wrapRTCStatsWithDefaultOptions(options, globalThis), only if you want to be explicit). It does not open the connection, so call .connect(url) on the returned trace. Pass log in the options to surface connection/auth errors.
  • Wrapping and connecting are deliberately separate: wrap once at startup, before any peer is created, and connect when you know the URL (it may carry a per-session token). Here the URL is static, so the two calls sit next to each other.
  • getStatsInterval defaults to 1000 ms in rtcstats-js. On mobile, 2000 ms is a gentler choice for battery and CPU; tune it to your needs.
  • rtcstats-js patches media capture on navigator.mediaDevices (which registerGlobals() populates), not on the react-native-webrtc mediaDevices export. Call it through navigator.mediaDevices so it is traced. See "Use the web globals, not the react-native-webrtc imports" below.

Step 3: build peers from the global RTCPeerConnection

Because Step 2 runs in the entry point before the app mounts, RTCPeerConnection refers to the wrapped global by the time any component renders, so build peers exactly as you would on the web. Only peers built after wrapping are traced.

// App.js - RTCPeerConnection here is the wrapped global from index.js:
const pc = new RTCPeerConnection({iceServers: [/* ... */]});

Set RTCSTATS_URL in your entry point. From the Android emulator the host machine is reachable at 10.0.2.2; use localhost for the iOS simulator, or a LAN IP / deployed URL on a real device. If your rtcstats-server requires auth, pass the token in the URL (e.g. ?rtcstats-token=...); a bad or missing token closes the socket with code 1008.

Use the web globals, not the react-native-webrtc imports

This is the rule that makes tracing work, and it is the same rule that keeps your call code portable to the web: call WebRTC through the standard web globals, not through the react-native-webrtc module exports.

rtcstats-js instruments by wrapping the globals (navigator.mediaDevices and the global RTCPeerConnection). The module exports are separate objects it does not touch:

  • Media capture: use navigator.mediaDevices.getUserMedia(...) (and getDisplayMedia/enumerateDevices). registerGlobals() populates navigator.mediaDevices with the methods rtcstats-js wraps. The mediaDevices export (import {mediaDevices} from 'react-native-webrtc') is a different object that is never wrapped, so calling it directly is not traced.
  • Peer connections: build them from the global RTCPeerConnection, which wrapRTCStatsWithDefaultOptions replaces with the wrapped constructor. import {RTCPeerConnection} from 'react-native-webrtc' gives you the unwrapped native class, and peers built from it are not traced.

Both are the same web-compat point: code written against navigator.mediaDevices and the global RTCPeerConnection runs unchanged in a browser and is what rtcstats-js instruments. Reaching for the react-native-webrtc-specific imports ties the code to React Native and bypasses tracing.

Verify it works

With rtcstats-server running, start a call. The server emits one log line on connect and another on disconnect when the call ends:

Accepted new connection with uuid 93a750f8-223a-4c64-a39f-4c01f2731ae3
Connection with uuid 93a750f8-223a-4c64-a39f-4c01f2731ae3 disconnected, starting to process data

That pair confirms the connection opened and the dump was processed. Your session data is now flowing to rtcstats-server.

What you get, and what you do not

Everything the browser SDK captures works: the peer connection lifecycle, offer/answer and ICE, addTrack/addTransceiver, getUserMedia, enumerateDevices, and the periodic getStats polling that drives the timeline on rtcstats.com. Two browser-only details are absent by design on React Native and simply do not appear in the trace:

  • Displayed video dimensions (HTMLMediaElement.resize), which come from walking <video> elements. <RTCView> has no equivalent, so this is a no-op.
  • contentHint, which react-native-webrtc's MediaStreamTrack does not implement. rtcstats-js skips wrapping properties that are not present, so nothing breaks; the field is just not reported.

NOTE: Can't get this to work? Need help? Contact us

Was this page helpful?