@observertc/client-monitor-js 1.3.2 → 2.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 +18 -656
- package/lib/Accumulator.d.ts +3 -3
- package/lib/Accumulator.d.ts.map +1 -1
- package/lib/Accumulator.js +7 -11
- package/lib/ClientDevices.d.ts +3 -3
- package/lib/ClientDevices.d.ts.map +1 -1
- package/lib/ClientDevices.js +16 -43
- package/lib/ClientMonitor.d.ts +126 -105
- package/lib/ClientMonitor.d.ts.map +1 -1
- package/lib/ClientMonitor.js +1 -2
- package/lib/ClientMonitorImpl.d.ts +18 -21
- package/lib/ClientMonitorImpl.d.ts.map +1 -1
- package/lib/ClientMonitorImpl.js +117 -196
- package/lib/Collectors.d.ts +1 -1
- package/lib/Collectors.d.ts.map +1 -1
- package/lib/Collectors.js +19 -19
- package/lib/Metrics.js +1 -5
- package/lib/Sampler.d.ts +4 -60
- package/lib/Sampler.d.ts.map +1 -1
- package/lib/Sampler.js +42 -167
- package/lib/adapters/Adapter.d.ts +1 -1
- package/lib/adapters/Adapter.d.ts.map +1 -1
- package/lib/adapters/Adapter.js +64 -69
- package/lib/adapters/Chrome86Adapter.js +7 -11
- package/lib/adapters/DefaultAdapter.js +5 -9
- package/lib/adapters/Firefox94Adapter.js +5 -9
- package/lib/adapters/Safari14Adapter.js +7 -11
- package/lib/collectors/MediasoupStatsCollector.d.ts +2 -2
- package/lib/collectors/MediasoupStatsCollector.d.ts.map +1 -1
- package/lib/collectors/MediasoupStatsCollector.js +78 -55
- package/lib/collectors/MediasoupSurrogates.d.ts +7 -7
- package/lib/collectors/MediasoupSurrogates.d.ts.map +1 -1
- package/lib/collectors/MediasoupSurrogates.js +1 -2
- package/lib/collectors/PeerConnectionStatsCollector.d.ts.map +1 -1
- package/lib/collectors/PeerConnectionStatsCollector.js +6 -15
- package/lib/collectors/StatsCollector.d.ts +1 -1
- package/lib/collectors/StatsCollector.d.ts.map +1 -1
- package/lib/collectors/StatsCollector.js +1 -2
- package/lib/entries/PeerConnectionEntryImpl.d.ts +24 -14
- package/lib/entries/PeerConnectionEntryImpl.d.ts.map +1 -1
- package/lib/entries/PeerConnectionEntryImpl.js +841 -901
- package/lib/entries/StatsEntryInterfaces.d.ts +61 -18
- package/lib/entries/StatsEntryInterfaces.d.ts.map +1 -1
- package/lib/entries/StatsEntryInterfaces.js +1 -2
- package/lib/entries/StatsStorage.d.ts +24 -8
- package/lib/entries/StatsStorage.d.ts.map +1 -1
- package/lib/entries/StatsStorage.js +72 -31
- package/lib/entries/UpdateFields.d.ts +6 -0
- package/lib/entries/UpdateFields.d.ts.map +1 -0
- package/lib/entries/UpdateFields.js +29 -0
- package/lib/index.d.ts +5 -10
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +6 -17
- package/lib/utils/MediaDevices.d.ts +2 -2
- package/lib/utils/MediaDevices.d.ts.map +1 -1
- package/lib/utils/MediaDevices.js +1 -5
- package/lib/utils/StatsVisitor.d.ts +4 -9
- package/lib/utils/StatsVisitor.d.ts.map +1 -1
- package/lib/utils/StatsVisitor.js +24 -44
- package/lib/utils/Timer.d.ts +2 -2
- package/lib/utils/Timer.d.ts.map +1 -1
- package/lib/utils/Timer.js +5 -9
- package/lib/utils/callEvents.d.ts +12 -0
- package/lib/utils/callEvents.d.ts.map +1 -0
- package/lib/utils/callEvents.js +12 -0
- package/lib/utils/common.d.ts +1 -0
- package/lib/utils/common.d.ts.map +1 -1
- package/lib/utils/common.js +8 -17
- package/lib/utils/logger.d.ts +18 -5
- package/lib/utils/logger.d.ts.map +1 -1
- package/lib/utils/logger.js +120 -52
- package/lib/utils/{hash.d.ts → makeStamp.d.ts} +1 -1
- package/lib/utils/makeStamp.d.ts.map +1 -0
- package/lib/utils/{hash.js → makeStamp.js} +2 -5
- package/lib/utils/reverseEnums.d.ts +1 -1
- package/lib/utils/reverseEnums.d.ts.map +1 -1
- package/lib/utils/reverseEnums.js +1 -5
- package/lib/utils/validators.js +3 -9
- package/package.json +5 -5
- package/src/Accumulator.ts +1 -1
- package/src/ClientDevices.ts +12 -12
- package/src/ClientMonitor.ts +135 -109
- package/src/ClientMonitorImpl.ts +122 -166
- package/src/Collectors.ts +7 -0
- package/src/Sampler.ts +45 -217
- package/src/adapters/Adapter.ts +1 -1
- package/src/adapters/Chrome86Adapter.ts +1 -1
- package/src/collectors/MediasoupStatsCollector.ts +82 -37
- package/src/collectors/MediasoupSurrogates.ts +3 -1
- package/src/collectors/PeerConnectionStatsCollector.ts +4 -6
- package/src/collectors/StatsCollector.ts +1 -0
- package/src/entries/PeerConnectionEntryImpl.ts +274 -286
- package/src/entries/StatsEntryInterfaces.ts +85 -18
- package/src/entries/StatsStorage.ts +99 -36
- package/src/entries/UpdateFields.ts +44 -0
- package/src/index.ts +23 -12
- package/src/utils/MediaDevices.ts +1 -1
- package/src/utils/StatsVisitor.ts +6 -22
- package/src/utils/callEvents.ts +12 -0
- package/src/utils/common.ts +1 -1
- package/src/utils/logger.ts +155 -30
- package/src/utils/{hash.ts → makeStamp.ts} +2 -1
- package/tsconfig.json +1 -1
- package/.github/workflows/publish.yaml +0 -42
- package/CHANGELOG.md +0 -37
- package/CONTRIBUTING.md +0 -29
- package/docs/.nojekyll +0 -1
- package/docs/assets/highlight.css +0 -92
- package/docs/assets/icons.css +0 -1043
- package/docs/assets/icons.png +0 -0
- package/docs/assets/icons@2x.png +0 -0
- package/docs/assets/main.js +0 -52
- package/docs/assets/search.js +0 -1
- package/docs/assets/style.css +0 -1413
- package/docs/assets/widgets.png +0 -0
- package/docs/assets/widgets@2x.png +0 -0
- package/docs/classes/MediaDevices.html +0 -1
- package/docs/index.html +0 -100
- package/docs/interfaces/CertificateEntry.html +0 -4
- package/docs/interfaces/ClientMonitor.ClientMonitor-1.html +0 -82
- package/docs/interfaces/CodecEntry.html +0 -6
- package/docs/interfaces/ContributingSourceEntry.html +0 -4
- package/docs/interfaces/DataChannelEntry.html +0 -4
- package/docs/interfaces/IceCandidatePairEntry.html +0 -4
- package/docs/interfaces/IceServerEntry.html +0 -4
- package/docs/interfaces/InboundRtpEntry.html +0 -14
- package/docs/interfaces/LocalCandidateEntry.html +0 -4
- package/docs/interfaces/MediaSourceEntry.html +0 -4
- package/docs/interfaces/OutboundRtpEntry.html +0 -10
- package/docs/interfaces/PcStatsCollector.html +0 -9
- package/docs/interfaces/PeerConnectionEntry.html +0 -6
- package/docs/interfaces/ReceiverEntry.html +0 -5
- package/docs/interfaces/RemoteCandidateEntry.html +0 -4
- package/docs/interfaces/RemoteInboundRtpEntry.html +0 -8
- package/docs/interfaces/RemoteOutboundRtpEntry.html +0 -8
- package/docs/interfaces/SctpTransportEntry.html +0 -4
- package/docs/interfaces/SenderEntry.html +0 -5
- package/docs/interfaces/StatsReader.html +0 -41
- package/docs/interfaces/TransceiverEntry.html +0 -4
- package/docs/interfaces/TransportEntry.html +0 -4
- package/docs/modules/ClientMonitor.html +0 -33
- package/docs/modules.html +0 -1
- package/figures/navigation.png +0 -0
- package/figures/navigation.puml +0 -150
- package/lib/EventsRelayer.d.ts +0 -47
- package/lib/EventsRelayer.d.ts.map +0 -1
- package/lib/EventsRelayer.js +0 -73
- package/lib/Sender.d.ts +0 -43
- package/lib/Sender.d.ts.map +0 -1
- package/lib/Sender.js +0 -157
- package/lib/codecs/Base64Codec.d.ts +0 -12
- package/lib/codecs/Base64Codec.d.ts.map +0 -1
- package/lib/codecs/Base64Codec.js +0 -25
- package/lib/codecs/Codec.d.ts +0 -11
- package/lib/codecs/Codec.d.ts.map +0 -1
- package/lib/codecs/Codec.js +0 -52
- package/lib/codecs/FacadedCodec.d.ts +0 -10
- package/lib/codecs/FacadedCodec.d.ts.map +0 -1
- package/lib/codecs/FacadedCodec.js +0 -34
- package/lib/codecs/JsonCodec.d.ts +0 -12
- package/lib/codecs/JsonCodec.d.ts.map +0 -1
- package/lib/codecs/JsonCodec.js +0 -30
- package/lib/codecs/ProtobufCodec.d.ts +0 -14
- package/lib/codecs/ProtobufCodec.d.ts.map +0 -1
- package/lib/codecs/ProtobufCodec.js +0 -67
- package/lib/codecs/TextCodec.d.ts +0 -14
- package/lib/codecs/TextCodec.d.ts.map +0 -1
- package/lib/codecs/TextCodec.js +0 -22
- package/lib/transports/Transport.d.ts +0 -16
- package/lib/transports/Transport.d.ts.map +0 -1
- package/lib/transports/Transport.js +0 -16
- package/lib/transports/WebsocketTransport.d.ts +0 -58
- package/lib/transports/WebsocketTransport.d.ts.map +0 -1
- package/lib/transports/WebsocketTransport.js +0 -217
- package/lib/utils/hash.d.ts.map +0 -1
- package/src/EventsRelayer.ts +0 -119
- package/src/Sender.ts +0 -190
- package/src/codecs/Base64Codec.ts +0 -32
- package/src/codecs/Codec.ts +0 -38
- package/src/codecs/FacadedCodec.ts +0 -36
- package/src/codecs/JsonCodec.ts +0 -39
- package/src/codecs/ProtobufCodec.ts +0 -53
- package/src/codecs/TextCodec.ts +0 -32
- package/src/transports/Transport.ts +0 -30
- package/src/transports/WebsocketTransport.ts +0 -260
- package/tests/Accumulator.spec.ts +0 -74
- package/tests/Collectors.spec.ts +0 -373
- package/tests/EventsRelayer.spec.ts +0 -122
- package/tests/Metrics.spec.ts +0 -40
- package/tests/Sampler.spec.ts +0 -237
- package/tests/Sender.spec.ts +0 -7
- package/tests/adapters/Adapter.spec.ts +0 -39
- package/tests/adapters/ChromeAdapter86.spec.ts +0 -62
- package/tests/adapters/DefaultAdapter.spec.ts +0 -6
- package/tests/adapters/Firefox94Adapter.spec.ts +0 -92
- package/tests/adapters/Safari14Adapter.spec.ts +0 -66
- package/tests/codecs/Base64Codec.spec.ts +0 -10
- package/tests/codecs/FacadedCodec.spec.ts +0 -18
- package/tests/codecs/JsonCodec.spec.ts +0 -10
- package/tests/codecs/ProtobufCodec.spec.ts +0 -41
- package/tests/codecs/TextCodec.spec.ts +0 -12
- package/tests/entries/StatsStorage.spec.ts +0 -399
- package/tests/helpers/ClientMonitorGenerator.ts +0 -173
- package/tests/helpers/StatsGenerator.ts +0 -310
- package/tests/helpers/createBaseAdapterTests.ts +0 -192
- package/tests/transports/WebsocketTransport.spec.ts +0 -17
- package/tests/utils/MediaDevices.spec.ts +0 -68
- package/tests/utils/Timer.spec.ts +0 -167
- package/tests/utils/common.spec.ts +0 -113
- package/tests/utils/hash.spec.ts +0 -110
- package/tests/utils/validators.spec.ts +0 -35
package/README.md
CHANGED
|
@@ -8,10 +8,7 @@ Table of Contents:
|
|
|
8
8
|
* [Quick Start](#quick-start)
|
|
9
9
|
* [Integrations](#integrations)
|
|
10
10
|
- [Mediasoup](#mediasoup)
|
|
11
|
-
* [Client Monitor Storage](#client-monitor-storage)
|
|
12
|
-
* [Connect to Observer](#connect-to-observer)
|
|
13
11
|
* [Configurations](#configurations)
|
|
14
|
-
* [Examples](#examples)
|
|
15
12
|
* [NPM package](#npm-package)
|
|
16
13
|
* [API docs](#api-docs)
|
|
17
14
|
* [Schemas](#schemas)
|
|
@@ -26,7 +23,7 @@ Install it from [npm](https://www.npmjs.com/package/@observertc/client-monitor-j
|
|
|
26
23
|
npm i @observertc/client-monitor-js
|
|
27
24
|
```
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
Add `@observertc/client-monitor-js` to your WebRTC app.
|
|
30
27
|
|
|
31
28
|
```javascript
|
|
32
29
|
import { createClientMonitor } from "@observertc/client-monitor-js";
|
|
@@ -37,7 +34,7 @@ const config = {
|
|
|
37
34
|
const monitor = createClientMonitor(config);
|
|
38
35
|
const statsCollector = monitor.collectors.collectFromRTCPeerConnection(peerConnection);
|
|
39
36
|
|
|
40
|
-
monitor.
|
|
37
|
+
monitor.on('stats-collected', () => {
|
|
41
38
|
const storage = monitor.storage;
|
|
42
39
|
for (const inboundRtp of storage.inboundRtps()) {
|
|
43
40
|
const trackId = inboundRtp.getTrackId();
|
|
@@ -54,6 +51,7 @@ The above example do as follows:
|
|
|
54
51
|
3. register an event called after stats are collected
|
|
55
52
|
4. print out the inbound rtps and then close the stats collector we registered in step 3.
|
|
56
53
|
|
|
54
|
+
|
|
57
55
|
## Integrations
|
|
58
56
|
|
|
59
57
|
### Mediasoup
|
|
@@ -70,12 +68,10 @@ const monitor = createClientMonitor(config);
|
|
|
70
68
|
const mediasoupStatsCollector = monitor.collectors.collectFromMediasoupDevice(mediasoupDevice);
|
|
71
69
|
|
|
72
70
|
monitor.events.onStatsCollected(() => {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
console.log(trackId, inboundRtp.stats, remoteOutboundRtp.stats);
|
|
78
|
-
}
|
|
71
|
+
// do your stuff
|
|
72
|
+
|
|
73
|
+
// you can close detach mediasoup
|
|
74
|
+
// collector by calling the close
|
|
79
75
|
mediasoupStatsCollector.close();
|
|
80
76
|
})
|
|
81
77
|
```
|
|
@@ -92,490 +88,6 @@ mediasoupStatsCollector.addTransport(myTransport)
|
|
|
92
88
|
```
|
|
93
89
|
|
|
94
90
|
|
|
95
|
-
## Client Monitor Storage
|
|
96
|
-
|
|
97
|
-
Client Monitor collects [WebRTCStats](https://www.w3.org/TR/webrtc-stats/). The collected stats can be accessed through entries the client monitor storage provides.
|
|
98
|
-
|
|
99
|
-

|
|
100
|
-
|
|
101
|
-
Entries:
|
|
102
|
-
|
|
103
|
-
* [inbound-rtp](#inbound-rtp-entries)
|
|
104
|
-
* [outbound-rtp](#outbound-rtp-entries)
|
|
105
|
-
* [remote-inbound-rtp](#remote-inbound-rtp-entries)
|
|
106
|
-
* [remote-outbound-rtp](#remote-outbound-rtp-entries)
|
|
107
|
-
* [media-source](#media-source-entries)
|
|
108
|
-
* [contributing-source](#contributing-source-entries)
|
|
109
|
-
* [data-channel](#data-channel-entries)
|
|
110
|
-
* [transceiver](#transceiver-entries)
|
|
111
|
-
* [sender](#sender-entries)
|
|
112
|
-
* [receiver](#receiver-entries)
|
|
113
|
-
* [transport](#transport-entries)
|
|
114
|
-
* [sctp-transport](#sctp-transport-entries)
|
|
115
|
-
* [ice-candidate-pair](#ice-candidate-pair-entries)
|
|
116
|
-
* [local-candidate](#local-candidate-entries)
|
|
117
|
-
* [remote-candidate](#remote-candidate-entries)
|
|
118
|
-
* [certificate](#certificate-entries)
|
|
119
|
-
* [ice-server](#ice-server-entries)
|
|
120
|
-
* [peer-connection](#peer-connection-entries)
|
|
121
|
-
|
|
122
|
-
### inbound-rtp entries
|
|
123
|
-
```javascript
|
|
124
|
-
const storage = monitor.storage;
|
|
125
|
-
|
|
126
|
-
for (const inboundRtp of storage.inboundRtps()) {
|
|
127
|
-
const receiver = inboundRtp.getReceiver();
|
|
128
|
-
const trackId = inboundRtp.getTrackId();
|
|
129
|
-
const ssrc = inboundRtp.getSsrc();
|
|
130
|
-
const remoteOutboundRtp = inboundRtp.getRemoteOutboundRtp();
|
|
131
|
-
const peerConnection = inboundRtp.getPeerConnection();
|
|
132
|
-
const transport = inboundRtp.getTransport();
|
|
133
|
-
const codec = inboundRtp.getCodec();
|
|
134
|
-
|
|
135
|
-
console.log(trackId, ssrc,
|
|
136
|
-
inboundRtp.stats,
|
|
137
|
-
remoteOutboundRtp.stats,
|
|
138
|
-
receiver.stats,
|
|
139
|
-
peerConnection.stats,
|
|
140
|
-
transport.stats,
|
|
141
|
-
codec.stats
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### outbound-rtp entries
|
|
147
|
-
|
|
148
|
-
```javascript
|
|
149
|
-
const storage = monitor.storage;
|
|
150
|
-
|
|
151
|
-
for (const outboundRtp of storage.outboundRtps()) {
|
|
152
|
-
const sender = outboundRtp.getSender();
|
|
153
|
-
const trackId = outboundRtp.getTrackId();
|
|
154
|
-
const ssrc = outboundRtp.getSsrc();
|
|
155
|
-
const remoteInboundRtp = outboundRtp.getRemoteInboundRtp();
|
|
156
|
-
const peerConnection = outboundRtp.getPeerConnection();
|
|
157
|
-
const transport = outboundRtp.getTransport();
|
|
158
|
-
const mediaSource = outboundRtp.getMediaSource();
|
|
159
|
-
|
|
160
|
-
console.log(trackId, ssrc,
|
|
161
|
-
outboundRtp.stats,
|
|
162
|
-
remoteInboundRtp.stats,
|
|
163
|
-
sender.stats,
|
|
164
|
-
peerConnection.stats,
|
|
165
|
-
transport.stats,
|
|
166
|
-
mediaSource.stats
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### remote-inbound-rtp entries
|
|
172
|
-
|
|
173
|
-
```javascript
|
|
174
|
-
const storage = monitor.storage;
|
|
175
|
-
|
|
176
|
-
for (const remoteInboundRtp of storage.remoteInboundRtps()) {
|
|
177
|
-
const ssrc = remoteInboundRtp.getSsrc();
|
|
178
|
-
const outboundRtp = remoteInboundRtp.getOutboundRtp();
|
|
179
|
-
const peerConnection = remoteInboundRtp.getPeerConnection();
|
|
180
|
-
|
|
181
|
-
console.log(ssrc,
|
|
182
|
-
remoteInboundRtp.stats,
|
|
183
|
-
outboundRtp.stats,
|
|
184
|
-
peerConnection.stats
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### remote-outbound-rtp entries
|
|
190
|
-
|
|
191
|
-
```javascript
|
|
192
|
-
const storage = monitor.storage;
|
|
193
|
-
|
|
194
|
-
for (const remoteOutboundRtp of storage.remoteOutboundRtps()) {
|
|
195
|
-
const ssrc = remoteOutboundRtp.getSsrc();
|
|
196
|
-
const inboundRtp = remoteOutboundRtp.getInboundRtp();
|
|
197
|
-
const peerConnection = remoteOutboundRtp.getPeerConnection();
|
|
198
|
-
|
|
199
|
-
console.log(ssrc,
|
|
200
|
-
remoteOutboundRtp.stats,
|
|
201
|
-
inboundRtp.stats,
|
|
202
|
-
peerConnection.stats
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### media-sources entries
|
|
208
|
-
|
|
209
|
-
```javascript
|
|
210
|
-
const storage = monitor.storage;
|
|
211
|
-
|
|
212
|
-
for (const mediaSource of storage.mediaSources()) {
|
|
213
|
-
const peerConnection = mediaSource.getPeerConnection();
|
|
214
|
-
|
|
215
|
-
console.log(
|
|
216
|
-
mediaSource.stats,
|
|
217
|
-
peerConnection.stats
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### contributing-source entries
|
|
223
|
-
|
|
224
|
-
```javascript
|
|
225
|
-
const storage = monitor.storage;
|
|
226
|
-
|
|
227
|
-
for (const cssrc of storage.contributingSources()) {
|
|
228
|
-
const peerConnection = cssrc.getPeerConnection();
|
|
229
|
-
|
|
230
|
-
console.log(
|
|
231
|
-
cssrc.stats,
|
|
232
|
-
peerConnection.stats
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### data-channel entries
|
|
238
|
-
|
|
239
|
-
```javascript
|
|
240
|
-
const storage = monitor.storage;
|
|
241
|
-
|
|
242
|
-
for (const dataChannel of storage.dataChannels()) {
|
|
243
|
-
const peerConnection = dataChannel.getPeerConnection();
|
|
244
|
-
|
|
245
|
-
console.log(
|
|
246
|
-
dataChannel.stats,
|
|
247
|
-
peerConnection.stats
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### transceiver entries
|
|
253
|
-
|
|
254
|
-
```javascript
|
|
255
|
-
const storage = monitor.storage;
|
|
256
|
-
|
|
257
|
-
for (const transceiver of storage.transceivers()) {
|
|
258
|
-
const receiver = transceiver.getReceiver();
|
|
259
|
-
const sender = transceiver.getSender();
|
|
260
|
-
const peerConnection = transceiver.getPeerConnection();
|
|
261
|
-
|
|
262
|
-
console.log(
|
|
263
|
-
transceiver.stats,
|
|
264
|
-
receiver.stats,
|
|
265
|
-
sender.stats,
|
|
266
|
-
peerConnection.stats
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
### sender entries
|
|
273
|
-
|
|
274
|
-
```javascript
|
|
275
|
-
const storage = monitor.storage;
|
|
276
|
-
|
|
277
|
-
for (const sender of storage.senders()) {
|
|
278
|
-
const mediaSource = sender.getMediaSource();
|
|
279
|
-
const peerConnection = sender.getPeerConnection();
|
|
280
|
-
|
|
281
|
-
console.log(
|
|
282
|
-
sender.stats,
|
|
283
|
-
mediaSource.stats,
|
|
284
|
-
peerConnection.stats
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
### receiver entries
|
|
291
|
-
|
|
292
|
-
```javascript
|
|
293
|
-
const storage = monitor.storage;
|
|
294
|
-
|
|
295
|
-
for (const receiver of storage.receivers()) {
|
|
296
|
-
const peerConnection = receiver.getPeerConnection();
|
|
297
|
-
|
|
298
|
-
console.log(
|
|
299
|
-
receiver.stats,
|
|
300
|
-
peerConnection.stats
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
### transport entries
|
|
306
|
-
|
|
307
|
-
```javascript
|
|
308
|
-
const storage = monitor.storage;
|
|
309
|
-
|
|
310
|
-
for (const transport of storage.transports()) {
|
|
311
|
-
const contributingTransport = transport.getRtcpTransport();
|
|
312
|
-
const selectedIceCandidatePair = transport.getSelectedIceCandidatePair();
|
|
313
|
-
const localCandidate = transport.getLocalCertificate();
|
|
314
|
-
const remoteCandidate = transport.getRemoteCertificate();
|
|
315
|
-
const peerConnection = transport.getPeerConnection();
|
|
316
|
-
|
|
317
|
-
console.log(
|
|
318
|
-
transport.stats,
|
|
319
|
-
contributingTransport?.stats,
|
|
320
|
-
selectedIceCandidatePair?.stats,
|
|
321
|
-
localCandidate?.stats,
|
|
322
|
-
remoteCandidate?.stats,
|
|
323
|
-
remoteCandidate?.stats
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
### sctp transport entries
|
|
329
|
-
|
|
330
|
-
```javascript
|
|
331
|
-
const storage = monitor.storage;
|
|
332
|
-
|
|
333
|
-
for (const sctpTransport of storage.sctpTransports()) {
|
|
334
|
-
const transport = sctpTransport.getTransport();
|
|
335
|
-
const peerConnection = sctpTransport.getPeerConnection();
|
|
336
|
-
|
|
337
|
-
console.log(
|
|
338
|
-
sctpTransport.stats,
|
|
339
|
-
transport?.stats,
|
|
340
|
-
peerConnection?.stats,
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
### ice candidate pair entries
|
|
347
|
-
|
|
348
|
-
```javascript
|
|
349
|
-
const storage = monitor.storage;
|
|
350
|
-
|
|
351
|
-
for (const iceCandidatePair of storage.iceCandidatePairs()) {
|
|
352
|
-
const transport = iceCandidatePair.getTransport();
|
|
353
|
-
const localCandidate = iceCandidatePair.getLocalCandidate();
|
|
354
|
-
const remoteCandidate = iceCandidatePair.getRemoteCandidate();
|
|
355
|
-
const peerConnection = iceCandidatePair.getPeerConnection();
|
|
356
|
-
|
|
357
|
-
console.log(
|
|
358
|
-
iceCandidatePair.stats,
|
|
359
|
-
transport?.stats,
|
|
360
|
-
localCandidate?.stats,
|
|
361
|
-
remoteCandidate?.stats,
|
|
362
|
-
peerConnection?.stats,
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
### local ice candidate entries
|
|
368
|
-
|
|
369
|
-
```javascript
|
|
370
|
-
const storage = monitor.storage;
|
|
371
|
-
|
|
372
|
-
for (const localCandidate of storage.localCandidates()) {
|
|
373
|
-
const transport = localCandidate.getTransport();
|
|
374
|
-
const peerConnection = localCandidate.getPeerConnection();
|
|
375
|
-
|
|
376
|
-
console.log(
|
|
377
|
-
localCandidate.stats,
|
|
378
|
-
peerConnection?.stats,
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
### remote ice candidate entries
|
|
384
|
-
|
|
385
|
-
```javascript
|
|
386
|
-
const storage = monitor.storage;
|
|
387
|
-
|
|
388
|
-
for (const remoteCandidate of storage.remoteCandidates()) {
|
|
389
|
-
const transport = remoteCandidate.getTransport();
|
|
390
|
-
const peerConnection = remoteCandidate.getPeerConnection();
|
|
391
|
-
|
|
392
|
-
console.log(
|
|
393
|
-
remoteCandidate.stats,
|
|
394
|
-
peerConnection?.stats,
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
### certificate entries
|
|
400
|
-
|
|
401
|
-
```javascript
|
|
402
|
-
const storage = monitor.storage;
|
|
403
|
-
|
|
404
|
-
for (const certificate of storage.certificates()) {
|
|
405
|
-
const peerConnection = certificate.getPeerConnection();
|
|
406
|
-
|
|
407
|
-
console.log(
|
|
408
|
-
certificate.stats,
|
|
409
|
-
peerConnection?.stats,
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
### ice server entries
|
|
415
|
-
|
|
416
|
-
```javascript
|
|
417
|
-
const storage = monitor.storage;
|
|
418
|
-
|
|
419
|
-
for (const iceServer of storage.iceServers()) {
|
|
420
|
-
const peerConnection = iceServer.getPeerConnection();
|
|
421
|
-
|
|
422
|
-
console.log(
|
|
423
|
-
iceServer.stats,
|
|
424
|
-
peerConnection?.stats,
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
### peer connection entries
|
|
430
|
-
|
|
431
|
-
```javascript
|
|
432
|
-
const storage = monitor.storage;
|
|
433
|
-
|
|
434
|
-
for (const peerConnection of storage.peerConnections()) {
|
|
435
|
-
|
|
436
|
-
for (const codec of peerConnection.getCodecs())
|
|
437
|
-
console.log(
|
|
438
|
-
`peerConnection(${peerConnection.id}).codec(${codec.id}).stats: `,
|
|
439
|
-
codec.stats
|
|
440
|
-
);
|
|
441
|
-
|
|
442
|
-
for (const inboundRtp of peerConnection.inboundRtps())
|
|
443
|
-
console.log(
|
|
444
|
-
`peerConnection(${peerConnection.id}).inboundRtp(${inboundRtp.id}).stats: `,
|
|
445
|
-
inboundRtp.stats
|
|
446
|
-
);
|
|
447
|
-
|
|
448
|
-
for (const outboundRtp of peerConnection.outboundRtps())
|
|
449
|
-
console.log(
|
|
450
|
-
`peerConnection(${peerConnection.id}).outboundRtp(${outboundRtp.id}).stats: `,
|
|
451
|
-
outboundRtp.stats
|
|
452
|
-
);
|
|
453
|
-
|
|
454
|
-
for (const remoteInboundRtp of peerConnection.remoteInboundRtps())
|
|
455
|
-
console.log(
|
|
456
|
-
`peerConnection(${peerConnection.id}).remoteInboundRtp(${remoteInboundRtp.id}).stats: `,
|
|
457
|
-
remoteInboundRtp.stats
|
|
458
|
-
);
|
|
459
|
-
|
|
460
|
-
for (const remoteOutboundRtp of peerConnection.remoteOutboundRtps())
|
|
461
|
-
console.log(
|
|
462
|
-
`peerConnection(${peerConnection.id}).remoteOutboundRtp(${remoteOutboundRtp.id}).stats: `,
|
|
463
|
-
remoteOutboundRtp.stats
|
|
464
|
-
);
|
|
465
|
-
|
|
466
|
-
for (const mediaSource of peerConnection.mediaSources())
|
|
467
|
-
console.log(
|
|
468
|
-
`peerConnection(${peerConnection.id}).mediaSource(${mediaSource.id}).stats: `,
|
|
469
|
-
mediaSource.stats
|
|
470
|
-
);
|
|
471
|
-
|
|
472
|
-
for (const cssrc of peerConnection.contributingSources())
|
|
473
|
-
console.log(
|
|
474
|
-
`peerConnection(${peerConnection.id}).cssrc(${cssrc.id}).stats: `,
|
|
475
|
-
cssrc.stats
|
|
476
|
-
);
|
|
477
|
-
|
|
478
|
-
for (const dataChannel of peerConnection.dataChannels())
|
|
479
|
-
console.log(
|
|
480
|
-
`peerConnection(${peerConnection.id}).dataChannel(${dataChannel.id}).stats: `,
|
|
481
|
-
dataChannel.stats
|
|
482
|
-
);
|
|
483
|
-
|
|
484
|
-
for (const transceiver of peerConnection.transceivers())
|
|
485
|
-
console.log(
|
|
486
|
-
`peerConnection(${peerConnection.id}).transceiver(${transceiver.id}).stats: `,
|
|
487
|
-
transceiver.stats
|
|
488
|
-
);
|
|
489
|
-
|
|
490
|
-
for (const sender of peerConnection.senders())
|
|
491
|
-
console.log(
|
|
492
|
-
`peerConnection(${peerConnection.id}).sender(${sender.id}).stats: `,
|
|
493
|
-
sender.stats
|
|
494
|
-
);
|
|
495
|
-
|
|
496
|
-
for (const receiver of peerConnection.receivers())
|
|
497
|
-
console.log(
|
|
498
|
-
`peerConnection(${peerConnection.id}).receiver(${receiver.id}).stats: `,
|
|
499
|
-
receiver.stats
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
for (const transport of peerConnection.transports())
|
|
503
|
-
console.log(
|
|
504
|
-
`peerConnection(${peerConnection.id}).transport(${transport.id}).stats: `,
|
|
505
|
-
transport.stats
|
|
506
|
-
);
|
|
507
|
-
|
|
508
|
-
for (const sctpTransport of peerConnection.sctpTransports())
|
|
509
|
-
console.log(
|
|
510
|
-
`peerConnection(${peerConnection.id}).sctpTransport(${sctpTransport.id}).stats: `,
|
|
511
|
-
sctpTransport.stats
|
|
512
|
-
);
|
|
513
|
-
|
|
514
|
-
for (const iceCandidate of peerConnection.iceCandidatePairs())
|
|
515
|
-
console.log(
|
|
516
|
-
`peerConnection(${peerConnection.id}).iceCandidate(${iceCandidate.id}).stats: `,
|
|
517
|
-
iceCandidate.stats
|
|
518
|
-
);
|
|
519
|
-
|
|
520
|
-
for (const localCandidate of peerConnection.localCandidates())
|
|
521
|
-
console.log(
|
|
522
|
-
`peerConnection(${peerConnection.id}).localCandidate(${localCandidate.id}).stats: `,
|
|
523
|
-
localCandidate.stats
|
|
524
|
-
);
|
|
525
|
-
|
|
526
|
-
for (const remoteCandidate of peerConnection.remoteCandidates())
|
|
527
|
-
console.log(
|
|
528
|
-
`peerConnection(${peerConnection.id}).remoteCandidate(${remoteCandidate.id}).stats: `,
|
|
529
|
-
remoteCandidate.stats
|
|
530
|
-
);
|
|
531
|
-
|
|
532
|
-
for (const certificate of peerConnection.certificates())
|
|
533
|
-
console.log(
|
|
534
|
-
`peerConnection(${peerConnection.id}).certificate(${certificate.id}).stats: `,
|
|
535
|
-
certificate.stats
|
|
536
|
-
);
|
|
537
|
-
|
|
538
|
-
for (const iceServer of peerConnection.iceServers())
|
|
539
|
-
console.log(
|
|
540
|
-
`peerConnection(${peerConnection.id}).iceServer(${iceServer.id}).stats: `,
|
|
541
|
-
iceServer.stats
|
|
542
|
-
);
|
|
543
|
-
|
|
544
|
-
console.log(`peerConnection(${peerConnection.id}) trackIds:`,
|
|
545
|
-
Array.from(peerConnection.trackIds())
|
|
546
|
-
);
|
|
547
|
-
}
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
## Connect to Observer
|
|
551
|
-
|
|
552
|
-
The client-monitor can be connected to an [Observer](https://github.com/ObserveRTC/observer).
|
|
553
|
-
|
|
554
|
-
```javascript
|
|
555
|
-
import { ClientMontior } from "@observertc/client-monitor-js";
|
|
556
|
-
// see full config in Configuration section
|
|
557
|
-
const config = {
|
|
558
|
-
collectingPeriodInMs: 5000,
|
|
559
|
-
samplingPeriodInMs: 10000,
|
|
560
|
-
sendingPeriodInMs: 15000,
|
|
561
|
-
sampler: {
|
|
562
|
-
roomId: "testRoom",
|
|
563
|
-
},
|
|
564
|
-
sender: {
|
|
565
|
-
websocket: {
|
|
566
|
-
urls: ["ws://localhost:7080/samples/myServiceId/myMediaUnitId"]
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
};
|
|
570
|
-
const monitor = ClientMontior.create(config);
|
|
571
|
-
monitor.addStatsCollector({
|
|
572
|
-
id: "collectorId",
|
|
573
|
-
getStats: () => peerConnection.getStats(),
|
|
574
|
-
});
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
The stats are collected in every 5s, but samples are only made in every 10s. Samples are sent to the observer in every 15s.
|
|
578
|
-
|
|
579
91
|
|
|
580
92
|
## Configurations
|
|
581
93
|
|
|
@@ -603,19 +115,16 @@ const config = {
|
|
|
603
115
|
sendingPeriodInMs: 10000,
|
|
604
116
|
|
|
605
117
|
/**
|
|
606
|
-
* By
|
|
607
|
-
*
|
|
608
|
-
*
|
|
118
|
+
* By enabling this option, the monitor automatically generates events
|
|
119
|
+
* when a peer connection added to the collector undergoes a change in connection state or when a track on it is added or removed.
|
|
120
|
+
*
|
|
121
|
+
* If this option is set to true, the samples created by the monitor will include the generated events. However, if
|
|
122
|
+
* no sample is created, events will accumulate indefinitely within the monitor. It is recommended to set this option to true
|
|
123
|
+
* if you want to create a sample with events.
|
|
124
|
+
*
|
|
125
|
+
* DEFAULT: false
|
|
609
126
|
*/
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
/**
|
|
613
|
-
* By setting this flag to true the samples are buffered even if no sender is available.
|
|
614
|
-
* This is useful if the sender is set later than the monitor is created, so no samples will be lost.
|
|
615
|
-
*
|
|
616
|
-
* DEFAULT: true
|
|
617
|
-
*/
|
|
618
|
-
bufferingSamples: true;
|
|
127
|
+
createCallEvents: false,
|
|
619
128
|
|
|
620
129
|
/**
|
|
621
130
|
* Collector Component related configurations
|
|
@@ -645,90 +154,6 @@ const config = {
|
|
|
645
154
|
},
|
|
646
155
|
},
|
|
647
156
|
|
|
648
|
-
/**
|
|
649
|
-
* Sampling Component Related configurations
|
|
650
|
-
*
|
|
651
|
-
*/
|
|
652
|
-
sampler: {
|
|
653
|
-
/**
|
|
654
|
-
* The identifier of the room the clients are in.
|
|
655
|
-
*
|
|
656
|
-
* If server side componet is used to collect the samples, this parameter is the critical to provide to match clients being in the same room.
|
|
657
|
-
*
|
|
658
|
-
* DEFAULT: a generated unique value
|
|
659
|
-
*
|
|
660
|
-
* NOTE: if this value has not been set clients which are in the same room will not be matched at the monitor
|
|
661
|
-
*/
|
|
662
|
-
roomId: "testRoom",
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* The identifier of the client. If it is not provided, then it a UUID is generated. If it is provided it must be a valid UUID.
|
|
666
|
-
*
|
|
667
|
-
* DEFAULT: a generated unique value
|
|
668
|
-
*/
|
|
669
|
-
clientId: "clientId",
|
|
670
|
-
|
|
671
|
-
/**
|
|
672
|
-
* the identifier of the call between clients in the same room. If not given then the server side assigns one. If it is given it must be a valid UUID.
|
|
673
|
-
*
|
|
674
|
-
* DEFAULT: undefined
|
|
675
|
-
*/
|
|
676
|
-
callId: "callId",
|
|
677
|
-
/**
|
|
678
|
-
* The userId of the client appeared to other users.
|
|
679
|
-
*
|
|
680
|
-
* DEFAULT: undefined
|
|
681
|
-
*/
|
|
682
|
-
userId: "testUser",
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Indicate if the sampler only sample stats updated since the last sampling.
|
|
686
|
-
*
|
|
687
|
-
* DEFAULT: true
|
|
688
|
-
*/
|
|
689
|
-
incrementalSampling: true,
|
|
690
|
-
|
|
691
|
-
/**
|
|
692
|
-
* Indicate if the sampler should use the timestamp provided by the rtcstats (the minimum of the peer connection stats will be used as the timestamp of the sample)
|
|
693
|
-
*
|
|
694
|
-
* DEFAULT: true
|
|
695
|
-
*/
|
|
696
|
-
useStatsSample: true,
|
|
697
|
-
},
|
|
698
|
-
/**
|
|
699
|
-
* Configure the sender component.
|
|
700
|
-
*/
|
|
701
|
-
sender: {
|
|
702
|
-
/**
|
|
703
|
-
* Configure the format used to transport samples or receieve
|
|
704
|
-
* feedback from the server.
|
|
705
|
-
*
|
|
706
|
-
* Possible values: json, protobuf
|
|
707
|
-
*
|
|
708
|
-
* DEFAULT: json
|
|
709
|
-
*
|
|
710
|
-
*/
|
|
711
|
-
format: "json",
|
|
712
|
-
/**
|
|
713
|
-
* Websocket configuration to transport the samples
|
|
714
|
-
*/
|
|
715
|
-
websocket: {
|
|
716
|
-
/**
|
|
717
|
-
* Target urls in a priority order. If the Websocket has not succeeded for the first,
|
|
718
|
-
* it tries with the second. If no more url left the connection is failed
|
|
719
|
-
*
|
|
720
|
-
*/
|
|
721
|
-
urls: ["ws://localhost:7080/samples/myServiceId/myMediaUnitId"],
|
|
722
|
-
/**
|
|
723
|
-
* The maximum number of retries to connect to a server before,
|
|
724
|
-
* tha connection failed is stated.
|
|
725
|
-
*
|
|
726
|
-
* DEFAULT: 3
|
|
727
|
-
*/
|
|
728
|
-
maxRetries: 1,
|
|
729
|
-
}
|
|
730
|
-
},
|
|
731
|
-
|
|
732
157
|
/**
|
|
733
158
|
* Configuration for the samples accumulator to balance the transfer the size of the Samples
|
|
734
159
|
* prepared to be sent to the server
|
|
@@ -759,75 +184,13 @@ const config = {
|
|
|
759
184
|
};
|
|
760
185
|
```
|
|
761
186
|
|
|
762
|
-
## Examples
|
|
763
|
-
|
|
764
|
-
* [Calculate video tracks Fps](#calculate-video-tracks-fps)
|
|
765
|
-
* [Collect RTT measurements for Peer Connections](#collect-rtt-measurements-for-peer-connections)
|
|
766
|
-
|
|
767
|
-
### Calculate video tracks Fps
|
|
768
|
-
|
|
769
|
-
Assuming you have a configured and running monitor and a collector you added to poll the stats from
|
|
770
|
-
peer connection, here is an example to calculate the frame per sec for tracks.
|
|
771
|
-
|
|
772
|
-
```javascript
|
|
773
|
-
const monitor = //.. defined above
|
|
774
|
-
monitor.onStatsCollected(() => {
|
|
775
|
-
const now = Date.now();
|
|
776
|
-
for (const inboundRtp of monitor.storage.inboundRtps()) {
|
|
777
|
-
const trackId = inboundRtp.getTrackId();
|
|
778
|
-
const SSRC = inboundRtp.getSsrc();
|
|
779
|
-
const traceId = `${trackId}-${SSRC}`;
|
|
780
|
-
// lets extract what we need from the stats for inboundRtp: https://www.w3.org/TR/webrtc-stats/#inboundrtpstats-dict*
|
|
781
|
-
const { framesReceived, kind } = inboundRtp.stats;
|
|
782
|
-
if (kind !== "video") continue;
|
|
783
|
-
const trace = traces.get(traceId);
|
|
784
|
-
if (!trace) {
|
|
785
|
-
traces.set(traceId, {
|
|
786
|
-
framesReceived,
|
|
787
|
-
timestamp: now,
|
|
788
|
-
});
|
|
789
|
-
continue;
|
|
790
|
-
}
|
|
791
|
-
const elapsedTimeInS = (now - trace.timestamp) / 1000;
|
|
792
|
-
const fps = (framesReceived - trace.framesReceived) / elapsedTimeInS;
|
|
793
|
-
const peerConnectionId = inboundRtp.getPeerConnection()?.id;
|
|
794
|
-
trace.framesReceived = framesReceived;
|
|
795
|
-
trace.timestamp = now;
|
|
796
|
-
|
|
797
|
-
console.log(`On peerConnection: ${peerConnectionId}, track ${trackId}, SSRC: ${SSRC} the FPS is ${fps}`);
|
|
798
|
-
}
|
|
799
|
-
});
|
|
800
|
-
```
|
|
801
|
-
|
|
802
|
-
### Collect RTT measurements for peer connections
|
|
803
|
-
|
|
804
|
-
```javascript
|
|
805
|
-
const monitor = //.. defined above
|
|
806
|
-
monitor.onStatsCollected(() => {
|
|
807
|
-
const RTTs = new Map();
|
|
808
|
-
for (const outboundRtp of monitor.storage.outboundRtps()) {
|
|
809
|
-
const remoteInboundRtp = outboundRtp.getRemoteInboundRtp();
|
|
810
|
-
const { roundTripTime } = remoteInboundRtp.stats;
|
|
811
|
-
const peerConnectionId = outboundRtp.getPeerConnection()?.collectorId;
|
|
812
|
-
let measurements = results.get(peerConnectionId);
|
|
813
|
-
if (!measurements) {
|
|
814
|
-
measurements = [];
|
|
815
|
-
RTTs.set(peerConnectionId, measurements);
|
|
816
|
-
}
|
|
817
|
-
measurements.push(roundTripTime);
|
|
818
|
-
}
|
|
819
|
-
// here you have the RTT measurements groupped by peer connections
|
|
820
|
-
console.log(Array.from(RTTs.entries()));
|
|
821
|
-
});
|
|
822
|
-
```
|
|
823
|
-
|
|
824
187
|
## NPM package
|
|
825
188
|
|
|
826
189
|
https://www.npmjs.com/package/@observertc/client-monitor-js
|
|
827
190
|
|
|
828
191
|
## API docs
|
|
829
192
|
|
|
830
|
-
https://observertc.
|
|
193
|
+
https://observertc.org/docs/api/client-monitor-js-v2/
|
|
831
194
|
|
|
832
195
|
## Schemas
|
|
833
196
|
|
|
@@ -837,8 +200,7 @@ https://github.com/observertc/schemas
|
|
|
837
200
|
## Getting Involved
|
|
838
201
|
|
|
839
202
|
Client-monitor is made with the intention to provide an open-source monitoring solution for
|
|
840
|
-
WebRTC developers.
|
|
841
|
-
product with the help of the community. If you are interested in getting involved
|
|
203
|
+
WebRTC developers. If you are interested in getting involved
|
|
842
204
|
please read our [contribution](CONTRIBUTING.md) guideline.
|
|
843
205
|
|
|
844
206
|
## License
|