Integrations

The monitor collects nothing until you give it a source. A source is anything that owns RTCPeerConnections: a peer connection itself, a mediasoup Device, or a single mediasoup transport.

monitor.addSource(source);

The monitor keeps polling each source on the collecting period and cleans up automatically when a peer connection closes — you never need to remove a source by hand.

RTCPeerConnection

The plain case. Works for P2P, for a client talking to any SFU, and for any number of connections.

import { ClientMonitor } from "@observertc/client-monitor-js";

const monitor = new ClientMonitor({ clientId, callId });

const pc = new RTCPeerConnection(config);
monitor.addSource(pc);

Multiple peer connections are supported directly — add each one, and the client-level metrics aggregate across all of them:

monitor.addSource(publishPc);
monitor.addSource(subscribePc);

monitor.sendingVideoBitrate;   // summed across both
monitor.peerConnections;       // PeerConnectionMonitor[]

Closed connections

When a peer connection closes, the monitor releases the associated monitors and emits the corresponding events. There is no removeSource to call.

mediasoup

Adding a mediasoup Device is the recommended integration: the monitor hooks the device’s newtransport event, so every transport created after the device is added is monitored automatically.

import mediasoup from "mediasoup-client";
import { ClientMonitor } from "@observertc/client-monitor-js";

const device = new mediasoup.Device();
await device.load({ routerRtpCapabilities });

const monitor = new ClientMonitor({ clientId, callId });
monitor.addSource(device);

// Automatically monitored — no extra call needed.
const sendTransport = device.createSendTransport(transportOptions);
const producer = await sendTransport.produce({ track: videoTrack });

Transports created before the device was added

The newtransport hook only sees transports created after addSource(device). If your application creates transports first and wires up monitoring later, add those transports explicitly:

monitor.addSource(existingTransport);

The mediasoup integration also installs a stats adapter that filters mediasoup’s probator track out of the collected stats, so probe traffic does not pollute your bitrates or trigger detectors.

Tagging tracks for server-side correlation

If you are going to feed samples to observer-js and want it to link a publisher’s outbound track to every subscriber’s inbound track, put mediasoup’s ids into the track attachments. The default resolver on the server reads producerId and consumerId:

const producer = await sendTransport.produce({ track });
monitor.getTrackMonitor(track.id).attachments = {
    producerId: producer.id,
    direction: "send",
    label: "camera",
};

const consumer = await recvTransport.consume(consumerOptions);
monitor.getTrackMonitor(consumer.track.id).attachments = {
    consumerId: consumer.id,
    producerId: consumer.producerId,
    direction: "recv",
};

This is what makes server-side questions like “did everyone receiving Alice see the same freeze?” answerable. See Remote track resolution.

Logging

By default the library logs warn and error to the console and treats trace / debug / info as no-ops. Pass your own logger to route everything into your application’s logging stack — the same instance is propagated into sources and internal monitors, and messages carry module prefixes such as [ClientMonitor]: and [Sources]:.

Breaking change in 4.3.0

The global setLogger() API was removed in favour of per-instance injection through the constructor. If you were calling setLogger, move that object into new ClientMonitor({ logger }).

Browser environment integration

Two optional helpers enrich samples with environment information:

integrateNavigatorMediaDevices defaults to true, so the monitor watches navigator.mediaDevices and records the device list and any device changes as client metadata without you doing anything. Set it to false in the configuration to opt out.

Browser, engine, platform and OS are resolved from User-Agent Client Hints on start-up; you can also trigger it yourself:

await monitor.fetchUserAgentData();

All of this arrives server-side as client-metadata events and populates observedClient.browser / .platform / .operationSystem / .mediaDevices.

Browser differences

You do not need to handle browser quirks yourself — the monitor installs adapters based on detected browser:

AdapterApplies toWhat it fixes
Firefox94StatsAdapterFirefoxNormalises mediaType to kind on RTP stats
FirefoxTransportStatsAdapterFirefoxSynthesizes transport stats from ICE candidate pairs
mediasoup probator filtermediasoup sourcesDrops probator track records

If you need your own normalisation on top, see Stats adapters.