OBSERVERTC / Client Monitor
Monitor API and relationships
Navigate ownership, cross-links and the public API.
What a monitor represents
ClientMonitor is the starting point for one endpoint. Each peer connection has its own monitor, and its tracks, RTP streams, codecs, and network path are connected through references. This lets you follow a symptom from “receiving video is poor” down to the relevant stream and transport.
A track is the audio or video your application works with. An RTP stream is an encoded stream carrying that track over the network. A sending video track can have several RTP streams, such as simulcast layers, so a track and a stream are not interchangeable.
Navigate the graph
ClientMonitor — this endpoint
├── PeerConnectionMonitor — one browser connection
│ ├── InboundTrackMonitor — received audio/video
│ │ └── InboundRtpMonitor — received stream measurements
│ ├── OutboundTrackMonitor — sent audio/video
│ │ └── OutboundRtpMonitor(s) — one or more encoded streams
│ ├── Remote RTP reports — the other endpoint's reported measurements
│ ├── Codecs — encoding used by the streams
│ ├── Media sources / playout — capture and playback measurements
│ ├── ICE transports → selected candidate pair → candidates
│ └── Data channels / certificates / peer-connection statistics
└── ExtensionStatsMonitor — your additional application measurementsThe diagram groups objects for reading; references between them are described below. Root getters also provide convenient arrays across connections, so you do not need to traverse every connection for every query.
monitor.on('stats-collected', () => {
for (const connection of monitor.peerConnections) {
console.log('Connection:', connection.peerConnectionId);
}
const trackMonitor = monitor.getTrackMonitor(mediaTrack.id);
if (trackMonitor) {
console.log('Track score:', trackMonitor.score);
}
});mediaTrack is a MediaStreamTrack from your application. A lookup can return undefined before statistics discover the object or after it is removed. Read the graph after stats-collected when you need the latest metrics.
Choose the right scope
| Question | Start with |
|---|---|
| Is this endpoint generally healthy? | The root client’s aggregate metrics and score. |
| Which connection is failing? | peerConnections and the connection’s issues/transport state. |
| Is a specific camera or received video affected? | getTrackMonitor(trackId) and its associated RTP measurements. |
| Are multiple encodings being sent? | The outbound track’s RTP streams. |
| What network path is carrying media? | ICE transports, selected pairs, and local/remote candidates. |
| What did the remote endpoint report? | Remote RTP reports when the browser supplies them. |
| Which fields exist and what are their types? | The public monitor catalog. |
Public operations
src/index.ts is the package export boundary. ClientMonitor is the root EventEmitter3-based API. Its core operations are:
| Need | Public surface |
|---|---|
| Attach instrumentation | addSource(source, type?), removeSource(source, type?); types: RTCPeerConnection, mediasoup device, mediasoup transport |
| Control updates | collect(), setCollectingPeriod(ms), setSamplingPeriod(ms), createSample(), close() |
| Identity / app-owned state | clientId, callId, appData, attachments |
| Traverse | peerConnections, tracks, inboundRtps, outboundRtps, remote RTP arrays, codecs, ICE entities, media sources/playouts, certificates, data channels; getPeerConnectionMonitor, getTrackMonitor, direction-specific getters |
| Declare intent | setInboundTrackContext(trackId, context), setOutboundTrackContext(trackId, context); pending context is applied when the track appears |
| Extend | extensionStatsProviders, addExtensionStats, getExtensionStatsPayload, getExtensionStatsMonitor; replaceable scoreCalculator; per-PC statsAdapters; scoped detector registries |
| Record | addEvent, addIssue, raiseIssue, resolveIssue, addMetaData, join/left event helpers |
| Subscribe | on, once, off, typed ClientMonitorEvents; convenience callback setters |
Source references
Export nuances matter: DefaultScoreCalculator is exported as a type from the package root, despite being a runtime class internally; not every detector class is root-exported merely because its config type is.
Ownership and cross-links
The graph has ownership and cross-links:
- Client → peer connections, plus root extension monitors and client detector registry.
- PC → maps of codecs, inbound/outbound RTP, remote RTP, sources/playouts, data channels, ICE candidates/pairs/transports, certificates, peer-connection stats, inbound/outbound tracks.
- RTP → codec (
codecId), ICE transport (transportId), counterpart remote report, track; outbound RTP → media source; inbound RTP → media playout. - Track → actual
MediaStreamTrackplus associated stream(s); outbound tracks can own several simulcast encodings, while an inbound track holds its inbound RTP monitor. - ICE transport → selected pair; pair → local/remote candidates;
SelectedIcePathtracks path identity, evidence and transitions. A PC may contain several transports; never assume one transport equals one PC. - Track/PC issue registries forward toward the root registry. Ownership is not the same as the detector taxonomy.
Source references
- client-monitor-js/src/monitors/PeerConnectionMonitor.ts
- client-monitor-js/src/monitors/InboundRtpMonitor.ts
- client-monitor-js/src/monitors/OutboundRtpMonitor.ts
- client-monitor-js/src/monitors/InboundTrackMonitor.ts
- client-monitor-js/src/monitors/OutboundTrackMonitor.ts
- client-monitor-js/src/monitors/SelectedIcePath.ts
- client-monitor-js/src/utils/IssueRegistry.ts
Identifier scope
RTP monitor maps use SSRC in several lookup paths; stats-object IDs are not interchangeable with SSRC or MediaStreamTrack IDs. Preserve each identifier’s role when adding fields or joining server entities. visited getters can consume/reset bookkeeping state; never read them from an inspector UI.