@pexip/peer-connection-stats 18.0.0 → 18.2.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 +24 -0
- package/README.md +3 -3
- package/dist/statsCollector.d.ts +11 -1
- package/dist/statsCollector.js +48 -3
- package/dist/statsCollector.types.d.ts +18 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/typeGuards.d.ts +2 -0
- package/dist/typeGuards.js +1 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 18.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7f9df17: Expose `qualityLimitationDurations` and `qualityLimitationReason`.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 709232f: Correct typedoc comment to fix the document generation.
|
|
12
|
+
- Updated dependencies [6fddc11]
|
|
13
|
+
- @pexip/signal@16.9.0
|
|
14
|
+
- @pexip/utils@17.0.0
|
|
15
|
+
|
|
16
|
+
## 18.1.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- fdfb189: Adds new stats emitted via `onRtcStats` signal.
|
|
21
|
+
- `timeToCaptureStats` to any stats: time taken in ms to capture stats
|
|
22
|
+
- `fpsVolatility` to `VideoMetrics`: <4% is good, <8% can be reasonable. Src:
|
|
23
|
+
https://www.youtube.com/watch?v=jlW1VdTjkbk
|
|
24
|
+
- `averageDecodeTime` to `InboundVideoMetrics`: decode time per frame
|
|
25
|
+
- `averageEncodeTime` to `OutboundVideoMetrics`: encode time per frame
|
|
26
|
+
|
|
3
27
|
## 18.0.0
|
|
4
28
|
|
|
5
29
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -8,6 +8,6 @@ Helps with normalization of data from `RTCStats` and friends.
|
|
|
8
8
|
|
|
9
9
|
Related documentation:
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
11
|
+
- [https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_Statistics_API](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_Statistics_API)
|
|
12
|
+
- [https://developer.mozilla.org/en-US/docs/Web/API/RTCStatsReport](https://developer.mozilla.org/en-US/docs/Web/API/RTCStatsReport)
|
|
13
|
+
- [https://developer.mozilla.org/en-US/docs/Web/API/RTCStats](https://developer.mozilla.org/en-US/docs/Web/API/RTCStats)
|
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).
|
|
@@ -95,11 +96,13 @@ export declare const addDeltaStats: (newStats: NormalizedRTCStats, cache: CacheS
|
|
|
95
96
|
roundTripTime?: number;
|
|
96
97
|
timestamp?: number;
|
|
97
98
|
totalPercentageLost?: number;
|
|
99
|
+
timeToCaptureStats?: number;
|
|
98
100
|
} | {
|
|
99
101
|
framesPerSecond?: number;
|
|
100
102
|
resolution?: string;
|
|
101
103
|
resolutionHeight?: number;
|
|
102
104
|
resolutionWidth?: number;
|
|
105
|
+
fpsVolatility?: number;
|
|
103
106
|
type: "inbound-rtp" | "outbound-rtp";
|
|
104
107
|
kind: "audio" | "video";
|
|
105
108
|
bitrate?: number;
|
|
@@ -112,11 +115,14 @@ export declare const addDeltaStats: (newStats: NormalizedRTCStats, cache: CacheS
|
|
|
112
115
|
roundTripTime?: number;
|
|
113
116
|
timestamp?: number;
|
|
114
117
|
totalPercentageLost?: number;
|
|
118
|
+
timeToCaptureStats?: number;
|
|
119
|
+
averageDecodeTime?: number;
|
|
115
120
|
} | {
|
|
116
121
|
framesPerSecond?: number;
|
|
117
122
|
resolution?: string;
|
|
118
123
|
resolutionHeight?: number;
|
|
119
124
|
resolutionWidth?: number;
|
|
125
|
+
fpsVolatility?: number;
|
|
120
126
|
type: "inbound-rtp" | "outbound-rtp";
|
|
121
127
|
kind: "audio" | "video";
|
|
122
128
|
bitrate?: number;
|
|
@@ -129,8 +135,12 @@ export declare const addDeltaStats: (newStats: NormalizedRTCStats, cache: CacheS
|
|
|
129
135
|
roundTripTime?: number;
|
|
130
136
|
timestamp?: number;
|
|
131
137
|
totalPercentageLost?: number;
|
|
138
|
+
timeToCaptureStats?: number;
|
|
132
139
|
totalPacketSendDelay?: number;
|
|
133
140
|
averagePacketSendDelay?: number;
|
|
141
|
+
averageEncodeTime?: number;
|
|
142
|
+
qualityLimitationReason?: OutboundVideo["qualityLimitationReason"];
|
|
143
|
+
qualityLimitationDurations?: OutboundVideo["qualityLimitationDurations"];
|
|
134
144
|
}, Quality, CallQualityStats];
|
|
135
145
|
export declare const calculateQuality: (stats: number | [number, number]) => Quality;
|
|
136
146
|
/**
|
|
@@ -140,6 +150,6 @@ export declare const calculateQuality: (stats: number | [number, number]) => Qua
|
|
|
140
150
|
* @param signal - Signal which distributes stats
|
|
141
151
|
* @param interval - how often signal with fire with new stats
|
|
142
152
|
*
|
|
143
|
-
* @returns
|
|
153
|
+
* @returns {Object} function to reset stats window, cleanup func
|
|
144
154
|
*/
|
|
145
155
|
export declare const createStatsCollector: ({ input, signals: { onCallQuality, onCallQualityStats, onRtcStats }, interval, }: StatsCollectorOptions) => StatsCollector;
|
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,11 @@ 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,
|
|
227
|
+
qualityLimitationReason: statsData.qualityLimitationReason,
|
|
228
|
+
qualityLimitationDurations: statsData.qualityLimitationDurations,
|
|
219
229
|
};
|
|
220
230
|
};
|
|
221
231
|
const isInbound = (entry) => entry.type === 'inbound-rtp';
|
|
@@ -306,6 +316,15 @@ const recordCallPacketsStats = (oldStats, newStats, callPacketsStats) => {
|
|
|
306
316
|
}
|
|
307
317
|
return callPacketsStats;
|
|
308
318
|
};
|
|
319
|
+
const recordFPSStats = (fps, fpsStats) => {
|
|
320
|
+
if (fps) {
|
|
321
|
+
if (!fpsStats) {
|
|
322
|
+
fpsStats = [];
|
|
323
|
+
}
|
|
324
|
+
removeObsolete(fpsStats, FPS_STATS_SIZE);
|
|
325
|
+
fpsStats.unshift(fps);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
309
328
|
const addAudioQualityMetric = (metric, data) => {
|
|
310
329
|
removeObsolete(metric);
|
|
311
330
|
metric.unshift(data);
|
|
@@ -374,10 +393,19 @@ export const addDeltaStats = (newStats, cache) => {
|
|
|
374
393
|
...newStats,
|
|
375
394
|
};
|
|
376
395
|
const callPacketsStats = recordCallPacketsStats(oldStats, newStats, cache.callPacketsStats);
|
|
396
|
+
if (isVideoMetrics(newStats)) {
|
|
397
|
+
recordFPSStats(newStats.framesPerSecond, cache.fpsStats);
|
|
398
|
+
}
|
|
377
399
|
const metrics = [
|
|
378
|
-
[
|
|
400
|
+
[
|
|
401
|
+
newStats,
|
|
402
|
+
oldStats,
|
|
403
|
+
deltaStats,
|
|
404
|
+
callPacketsStats ?? [],
|
|
405
|
+
cache.fpsStats,
|
|
406
|
+
],
|
|
379
407
|
];
|
|
380
|
-
metrics.forEach(([newMetrics, oldMetrics, deltaMetrics, packets]) => {
|
|
408
|
+
metrics.forEach(([newMetrics, oldMetrics, deltaMetrics, packets, fpsStats]) => {
|
|
381
409
|
if (newMetrics?.bytesTransmitted &&
|
|
382
410
|
newMetrics?.timestamp &&
|
|
383
411
|
oldMetrics?.bytesTransmitted &&
|
|
@@ -391,6 +419,7 @@ export const addDeltaStats = (newStats, cache) => {
|
|
|
391
419
|
}
|
|
392
420
|
}
|
|
393
421
|
if (deltaMetrics) {
|
|
422
|
+
// packet stats
|
|
394
423
|
const [recentPacketsLost, recentTotalPackets] = packets.reduce((acc, [lost, total]) => {
|
|
395
424
|
acc[0] += lost;
|
|
396
425
|
acc[1] += total;
|
|
@@ -400,6 +429,18 @@ export const addDeltaStats = (newStats, cache) => {
|
|
|
400
429
|
recentTotalPackets === 0
|
|
401
430
|
? 0
|
|
402
431
|
: recentPacketsLost / recentTotalPackets;
|
|
432
|
+
// fps stats
|
|
433
|
+
if (isVideoMetrics(deltaMetrics)) {
|
|
434
|
+
if (fpsStats.length === 0) {
|
|
435
|
+
deltaMetrics.fpsVolatility = 0;
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const fpsSum = fpsStats.reduce((acc, curr) => acc + curr, 0);
|
|
439
|
+
const meanFps = Math.round(fpsSum / fpsStats.length);
|
|
440
|
+
const fpsVolatility = fpsStats.reduce((acc, curr) => acc + Math.abs(curr - meanFps), 0) / fpsStats.length;
|
|
441
|
+
const fpsVolatilityPercentage = (fpsVolatility * 100) / meanFps;
|
|
442
|
+
deltaMetrics.fpsVolatility = fpsVolatilityPercentage;
|
|
443
|
+
}
|
|
403
444
|
}
|
|
404
445
|
});
|
|
405
446
|
const callQualityStats = recordCallQualityStats(oldStats, deltaStats, cache.callQualityStats);
|
|
@@ -454,18 +495,22 @@ export const calculateQuality = (stats) => {
|
|
|
454
495
|
* @param signal - Signal which distributes stats
|
|
455
496
|
* @param interval - how often signal with fire with new stats
|
|
456
497
|
*
|
|
457
|
-
* @returns
|
|
498
|
+
* @returns {Object} function to reset stats window, cleanup func
|
|
458
499
|
*/
|
|
459
500
|
export const createStatsCollector = ({ input, signals: { onCallQuality, onCallQualityStats, onRtcStats }, interval = 1000, }) => {
|
|
460
501
|
const newCache = () => ({
|
|
461
502
|
callQuality: Quality.GOOD,
|
|
462
503
|
callPacketsStats: [],
|
|
463
504
|
callQualityStats: [],
|
|
505
|
+
fpsStats: [],
|
|
464
506
|
});
|
|
465
507
|
let cache = newCache();
|
|
466
508
|
const pushStats = () => {
|
|
509
|
+
const startCapture = Date.now();
|
|
467
510
|
void statsFromRTCPeer(input).then(newStats => {
|
|
468
511
|
const [stats, callQuality, callQualityStats] = addDeltaStats(newStats, cache);
|
|
512
|
+
const endCapture = Date.now();
|
|
513
|
+
stats.timeToCaptureStats = endCapture - startCapture;
|
|
469
514
|
if (callQuality != cache.callQuality) {
|
|
470
515
|
onCallQuality.emit(callQuality);
|
|
471
516
|
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,8 +82,14 @@ 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;
|
|
89
|
+
qualityLimitationReason?: 'cpu' | 'bandwidth' | 'other' | 'none';
|
|
90
|
+
qualityLimitationDurations?: {
|
|
91
|
+
[key in NonNullable<OutboundVideo['qualityLimitationReason']>]?: number;
|
|
92
|
+
};
|
|
85
93
|
}
|
|
86
94
|
export interface MediaSource extends RTCStats {
|
|
87
95
|
type: 'media-source';
|
|
@@ -110,24 +118,32 @@ export interface Metrics {
|
|
|
110
118
|
roundTripTime?: number;
|
|
111
119
|
timestamp?: number;
|
|
112
120
|
totalPercentageLost?: number;
|
|
121
|
+
timeToCaptureStats?: number;
|
|
113
122
|
}
|
|
114
123
|
export interface VideoMetrics extends Metrics {
|
|
115
124
|
framesPerSecond?: number;
|
|
116
125
|
resolution?: string;
|
|
117
126
|
resolutionHeight?: number;
|
|
118
127
|
resolutionWidth?: number;
|
|
128
|
+
fpsVolatility?: number;
|
|
119
129
|
}
|
|
120
130
|
export type InboundAudioMetrics = Metrics;
|
|
121
|
-
export type InboundVideoMetrics = VideoMetrics
|
|
131
|
+
export type InboundVideoMetrics = VideoMetrics & {
|
|
132
|
+
averageDecodeTime?: number;
|
|
133
|
+
};
|
|
122
134
|
export type OutboundAudioMetrics = Metrics;
|
|
123
135
|
export type OutboundVideoMetrics = VideoMetrics & {
|
|
124
136
|
totalPacketSendDelay?: number;
|
|
125
137
|
averagePacketSendDelay?: number;
|
|
138
|
+
averageEncodeTime?: number;
|
|
139
|
+
qualityLimitationReason?: OutboundVideo['qualityLimitationReason'];
|
|
140
|
+
qualityLimitationDurations?: OutboundVideo['qualityLimitationDurations'];
|
|
126
141
|
};
|
|
127
142
|
export type NormalizedRTCStats = InboundAudioMetrics | InboundVideoMetrics | OutboundAudioMetrics | OutboundVideoMetrics;
|
|
128
143
|
export type PacketsStats = [number, number][];
|
|
129
144
|
export type AudioQualityStats = [number, number][];
|
|
130
145
|
export type VideoQualityStats = number[];
|
|
146
|
+
export type FPSStats = number[];
|
|
131
147
|
export type CallQualityStats = AudioQualityStats | VideoQualityStats;
|
|
132
148
|
export type CallPacketsStats = PacketsStats;
|
|
133
149
|
export interface CacheStats {
|
|
@@ -135,6 +151,7 @@ export interface CacheStats {
|
|
|
135
151
|
callPacketsStats: CallPacketsStats;
|
|
136
152
|
callQuality: Quality;
|
|
137
153
|
callQualityStats: CallQualityStats;
|
|
154
|
+
fpsStats: FPSStats;
|
|
138
155
|
}
|
|
139
156
|
export declare enum Quality {
|
|
140
157
|
GOOD = 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/index.ts","../src/statsCollector.ts","../src/statsCollector.types.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"}
|
|
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": "18.
|
|
3
|
+
"version": "18.2.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",
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
"typecheck": "yarn tsc --noEmit -p ."
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@pexip/signal": "16.
|
|
43
|
+
"@pexip/signal": "16.9.0",
|
|
44
44
|
"@pexip/utils": "17.0.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@pexip/bundler": "18.
|
|
47
|
+
"@pexip/bundler": "18.1.1",
|
|
48
48
|
"@swc/core": "^1.10.12",
|
|
49
49
|
"@swc/jest": "^0.2.37",
|
|
50
50
|
"jest": "^29.5.12",
|