@decartai/sdk 0.0.67 → 0.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/README.md +55 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +43 -28
- package/dist/process/client.js +1 -3
- package/dist/process/request.js +1 -3
- package/dist/queue/client.js +1 -3
- package/dist/queue/polling.js +1 -2
- package/dist/queue/request.js +1 -3
- package/dist/realtime/client.d.ts +23 -13
- package/dist/realtime/client.js +74 -244
- package/dist/realtime/config-realtime.js +49 -0
- package/dist/realtime/event-buffer.js +1 -3
- package/dist/realtime/initial-state-gate.js +21 -0
- package/dist/realtime/media-channel.js +82 -0
- package/dist/realtime/methods.js +12 -42
- package/dist/realtime/mirror-stream.js +1 -2
- package/dist/realtime/observability/diagnostics.d.ts +39 -0
- package/dist/realtime/observability/livekit-stats-provider.js +25 -0
- package/dist/realtime/observability/realtime-observability.js +173 -0
- package/dist/realtime/{telemetry-reporter.js → observability/telemetry-reporter.js} +12 -31
- package/dist/realtime/observability/webrtc-stats.d.ts +148 -0
- package/dist/realtime/observability/webrtc-stats.js +276 -0
- package/dist/realtime/signaling-channel.js +286 -0
- package/dist/realtime/stream-session.js +252 -0
- package/dist/realtime/subscribe-client.d.ts +2 -1
- package/dist/realtime/subscribe-client.js +115 -11
- package/dist/realtime/types.d.ts +25 -1
- package/dist/shared/model.d.ts +11 -1
- package/dist/shared/model.js +51 -14
- package/dist/shared/request.js +1 -3
- package/dist/shared/types.js +1 -3
- package/dist/tokens/client.js +1 -3
- package/dist/utils/env.js +1 -2
- package/dist/utils/errors.js +1 -2
- package/dist/utils/logger.js +1 -2
- package/dist/utils/media.js +43 -0
- package/dist/utils/platform.js +13 -0
- package/dist/utils/user-agent.js +1 -3
- package/dist/version.js +1 -2
- package/package.json +2 -1
- package/dist/realtime/diagnostics.d.ts +0 -78
- package/dist/realtime/webrtc-connection.js +0 -501
- package/dist/realtime/webrtc-manager.js +0 -189
- package/dist/realtime/webrtc-stats.d.ts +0 -59
- package/dist/realtime/webrtc-stats.js +0 -154
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/realtime/observability/livekit-stats-provider.ts
|
|
2
|
+
function createLiveKitStatsProvider(room) {
|
|
3
|
+
let uid = 0;
|
|
4
|
+
const collectFromTrack = async (track, entries) => {
|
|
5
|
+
if (!track) return;
|
|
6
|
+
let report;
|
|
7
|
+
try {
|
|
8
|
+
report = await track.getRTCStatsReport();
|
|
9
|
+
} catch {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (!report) return;
|
|
13
|
+
report.forEach((stat, id) => {
|
|
14
|
+
entries.push([`${id}#${uid++}`, stat]);
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
return { async getStats() {
|
|
18
|
+
const entries = [];
|
|
19
|
+
for (const pub of room.localParticipant.trackPublications.values()) await collectFromTrack(pub.track, entries);
|
|
20
|
+
for (const participant of room.remoteParticipants.values()) for (const pub of participant.trackPublications.values()) await collectFromTrack(pub.track, entries);
|
|
21
|
+
return new Map(entries);
|
|
22
|
+
} };
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { createLiveKitStatsProvider };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { REALTIME_CONFIG } from "../config-realtime.js";
|
|
2
|
+
import { createLiveKitStatsProvider } from "./livekit-stats-provider.js";
|
|
3
|
+
import { NullTelemetryReporter, TelemetryReporter } from "./telemetry-reporter.js";
|
|
4
|
+
import { WebRTCStatsCollector } from "./webrtc-stats.js";
|
|
5
|
+
//#region src/realtime/observability/realtime-observability.ts
|
|
6
|
+
var RealtimeObservability = class {
|
|
7
|
+
telemetryReporter = new NullTelemetryReporter();
|
|
8
|
+
telemetryReporterReady = false;
|
|
9
|
+
pendingTelemetryDiagnostics = [];
|
|
10
|
+
statsCollector = null;
|
|
11
|
+
statsCollectorSource = null;
|
|
12
|
+
liveKitRoom = null;
|
|
13
|
+
videoStalled = false;
|
|
14
|
+
stallStartMs = 0;
|
|
15
|
+
connectionBreakdown = null;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.options = options;
|
|
18
|
+
}
|
|
19
|
+
diagnostic(name, data, timestamp = Date.now()) {
|
|
20
|
+
this.options.logger.debug(name, data);
|
|
21
|
+
this.options.onDiagnostic?.({
|
|
22
|
+
name,
|
|
23
|
+
data
|
|
24
|
+
});
|
|
25
|
+
this.addTelemetryDiagnostic(name, data, timestamp);
|
|
26
|
+
}
|
|
27
|
+
beginConnectionBreakdown(attempt, initialImageSizeKb) {
|
|
28
|
+
this.connectionBreakdown = {
|
|
29
|
+
attempt,
|
|
30
|
+
connectStartedAt: Date.now(),
|
|
31
|
+
initialImageSizeKb,
|
|
32
|
+
phases: /* @__PURE__ */ new Map()
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
startPhase(name) {
|
|
36
|
+
if (!this.connectionBreakdown) return;
|
|
37
|
+
this.connectionBreakdown.phases.set(name, { startedAt: Date.now() });
|
|
38
|
+
}
|
|
39
|
+
endPhase(name, opts) {
|
|
40
|
+
if (!this.connectionBreakdown) return;
|
|
41
|
+
const entry = this.connectionBreakdown.phases.get(name);
|
|
42
|
+
if (!entry) {
|
|
43
|
+
this.options.logger.warn("observability: endPhase called for unknown phase", { phase: name });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
entry.endedAt = Date.now();
|
|
47
|
+
entry.success = opts.success;
|
|
48
|
+
if (opts.error !== void 0) entry.error = opts.error;
|
|
49
|
+
}
|
|
50
|
+
finishConnectionBreakdown(opts) {
|
|
51
|
+
const buffer = this.connectionBreakdown;
|
|
52
|
+
if (!buffer) return;
|
|
53
|
+
this.connectionBreakdown = null;
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
const phases = [];
|
|
56
|
+
for (const [phase, entry] of buffer.phases) {
|
|
57
|
+
const unfinished = entry.endedAt === void 0;
|
|
58
|
+
const endedAt = entry.endedAt ?? now;
|
|
59
|
+
const success = entry.success ?? false;
|
|
60
|
+
const error = entry.error ?? (unfinished && !opts.success ? opts.error : void 0);
|
|
61
|
+
phases.push({
|
|
62
|
+
phase,
|
|
63
|
+
durationMs: endedAt - entry.startedAt,
|
|
64
|
+
success,
|
|
65
|
+
...error !== void 0 ? { error } : {}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
this.diagnostic("client-session-connection-breakdown", {
|
|
69
|
+
attempt: buffer.attempt,
|
|
70
|
+
success: opts.success,
|
|
71
|
+
totalDurationMs: now - buffer.connectStartedAt,
|
|
72
|
+
initialImageSizeKb: buffer.initialImageSizeKb,
|
|
73
|
+
phases,
|
|
74
|
+
...opts.error !== void 0 ? { error: opts.error } : {}
|
|
75
|
+
}, now);
|
|
76
|
+
}
|
|
77
|
+
sessionStarted(sessionId) {
|
|
78
|
+
if (!this.options.telemetryEnabled) return;
|
|
79
|
+
if (this.telemetryReporterReady) this.telemetryReporter.stop();
|
|
80
|
+
const reporter = new TelemetryReporter({
|
|
81
|
+
apiKey: this.options.apiKey,
|
|
82
|
+
sessionId,
|
|
83
|
+
model: this.options.model,
|
|
84
|
+
integration: this.options.integration,
|
|
85
|
+
logger: this.options.logger
|
|
86
|
+
});
|
|
87
|
+
reporter.start();
|
|
88
|
+
this.telemetryReporter = reporter;
|
|
89
|
+
this.telemetryReporterReady = true;
|
|
90
|
+
for (const diagnostic of this.pendingTelemetryDiagnostics) this.telemetryReporter.addDiagnostic(diagnostic);
|
|
91
|
+
this.pendingTelemetryDiagnostics.length = 0;
|
|
92
|
+
}
|
|
93
|
+
setStatsProvider(source) {
|
|
94
|
+
if (!source) {
|
|
95
|
+
this.stopStats();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (source === this.statsCollectorSource) return;
|
|
99
|
+
this.stopStats();
|
|
100
|
+
this.resetStallDetection();
|
|
101
|
+
this.statsCollectorSource = source;
|
|
102
|
+
if (!this.options.telemetryEnabled && !this.options.onStats) return;
|
|
103
|
+
this.statsCollector = new WebRTCStatsCollector();
|
|
104
|
+
this.statsCollector.start(source, (stats) => this.handleStats(stats));
|
|
105
|
+
}
|
|
106
|
+
setLiveKitRoom(room) {
|
|
107
|
+
if (!room) {
|
|
108
|
+
this.liveKitRoom = null;
|
|
109
|
+
this.setStatsProvider(null);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (room === this.liveKitRoom) return;
|
|
113
|
+
this.setStatsProvider(createLiveKitStatsProvider(room));
|
|
114
|
+
this.liveKitRoom = room;
|
|
115
|
+
}
|
|
116
|
+
stopStats() {
|
|
117
|
+
this.statsCollector?.stop();
|
|
118
|
+
this.statsCollector = null;
|
|
119
|
+
this.statsCollectorSource = null;
|
|
120
|
+
this.liveKitRoom = null;
|
|
121
|
+
this.resetStallDetection();
|
|
122
|
+
}
|
|
123
|
+
stop() {
|
|
124
|
+
this.stopStats();
|
|
125
|
+
this.telemetryReporter.stop();
|
|
126
|
+
this.telemetryReporter = new NullTelemetryReporter();
|
|
127
|
+
this.telemetryReporterReady = false;
|
|
128
|
+
this.pendingTelemetryDiagnostics.length = 0;
|
|
129
|
+
this.connectionBreakdown = null;
|
|
130
|
+
}
|
|
131
|
+
handleStats(stats) {
|
|
132
|
+
this.options.onStats?.(stats);
|
|
133
|
+
this.telemetryReporter.addStats(stats);
|
|
134
|
+
this.detectVideoStall(stats);
|
|
135
|
+
}
|
|
136
|
+
detectVideoStall(stats) {
|
|
137
|
+
const fps = stats.video?.framesPerSecond ?? 0;
|
|
138
|
+
if (!this.videoStalled && stats.video && fps < REALTIME_CONFIG.observability.stallFpsThreshold) {
|
|
139
|
+
this.videoStalled = true;
|
|
140
|
+
this.stallStartMs = Date.now();
|
|
141
|
+
this.diagnostic("videoStall", {
|
|
142
|
+
stalled: true,
|
|
143
|
+
durationMs: 0
|
|
144
|
+
}, this.stallStartMs);
|
|
145
|
+
} else if (this.videoStalled && fps >= REALTIME_CONFIG.observability.stallFpsThreshold) {
|
|
146
|
+
const durationMs = Date.now() - this.stallStartMs;
|
|
147
|
+
this.videoStalled = false;
|
|
148
|
+
this.diagnostic("videoStall", {
|
|
149
|
+
stalled: false,
|
|
150
|
+
durationMs
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
addTelemetryDiagnostic(name, data, timestamp) {
|
|
155
|
+
if (!this.options.telemetryEnabled) return;
|
|
156
|
+
const diagnostic = {
|
|
157
|
+
name,
|
|
158
|
+
data,
|
|
159
|
+
timestamp
|
|
160
|
+
};
|
|
161
|
+
if (!this.telemetryReporterReady) {
|
|
162
|
+
this.pendingTelemetryDiagnostics.push(diagnostic);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
this.telemetryReporter.addDiagnostic(diagnostic);
|
|
166
|
+
}
|
|
167
|
+
resetStallDetection() {
|
|
168
|
+
this.videoStalled = false;
|
|
169
|
+
this.stallStartMs = 0;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
//#endregion
|
|
173
|
+
export { RealtimeObservability };
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
import { VERSION } from "
|
|
2
|
-
import { buildAuthHeaders } from "
|
|
3
|
-
|
|
4
|
-
//#region src/realtime/telemetry-reporter.ts
|
|
5
|
-
const DEFAULT_REPORT_INTERVAL_MS = 1e4;
|
|
6
|
-
const TELEMETRY_URL = "https://platform.decart.ai/api/v1/telemetry";
|
|
7
|
-
/**
|
|
8
|
-
* Maximum number of items per array (stats / diagnostics) in a single report.
|
|
9
|
-
* Matches the backend Zod schema which enforces `z.array().max(120)`.
|
|
10
|
-
*/
|
|
11
|
-
const MAX_ITEMS_PER_REPORT = 120;
|
|
1
|
+
import { VERSION } from "../../version.js";
|
|
2
|
+
import { buildAuthHeaders } from "../../shared/request.js";
|
|
3
|
+
import { REALTIME_CONFIG } from "../config-realtime.js";
|
|
4
|
+
//#region src/realtime/observability/telemetry-reporter.ts
|
|
12
5
|
/** No-op reporter that silently discards all data. Used when telemetry is disabled. */
|
|
13
6
|
var NullTelemetryReporter = class {
|
|
14
7
|
start() {}
|
|
@@ -22,7 +15,6 @@ var TelemetryReporter = class {
|
|
|
22
15
|
sessionId;
|
|
23
16
|
model;
|
|
24
17
|
integration;
|
|
25
|
-
logger;
|
|
26
18
|
reportIntervalMs;
|
|
27
19
|
intervalId = null;
|
|
28
20
|
statsBuffer = [];
|
|
@@ -32,8 +24,7 @@ var TelemetryReporter = class {
|
|
|
32
24
|
this.sessionId = options.sessionId;
|
|
33
25
|
this.model = options.model;
|
|
34
26
|
this.integration = options.integration;
|
|
35
|
-
this.
|
|
36
|
-
this.reportIntervalMs = options.reportIntervalMs ?? DEFAULT_REPORT_INTERVAL_MS;
|
|
27
|
+
this.reportIntervalMs = options.reportIntervalMs ?? REALTIME_CONFIG.observability.telemetryReportIntervalMs;
|
|
37
28
|
}
|
|
38
29
|
/** Start the periodic reporting timer. */
|
|
39
30
|
start() {
|
|
@@ -62,7 +53,7 @@ var TelemetryReporter = class {
|
|
|
62
53
|
this.diagnosticsBuffer = [];
|
|
63
54
|
}
|
|
64
55
|
/**
|
|
65
|
-
* Build a single chunk from the front of the buffers, respecting
|
|
56
|
+
* Build a single chunk from the front of the buffers, respecting the configured report item cap.
|
|
66
57
|
* Returns null when both buffers are empty.
|
|
67
58
|
*/
|
|
68
59
|
createReportChunk() {
|
|
@@ -79,8 +70,8 @@ var TelemetryReporter = class {
|
|
|
79
70
|
sdkVersion: VERSION,
|
|
80
71
|
...this.model ? { model: this.model } : {},
|
|
81
72
|
tags,
|
|
82
|
-
stats: this.statsBuffer.splice(0,
|
|
83
|
-
diagnostics: this.diagnosticsBuffer.splice(0,
|
|
73
|
+
stats: this.statsBuffer.splice(0, REALTIME_CONFIG.observability.telemetryMaxItemsPerReport),
|
|
74
|
+
diagnostics: this.diagnosticsBuffer.splice(0, REALTIME_CONFIG.observability.telemetryMaxItemsPerReport)
|
|
84
75
|
};
|
|
85
76
|
}
|
|
86
77
|
sendReport() {
|
|
@@ -95,25 +86,15 @@ var TelemetryReporter = class {
|
|
|
95
86
|
};
|
|
96
87
|
let chunk = this.createReportChunk();
|
|
97
88
|
while (chunk !== null) {
|
|
98
|
-
fetch(
|
|
89
|
+
fetch(REALTIME_CONFIG.observability.telemetryUrl, {
|
|
99
90
|
method: "POST",
|
|
100
91
|
headers: commonHeaders,
|
|
101
92
|
body: JSON.stringify(chunk)
|
|
102
|
-
}).
|
|
103
|
-
if (!response.ok) this.logger.warn("Telemetry report rejected", {
|
|
104
|
-
status: response.status,
|
|
105
|
-
statusText: response.statusText
|
|
106
|
-
});
|
|
107
|
-
}).catch((error) => {
|
|
108
|
-
this.logger.debug("Telemetry report failed", { error: String(error) });
|
|
109
|
-
});
|
|
93
|
+
}).catch(() => {});
|
|
110
94
|
chunk = this.createReportChunk();
|
|
111
95
|
}
|
|
112
|
-
} catch
|
|
113
|
-
this.logger.debug("Telemetry report failed", { error: String(error) });
|
|
114
|
-
}
|
|
96
|
+
} catch {}
|
|
115
97
|
}
|
|
116
98
|
};
|
|
117
|
-
|
|
118
99
|
//#endregion
|
|
119
|
-
export { NullTelemetryReporter, TelemetryReporter };
|
|
100
|
+
export { NullTelemetryReporter, TelemetryReporter };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
//#region src/realtime/observability/webrtc-stats.d.ts
|
|
2
|
+
type WebRTCStats = {
|
|
3
|
+
timestamp: number;
|
|
4
|
+
video: {
|
|
5
|
+
framesDecoded: number;
|
|
6
|
+
framesDropped: number;
|
|
7
|
+
framesReceived: number;
|
|
8
|
+
keyFramesDecoded: number;
|
|
9
|
+
framesPerSecond: number;
|
|
10
|
+
frameWidth: number;
|
|
11
|
+
frameHeight: number;
|
|
12
|
+
bytesReceived: number;
|
|
13
|
+
packetsReceived: number;
|
|
14
|
+
packetsLost: number;
|
|
15
|
+
jitter: number;
|
|
16
|
+
/** Estimated inbound bitrate in bits/sec, computed from bytesReceived delta. */
|
|
17
|
+
bitrate: number;
|
|
18
|
+
freezeCount: number;
|
|
19
|
+
totalFreezesDuration: number;
|
|
20
|
+
/** Delta: packets lost since previous sample. */
|
|
21
|
+
packetsLostDelta: number;
|
|
22
|
+
/** Delta: frames dropped since previous sample. */
|
|
23
|
+
framesDroppedDelta: number;
|
|
24
|
+
/** Delta: freeze count since previous sample. */
|
|
25
|
+
freezeCountDelta: number;
|
|
26
|
+
/** Delta: freeze duration (seconds) since previous sample. */
|
|
27
|
+
freezeDurationDelta: number;
|
|
28
|
+
/** NACKs sent to the sender (requesting packet retransmission). */
|
|
29
|
+
nackCount: number;
|
|
30
|
+
nackCountDelta: number;
|
|
31
|
+
/** PLIs sent to the sender (full frame retransmission request). */
|
|
32
|
+
pliCount: number;
|
|
33
|
+
/** FIRs sent to the sender (forced intra-refresh request). */
|
|
34
|
+
firCount: number;
|
|
35
|
+
/**
|
|
36
|
+
* Average decode time (ms/frame), cumulative since stream start.
|
|
37
|
+
* Derived from totalDecodeTime/framesDecoded. `null` if the browser
|
|
38
|
+
* hasn't produced the underlying counters yet.
|
|
39
|
+
*/
|
|
40
|
+
avgDecodeTimeMs: number | null;
|
|
41
|
+
/** Average jitter-buffer time (ms/frame emitted). Cumulative. */
|
|
42
|
+
avgJitterBufferMs: number | null;
|
|
43
|
+
/**
|
|
44
|
+
* Average total processing delay (ms/frame decoded) — from network
|
|
45
|
+
* receive to decoder output. Cumulative.
|
|
46
|
+
*/
|
|
47
|
+
avgProcessingDelayMs: number | null;
|
|
48
|
+
/** Average inter-frame delay at the decoder (ms). */
|
|
49
|
+
avgInterFrameDelayMs: number | null;
|
|
50
|
+
/**
|
|
51
|
+
* Std-dev of inter-frame delay (ms), computed from
|
|
52
|
+
* totalInterFrameDelay + totalSquaredInterFrameDelay.
|
|
53
|
+
*/
|
|
54
|
+
interFrameDelayStdDevMs: number | null;
|
|
55
|
+
/** Current target delay of the jitter buffer (ms). */
|
|
56
|
+
jitterBufferTargetDelayMs: number | null;
|
|
57
|
+
/** Current minimum delay of the jitter buffer (ms). */
|
|
58
|
+
jitterBufferMinimumDelayMs: number | null;
|
|
59
|
+
/** Which decoder the browser picked (e.g. "libvpx", "ExternalDecoder"). */
|
|
60
|
+
decoderImplementation: string;
|
|
61
|
+
} | null;
|
|
62
|
+
audio: {
|
|
63
|
+
bytesReceived: number;
|
|
64
|
+
packetsReceived: number;
|
|
65
|
+
packetsLost: number;
|
|
66
|
+
jitter: number;
|
|
67
|
+
/** Estimated inbound bitrate in bits/sec, computed from bytesReceived delta. */
|
|
68
|
+
bitrate: number;
|
|
69
|
+
/** Delta: packets lost since previous sample. */
|
|
70
|
+
packetsLostDelta: number;
|
|
71
|
+
} | null;
|
|
72
|
+
/** Outbound video track stats (from the local camera/screen share being sent). */
|
|
73
|
+
outboundVideo: {
|
|
74
|
+
/** Why the encoder is limiting quality: "none", "bandwidth", "cpu", or "other". */
|
|
75
|
+
qualityLimitationReason: string;
|
|
76
|
+
/** Cumulative time (seconds) spent in each quality limitation state. */
|
|
77
|
+
qualityLimitationDurations: Record<string, number>;
|
|
78
|
+
bytesSent: number;
|
|
79
|
+
packetsSent: number;
|
|
80
|
+
framesPerSecond: number;
|
|
81
|
+
frameWidth: number;
|
|
82
|
+
frameHeight: number;
|
|
83
|
+
/** Estimated outbound bitrate in bits/sec, computed from bytesSent delta. */
|
|
84
|
+
bitrate: number;
|
|
85
|
+
/** Encoder's current target bitrate in kbps (BWE output). */
|
|
86
|
+
targetBitrateKbps: number | null;
|
|
87
|
+
/** Average encode time per frame (ms), cumulative. */
|
|
88
|
+
avgEncodeTimeMs: number | null;
|
|
89
|
+
/** Average packet send delay (ms), cumulative. */
|
|
90
|
+
avgPacketSendDelayMs: number | null;
|
|
91
|
+
/** Average quantization parameter across encoded frames (lower is better). */
|
|
92
|
+
avgQp: number | null;
|
|
93
|
+
/** NACKs received from receiver (retransmission requests). */
|
|
94
|
+
nackCount: number;
|
|
95
|
+
/** PLIs received from receiver. */
|
|
96
|
+
pliCount: number;
|
|
97
|
+
/** FIRs received from receiver. */
|
|
98
|
+
firCount: number;
|
|
99
|
+
retransmittedBytesSent: number;
|
|
100
|
+
retransmittedPacketsSent: number;
|
|
101
|
+
/** Which encoder the browser picked (e.g. "libvpx", "SimulcastEncoderAdapter"). */
|
|
102
|
+
encoderImplementation: string;
|
|
103
|
+
} | null;
|
|
104
|
+
/**
|
|
105
|
+
* Remote-inbound stats — what the far end reports *about its reception
|
|
106
|
+
* of our outbound stream*. Answers "does the server think we're lossy?"
|
|
107
|
+
* independently of what we see locally. Populated from
|
|
108
|
+
* `remote-inbound-rtp` reports.
|
|
109
|
+
*/
|
|
110
|
+
remoteInbound: {
|
|
111
|
+
fractionLost: number | null;
|
|
112
|
+
/** In seconds. */
|
|
113
|
+
jitter: number | null;
|
|
114
|
+
/** In seconds. Often more accurate than connection.currentRoundTripTime. */
|
|
115
|
+
roundTripTime: number | null;
|
|
116
|
+
} | null;
|
|
117
|
+
connection: {
|
|
118
|
+
/** Current round-trip time in seconds, or null if unavailable. */
|
|
119
|
+
currentRoundTripTime: number | null;
|
|
120
|
+
/** Available outgoing bitrate estimate in bits/sec, or null if unavailable. */
|
|
121
|
+
availableOutgoingBitrate: number | null;
|
|
122
|
+
/**
|
|
123
|
+
* Selected ICE candidate pairs (usually one per PC). Populated from
|
|
124
|
+
* the `candidate-pair` report with state="succeeded" plus the matching
|
|
125
|
+
* `local-candidate` / `remote-candidate` lookups. Lets diagnostic tools
|
|
126
|
+
* tell direct-UDP sessions from TURN-relayed ones — the path affects
|
|
127
|
+
* jitter and failure modes, so this is essential signal for
|
|
128
|
+
* benchmarking and incident triage.
|
|
129
|
+
*/
|
|
130
|
+
selectedCandidatePairs: IceCandidatePair[];
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
/** One side of an ICE candidate pair (sender or receiver). */
|
|
134
|
+
type IceCandidateInfo = {
|
|
135
|
+
/** "host" | "srflx" | "prflx" | "relay" */
|
|
136
|
+
candidateType: string;
|
|
137
|
+
/** IP (v4 or v6). May be `""` for mDNS-obfuscated host candidates. */
|
|
138
|
+
address: string;
|
|
139
|
+
port: number;
|
|
140
|
+
/** "udp" | "tcp" */
|
|
141
|
+
protocol: string;
|
|
142
|
+
};
|
|
143
|
+
type IceCandidatePair = {
|
|
144
|
+
local: IceCandidateInfo;
|
|
145
|
+
remote: IceCandidateInfo;
|
|
146
|
+
};
|
|
147
|
+
//#endregion
|
|
148
|
+
export { WebRTCStats };
|