@rtcstats/rtcstats-shared 2.5.0 → 2.6.1
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/dump.js +87 -10
- package/index.d.ts +1 -0
- package/package.json +1 -1
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,6 +106,8 @@ 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
|
|
65
112
|
// statsDecompression does not modify its base stats argument.
|
|
66
113
|
value = statsDecompression(baseStats[connection_id], value);
|
|
@@ -116,6 +163,9 @@ export async function readRTCStatsDump(blob) {
|
|
|
116
163
|
method,
|
|
117
164
|
});
|
|
118
165
|
}
|
|
166
|
+
if (!data.peerConnections['null'].length) {
|
|
167
|
+
delete data.peerConnections['null'];
|
|
168
|
+
}
|
|
119
169
|
return data;
|
|
120
170
|
}
|
|
121
171
|
|
|
@@ -151,11 +201,11 @@ export function internalsToRtcstats(data) {
|
|
|
151
201
|
const tracks = [];
|
|
152
202
|
if (gum.audio_track_info) {
|
|
153
203
|
const t = JSON.parse(gum.audio_track_info);
|
|
154
|
-
tracks.push(['audio', t.id, t.label, gum.stream_id]);
|
|
204
|
+
if (t) tracks.push(['audio', t.id, t.label, gum.stream_id]);
|
|
155
205
|
}
|
|
156
206
|
if (gum.video_track_info) {
|
|
157
207
|
const t = JSON.parse(gum.video_track_info);
|
|
158
|
-
tracks.push(['video', t.id, t.label, gum.stream_id]);
|
|
208
|
+
if (t) tracks.push(['video', t.id, t.label, gum.stream_id]);
|
|
159
209
|
}
|
|
160
210
|
clientTrace.push({type: baseType + 'OnSuccess', value: tracks, timestamp: gum.timestamp, extra: []});
|
|
161
211
|
} else if (gum.audio !== undefined || gum.video !== undefined) {
|
|
@@ -216,12 +266,16 @@ export function internalsToRtcstats(data) {
|
|
|
216
266
|
return {peerConnections, eventSizes: {}};
|
|
217
267
|
}
|
|
218
268
|
|
|
219
|
-
//
|
|
269
|
+
// Uncompress if needed, auto-detect the dump format and dispatch.
|
|
220
270
|
export async function readDump(blob) {
|
|
221
|
-
|
|
222
|
-
|
|
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');
|
|
223
277
|
}
|
|
224
|
-
return internalsToRtcstats(await readWebRTCInternalsDump(
|
|
278
|
+
return internalsToRtcstats(await readWebRTCInternalsDump(dump));
|
|
225
279
|
}
|
|
226
280
|
|
|
227
281
|
export async function extractTracks(peerConnectionTrace) {
|
|
@@ -236,7 +290,10 @@ export async function extractTracks(peerConnectionTrace) {
|
|
|
236
290
|
const trackInformation = parseTrackWithStreams(traceEvent.value);
|
|
237
291
|
trackInformation.startTime = traceEvent.timestamp;
|
|
238
292
|
trackInformation.direction = 'inbound';
|
|
239
|
-
tracks.
|
|
293
|
+
if (tracks.find(info => info.direction === 'inbound' &&
|
|
294
|
+
info.id === trackInformation.id) === undefined) {
|
|
295
|
+
tracks.push(trackInformation);
|
|
296
|
+
}
|
|
240
297
|
} else if (traceEvent.type === 'addTransceiver') {
|
|
241
298
|
if (typeof traceEvent.value[0] === 'string') {
|
|
242
299
|
// TODO: if we pass a kind here, how do we determine the track id?
|
|
@@ -307,7 +364,27 @@ export async function extractTracks(peerConnectionTrace) {
|
|
|
307
364
|
label: value.receiver.track,
|
|
308
365
|
streams: value.receiver.streams,
|
|
309
366
|
};
|
|
310
|
-
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
|
+
}
|
|
311
388
|
}
|
|
312
389
|
}
|
|
313
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>;
|