@pexip/peer-connection-stats 18.0.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,16 @@
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
+
3
14
  ## 18.0.0
4
15
 
5
16
  ### Major 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).
@@ -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,10 @@ 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;
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
137
  export type PacketsStats = [number, number][];
129
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,
@@ -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,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": "18.0.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",
@@ -44,7 +44,7 @@
44
44
  "@pexip/utils": "17.0.0"
45
45
  },
46
46
  "devDependencies": {
47
- "@pexip/bundler": "18.0.0",
47
+ "@pexip/bundler": "18.1.0",
48
48
  "@swc/core": "^1.10.12",
49
49
  "@swc/jest": "^0.2.37",
50
50
  "jest": "^29.5.12",