client-monitor-js
The documentation is created for client-monitor-js
version 2.x.y
Install
npm i @observertc/client-monitor-js
Source code
https://github.com/ObserveRTC/client-monitor-js
Top-level components
// Using ES6 import.
import * as ObserveRTC from '@observertc/client-monitor-js';
// Using CommonJS.
const ObserveRTC = require("@observertc/client-monitor-js");
// Or using destructuring assignment:
import {
/**
* An interface for the ClientMonitor.
*/
ClientMonitor,
/**
* Type for ClientMonitorConfig.
*/
ClientMonitorConfig,
/**
* interface for ClientMonitor events and emitted data types
*/
ClientMonitorEvents,
/**
* collection of types the client-monitor collect w3c stats
*/
W3CStats,
/**
* Additional types are imported from the `@observertc/sample-schemas-js` package
*/
Samples,
ClientSample,
SfuSample,
ExtensionStat,
PeerConnectionTransport,
IceCandidatePair,
MediaSourceStat,
MediaCodecStats,
InboundAudioTrack,
InboundVideoTrack,
OutboundAudioTrack,
OutboundVideoTrack,
IceLocalCandidate,
IceRemoteCandidate,
CustomCallEvent,
/**
* interface for entries ClientMonitor storage provide
*/
CodecEntry,
InboundRtpEntry,
OutboundRtpEntry,
RemoteInboundRtpEntry,
RemoteOutboundRtpEntry,
MediaSourceEntry,
ContributingSourceEntry,
DataChannelEntry,
TransceiverEntry,
SenderEntry,
ReceiverEntry,
TransportEntry,
SctpTransportEntry,
IceCandidatePairEntry,
LocalCandidateEntry,
RemoteCandidateEntry,
CertificateEntry,
IceServerEntry,
PeerConnectionEntry,
} from '@observertc/client-monitor-js';
createClientMonitor
Creates a new ClientMonitor instance with the specified configuration.
import * as ObserveRTC from '@observertc/client-monitor-js';
// quick start to create a monitor and log the collected stats
const monitor = ObserveRTC.createClientMonitor({
collectingPeriodInMs: 2000,
});
monitor.on('stats-collected', stats => {
console.log('The collected stats', stats);
});
import * as ObserveRTC from '@observertc/client-monitor-js';
// configuration for the client-monitor
const config: ObserveRTC.ClientMonitorConfig = {
/**
* By setting it, the monitor calls the added statsCollectors periodically
* and pulls the stats.
*
* DEFAULT: undefined
*/
collectingPeriodInMs: 5000,
/**
* By setting it, the monitor make samples periodically.
*
* DEFAULT: undefined
*/
samplingPeriodInMs: 10000,
/**
* By setting it, the monitor sends the samples periodically.
*
* DEFAULT: undefined
*/
sendingPeriodInMs: 10000,
/**
* By enabling this option, the monitor automatically generates events
* when a peer connection added to the collector undergoes a change in connection state or when a track on it is added or removed.
*
* If this option is set to true, the samples created by the monitor will include the generated events. However, if
* no sample is created, events will accumulate indefinitely within the monitor. It is recommended to set this option to true
* if you want to create a sample with events.
*
* DEFAULT: false
*/
createCallEvents: false,
/**
* Collector Component related configurations
*
* DEFAULT: configured by the monitor
*/
collectors: {
/**
* Sets the adapter adapt different browser type and version
* provided stats.
*
* DEFAULT: configured by the monitor
*/
adapter: {
/**
* the type of the browser, e.g.: chrome, firefox, safari
*
* DEFAULT: configured by the collector
*/
browserType: "chrome",
/**
* the version of the browser, e.g.: 97.xx.xxxxx
*
* DEFAULT: configured by the collector
*/
browserVersion: "97.1111.111",
},
},
/**
* Configuration for the samples accumulator to balance the transfer the size of the Samples
* prepared to be sent to the server
*
*/
accumulator: {
/**
* Sets the maximum number of client sample allowed to be in one Sample
*
* DEFAULT: 100
*/
maxClientSamples: 100,
/**
* Sets the maximum number of Samples the accumulator can hold
*
* DEFAULT: 10
*/
maxSamples: 10,
/**
* Forward a Sample to the server even if it is empty
*
* DEFAULT: false
*/
forwardIfEmpty: false
}
};
const monitor = ObserveRTC.createClientMonitor(config);
Integrations
Mediasoup
import { createClientMonitor } from "@observertc/client-monitor-js";
import mediasoup from "mediaousp-client";
const mediasoupDevice = new mediasoup.Device();
const config = {
collectingPeriodInMs: 5000,
};
const monitor = createClientMonitor(config);
// adds a mediasoup device to the collectors
const mediasoupStatsCollector = monitor.collectors.collectFromMediasoupDevice(mediasoupDevice);
// close the statscollector and remove it from collectors
mediasoupStatsCollector.close();
Important Note: The created collector is hooked on the device ‘newtransport’ event,
and can detect transports automatically when they are created after the device is added.
If you create transports before you add the device to the monitor,
transports you created before will not be monitored automatically, you need to add them
to the statscollector, like this:
const myTransport = // your transport created before the device is added to the monitor
mediasoupStatsCollector.addTransport(myTransport)
ClientMonitor
The ClientMonitor is the central element for evaluating WebRTC apps running in the browser. It collects stats from peer connections, creates samples, and provides access to storage. This storage organizes the collected stats and allows for structured access.
Events
ClientMonitor emitted stats-collected
, sample-created
, send
events.
stats-collected
Emitted event after each collection period or if the monitor.collect()
method is called.
const subscription = stats => {
console.log('The follosing stats are collected:', stats);
};
monitor.on('stats-collected', subscription);
monitor.once('stats-collected', subscription);
monitor.off('stats-collected', subscription);
sample-created
Emitted event after each sampling period or if the monitor.sample()
method is called.
const subscription = clientSample => {
console.log('The ClientSample is created', clientSample);
};
monitor.on('sample-created', subscription);
monitor.once('sample-created', subscription);
monitor.off('sample-created', subscription);
send
Emitted event after each sending period or if the monitor.send()
method is called.
const subscription = samples => {
console.log('Samples are ready for sending', samples);
};
monitor.on('send', subscription);
monitor.once('send', subscription);
monitor.off('send', subscription);
Properties
config
: The assigned configuration for the ClientMonitor upon creation.os
: Information about the operating system from which the observer is obtained.browser
: Information about the browser from which the observer is obtained.platform
: Information about the platform from which the observer is obtained.engine
: Information about the engine from which the observer is obtained.audioInputs
: Iterable iterator for the audio input devices obtained by the observer.audioOutputs
: Iterable iterator for the audio output devices obtained by the observer.videoInputs
: Iterable iterator for the video input devices obtained by the observer.metrics
: Provides access to the observer’s self metrics (last collection time, etc.).storage
: Provides access to the collected stats.collectors
: Provides access to built-in integrations for different providers.closed
: Flag indicating whether the monitor is closed or not.
const config = monitor.config;
console.log(
'The assigned configuration for the ClientMonitor upon creation.',
config
);
const os = monitor.os;
console.log(
'Information about the operating system from which the observer is obtained:',
os
);
const browser = monitor.browser;
console.log(
'Information about the browser from which the observer is obtained:',
browser
);
const platform = monitor.platform;
console.log(
'Information about the platform from which the observer is obtained:',
platform
);
const audioInputs = monitor.audioInputs;
for (const audioInput of audioInputs) {
console.log('Audio input device obtained by the observer:', audioInput);
}
const audioOutputs = monitor.audioOutputs;
for (const audioOutput of audioOutputs) {
console.log('Audio output device obtained by the observer:', audioOutput);
}
const videoInputs = monitor.videoInputs;
for (const videoInput of videoInputs) {
console.log('Video input device obtained by the observer:', videoInput);
}
const metrics = monitor.metrics;
console.log(
"Access to the observer's self metrics (last collection time, etc.):",
metrics
);
const storage = monitor.storage;
console.log('Access to the collected stats:', storage);
const collectors = monitor.collectors;
console.log(
'Access to built-in integrations for different providers:',
collectors
);
const closed = monitor.closed;
console.log('Flag indicating whether the monitor is closed or not:', closed);
addTrackRelation
Adds a track relation to bind tracks to produced/published or consumed/subscribed media objects from SFUs.
monitor.addTrackRelation({
/**
* The identifier of the track the relation is defined for.
*/
trackId: mediaStreamTrack.id;
/**
* The identifier of the media stream published / produced using the above defined track id.
*/
sfuStreamId: 'outbound-track-sfu-stream-id'
/**
* The identifier of the media sink subscribed / consumed for streaming the above defined track id.
*/
sfuSinkId: 'inbound-track-sfu-sink-id'
});
removeTrackRelation
Removes a track relation associated with the given track id.
monitor.removeTrackRelation(mediaStreamTrack.id);
addLocalSDP
Adds the local part of the Session Description Protocol (SDP). The Monitor adds it to the next sample it creates and sends it to the observer.
monitor.addLocalSDP('a=...');
addMediaConstraints
Adds media constraints used to obtain media. Typically, these are the parameters given to MediaDevices.getUserMedia(). Constraints added to the observer are sampled by a sampler when a ClientSample is created.
const constraints = {
audio: true,
video: true
};
monitor.addMediaConstraints(constraints);
navigator.getUserMedia(constraints);
addUserMediaError
Adds a user media error. Typically, this is an error caught while obtaining getUserMedia from MediaDevices. The obtained user media error is added to the observer and sampled by a sampler when a ClientSample is created.
navigator.getUserMedia(constraints, () => {
// use the stream
}, err => {
// adds the error to the sample can be send to the server
monitor.addUserMediaError(err);
})
addMediaTrackAddedCallEvent
Adds a MEDIA_TRACK_ADDED type call event to the sample created next time.
monitor.addMediaTrackAddedCallEvent('peerConnectionId', 'mediaTrackId');
addMediaTrackRemovedCallEvent
Adds a MEDIA_TRACK_REMOVED type call event to the sample created next time.
monitor.addMediaTrackRemovedCallEvent('peerConnectionId', 'mediaTrackId');
addPeerConnectionOpenedCallEvent
Adds a PEER_CONNECTION_OPENED type call event to the sample created next time.
monitor.addPeerConnectionOpenedCallEvent('peerConnectionId');
addPeerConnectionClosedCallEvent
Adds a PEER_CONNECTION_CLOSED type call event to the sample created next time.
monitor.addPeerConnectionClosedCallEvent('peerConnectionId');
addIceConnectionStateChangedCallEvent
Adds an ICE_CONNECTION_STATE_CHANGED type call event to the sample created next time.
monitor.addIceConnectionStateChangedCallEvent('peerConnectionId', 'connectionState');
addCustomCallEvent
Adds a custom call event that will be sent along with the sample to the observer. The added event will be reported as a CallEvent by the observer.
monitor.addCustomCallEvent({
type: 'custom-event',
payload: { customData: 'example' },
});
addExtensionStats
Adds an application-provided custom payload object to the observer. This is typically extra information that the application wants to obtain and send to the backend. The added information is obtained by the sampler, and the ClientSample holds and sends this information to the observer. The observer will forward this information along with the call it belongs to.
monitor.addExtensionStats({ name: 'customStats', value: 'example' });
setMediaDevices
Sets the media devices used by the WebRTC app. Typically, this is a list of MediaDevices.getUserMedia().
The client monitor keeps track of the already added devices, removes the ones not updated, and in the next sample sent to the observer, only the new devices will be sent
monitor.setMediaDevices(device1, device2, device3);
setCollectingPeriod
Sets the collecting period in milliseconds. The Monitor calls its collect method at the specified time interval.
monitor.setCollectingPeriod(1000);
setSamplingPeriod
Sets the sampling period in milliseconds. The Monitor calls its sample method at the specified time interval.
monitor.setSamplingPeriod(1000);
setSendingPeriod
Sets the sending period in milliseconds. The Monitor calls its send method with a given time period.
monitor.setSendingPeriod(1000);
collect
Collect all stats simultaneously and update the storage.
monitor.collect().then(() => console.log('stats are collected'));
sample
Make ClientSample from collected stats.
monitor.sample();
send
Create Samples from the accumulated ClientSamples (and/or SfuSamples) and emit send event with the created samples.
monitor.send();
close
Close the ClientObserver, clear the storage, and stats collectors.
monitor.close();
Collectors
The Collectors
interface represents a collection of StatsCollector
instances. It provides methods for managing and interacting with these instances.
hasCollector
Checks if a collector with the given collectorId
exists in the collection.
removeCollector
Removes a collector with the given collectorId
from the collection.
addGetStats
Adds a custom stats collector using a getStats
function.
const collector = monitor.addGetStats(() => myPeerConnection.getStats());
addStatsProvider
Adds a stats collector based on a provided StatsProvider
.
const collector = monitor.addStatsProvider({
/**
* A unique id for a peer connection the stats belongs to.
*/
peerConnectionId: 'unique-pc-id'
/**
* An optional label of the peer collector added as label to the samples as well.
*/
label: 'sending-pc',
/**
* Set a function called when stats are collected
*/
getStats: myPeerConnection.getStats,
});
addRTCPeerConnection
Adds a stats collector for an RTCPeerConnection
.
const collector = monitor.addRTCPeerConnection(peerConnection);
addMediasoupDevice
Adds a stats collector for a Mediasoup device.
const collector = monitor.addMediasoupDevice(mediasoupDevice);
Storage
Client Monitor collects WebRTCStats. The collected stats can be accessed through entries the client monitor storage provides.
inboundRtps
const storage = monitor.storage;
for (const inboundRtp of storage.inboundRtps()) {
const receiver = inboundRtp.getReceiver();
const trackId = inboundRtp.getTrackId();
const ssrc = inboundRtp.getSsrc();
const remoteOutboundRtp = inboundRtp.getRemoteOutboundRtp();
const peerConnection = inboundRtp.getPeerConnection();
const transport = inboundRtp.getTransport();
const codec = inboundRtp.getCodec();
console.log(trackId, ssrc,
inboundRtp.stats,
remoteOutboundRtp.stats,
receiver.stats,
peerConnection.stats,
transport.stats,
codec.stats
);
}
outboundRtps
const storage = monitor.storage;
for (const outboundRtp of storage.outboundRtps()) {
const sender = outboundRtp.getSender();
const trackId = outboundRtp.getTrackId();
const ssrc = outboundRtp.getSsrc();
const remoteInboundRtp = outboundRtp.getRemoteInboundRtp();
const peerConnection = outboundRtp.getPeerConnection();
const transport = outboundRtp.getTransport();
const mediaSource = outboundRtp.getMediaSource();
console.log(trackId, ssrc,
outboundRtp.stats,
remoteInboundRtp.stats,
sender.stats,
peerConnection.stats,
transport.stats,
mediaSource.stats
);
}
remoteInboundRtps
const storage = monitor.storage;
for (const remoteInboundRtp of storage.remoteInboundRtps()) {
const ssrc = remoteInboundRtp.getSsrc();
const outboundRtp = remoteInboundRtp.getOutboundRtp();
const peerConnection = remoteInboundRtp.getPeerConnection();
console.log(ssrc,
remoteInboundRtp.stats,
outboundRtp.stats,
peerConnection.stats
);
}
remoteOutboundRtps
const storage = monitor.storage;
for (const remoteOutboundRtp of storage.remoteOutboundRtps()) {
const ssrc = remoteOutboundRtp.getSsrc();
const inboundRtp = remoteOutboundRtp.getInboundRtp();
const peerConnection = remoteOutboundRtp.getPeerConnection();
console.log(ssrc,
remoteOutboundRtp.stats,
inboundRtp.stats,
peerConnection.stats
);
}
mediaSources
const storage = monitor.storage;
for (const mediaSource of storage.mediaSources()) {
const peerConnection = mediaSource.getPeerConnection();
console.log(
mediaSource.stats,
peerConnection.stats
);
}
contributingSources
const storage = monitor.storage;
for (const cssrc of storage.contributingSources()) {
const peerConnection = cssrc.getPeerConnection();
console.log(
cssrc.stats,
peerConnection.stats
);
}
dataChannels
const storage = monitor.storage;
for (const dataChannel of storage.dataChannels()) {
const peerConnection = dataChannel.getPeerConnection();
console.log(
dataChannel.stats,
peerConnection.stats
);
}
transceivers
const storage = monitor.storage;
for (const transceiver of storage.transceivers()) {
const receiver = transceiver.getReceiver();
const sender = transceiver.getSender();
const peerConnection = transceiver.getPeerConnection();
console.log(
transceiver.stats,
receiver.stats,
sender.stats,
peerConnection.stats
);
}
senders
const storage = monitor.storage;
for (const sender of storage.senders()) {
const mediaSource = sender.getMediaSource();
const peerConnection = sender.getPeerConnection();
console.log(
sender.stats,
mediaSource.stats,
peerConnection.stats
);
}
receivers
const storage = monitor.storage;
for (const receiver of storage.receivers()) {
const peerConnection = receiver.getPeerConnection();
console.log(
receiver.stats,
peerConnection.stats
);
}
transports
const storage = monitor.storage;
for (const transport of storage.transports()) {
const contributingTransport = transport.getRtcpTransport();
const selectedIceCandidatePair = transport.getSelectedIceCandidatePair();
const localCandidate = transport.getLocalCertificate();
const remoteCandidate = transport.getRemoteCertificate();
const peerConnection = transport.getPeerConnection();
console.log(
transport.stats,
contributingTransport?.stats,
selectedIceCandidatePair?.stats,
localCandidate?.stats,
remoteCandidate?.stats,
remoteCandidate?.stats
);
}
sctpTransports
const storage = monitor.storage;
for (const sctpTransport of storage.sctpTransports()) {
const transport = sctpTransport.getTransport();
const peerConnection = sctpTransport.getPeerConnection();
console.log(
sctpTransport.stats,
transport?.stats,
peerConnection?.stats,
);
}
iceCandidatePairs
const storage = monitor.storage;
for (const iceCandidatePair of storage.iceCandidatePairs()) {
const transport = iceCandidatePair.getTransport();
const localCandidate = iceCandidatePair.getLocalCandidate();
const remoteCandidate = iceCandidatePair.getRemoteCandidate();
const peerConnection = iceCandidatePair.getPeerConnection();
console.log(
iceCandidatePair.stats,
transport?.stats,
localCandidate?.stats,
remoteCandidate?.stats,
peerConnection?.stats,
);
}
localCandidates
const storage = monitor.storage;
for (const localCandidate of storage.localCandidates()) {
const transport = localCandidate.getTransport();
const peerConnection = localCandidate.getPeerConnection();
console.log(
localCandidate.stats,
peerConnection?.stats,
);
}
remoteCandidates
const storage = monitor.storage;
for (const remoteCandidate of storage.remoteCandidates()) {
const transport = remoteCandidate.getTransport();
const peerConnection = remoteCandidate.getPeerConnection();
console.log(
remoteCandidate.stats,
peerConnection?.stats,
);
}
certificates
const storage = monitor.storage;
for (const certificate of storage.certificates()) {
const peerConnection = certificate.getPeerConnection();
console.log(
certificate.stats,
peerConnection?.stats,
);
}
iceServers
const storage = monitor.storage;
for (const iceServer of storage.iceServers()) {
const peerConnection = iceServer.getPeerConnection();
console.log(
iceServer.stats,
peerConnection?.stats,
);
}
peerConnections
const storage = monitor.storage;
for (const peerConnection of storage.peerConnections()) {
for (const codec of peerConnection.getCodecs())
console.log(
`peerConnection(${peerConnection.id}).codec(${codec.id}).stats: `,
codec.stats
);
for (const inboundRtp of peerConnection.inboundRtps())
console.log(
`peerConnection(${peerConnection.id}).inboundRtp(${inboundRtp.id}).stats: `,
inboundRtp.stats
);
for (const outboundRtp of peerConnection.outboundRtps())
console.log(
`peerConnection(${peerConnection.id}).outboundRtp(${outboundRtp.id}).stats: `,
outboundRtp.stats
);
for (const remoteInboundRtp of peerConnection.remoteInboundRtps())
console.log(
`peerConnection(${peerConnection.id}).remoteInboundRtp(${remoteInboundRtp.id}).stats: `,
remoteInboundRtp.stats
);
for (const remoteOutboundRtp of peerConnection.remoteOutboundRtps())
console.log(
`peerConnection(${peerConnection.id}).remoteOutboundRtp(${remoteOutboundRtp.id}).stats: `,
remoteOutboundRtp.stats
);
for (const mediaSource of peerConnection.mediaSources())
console.log(
`peerConnection(${peerConnection.id}).mediaSource(${mediaSource.id}).stats: `,
mediaSource.stats
);
for (const cssrc of peerConnection.contributingSources())
console.log(
`peerConnection(${peerConnection.id}).cssrc(${cssrc.id}).stats: `,
cssrc.stats
);
for (const dataChannel of peerConnection.dataChannels())
console.log(
`peerConnection(${peerConnection.id}).dataChannel(${dataChannel.id}).stats: `,
dataChannel.stats
);
for (const transceiver of peerConnection.transceivers())
console.log(
`peerConnection(${peerConnection.id}).transceiver(${transceiver.id}).stats: `,
transceiver.stats
);
for (const sender of peerConnection.senders())
console.log(
`peerConnection(${peerConnection.id}).sender(${sender.id}).stats: `,
sender.stats
);
for (const receiver of peerConnection.receivers())
console.log(
`peerConnection(${peerConnection.id}).receiver(${receiver.id}).stats: `,
receiver.stats
);
for (const transport of peerConnection.transports())
console.log(
`peerConnection(${peerConnection.id}).transport(${transport.id}).stats: `,
transport.stats
);
for (const sctpTransport of peerConnection.sctpTransports())
console.log(
`peerConnection(${peerConnection.id}).sctpTransport(${sctpTransport.id}).stats: `,
sctpTransport.stats
);
for (const iceCandidate of peerConnection.iceCandidatePairs())
console.log(
`peerConnection(${peerConnection.id}).iceCandidate(${iceCandidate.id}).stats: `,
iceCandidate.stats
);
for (const localCandidate of peerConnection.localCandidates())
console.log(
`peerConnection(${peerConnection.id}).localCandidate(${localCandidate.id}).stats: `,
localCandidate.stats
);
for (const remoteCandidate of peerConnection.remoteCandidates())
console.log(
`peerConnection(${peerConnection.id}).remoteCandidate(${remoteCandidate.id}).stats: `,
remoteCandidate.stats
);
for (const certificate of peerConnection.certificates())
console.log(
`peerConnection(${peerConnection.id}).certificate(${certificate.id}).stats: `,
certificate.stats
);
for (const iceServer of peerConnection.iceServers())
console.log(
`peerConnection(${peerConnection.id}).iceServer(${iceServer.id}).stats: `,
iceServer.stats
);
console.log(`peerConnection(${peerConnection.id}) trackIds:`,
Array.from(peerConnection.trackIds())
);
}