@pexip/peer-connection-stats 17.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 +253 -0
- package/LICENSE +188 -0
- package/README.md +9 -0
- package/dist/index.d.ts +325 -0
- package/dist/index.mjs +478 -0
- package/package.json +43 -0
- package/src/index.ts +3 -0
- package/src/statsCollector.ts +665 -0
- package/src/statsCollector.types.ts +233 -0
- package/src/tests/__snapshots__/statsCollector.test.ts.snap +208 -0
- package/src/tests/statsCollector.chrome-inbound-presentation.fixtures.ts +990 -0
- package/src/tests/statsCollector.chrome-multiple.fixtures.ts +2692 -0
- package/src/tests/statsCollector.chrome-outbound-presentation.fixtures.ts +925 -0
- package/src/tests/statsCollector.firefox.fixtures.ts +473 -0
- package/src/tests/statsCollector.test.ts +480 -0
- package/src/utils.ts +41 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
// src/statsCollector.types.ts
|
|
2
|
+
var Quality = /* @__PURE__ */ ((Quality2) => {
|
|
3
|
+
Quality2[Quality2["GOOD"] = 0] = "GOOD";
|
|
4
|
+
Quality2[Quality2["OK"] = 1] = "OK";
|
|
5
|
+
Quality2[Quality2["BAD"] = 2] = "BAD";
|
|
6
|
+
Quality2[Quality2["TERRIBLE"] = 3] = "TERRIBLE";
|
|
7
|
+
return Quality2;
|
|
8
|
+
})(Quality || {});
|
|
9
|
+
|
|
10
|
+
// src/statsCollector.ts
|
|
11
|
+
var STATS_SIZE = 60;
|
|
12
|
+
var createResolver = (statsReport) => {
|
|
13
|
+
const idLookup = statsReport.reduce(
|
|
14
|
+
(map, entry) => map.set(entry.id, entry),
|
|
15
|
+
/* @__PURE__ */ new Map()
|
|
16
|
+
);
|
|
17
|
+
const expandTail = (stats, seenIds = []) => Object.keys(stats).reduce((expanded, key) => {
|
|
18
|
+
const id = stats[key];
|
|
19
|
+
expanded[key] = id;
|
|
20
|
+
if (!id || typeof id !== "string" || seenIds.includes(id)) {
|
|
21
|
+
return expanded;
|
|
22
|
+
}
|
|
23
|
+
switch (key) {
|
|
24
|
+
case "codecId": {
|
|
25
|
+
const codec = idLookup.get(id);
|
|
26
|
+
if (codec) {
|
|
27
|
+
expanded.codec = expandTail(codec, [...seenIds, id]);
|
|
28
|
+
}
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
case "remoteCandidateId": {
|
|
32
|
+
const remoteCandidate = idLookup.get(id);
|
|
33
|
+
if (remoteCandidate) {
|
|
34
|
+
expanded.remoteCandidate = expandTail(remoteCandidate, [
|
|
35
|
+
...seenIds,
|
|
36
|
+
id
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
case "remoteId": {
|
|
42
|
+
const remote = idLookup.get(id);
|
|
43
|
+
if (remote) {
|
|
44
|
+
expanded.remote = expandTail(remote, [...seenIds, id]);
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case "selectedCandidatePairId": {
|
|
49
|
+
const selectedCandidatePair = idLookup.get(id);
|
|
50
|
+
if (selectedCandidatePair) {
|
|
51
|
+
expanded.selectedCandidatePair = expandTail(
|
|
52
|
+
selectedCandidatePair,
|
|
53
|
+
[...seenIds, id]
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case "trackId": {
|
|
59
|
+
const track = idLookup.get(id);
|
|
60
|
+
if (track) {
|
|
61
|
+
expanded.track = expandTail(track, [...seenIds, id]);
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case "trackIds": {
|
|
66
|
+
if (Array.isArray(id)) {
|
|
67
|
+
const ids = id;
|
|
68
|
+
const tracks = ids.flatMap((id2) => {
|
|
69
|
+
const track = idLookup.get(id2);
|
|
70
|
+
return !track ? [] : [expandTail(track, [...seenIds, ...ids])];
|
|
71
|
+
});
|
|
72
|
+
expanded.tracks = tracks;
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case "transportId": {
|
|
77
|
+
const transport = idLookup.get(id);
|
|
78
|
+
if (transport) {
|
|
79
|
+
expanded.transport = expandTail(transport, [
|
|
80
|
+
...seenIds,
|
|
81
|
+
id
|
|
82
|
+
]);
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case "localCandidateId":
|
|
87
|
+
case "localCertificateId":
|
|
88
|
+
case "localId":
|
|
89
|
+
case "mediaSourceId":
|
|
90
|
+
case "remoteCertificateId":
|
|
91
|
+
default:
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
return expanded;
|
|
95
|
+
}, {});
|
|
96
|
+
function expand(rawStats) {
|
|
97
|
+
return expandTail(rawStats);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
expand
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
var inboundAudio = (statsData) => {
|
|
104
|
+
const packetsReceived = statsData.packetsReceived ?? 0;
|
|
105
|
+
const packetsLost = statsData.packetsLost ?? 0;
|
|
106
|
+
const totalPackets = packetsReceived + packetsLost;
|
|
107
|
+
return {
|
|
108
|
+
type: statsData.type,
|
|
109
|
+
kind: statsData.kind,
|
|
110
|
+
jitter: statsData.jitter ?? 0,
|
|
111
|
+
timestamp: statsData.timestamp,
|
|
112
|
+
packetsTransmitted: packetsReceived,
|
|
113
|
+
packetsLost,
|
|
114
|
+
bytesTransmitted: statsData.bytesReceived,
|
|
115
|
+
codec: statsData.codec?.mimeType,
|
|
116
|
+
roundTripTime: statsData.transport?.selectedCandidatePair?.currentRoundTripTime,
|
|
117
|
+
totalPercentageLost: totalPackets && packetsLost / totalPackets
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
var outboundAudio = (statsData) => {
|
|
121
|
+
const packetsSent = statsData.packetsSent ?? 0;
|
|
122
|
+
const packetsLost = statsData.remote?.packetsLost ?? 0;
|
|
123
|
+
const totalPackets = packetsSent + packetsLost;
|
|
124
|
+
const roundTripTime = statsData.remote?.roundTripTime ?? statsData.transport?.selectedCandidatePair?.currentRoundTripTime;
|
|
125
|
+
return {
|
|
126
|
+
type: statsData.type,
|
|
127
|
+
kind: statsData.kind,
|
|
128
|
+
jitter: statsData?.remote?.jitter ?? 0,
|
|
129
|
+
timestamp: statsData.timestamp,
|
|
130
|
+
packetsTransmitted: packetsSent,
|
|
131
|
+
packetsLost,
|
|
132
|
+
bytesTransmitted: statsData.bytesSent,
|
|
133
|
+
codec: statsData.codec?.mimeType,
|
|
134
|
+
roundTripTime,
|
|
135
|
+
totalPercentageLost: totalPackets && packetsLost / totalPackets
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
var inboundVideo = (statsData) => {
|
|
139
|
+
const packetsReceived = statsData.packetsReceived ?? 0;
|
|
140
|
+
const packetsLost = statsData.packetsLost ?? 0;
|
|
141
|
+
const totalPackets = packetsReceived + packetsLost;
|
|
142
|
+
return {
|
|
143
|
+
type: statsData.type,
|
|
144
|
+
kind: statsData.kind,
|
|
145
|
+
timestamp: statsData.timestamp,
|
|
146
|
+
packetsTransmitted: packetsReceived,
|
|
147
|
+
packetsLost,
|
|
148
|
+
bytesTransmitted: statsData.bytesReceived,
|
|
149
|
+
// firefox typically uses bitrateMean while chrome does not
|
|
150
|
+
bitrate: statsData.bitrateMean,
|
|
151
|
+
codec: statsData.codec?.mimeType,
|
|
152
|
+
resolutionWidth: statsData.frameWidth,
|
|
153
|
+
resolutionHeight: statsData.frameHeight,
|
|
154
|
+
resolution: statsData.frameWidth && statsData.frameHeight ? `${statsData.frameWidth}x${statsData.frameHeight}` : void 0,
|
|
155
|
+
// firefox typically uses framerateMean while chrome does not
|
|
156
|
+
framesPerSecond: statsData.framerateMean ?? statsData.framesPerSecond,
|
|
157
|
+
roundTripTime: statsData.transport?.selectedCandidatePair?.currentRoundTripTime,
|
|
158
|
+
totalPercentageLost: totalPackets && packetsLost / totalPackets
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
var outboundVideo = (statsData) => {
|
|
162
|
+
const totalPacketSendDelay = statsData?.totalPacketSendDelay ?? 0;
|
|
163
|
+
const packetsSent = statsData.packetsSent ?? 0;
|
|
164
|
+
const packetsLost = statsData.remote?.packetsLost ?? 0;
|
|
165
|
+
const totalPackets = packetsSent + packetsLost;
|
|
166
|
+
const roundTripTime = statsData.remote?.roundTripTime ?? statsData.transport?.selectedCandidatePair?.currentRoundTripTime;
|
|
167
|
+
return {
|
|
168
|
+
type: statsData.type,
|
|
169
|
+
kind: statsData.kind,
|
|
170
|
+
timestamp: statsData.timestamp,
|
|
171
|
+
packetsTransmitted: packetsSent,
|
|
172
|
+
packetsLost,
|
|
173
|
+
bytesTransmitted: statsData.bytesSent,
|
|
174
|
+
totalPacketSendDelay: statsData.totalPacketSendDelay,
|
|
175
|
+
averagePacketSendDelay: totalPacketSendDelay && totalPacketSendDelay / packetsSent,
|
|
176
|
+
// firefox typically uses bitrateMean while chrome does not
|
|
177
|
+
bitrate: statsData.bitrateMean,
|
|
178
|
+
codec: statsData.codec?.mimeType,
|
|
179
|
+
resolutionWidth: statsData?.frameWidth,
|
|
180
|
+
resolutionHeight: statsData?.frameHeight,
|
|
181
|
+
resolution: statsData.frameWidth && statsData.frameHeight ? `${statsData.frameWidth}x${statsData.frameHeight}` : void 0,
|
|
182
|
+
// firefox typically uses framerateMean while chrome does not
|
|
183
|
+
framesPerSecond: statsData.framerateMean ?? statsData.framesPerSecond,
|
|
184
|
+
roundTripTime,
|
|
185
|
+
totalPercentageLost: totalPackets && packetsLost / totalPackets
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
var isInbound = (entry) => entry.type === "inbound-rtp";
|
|
189
|
+
var isOutbound = (entry) => entry.type === "outbound-rtp";
|
|
190
|
+
var isAudio = (entry) => entry.kind === "audio";
|
|
191
|
+
var isVideo = (entry) => entry.kind === "video";
|
|
192
|
+
var isAudioInbound = (entry) => isInbound(entry) && isAudio(entry);
|
|
193
|
+
var isAudioOutbound = (entry) => isOutbound(entry) && isAudio(entry);
|
|
194
|
+
var isVideoInbound = (entry) => isInbound(entry) && isVideo(entry);
|
|
195
|
+
var isVideoOutbound = (entry) => isOutbound(entry) && isVideo(entry);
|
|
196
|
+
var statsFrom = (statsReports) => {
|
|
197
|
+
const resolver = createResolver(statsReports);
|
|
198
|
+
const audioIn = statsReports.find(isAudioInbound);
|
|
199
|
+
if (audioIn) {
|
|
200
|
+
return inboundAudio(resolver.expand(audioIn));
|
|
201
|
+
}
|
|
202
|
+
const audioOut = statsReports.find(isAudioOutbound);
|
|
203
|
+
if (audioOut) {
|
|
204
|
+
return outboundAudio(resolver.expand(audioOut));
|
|
205
|
+
}
|
|
206
|
+
const videoIn = statsReports.find(isVideoInbound);
|
|
207
|
+
if (videoIn) {
|
|
208
|
+
return inboundVideo(resolver.expand(videoIn));
|
|
209
|
+
}
|
|
210
|
+
const videoOut = statsReports.find(isVideoOutbound);
|
|
211
|
+
if (videoOut) {
|
|
212
|
+
return outboundVideo(resolver.expand(videoOut));
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var statsFromRTCPeer = async (rtcPeer) => {
|
|
216
|
+
const stats = await rtcPeer?.getStats(null);
|
|
217
|
+
const reports = [];
|
|
218
|
+
stats.forEach((report) => reports.push(report));
|
|
219
|
+
return statsFrom(reports) ?? {
|
|
220
|
+
type: "inbound-rtp",
|
|
221
|
+
kind: "audio",
|
|
222
|
+
packetsLost: 0,
|
|
223
|
+
packetsTransmitted: 0
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
var getRecentPacketsLost = (oldMetrics, newMetrics) => Math.max(newMetrics.packetsLost - oldMetrics.packetsLost, 0);
|
|
227
|
+
var getRecentTotalPackets = (oldMetrics, newMetrics) => {
|
|
228
|
+
const totalPacketsDelta = newMetrics.packetsTransmitted + newMetrics.packetsLost - (oldMetrics.packetsTransmitted + oldMetrics.packetsLost);
|
|
229
|
+
return Math.max(totalPacketsDelta, 1);
|
|
230
|
+
};
|
|
231
|
+
var getPacketStats = (oldMetrics, newMetrics) => [getRecentPacketsLost, getRecentTotalPackets].map(
|
|
232
|
+
(fn) => fn(oldMetrics, newMetrics)
|
|
233
|
+
);
|
|
234
|
+
var removeObsolete = (metric, qualityHistorySize = STATS_SIZE) => {
|
|
235
|
+
if (metric.length === qualityHistorySize) {
|
|
236
|
+
metric.pop();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
var addPacketsStats = (metric, data) => {
|
|
240
|
+
removeObsolete(metric);
|
|
241
|
+
metric.unshift(data);
|
|
242
|
+
};
|
|
243
|
+
var recordCallPacketsStats = (oldStats, newStats, callPacketsStats) => {
|
|
244
|
+
if (oldStats && newStats) {
|
|
245
|
+
if (!callPacketsStats) {
|
|
246
|
+
callPacketsStats = [];
|
|
247
|
+
}
|
|
248
|
+
addPacketsStats(callPacketsStats, getPacketStats(oldStats, newStats));
|
|
249
|
+
}
|
|
250
|
+
return callPacketsStats;
|
|
251
|
+
};
|
|
252
|
+
var addAudioQualityMetric = (metric, data) => {
|
|
253
|
+
removeObsolete(metric);
|
|
254
|
+
metric.unshift(data);
|
|
255
|
+
};
|
|
256
|
+
var addVideoQualityMetric = (metric, data) => {
|
|
257
|
+
removeObsolete(metric);
|
|
258
|
+
metric.unshift(data);
|
|
259
|
+
};
|
|
260
|
+
var recordCallQualityStats = (prevStats, stats, callQualityStats) => {
|
|
261
|
+
const calcRecentPacketLoss = ({ pT = 0, prevPT = 0, pL = 0, prevPL = 0 }) => {
|
|
262
|
+
if (pT <= 0) {
|
|
263
|
+
return 0;
|
|
264
|
+
}
|
|
265
|
+
if (pT - prevPT <= 0) {
|
|
266
|
+
return pL / pT;
|
|
267
|
+
}
|
|
268
|
+
return (pL - prevPL) / (pT - prevPT);
|
|
269
|
+
};
|
|
270
|
+
if (stats) {
|
|
271
|
+
if (!callQualityStats) {
|
|
272
|
+
callQualityStats = [];
|
|
273
|
+
}
|
|
274
|
+
if (stats.kind === "audio") {
|
|
275
|
+
addAudioQualityMetric(
|
|
276
|
+
callQualityStats || [],
|
|
277
|
+
[
|
|
278
|
+
calcRecentPacketLoss({
|
|
279
|
+
pT: stats.packetsTransmitted,
|
|
280
|
+
prevPT: prevStats?.packetsTransmitted,
|
|
281
|
+
pL: stats.packetsLost,
|
|
282
|
+
prevPL: prevStats?.packetsLost
|
|
283
|
+
}),
|
|
284
|
+
stats.jitter ?? 0
|
|
285
|
+
]
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
if (stats.kind === "video") {
|
|
289
|
+
addVideoQualityMetric(
|
|
290
|
+
callQualityStats || [],
|
|
291
|
+
calcRecentPacketLoss({
|
|
292
|
+
pT: stats.packetsTransmitted,
|
|
293
|
+
prevPT: prevStats?.packetsTransmitted,
|
|
294
|
+
pL: stats.packetsLost,
|
|
295
|
+
prevPL: prevStats?.packetsLost
|
|
296
|
+
})
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return callQualityStats;
|
|
301
|
+
};
|
|
302
|
+
var getQuality = (stats) => {
|
|
303
|
+
const qualityOverTime = stats.map((stats2) => calculateQuality(stats2));
|
|
304
|
+
const goodOrOk = qualityOverTime.filter(
|
|
305
|
+
(stat) => stat === 0 /* GOOD */ || stat === 1 /* OK */
|
|
306
|
+
);
|
|
307
|
+
const qualitySum = qualityOverTime.reduce((acc, val) => acc + val, 0);
|
|
308
|
+
return {
|
|
309
|
+
quality: Math.round(qualitySum / qualityOverTime.length),
|
|
310
|
+
goodOrOkQuality: goodOrOk.length / qualityOverTime.length,
|
|
311
|
+
qualityOverTime
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
var addDeltaStats = (newStats, cache) => {
|
|
315
|
+
const oldStats = cache.previous ?? Object.keys(newStats).reduce((stats, key) => {
|
|
316
|
+
const statKey = key;
|
|
317
|
+
if (typeof newStats[statKey] === "number" && statKey !== "timestamp") {
|
|
318
|
+
return { ...stats, [statKey]: 0 };
|
|
319
|
+
}
|
|
320
|
+
return { ...stats, [statKey]: newStats[statKey] };
|
|
321
|
+
}, {});
|
|
322
|
+
const deltaStats = {
|
|
323
|
+
...newStats
|
|
324
|
+
};
|
|
325
|
+
const callPacketsStats = recordCallPacketsStats(
|
|
326
|
+
oldStats,
|
|
327
|
+
newStats,
|
|
328
|
+
cache.callPacketsStats
|
|
329
|
+
);
|
|
330
|
+
const metrics = [
|
|
331
|
+
[newStats, oldStats, deltaStats, callPacketsStats ?? []]
|
|
332
|
+
];
|
|
333
|
+
metrics.forEach(([newMetrics, oldMetrics, deltaMetrics, packets]) => {
|
|
334
|
+
if (newMetrics?.bytesTransmitted && newMetrics?.timestamp && oldMetrics?.bytesTransmitted && oldMetrics?.timestamp && deltaMetrics) {
|
|
335
|
+
const dMs = newMetrics.timestamp - oldMetrics.timestamp;
|
|
336
|
+
const dBytes = newMetrics.bytesTransmitted - oldMetrics.bytesTransmitted;
|
|
337
|
+
if (dMs !== 0) {
|
|
338
|
+
deltaMetrics.bitrate = Math.round(dBytes * 8 / (dMs / 1e3));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (deltaMetrics) {
|
|
342
|
+
const [recentPacketsLost, recentTotalPackets] = packets.reduce(
|
|
343
|
+
(acc, [lost, total]) => {
|
|
344
|
+
acc[0] += lost;
|
|
345
|
+
acc[1] += total;
|
|
346
|
+
return acc;
|
|
347
|
+
},
|
|
348
|
+
[0, 0]
|
|
349
|
+
);
|
|
350
|
+
deltaMetrics.recentPercentageLost = recentTotalPackets === 0 ? 0 : recentPacketsLost / recentTotalPackets;
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
const callQualityStats = recordCallQualityStats(
|
|
354
|
+
oldStats,
|
|
355
|
+
deltaStats,
|
|
356
|
+
cache.callQualityStats
|
|
357
|
+
);
|
|
358
|
+
return [
|
|
359
|
+
deltaStats,
|
|
360
|
+
getQuality(callQualityStats ?? []).quality,
|
|
361
|
+
callQualityStats
|
|
362
|
+
];
|
|
363
|
+
};
|
|
364
|
+
var calculateQuality = (stats) => {
|
|
365
|
+
let packetLoss;
|
|
366
|
+
let jitter;
|
|
367
|
+
if (typeof stats === "number") {
|
|
368
|
+
packetLoss == stats;
|
|
369
|
+
} else {
|
|
370
|
+
[packetLoss, jitter] = stats;
|
|
371
|
+
}
|
|
372
|
+
let callQuality = 1 /* OK */;
|
|
373
|
+
if (typeof packetLoss === "number") {
|
|
374
|
+
if (packetLoss < 0.01) {
|
|
375
|
+
callQuality = 0 /* GOOD */;
|
|
376
|
+
} else if (packetLoss < 0.03) {
|
|
377
|
+
callQuality = 1 /* OK */;
|
|
378
|
+
} else if (packetLoss < 0.1) {
|
|
379
|
+
callQuality = 2 /* BAD */;
|
|
380
|
+
} else {
|
|
381
|
+
callQuality = 3 /* TERRIBLE */;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (jitter && jitter > 0.04) {
|
|
385
|
+
if (callQuality === 0 /* GOOD */) {
|
|
386
|
+
callQuality = 1 /* OK */;
|
|
387
|
+
} else if (callQuality === 1 /* OK */) {
|
|
388
|
+
callQuality = 2 /* BAD */;
|
|
389
|
+
} else if (callQuality === 2 /* BAD */) {
|
|
390
|
+
callQuality = 3 /* TERRIBLE */;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return callQuality;
|
|
394
|
+
};
|
|
395
|
+
var createStatsCollector = ({
|
|
396
|
+
input,
|
|
397
|
+
signals: { onCallQuality, onCallQualityStats, onRtcStats },
|
|
398
|
+
interval = 1e3
|
|
399
|
+
}) => {
|
|
400
|
+
const newCache = () => ({
|
|
401
|
+
callQuality: 0 /* GOOD */,
|
|
402
|
+
callPacketsStats: [],
|
|
403
|
+
callQualityStats: []
|
|
404
|
+
});
|
|
405
|
+
let cache = newCache();
|
|
406
|
+
const pushStats = () => {
|
|
407
|
+
void statsFromRTCPeer(input).then((newStats) => {
|
|
408
|
+
const [stats, callQuality, callQualityStats] = addDeltaStats(
|
|
409
|
+
newStats,
|
|
410
|
+
cache
|
|
411
|
+
);
|
|
412
|
+
if (callQuality != cache.callQuality) {
|
|
413
|
+
onCallQuality.emit(callQuality);
|
|
414
|
+
cache.callQuality = callQuality;
|
|
415
|
+
}
|
|
416
|
+
cache.previous = newStats;
|
|
417
|
+
onRtcStats.emit(stats);
|
|
418
|
+
onCallQualityStats.emit(callQualityStats);
|
|
419
|
+
});
|
|
420
|
+
};
|
|
421
|
+
let it = window.setInterval(pushStats, interval);
|
|
422
|
+
const clearInterval = () => {
|
|
423
|
+
window.clearInterval(it);
|
|
424
|
+
it = 0;
|
|
425
|
+
};
|
|
426
|
+
const resumeStats = () => {
|
|
427
|
+
if (it === 0) {
|
|
428
|
+
cache = newCache();
|
|
429
|
+
pushStats();
|
|
430
|
+
it = window.setInterval(pushStats, interval);
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
return {
|
|
434
|
+
resetStats: () => {
|
|
435
|
+
clearInterval();
|
|
436
|
+
return resumeStats;
|
|
437
|
+
},
|
|
438
|
+
cleanup: clearInterval
|
|
439
|
+
};
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// src/utils.ts
|
|
443
|
+
import { createSignal } from "@pexip/signal";
|
|
444
|
+
var SIGNAL_PREFIX = "call:peerConnection:stats";
|
|
445
|
+
var REQUIRED_STATS_SIGNAL_KEYS = [
|
|
446
|
+
"onRtcStats",
|
|
447
|
+
"onCallQualityStats",
|
|
448
|
+
"onCallQuality"
|
|
449
|
+
];
|
|
450
|
+
var createStatsSignals = (scope = "") => {
|
|
451
|
+
const signalScope = scope && [scope, ":"].join("");
|
|
452
|
+
return REQUIRED_STATS_SIGNAL_KEYS.reduce(
|
|
453
|
+
(signals, key) => ({
|
|
454
|
+
...signals,
|
|
455
|
+
[key]: createSignal({
|
|
456
|
+
name: `${SIGNAL_PREFIX}:${signalScope}:${key}`,
|
|
457
|
+
allowEmittingWithoutObserver: true
|
|
458
|
+
})
|
|
459
|
+
}),
|
|
460
|
+
{}
|
|
461
|
+
);
|
|
462
|
+
};
|
|
463
|
+
export {
|
|
464
|
+
Quality,
|
|
465
|
+
STATS_SIZE,
|
|
466
|
+
addDeltaStats,
|
|
467
|
+
calculateQuality,
|
|
468
|
+
createResolver,
|
|
469
|
+
createStatsCollector,
|
|
470
|
+
createStatsSignals,
|
|
471
|
+
getQuality,
|
|
472
|
+
inboundAudio,
|
|
473
|
+
inboundVideo,
|
|
474
|
+
outboundAudio,
|
|
475
|
+
outboundVideo,
|
|
476
|
+
statsFrom,
|
|
477
|
+
statsFromRTCPeer
|
|
478
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pexip/peer-connection-stats",
|
|
3
|
+
"version": "17.2.0",
|
|
4
|
+
"description": "Normalizer of RTCStats and related",
|
|
5
|
+
"homepage": "https://gitlab.com/pexip/zoo",
|
|
6
|
+
"bugs": "https://gitlab.com/pexip/zoo/issues",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://gitlab.com/pexip/zoo.git",
|
|
10
|
+
"directory": "src/aquila/packages/peer-connection-stats"
|
|
11
|
+
},
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
|
+
"module": "dist/index.mjs",
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "pexip-bundler build",
|
|
21
|
+
"clean": "rm -fr dist",
|
|
22
|
+
"dev": "pexip-bundler dev",
|
|
23
|
+
"prepack": "yarn build",
|
|
24
|
+
"test": "pexip-bundler test",
|
|
25
|
+
"tsc": "tsc --noEmit",
|
|
26
|
+
"check-format": "prettier --ignore-path=../../.prettierignore --check .",
|
|
27
|
+
"format": "prettier --ignore-path=../../.prettierignore --write .",
|
|
28
|
+
"lint": "yarn run -T eslint --ext .js,.ts,.tsx --report-unused-disable-directives .",
|
|
29
|
+
"typecheck": "yarn tsc --noEmit -p ."
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@pexip/signal": "16.5.0",
|
|
33
|
+
"@pexip/utils": "16.5.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@pexip/bundler": "16.3.0",
|
|
37
|
+
"typescript": "~5.0.0"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public",
|
|
41
|
+
"registry": "https://registry.npmjs.org"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/index.ts
ADDED