Skip to content

Commit 21442fe

Browse files
committed
feat: First commit
0 parents  commit 21442fe

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+5781
-0
lines changed

.eslintrc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"root": true,
3+
"extends": [
4+
"airbnb-base",
5+
"airbnb-typescript/base"
6+
],
7+
"parserOptions": {
8+
"project": "./tsconfig.json"
9+
},
10+
"rules": {
11+
"@typescript-eslint/no-floating-promises": "off",
12+
"@typescript-eslint/no-unsafe-member-access": "off",
13+
"@typescript-eslint/no-unsafe-call": "off",
14+
"@typescript-eslint/no-unsafe-assignment": "off",
15+
"no-underscore-dangle": "off",
16+
"max-len": ["error", { "code": 120 }],
17+
"import/extensions": "off",
18+
"import/no-cycle": "off"
19+
}
20+
}

.github/workflows/release.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
workflow_dispatch:
7+
8+
jobs:
9+
release:
10+
runs-on: ubuntu-latest
11+
name: release
12+
steps:
13+
- uses: actions/checkout@v2
14+
with:
15+
fetch-depth: 0
16+
persist-credentials: false
17+
18+
- name: Use Node.js
19+
uses: actions/setup-node@v2
20+
with:
21+
node-version: '14'
22+
cache: 'yarn'
23+
24+
- name: Install packages
25+
run: yarn
26+
27+
- name: Lint
28+
run: yarn lint
29+
30+
- name: Test
31+
run: yarn test
32+
33+
- name: Build
34+
run: yarn build
35+
36+
- name: Release
37+
env:
38+
GITHUB_TOKEN: ${{ secrets.VL_BOT_TOKEN }}
39+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
40+
GIT_AUTHOR_NAME: vlprojects-bot
41+
GIT_AUTHOR_EMAIL: info@vlprojects.pro
42+
GIT_COMMITTER_NAME: vlprojects-bot
43+
GIT_COMMITTER_EMAIL: info@vlprojects.pro
44+
run: npx semantic-release

.gitignore

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
8+
# Runtime data
9+
pids
10+
*.pid
11+
*.seed
12+
*.pid.lock
13+
14+
# Directory for instrumented libs generated by jscoverage/JSCover
15+
lib-cov
16+
17+
# Coverage directory used by tools like istanbul
18+
coverage
19+
20+
# nyc test coverage
21+
.nyc_output
22+
23+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24+
.grunt
25+
26+
# Bower dependency directory (https://bower.io/)
27+
bower_components
28+
29+
# node-waf configuration
30+
.lock-wscript
31+
32+
# Compiled binary addons (https://nodejs.org/api/addons.html)
33+
build/Release
34+
35+
# Dependency directories
36+
node_modules/
37+
jspm_packages/
38+
39+
# TypeScript v1 declaration files
40+
typings/
41+
42+
# Optional npm cache directory
43+
.npm
44+
45+
# Optional eslint cache
46+
.eslintcache
47+
48+
# Optional REPL history
49+
.node_repl_history
50+
51+
# Output of 'npm pack'
52+
*.tgz
53+
54+
# Yarn Integrity file
55+
.yarn-integrity
56+
57+
# dotenv environment variables file
58+
.env
59+
60+
# next.js build output
61+
.next
62+
63+
# Typescript build
64+
dist/
65+
66+
# Package lock file
67+
package-lock.json

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lib/
2+
test/

LICENSE

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright 2022-present VLProjects & Collaborators
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8+

README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# webrtc-issue-detector
2+
WebRTC diagnostic tool that detects issues with network or user devices
3+
4+
----
5+
6+
## Installation
7+
`yarn add webrtc-issue-detector`
8+
9+
10+
## Usage
11+
### Import
12+
```typescript
13+
import WebRTCIssueDetector from 'webrtc-issue-detector';
14+
15+
return new WebRTCIssueDetector({
16+
onIssues: (issues) => console.log('Issues detected:', issues),
17+
onNetworkScoresUpdated: (scores) => console.log('Network scores updated:', scores),
18+
});
19+
```
20+
21+
### Configure
22+
```typescript
23+
import WebRTCIssueDetector, {
24+
QualityLimitationsIssueDetector,
25+
FramesDroppedIssueDetector,
26+
FramesEncodedSentIssueDetector,
27+
InboundNetworkIssueDetector,
28+
OutboundNetworkIssueDetector,
29+
NetworkMediaSyncIssueDetector,
30+
AvailableOutgoingBitrateIssueDetector,
31+
VideoCodecMismatchDetector,
32+
CompositeRTCStatsParser,
33+
WebRTCIssueEmitter,
34+
NetworkScoresCalculator,
35+
PeriodicWebRTCStatsReporter,
36+
RTCStatsParser,
37+
} from 'webrtc-issue-detector';
38+
39+
new WebRTCIssueDetector({
40+
issueEmitter: new WebRTCIssueEmitter(),
41+
networkScoresCalculator: new NetworkScoresCalculator(),
42+
detectors: [
43+
new QualityLimitationsIssueDetector(),
44+
new FramesDroppedIssueDetector(),
45+
new FramesEncodedSentIssueDetector(),
46+
new InboundNetworkIssueDetector(),
47+
new OutboundNetworkIssueDetector(),
48+
new NetworkMediaSyncIssueDetector(),
49+
new AvailableOutgoingBitrateIssueDetector(),
50+
new VideoCodecMismatchDetector(),
51+
],
52+
statsReporter: new PeriodicWebRTCStatsReporter({
53+
compositeStatsParser,
54+
getStatsInterval: 5000,
55+
}),
56+
onIssues: params.onIssues,
57+
onNetworkScoresUpdated: params.onNetworkScoresUpdated,
58+
ignoreSSRCList: params.ignoreSSRCList,
59+
compositeStatsParser,
60+
logger,
61+
});
62+
```
63+
64+
65+
## Test
66+
`yarn test`
67+
68+
69+
## Build
70+
`yarn build`

package.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"name": "webrtc-issue-detector",
3+
"version": "1.0.8",
4+
"description": "WebRTC diagnostic tool that detects issues with network or user devices",
5+
"main": "dist/index.js",
6+
"types": "dist/index.d.ts",
7+
"typings": "dist/index",
8+
"repository": "git@github.com:VLprojects/webrtc-issue-detector.git",
9+
"author": "Roman Kuzakov <roman.kuzakov@gmail.com>",
10+
"license": "MIT",
11+
"private": false,
12+
"homepage": "https://github.com/VLprojects/webrtc-issue-detector#readme",
13+
"keywords": [
14+
"webrtc"
15+
],
16+
"files": [
17+
"dist/"
18+
],
19+
"scripts": {
20+
"build": "tsc",
21+
"lint": "eslint ./src",
22+
"test": "NODE_ENV=test mocha --config test/utils/runners/mocha/.mocharc.js"
23+
},
24+
"devDependencies": {
25+
"@types/chai": "^4.3.3",
26+
"@types/chai-as-promised": "^7.1.5",
27+
"@types/chai-subset": "^1.3.3",
28+
"@types/faker": "^6.6.9",
29+
"@types/mocha": "^9.1.1",
30+
"@types/node": "12",
31+
"@types/sinon": "^10.0.13",
32+
"@types/sinon-chai": "^3.2.8",
33+
"@typescript-eslint/eslint-plugin": "^5.33.0",
34+
"@typescript-eslint/parser": "^5.33.0",
35+
"chai": "^4.3.6",
36+
"chai-as-promised": "^7.1.1",
37+
"chai-subset": "^1.6.0",
38+
"eslint": "7.32.0",
39+
"eslint-config-airbnb-typescript": "^14.0.1",
40+
"eslint-config-prettier": "^8.3.0",
41+
"eslint-plugin-import": "^2.26.0",
42+
"eslint-plugin-jsx-a11y": "^6.6.1",
43+
"faker": "^5.5.3",
44+
"mocha": "^10.0.0",
45+
"sinon": "^14.0.0",
46+
"sinon-chai": "^3.7.0",
47+
"ts-node": "^10.9.1",
48+
"typescript": "^4.7.4"
49+
}
50+
}

src/NetworkScoresCalculator.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/* eslint-disable class-methods-use-this */
2+
import {
3+
NetworkScore,
4+
NetworkScores,
5+
NetworkScoresCalculator as INetworkScoresCalculator,
6+
WebRTCStatsParsed,
7+
} from './types';
8+
9+
class NetworkScoresCalculator implements INetworkScoresCalculator {
10+
#lastProcessedStats: { [connectionId: string]: WebRTCStatsParsed } = {};
11+
12+
calculate(data: WebRTCStatsParsed): NetworkScores {
13+
const outbound = this.calcucateOutboundScore(data);
14+
const inbound = this.calculateInboundScore(data);
15+
this.#lastProcessedStats[data.connection.id] = data;
16+
return { outbound, inbound };
17+
}
18+
19+
private calcucateOutboundScore(data: WebRTCStatsParsed): NetworkScore | undefined {
20+
const remoteInboundRTPStreamsStats = [
21+
...data.remote?.audio.inbound || [],
22+
...data.remote?.video.inbound || [],
23+
];
24+
25+
if (!remoteInboundRTPStreamsStats.length) {
26+
return undefined;
27+
}
28+
29+
const previousStats = this.#lastProcessedStats[data.connection.id];
30+
if (!previousStats) {
31+
return undefined;
32+
}
33+
34+
const previousRemoteInboundRTPStreamsStats = [
35+
...previousStats.remote?.audio.inbound || [],
36+
...previousStats.remote?.video.inbound || [],
37+
];
38+
39+
const { packetsSent } = data.connection;
40+
const lastPacketsSent = previousStats.connection.packetsSent;
41+
42+
const rtpNetworkStats = remoteInboundRTPStreamsStats.reduce((stats, currentStreamStats) => {
43+
const previousStreamStats = previousRemoteInboundRTPStreamsStats
44+
.find((stream) => stream.ssrc === currentStreamStats.ssrc);
45+
46+
return {
47+
sumJitter: stats.sumJitter + currentStreamStats.jitter,
48+
packetsLost: stats.packetsLost + currentStreamStats.packetsLost,
49+
lastPacketsLost: stats.lastPacketsLost + (previousStreamStats?.packetsLost || 0),
50+
};
51+
}, {
52+
sumJitter: 0,
53+
packetsLost: 0,
54+
lastPacketsLost: 0,
55+
});
56+
57+
const rtt = (1e3 * data.connection.currentRoundTripTime) || 0;
58+
const { sumJitter } = rtpNetworkStats;
59+
const avgJitter = sumJitter / remoteInboundRTPStreamsStats.length;
60+
61+
const deltaPacketSent = packetsSent - lastPacketsSent;
62+
const deltaPacketLost = rtpNetworkStats.packetsLost - rtpNetworkStats.lastPacketsLost;
63+
64+
const packetsLoss = deltaPacketSent && deltaPacketLost
65+
? Math.round((deltaPacketLost * 100) / (deltaPacketSent + deltaPacketLost))
66+
: 0;
67+
68+
return this.calculateMOS({ avgJitter, rtt, packetsLoss });
69+
}
70+
71+
private calculateInboundScore(data: WebRTCStatsParsed): NetworkScore | undefined {
72+
const inboundRTPStreamsStats = [...data.audio?.inbound, ...data.video?.inbound];
73+
if (!inboundRTPStreamsStats.length) {
74+
return undefined;
75+
}
76+
77+
const previousStats = this.#lastProcessedStats[data.connection.id];
78+
if (!previousStats) {
79+
return undefined;
80+
}
81+
82+
const previousInboundStreamStats = [...previousStats.video?.inbound, ...previousStats.audio?.inbound];
83+
const { packetsReceived } = data.connection;
84+
const lastPacketsReceived = previousStats.connection.packetsReceived;
85+
86+
const rtpNetworkStats = inboundRTPStreamsStats.reduce((stats, currentStreamStats) => {
87+
const previousStreamStats = previousInboundStreamStats.find((stream) => stream.ssrc === currentStreamStats.ssrc);
88+
return {
89+
sumJitter: stats.sumJitter + currentStreamStats.jitter,
90+
packetsLost: stats.packetsLost + currentStreamStats.packetsLost,
91+
lastPacketsLost: stats.lastPacketsLost + (previousStreamStats?.packetsLost || 0),
92+
};
93+
}, {
94+
sumJitter: 0,
95+
packetsLost: 0,
96+
lastPacketsLost: 0,
97+
});
98+
99+
const rtt = (1e3 * data.connection.currentRoundTripTime) || 0;
100+
const { sumJitter } = rtpNetworkStats;
101+
const avgJitter = sumJitter / inboundRTPStreamsStats.length;
102+
103+
const deltaPacketReceived = packetsReceived - lastPacketsReceived;
104+
const deltaPacketLost = rtpNetworkStats.packetsLost - rtpNetworkStats.lastPacketsLost;
105+
106+
const packetsLoss = deltaPacketReceived && deltaPacketLost
107+
? Math.round((deltaPacketLost * 100) / (deltaPacketReceived + deltaPacketLost))
108+
: 0;
109+
110+
return this.calculateMOS({ avgJitter, rtt, packetsLoss });
111+
}
112+
113+
private calculateMOS(
114+
{ avgJitter, rtt, packetsLoss }:
115+
{ avgJitter: number, rtt: number, packetsLoss: number },
116+
): number {
117+
const effectiveLatency = rtt + (avgJitter * 2) + 10;
118+
let rFactor = effectiveLatency < 160
119+
? 93.2 - (effectiveLatency / 40)
120+
: 93.2 - (effectiveLatency / 120) - 10;
121+
rFactor -= (packetsLoss * 2.5);
122+
return 1 + (0.035) * rFactor + (0.000007) * rFactor * (rFactor - 60) * (100 - rFactor);
123+
}
124+
}
125+
126+
export default NetworkScoresCalculator;

0 commit comments

Comments
 (0)