@rtcstats/rtcstats-shared 2.4.0 → 2.6.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/compression.js +26 -11
- package/dump.js +87 -9
- package/index.d.ts +1 -0
- package/package.json +1 -1
package/compression.js
CHANGED
|
@@ -175,23 +175,29 @@ export function statsDecompression(baseStatsInput, delta) {
|
|
|
175
175
|
delete newStats[id];
|
|
176
176
|
return;
|
|
177
177
|
}
|
|
178
|
+
// A timestamp carried explicitly in the delta differs from the
|
|
179
|
+
// collapsed top-level one and must not be overwritten below.
|
|
180
|
+
const hasOwnTimestamp = newStats[id] !== undefined &&
|
|
181
|
+
newStats[id].timestamp !== undefined;
|
|
178
182
|
if (!newStats[id]) {
|
|
179
|
-
|
|
183
|
+
// Copy the report so the timestamp assignment below does not
|
|
184
|
+
// modify the base stats.
|
|
185
|
+
newStats[id] = Object.assign({}, baseStats[id]);
|
|
180
186
|
} else {
|
|
181
187
|
const report = baseStats[id];
|
|
182
188
|
Object.keys(report).forEach(name => {
|
|
183
|
-
if (
|
|
189
|
+
if (newStats[id][name] === undefined) {
|
|
184
190
|
newStats[id][name] = report[name];
|
|
185
|
-
} else if (typeof(report[name]) === 'object'
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
});
|
|
191
|
+
} else if (typeof(report[name]) === 'object' &&
|
|
192
|
+
!Array.isArray(newStats[id][name])) {
|
|
193
|
+
// Merge into a copy so the nested object of the
|
|
194
|
+
// base stats is not modified. Arrays are serialized
|
|
195
|
+
// wholesale on change and take the new value.
|
|
196
|
+
newStats[id][name] = Object.assign({}, report[name], newStats[id][name]);
|
|
191
197
|
} // Else: take the new value.
|
|
192
198
|
});
|
|
193
199
|
}
|
|
194
|
-
if (timestamp !== undefined) {
|
|
200
|
+
if (timestamp !== undefined && !hasOwnTimestamp) {
|
|
195
201
|
newStats[id].timestamp = timestamp;
|
|
196
202
|
}
|
|
197
203
|
});
|
|
@@ -367,12 +373,20 @@ const methodTable = {
|
|
|
367
373
|
// getUserMedia and friends.
|
|
368
374
|
'navigator.mediaDevices.getUserMedia': 60,
|
|
369
375
|
'navigator.mediaDevices.getUserMediaOnSuccess': 61,
|
|
370
|
-
|
|
376
|
+
// 62 is not usable. It was originally assigned to
|
|
377
|
+
// getUserMediaOnFailure but a copy-paste bug duplicated the
|
|
378
|
+
// key onto 65, which was intended for getDisplayMediaOnFailure.
|
|
379
|
+
// The last duplicate key wins in an object literal, so released clients
|
|
380
|
+
// have always compressed getUserMediaOnFailure to 65.
|
|
381
|
+
'navigator.mediaDevices.getUserMediaOnFailure': 65,
|
|
371
382
|
'navigator.mediaDevices.getDisplayMedia': 63,
|
|
372
383
|
'navigator.mediaDevices.getDisplayMediaOnSuccess': 64,
|
|
373
|
-
'navigator.mediaDevices.getUserMediaOnFailure': 65,
|
|
374
384
|
'navigator.mediaDevices.enumerateDevices': 66,
|
|
375
385
|
'navigator.mediaDevices.ondevicechange': 67,
|
|
386
|
+
// Decompression-only until the next major version bump, see below.
|
|
387
|
+
// This method is therefore not compressed.
|
|
388
|
+
// 'navigator.mediaDevices.getDisplayMediaOnFailure': 68,
|
|
389
|
+
// Next free method id: 69.
|
|
376
390
|
// MediaStreamTrack methods and events.
|
|
377
391
|
'MediaStreamTrack.stop': 70,
|
|
378
392
|
'MediaStreamTrack.applyConstraints': 71,
|
|
@@ -386,6 +400,7 @@ const methodTable = {
|
|
|
386
400
|
// These should be moved to the compression table at major version bumps.
|
|
387
401
|
const methodTableDecompression = {
|
|
388
402
|
1: 'getStats',
|
|
403
|
+
68: 'navigator.mediaDevices.getDisplayMediaOnFailure',
|
|
389
404
|
};
|
|
390
405
|
const reverseMethodTable = Object.keys(methodTable).reduce((table, method) => {
|
|
391
406
|
table[methodTable[method]] = method;
|
package/dump.js
CHANGED
|
@@ -6,13 +6,57 @@ import {
|
|
|
6
6
|
import {createInternalsTimeSeries} from './timeseries.js';
|
|
7
7
|
import {parseTrackWithStreams} from './utils.js';
|
|
8
8
|
|
|
9
|
+
// Chrome JSON-encodes the state in these events, rtcstats-js writes it bare.
|
|
10
|
+
// https://chromium-review.googlesource.com/c/chromium/src/+/6917903
|
|
11
|
+
const STATE_VALUES = {
|
|
12
|
+
onsignalingstatechange: {
|
|
13
|
+
'"stable"': 'stable',
|
|
14
|
+
'"have-local-offer"': 'have-local-offer',
|
|
15
|
+
'"have-remote-offer"': 'have-remote-offer',
|
|
16
|
+
'"have-local-pranswer"': 'have-local-pranswer',
|
|
17
|
+
'"have-remote-pranswer"': 'have-remote-pranswer',
|
|
18
|
+
'"closed"': 'closed',
|
|
19
|
+
},
|
|
20
|
+
oniceconnectionstatechange: {
|
|
21
|
+
'"new"': 'new',
|
|
22
|
+
'"checking"': 'checking',
|
|
23
|
+
'"connected"': 'connected',
|
|
24
|
+
'"completed"': 'completed',
|
|
25
|
+
'"failed"': 'failed',
|
|
26
|
+
'"disconnected"': 'disconnected',
|
|
27
|
+
'"closed"': 'closed',
|
|
28
|
+
},
|
|
29
|
+
onconnectionstatechange: {
|
|
30
|
+
'"new"': 'new',
|
|
31
|
+
'"connecting"': 'connecting',
|
|
32
|
+
'"connected"': 'connected',
|
|
33
|
+
'"disconnected"': 'disconnected',
|
|
34
|
+
'"failed"': 'failed',
|
|
35
|
+
'"closed"': 'closed',
|
|
36
|
+
},
|
|
37
|
+
onicegatheringstatechange: {
|
|
38
|
+
'"new"': 'new',
|
|
39
|
+
'"gathering"': 'gathering',
|
|
40
|
+
'"complete"': 'complete',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Call before format detection, dumps are frequently stored gzipped.
|
|
45
|
+
export async function maybeUncompressDump(blob) {
|
|
46
|
+
const magic = new Uint8Array(await blob.slice(0, 2).arrayBuffer());
|
|
47
|
+
if (magic[0] !== 0x1f || magic[1] !== 0x8b) {
|
|
48
|
+
return blob;
|
|
49
|
+
}
|
|
50
|
+
return new Response(blob.stream().pipeThrough(new DecompressionStream('gzip'))).blob();
|
|
51
|
+
}
|
|
52
|
+
|
|
9
53
|
export async function detectRTCStatsDump(blob) {
|
|
10
54
|
const magic = await blob.slice(0, 13).text();
|
|
11
55
|
return magic.startsWith('RTCStatsDump\n');
|
|
12
56
|
}
|
|
13
57
|
|
|
14
58
|
export async function detectWebRTCInternalsDump(blob) {
|
|
15
|
-
return (await blob.text())
|
|
59
|
+
return (await blob.slice(0, 1).text()) === '{';
|
|
16
60
|
}
|
|
17
61
|
|
|
18
62
|
export async function readRTCStatsDump(blob) {
|
|
@@ -36,7 +80,8 @@ export async function readRTCStatsDump(blob) {
|
|
|
36
80
|
console.error('Second line must be an object');
|
|
37
81
|
return;
|
|
38
82
|
}
|
|
39
|
-
|
|
83
|
+
// Ensure the client-level connection is listed before the peer connections.
|
|
84
|
+
data.peerConnections = {null: []};
|
|
40
85
|
data.eventSizes = {};
|
|
41
86
|
|
|
42
87
|
const baseStats = {};
|
|
@@ -61,9 +106,12 @@ export async function readRTCStatsDump(blob) {
|
|
|
61
106
|
lastTime = extra.pop() + lastTime;
|
|
62
107
|
const time = new Date(lastTime);
|
|
63
108
|
|
|
109
|
+
value = STATE_VALUES[method]?.[value] || value;
|
|
110
|
+
|
|
64
111
|
if (method === 'getStats') { // delta-compressed stats
|
|
112
|
+
// statsDecompression does not modify its base stats argument.
|
|
65
113
|
value = statsDecompression(baseStats[connection_id], value);
|
|
66
|
-
baseStats[connection_id] =
|
|
114
|
+
baseStats[connection_id] = value;
|
|
67
115
|
} else if (method === 'setLocalDescription' && value &&
|
|
68
116
|
// Workaround for Chrome <145 bug: https://source.chromium.org/chromium/chromium/src/+/4a2a384e43ea163127219548087e949fb00fa562
|
|
69
117
|
connection_id !== 'undefined-undefined') {
|
|
@@ -115,6 +163,9 @@ export async function readRTCStatsDump(blob) {
|
|
|
115
163
|
method,
|
|
116
164
|
});
|
|
117
165
|
}
|
|
166
|
+
if (!data.peerConnections['null'].length) {
|
|
167
|
+
delete data.peerConnections['null'];
|
|
168
|
+
}
|
|
118
169
|
return data;
|
|
119
170
|
}
|
|
120
171
|
|
|
@@ -215,12 +266,16 @@ export function internalsToRtcstats(data) {
|
|
|
215
266
|
return {peerConnections, eventSizes: {}};
|
|
216
267
|
}
|
|
217
268
|
|
|
218
|
-
//
|
|
269
|
+
// Uncompress if needed, auto-detect the dump format and dispatch.
|
|
219
270
|
export async function readDump(blob) {
|
|
220
|
-
|
|
221
|
-
|
|
271
|
+
const dump = await maybeUncompressDump(blob);
|
|
272
|
+
if (await detectRTCStatsDump(dump)) {
|
|
273
|
+
return readRTCStatsDump(dump);
|
|
274
|
+
}
|
|
275
|
+
if (!await detectWebRTCInternalsDump(dump)) {
|
|
276
|
+
throw new Error('Unrecognized dump format');
|
|
222
277
|
}
|
|
223
|
-
return internalsToRtcstats(await readWebRTCInternalsDump(
|
|
278
|
+
return internalsToRtcstats(await readWebRTCInternalsDump(dump));
|
|
224
279
|
}
|
|
225
280
|
|
|
226
281
|
export async function extractTracks(peerConnectionTrace) {
|
|
@@ -235,7 +290,10 @@ export async function extractTracks(peerConnectionTrace) {
|
|
|
235
290
|
const trackInformation = parseTrackWithStreams(traceEvent.value);
|
|
236
291
|
trackInformation.startTime = traceEvent.timestamp;
|
|
237
292
|
trackInformation.direction = 'inbound';
|
|
238
|
-
tracks.
|
|
293
|
+
if (tracks.find(info => info.direction === 'inbound' &&
|
|
294
|
+
info.id === trackInformation.id) === undefined) {
|
|
295
|
+
tracks.push(trackInformation);
|
|
296
|
+
}
|
|
239
297
|
} else if (traceEvent.type === 'addTransceiver') {
|
|
240
298
|
if (typeof traceEvent.value[0] === 'string') {
|
|
241
299
|
// TODO: if we pass a kind here, how do we determine the track id?
|
|
@@ -306,7 +364,27 @@ export async function extractTracks(peerConnectionTrace) {
|
|
|
306
364
|
label: value.receiver.track,
|
|
307
365
|
streams: value.receiver.streams,
|
|
308
366
|
};
|
|
309
|
-
tracks.
|
|
367
|
+
if (tracks.find(info => info.id === trackInformation.id) === undefined) {
|
|
368
|
+
tracks.push(trackInformation);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
} else if (traceEvent.type === 'transceiverModified') {
|
|
372
|
+
// A transceiver created locally only starts receiving once the
|
|
373
|
+
// negotiation is done, which is the only signal for inbound tracks
|
|
374
|
+
// in dumps from Chrome versions without the ontrack event.
|
|
375
|
+
const {value} = traceEvent;
|
|
376
|
+
if (value.currentDirection?.includes('recv') && value.receiver?.track) {
|
|
377
|
+
const trackInformation = {
|
|
378
|
+
startTime: traceEvent.timestamp,
|
|
379
|
+
direction: 'inbound',
|
|
380
|
+
kind: value.kind,
|
|
381
|
+
id: value.receiver.track,
|
|
382
|
+
label: value.receiver.track,
|
|
383
|
+
streams: value.receiver.streams,
|
|
384
|
+
};
|
|
385
|
+
if (tracks.find(info => info.id === trackInformation.id) === undefined) {
|
|
386
|
+
tracks.push(trackInformation);
|
|
387
|
+
}
|
|
310
388
|
}
|
|
311
389
|
}
|
|
312
390
|
}
|
package/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Type definitions for @rtcstats/rtcstats-shared
|
|
2
2
|
|
|
3
|
+
export async function maybeUncompressDump(blob: Blob): Promise<Blob>;
|
|
3
4
|
export async function detectWebRTCInternalsDump(blob: Blob): Promise<boolean>;
|
|
4
5
|
export async function readWebRTCInternalsDump(blob: Blob): Promise<object>;
|
|
5
6
|
export async function detectRTCStatsDump(blob: Blob): Promise<boolean>;
|