Title here
Summary here
new ClientMonitor(config?)Creates a monitor. Every configuration field is optional — see Configuration for the full object and defaults.
const monitor = new ClientMonitor(config?: ClientMonitorConfig);| Method | Description |
|---|---|
addSource(source) | Adds an RTCPeerConnection, mediasoup Device or mediasoup transport for monitoring |
collect() | Runs one stats collection immediately, outside the timer |
createSample() | Builds a ClientSample from current state; requires bufferingEventsForSamples when automatic sampling is off |
close() | Stops collection, releases monitors, auto-resolves open issues, emits 'close' |
| Method | Description |
|---|---|
setCollectingPeriod(periodInMs) | Changes the stats collection interval |
setSamplingPeriod(periodInMs) | Changes the sampling interval |
setScore(score, reasons?) | Sets the client score directly, bypassing the calculator |
| Method | Description |
|---|---|
addEvent(event) | Records an immutable ClientEvent |
addIssue({ type, payload?, timestamp? }) | Records a one-shot issue; emits 'issue', never enters activeIssues |
raiseIssue(key, { type, payload?, timestamp? }) | Creates or refreshes a stateful issue; emits 'issue' or 'issue-updated' |
resolveIssue(key, { comment?, payload?, resolvedAt? }) | Resolves a stateful issue; emits 'issue-resolved' |
getActiveIssuesByType(type?) | Snapshot of active stateful issues, optionally filtered |
isIssueActive(key) | true when an issue with that key is open |
addMetaData(metaData) | Records a ClientMetaData item |
addExtensionStats(stats) | Records a one-off ExtensionStat |
| Method | Description |
|---|---|
getTrackMonitor(trackId) | Track monitor by MediaStreamTrack id |
fetchUserAgentData() | Resolves User-Agent Client Hints into client metadata |
| Property | Type | Description |
|---|---|---|
score | number | undefined | Current client score, 0.0–5.0 |
scoreReasons | Record<string, number> | undefined | Penalty breakdown |
closed | boolean | Whether close() has been called |
config | ClientMonitorConfig | The effective configuration |
detectors | Detectors | Client-level detector registry |
extensionStatsProviders | Set<ExtensionStatProvider> | Providers called on every collection |
peerConnections | PeerConnectionMonitor[] | Monitored peer connections |
mappedPeerConnections | Map<string, PeerConnectionMonitor> | Same, keyed by id |
tracks | (InboundTrackMonitor | OutboundTrackMonitor)[] | All monitored tracks |
activeIssues | Map<string, RaisedClientIssue> | Open stateful issues, keyed by key |
attachments | Record<string, unknown> | Shipped with every sample |
appData | Record<string, unknown> | Local only, never shipped |
Derived client-level metrics (sendingVideoBitrate, avgRttInSec,
totalAvailableOutgoingBitrate, durationOfCollectingStatsInMs, …) are documented in
Monitors & derived metrics.
interface ClientMonitorEvents {
// Collection & sampling
"stats-collected": (data: {
durationOfCollectingStatsInMs: number;
collectedStats: [string, RTCStats[]][];
}) => void;
"sample-created": (sample: ClientSample) => void;
// Scoring
"score": (data: { clientScore: number; scoreReasons?: Record<string, number> }) => void;
// Issue lifecycle
"issue": (issue: ClientIssue) => void;
"issue-updated": (issue: RaisedClientIssue) => void;
"issue-resolved": (issue: ResolvedClientIssue) => void;
// Application records
"client-event": (event: ClientEvent) => void;
// Detector-specific
"congestion": (e: CongestionEvent) => void;
"cpulimitation": (e: CpuPerformanceEvent) => void;
"audio-desync-track": (e: AudioDesyncEvent) => void;
"freezed-video-track": (e: FreezedVideoTrackEvent) => void;
"dry-inbound-track": (e: DryInboundTrackEvent) => void;
"dry-outbound-track": (e: DryOutboundTrackEvent) => void;
"inbound-video-playout-discrepancy": (e: PlayoutDiscrepancyEvent) => void;
// Lifecycle
"close": () => void;
}Detectorinterface Detector {
readonly name: string;
disabled?: boolean;
update(): void;
}StatsAdapterinterface StatsAdapter {
readonly name: string;
adapt(stats: RtcStats[]): RtcStats[]; // before monitors update — required
postAdapt?(stats: RtcStats[]): RtcStats[]; // after monitors update — optional
}Registered on a peer connection monitor, not on the client:
peerConnectionMonitor.statsAdapters.add(adapter);ExtensionStatProvidertype ExtensionStatProvider = () =>
| { type: string; payload?: Record<string, unknown> }
| Promise<{ type: string; payload?: Record<string, unknown> }>;ScoreCalculatorinterface ScoreCalculator {
update(): void;
encodeClientScoreReasons?<T extends Record<string, number>>(reasons?: T): string;
encodePeerConnectionScoreReasons?<T extends Record<string, number>>(reasons?: T): string;
encodeInboundAudioScoreReasons?<T extends Record<string, number>>(reasons?: T): string;
encodeInboundVideoScoreReasons?<T extends Record<string, number>>(reasons?: T): string;
encodeOutboundAudioScoreReasons?<T extends Record<string, number>>(reasons?: T): string;
encodeOutboundVideoScoreReasons?<T extends Record<string, number>>(reasons?: T): string;
}Loggerinterface Logger {
trace(...args: unknown[]): void;
debug(...args: unknown[]): void;
info(...args: unknown[]): void;
warn(...args: unknown[]): void;
error(...args: unknown[]): void;
}Each built-in detector’s payload type is exported from the package root, along with the discriminated unions and type guards described in Events & issues:
import {
AudioDesyncIssuePayload,
CongestionIssuePayload,
CpuPerformanceIssuePayload,
DryInboundTrackIssuePayload,
DryOutboundTrackIssuePayload,
FreezedVideoTrackIssuePayload,
PlayoutDiscrepancyIssuePayload,
ClientMonitorIssue,
ClientMonitorResolvedIssue,
isClientMonitorIssue,
isClientMonitorResolvedIssue,
} from "@observertc/client-monitor-js";CpuPerformanceDetector no longer infers inbound CPU limitation from frame-rate volatility, which
false-triggered on screen share. It now uses the decoded-to-received frames ratio.
cpuPerformanceDetector.fpsVolatilityThresholds was replaced by
incomingDecodedFramesRatioThresholds ({ alertOn: 0.7, alertOff: 0.85, minReceivedFrames: 10 }).dataChannels added to peer-connection sample serialisation, so data channel stats now reach
ClientSample.peerConnections[].dataChannels.raiseIssue(key, …) / resolveIssue(key, …);
resolveActiveIssues removed.activeIssues changed from Record<string, ClientIssue[]> to Map<string, RaisedClientIssue>.createIssue and disabled removed from detector config blocks; use null config or the
runtime detector.disabled flag.'resolved-issue' renamed to 'issue-resolved'.setLogger removed in favour of new ClientMonitor({ logger }).ClientMonitorIssue / ClientMonitorResolvedIssue unions and type guards added.Detectors registry gained has, getByName, find, filter, iteration and
disable / enable / disableAll / enableAll / isEnabled.Full history: CHANGELOG on GitHub.