@observertc/client-monitor-js 3.11.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1821 -667
- package/dist/ClientMonitor.d.ts +114 -0
- package/dist/ClientMonitor.js +1 -0
- package/dist/ClientMonitorConfig.d.ts +66 -0
- package/dist/ClientMonitorConfig.js +1 -0
- package/dist/ClientMonitorEvents.d.ts +170 -0
- package/dist/ClientMonitorEvents.js +1 -0
- package/dist/adapters/Firefox94StatsAdapter.d.ts +5 -0
- package/dist/adapters/Firefox94StatsAdapter.js +1 -0
- package/dist/adapters/FirefoxTransportStatsAdapter.d.ts +10 -0
- package/dist/adapters/FirefoxTransportStatsAdapter.js +1 -0
- package/dist/adapters/StatsAdapter.d.ts +6 -0
- package/dist/adapters/StatsAdapter.js +1 -0
- package/dist/adapters/StatsAdapters.d.ts +9 -0
- package/dist/adapters/StatsAdapters.js +1 -0
- package/dist/collectors/MediasoupTransportStatsCollector.d.ts +20 -0
- package/dist/collectors/MediasoupTransportStatsCollector.js +1 -0
- package/dist/collectors/RtcPeerConnectionStatsCollector.d.ts +16 -0
- package/dist/collectors/RtcPeerConnectionStatsCollector.js +1 -0
- package/dist/collectors/StatsCollector.d.ts +5 -0
- package/dist/collectors/StatsCollector.js +1 -0
- package/dist/collectors/utils.d.ts +2 -0
- package/dist/collectors/utils.js +1 -0
- package/dist/detectors/AudioDesyncDetector.d.ts +13 -0
- package/dist/detectors/AudioDesyncDetector.js +1 -0
- package/dist/detectors/CongestionDetector.d.ts +15 -0
- package/dist/detectors/CongestionDetector.js +1 -0
- package/dist/detectors/CpuPerformanceDetector.d.ts +8 -0
- package/dist/detectors/CpuPerformanceDetector.js +1 -0
- package/dist/detectors/Detector.d.ts +4 -0
- package/dist/detectors/Detector.js +1 -0
- package/dist/detectors/Detectors.d.ts +10 -0
- package/dist/detectors/Detectors.js +1 -0
- package/dist/detectors/DryInboundTrackDetector.d.ts +12 -0
- package/dist/detectors/DryInboundTrackDetector.js +1 -0
- package/dist/detectors/DryOutboundTrackDetector.d.ts +12 -0
- package/dist/detectors/DryOutboundTrackDetector.js +1 -0
- package/dist/detectors/FreezedVideoTrackDetector.d.ts +11 -0
- package/dist/detectors/FreezedVideoTrackDetector.js +1 -0
- package/dist/detectors/IceTupleChangeDetector.d.ts +9 -0
- package/dist/detectors/IceTupleChangeDetector.js +1 -0
- package/dist/detectors/LongPcConnectionEstablishment.d.ts +10 -0
- package/dist/detectors/LongPcConnectionEstablishment.js +1 -0
- package/dist/detectors/PlayoutDiscrepancyDetector.d.ts +11 -0
- package/dist/detectors/PlayoutDiscrepancyDetector.js +1 -0
- package/dist/detectors/ResolutionChangeDetector.d.ts +1 -0
- package/dist/detectors/ResolutionChangeDetector.js +1 -0
- package/dist/detectors/SynthesizedSamplesDetector.d.ts +10 -0
- package/dist/detectors/SynthesizedSamplesDetector.js +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +1 -0
- package/dist/monitors/CertificateMonitor.d.ts +19 -0
- package/dist/monitors/CertificateMonitor.js +1 -0
- package/dist/monitors/CodecMonitor.d.ts +22 -0
- package/dist/monitors/CodecMonitor.js +1 -0
- package/dist/monitors/DataChannelMonitor.d.ts +25 -0
- package/dist/monitors/DataChannelMonitor.js +1 -0
- package/dist/monitors/IceCandidateMonitor.d.ts +29 -0
- package/dist/monitors/IceCandidateMonitor.js +1 -0
- package/dist/monitors/IceCandidatePairMonitor.d.ts +44 -0
- package/dist/monitors/IceCandidatePairMonitor.js +1 -0
- package/dist/monitors/IceTransportMonitor.d.ts +38 -0
- package/dist/monitors/IceTransportMonitor.js +1 -0
- package/dist/monitors/InboundRtpMonitor.d.ts +106 -0
- package/dist/monitors/InboundRtpMonitor.js +1 -0
- package/dist/monitors/InboundTrackMonitor.d.ts +26 -0
- package/dist/monitors/InboundTrackMonitor.js +1 -0
- package/dist/monitors/MediaPlayoutMonitor.d.ts +26 -0
- package/dist/monitors/MediaPlayoutMonitor.js +1 -0
- package/dist/monitors/MediaSourceMonitor.d.ts +28 -0
- package/dist/monitors/MediaSourceMonitor.js +1 -0
- package/dist/monitors/OutboundRtpMonitor.d.ts +63 -0
- package/dist/monitors/OutboundRtpMonitor.js +1 -0
- package/dist/monitors/OutboundTrackMonitor.d.ts +29 -0
- package/dist/monitors/OutboundTrackMonitor.js +1 -0
- package/dist/monitors/PeerConnectionMonitor.d.ts +145 -0
- package/dist/monitors/PeerConnectionMonitor.js +1 -0
- package/dist/monitors/PeerConnectionTransportMonitor.d.ts +17 -0
- package/dist/monitors/PeerConnectionTransportMonitor.js +1 -0
- package/dist/monitors/RemoteInboundRtpMonitor.d.ts +31 -0
- package/dist/monitors/RemoteInboundRtpMonitor.js +1 -0
- package/dist/monitors/RemoteOutboundRtpMonitor.d.ts +30 -0
- package/dist/monitors/RemoteOutboundRtpMonitor.js +1 -0
- package/dist/monitors/TrackMonitor.d.ts +7 -0
- package/dist/monitors/TrackMonitor.js +1 -0
- package/dist/schema/ClientEventTypes.d.ts +211 -0
- package/dist/schema/ClientEventTypes.js +1 -0
- package/dist/schema/ClientMetaTypes.d.ts +33 -0
- package/dist/schema/ClientMetaTypes.js +1 -0
- package/dist/schema/ClientSample.d.ts +355 -0
- package/dist/schema/ClientSample.js +1 -0
- package/dist/schema/W3cStatsIdentifiers.d.ts +37 -0
- package/dist/schema/W3cStatsIdentifiers.js +1 -0
- package/dist/scores/CalculatedScore.d.ts +79 -0
- package/dist/scores/CalculatedScore.js +1 -0
- package/dist/scores/DefaultScoreCalculator.d.ts +47 -0
- package/dist/scores/DefaultScoreCalculator.js +1 -0
- package/dist/scores/ScoreCalculator.d.ts +9 -0
- package/dist/scores/ScoreCalculator.js +1 -0
- package/{lib/entries/UpdateFields.d.ts → dist/scores/mosScores.d.ts} +0 -1
- package/dist/scores/mosScores.js +1 -0
- package/dist/sources/ClientEventPayloadProvider.d.ts +67 -0
- package/dist/sources/ClientEventPayloadProvider.js +1 -0
- package/dist/sources/MediasoupDeviceBinding.d.ts +10 -0
- package/dist/sources/MediasoupDeviceBinding.js +1 -0
- package/dist/sources/MediasoupTransportBinding.d.ts +21 -0
- package/dist/sources/MediasoupTransportBinding.js +1 -0
- package/dist/sources/RtcPeerConnectionBinding.d.ts +36 -0
- package/dist/sources/RtcPeerConnectionBinding.js +1 -0
- package/dist/sources/Sources.d.ts +27 -0
- package/dist/sources/Sources.js +1 -0
- package/dist/sources/fetchUserAgentData.d.ts +2 -0
- package/dist/sources/fetchUserAgentData.js +1 -0
- package/dist/sources/inferSourceType.d.ts +2 -0
- package/dist/sources/inferSourceType.js +1 -0
- package/dist/sources/watchMediaDevice.d.ts +2 -0
- package/dist/sources/watchMediaDevice.js +1 -0
- package/dist/utils/Processor.d.ts +10 -0
- package/dist/utils/Processor.js +1 -0
- package/{lib → dist}/utils/common.d.ts +2 -1
- package/dist/utils/common.js +1 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.js +1 -0
- package/{lib → dist}/utils/validators.d.ts +0 -1
- package/dist/utils/validators.js +1 -0
- package/package.json +45 -22
- package/lib/ClientMonitor.d.ts +0 -318
- package/lib/ClientMonitor.d.ts.map +0 -1
- package/lib/ClientMonitor.js +0 -978
- package/lib/Collectors.d.ts +0 -50
- package/lib/Collectors.d.ts.map +0 -1
- package/lib/Collectors.js +0 -182
- package/lib/Sampler.d.ts +0 -37
- package/lib/Sampler.d.ts.map +0 -1
- package/lib/Sampler.js +0 -516
- package/lib/collectors/Adapter.d.ts +0 -14
- package/lib/collectors/Adapter.d.ts.map +0 -1
- package/lib/collectors/Adapter.js +0 -57
- package/lib/collectors/MediasoupStatsCollector.d.ts +0 -23
- package/lib/collectors/MediasoupStatsCollector.d.ts.map +0 -1
- package/lib/collectors/MediasoupStatsCollector.js +0 -305
- package/lib/collectors/MediasoupSurrogates.d.ts +0 -83
- package/lib/collectors/MediasoupSurrogates.d.ts.map +0 -1
- package/lib/collectors/MediasoupSurrogates.js +0 -2
- package/lib/collectors/PeerConnectionStatsCollector.d.ts +0 -21
- package/lib/collectors/PeerConnectionStatsCollector.d.ts.map +0 -1
- package/lib/collectors/PeerConnectionStatsCollector.js +0 -117
- package/lib/collectors/StatsCollector.d.ts +0 -12
- package/lib/collectors/StatsCollector.d.ts.map +0 -1
- package/lib/collectors/StatsCollector.js +0 -2
- package/lib/collectors/StatsProvider.d.ts +0 -7
- package/lib/collectors/StatsProvider.d.ts.map +0 -1
- package/lib/collectors/StatsProvider.js +0 -11
- package/lib/collectors/browserAdapterMiddlewares.d.ts +0 -5
- package/lib/collectors/browserAdapterMiddlewares.d.ts.map +0 -1
- package/lib/collectors/browserAdapterMiddlewares.js +0 -75
- package/lib/collectors/utils.d.ts +0 -16
- package/lib/collectors/utils.d.ts.map +0 -1
- package/lib/collectors/utils.js +0 -73
- package/lib/detectors/AudioDesyncDetector.d.ts +0 -52
- package/lib/detectors/AudioDesyncDetector.d.ts.map +0 -1
- package/lib/detectors/AudioDesyncDetector.js +0 -77
- package/lib/detectors/CongestionDetector.d.ts +0 -49
- package/lib/detectors/CongestionDetector.d.ts.map +0 -1
- package/lib/detectors/CongestionDetector.js +0 -139
- package/lib/detectors/CpuPerformanceDetector.d.ts +0 -36
- package/lib/detectors/CpuPerformanceDetector.d.ts.map +0 -1
- package/lib/detectors/CpuPerformanceDetector.js +0 -71
- package/lib/detectors/Detector.d.ts +0 -9
- package/lib/detectors/Detector.d.ts.map +0 -1
- package/lib/detectors/Detector.js +0 -2
- package/lib/detectors/LayersChanged.d.ts +0 -37
- package/lib/detectors/LayersChanged.d.ts.map +0 -1
- package/lib/detectors/LayersChanged.js +0 -75
- package/lib/detectors/LongPcConnectionEstablishment.d.ts +0 -33
- package/lib/detectors/LongPcConnectionEstablishment.d.ts.map +0 -1
- package/lib/detectors/LongPcConnectionEstablishment.js +0 -72
- package/lib/detectors/StuckedInboundTrack.d.ts +0 -42
- package/lib/detectors/StuckedInboundTrack.d.ts.map +0 -1
- package/lib/detectors/StuckedInboundTrack.js +0 -81
- package/lib/detectors/StuckedOutboundTrack.d.ts +0 -42
- package/lib/detectors/StuckedOutboundTrack.d.ts.map +0 -1
- package/lib/detectors/StuckedOutboundTrack.js +0 -81
- package/lib/detectors/VideoFreezesDetector.d.ts +0 -40
- package/lib/detectors/VideoFreezesDetector.d.ts.map +0 -1
- package/lib/detectors/VideoFreezesDetector.js +0 -87
- package/lib/entries/InboundTrackStats.d.ts +0 -28
- package/lib/entries/InboundTrackStats.d.ts.map +0 -1
- package/lib/entries/InboundTrackStats.js +0 -103
- package/lib/entries/OutboundTrackStats.d.ts +0 -27
- package/lib/entries/OutboundTrackStats.d.ts.map +0 -1
- package/lib/entries/OutboundTrackStats.js +0 -76
- package/lib/entries/PeerConnectionEntryManifest.d.ts +0 -145
- package/lib/entries/PeerConnectionEntryManifest.d.ts.map +0 -1
- package/lib/entries/PeerConnectionEntryManifest.js +0 -1036
- package/lib/entries/StatsEntryInterfaces.d.ts +0 -350
- package/lib/entries/StatsEntryInterfaces.d.ts.map +0 -1
- package/lib/entries/StatsEntryInterfaces.js +0 -2
- package/lib/entries/StatsStorage.d.ts +0 -159
- package/lib/entries/StatsStorage.d.ts.map +0 -1
- package/lib/entries/StatsStorage.js +0 -332
- package/lib/entries/UpdateFields.d.ts.map +0 -1
- package/lib/entries/UpdateFields.js +0 -36
- package/lib/index.d.ts +0 -32
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js +0 -66
- package/lib/schema/Samples.d.ts +0 -367
- package/lib/schema/Samples.d.ts.map +0 -1
- package/lib/schema/Samples.js +0 -4
- package/lib/schema/W3cStatsIdentifiers.d.ts +0 -362
- package/lib/schema/W3cStatsIdentifiers.d.ts.map +0 -1
- package/lib/schema/W3cStatsIdentifiers.js +0 -6
- package/lib/utils/CallEventType.d.ts +0 -22
- package/lib/utils/CallEventType.d.ts.map +0 -1
- package/lib/utils/CallEventType.js +0 -25
- package/lib/utils/IndexedMap.d.ts +0 -26
- package/lib/utils/IndexedMap.d.ts.map +0 -1
- package/lib/utils/IndexedMap.js +0 -219
- package/lib/utils/Processor.d.ts +0 -7
- package/lib/utils/Processor.d.ts.map +0 -1
- package/lib/utils/Processor.js +0 -38
- package/lib/utils/Stats.d.ts +0 -60
- package/lib/utils/Stats.d.ts.map +0 -1
- package/lib/utils/Stats.js +0 -204
- package/lib/utils/TypedEmitter.d.ts +0 -14
- package/lib/utils/TypedEmitter.d.ts.map +0 -1
- package/lib/utils/TypedEmitter.js +0 -38
- package/lib/utils/callEvents.d.ts +0 -26
- package/lib/utils/callEvents.d.ts.map +0 -1
- package/lib/utils/callEvents.js +0 -167
- package/lib/utils/common.d.ts.map +0 -1
- package/lib/utils/common.js +0 -14
- package/lib/utils/logger.d.ts +0 -27
- package/lib/utils/logger.d.ts.map +0 -1
- package/lib/utils/logger.js +0 -89
- package/lib/utils/validators.d.ts.map +0 -1
- package/lib/utils/validators.js +0 -24
package/README.md
CHANGED
|
@@ -1,877 +1,2031 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
1
|
+
# @observertc/client-monitor-js
|
|
2
|
+
|
|
3
|
+
**JavaScript library to monitor WebRTC applications**
|
|
4
|
+
|
|
5
|
+
@observertc/client-monitor-js is a client-side library to monitor [WebRTCStats](https://www.w3.org/TR/webrtc-stats/) and integrate your app with ObserveRTC components.
|
|
6
|
+
|
|
7
|
+
[](https://badge.fury.io/js/@observertc%2Fclient-monitor-js)
|
|
8
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
9
|
+
|
|
10
|
+
## Table of Contents
|
|
11
|
+
|
|
12
|
+
1. [Installation](#installation)
|
|
13
|
+
2. [Quick Start](#quick-start)
|
|
14
|
+
3. [Integrations](#integrations)
|
|
15
|
+
4. [Configuration](#configuration)
|
|
16
|
+
5. [ClientMonitor](#clientmonitor)
|
|
17
|
+
6. [Detectors](#detectors)
|
|
18
|
+
7. [Score Calculation](#score-calculation)
|
|
19
|
+
8. [Collecting and Adapting Stats](#collecting-and-adapting-stats)
|
|
20
|
+
9. [Sampling](#sampling)
|
|
21
|
+
10. [Events and Issues](#events-and-issues)
|
|
22
|
+
11. [WebRTC Stats Monitors](#webrtc-stats-monitors)
|
|
23
|
+
12. [Stats Adapters](#stats-adapters)
|
|
24
|
+
13. [Derived Metrics](#derived-metrics)
|
|
25
|
+
14. [Schema Reference](#schema-reference)
|
|
26
|
+
15. [Examples](#examples)
|
|
27
|
+
16. [Troubleshooting](#troubleshooting)
|
|
28
|
+
17. [API Reference](#api-reference)
|
|
29
|
+
18. [FAQ](#faq)
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @observertc/client-monitor-js
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
or
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
yarn add @observertc/client-monitor-js
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
import { ClientMonitor } from "@observertc/client-monitor-js";
|
|
47
|
+
|
|
48
|
+
// Create a monitor with default configuration
|
|
49
|
+
const monitor = new ClientMonitor({
|
|
50
|
+
clientId: "my-client-id",
|
|
51
|
+
callId: "my-call-id",
|
|
52
|
+
collectingPeriodInMs: 2000,
|
|
53
|
+
samplingPeriodInMs: 4000,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Add a peer connection to monitor
|
|
57
|
+
monitor.addSource(peerConnection);
|
|
58
|
+
|
|
59
|
+
// Listen for samples
|
|
60
|
+
monitor.on("sample-created", (sample) => {
|
|
61
|
+
console.log("Sample created:", sample);
|
|
62
|
+
// Send sample to your analytics backend
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Listen for issues
|
|
66
|
+
monitor.on("issue", (issue) => {
|
|
67
|
+
console.log("Issue detected:", issue);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Close when done
|
|
71
|
+
monitor.close();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Integrations
|
|
75
|
+
|
|
76
|
+
### RTCPeerConnection Integration
|
|
77
|
+
|
|
78
|
+
Direct integration with native WebRTC PeerConnections:
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
import { ClientMonitor } from "@observertc/client-monitor-js";
|
|
82
|
+
|
|
83
|
+
const peerConnection = new RTCPeerConnection();
|
|
84
|
+
const monitor = new ClientMonitor();
|
|
85
|
+
|
|
86
|
+
// Add the peer connection for monitoring
|
|
87
|
+
monitor.addSource(peerConnection);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Mediasoup Integration
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
import { ClientMonitor } from "@observertc/client-monitor-js";
|
|
94
|
+
import mediasoup from "mediasoup-client";
|
|
95
|
+
|
|
96
|
+
const device = new mediasoup.Device();
|
|
97
|
+
const monitor = new ClientMonitor();
|
|
98
|
+
|
|
99
|
+
// Monitor the mediasoup device
|
|
100
|
+
monitor.addSource(device);
|
|
101
|
+
|
|
102
|
+
// The monitor will automatically detect new transports created after adding the device
|
|
103
|
+
const transport = device.createSendTransport(/* ... */);
|
|
104
|
+
|
|
105
|
+
// For transports created before adding the device, add them manually:
|
|
106
|
+
monitor.addSource(transport);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Important**: When adding a mediasoup device, the monitor automatically hooks into the `newtransport` event to detect newly created transports. However, transports created before adding the device must be added manually.
|
|
110
|
+
|
|
111
|
+
### Logger Integration
|
|
112
|
+
|
|
113
|
+
Customize logging behavior by providing your own logger:
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
import { setLogger, Logger } from "@observertc/client-monitor-js";
|
|
117
|
+
|
|
118
|
+
const customLogger: Logger = {
|
|
119
|
+
trace: (...args) => console.trace(...args),
|
|
120
|
+
debug: (...args) => console.debug(...args),
|
|
121
|
+
info: (...args) => console.info(...args),
|
|
122
|
+
warn: (...args) => console.warn(...args),
|
|
123
|
+
error: (...args) => console.error(...args),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
setLogger(customLogger);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Configuration
|
|
130
|
+
|
|
131
|
+
The `ClientMonitor` accepts a comprehensive configuration object. All configuration options are optional except when specifically noted:
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
import { ClientMonitor } from "@observertc/client-monitor-js";
|
|
135
|
+
|
|
136
|
+
const monitor = new ClientMonitor({
|
|
137
|
+
// Basic configuration (all optional)
|
|
138
|
+
clientId: "unique-client-id",
|
|
139
|
+
callId: "unique-call-id",
|
|
140
|
+
collectingPeriodInMs: 2000, // Default: 2000ms
|
|
141
|
+
samplingPeriodInMs: 4000, // Optional, no default
|
|
142
|
+
|
|
143
|
+
// Integration settings (optional with defaults)
|
|
144
|
+
integrateNavigatorMediaDevices: true, // Default: true
|
|
145
|
+
addClientJointEventOnCreated: true, // Default: true
|
|
146
|
+
addClientLeftEventOnClose: true, // Default: true
|
|
147
|
+
bufferingEventsForSamples: false, // Default: false
|
|
148
|
+
|
|
149
|
+
// Detector configurations (all optional with defaults)
|
|
150
|
+
audioDesyncDetector: {
|
|
151
|
+
disabled: false,
|
|
152
|
+
createIssue: true,
|
|
153
|
+
fractionalCorrectionAlertOnThreshold: 0.1,
|
|
154
|
+
fractionalCorrectionAlertOffThreshold: 0.05,
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
congestionDetector: {
|
|
158
|
+
disabled: false,
|
|
159
|
+
createIssue: true,
|
|
160
|
+
sensitivity: "medium", // 'low', 'medium', 'high'
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
cpuPerformanceDetector: {
|
|
164
|
+
disabled: false,
|
|
165
|
+
createIssue: true,
|
|
166
|
+
fpsVolatilityThresholds: {
|
|
167
|
+
lowWatermark: 0.1,
|
|
168
|
+
highWatermark: 0.3,
|
|
169
|
+
},
|
|
170
|
+
durationOfCollectingStatsThreshold: {
|
|
171
|
+
lowWatermark: 5000,
|
|
172
|
+
highWatermark: 10000,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
dryInboundTrackDetector: {
|
|
177
|
+
disabled: false,
|
|
178
|
+
createIssue: true,
|
|
179
|
+
thresholdInMs: 5000,
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
dryOutboundTrackDetector: {
|
|
183
|
+
disabled: false,
|
|
184
|
+
createIssue: true,
|
|
185
|
+
thresholdInMs: 5000,
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
videoFreezesDetector: {
|
|
189
|
+
disabled: false,
|
|
190
|
+
createIssue: true,
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
playoutDiscrepancyDetector: {
|
|
194
|
+
disabled: false,
|
|
195
|
+
createIssue: true,
|
|
196
|
+
lowSkewThreshold: 2,
|
|
197
|
+
highSkewThreshold: 5,
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
syntheticSamplesDetector: {
|
|
201
|
+
disabled: false,
|
|
202
|
+
createIssue: true,
|
|
203
|
+
minSynthesizedSamplesDuration: 1000,
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
longPcConnectionEstablishmentDetector: {
|
|
207
|
+
disabled: false,
|
|
208
|
+
createIssue: true,
|
|
209
|
+
thresholdInMs: 5000,
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
// Application data (optional)
|
|
213
|
+
appData: {
|
|
214
|
+
userId: "user-123",
|
|
215
|
+
roomId: "room-456",
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Important**: You can create a monitor with minimal configuration or even no configuration at all:
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
// Minimal configuration
|
|
224
|
+
const monitor = new ClientMonitor({
|
|
225
|
+
clientId: "my-client",
|
|
226
|
+
collectingPeriodInMs: 1000,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// No configuration (uses all defaults)
|
|
230
|
+
const monitor = new ClientMonitor();
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## ClientMonitor
|
|
234
|
+
|
|
235
|
+
The `ClientMonitor` is the main class that orchestrates WebRTC monitoring, statistics collection, and anomaly detection.
|
|
236
|
+
|
|
237
|
+
### Core Features
|
|
238
|
+
|
|
239
|
+
- **Multi-source monitoring**: Supports RTCPeerConnection, mediasoup devices and transports
|
|
240
|
+
- **Automatic stats collection**: Periodically collects WebRTC statistics
|
|
241
|
+
- **Real-time anomaly detection**: Built-in detectors for common issues
|
|
242
|
+
- **Performance scoring**: Calculates quality scores for connections and tracks
|
|
243
|
+
- **Event generation**: Emits events for WebRTC state changes and issues
|
|
244
|
+
- **Sampling**: Creates periodic snapshots of the client state
|
|
245
|
+
|
|
246
|
+
### Public Methods
|
|
247
|
+
|
|
248
|
+
#### Core Methods
|
|
249
|
+
|
|
250
|
+
- **`addSource(source: RTCPeerConnection | MediasoupDevice | MediasoupTransport)`**: Adds a source for monitoring
|
|
251
|
+
- **`close()`**: Closes the monitor and stops all monitoring activities
|
|
252
|
+
- **`collect()`**: Manually collects stats from all monitored sources
|
|
253
|
+
- **`createSample()`**: Creates a client sample with current state
|
|
254
|
+
|
|
255
|
+
#### Configuration Methods
|
|
256
|
+
|
|
257
|
+
- **`setCollectingPeriod(periodInMs: number)`**: Updates the stats collection interval
|
|
258
|
+
- **`setSamplingPeriod(periodInMs: number)`**: Updates the sampling interval
|
|
259
|
+
- **`setScore(score: number, reasons?: Record<string, number>)`**: Manually sets the client score
|
|
260
|
+
|
|
261
|
+
#### Event Methods
|
|
262
|
+
|
|
263
|
+
- **`addEvent(event: ClientEvent)`**: Adds a custom client event
|
|
264
|
+
- **`addIssue(issue: ClientIssue)`**: Adds a custom client issue
|
|
265
|
+
- **`addMetaData(metaData: ClientMetaData)`**: Adds metadata
|
|
266
|
+
- **`addExtensionStats(stats: ExtensionStat)`**: Adds custom extension stats
|
|
267
|
+
|
|
268
|
+
#### Utility Methods
|
|
269
|
+
|
|
270
|
+
- **`getTrackMonitor(trackId: string)`**: Retrieves a track monitor by ID
|
|
271
|
+
- **`watchMediaDevices()`**: Integrates with navigator.mediaDevices
|
|
272
|
+
- **`fetchUserAgentData()`**: Fetches browser user agent information
|
|
273
|
+
|
|
274
|
+
### Properties
|
|
275
|
+
|
|
276
|
+
- **`score`**: Current client performance score (0.0-5.0)
|
|
277
|
+
- **`scoreReasons`**: Detailed score calculation reasons
|
|
278
|
+
- **`closed`**: Whether the monitor is closed
|
|
279
|
+
- **`config`**: Current configuration
|
|
280
|
+
- **`detectors`**: Detector management instance
|
|
281
|
+
- **`peerConnections`**: Array of monitored peer connections
|
|
282
|
+
- **`tracks`**: Array of monitored tracks
|
|
283
|
+
|
|
284
|
+
## Detectors
|
|
285
|
+
|
|
286
|
+
Detectors are specialized components that monitor for specific anomalies and issues in WebRTC connections. Each detector focuses on a particular aspect of the connection quality.
|
|
287
|
+
|
|
288
|
+
### Built-in Detectors
|
|
289
|
+
|
|
290
|
+
#### AudioDesyncDetector
|
|
291
|
+
|
|
292
|
+
Detects audio synchronization issues by monitoring sample corrections.
|
|
293
|
+
|
|
294
|
+
**Triggers on:**
|
|
295
|
+
|
|
296
|
+
- Audio acceleration/deceleration corrections exceed thresholds
|
|
297
|
+
- Indicates audio-video sync problems
|
|
298
|
+
|
|
299
|
+
**Configuration:**
|
|
300
|
+
|
|
301
|
+
```javascript
|
|
302
|
+
audioDesyncDetector: {
|
|
303
|
+
disabled: false,
|
|
304
|
+
createIssue: true,
|
|
305
|
+
fractionalCorrectionAlertOnThreshold: 0.1, // 10% correction rate triggers alert
|
|
306
|
+
fractionalCorrectionAlertOffThreshold: 0.05, // 5% correction rate clears alert
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### CongestionDetector
|
|
311
|
+
|
|
312
|
+
Monitors network congestion by analyzing available bandwidth vs. usage.
|
|
313
|
+
|
|
314
|
+
**Triggers on:**
|
|
315
|
+
|
|
316
|
+
- Available bandwidth falls below sending/receiving bitrates
|
|
317
|
+
- Network congestion conditions
|
|
318
|
+
|
|
319
|
+
**Configuration:**
|
|
320
|
+
|
|
321
|
+
```javascript
|
|
322
|
+
congestionDetector: {
|
|
323
|
+
disabled: false,
|
|
324
|
+
createIssue: true,
|
|
325
|
+
sensitivity: 'medium', // 'low', 'medium', 'high'
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### CpuPerformanceDetector
|
|
330
|
+
|
|
331
|
+
Detects CPU performance issues affecting media processing.
|
|
332
|
+
|
|
333
|
+
**Triggers on:**
|
|
334
|
+
|
|
335
|
+
- FPS volatility exceeds thresholds
|
|
336
|
+
- Stats collection takes too long (indicating CPU stress)
|
|
337
|
+
|
|
338
|
+
**Configuration:**
|
|
339
|
+
|
|
340
|
+
```javascript
|
|
341
|
+
cpuPerformanceDetector: {
|
|
342
|
+
disabled: false,
|
|
343
|
+
createIssue: true,
|
|
344
|
+
fpsVolatilityThresholds: {
|
|
345
|
+
lowWatermark: 0.1,
|
|
346
|
+
highWatermark: 0.3,
|
|
347
|
+
},
|
|
348
|
+
durationOfCollectingStatsThreshold: {
|
|
349
|
+
lowWatermark: 5000,
|
|
350
|
+
highWatermark: 10000,
|
|
351
|
+
},
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
#### DryInboundTrackDetector
|
|
356
|
+
|
|
357
|
+
Detects inbound tracks that stop receiving data.
|
|
358
|
+
|
|
359
|
+
**Triggers on:**
|
|
360
|
+
|
|
361
|
+
- Inbound track receives no data for specified duration
|
|
362
|
+
- Track stalling or connection issues
|
|
363
|
+
|
|
364
|
+
**Configuration:**
|
|
365
|
+
|
|
366
|
+
```javascript
|
|
367
|
+
dryInboundTrackDetector: {
|
|
368
|
+
disabled: false,
|
|
369
|
+
createIssue: true,
|
|
370
|
+
thresholdInMs: 5000,
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
#### DryOutboundTrackDetector
|
|
375
|
+
|
|
376
|
+
Detects outbound tracks that stop sending data.
|
|
377
|
+
|
|
378
|
+
**Triggers on:**
|
|
379
|
+
|
|
380
|
+
- Outbound track sends no data for specified duration
|
|
381
|
+
- Local media issues or encoding problems
|
|
382
|
+
|
|
383
|
+
**Configuration:**
|
|
384
|
+
|
|
385
|
+
```javascript
|
|
386
|
+
dryOutboundTrackDetector: {
|
|
387
|
+
disabled: false,
|
|
388
|
+
createIssue: true,
|
|
389
|
+
thresholdInMs: 5000,
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
#### FreezedVideoTrackDetector
|
|
394
|
+
|
|
395
|
+
Detects frozen video tracks.
|
|
396
|
+
|
|
397
|
+
**Triggers on:**
|
|
398
|
+
|
|
399
|
+
- Video frames stop updating
|
|
400
|
+
- Video freeze conditions
|
|
401
|
+
|
|
402
|
+
**Configuration:**
|
|
403
|
+
|
|
404
|
+
```javascript
|
|
405
|
+
videoFreezesDetector: {
|
|
406
|
+
disabled: false,
|
|
407
|
+
createIssue: true,
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
#### PlayoutDiscrepancyDetector
|
|
412
|
+
|
|
413
|
+
Detects discrepancies between received and rendered frames.
|
|
414
|
+
|
|
415
|
+
**Triggers on:**
|
|
416
|
+
|
|
417
|
+
- Frame skew exceeds thresholds
|
|
418
|
+
- Video playout buffer issues
|
|
419
|
+
|
|
420
|
+
**Configuration:**
|
|
421
|
+
|
|
422
|
+
```javascript
|
|
423
|
+
playoutDiscrepancyDetector: {
|
|
424
|
+
disabled: false,
|
|
425
|
+
createIssue: true,
|
|
426
|
+
lowSkewThreshold: 2,
|
|
427
|
+
highSkewThreshold: 5,
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
#### SynthesizedSamplesDetector
|
|
432
|
+
|
|
433
|
+
Detects when audio playout synthesizes samples due to missing data.
|
|
434
|
+
|
|
435
|
+
**Triggers on:**
|
|
436
|
+
|
|
437
|
+
- Synthesized audio samples exceed duration threshold
|
|
438
|
+
- Audio gaps requiring interpolation
|
|
439
|
+
|
|
440
|
+
**Configuration:**
|
|
441
|
+
|
|
442
|
+
```javascript
|
|
443
|
+
syntheticSamplesDetector: {
|
|
444
|
+
disabled: false,
|
|
445
|
+
createIssue: true,
|
|
446
|
+
minSynthesizedSamplesDuration: 1000,
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
#### LongPcConnectionEstablishmentDetector
|
|
451
|
+
|
|
452
|
+
Detects slow peer connection establishment.
|
|
453
|
+
|
|
454
|
+
**Triggers on:**
|
|
455
|
+
|
|
456
|
+
- Peer connection takes too long to establish
|
|
457
|
+
- Connection setup issues
|
|
458
|
+
|
|
459
|
+
**Configuration:**
|
|
460
|
+
|
|
461
|
+
```javascript
|
|
462
|
+
longPcConnectionEstablishmentDetector: {
|
|
463
|
+
disabled: false,
|
|
464
|
+
createIssue: true,
|
|
465
|
+
thresholdInMs: 5000,
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Custom Detectors
|
|
470
|
+
|
|
471
|
+
Create custom detectors by implementing the `Detector` interface:
|
|
472
|
+
|
|
473
|
+
```javascript
|
|
474
|
+
import { Detector } from "@observertc/client-monitor-js";
|
|
475
|
+
|
|
476
|
+
class CustomDetector implements Detector {
|
|
477
|
+
public readonly name = 'custom-detector';
|
|
478
|
+
|
|
479
|
+
constructor(private monitor: any) {}
|
|
480
|
+
|
|
481
|
+
public update() {
|
|
482
|
+
// Custom detection logic
|
|
483
|
+
if (this.detectCustomCondition()) {
|
|
484
|
+
this.monitor.parent.emit('custom-issue', {
|
|
485
|
+
type: 'custom-issue',
|
|
486
|
+
payload: { reason: 'Custom condition detected' }
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
private detectCustomCondition(): boolean {
|
|
492
|
+
// Your detection logic here
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Add to monitor
|
|
498
|
+
const detector = new CustomDetector(someMonitor);
|
|
499
|
+
monitor.detectors.add(detector);
|
|
500
|
+
|
|
501
|
+
// Remove detector
|
|
502
|
+
monitor.detectors.remove(detector);
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
## Score Calculation
|
|
506
|
+
|
|
507
|
+
The scoring system provides quantitative quality assessment ranging from 0.0 (worst) to 5.0 (best). The library includes a `DefaultScoreCalculator` implementation and allows custom score calculators via the `ScoreCalculator` interface.
|
|
508
|
+
|
|
509
|
+
### ScoreCalculator Interface
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
interface ScoreCalculator {
|
|
513
|
+
update(): void;
|
|
514
|
+
encodeClientScoreReasons?<T extends Record<string, number>>(reasons?: T): string;
|
|
515
|
+
encodePeerConnectionScoreReasons?<T extends Record<string, number>>(reasons?: T): string;
|
|
516
|
+
encodeInboundAudioScoreReasons?<T extends Record<string, number>>(reasons?: T): string;
|
|
517
|
+
encodeInboundVideoScoreReasons?<T extends Record<string, number>>(reasons?: T): string;
|
|
518
|
+
encodeOutboundAudioScoreReasons?<T extends Record<string, number>>(reasons?: T): string;
|
|
519
|
+
encodeOutboundVideoScoreReasons?<T extends Record<string, number>>(reasons?: T): string;
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### DefaultScoreCalculator Implementation
|
|
524
|
+
|
|
525
|
+
The default implementation calculates scores using a hierarchical weighted average approach:
|
|
526
|
+
|
|
527
|
+
#### Score Hierarchy
|
|
528
|
+
|
|
529
|
+
The client score is calculated as a weighted average of:
|
|
530
|
+
|
|
531
|
+
1. **Peer Connection Stability Scores** (based on RTT and packet loss)
|
|
532
|
+
2. **Track Quality Scores** (inbound/outbound audio/video tracks)
|
|
533
|
+
|
|
534
|
+
#### Client Score Calculation
|
|
535
|
+
|
|
536
|
+
```
|
|
537
|
+
Client Score = Σ(PC_Score × PC_Weight) / Σ(PC_Weight)
|
|
538
|
+
|
|
539
|
+
Where PC_Score = Track_Score_Avg × PC_Stability_Score
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
#### Peer Connection Stability Score
|
|
543
|
+
|
|
544
|
+
Based on Round Trip Time (RTT) and packet loss:
|
|
545
|
+
|
|
546
|
+
**RTT Penalties:**
|
|
547
|
+
|
|
548
|
+
- High RTT (150-300ms): -1.0 point
|
|
549
|
+
- Very High RTT (>300ms): -2.0 points
|
|
550
|
+
|
|
551
|
+
**Packet Loss Penalties:**
|
|
552
|
+
|
|
553
|
+
- 1-5% loss: -1.0 point
|
|
554
|
+
- 5-20% loss: -2.0 points
|
|
555
|
+
- > 20% loss: -5.0 points
|
|
556
|
+
|
|
557
|
+
#### Track Score Calculations
|
|
558
|
+
|
|
559
|
+
**Inbound Audio Track Score:**
|
|
560
|
+
|
|
561
|
+
- Based on normalized bitrate and packet loss
|
|
562
|
+
- Uses logarithmic bitrate normalization
|
|
563
|
+
- Exponential decay for packet loss impact
|
|
564
|
+
|
|
565
|
+
```javascript
|
|
566
|
+
normalizedBitrate = log10(max(bitrate, MIN_AUDIO_BITRATE) / MIN_AUDIO_BITRATE) / NORMALIZATION_FACTOR;
|
|
567
|
+
lossPenalty = exp(-packetLoss / 2);
|
|
568
|
+
score = min(MAX_SCORE, 5 * normalizedBitrate * lossPenalty);
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
**Inbound Video Track Score:**
|
|
572
|
+
|
|
573
|
+
- FPS volatility penalties
|
|
574
|
+
- Dropped frames penalties
|
|
575
|
+
- Frame corruption penalties
|
|
576
|
+
|
|
577
|
+
**Outbound Audio Track Score:**
|
|
578
|
+
|
|
579
|
+
- Similar to inbound, using sending bitrate
|
|
580
|
+
- Remote packet loss consideration
|
|
581
|
+
|
|
582
|
+
**Outbound Video Track Score:**
|
|
583
|
+
|
|
584
|
+
- Bitrate deviation from target penalties
|
|
585
|
+
- CPU limitation penalties
|
|
586
|
+
- Bitrate volatility penalties
|
|
587
|
+
|
|
588
|
+
### Score Reasons
|
|
589
|
+
|
|
590
|
+
Each score calculation includes detailed reasons for penalties:
|
|
591
|
+
|
|
592
|
+
```javascript
|
|
593
|
+
monitor.on("score", (event) => {
|
|
594
|
+
console.log("Client Score:", event.clientScore);
|
|
595
|
+
console.log("Score Reasons:", event.scoreReasons);
|
|
596
|
+
// Example reasons:
|
|
597
|
+
// {
|
|
598
|
+
// "high-rtt": 1.0,
|
|
599
|
+
// "high-packetloss": 2.0,
|
|
600
|
+
// "cpu-limitation": 2.0,
|
|
601
|
+
// "dropped-video-frames": 1.0
|
|
602
|
+
// }
|
|
603
|
+
});
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Custom Score Calculator
|
|
607
|
+
|
|
608
|
+
Implement your own scoring logic by implementing the `ScoreCalculator` interface:
|
|
609
|
+
|
|
610
|
+
```javascript
|
|
611
|
+
import { ScoreCalculator } from "@observertc/client-monitor-js";
|
|
612
|
+
|
|
613
|
+
class CustomScoreCalculator {
|
|
614
|
+
constructor(clientMonitor) {
|
|
615
|
+
this.clientMonitor = clientMonitor;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
update() {
|
|
619
|
+
// Calculate peer connection scores
|
|
620
|
+
for (const pcMonitor of this.clientMonitor.peerConnections) {
|
|
621
|
+
this.calculatePeerConnectionScore(pcMonitor);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Calculate track scores
|
|
625
|
+
for (const track of this.clientMonitor.tracks) {
|
|
626
|
+
this.calculateTrackScore(track);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Calculate final client score
|
|
630
|
+
this.calculateClientScore();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
calculatePeerConnectionScore(pcMonitor) {
|
|
634
|
+
const rttMs = (pcMonitor.avgRttInSec ?? 0) * 1000;
|
|
635
|
+
const fractionLost = pcMonitor.inboundRtps.reduce((acc, rtp) => acc + (rtp.fractionLost ?? 0), 0);
|
|
636
|
+
|
|
637
|
+
let score = 5.0;
|
|
638
|
+
const reasons = {};
|
|
639
|
+
|
|
640
|
+
// Custom RTT penalties
|
|
641
|
+
if (rttMs > 200) {
|
|
642
|
+
score -= 1.5;
|
|
643
|
+
reasons["custom-high-rtt"] = 1.5;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Custom packet loss penalties
|
|
647
|
+
if (fractionLost > 0.02) {
|
|
648
|
+
score -= 2.0;
|
|
649
|
+
reasons["custom-packet-loss"] = 2.0;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
pcMonitor.calculatedStabilityScore.value = Math.max(0, score);
|
|
653
|
+
pcMonitor.calculatedStabilityScore.reasons = reasons;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
calculateTrackScore(trackMonitor) {
|
|
657
|
+
let score = 5.0;
|
|
658
|
+
const reasons = {};
|
|
659
|
+
|
|
660
|
+
if (trackMonitor.direction === "inbound" && trackMonitor.kind === "video") {
|
|
661
|
+
// Custom video quality scoring
|
|
662
|
+
const fps = trackMonitor.ewmaFps ?? 0;
|
|
663
|
+
if (fps < 15) {
|
|
664
|
+
score -= 2.0;
|
|
665
|
+
reasons["low-fps"] = 2.0;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
trackMonitor.calculatedScore.value = Math.max(0, score);
|
|
670
|
+
trackMonitor.calculatedScore.reasons = reasons;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
calculateClientScore() {
|
|
674
|
+
let totalScore = 0;
|
|
675
|
+
let totalWeight = 0;
|
|
676
|
+
const combinedReasons = {};
|
|
677
|
+
|
|
678
|
+
for (const pcMonitor of this.clientMonitor.peerConnections) {
|
|
679
|
+
if (pcMonitor.calculatedStabilityScore.value !== undefined) {
|
|
680
|
+
totalScore += pcMonitor.calculatedStabilityScore.value;
|
|
681
|
+
totalWeight += 1;
|
|
682
|
+
|
|
683
|
+
// Combine reasons
|
|
684
|
+
Object.assign(combinedReasons, pcMonitor.calculatedStabilityScore.reasons || {});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const clientScore = totalWeight > 0 ? totalScore / totalWeight : 5.0;
|
|
689
|
+
this.clientMonitor.setScore(clientScore, combinedReasons);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Optional: Custom encoding for reasons
|
|
693
|
+
encodeClientScoreReasons(reasons) {
|
|
694
|
+
return JSON.stringify(reasons || {});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Apply custom calculator
|
|
699
|
+
const monitor = new ClientMonitor();
|
|
700
|
+
monitor.scoreCalculator = new CustomScoreCalculator(monitor);
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
## Collecting and Adapting Stats
|
|
704
|
+
|
|
705
|
+
The monitor collects WebRTC statistics periodically and adapts them for consistent processing across different browsers and integrations.
|
|
706
|
+
|
|
707
|
+
### Stats Collection Process
|
|
708
|
+
|
|
709
|
+
1. **Collection Trigger**: Timer-based collection every `collectingPeriodInMs`
|
|
710
|
+
2. **Raw Stats Retrieval**: Calls `getStats()` on peer connections
|
|
711
|
+
3. **Stats Adaptation**: Applies browser-specific adaptations
|
|
712
|
+
4. **Monitor Updates**: Updates all relevant monitor objects
|
|
713
|
+
5. **Detector Updates**: Runs all attached detectors
|
|
714
|
+
6. **Score Calculation**: Updates performance scores
|
|
715
|
+
|
|
716
|
+
### Stats Adapters
|
|
717
|
+
|
|
718
|
+
Stats adapters handle browser-specific differences and integration requirements:
|
|
719
|
+
|
|
720
|
+
#### Browser Adaptations
|
|
721
|
+
|
|
722
|
+
- **Firefox**: Handles track identifier format differences
|
|
723
|
+
- **Chrome/Safari**: Handles various stats format variations
|
|
724
|
+
- **Mediasoup**: Filters probator tracks and adapts mediasoup-specific stats
|
|
725
|
+
|
|
726
|
+
#### Custom Stats Adapters
|
|
727
|
+
|
|
728
|
+
Add custom adaptation logic:
|
|
729
|
+
|
|
730
|
+
```javascript
|
|
731
|
+
monitor.statsAdapters.add((stats) => {
|
|
732
|
+
// Custom adaptation logic
|
|
733
|
+
return stats.map((stat) => {
|
|
734
|
+
if (stat.type === "inbound-rtp" && stat.trackIdentifier) {
|
|
735
|
+
// Custom track identifier handling
|
|
736
|
+
stat.trackIdentifier = stat.trackIdentifier.replace(/[{}]/g, "");
|
|
737
|
+
}
|
|
738
|
+
return stat;
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### Available WebRTC Stats
|
|
744
|
+
|
|
745
|
+
The monitor collects and processes all standard WebRTC statistics:
|
|
746
|
+
|
|
747
|
+
#### RTP Statistics
|
|
748
|
+
|
|
749
|
+
- **Inbound RTP**: Receiving stream statistics
|
|
750
|
+
- **Outbound RTP**: Sending stream statistics
|
|
751
|
+
- **Remote Inbound RTP**: Remote peer's receiving statistics
|
|
752
|
+
- **Remote Outbound RTP**: Remote peer's sending statistics
|
|
753
|
+
|
|
754
|
+
#### Connection Statistics
|
|
755
|
+
|
|
756
|
+
- **ICE Candidate**: ICE candidate information
|
|
757
|
+
- **ICE Candidate Pair**: ICE candidate pair statistics
|
|
758
|
+
- **ICE Transport**: ICE transport layer statistics
|
|
759
|
+
- **Certificate**: Security certificate information
|
|
760
|
+
|
|
761
|
+
#### Media Statistics
|
|
762
|
+
|
|
763
|
+
- **Codec**: Codec configuration and usage
|
|
764
|
+
- **Media Source**: Local media source statistics
|
|
765
|
+
- **Media Playout**: Audio playout statistics
|
|
766
|
+
- **Data Channel**: Data channel statistics
|
|
767
|
+
|
|
768
|
+
## Sampling
|
|
769
|
+
|
|
770
|
+
Sampling creates periodic snapshots (`ClientSample`) containing the complete state of the monitored client.
|
|
771
|
+
|
|
772
|
+
### Sample Structure
|
|
773
|
+
|
|
774
|
+
A `ClientSample` includes:
|
|
775
|
+
|
|
776
|
+
- **Client metadata**: clientId, callId, timestamp, score
|
|
777
|
+
- **Peer connection samples**: All monitored peer connections
|
|
778
|
+
- **Events**: Client events since last sample
|
|
779
|
+
- **Issues**: Detected issues since last sample
|
|
780
|
+
- **Extension stats**: Custom application statistics
|
|
781
|
+
|
|
782
|
+
### Automatic Sampling
|
|
783
|
+
|
|
784
|
+
Enable automatic sampling by setting `samplingPeriodInMs`:
|
|
785
|
+
|
|
786
|
+
```javascript
|
|
787
|
+
const monitor = new ClientMonitor({
|
|
788
|
+
collectingPeriodInMs: 2000,
|
|
789
|
+
samplingPeriodInMs: 4000, // Create sample every 4 seconds
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
monitor.on("sample-created", (sample) => {
|
|
793
|
+
console.log("Sample created:", sample);
|
|
794
|
+
// Send to analytics backend
|
|
795
|
+
sendToAnalytics(sample);
|
|
796
|
+
});
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
### Manual Sampling
|
|
800
|
+
|
|
801
|
+
Create samples on demand:
|
|
802
|
+
|
|
803
|
+
```javascript
|
|
804
|
+
const monitor = new ClientMonitor({
|
|
805
|
+
collectingPeriodInMs: 2000,
|
|
806
|
+
bufferingEventsForSamples: true, // Required for manual sampling
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// Create sample manually
|
|
810
|
+
const sample = monitor.createSample();
|
|
811
|
+
if (sample) {
|
|
812
|
+
console.log("Manual sample:", sample);
|
|
813
|
+
}
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
### Sample Compression
|
|
817
|
+
|
|
818
|
+
For efficient data transmission and storage, ObserveRTC provides dedicated compression packages for `ClientSample` objects:
|
|
819
|
+
|
|
820
|
+
**@observertc/samples-encoder** - Compresses ClientSample objects for transmission:
|
|
821
|
+
|
|
822
|
+
```javascript
|
|
823
|
+
import { SamplesEncoder } from "@observertc/samples-encoder";
|
|
824
|
+
|
|
825
|
+
const encoder = new SamplesEncoder();
|
|
826
|
+
const sample = monitor.createSample();
|
|
827
|
+
|
|
828
|
+
// Encode the sample for efficient transmission
|
|
829
|
+
const encodedSample = encoder.encode(sample);
|
|
830
|
+
|
|
831
|
+
// Send compressed data over the network
|
|
832
|
+
fetch("/api/samples", {
|
|
833
|
+
method: "POST",
|
|
834
|
+
headers: {
|
|
835
|
+
"Content-Type": "application/octet-stream",
|
|
836
|
+
},
|
|
837
|
+
body: encodedSample,
|
|
838
|
+
});
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
**@observertc/samples-decoder** - Decompresses received ClientSample objects:
|
|
842
|
+
|
|
843
|
+
```javascript
|
|
844
|
+
import { SamplesDecoder } from "@observertc/samples-decoder";
|
|
845
|
+
|
|
846
|
+
const decoder = new SamplesDecoder();
|
|
847
|
+
|
|
848
|
+
// Receive compressed sample data
|
|
849
|
+
const compressedData = await response.arrayBuffer();
|
|
850
|
+
|
|
851
|
+
// Decode back to ClientSample object
|
|
852
|
+
const decodedSample = decoder.decode(compressedData);
|
|
853
|
+
|
|
854
|
+
// Process the restored sample
|
|
855
|
+
console.log("Decoded sample:", decodedSample);
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
**Benefits of Using Compression:**
|
|
859
|
+
|
|
860
|
+
- **Reduced Bandwidth**: Compressed samples require significantly less network bandwidth
|
|
861
|
+
- **Faster Transmission**: Smaller payloads improve upload/download times
|
|
862
|
+
- **Storage Efficiency**: Compressed samples consume less storage space
|
|
863
|
+
- **Schema Consistency**: Ensures proper serialization/deserialization of all ClientSample fields
|
|
864
|
+
|
|
865
|
+
**Installation:**
|
|
866
|
+
|
|
867
|
+
```bash
|
|
868
|
+
# For encoding (client-side)
|
|
869
|
+
npm install @observertc/samples-encoder
|
|
870
|
+
|
|
871
|
+
# For decoding (server-side)
|
|
872
|
+
npm install @observertc/samples-decoder
|
|
873
|
+
|
|
874
|
+
# Both packages (if needed)
|
|
875
|
+
npm install @observertc/samples-encoder @observertc/samples-decoder
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
**Integration with ObserveRTC Stack:**
|
|
879
|
+
These compression packages are part of the broader ObserveRTC ecosystem and are designed to work seamlessly with:
|
|
880
|
+
|
|
881
|
+
- Client Monitor (sample generation)
|
|
882
|
+
- Observer Service (sample processing)
|
|
883
|
+
- Schema definitions (data consistency)
|
|
884
|
+
|
|
885
|
+
The compression format maintains full compatibility with the ObserveRTC schema definitions and can be used with any transport mechanism (WebSocket, HTTP REST, etc.).
|
|
886
|
+
|
|
887
|
+
## Events and Issues
|
|
888
|
+
|
|
889
|
+
The monitor generates events for WebRTC state changes and issues for detected problems.
|
|
890
|
+
|
|
891
|
+
### Client Events
|
|
892
|
+
|
|
893
|
+
Automatically generated events include:
|
|
894
|
+
|
|
895
|
+
- **PEER_CONNECTION_OPENED**: New peer connection
|
|
896
|
+
- **PEER_CONNECTION_CLOSED**: Peer connection closed
|
|
897
|
+
- **MEDIA_TRACK_ADDED**: New media track
|
|
898
|
+
- **MEDIA_TRACK_REMOVED**: Media track ended
|
|
899
|
+
- **ICE_CANDIDATE**: ICE candidate discovered
|
|
900
|
+
- **NEGOTIATION_NEEDED**: SDP negotiation required
|
|
901
|
+
|
|
902
|
+
### Client Issues
|
|
903
|
+
|
|
904
|
+
Issues are generated by detectors:
|
|
905
|
+
|
|
906
|
+
- **congestion**: Network congestion detected
|
|
907
|
+
- **cpu-limitation**: CPU performance issues
|
|
908
|
+
- **audio-desync**: Audio synchronization problems
|
|
909
|
+
- **video-freeze**: Video track frozen
|
|
910
|
+
- **dry-track**: Track not flowing data
|
|
911
|
+
|
|
912
|
+
### Custom Events and Issues
|
|
913
|
+
|
|
914
|
+
Add custom events and issues:
|
|
915
|
+
|
|
916
|
+
```javascript
|
|
917
|
+
// Custom event
|
|
918
|
+
monitor.addEvent({
|
|
919
|
+
type: "user-action",
|
|
920
|
+
payload: { action: "mute-audio" },
|
|
921
|
+
timestamp: Date.now(),
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
// Custom issue
|
|
925
|
+
monitor.addIssue({
|
|
926
|
+
type: "custom-problem",
|
|
927
|
+
payload: { severity: "high", description: "Custom issue detected" },
|
|
928
|
+
timestamp: Date.now(),
|
|
929
|
+
});
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
### Event Listeners
|
|
933
|
+
|
|
934
|
+
Listen for real-time events:
|
|
935
|
+
|
|
936
|
+
```javascript
|
|
937
|
+
// Sample created
|
|
938
|
+
monitor.on("sample-created", (sample) => {
|
|
939
|
+
console.log("New sample:", sample);
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
// Issue detected
|
|
943
|
+
monitor.on("issue", (issue) => {
|
|
944
|
+
console.log("Issue:", issue.type, issue.payload);
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
// Score updated
|
|
948
|
+
monitor.on("score", ({ clientScore, scoreReasons }) => {
|
|
949
|
+
console.log("Score:", clientScore, "Reasons:", scoreReasons);
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
// Congestion detected
|
|
953
|
+
monitor.on("congestion", ({ peerConnectionMonitor, availableIncomingBitrate }) => {
|
|
954
|
+
console.log("Congestion detected on PC:", peerConnectionMonitor.peerConnectionId);
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
// Stats collected
|
|
958
|
+
monitor.on("stats-collected", ({ durationOfCollectingStatsInMs, collectedStats }) => {
|
|
959
|
+
console.log("Stats collection took:", durationOfCollectingStatsInMs, "ms");
|
|
960
|
+
});
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
## WebRTC Stats Monitors
|
|
35
964
|
|
|
36
|
-
|
|
965
|
+
The monitor creates specialized monitor objects for each WebRTC statistics type, providing navigation, derived fields, and lifecycle management.
|
|
37
966
|
|
|
38
|
-
|
|
967
|
+
### Monitor Hierarchy
|
|
39
968
|
|
|
40
969
|
```
|
|
41
|
-
|
|
970
|
+
ClientMonitor
|
|
971
|
+
├── PeerConnectionMonitor[]
|
|
972
|
+
│ ├── InboundRtpMonitor[]
|
|
973
|
+
│ ├── OutboundRtpMonitor[]
|
|
974
|
+
│ ├── RemoteInboundRtpMonitor[]
|
|
975
|
+
│ ├── RemoteOutboundRtpMonitor[]
|
|
976
|
+
│ ├── MediaSourceMonitor[]
|
|
977
|
+
│ ├── CodecMonitor[]
|
|
978
|
+
│ ├── IceTransportMonitor[]
|
|
979
|
+
│ ├── IceCandidateMonitor[]
|
|
980
|
+
│ ├── IceCandidatePairMonitor[]
|
|
981
|
+
│ ├── CertificateMonitor[]
|
|
982
|
+
│ ├── DataChannelMonitor[]
|
|
983
|
+
│ └── MediaPlayoutMonitor[]
|
|
984
|
+
├── InboundTrackMonitor[]
|
|
985
|
+
└── OutboundTrackMonitor[]
|
|
42
986
|
```
|
|
43
987
|
|
|
44
|
-
|
|
988
|
+
### Track Monitors
|
|
45
989
|
|
|
46
|
-
|
|
47
|
-
yarn add @observertc/client-monitor-js
|
|
48
|
-
```
|
|
990
|
+
#### InboundTrackMonitor
|
|
49
991
|
|
|
50
|
-
|
|
992
|
+
Monitors incoming media tracks with attached detectors:
|
|
51
993
|
|
|
52
|
-
|
|
53
|
-
import { createClientMonitor } from "@observertc/client-monitor-js";
|
|
54
|
-
const monitor = createClientMonitor();
|
|
55
|
-
const collector = monitor.collectors.addRTCPeerConnection(peerConnection);
|
|
994
|
+
**Properties:**
|
|
56
995
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
```
|
|
996
|
+
- `score`: Calculated quality score
|
|
997
|
+
- `bitrate`: Receiving bitrate
|
|
998
|
+
- `jitter`: Network jitter
|
|
999
|
+
- `fractionLost`: Packet loss fraction
|
|
1000
|
+
- `dtxMode`: Discontinuous transmission mode
|
|
1001
|
+
- `detectors`: Attached detectors
|
|
64
1002
|
|
|
65
|
-
|
|
1003
|
+
**Detectors:**
|
|
66
1004
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
1005
|
+
- AudioDesyncDetector (for audio tracks)
|
|
1006
|
+
- FreezedVideoTrackDetector (for video tracks)
|
|
1007
|
+
- DryInboundTrackDetector
|
|
1008
|
+
- PlayoutDiscrepancyDetector (for video tracks)
|
|
70
1009
|
|
|
71
|
-
|
|
72
|
-
You can pass a configuration object to the `createClientMonitor(config: ClientMonitorConfig)` method to customize the monitor instance.
|
|
73
|
-
See the full list of configurations [here](#configurations).
|
|
1010
|
+
#### OutboundTrackMonitor
|
|
74
1011
|
|
|
75
|
-
|
|
1012
|
+
Monitors outgoing media tracks:
|
|
76
1013
|
|
|
77
|
-
|
|
1014
|
+
**Properties:**
|
|
78
1015
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
1016
|
+
- `score`: Calculated quality score
|
|
1017
|
+
- `bitrate`: Aggregate sending bitrate
|
|
1018
|
+
- `sendingPacketRate`: Packet sending rate
|
|
1019
|
+
- `remoteReceivedPacketRate`: Remote receiving rate
|
|
1020
|
+
- `detectors`: Attached detectors
|
|
82
1021
|
|
|
83
|
-
|
|
84
|
-
const monitor = createClientMonitor();
|
|
85
|
-
const collector = monitor.collectors.addMediasoupDevice(mediasoupDevice);
|
|
1022
|
+
**Methods:**
|
|
86
1023
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
console.log(`Sending video bitrate: ${monitor.sendingVideoBitrate}`);
|
|
90
|
-
console.log(`Receiving audio bitrate: ${monitor.receivingAudioBitrate}`);
|
|
91
|
-
console.log(`Receiving video bitrate: ${monitor.receivingVideoBitrate}`);
|
|
92
|
-
});
|
|
93
|
-
```
|
|
1024
|
+
- `getHighestLayer()`: Gets highest bitrate layer
|
|
1025
|
+
- `getOutboundRtps()`: Gets all outbound RTP monitors
|
|
94
1026
|
|
|
95
|
-
|
|
1027
|
+
### RTP Monitors
|
|
96
1028
|
|
|
97
|
-
|
|
98
|
-
const myTransport = collector.addTransport(myTransport); // your transport created before the device is added to the monitor
|
|
99
|
-
```
|
|
1029
|
+
#### InboundRtpMonitor
|
|
100
1030
|
|
|
101
|
-
|
|
1031
|
+
Extended inbound RTP statistics with derived fields:
|
|
102
1032
|
|
|
103
|
-
|
|
104
|
-
Each entry in clientMonitor represents a WebRTC component such as `RTCPeerConnection`, `MediaStreamTrack`, etc. Entries can relate to each other and from one entry you can navigate to a correspondent entry. For example, from an `InboundRTP` entry you can navigate to the correspondent `RemoteOutboundRtp` entry, and reverse. Additionally entries are exposing basic derivatives calculated from the collected metrics.
|
|
1033
|
+
**Derived Fields:**
|
|
105
1034
|
|
|
106
|
-
|
|
1035
|
+
- `bitrate`: Calculated receiving bitrate
|
|
1036
|
+
- `packetRate`: Packet receiving rate
|
|
1037
|
+
- `deltaPacketsLost`: Packets lost since last collection
|
|
1038
|
+
- `deltaJitterBufferDelay`: Jitter buffer delay change
|
|
1039
|
+
- `ewmaFps`: Exponentially weighted moving average FPS
|
|
107
1040
|
|
|
108
|
-
|
|
1041
|
+
#### OutboundRtpMonitor
|
|
109
1042
|
|
|
110
|
-
|
|
1043
|
+
Extended outbound RTP statistics:
|
|
111
1044
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
console.log(`Total data channel bytes received: ${totalDataChannelBytesReceived}`);
|
|
162
|
-
|
|
163
|
-
console.log(`Lost Inbound packets since last collecting: ${deltaInboundPacketsLost}`);
|
|
164
|
-
console.log(`Received Inbound packets since last collecting: ${deltaInboundPacketsReceived}`);
|
|
165
|
-
console.log(`Lost Outbound packets since last collecting: ${deltaOutboundPacketsLost}`);
|
|
166
|
-
console.log(`Received Outbound packets since last collecting: ${deltaOutboundPacketsReceived}`);
|
|
167
|
-
console.log(`Sent Outbound packets since last collecting: ${deltaOutboundPacketsSent}`);
|
|
168
|
-
console.log(`Sent audio bytes since last collecting: ${deltaSentAudioBytes}`);
|
|
169
|
-
console.log(`Sent video bytes since last collecting: ${deltaSentVideoBytes}`);
|
|
170
|
-
console.log(`Received audio bytes since last collecting: ${deltaReceivedAudioBytes}`);
|
|
171
|
-
console.log(`Received video bytes since last collecting: ${deltaReceivedVideoBytes}`);
|
|
172
|
-
console.log(`Data channel bytes sent since last collecting: ${deltaDataChannelBytesSent}`);
|
|
173
|
-
console.log(`Data channel bytes received since last collecting: ${deltaDataChannelBytesReceived}`);
|
|
174
|
-
|
|
175
|
-
console.log(`Average RTT: ${avgRttInS}`);
|
|
176
|
-
console.log(`Sending audio bitrate: ${sendingAudioBitrate}`);
|
|
177
|
-
console.log(`Sending video bitrate: ${sendingVideoBitrate}`);
|
|
178
|
-
console.log(`Receiving audio bitrate: ${receivingAudioBitrate}`);
|
|
179
|
-
console.log(`Receiving video bitrate: ${receivingVideoBitrate}`);
|
|
180
|
-
|
|
181
|
-
console.log(`Highest seen sending bitrate: ${highestSeenSendingBitrate}`);
|
|
182
|
-
console.log(`Highest seen receiving bitrate: ${highestSeenReceivingBitrate}`);
|
|
183
|
-
console.log(`Highest seen available outgoing bitrate: ${highestSeenAvailableOutgoingBitrate}`);
|
|
184
|
-
console.log(`Highest seen available incoming bitrate: ${highestSeenAvailableIncomingBitrate}`);
|
|
185
|
-
});
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
As mentioned above, the `ClientMonitor` instance stores entries, and each entry has a `stats` property that contains the raw stats collected from the WebRTC API. The `ClientMonitor` also stores calculated updates for each entry, which are derived from the raw stats. These calculated updates are accessible from the `ClientMonitor` instance and are updated every time the `stats-collected` event is emitted.
|
|
189
|
-
|
|
190
|
-
### PeerConnection Entry
|
|
191
|
-
|
|
192
|
-
**Accessing Stats**:
|
|
193
|
-
|
|
194
|
-
```javascript
|
|
195
|
-
// from monitor
|
|
196
|
-
monitor.peerConnections.forEach((pc) => console.log(`PeerConnection: ${pc.statsId}`, pc.stats));
|
|
197
|
-
|
|
198
|
-
// or from storage
|
|
199
|
-
[...monitor.storage.peerConnections()].forEach((pc) => console.log(`PeerConnection: ${pc.statsId}`, pc.stats));
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
**Accessing Calculated Updates**:
|
|
1045
|
+
**Derived Fields:**
|
|
1046
|
+
|
|
1047
|
+
- `bitrate`: Calculated sending bitrate
|
|
1048
|
+
- `payloadBitrate`: Payload-only bitrate
|
|
1049
|
+
- `packetRate`: Packet sending rate
|
|
1050
|
+
- `retransmissionRate`: Retransmission rate
|
|
1051
|
+
|
|
1052
|
+
**Navigation:**
|
|
1053
|
+
|
|
1054
|
+
- `getRemoteInboundRtp()`: Navigate to corresponding remote stats
|
|
1055
|
+
- `getMediaSource()`: Navigate to media source
|
|
1056
|
+
|
|
1057
|
+
### Connection Monitors
|
|
1058
|
+
|
|
1059
|
+
#### IceCandidatePairMonitor
|
|
1060
|
+
|
|
1061
|
+
ICE candidate pair with derived metrics:
|
|
1062
|
+
|
|
1063
|
+
**Derived Fields:**
|
|
1064
|
+
|
|
1065
|
+
- `availableIncomingBitrate`: Calculated available bandwidth
|
|
1066
|
+
- `availableOutgoingBitrate`: Calculated available bandwidth
|
|
1067
|
+
|
|
1068
|
+
#### IceTransportMonitor
|
|
1069
|
+
|
|
1070
|
+
ICE transport layer monitoring:
|
|
1071
|
+
|
|
1072
|
+
**Properties:**
|
|
1073
|
+
|
|
1074
|
+
- `selectedCandidatePair`: Currently selected candidate pair
|
|
1075
|
+
- All standard ICE transport fields
|
|
1076
|
+
|
|
1077
|
+
### appData and attachments
|
|
1078
|
+
|
|
1079
|
+
Every monitor supports two types of additional data properties that serve different purposes:
|
|
1080
|
+
|
|
1081
|
+
**`attachments`** - Data shipped with ClientSample:
|
|
1082
|
+
|
|
1083
|
+
- Included in the `ClientSample` when `createSample()` is called
|
|
1084
|
+
- Sent to your analytics backend/server
|
|
1085
|
+
- Used for server-side processing, analysis, and correlation
|
|
1086
|
+
- Survives the monitoring lifecycle and becomes part of the permanent sample data
|
|
1087
|
+
|
|
1088
|
+
**`appData`** - Application-specific data (not shipped):
|
|
1089
|
+
|
|
1090
|
+
- Never included in `ClientSample` creation
|
|
1091
|
+
- Used exclusively for local application logic
|
|
1092
|
+
- Temporary data for runtime decisions and local processing
|
|
1093
|
+
- Does not consume bandwidth or storage in your analytics pipeline
|
|
203
1094
|
|
|
204
1095
|
```javascript
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
`Inbound video bitrate${pc.receivingVideoBitrate}`,
|
|
220
|
-
`Received bytes on all audio tracks ${pc.deltaReceivedAudioBytes}`,
|
|
221
|
-
`Received bytes on all video tracks ${pc.deltaReceivedVideoBytes}`,
|
|
222
|
-
`Sent bytes on all audio tracks ${pc.deltaSentAudioBytes}`,
|
|
223
|
-
`Sent bytes on all video tracks ${pc.deltaSentVideoBytes}`,
|
|
224
|
-
].join("\n")
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
});
|
|
1096
|
+
// Set application data (not shipped with samples)
|
|
1097
|
+
trackMonitor.appData = {
|
|
1098
|
+
userId: "user-123",
|
|
1099
|
+
internalTrackId: "track-abc",
|
|
1100
|
+
localProcessingFlags: { enableProcessing: true },
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
// Set attachments (shipped with samples)
|
|
1104
|
+
trackMonitor.attachments = {
|
|
1105
|
+
roomId: "room-456",
|
|
1106
|
+
participantRole: "presenter",
|
|
1107
|
+
mediaType: "screen-share",
|
|
1108
|
+
customMetrics: { quality: "high" },
|
|
1109
|
+
};
|
|
228
1110
|
```
|
|
229
1111
|
|
|
230
|
-
|
|
1112
|
+
Every monitor in the hierarchy supports both properties:
|
|
231
1113
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
1114
|
+
- `ClientMonitor.attachments` / `ClientMonitor.appData`
|
|
1115
|
+
- `PeerConnectionMonitor.appData` (attachments set via tracks)
|
|
1116
|
+
- All track monitors: `InboundTrackMonitor`, `OutboundTrackMonitor`
|
|
1117
|
+
- All RTP monitors: `InboundRtpMonitor`, `OutboundRtpMonitor`, etc.
|
|
1118
|
+
- All connection monitors: `IceCandidatePairMonitor`, `IceTransportMonitor`, etc.
|
|
1119
|
+
|
|
1120
|
+
**Use Cases:**
|
|
1121
|
+
|
|
1122
|
+
_attachments_ for:
|
|
1123
|
+
|
|
1124
|
+
- User/session identification for server-side analysis
|
|
1125
|
+
- Room/conference context for grouping samples
|
|
1126
|
+
- A/B testing flags for performance comparison
|
|
1127
|
+
- Custom quality metrics for specialized analysis
|
|
1128
|
+
|
|
1129
|
+
_appData_ for:
|
|
1130
|
+
|
|
1131
|
+
- Local UI state management
|
|
1132
|
+
- Runtime feature toggles
|
|
1133
|
+
- Temporary computation results
|
|
1134
|
+
- Internal application routing information
|
|
237
1135
|
|
|
238
|
-
|
|
239
|
-
[...pc.remoteCandidates()].forEach((remoteICECandidate) => void 0);
|
|
240
|
-
[...pc.iceCandidatePairs()].forEach((iceCandidatePair) => void 0);
|
|
1136
|
+
## Stats Adapters
|
|
241
1137
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
1138
|
+
Stats adapters provide a powerful mechanism to customize how WebRTC statistics are processed before being consumed by monitors. They handle browser-specific differences and allow custom preprocessing logic.
|
|
1139
|
+
|
|
1140
|
+
### Built-in Adapters
|
|
1141
|
+
|
|
1142
|
+
The library includes several built-in adapters that are automatically applied based on browser detection:
|
|
1143
|
+
|
|
1144
|
+
#### Firefox Adapters
|
|
1145
|
+
|
|
1146
|
+
- **Firefox94StatsAdapter**: Normalizes `mediaType` to `kind` field for RTP stats
|
|
1147
|
+
- **FirefoxTransportStatsAdapter**: Creates transport stats from ICE candidate pairs when native transport stats are missing
|
|
1148
|
+
|
|
1149
|
+
#### Browser-Specific Adaptations
|
|
1150
|
+
|
|
1151
|
+
Stats adapters are automatically added based on detected browser:
|
|
1152
|
+
|
|
1153
|
+
```javascript
|
|
1154
|
+
// Automatically applied for Firefox
|
|
1155
|
+
if (browser.name === "firefox") {
|
|
1156
|
+
pcMonitor.statsAdapters.add(new Firefox94StatsAdapter());
|
|
1157
|
+
pcMonitor.statsAdapters.add(new FirefoxTransportStatsAdapter());
|
|
249
1158
|
}
|
|
250
1159
|
```
|
|
251
1160
|
|
|
252
|
-
###
|
|
1161
|
+
### Custom Stats Adapters
|
|
253
1162
|
|
|
254
|
-
|
|
1163
|
+
Create custom adapters by implementing the `StatsAdapter` interface:
|
|
255
1164
|
|
|
256
1165
|
```javascript
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
1166
|
+
import { StatsAdapter } from "@observertc/client-monitor-js";
|
|
1167
|
+
|
|
1168
|
+
class CustomStatsAdapter {
|
|
1169
|
+
name = "custom-stats-adapter";
|
|
1170
|
+
|
|
1171
|
+
adapt(stats) {
|
|
1172
|
+
// Pre-processing: runs before monitor updates
|
|
1173
|
+
return stats.map((stat) => {
|
|
1174
|
+
if (stat.type === "inbound-rtp" && stat.trackIdentifier) {
|
|
1175
|
+
// Custom track identifier normalization
|
|
1176
|
+
stat.trackIdentifier = stat.trackIdentifier.replace(/[{}]/g, "");
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
if (stat.type === "outbound-rtp" && stat.mediaSourceId) {
|
|
1180
|
+
// Add custom metadata
|
|
1181
|
+
stat.customQualityFlag = this.calculateQualityFlag(stat);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
return stat;
|
|
1185
|
+
});
|
|
260
1186
|
}
|
|
261
|
-
|
|
1187
|
+
|
|
1188
|
+
postAdapt(stats) {
|
|
1189
|
+
// Post-processing: runs after initial monitor updates
|
|
1190
|
+
// Useful for cross-stat calculations
|
|
1191
|
+
const inboundStats = stats.filter((s) => s.type === "inbound-rtp");
|
|
1192
|
+
const outboundStats = stats.filter((s) => s.type === "outbound-rtp");
|
|
1193
|
+
|
|
1194
|
+
// Add custom correlation stats
|
|
1195
|
+
if (inboundStats.length > 0 && outboundStats.length > 0) {
|
|
1196
|
+
stats.push({
|
|
1197
|
+
type: "custom-correlation",
|
|
1198
|
+
id: "correlation-metrics",
|
|
1199
|
+
timestamp: Date.now(),
|
|
1200
|
+
totalStreams: inboundStats.length + outboundStats.length,
|
|
1201
|
+
avgBitrate: this.calculateAvgBitrate(inboundStats, outboundStats),
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
return stats;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
calculateQualityFlag(stat) {
|
|
1209
|
+
// Custom quality assessment logic
|
|
1210
|
+
return stat.bitrate > 1000000 ? "high" : "standard";
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
calculateAvgBitrate(inbound, outbound) {
|
|
1214
|
+
// Custom correlation calculation
|
|
1215
|
+
const totalBitrate = [...inbound, ...outbound].reduce((sum, stat) => sum + (stat.bitrate || 0), 0);
|
|
1216
|
+
return totalBitrate / (inbound.length + outbound.length);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// Add to peer connection monitor
|
|
1221
|
+
const adapter = new CustomStatsAdapter();
|
|
1222
|
+
pcMonitor.statsAdapters.add(adapter);
|
|
1223
|
+
|
|
1224
|
+
// Remove adapter
|
|
1225
|
+
pcMonitor.statsAdapters.remove(adapter);
|
|
1226
|
+
// or by name
|
|
1227
|
+
pcMonitor.statsAdapters.remove("custom-stats-adapter");
|
|
262
1228
|
```
|
|
263
1229
|
|
|
264
|
-
|
|
1230
|
+
### Adapter Processing Flow
|
|
265
1231
|
|
|
266
|
-
|
|
267
|
-
monitor.on("stats-collected", () => {
|
|
268
|
-
for (const track of monitor.tracks) {
|
|
269
|
-
if (track?.direction === "outbound") {
|
|
270
|
-
console.log(`Stats belongs to Track ${track.trackId} `);
|
|
271
|
-
console.log(`Lost packets reported by remote endpoint: ${track.remoteLostPackets}`);
|
|
272
|
-
console.log(`Received packets reported by remote endpoint: ${track.remoteReceivedPackets}`);
|
|
273
|
-
console.log(`Sent packets reported by local endpoint: ${track.sentPackets}`);
|
|
274
|
-
console.log(`Sending bitrate ${track.sendingBitrate}`);
|
|
275
|
-
console.log(`Associated sfu stream id: ${track.sfuStreamId}`);
|
|
276
|
-
}
|
|
1232
|
+
Adapters are processed in a specific order during stats collection:
|
|
277
1233
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
1234
|
+
1. **Raw Stats Collection**: `getStats()` called on peer connection
|
|
1235
|
+
2. **Pre-Adaptation**: `adapt()` method called on all adapters in order
|
|
1236
|
+
3. **Monitor Updates**: Monitors process adapted stats and update derived fields
|
|
1237
|
+
4. **Post-Adaptation**: `postAdapt()` method called for advanced cross-stat processing
|
|
1238
|
+
5. **Final Processing**: Detectors run and scores calculated
|
|
1239
|
+
|
|
1240
|
+
### Advanced Adapter Examples
|
|
1241
|
+
|
|
1242
|
+
#### Mediasoup Probator Filter
|
|
1243
|
+
|
|
1244
|
+
```javascript
|
|
1245
|
+
class MediasoupProbatorFilter {
|
|
1246
|
+
name = "mediasoup-probator-filter";
|
|
1247
|
+
|
|
1248
|
+
adapt(stats) {
|
|
1249
|
+
// Filter out mediasoup probator tracks
|
|
1250
|
+
return stats.filter((stat) => {
|
|
1251
|
+
if (stat.type === "inbound-rtp" || stat.type === "outbound-rtp") {
|
|
1252
|
+
return stat.trackIdentifier !== "probator";
|
|
1253
|
+
}
|
|
1254
|
+
return true;
|
|
1255
|
+
});
|
|
286
1256
|
}
|
|
287
|
-
}
|
|
1257
|
+
}
|
|
288
1258
|
```
|
|
289
1259
|
|
|
290
|
-
|
|
1260
|
+
#### Bandwidth Estimation Adapter
|
|
291
1261
|
|
|
292
1262
|
```javascript
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
1263
|
+
class BandwidthEstimationAdapter {
|
|
1264
|
+
name = "bandwidth-estimation-adapter";
|
|
1265
|
+
|
|
1266
|
+
postAdapt(stats) {
|
|
1267
|
+
const candidatePairs = stats.filter((s) => s.type === "candidate-pair");
|
|
1268
|
+
const selectedPair = candidatePairs.find((p) => p.state === "succeeded");
|
|
1269
|
+
|
|
1270
|
+
if (selectedPair && selectedPair.availableIncomingBitrate) {
|
|
1271
|
+
// Add custom bandwidth metrics
|
|
1272
|
+
stats.push({
|
|
1273
|
+
type: "custom-bandwidth",
|
|
1274
|
+
id: "bandwidth-estimation",
|
|
1275
|
+
timestamp: Date.now(),
|
|
1276
|
+
estimatedBandwidth: selectedPair.availableIncomingBitrate,
|
|
1277
|
+
bandwidthUtilization: this.calculateUtilization(stats, selectedPair),
|
|
299
1278
|
});
|
|
300
1279
|
}
|
|
301
1280
|
|
|
302
|
-
|
|
303
|
-
[...track.inboundRtps()].forEach((inboundRtp) => {
|
|
304
|
-
console.log(`Inbound RTP ${inboundRtp.getSsrc()} has ${inboundRtp.receivedPackets} received packets`);
|
|
305
|
-
});
|
|
306
|
-
}
|
|
1281
|
+
return stats;
|
|
307
1282
|
}
|
|
308
|
-
|
|
1283
|
+
|
|
1284
|
+
calculateUtilization(stats, selectedPair) {
|
|
1285
|
+
const totalBitrate = stats
|
|
1286
|
+
.filter((s) => s.type === "inbound-rtp")
|
|
1287
|
+
.reduce((sum, s) => sum + (s.bitrate || 0), 0);
|
|
1288
|
+
return totalBitrate / selectedPair.availableIncomingBitrate;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
309
1291
|
```
|
|
310
1292
|
|
|
311
|
-
|
|
1293
|
+
## Derived Metrics
|
|
1294
|
+
|
|
1295
|
+
The library automatically calculates numerous derived metrics from raw WebRTC statistics, providing enhanced insights into connection quality and performance. These metrics are computed during stats processing and are available on monitor objects.
|
|
312
1296
|
|
|
313
|
-
|
|
1297
|
+
### Client-Level Derived Metrics
|
|
1298
|
+
|
|
1299
|
+
Available on `ClientMonitor`:
|
|
314
1300
|
|
|
315
1301
|
```javascript
|
|
316
|
-
monitor
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
1302
|
+
const monitor = new ClientMonitor();
|
|
1303
|
+
|
|
1304
|
+
// Aggregated bitrates across all peer connections
|
|
1305
|
+
console.log(monitor.sendingAudioBitrate); // Total audio sending bitrate (bps)
|
|
1306
|
+
console.log(monitor.sendingVideoBitrate); // Total video sending bitrate (bps)
|
|
1307
|
+
console.log(monitor.receivingAudioBitrate); // Total audio receiving bitrate (bps)
|
|
1308
|
+
console.log(monitor.receivingVideoBitrate); // Total video receiving bitrate (bps)
|
|
1309
|
+
|
|
1310
|
+
// Network capacity metrics
|
|
1311
|
+
console.log(monitor.totalAvailableIncomingBitrate); // Available bandwidth for receiving
|
|
1312
|
+
console.log(monitor.totalAvailableOutgoingBitrate); // Available bandwidth for sending
|
|
1313
|
+
|
|
1314
|
+
// Connection quality
|
|
1315
|
+
console.log(monitor.avgRttInSec); // Average RTT across connections (seconds)
|
|
1316
|
+
console.log(monitor.score); // Calculated quality score (0.0-5.0)
|
|
1317
|
+
console.log(monitor.durationOfCollectingStatsInMs); // Time to collect stats (performance indicator)
|
|
321
1318
|
```
|
|
322
1319
|
|
|
323
|
-
|
|
1320
|
+
### Peer Connection Derived Metrics
|
|
1321
|
+
|
|
1322
|
+
Available on `PeerConnectionMonitor`:
|
|
324
1323
|
|
|
325
1324
|
```javascript
|
|
326
|
-
monitor.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
1325
|
+
const pcMonitor = /* get from monitor.peerConnections */;
|
|
1326
|
+
|
|
1327
|
+
// Bitrate metrics by media type
|
|
1328
|
+
console.log(pcMonitor.sendingAudioBitrate); // Audio sending bitrate (bps)
|
|
1329
|
+
console.log(pcMonitor.sendingVideoBitrate); // Video sending bitrate (bps)
|
|
1330
|
+
console.log(pcMonitor.receivingAudioBitrate); // Audio receiving bitrate (bps)
|
|
1331
|
+
console.log(pcMonitor.receivingVideoBitrate); // Video receiving bitrate (bps)
|
|
1332
|
+
|
|
1333
|
+
// Packet loss rates
|
|
1334
|
+
console.log(pcMonitor.outboundFractionLost); // Outbound packet loss fraction
|
|
1335
|
+
console.log(pcMonitor.inboundFractionalLost); // Inbound packet loss fraction
|
|
1336
|
+
|
|
1337
|
+
// Delta metrics (change since last collection)
|
|
1338
|
+
console.log(pcMonitor.deltaInboundPacketsLost); // Packets lost in period
|
|
1339
|
+
console.log(pcMonitor.deltaInboundPacketsReceived); // Packets received in period
|
|
1340
|
+
console.log(pcMonitor.deltaOutboundPacketsSent); // Packets sent in period
|
|
1341
|
+
console.log(pcMonitor.deltaAudioBytesSent); // Audio bytes sent in period
|
|
1342
|
+
console.log(pcMonitor.deltaVideoBytesSent); // Video bytes sent in period
|
|
1343
|
+
console.log(pcMonitor.deltaDataChannelBytesSent); // Data channel bytes sent
|
|
1344
|
+
|
|
1345
|
+
// Connection timing and RTT
|
|
1346
|
+
console.log(pcMonitor.avgRttInSec); // Current average RTT (seconds)
|
|
1347
|
+
console.log(pcMonitor.ewmaRttInSec); // EWMA smoothed RTT (seconds)
|
|
1348
|
+
console.log(pcMonitor.connectingStartedAt); // Connection start timestamp
|
|
1349
|
+
console.log(pcMonitor.connectedAt); // Connection established timestamp
|
|
1350
|
+
|
|
1351
|
+
// Network topology detection
|
|
1352
|
+
console.log(pcMonitor.usingTURN); // Boolean: using TURN relay
|
|
1353
|
+
console.log(pcMonitor.usingTCP); // Boolean: using TCP transport
|
|
1354
|
+
console.log(pcMonitor.iceState); // ICE connection state
|
|
1355
|
+
|
|
1356
|
+
// Historical peaks
|
|
1357
|
+
console.log(pcMonitor.highestSeenSendingBitrate); // Peak sending bitrate seen
|
|
1358
|
+
console.log(pcMonitor.highestSeenReceivingBitrate); // Peak receiving bitrate seen
|
|
1359
|
+
console.log(pcMonitor.highestSeenAvailableIncomingBitrate); // Peak available incoming
|
|
1360
|
+
console.log(pcMonitor.highestSeenAvailableOutgoingBitrate); // Peak available outgoing
|
|
341
1361
|
```
|
|
342
1362
|
|
|
343
|
-
|
|
1363
|
+
### Track-Level Derived Metrics
|
|
1364
|
+
|
|
1365
|
+
#### Inbound Track Metrics
|
|
1366
|
+
|
|
1367
|
+
Available on `InboundTrackMonitor`:
|
|
344
1368
|
|
|
345
1369
|
```javascript
|
|
346
|
-
monitor.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
);
|
|
353
|
-
console.log(
|
|
354
|
-
`inbound RTP associated with SSRC ${inboundRtp.getSsrc()} remote endpoint sent ${
|
|
355
|
-
inboundRtp.getRemoteOutboundRtp()?.stats.packetsSent
|
|
356
|
-
} packets`
|
|
357
|
-
);
|
|
358
|
-
|
|
359
|
-
inboundRtp.kind === "audio" &&
|
|
360
|
-
console.log(
|
|
361
|
-
`inbound RTP associated with SSRC ${inboundRtp.getSsrc()} has played out ${
|
|
362
|
-
inboundRtp.getAudioPlayout()?.stats.totalSamplesCount
|
|
363
|
-
} audio samples`
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
const audioPlayout = inboundRtp.getAudioPlayout(); // The AudioPlayoutEntry associated with the inboundRtp
|
|
367
|
-
const peerConnection = inboundRtp.getPeerConnection(); // The PeerConnectionEntry associated with the inboundRtp
|
|
368
|
-
const trackId = inboundRtp.getTrackId(); // The trackId associated with the inboundRtp
|
|
369
|
-
}
|
|
370
|
-
});
|
|
1370
|
+
const inboundTrack = /* get from monitor.tracks */;
|
|
1371
|
+
|
|
1372
|
+
console.log(inboundTrack.bitrate); // Receiving bitrate (bps)
|
|
1373
|
+
console.log(inboundTrack.jitter); // Network jitter (seconds)
|
|
1374
|
+
console.log(inboundTrack.fractionLost); // Packet loss fraction
|
|
1375
|
+
console.log(inboundTrack.score); // Track quality score (0.0-5.0)
|
|
371
1376
|
```
|
|
372
1377
|
|
|
373
|
-
|
|
1378
|
+
#### Outbound Track Metrics
|
|
374
1379
|
|
|
375
|
-
|
|
1380
|
+
Available on `OutboundTrackMonitor`:
|
|
376
1381
|
|
|
377
1382
|
```javascript
|
|
378
|
-
monitor.
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
1383
|
+
const outboundTrack = /* get from monitor.tracks */;
|
|
1384
|
+
|
|
1385
|
+
console.log(outboundTrack.bitrate); // Sending bitrate (bps)
|
|
1386
|
+
console.log(outboundTrack.sendingPacketRate); // Packets sent per second
|
|
1387
|
+
console.log(outboundTrack.remoteReceivedPacketRate); // Remote packets received per second
|
|
1388
|
+
console.log(outboundTrack.jitter); // Remote reported jitter
|
|
1389
|
+
console.log(outboundTrack.fractionLost); // Remote reported packet loss
|
|
1390
|
+
console.log(outboundTrack.score); // Track quality score (0.0-5.0)
|
|
383
1391
|
```
|
|
384
1392
|
|
|
385
|
-
|
|
1393
|
+
### RTP-Level Derived Metrics
|
|
1394
|
+
|
|
1395
|
+
#### Inbound RTP Metrics
|
|
1396
|
+
|
|
1397
|
+
Available on `InboundRtpMonitor`:
|
|
386
1398
|
|
|
387
1399
|
```javascript
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
1400
|
+
const inboundRtp = /* get from pcMonitor.mappedInboundRtpMonitors */;
|
|
1401
|
+
|
|
1402
|
+
// Bitrate and packet metrics
|
|
1403
|
+
console.log(inboundRtp.bitrate); // Calculated receiving bitrate (bps)
|
|
1404
|
+
console.log(inboundRtp.packetRate); // Packets received per second
|
|
1405
|
+
console.log(inboundRtp.fractionLost); // Calculated packet loss fraction
|
|
1406
|
+
console.log(inboundRtp.bitPerPixel); // Video: bits per pixel efficiency
|
|
1407
|
+
|
|
1408
|
+
// Video-specific derived metrics
|
|
1409
|
+
console.log(inboundRtp.avgFramesPerSec); // Average FPS over recent samples
|
|
1410
|
+
console.log(inboundRtp.ewmaFps); // EWMA smoothed FPS
|
|
1411
|
+
console.log(inboundRtp.fpsVolatility); // FPS stability (lower is better)
|
|
1412
|
+
console.log(inboundRtp.isFreezed); // Boolean: video appears frozen
|
|
1413
|
+
|
|
1414
|
+
// Audio-specific metrics
|
|
1415
|
+
console.log(inboundRtp.receivingAudioSamples); // Audio samples received in period
|
|
1416
|
+
console.log(inboundRtp.desync); // Boolean: audio desync detected
|
|
1417
|
+
|
|
1418
|
+
// Delta metrics (change since last collection)
|
|
1419
|
+
console.log(inboundRtp.deltaPacketsLost); // Packets lost in period
|
|
1420
|
+
console.log(inboundRtp.deltaPacketsReceived); // Packets received in period
|
|
1421
|
+
console.log(inboundRtp.deltaBytesReceived); // Bytes received in period
|
|
1422
|
+
console.log(inboundRtp.deltaJitterBufferDelay); // Jitter buffer delay change
|
|
1423
|
+
console.log(inboundRtp.deltaFramesDecoded); // Video frames decoded in period
|
|
1424
|
+
console.log(inboundRtp.deltaFramesReceived); // Video frames received in period
|
|
1425
|
+
console.log(inboundRtp.deltaFramesRendered); // Video frames rendered in period
|
|
1426
|
+
console.log(inboundRtp.deltaCorruptionProbability); // Frame corruption change
|
|
1427
|
+
console.log(inboundRtp.deltaTime); // Elapsed time for calculations (ms)
|
|
397
1428
|
```
|
|
398
1429
|
|
|
399
|
-
|
|
1430
|
+
#### Outbound RTP Metrics
|
|
1431
|
+
|
|
1432
|
+
Available on `OutboundRtpMonitor`:
|
|
400
1433
|
|
|
401
1434
|
```javascript
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
);
|
|
414
|
-
|
|
415
|
-
const remoteInboundRtp = outboundRtp.getRemoteInboundRtp(); // The RemoteInboundRtpEntry associated with the outboundRtp
|
|
416
|
-
const peerConnection = outboundRtp.getPeerConnection(); // The PeerConnectionEntry associated with the outboundRtp
|
|
417
|
-
const trackId = outboundRtp.getTrackId(); // The trackId associated with the outboundRtp
|
|
418
|
-
}
|
|
419
|
-
});
|
|
1435
|
+
const outboundRtp = /* get from pcMonitor.mappedOutboundRtpMonitors */;
|
|
1436
|
+
|
|
1437
|
+
// Bitrate metrics
|
|
1438
|
+
console.log(outboundRtp.bitrate); // Total sending bitrate (bps)
|
|
1439
|
+
console.log(outboundRtp.payloadBitrate); // Payload-only bitrate (excluding headers/retransmissions)
|
|
1440
|
+
console.log(outboundRtp.packetRate); // Packets sent per second
|
|
1441
|
+
console.log(outboundRtp.bitPerPixel); // Video: bits per pixel efficiency
|
|
1442
|
+
|
|
1443
|
+
// Delta metrics
|
|
1444
|
+
console.log(outboundRtp.deltaPacketsSent); // Packets sent in period
|
|
1445
|
+
console.log(outboundRtp.deltaBytesSent); // Bytes sent in period
|
|
420
1446
|
```
|
|
421
1447
|
|
|
422
|
-
|
|
1448
|
+
#### Remote RTP Metrics
|
|
423
1449
|
|
|
424
|
-
|
|
1450
|
+
**Remote Inbound RTP** (remote peer's receiving stats):
|
|
425
1451
|
|
|
426
|
-
|
|
1452
|
+
```javascript
|
|
1453
|
+
const remoteInboundRtp = /* get from pcMonitor.mappedRemoteInboundRtpMonitors */;
|
|
427
1454
|
|
|
428
|
-
|
|
1455
|
+
console.log(remoteInboundRtp.packetRate); // Remote receiving packet rate
|
|
1456
|
+
console.log(remoteInboundRtp.deltaPacketsLost); // Remote packets lost in period
|
|
1457
|
+
```
|
|
429
1458
|
|
|
430
|
-
|
|
1459
|
+
**Remote Outbound RTP** (remote peer's sending stats):
|
|
431
1460
|
|
|
432
1461
|
```javascript
|
|
433
|
-
const
|
|
1462
|
+
const remoteOutboundRtp = /* get from pcMonitor.mappedRemoteOutboundRtpMonitors */;
|
|
434
1463
|
|
|
435
|
-
|
|
436
|
-
console.log("congestion detected on media streaming");
|
|
437
|
-
console.log("Available incoming bitrate before congestion", event.incomingBitrateBeforeCongestion);
|
|
438
|
-
console.log("Available outgoing bitrate before congestion", event.outgoingBitrateBeforeCongestion);
|
|
439
|
-
console.log("Available incoming bitrate after congestion", event.incomingBitrateAfterCongestion);
|
|
440
|
-
console.log("Available outgoing bitrate after congestion", event.outgoingBitrateAfterCongestion);
|
|
441
|
-
});
|
|
1464
|
+
console.log(remoteOutboundRtp.bitrate); // Remote sending bitrate
|
|
442
1465
|
```
|
|
443
1466
|
|
|
444
|
-
###
|
|
1467
|
+
### ICE Transport Derived Metrics
|
|
445
1468
|
|
|
446
|
-
|
|
447
|
-
const detector = monitor.createAudioDesyncDetector();
|
|
1469
|
+
Available on `IceTransportMonitor` and `IceCandidatePairMonitor`:
|
|
448
1470
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
1471
|
+
```javascript
|
|
1472
|
+
const iceTransport = /* get from pcMonitor.mappedIceTransportMonitors */;
|
|
1473
|
+
|
|
1474
|
+
// Transport-level bitrates
|
|
1475
|
+
console.log(iceTransport.sendingBitrate); // Transport sending bitrate
|
|
1476
|
+
console.log(iceTransport.receivingBitrate); // Transport receiving bitrate
|
|
1477
|
+
|
|
1478
|
+
// Delta metrics
|
|
1479
|
+
console.log(iceTransport.deltaPacketsSent); // Packets sent in period
|
|
1480
|
+
console.log(iceTransport.deltaPacketsReceived); // Packets received in period
|
|
1481
|
+
console.log(iceTransport.deltaBytesSent); // Bytes sent in period
|
|
1482
|
+
console.log(iceTransport.deltaBytesReceived); // Bytes received in period
|
|
1483
|
+
|
|
1484
|
+
// ICE candidate pair specific
|
|
1485
|
+
const candidatePair = /* get from pcMonitor.mappedIceCandidatePairMonitors */;
|
|
1486
|
+
console.log(candidatePair.availableIncomingBitrate); // Bandwidth estimation for receiving
|
|
1487
|
+
console.log(candidatePair.availableOutgoingBitrate); // Bandwidth estimation for sending
|
|
452
1488
|
```
|
|
453
1489
|
|
|
454
|
-
|
|
1490
|
+
### Data Channel Derived Metrics
|
|
1491
|
+
|
|
1492
|
+
Available on `DataChannelMonitor`:
|
|
455
1493
|
|
|
456
1494
|
```javascript
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
//
|
|
461
|
-
createIssueOnDetection: {
|
|
462
|
-
severity: "major",
|
|
463
|
-
attachments: {
|
|
464
|
-
// various custom data
|
|
465
|
-
},
|
|
466
|
-
},
|
|
467
|
-
/**
|
|
468
|
-
* The fractional threshold used to determine if the audio desynchronization
|
|
469
|
-
* correction is considered significant or not.
|
|
470
|
-
* It represents the minimum required ratio of corrected samples to total samples.
|
|
471
|
-
* For example, a value of 0.1 means that if the corrected samples ratio
|
|
472
|
-
* exceeds 0.1, it will be considered a significant audio desynchronization issue.
|
|
473
|
-
*/
|
|
474
|
-
fractionalCorrectionAlertOnThreshold: 0.1,
|
|
475
|
-
/**
|
|
476
|
-
* The fractional threshold used to determine if the audio desynchronization
|
|
477
|
-
* correction is considered negligible and the alert should be turned off.
|
|
478
|
-
* It represents the maximum allowed ratio of corrected samples to total samples.
|
|
479
|
-
* For example, a value of 0.05 means that if the corrected samples ratio
|
|
480
|
-
* falls below 0.05, the audio desynchronization alert will be turned off.
|
|
481
|
-
*/
|
|
482
|
-
fractionalCorrectionAlertOffThreshold: 0.05,
|
|
483
|
-
});
|
|
1495
|
+
const dataChannel = /* get from pcMonitor.mappedDataChannelMonitors */;
|
|
1496
|
+
|
|
1497
|
+
console.log(dataChannel.deltaBytesSent); // Bytes sent in period
|
|
1498
|
+
console.log(dataChannel.deltaBytesReceived); // Bytes received in period
|
|
484
1499
|
```
|
|
485
1500
|
|
|
486
|
-
###
|
|
1501
|
+
### Media Source and Playout Metrics
|
|
487
1502
|
|
|
488
|
-
|
|
1503
|
+
**Media Source derived metrics** (local media):
|
|
489
1504
|
|
|
490
1505
|
```javascript
|
|
491
|
-
const
|
|
1506
|
+
const mediaSource = /* get from pcMonitor.mappedMediaSourceMonitors */;
|
|
1507
|
+
// Media source stats are mostly raw WebRTC stats
|
|
1508
|
+
// Derived metrics are primarily calculated at RTP level
|
|
1509
|
+
```
|
|
492
1510
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
1511
|
+
**Media Playout derived metrics** (audio playout):
|
|
1512
|
+
|
|
1513
|
+
```javascript
|
|
1514
|
+
const mediaPlayout = /* get from pcMonitor.mappedMediaPlayoutMonitors */;
|
|
1515
|
+
|
|
1516
|
+
console.log(mediaPlayout.deltaSynthesizedSamplesDuration); // Synthesized audio duration in period
|
|
1517
|
+
console.log(mediaPlayout.deltaSamplesDuration); // Total samples duration in period
|
|
496
1518
|
```
|
|
497
1519
|
|
|
498
|
-
|
|
1520
|
+
### Accessing Derived Metrics
|
|
499
1521
|
|
|
500
1522
|
```javascript
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
//
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
1523
|
+
// Access derived metrics through monitor hierarchy
|
|
1524
|
+
monitor.on("stats-collected", () => {
|
|
1525
|
+
// Client-level aggregates
|
|
1526
|
+
console.log("Total sending bitrate:", monitor.sendingAudioBitrate + monitor.sendingVideoBitrate);
|
|
1527
|
+
|
|
1528
|
+
// Per-connection metrics
|
|
1529
|
+
monitor.peerConnections.forEach((pc) => {
|
|
1530
|
+
console.log(`PC ${pc.peerConnectionId} RTT:`, pc.avgRttInSec * 1000, "ms");
|
|
1531
|
+
|
|
1532
|
+
// Per-track metrics
|
|
1533
|
+
pc.mappedInboundTracks.forEach((track) => {
|
|
1534
|
+
if (track.kind === "video") {
|
|
1535
|
+
const inboundRtp = track.getInboundRtp();
|
|
1536
|
+
console.log(`Video FPS: ${inboundRtp?.ewmaFps}, Volatility: ${inboundRtp?.fpsVolatility}`);
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
});
|
|
511
1540
|
});
|
|
1541
|
+
|
|
1542
|
+
// Manual access to specific metrics
|
|
1543
|
+
const videoTrack = monitor.tracks.find((t) => t.kind === "video" && t.direction === "inbound");
|
|
1544
|
+
if (videoTrack) {
|
|
1545
|
+
const rtp = videoTrack.getInboundRtp();
|
|
1546
|
+
console.log("Video quality metrics:", {
|
|
1547
|
+
bitrate: rtp.bitrate,
|
|
1548
|
+
fps: rtp.ewmaFps,
|
|
1549
|
+
volatility: rtp.fpsVolatility,
|
|
1550
|
+
packetLoss: rtp.fractionLost,
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
```
|
|
1554
|
+
|
|
1555
|
+
## Schema Reference
|
|
1556
|
+
|
|
1557
|
+
### ClientSample
|
|
1558
|
+
|
|
1559
|
+
The main sample structure containing complete client state:
|
|
1560
|
+
|
|
1561
|
+
```typescript
|
|
1562
|
+
type ClientSample = {
|
|
1563
|
+
timestamp: number;
|
|
1564
|
+
clientId?: string;
|
|
1565
|
+
callId?: string;
|
|
1566
|
+
score?: number;
|
|
1567
|
+
scoreReasons?: string;
|
|
1568
|
+
attachments?: Record<string, unknown>;
|
|
1569
|
+
peerConnections?: PeerConnectionSample[];
|
|
1570
|
+
clientEvents?: ClientEvent[];
|
|
1571
|
+
clientIssues?: ClientIssue[];
|
|
1572
|
+
clientMetaItems?: ClientMetaData[];
|
|
1573
|
+
extensionStats?: ExtensionStat[];
|
|
1574
|
+
};
|
|
1575
|
+
```
|
|
1576
|
+
|
|
1577
|
+
### PeerConnectionSample
|
|
1578
|
+
|
|
1579
|
+
Per-peer-connection statistics:
|
|
1580
|
+
|
|
1581
|
+
```typescript
|
|
1582
|
+
type PeerConnectionSample = {
|
|
1583
|
+
peerConnectionId: string;
|
|
1584
|
+
score?: number;
|
|
1585
|
+
scoreReasons?: string;
|
|
1586
|
+
attachments?: Record<string, unknown>;
|
|
1587
|
+
inboundTracks?: InboundTrackSample[];
|
|
1588
|
+
outboundTracks?: OutboundTrackSample[];
|
|
1589
|
+
codecs?: CodecStats[];
|
|
1590
|
+
inboundRtps?: InboundRtpStats[];
|
|
1591
|
+
outboundRtps?: OutboundRtpStats[];
|
|
1592
|
+
remoteInboundRtps?: RemoteInboundRtpStats[];
|
|
1593
|
+
remoteOutboundRtps?: RemoteOutboundRtpStats[];
|
|
1594
|
+
mediaSources?: MediaSourceStats[];
|
|
1595
|
+
mediaPlayouts?: MediaPlayoutStats[];
|
|
1596
|
+
dataChannels?: DataChannelStats[];
|
|
1597
|
+
iceTransports?: IceTransportStats[];
|
|
1598
|
+
iceCandidates?: IceCandidateStats[];
|
|
1599
|
+
iceCandidatePairs?: IceCandidatePairStats[];
|
|
1600
|
+
certificates?: CertificateStats[];
|
|
1601
|
+
};
|
|
512
1602
|
```
|
|
513
1603
|
|
|
514
|
-
###
|
|
1604
|
+
### Stats Types
|
|
1605
|
+
|
|
1606
|
+
All stats types include standard WebRTC fields plus:
|
|
1607
|
+
|
|
1608
|
+
- `timestamp`: When the stats were collected
|
|
1609
|
+
- `id`: Unique identifier
|
|
1610
|
+
- `attachments`: Additional data for sampling
|
|
1611
|
+
|
|
1612
|
+
**Key Stats Types:**
|
|
1613
|
+
|
|
1614
|
+
- `InboundRtpStats`: Receiving stream statistics
|
|
1615
|
+
- `OutboundRtpStats`: Sending stream statistics
|
|
1616
|
+
- `IceCandidatePairStats`: ICE candidate pair information
|
|
1617
|
+
- `CodecStats`: Codec configuration
|
|
1618
|
+
- `MediaSourceStats`: Local media source stats
|
|
1619
|
+
|
|
1620
|
+
## Examples
|
|
1621
|
+
|
|
1622
|
+
### Basic Monitoring Setup
|
|
515
1623
|
|
|
516
1624
|
```javascript
|
|
517
|
-
|
|
1625
|
+
import { ClientMonitor } from "@observertc/client-monitor-js";
|
|
1626
|
+
|
|
1627
|
+
const monitor = new ClientMonitor({
|
|
1628
|
+
clientId: "client-123",
|
|
1629
|
+
callId: "call-456",
|
|
1630
|
+
collectingPeriodInMs: 2000,
|
|
1631
|
+
samplingPeriodInMs: 5000,
|
|
1632
|
+
});
|
|
518
1633
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
1634
|
+
// Add peer connection
|
|
1635
|
+
const pc = new RTCPeerConnection();
|
|
1636
|
+
monitor.addSource(pc);
|
|
1637
|
+
|
|
1638
|
+
// Handle samples
|
|
1639
|
+
monitor.on("sample-created", (sample) => {
|
|
1640
|
+
// Send to analytics
|
|
1641
|
+
fetch("/analytics", {
|
|
1642
|
+
method: "POST",
|
|
1643
|
+
body: JSON.stringify(sample),
|
|
1644
|
+
headers: { "Content-Type": "application/json" },
|
|
1645
|
+
});
|
|
524
1646
|
});
|
|
525
1647
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
console.
|
|
529
|
-
console.log("Freeze duration in Seconds", event.durationInS);
|
|
530
|
-
console.log("PeerConnectionId", event.peerConnectionId);
|
|
531
|
-
console.log("SSRC:", event.ssrc);
|
|
1648
|
+
// Handle issues
|
|
1649
|
+
monitor.on("issue", (issue) => {
|
|
1650
|
+
console.warn("Issue detected:", issue.type, issue.payload);
|
|
532
1651
|
});
|
|
533
1652
|
```
|
|
534
1653
|
|
|
535
|
-
|
|
1654
|
+
### Advanced Configuration
|
|
536
1655
|
|
|
537
1656
|
```javascript
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
1657
|
+
const monitor = new ClientMonitor({
|
|
1658
|
+
clientId: "advanced-client",
|
|
1659
|
+
collectingPeriodInMs: 1000,
|
|
1660
|
+
samplingPeriodInMs: 3000,
|
|
1661
|
+
|
|
1662
|
+
// Sensitive congestion detection
|
|
1663
|
+
congestionDetector: {
|
|
1664
|
+
sensitivity: "high",
|
|
1665
|
+
createIssue: true,
|
|
1666
|
+
},
|
|
1667
|
+
|
|
1668
|
+
// Strict CPU monitoring
|
|
1669
|
+
cpuPerformanceDetector: {
|
|
1670
|
+
fpsVolatilityThresholds: {
|
|
1671
|
+
lowWatermark: 0.05,
|
|
1672
|
+
highWatermark: 0.2,
|
|
546
1673
|
},
|
|
1674
|
+
durationOfCollectingStatsThreshold: {
|
|
1675
|
+
lowWatermark: 3000,
|
|
1676
|
+
highWatermark: 6000,
|
|
1677
|
+
},
|
|
1678
|
+
},
|
|
1679
|
+
|
|
1680
|
+
// Quick dry track detection
|
|
1681
|
+
dryInboundTrackDetector: {
|
|
1682
|
+
thresholdInMs: 3000,
|
|
1683
|
+
},
|
|
1684
|
+
|
|
1685
|
+
appData: {
|
|
1686
|
+
version: "1.0.0",
|
|
1687
|
+
feature: "screen-share",
|
|
547
1688
|
},
|
|
548
1689
|
});
|
|
549
1690
|
```
|
|
550
1691
|
|
|
551
|
-
###
|
|
552
|
-
|
|
553
|
-
The Stucked Inbound Track Detector is designed to identify tracks that are experiencing a complete lack of traffic since their creation. These tracks, termed "stucked inbound tracks," are characterized by the absence of any packet reception from the moment of their creation. In essence, stucked inbound tracks refer to tracks that have not received any data packets since their inception.
|
|
1692
|
+
### Mediasoup Integration
|
|
554
1693
|
|
|
555
1694
|
```javascript
|
|
556
|
-
|
|
1695
|
+
import mediasoup from "mediasoup-client";
|
|
557
1696
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
console.log("PeerConnectionId", event.peerConnectionId);
|
|
562
|
-
console.log("SSRC:", event.ssrc);
|
|
1697
|
+
const device = new mediasoup.Device();
|
|
1698
|
+
const monitor = new ClientMonitor({
|
|
1699
|
+
clientId: "mediasoup-client",
|
|
563
1700
|
});
|
|
564
|
-
```
|
|
565
1701
|
|
|
566
|
-
|
|
567
|
-
|
|
1702
|
+
// Load device capabilities
|
|
1703
|
+
await device.load({ routerRtpCapabilities });
|
|
568
1704
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
```
|
|
1705
|
+
// Add device for monitoring
|
|
1706
|
+
monitor.addSource(device);
|
|
572
1707
|
|
|
573
|
-
|
|
1708
|
+
// Create transport
|
|
1709
|
+
const sendTransport = device.createSendTransport({
|
|
1710
|
+
// transport options
|
|
1711
|
+
});
|
|
574
1712
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
// various custom data
|
|
584
|
-
},
|
|
585
|
-
},
|
|
586
|
-
// Minimum duration in milliseconds for a track to be considered stuck
|
|
587
|
-
minStuckedDurationInMs: 5000,
|
|
1713
|
+
// The monitor automatically detects the new transport
|
|
1714
|
+
// For existing transports, add manually:
|
|
1715
|
+
// monitor.addSource(sendTransport);
|
|
1716
|
+
|
|
1717
|
+
// Produce media
|
|
1718
|
+
const producer = await sendTransport.produce({
|
|
1719
|
+
track: videoTrack,
|
|
1720
|
+
codecOptions: {},
|
|
588
1721
|
});
|
|
589
|
-
```
|
|
590
1722
|
|
|
591
|
-
|
|
1723
|
+
// Track is automatically monitored
|
|
1724
|
+
```
|
|
592
1725
|
|
|
593
|
-
|
|
1726
|
+
### Custom Detector
|
|
594
1727
|
|
|
595
1728
|
```javascript
|
|
596
|
-
|
|
1729
|
+
class NetworkLatencyDetector {
|
|
1730
|
+
name = "network-latency-detector";
|
|
1731
|
+
|
|
1732
|
+
constructor(pcMonitor) {
|
|
1733
|
+
this.pcMonitor = pcMonitor;
|
|
1734
|
+
this.highLatencyThreshold = 200; // ms
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
update() {
|
|
1738
|
+
const rttMs = (this.pcMonitor.avgRttInSec || 0) * 1000;
|
|
1739
|
+
|
|
1740
|
+
if (rttMs > this.highLatencyThreshold) {
|
|
1741
|
+
this.pcMonitor.parent.emit("high-latency", {
|
|
1742
|
+
peerConnectionId: this.pcMonitor.peerConnectionId,
|
|
1743
|
+
rttMs,
|
|
1744
|
+
});
|
|
1745
|
+
|
|
1746
|
+
this.pcMonitor.parent.addIssue({
|
|
1747
|
+
type: "high-latency",
|
|
1748
|
+
payload: { rttMs, threshold: this.highLatencyThreshold },
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
597
1753
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
1754
|
+
// Add to peer connection monitor
|
|
1755
|
+
monitor.on("peer-connection-opened", ({ peerConnectionMonitor }) => {
|
|
1756
|
+
const detector = new NetworkLatencyDetector(peerConnectionMonitor);
|
|
1757
|
+
peerConnectionMonitor.detectors.add(detector);
|
|
601
1758
|
});
|
|
602
1759
|
```
|
|
603
1760
|
|
|
604
|
-
|
|
1761
|
+
### Real-time Monitoring Dashboard
|
|
605
1762
|
|
|
606
1763
|
```javascript
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
1764
|
+
class MonitoringDashboard {
|
|
1765
|
+
constructor(monitor) {
|
|
1766
|
+
this.monitor = monitor;
|
|
1767
|
+
this.setupEventListeners();
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
setupEventListeners() {
|
|
1771
|
+
this.monitor.on("score", ({ clientScore, scoreReasons }) => {
|
|
1772
|
+
this.updateScoreDisplay(clientScore, scoreReasons);
|
|
1773
|
+
});
|
|
1774
|
+
|
|
1775
|
+
this.monitor.on("congestion", ({ availableIncomingBitrate, availableOutgoingBitrate }) => {
|
|
1776
|
+
this.showCongestionAlert(availableIncomingBitrate, availableOutgoingBitrate);
|
|
1777
|
+
});
|
|
1778
|
+
|
|
1779
|
+
this.monitor.on("stats-collected", ({ durationOfCollectingStatsInMs }) => {
|
|
1780
|
+
this.updatePerformanceMetrics(durationOfCollectingStatsInMs);
|
|
1781
|
+
});
|
|
1782
|
+
|
|
1783
|
+
this.monitor.on("issue", (issue) => {
|
|
1784
|
+
this.addIssueToLog(issue);
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
updateScoreDisplay(score, reasons) {
|
|
1789
|
+
document.getElementById("score").textContent = score.toFixed(1);
|
|
1790
|
+
document.getElementById("score-reasons").textContent = JSON.stringify(reasons, null, 2);
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
showCongestionAlert(incoming, outgoing) {
|
|
1794
|
+
const alert = document.createElement("div");
|
|
1795
|
+
alert.className = "congestion-alert";
|
|
1796
|
+
alert.textContent = `Congestion detected! Available: ${incoming}/${outgoing} kbps`;
|
|
1797
|
+
document.body.appendChild(alert);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
updatePerformanceMetrics(duration) {
|
|
1801
|
+
document.getElementById("collection-time").textContent = `${duration}ms`;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
addIssueToLog(issue) {
|
|
1805
|
+
const log = document.getElementById("issue-log");
|
|
1806
|
+
const entry = document.createElement("div");
|
|
1807
|
+
entry.textContent = `${new Date().toISOString()}: ${issue.type} - ${JSON.stringify(issue.payload)}`;
|
|
1808
|
+
log.appendChild(entry);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
// Initialize dashboard
|
|
1813
|
+
const dashboard = new MonitoringDashboard(monitor);
|
|
620
1814
|
```
|
|
621
1815
|
|
|
622
|
-
|
|
1816
|
+
## Troubleshooting
|
|
1817
|
+
|
|
1818
|
+
### Common Issues
|
|
1819
|
+
|
|
1820
|
+
#### High Memory Usage
|
|
623
1821
|
|
|
624
|
-
|
|
1822
|
+
```javascript
|
|
1823
|
+
// Limit stored scores history
|
|
1824
|
+
monitor.scoreCalculator.constructor.lastNScoresMaxLength = 5;
|
|
1825
|
+
|
|
1826
|
+
// Disable unnecessary detectors
|
|
1827
|
+
monitor.config.audioDesyncDetector.disabled = true;
|
|
625
1828
|
|
|
626
|
-
|
|
1829
|
+
// Reduce collection frequency
|
|
1830
|
+
monitor.setCollectingPeriod(5000);
|
|
1831
|
+
```
|
|
627
1832
|
|
|
628
|
-
|
|
1833
|
+
#### Missing Statistics
|
|
629
1834
|
|
|
630
1835
|
```javascript
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
1836
|
+
// Check if source is properly added
|
|
1837
|
+
console.log("Peer connections:", monitor.peerConnections.length);
|
|
1838
|
+
|
|
1839
|
+
// Verify stats collection
|
|
1840
|
+
monitor.on("stats-collected", ({ collectedStats }) => {
|
|
1841
|
+
console.log("Collected stats from PCs:", collectedStats.length);
|
|
1842
|
+
});
|
|
1843
|
+
|
|
1844
|
+
// Check for adaptation issues
|
|
1845
|
+
monitor.statsAdapters.add((stats) => {
|
|
1846
|
+
console.log("Raw stats count:", stats.length);
|
|
1847
|
+
return stats;
|
|
639
1848
|
});
|
|
640
1849
|
```
|
|
641
1850
|
|
|
642
|
-
|
|
1851
|
+
#### Browser Compatibility
|
|
643
1852
|
|
|
644
1853
|
```javascript
|
|
645
|
-
|
|
646
|
-
|
|
1854
|
+
// Check browser support
|
|
1855
|
+
if (!window.RTCPeerConnection) {
|
|
1856
|
+
console.error("WebRTC not supported");
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
// Handle browser-specific issues
|
|
1860
|
+
monitor.on("stats-collected", ({ collectedStats }) => {
|
|
1861
|
+
if (collectedStats.length === 0) {
|
|
1862
|
+
console.warn("No stats collected - possible browser issue");
|
|
1863
|
+
}
|
|
647
1864
|
});
|
|
648
1865
|
```
|
|
649
1866
|
|
|
650
|
-
|
|
1867
|
+
### Debug Information
|
|
651
1868
|
|
|
652
|
-
|
|
1869
|
+
Enable debug logging:
|
|
653
1870
|
|
|
654
1871
|
```javascript
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
1872
|
+
import { setLogger } from "@observertc/client-monitor-js";
|
|
1873
|
+
|
|
1874
|
+
setLogger({
|
|
1875
|
+
trace: console.trace,
|
|
1876
|
+
debug: console.debug,
|
|
1877
|
+
info: console.info,
|
|
1878
|
+
warn: console.warn,
|
|
1879
|
+
error: console.error,
|
|
662
1880
|
});
|
|
663
1881
|
```
|
|
664
1882
|
|
|
665
|
-
|
|
1883
|
+
### Performance Optimization
|
|
666
1884
|
|
|
667
1885
|
```javascript
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
1886
|
+
// Optimize for large numbers of tracks
|
|
1887
|
+
const monitor = new ClientMonitor({
|
|
1888
|
+
collectingPeriodInMs: 3000, // Reduce frequency
|
|
1889
|
+
samplingPeriodInMs: 10000, // Less frequent sampling
|
|
1890
|
+
|
|
1891
|
+
// Disable resource-intensive detectors
|
|
1892
|
+
cpuPerformanceDetector: { disabled: true },
|
|
1893
|
+
audioDesyncDetector: { disabled: true },
|
|
1894
|
+
});
|
|
676
1895
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
*
|
|
683
|
-
* DEFAULT: 3
|
|
684
|
-
*/
|
|
685
|
-
samplingTick: 3,
|
|
686
|
-
|
|
687
|
-
/**
|
|
688
|
-
* By setting this to true monitor will
|
|
689
|
-
*
|
|
690
|
-
* DEFAULT: 0
|
|
691
|
-
*/
|
|
692
|
-
integrateNavigatorMediaDevices: true,
|
|
693
|
-
/**
|
|
694
|
-
* If true, the monitor creates a CLIENT_JOINED event when the monitor is created.
|
|
695
|
-
*
|
|
696
|
-
* additionally you can add attachments to the join event by setting it to:
|
|
697
|
-
* {
|
|
698
|
-
* attachments: {
|
|
699
|
-
* my_custom_id: 'customId',
|
|
700
|
-
* }
|
|
701
|
-
* }
|
|
702
|
-
*
|
|
703
|
-
* DEFAULT: true
|
|
704
|
-
*/
|
|
705
|
-
createClientJoinedEvent: true
|
|
706
|
-
/**
|
|
707
|
-
* Configuration for detecting issues.
|
|
708
|
-
*
|
|
709
|
-
* By default, all detectors are enabled.
|
|
710
|
-
*/
|
|
711
|
-
detectIssues: {
|
|
712
|
-
/**
|
|
713
|
-
* Configuration for detecting congestion issues.
|
|
714
|
-
*
|
|
715
|
-
* DEFAULT: 'major'
|
|
716
|
-
*/
|
|
717
|
-
congestion: 'major',
|
|
718
|
-
|
|
719
|
-
/**
|
|
720
|
-
* Configuration for detecting audio desynchronization issues.
|
|
721
|
-
*/
|
|
722
|
-
audioDesync: 'minor',
|
|
723
|
-
|
|
724
|
-
/**
|
|
725
|
-
* Configuration for detecting frozen video issues.
|
|
726
|
-
*/
|
|
727
|
-
freezedVideo: 'minor',
|
|
728
|
-
|
|
729
|
-
/**
|
|
730
|
-
* Configuration for detecting CPU limitation issues.
|
|
731
|
-
*/
|
|
732
|
-
cpuLimitation: 'major',
|
|
733
|
-
|
|
734
|
-
/**
|
|
735
|
-
* Configuration for detecting stucked inbound track issues.
|
|
736
|
-
*/
|
|
737
|
-
stuckedInboundTrack: 'major',
|
|
738
|
-
|
|
739
|
-
/**
|
|
740
|
-
* Configuration for detecting long peer connection establishment issues.
|
|
741
|
-
*/
|
|
742
|
-
longPcConnectionEstablishment: 'major',
|
|
743
|
-
}
|
|
744
|
-
};
|
|
1896
|
+
// Manual garbage collection
|
|
1897
|
+
setInterval(() => {
|
|
1898
|
+
// Clear old data periodically
|
|
1899
|
+
monitor.scoreCalculator.totalReasons = {};
|
|
1900
|
+
}, 60000);
|
|
745
1901
|
```
|
|
746
1902
|
|
|
747
|
-
##
|
|
1903
|
+
## API Reference
|
|
1904
|
+
|
|
1905
|
+
### Types
|
|
748
1906
|
|
|
749
|
-
|
|
1907
|
+
```typescript
|
|
1908
|
+
// Configuration
|
|
1909
|
+
type ClientMonitorConfig = {
|
|
1910
|
+
/* ... */
|
|
1911
|
+
};
|
|
750
1912
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
- `MEDIA_TRACK_MUTED`: A media track is muted
|
|
758
|
-
- `MEDIA_TRACK_UNMUTED`: A media track is unmuted
|
|
759
|
-
- `ICE_GATHERING_STATE_CHANGED`: The ICE gathering state has changed
|
|
760
|
-
- `PEER_CONNECTION_STATE_CHANGED`: The peer connection state has changed
|
|
761
|
-
- `ICE_CONNECTION_STATE_CHANGED`: The ICE connection state has changed
|
|
762
|
-
- `DATA_CHANNEL_OPEN`: A data channel is opened
|
|
763
|
-
- `DATA_CHANNEL_CLOSED`: A data channel is closed
|
|
764
|
-
- `DATA_CHANNEL_ERROR`: A data channel error occurred
|
|
1913
|
+
// Core types
|
|
1914
|
+
type ClientSample = {
|
|
1915
|
+
/* ... */
|
|
1916
|
+
};
|
|
1917
|
+
type ClientEvent = { type: string; payload?: any; timestamp: number };
|
|
1918
|
+
type ClientIssue = { type: string; payload?: any; timestamp: number };
|
|
765
1919
|
|
|
766
|
-
|
|
1920
|
+
// Monitor types
|
|
1921
|
+
class InboundTrackMonitor {
|
|
1922
|
+
/* ... */
|
|
1923
|
+
}
|
|
1924
|
+
class OutboundTrackMonitor {
|
|
1925
|
+
/* ... */
|
|
1926
|
+
}
|
|
1927
|
+
class PeerConnectionMonitor {
|
|
1928
|
+
/* ... */
|
|
1929
|
+
}
|
|
767
1930
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
- `CONSUMER_PAUSED`: A consumer is paused
|
|
775
|
-
- `CONSUMER_RESUMED`: A consumer is resumed
|
|
776
|
-
- `DATA_PRODUCER_ADDED`: A data producer is added
|
|
777
|
-
- `DATA_PRODUCER_REMOVED`: A data producer is removed
|
|
778
|
-
- `DATA_CONSUMER_ADDED`: A data consumer is added
|
|
779
|
-
- `DATA_CONSUMER_REMOVED`: A data consumer is removed
|
|
1931
|
+
// Detector interface
|
|
1932
|
+
interface Detector {
|
|
1933
|
+
readonly name: string;
|
|
1934
|
+
update(): void;
|
|
1935
|
+
}
|
|
1936
|
+
```
|
|
780
1937
|
|
|
781
|
-
###
|
|
1938
|
+
### Events
|
|
1939
|
+
|
|
1940
|
+
```typescript
|
|
1941
|
+
interface ClientMonitorEvents {
|
|
1942
|
+
"sample-created": (sample: ClientSample) => void;
|
|
1943
|
+
"stats-collected": (data: {
|
|
1944
|
+
durationOfCollectingStatsInMs: number;
|
|
1945
|
+
collectedStats: [string, RTCStats[]][];
|
|
1946
|
+
}) => void;
|
|
1947
|
+
score: (data: { clientScore: number; scoreReasons?: Record<string, number> }) => void;
|
|
1948
|
+
issue: (issue: ClientIssue) => void;
|
|
1949
|
+
congestion: (data: CongestionEvent) => void;
|
|
1950
|
+
close: () => void;
|
|
1951
|
+
// ... detector-specific events
|
|
1952
|
+
}
|
|
1953
|
+
```
|
|
782
1954
|
|
|
783
|
-
|
|
784
|
-
the joined timestamp is set to the time of the monitor creation. However, you can
|
|
785
|
-
manually set the event by calling the `monitor.join()` method.
|
|
1955
|
+
## FAQ
|
|
786
1956
|
|
|
787
|
-
|
|
1957
|
+
### Q: How often should I collect statistics?
|
|
788
1958
|
|
|
789
|
-
|
|
790
|
-
monitor.join({
|
|
791
|
-
timestamp: Date.now(),
|
|
792
|
-
});
|
|
793
|
-
```
|
|
1959
|
+
**A**: The default 2-second interval (2000ms) works well for most applications. For real-time applications or debugging, you might use 1 second. For low-bandwidth situations, 5 seconds is acceptable.
|
|
794
1960
|
|
|
795
|
-
###
|
|
1961
|
+
### Q: What's the difference between collectingPeriod and samplingPeriod?
|
|
796
1962
|
|
|
797
|
-
A
|
|
1963
|
+
**A**:
|
|
798
1964
|
|
|
799
|
-
|
|
1965
|
+
- `collectingPeriod`: How often to collect WebRTC stats from browser APIs
|
|
1966
|
+
- `samplingPeriod`: How often to create complete client samples (includes events, issues, metadata)
|
|
800
1967
|
|
|
801
|
-
|
|
802
|
-
monitor.leave({
|
|
803
|
-
timestamp: Date.now(),
|
|
804
|
-
});
|
|
805
|
-
```
|
|
1968
|
+
### Q: How do I reduce bandwidth usage?
|
|
806
1969
|
|
|
807
|
-
|
|
1970
|
+
**A**:
|
|
808
1971
|
|
|
809
|
-
|
|
1972
|
+
1. Increase sampling period
|
|
1973
|
+
2. Use sample compression (@observertc/samples-encoder)
|
|
1974
|
+
3. Filter samples before sending
|
|
1975
|
+
4. Disable unnecessary detectors
|
|
810
1976
|
|
|
811
|
-
|
|
812
|
-
monitor.addCustomCallEvent({
|
|
813
|
-
name: "MY CUSTOM EVENT", // mandatory
|
|
1977
|
+
### Q: Can I use this with React Native?
|
|
814
1978
|
|
|
815
|
-
|
|
816
|
-
attachments: JSON.stringify({
|
|
817
|
-
value: "my custom value",
|
|
818
|
-
}), // optional
|
|
819
|
-
timestamp: Date.now(), // optional
|
|
820
|
-
value: `simple string value`, // optional
|
|
821
|
-
peerConnectionId: "peer-connection-id", // optional;
|
|
822
|
-
mediaTrackId: "media-track-id", // optional;
|
|
823
|
-
});
|
|
824
|
-
```
|
|
1979
|
+
**A**: The library is designed for web browsers with WebRTC support. For React Native, you'd need WebRTC polyfills and may encounter platform-specific issues.
|
|
825
1980
|
|
|
826
|
-
###
|
|
1981
|
+
### Q: How do I handle multiple peer connections?
|
|
827
1982
|
|
|
828
|
-
|
|
1983
|
+
**A**: Just add each peer connection as a source:
|
|
829
1984
|
|
|
830
1985
|
```javascript
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
},
|
|
837
|
-
});
|
|
1986
|
+
const pc1 = new RTCPeerConnection();
|
|
1987
|
+
const pc2 = new RTCPeerConnection();
|
|
1988
|
+
|
|
1989
|
+
monitor.addSource(pc1);
|
|
1990
|
+
monitor.addSource(pc2);
|
|
838
1991
|
```
|
|
839
1992
|
|
|
840
|
-
|
|
1993
|
+
### Q: What happens when a peer connection is closed?
|
|
841
1994
|
|
|
842
|
-
|
|
843
|
-
- Use `addExtensionStats` for events that are related to the client's environment, such as CPU usage and collected periodically.
|
|
1995
|
+
**A**: The monitor automatically cleans up associated resources and emits appropriate events. You don't need to manually remove closed connections.
|
|
844
1996
|
|
|
845
|
-
|
|
1997
|
+
### Q: How accurate are the quality scores?
|
|
846
1998
|
|
|
847
|
-
|
|
1999
|
+
**A**: Scores are based on standard WebRTC metrics and industry best practices. They provide good relative quality assessment but should be calibrated based on your specific use case and user feedback.
|
|
2000
|
+
|
|
2001
|
+
### Q: Can I customize which events are included in samples?
|
|
2002
|
+
|
|
2003
|
+
**A**: Yes, you can filter events before sampling or add custom logic in event handlers to control what gets included.
|
|
2004
|
+
|
|
2005
|
+
### Q: How do I monitor screen sharing vs camera streams differently?
|
|
2006
|
+
|
|
2007
|
+
**A**: Use the `attachments` property to tag tracks:
|
|
848
2008
|
|
|
849
2009
|
```javascript
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
console.log("Elapsed time in milliseconds since the last sample was created:", elapsedSinceLastSampleInMs);
|
|
853
|
-
});
|
|
2010
|
+
// When adding a screen share track
|
|
2011
|
+
trackMonitor.attachments = { mediaType: "screen-share" };
|
|
854
2012
|
```
|
|
855
2013
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
## NPM package
|
|
2014
|
+
### Q: What's the performance impact of monitoring?
|
|
859
2015
|
|
|
860
|
-
|
|
2016
|
+
**A**: The library is designed to be lightweight. Typical overhead is <1% CPU usage. The main cost is the periodic `getStats()` calls, which is why the collection period is configurable.
|
|
861
2017
|
|
|
862
|
-
##
|
|
2018
|
+
## NPM Package
|
|
863
2019
|
|
|
864
|
-
https://
|
|
2020
|
+
https://www.npmjs.com/package/@observertc/client-monitor-js
|
|
865
2021
|
|
|
866
2022
|
## Schemas
|
|
867
2023
|
|
|
868
|
-
https://github.com/observertc/schemas
|
|
2024
|
+
Schema definitions are available at https://github.com/observertc/schemas
|
|
869
2025
|
|
|
870
2026
|
## Getting Involved
|
|
871
2027
|
|
|
872
|
-
Client-monitor is made with the intention to provide an open-source monitoring solution for
|
|
873
|
-
WebRTC developers. If you are interested in getting involved
|
|
874
|
-
please read our [contribution](CONTRIBUTING.md) guideline.
|
|
2028
|
+
Client-monitor is made with the intention to provide an open-source monitoring solution for WebRTC developers. If you are interested in getting involved, please read our [contribution guidelines](CONTRIBUTING.md).
|
|
875
2029
|
|
|
876
2030
|
## License
|
|
877
2031
|
|