Skip to content

Commit e4773c1

Browse files
authored
feat: refactor debug fields from strings to objects (#7)
* feat: refactor debug fields from strings to objects * feat: change debug prop name to more relevant * feat: cleanup readme * feat: fix missed frames formula
1 parent 5dab634 commit e4773c1

13 files changed

+192
-83
lines changed

README.md

Lines changed: 73 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,21 @@ Diagnostic tool for WebRTC JS applications that analyzes WebRTC getStats() resul
55

66
## Key features
77

8-
- **Mean opinion score** - calculates [MOS](https://en.wikipedia.org/wiki/Mean_opinion_score) for inbound and outbound network connections that can indicate problems before it even appears.
8+
- **Mean opinion score** - calculates [MOS](https://en.wikipedia.org/wiki/Mean_opinion_score) for inbound and outbound network connections that can indicate a problem before it even appears.
99
- **CPU issues** - indicates possible issues with encoding and decoding media streams.
1010
- **Server issues** - indicates possible server side issues.
1111
- **Fully customizable** - allows to create your own detectors or WebRTC getStats() parsers.
1212

1313

1414
## Installation
15+
1516
`yarn add webrtc-issue-detector`
1617

1718

1819
## Usage
1920

2021
### Getting started
22+
2123
```typescript
2224
import WebRTCIssueDetector from 'webrtc-issue-detector';
2325

@@ -26,7 +28,7 @@ const webRtcIssueDetector = new WebRTCIssueDetector({
2628
onIssues: (issues) => issues.map((issue) => {
2729
console.log('Issues type:', issue.type); // eg. "network"
2830
console.log('Issues reason:', issue.reason); // eg. "outbound-network-throughput"
29-
console.log('Issues reason:', issue.debug); // eg. "packetLoss: 12%, jitter: 230, rtt: 150"
31+
console.log('Issues reason:', issue.statsSample); // eg. "packetLossPct: 12%, avgJitter: 230, rtt: 150"
3032
}),
3133
onNetworkScoresUpdated: (scores) => {
3234
console.log('Inbound network score', scores.inbound); // eg. 3.7
@@ -36,6 +38,9 @@ const webRtcIssueDetector = new WebRTCIssueDetector({
3638

3739
// start collecting getStats() and detecting issues
3840
webRtcIssueDetector.watchNewPeerConnections();
41+
42+
// stop collecting WebRTC stats and issues detection
43+
webRtcIssueDetector.stopWatchingNewPeerConnections();
3944
```
4045

4146
### Configure
@@ -51,7 +56,7 @@ import WebRTCIssueDetector, {
5156
OutboundNetworkIssueDetector,
5257
NetworkMediaSyncIssueDetector,
5358
AvailableOutgoingBitrateIssueDetector,
54-
VideoCodecMismatchDetector,
59+
UnknownVideoDecoderImplementationDetector,
5560
} from 'webrtc-issue-detector';
5661

5762
const widWithDefaultConstructorArgs = new WebRTCIssueDetector();
@@ -67,7 +72,7 @@ const widWithCustomConstructorArgs = new WebRTCIssueDetector({
6772
new OutboundNetworkIssueDetector(),
6873
new NetworkMediaSyncIssueDetector(),
6974
new AvailableOutgoingBitrateIssueDetector(),
70-
new VideoCodecMismatchDetector(),
75+
new UnknownVideoDecoderImplementationDetector(),
7176
],
7277
getStatsInterval: 10_000, // set custom stats parsing interval
7378
onIssues: (payload: IssueDetectorResult) => {
@@ -87,105 +92,143 @@ const widWithCustomConstructorArgs = new WebRTCIssueDetector({
8792
### AvailableOutgoingBitrateIssueDetector
8893
Detects issues with outgoing network connection.
8994
```js
90-
const issue = {
95+
const exampleIssue = {
9196
type: 'network',
9297
reason: 'outbound-network-throughput',
93-
debug: '...',
98+
statsSample: {
99+
availableOutgoingBitrate: 1234,
100+
videoStreamsTotalBitrate: 1234,
101+
audioStreamsTotalTargetBitrate: 1234,
102+
},
94103
}
95104
```
96105

97106
### FramesDroppedIssueDetector
98107
Detects issues with decoder.
99108
```js
100-
const issue = {
109+
const exampleIssue = {
101110
type: 'cpu',
102111
reason: 'decoder-cpu-throttling',
103-
debug: '...',
112+
statsSample: {
113+
deltaFramesDropped: 100,
114+
deltaFramesReceived: 1000,
115+
deltaFramesDecoded: 900,
116+
framesDroppedPct: 10,
117+
},
118+
ssrc: 1234,
104119
}
105120
```
106121

107122
### FramesEncodedSentIssueDetector
108123
Detects issues with outbound network throughput.
109124
```js
110-
const issue = {
125+
const exampleIssue = {
111126
type: 'network',
112127
reason: 'outbound-network-throughput',
113-
debug: '...',
128+
statsSample: {
129+
deltaFramesSent: 900,
130+
deltaFramesEncoded: 1000,
131+
missedFramesPct: 10,
132+
},
133+
ssrc: 1234,
114134
}
115135
```
116136

117137
### InboundNetworkIssueDetector
118138
Detects issues with inbound network connection.
119139
```js
120-
const issue = {
140+
const exampleIssue = {
121141
type: 'network',
122142
reason: 'inbound-network-quality' | 'inbound-network-media-latency' | 'network-media-sync-failure',
123143
iceCandidate: 'ice-candidate-id',
124-
debug: '...',
144+
statsSample: {
145+
rtt: 1234,
146+
packetLossPct: 1234,
147+
avgJitter: 1234,
148+
avgJitterBufferDelay: 1234,
149+
},
125150
}
126151
```
127152

128153
Also can detect server side issues if there is high RTT and jitter is ok.
129154
```js
130-
const issue = {
155+
const exampleIssue = {
131156
type: 'server',
132157
reason: 'server-issue',
133158
iceCandidate: 'ice-candidate-id',
134-
debug: '...',
159+
statsSample: {
160+
rtt: 1234,
161+
packetLossPct: 1234,
162+
avgJitter: 1234,
163+
avgJitterBufferDelay: 1234,
164+
},
135165
}
136166
```
137167

138168
### NetworkMediaSyncIssueDetector
139169
Detects issues with audio synchronization.
140170
```js
141-
const issue = {
171+
const exampleIssue = {
142172
type: 'network',
143173
reason: 'network-media-sync-failure',
144-
ssrc: '...',
145-
debug: '...',
174+
ssrc: 1234,
175+
statsSample: {
176+
correctedSamplesPct: 15,
177+
},
146178
}
147179
```
148180

149181
### OutboundNetworkIssueDetector
150182
Detects issues with outbound network connection.
151183
```js
152-
const issue = {
184+
const exampleIssue = {
153185
type: 'network',
154186
reason: 'outbound-network-quality' | 'outbound-network-media-latency',
155187
iceCandidate: 'ice-candidate-id',
156-
debug: '...',
188+
statsSample: {
189+
rtt: 1234,
190+
avgJitter: 1234,
191+
packetLossPct: 1234,
192+
},
157193
}
158194
```
159195

160196
### QualityLimitationsIssueDetector
161197
Detects issues with encoder and outbound network. Based on native qualityLimitationReason.
162198
```js
163-
const issue = {
199+
const exampleIssue = {
164200
type: 'cpu',
165201
reason: 'encoder-cpu-throttling',
166-
ssrc: '...',
167-
debug: '...',
202+
ssrc: 1234,
203+
statsSample: {
204+
qualityLimitationReason: 'cpu',
205+
},
168206
}
169207
```
170208

171209
```js
172-
const issue = {
210+
const exampleIssue = {
173211
type: 'network',
174212
reason: 'outbound-network-throughput',
175-
ssrc: '...',
176-
debug: '...',
213+
ssrc: 1234,
214+
statsSample: {
215+
qualityLimitationReason: 'bandwidth',
216+
},
177217
}
178218
```
179219

180220
### VideoCodecMismatchDetector
181221
Detects issues with decoding stream.
182222
```js
183-
const issue = {
223+
const exampleIssue = {
184224
type: 'stream',
185-
reason: 'codec-mismatch',
186-
ssrc: '...',
187-
trackIdentifier: '...',
188-
debug: '...',
225+
reason: 'unknown-video-decoder',
226+
ssrc: 1234,
227+
trackIdentifier: 'some-track-id',
228+
statsSample: {
229+
mimeType: 'video/vp9',
230+
decoderImplementation: 'unknown'
231+
},
189232
}
190233
```
191234

src/WebRTCIssueDetector.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
NetworkMediaSyncIssueDetector,
2323
OutboundNetworkIssueDetector,
2424
QualityLimitationsIssueDetector,
25-
VideoCodecMismatchDetector,
25+
UnknownVideoDecoderImplementationDetector,
2626
} from './detectors';
2727
import { CompositeRTCStatsParser, RTCStatsParser } from './parser';
2828
import createLogger from './utils/logger';
@@ -62,7 +62,7 @@ class WebRTCIssueDetector {
6262
new OutboundNetworkIssueDetector(),
6363
new NetworkMediaSyncIssueDetector(),
6464
new AvailableOutgoingBitrateIssueDetector(),
65-
new VideoCodecMismatchDetector(),
65+
new UnknownVideoDecoderImplementationDetector(),
6666
];
6767

6868
this.networkScoresCalculator = params.networkScoresCalculator ?? new DefaultNetworkScoresCalculator();
@@ -98,7 +98,8 @@ class WebRTCIssueDetector {
9898

9999
public watchNewPeerConnections(): void {
100100
if (this.#running) {
101-
throw new Error('WebRTCIssueDetector is already started');
101+
this.logger.warn('WebRTCIssueDetector is already started. Skip processing');
102+
return;
102103
}
103104

104105
this.logger.info('Start watching peer connections');
@@ -109,7 +110,8 @@ class WebRTCIssueDetector {
109110

110111
public stopWatchingNewPeerConnections(): void {
111112
if (!this.#running) {
112-
throw new Error('WebRTCIssueDetector is already stopped');
113+
this.logger.warn('WebRTCIssueDetector is already stopped. Skip processing');
114+
return;
113115
}
114116

115117
this.logger.info('Stop watching peer connections');

src/detectors/AvailableOutgoingBitrateIssueDetector.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,27 @@ class AvailableOutgoingBitrateIssueDetector extends BaseIssueDetector {
3737
return issues;
3838
}
3939

40+
const statsSample = {
41+
availableOutgoingBitrate,
42+
videoStreamsTotalBitrate,
43+
audioStreamsTotalTargetBitrate,
44+
};
45+
4046
if (audioStreamsTotalTargetBitrate > availableOutgoingBitrate) {
4147
issues.push({
48+
statsSample,
4249
type: IssueType.Network,
4350
reason: IssueReason.OutboundNetworkThroughput,
44-
debug: `availableOutgoingBitrate: ${availableOutgoingBitrate}`
45-
+ `, audioStreamsTotalTargetBitrate: ${audioStreamsTotalTargetBitrate}`,
4651
});
4752

4853
return issues;
4954
}
5055

5156
if (videoStreamsTotalBitrate > 0 && availableOutgoingBitrate < this.#availableOutgoingBitrateThreshold) {
5257
issues.push({
58+
statsSample,
5359
type: IssueType.Network,
5460
reason: IssueReason.OutboundNetworkThroughput,
55-
debug: `availableOutgoingBitrate: ${availableOutgoingBitrate}`
56-
+ `, videoStreamsTotalBitrate: ${videoStreamsTotalBitrate}`,
5761
});
5862

5963
return issues;

src/detectors/FramesDroppedIssueDetector.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,27 @@ class FramesDroppedIssueDetector extends BaseIssueDetector {
4848
const deltaFramesReceived = streamStats.framesReceived - previousStreamStats.framesReceived;
4949
const deltaFramesDecoded = streamStats.framesDecoded - previousStreamStats.framesDecoded;
5050
const deltaFramesDropped = streamStats.framesDropped - previousStreamStats.framesDropped;
51-
if (deltaFramesReceived === 0 && deltaFramesDecoded === 0) {
51+
const framesDropped = deltaFramesDropped / deltaFramesReceived;
52+
53+
if (deltaFramesReceived === 0 || deltaFramesDecoded === 0) {
5254
// looks like stream is stopped, skip checking framesDropped
5355
return;
5456
}
5557

56-
const framesDropped = deltaFramesDropped / deltaFramesReceived;
58+
const statsSample = {
59+
deltaFramesDropped,
60+
deltaFramesReceived,
61+
deltaFramesDecoded,
62+
framesDroppedPct: Math.round(framesDropped * 100),
63+
};
64+
5765
if (framesDropped >= this.#framesDroppedThreshold) {
5866
// more than half of the received frames were dropped
5967
issues.push({
68+
statsSample,
6069
type: IssueType.CPU,
6170
reason: IssueReason.DecoderCPUThrottling,
6271
ssrc: streamStats.ssrc,
63-
debug: `framesDropped: ${Math.round(framesDropped * 100)}`
64-
+ ` , deltaFramesDropped: ${deltaFramesDropped}, deltaFramesReceived: ${deltaFramesReceived}`,
6572
});
6673
}
6774
});

src/detectors/FramesEncodedSentIssueDetector.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class FramesEncodedSentIssueDetector extends BaseIssueDetector {
4848

4949
const deltaFramesEncoded = streamStats.framesEncoded - previousStreamStats.framesEncoded;
5050
const deltaFramesSent = streamStats.framesSent - previousStreamStats.framesSent;
51+
const missedFrames = 1 - deltaFramesSent / deltaFramesEncoded;
5152

5253
if (deltaFramesEncoded === 0) {
5354
// stream is paused
@@ -59,13 +60,18 @@ class FramesEncodedSentIssueDetector extends BaseIssueDetector {
5960
return;
6061
}
6162

62-
const missedFrames = deltaFramesSent / deltaFramesEncoded;
63+
const statsSample = {
64+
deltaFramesSent,
65+
deltaFramesEncoded,
66+
missedFramesPct: Math.round(missedFrames * 100),
67+
};
68+
6369
if (missedFrames >= this.#missedFramesThreshold) {
6470
issues.push({
71+
statsSample,
6572
type: IssueType.Network,
6673
reason: IssueReason.OutboundNetworkThroughput,
6774
ssrc: streamStats.ssrc,
68-
debug: `missedFrames: ${Math.round(missedFrames * 100)}%`,
6975
});
7076
}
7177
});

0 commit comments

Comments
 (0)