@pexip/peer-connection-stats 17.12.0 → 18.1.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/CHANGELOG.md +42 -0
- package/dist/statsCollector.d.ts +42 -34
- package/dist/statsCollector.js +45 -2
- package/dist/statsCollector.types.d.ts +14 -3
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/typeGuards.d.ts +2 -0
- package/dist/typeGuards.js +1 -0
- package/package.json +20 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 18.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- fdfb189: Adds new stats emitted via `onRtcStats` signal.
|
|
8
|
+
- `timeToCaptureStats` to any stats: time taken in ms to capture stats
|
|
9
|
+
- `fpsVolatility` to `VideoMetrics`: <4% is good, <8% can be reasonable. Src:
|
|
10
|
+
https://www.youtube.com/watch?v=jlW1VdTjkbk
|
|
11
|
+
- `averageDecodeTime` to `InboundVideoMetrics`: decode time per frame
|
|
12
|
+
- `averageEncodeTime` to `OutboundVideoMetrics`: encode time per frame
|
|
13
|
+
|
|
14
|
+
## 18.0.0
|
|
15
|
+
|
|
16
|
+
### Major Changes
|
|
17
|
+
|
|
18
|
+
- 2bfd9e1: Adds esm entry points.
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- 3555d9d: Adds `build:sourcemap` script.
|
|
23
|
+
- ed53b05: Upgrade Typescript
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- e76a15e: Migrate to biome linter
|
|
28
|
+
- Updated dependencies [2bfd9e1]
|
|
29
|
+
- Updated dependencies [4fe77de]
|
|
30
|
+
- Updated dependencies [2bbff64]
|
|
31
|
+
- Updated dependencies [db3a370]
|
|
32
|
+
- Updated dependencies [4ea0492]
|
|
33
|
+
- Updated dependencies [3555d9d]
|
|
34
|
+
- Updated dependencies [1601985]
|
|
35
|
+
- Updated dependencies [3cb5aee]
|
|
36
|
+
- Updated dependencies [fa06b8b]
|
|
37
|
+
- Updated dependencies [84956b8]
|
|
38
|
+
- Updated dependencies [ed53b05]
|
|
39
|
+
- Updated dependencies [941363c]
|
|
40
|
+
- Updated dependencies [d681fd3]
|
|
41
|
+
- Updated dependencies [e76a15e]
|
|
42
|
+
- @pexip/utils@17.0.0
|
|
43
|
+
- @pexip/signal@16.8.0
|
|
44
|
+
|
|
3
45
|
## 17.12.0
|
|
4
46
|
|
|
5
47
|
### Patch Changes
|
package/dist/statsCollector.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AnyStats, CacheStats, CallQualityStats, InboundAudio, InboundAudioMetrics, InboundVideo, InboundVideoMetrics, Metrics, NormalizedRTCStats, OutboundAudio, OutboundAudioMetrics, OutboundVideo, OutboundVideoMetrics, StatsCollector, StatsCollectorOptions } from './statsCollector.types';
|
|
2
2
|
import { Quality } from './statsCollector.types';
|
|
3
3
|
export declare const STATS_SIZE = 60;
|
|
4
|
+
export declare const FPS_STATS_SIZE = 8;
|
|
4
5
|
/**
|
|
5
6
|
* The WebRTC stats are given to us as a flat structure (an array of objects
|
|
6
7
|
* that contain id-fields pointing to other objects in the same array).
|
|
@@ -85,52 +86,59 @@ export declare const getQuality: (stats: CallQualityStats) => {
|
|
|
85
86
|
export declare const addDeltaStats: (newStats: NormalizedRTCStats, cache: CacheStats) => readonly [{
|
|
86
87
|
type: "inbound-rtp" | "outbound-rtp";
|
|
87
88
|
kind: "audio" | "video";
|
|
88
|
-
bitrate?: number
|
|
89
|
-
bytesTransmitted?: number
|
|
90
|
-
codec?: string
|
|
91
|
-
jitter?: number
|
|
89
|
+
bitrate?: number;
|
|
90
|
+
bytesTransmitted?: number;
|
|
91
|
+
codec?: string;
|
|
92
|
+
jitter?: number;
|
|
92
93
|
packetsLost: number;
|
|
93
94
|
packetsTransmitted: number;
|
|
94
|
-
recentPercentageLost?: number
|
|
95
|
-
roundTripTime?: number
|
|
96
|
-
timestamp?: number
|
|
97
|
-
totalPercentageLost?: number
|
|
95
|
+
recentPercentageLost?: number;
|
|
96
|
+
roundTripTime?: number;
|
|
97
|
+
timestamp?: number;
|
|
98
|
+
totalPercentageLost?: number;
|
|
99
|
+
timeToCaptureStats?: number;
|
|
98
100
|
} | {
|
|
99
|
-
framesPerSecond?: number
|
|
100
|
-
resolution?: string
|
|
101
|
-
resolutionHeight?: number
|
|
102
|
-
resolutionWidth?: number
|
|
101
|
+
framesPerSecond?: number;
|
|
102
|
+
resolution?: string;
|
|
103
|
+
resolutionHeight?: number;
|
|
104
|
+
resolutionWidth?: number;
|
|
105
|
+
fpsVolatility?: number;
|
|
103
106
|
type: "inbound-rtp" | "outbound-rtp";
|
|
104
107
|
kind: "audio" | "video";
|
|
105
|
-
bitrate?: number
|
|
106
|
-
bytesTransmitted?: number
|
|
107
|
-
codec?: string
|
|
108
|
-
jitter?: number
|
|
108
|
+
bitrate?: number;
|
|
109
|
+
bytesTransmitted?: number;
|
|
110
|
+
codec?: string;
|
|
111
|
+
jitter?: number;
|
|
109
112
|
packetsLost: number;
|
|
110
113
|
packetsTransmitted: number;
|
|
111
|
-
recentPercentageLost?: number
|
|
112
|
-
roundTripTime?: number
|
|
113
|
-
timestamp?: number
|
|
114
|
-
totalPercentageLost?: number
|
|
114
|
+
recentPercentageLost?: number;
|
|
115
|
+
roundTripTime?: number;
|
|
116
|
+
timestamp?: number;
|
|
117
|
+
totalPercentageLost?: number;
|
|
118
|
+
timeToCaptureStats?: number;
|
|
119
|
+
averageDecodeTime?: number;
|
|
115
120
|
} | {
|
|
116
|
-
framesPerSecond?: number
|
|
117
|
-
resolution?: string
|
|
118
|
-
resolutionHeight?: number
|
|
119
|
-
resolutionWidth?: number
|
|
121
|
+
framesPerSecond?: number;
|
|
122
|
+
resolution?: string;
|
|
123
|
+
resolutionHeight?: number;
|
|
124
|
+
resolutionWidth?: number;
|
|
125
|
+
fpsVolatility?: number;
|
|
120
126
|
type: "inbound-rtp" | "outbound-rtp";
|
|
121
127
|
kind: "audio" | "video";
|
|
122
|
-
bitrate?: number
|
|
123
|
-
bytesTransmitted?: number
|
|
124
|
-
codec?: string
|
|
125
|
-
jitter?: number
|
|
128
|
+
bitrate?: number;
|
|
129
|
+
bytesTransmitted?: number;
|
|
130
|
+
codec?: string;
|
|
131
|
+
jitter?: number;
|
|
126
132
|
packetsLost: number;
|
|
127
133
|
packetsTransmitted: number;
|
|
128
|
-
recentPercentageLost?: number
|
|
129
|
-
roundTripTime?: number
|
|
130
|
-
timestamp?: number
|
|
131
|
-
totalPercentageLost?: number
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
recentPercentageLost?: number;
|
|
135
|
+
roundTripTime?: number;
|
|
136
|
+
timestamp?: number;
|
|
137
|
+
totalPercentageLost?: number;
|
|
138
|
+
timeToCaptureStats?: number;
|
|
139
|
+
totalPacketSendDelay?: number;
|
|
140
|
+
averagePacketSendDelay?: number;
|
|
141
|
+
averageEncodeTime?: number;
|
|
134
142
|
}, Quality, CallQualityStats];
|
|
135
143
|
export declare const calculateQuality: (stats: number | [number, number]) => Quality;
|
|
136
144
|
/**
|
package/dist/statsCollector.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Quality } from './statsCollector.types';
|
|
2
|
+
import { isVideoMetrics } from './typeGuards';
|
|
2
3
|
export const STATS_SIZE = 60;
|
|
4
|
+
export const FPS_STATS_SIZE = 8;
|
|
3
5
|
/**
|
|
4
6
|
* The WebRTC stats are given to us as a flat structure (an array of objects
|
|
5
7
|
* that contain id-fields pointing to other objects in the same array).
|
|
@@ -178,6 +180,9 @@ export const inboundVideo = (statsData) => {
|
|
|
178
180
|
framesPerSecond: statsData.framerateMean ?? statsData.framesPerSecond,
|
|
179
181
|
roundTripTime: statsData.transport?.selectedCandidatePair?.currentRoundTripTime,
|
|
180
182
|
totalPercentageLost: totalPackets && packetsLost / totalPackets,
|
|
183
|
+
averageDecodeTime: statsData.framesDecoded && statsData.totalDecodeTime
|
|
184
|
+
? statsData.totalDecodeTime / statsData.framesDecoded
|
|
185
|
+
: 0,
|
|
181
186
|
};
|
|
182
187
|
};
|
|
183
188
|
/**
|
|
@@ -216,6 +221,9 @@ export const outboundVideo = (statsData) => {
|
|
|
216
221
|
framesPerSecond: statsData.framerateMean ?? statsData.framesPerSecond,
|
|
217
222
|
roundTripTime,
|
|
218
223
|
totalPercentageLost: totalPackets && packetsLost / totalPackets,
|
|
224
|
+
averageEncodeTime: statsData.framesEncoded && statsData.totalEncodeTime
|
|
225
|
+
? statsData.totalEncodeTime / statsData.framesEncoded
|
|
226
|
+
: 0,
|
|
219
227
|
};
|
|
220
228
|
};
|
|
221
229
|
const isInbound = (entry) => entry.type === 'inbound-rtp';
|
|
@@ -306,6 +314,15 @@ const recordCallPacketsStats = (oldStats, newStats, callPacketsStats) => {
|
|
|
306
314
|
}
|
|
307
315
|
return callPacketsStats;
|
|
308
316
|
};
|
|
317
|
+
const recordFPSStats = (fps, fpsStats) => {
|
|
318
|
+
if (fps) {
|
|
319
|
+
if (!fpsStats) {
|
|
320
|
+
fpsStats = [];
|
|
321
|
+
}
|
|
322
|
+
removeObsolete(fpsStats, FPS_STATS_SIZE);
|
|
323
|
+
fpsStats.unshift(fps);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
309
326
|
const addAudioQualityMetric = (metric, data) => {
|
|
310
327
|
removeObsolete(metric);
|
|
311
328
|
metric.unshift(data);
|
|
@@ -374,10 +391,19 @@ export const addDeltaStats = (newStats, cache) => {
|
|
|
374
391
|
...newStats,
|
|
375
392
|
};
|
|
376
393
|
const callPacketsStats = recordCallPacketsStats(oldStats, newStats, cache.callPacketsStats);
|
|
394
|
+
if (isVideoMetrics(newStats)) {
|
|
395
|
+
recordFPSStats(newStats.framesPerSecond, cache.fpsStats);
|
|
396
|
+
}
|
|
377
397
|
const metrics = [
|
|
378
|
-
[
|
|
398
|
+
[
|
|
399
|
+
newStats,
|
|
400
|
+
oldStats,
|
|
401
|
+
deltaStats,
|
|
402
|
+
callPacketsStats ?? [],
|
|
403
|
+
cache.fpsStats,
|
|
404
|
+
],
|
|
379
405
|
];
|
|
380
|
-
metrics.forEach(([newMetrics, oldMetrics, deltaMetrics, packets]) => {
|
|
406
|
+
metrics.forEach(([newMetrics, oldMetrics, deltaMetrics, packets, fpsStats]) => {
|
|
381
407
|
if (newMetrics?.bytesTransmitted &&
|
|
382
408
|
newMetrics?.timestamp &&
|
|
383
409
|
oldMetrics?.bytesTransmitted &&
|
|
@@ -391,6 +417,7 @@ export const addDeltaStats = (newStats, cache) => {
|
|
|
391
417
|
}
|
|
392
418
|
}
|
|
393
419
|
if (deltaMetrics) {
|
|
420
|
+
// packet stats
|
|
394
421
|
const [recentPacketsLost, recentTotalPackets] = packets.reduce((acc, [lost, total]) => {
|
|
395
422
|
acc[0] += lost;
|
|
396
423
|
acc[1] += total;
|
|
@@ -400,6 +427,18 @@ export const addDeltaStats = (newStats, cache) => {
|
|
|
400
427
|
recentTotalPackets === 0
|
|
401
428
|
? 0
|
|
402
429
|
: recentPacketsLost / recentTotalPackets;
|
|
430
|
+
// fps stats
|
|
431
|
+
if (isVideoMetrics(deltaMetrics)) {
|
|
432
|
+
if (fpsStats.length === 0) {
|
|
433
|
+
deltaMetrics.fpsVolatility = 0;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const fpsSum = fpsStats.reduce((acc, curr) => acc + curr, 0);
|
|
437
|
+
const meanFps = Math.round(fpsSum / fpsStats.length);
|
|
438
|
+
const fpsVolatility = fpsStats.reduce((acc, curr) => acc + Math.abs(curr - meanFps), 0) / fpsStats.length;
|
|
439
|
+
const fpsVolatilityPercentage = (fpsVolatility * 100) / meanFps;
|
|
440
|
+
deltaMetrics.fpsVolatility = fpsVolatilityPercentage;
|
|
441
|
+
}
|
|
403
442
|
}
|
|
404
443
|
});
|
|
405
444
|
const callQualityStats = recordCallQualityStats(oldStats, deltaStats, cache.callQualityStats);
|
|
@@ -461,11 +500,15 @@ export const createStatsCollector = ({ input, signals: { onCallQuality, onCallQu
|
|
|
461
500
|
callQuality: Quality.GOOD,
|
|
462
501
|
callPacketsStats: [],
|
|
463
502
|
callQualityStats: [],
|
|
503
|
+
fpsStats: [],
|
|
464
504
|
});
|
|
465
505
|
let cache = newCache();
|
|
466
506
|
const pushStats = () => {
|
|
507
|
+
const startCapture = Date.now();
|
|
467
508
|
void statsFromRTCPeer(input).then(newStats => {
|
|
468
509
|
const [stats, callQuality, callQualityStats] = addDeltaStats(newStats, cache);
|
|
510
|
+
const endCapture = Date.now();
|
|
511
|
+
stats.timeToCaptureStats = endCapture - startCapture;
|
|
469
512
|
if (callQuality != cache.callQuality) {
|
|
470
513
|
onCallQuality.emit(callQuality);
|
|
471
514
|
cache.callQuality = callQuality;
|
|
@@ -62,6 +62,8 @@ export interface InboundVideo extends RTCStats {
|
|
|
62
62
|
frameWidth?: number;
|
|
63
63
|
frameHeight?: number;
|
|
64
64
|
framesPerSecond?: number;
|
|
65
|
+
totalDecodeTime?: number;
|
|
66
|
+
framesDecoded?: number;
|
|
65
67
|
bitrateMean?: number;
|
|
66
68
|
framerateMean?: number;
|
|
67
69
|
}
|
|
@@ -80,6 +82,8 @@ export interface OutboundVideo extends RTCStats {
|
|
|
80
82
|
remote?: Remote;
|
|
81
83
|
track?: Track;
|
|
82
84
|
transport?: Transport;
|
|
85
|
+
totalEncodeTime?: number;
|
|
86
|
+
framesEncoded?: number;
|
|
83
87
|
bitrateMean?: number;
|
|
84
88
|
framerateMean?: number;
|
|
85
89
|
}
|
|
@@ -110,24 +114,30 @@ export interface Metrics {
|
|
|
110
114
|
roundTripTime?: number;
|
|
111
115
|
timestamp?: number;
|
|
112
116
|
totalPercentageLost?: number;
|
|
117
|
+
timeToCaptureStats?: number;
|
|
113
118
|
}
|
|
114
119
|
export interface VideoMetrics extends Metrics {
|
|
115
120
|
framesPerSecond?: number;
|
|
116
121
|
resolution?: string;
|
|
117
122
|
resolutionHeight?: number;
|
|
118
123
|
resolutionWidth?: number;
|
|
124
|
+
fpsVolatility?: number;
|
|
119
125
|
}
|
|
120
126
|
export type InboundAudioMetrics = Metrics;
|
|
121
|
-
export type InboundVideoMetrics = VideoMetrics
|
|
127
|
+
export type InboundVideoMetrics = VideoMetrics & {
|
|
128
|
+
averageDecodeTime?: number;
|
|
129
|
+
};
|
|
122
130
|
export type OutboundAudioMetrics = Metrics;
|
|
123
131
|
export type OutboundVideoMetrics = VideoMetrics & {
|
|
124
132
|
totalPacketSendDelay?: number;
|
|
125
133
|
averagePacketSendDelay?: number;
|
|
134
|
+
averageEncodeTime?: number;
|
|
126
135
|
};
|
|
127
136
|
export type NormalizedRTCStats = InboundAudioMetrics | InboundVideoMetrics | OutboundAudioMetrics | OutboundVideoMetrics;
|
|
128
|
-
export type PacketsStats =
|
|
129
|
-
export type AudioQualityStats =
|
|
137
|
+
export type PacketsStats = [number, number][];
|
|
138
|
+
export type AudioQualityStats = [number, number][];
|
|
130
139
|
export type VideoQualityStats = number[];
|
|
140
|
+
export type FPSStats = number[];
|
|
131
141
|
export type CallQualityStats = AudioQualityStats | VideoQualityStats;
|
|
132
142
|
export type CallPacketsStats = PacketsStats;
|
|
133
143
|
export interface CacheStats {
|
|
@@ -135,6 +145,7 @@ export interface CacheStats {
|
|
|
135
145
|
callPacketsStats: CallPacketsStats;
|
|
136
146
|
callQuality: Quality;
|
|
137
147
|
callQualityStats: CallQualityStats;
|
|
148
|
+
fpsStats: FPSStats;
|
|
138
149
|
}
|
|
139
150
|
export declare enum Quality {
|
|
140
151
|
GOOD = 0,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../src/index.ts","../src/statsCollector.ts","../src/statsCollector.types.ts","../src/typeGuards.ts","../src/utils.ts","../src/tests/statsCollector.chrome-inbound-presentation.fixtures.ts","../src/tests/statsCollector.chrome-multiple.fixtures.ts","../src/tests/statsCollector.chrome-outbound-presentation.fixtures.ts","../src/tests/statsCollector.firefox.fixtures.ts","../../../@types/globals.d.ts"],"version":"5.7.3"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const isVideoMetrics = (value) => 'framesPerSecond' in value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pexip/peer-connection-stats",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "18.1.0",
|
|
4
4
|
"description": "Normalizer of RTCStats and related",
|
|
5
5
|
"homepage": "https://gitlab.com/pexip/zoo",
|
|
6
6
|
"bugs": "https://gitlab.com/pexip/zoo/issues",
|
|
@@ -13,33 +13,44 @@
|
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"module": "dist/index.js",
|
|
15
15
|
"types": "dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./*": {
|
|
22
|
+
"types": "./dist/*.d.ts",
|
|
23
|
+
"import": "./dist/*.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
16
26
|
"files": [
|
|
17
27
|
"dist"
|
|
18
28
|
],
|
|
19
29
|
"scripts": {
|
|
20
30
|
"build": "pexip-bundler build",
|
|
31
|
+
"build:sourcemap": "pexip-bundler build --sourcemap",
|
|
21
32
|
"clean": "rm -fr dist",
|
|
22
33
|
"dev": "pexip-bundler dev",
|
|
23
|
-
"prepack": "yarn clean && yarn build
|
|
34
|
+
"prepack": "yarn clean && yarn build",
|
|
24
35
|
"test": "jest",
|
|
25
36
|
"tsc": "tsc --noEmit",
|
|
26
37
|
"check-format": "prettier --ignore-path=../../.prettierignore --check .",
|
|
27
38
|
"format": "prettier --ignore-path=../../.prettierignore --write .",
|
|
28
|
-
"lint": "yarn run -T
|
|
39
|
+
"lint": "yarn run -T biome lint",
|
|
29
40
|
"typecheck": "yarn tsc --noEmit -p ."
|
|
30
41
|
},
|
|
31
42
|
"dependencies": {
|
|
32
|
-
"@pexip/signal": "16.
|
|
33
|
-
"@pexip/utils": "
|
|
43
|
+
"@pexip/signal": "16.8.0",
|
|
44
|
+
"@pexip/utils": "17.0.0"
|
|
34
45
|
},
|
|
35
46
|
"devDependencies": {
|
|
36
|
-
"@pexip/bundler": "
|
|
37
|
-
"@swc/core": "^1.
|
|
38
|
-
"@swc/jest": "^0.2.
|
|
47
|
+
"@pexip/bundler": "18.1.0",
|
|
48
|
+
"@swc/core": "^1.10.12",
|
|
49
|
+
"@swc/jest": "^0.2.37",
|
|
39
50
|
"jest": "^29.5.12",
|
|
40
51
|
"jest-junit": "^16.0.0",
|
|
41
52
|
"prettier": "^3.2.5",
|
|
42
|
-
"typescript": "~5.3
|
|
53
|
+
"typescript": "~5.7.3"
|
|
43
54
|
},
|
|
44
55
|
"publishConfig": {
|
|
45
56
|
"access": "public",
|