@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 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
@@ -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 | undefined;
89
- bytesTransmitted?: number | undefined;
90
- codec?: string | undefined;
91
- jitter?: number | undefined;
89
+ bitrate?: number;
90
+ bytesTransmitted?: number;
91
+ codec?: string;
92
+ jitter?: number;
92
93
  packetsLost: number;
93
94
  packetsTransmitted: number;
94
- recentPercentageLost?: number | undefined;
95
- roundTripTime?: number | undefined;
96
- timestamp?: number | undefined;
97
- totalPercentageLost?: number | undefined;
95
+ recentPercentageLost?: number;
96
+ roundTripTime?: number;
97
+ timestamp?: number;
98
+ totalPercentageLost?: number;
99
+ timeToCaptureStats?: number;
98
100
  } | {
99
- framesPerSecond?: number | undefined;
100
- resolution?: string | undefined;
101
- resolutionHeight?: number | undefined;
102
- resolutionWidth?: number | undefined;
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 | undefined;
106
- bytesTransmitted?: number | undefined;
107
- codec?: string | undefined;
108
- jitter?: number | undefined;
108
+ bitrate?: number;
109
+ bytesTransmitted?: number;
110
+ codec?: string;
111
+ jitter?: number;
109
112
  packetsLost: number;
110
113
  packetsTransmitted: number;
111
- recentPercentageLost?: number | undefined;
112
- roundTripTime?: number | undefined;
113
- timestamp?: number | undefined;
114
- totalPercentageLost?: number | undefined;
114
+ recentPercentageLost?: number;
115
+ roundTripTime?: number;
116
+ timestamp?: number;
117
+ totalPercentageLost?: number;
118
+ timeToCaptureStats?: number;
119
+ averageDecodeTime?: number;
115
120
  } | {
116
- framesPerSecond?: number | undefined;
117
- resolution?: string | undefined;
118
- resolutionHeight?: number | undefined;
119
- resolutionWidth?: number | undefined;
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 | undefined;
123
- bytesTransmitted?: number | undefined;
124
- codec?: string | undefined;
125
- jitter?: number | undefined;
128
+ bitrate?: number;
129
+ bytesTransmitted?: number;
130
+ codec?: string;
131
+ jitter?: number;
126
132
  packetsLost: number;
127
133
  packetsTransmitted: number;
128
- recentPercentageLost?: number | undefined;
129
- roundTripTime?: number | undefined;
130
- timestamp?: number | undefined;
131
- totalPercentageLost?: number | undefined;
132
- totalPacketSendDelay?: number | undefined;
133
- averagePacketSendDelay?: number | undefined;
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
  /**
@@ -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
- [newStats, oldStats, deltaStats, callPacketsStats ?? []],
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 = Array<[number, number]>;
129
- export type AudioQualityStats = Array<[number, number]>;
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,2 @@
1
+ import type { NormalizedRTCStats, InboundVideoMetrics, OutboundVideoMetrics } from './statsCollector.types';
2
+ export declare const isVideoMetrics: (value: NormalizedRTCStats) => value is InboundVideoMetrics | OutboundVideoMetrics;
@@ -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": "17.12.0",
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 --no-sourcemap",
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 eslint --ext .js,.ts,.tsx --report-unused-disable-directives .",
39
+ "lint": "yarn run -T biome lint",
29
40
  "typecheck": "yarn tsc --noEmit -p ."
30
41
  },
31
42
  "dependencies": {
32
- "@pexip/signal": "16.7.1",
33
- "@pexip/utils": "16.13.0"
43
+ "@pexip/signal": "16.8.0",
44
+ "@pexip/utils": "17.0.0"
34
45
  },
35
46
  "devDependencies": {
36
- "@pexip/bundler": "17.1.0",
37
- "@swc/core": "^1.4.6",
38
- "@swc/jest": "^0.2.36",
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.0"
53
+ "typescript": "~5.7.3"
43
54
  },
44
55
  "publishConfig": {
45
56
  "access": "public",