@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 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
- - <https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_Statistics_API>
12
- - <https://developer.mozilla.org/en-US/docs/Web/API/RTCStatsReport>
13
- - <https://developer.mozilla.org/en-US/docs/Web/API/RTCStats>
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)
@@ -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 \{function to reset stats window, cleanup func\}
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;
@@ -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
- [newStats, oldStats, deltaStats, callPacketsStats ?? []],
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 \{function to reset stats window, cleanup func\}
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,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.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.8.0",
43
+ "@pexip/signal": "16.9.0",
44
44
  "@pexip/utils": "17.0.0"
45
45
  },
46
46
  "devDependencies": {
47
- "@pexip/bundler": "18.0.0",
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",