@camstack/ui-library 1.2.180 → 1.2.182
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/dist/composites/camera-stream-player.d.ts +15 -2
- package/dist/composites/cap-settings/camera-grid-layout.d.ts +8 -0
- package/dist/composites/index.d.ts +4 -0
- package/dist/composites/stream-debug/stream-debug-overlay.d.ts +34 -0
- package/dist/composites/stream-debug/webrtc-debug-stats.d.ts +129 -0
- package/dist/generated/system-hooks.d.ts +2 -0
- package/dist/hooks/use-device-webrtc.d.ts +15 -1
- package/dist/index.cjs +688 -55
- package/dist/index.js +685 -56
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7213,6 +7213,8 @@ var useWebrtcSessionCloseSession = trpc.webrtcSession.closeSession.useMutation;
|
|
|
7213
7213
|
var useWebrtcSessionHasAdaptiveBitrate = trpc.webrtcSession.hasAdaptiveBitrate.useQuery;
|
|
7214
7214
|
/** Generated alias around `trpc.webrtcSession.getSessionState.useQuery`. */
|
|
7215
7215
|
var useWebrtcSessionGetSessionState = trpc.webrtcSession.getSessionState.useQuery;
|
|
7216
|
+
/** Generated alias around `trpc.webrtcSession.getSessionDebug.useQuery`. */
|
|
7217
|
+
var useWebrtcSessionGetSessionDebug = trpc.webrtcSession.getSessionDebug.useQuery;
|
|
7216
7218
|
/** Generated alias around `trpc.zoneAnalytics.getCurrentSnapshot.useQuery`. */
|
|
7217
7219
|
var useZoneAnalyticsGetCurrentSnapshot = trpc.zoneAnalytics.getCurrentSnapshot.useQuery;
|
|
7218
7220
|
/** Generated alias around `trpc.zoneAnalytics.getCurrentSnapshotBatch.useQuery`. */
|
|
@@ -16642,7 +16644,8 @@ function readProfileOffer(value) {
|
|
|
16642
16644
|
profile,
|
|
16643
16645
|
offered,
|
|
16644
16646
|
missingSources,
|
|
16645
|
-
canvas: typeof value["canvas"] === "string" ? value["canvas"] : ""
|
|
16647
|
+
canvas: typeof value["canvas"] === "string" ? value["canvas"] : "",
|
|
16648
|
+
published: typeof value["published"] === "boolean" ? value["published"] : offered
|
|
16646
16649
|
};
|
|
16647
16650
|
}
|
|
16648
16651
|
/**
|
|
@@ -16887,7 +16890,7 @@ function CameraGridLayoutEditor({ deviceId }) {
|
|
|
16887
16890
|
setLoaded("unreadable");
|
|
16888
16891
|
});
|
|
16889
16892
|
}, [dev, reload]);
|
|
16890
|
-
const save = (0, react$1.useCallback)(async () => {
|
|
16893
|
+
const save = (0, react$1.useCallback)(async (extra) => {
|
|
16891
16894
|
const layout = dev?.cameraGridLayout;
|
|
16892
16895
|
if (draft === null || !layout) return;
|
|
16893
16896
|
setSaving(true);
|
|
@@ -16896,7 +16899,8 @@ function CameraGridLayoutEditor({ deviceId }) {
|
|
|
16896
16899
|
const outcome = parseGridView(await layout.saveLayout({
|
|
16897
16900
|
name: draft.name,
|
|
16898
16901
|
fps: draft.fps,
|
|
16899
|
-
cells: draft.cells.map((cell) => ({ ...cell }))
|
|
16902
|
+
cells: draft.cells.map((cell) => ({ ...cell })),
|
|
16903
|
+
...extra?.publishedProfiles !== void 0 ? { publishedProfiles: [...extra.publishedProfiles] } : {}
|
|
16900
16904
|
}));
|
|
16901
16905
|
if (outcome !== null && outcome !== "unreadable") {
|
|
16902
16906
|
setLoaded(outcome);
|
|
@@ -17084,7 +17088,12 @@ function CameraGridLayoutEditor({ deviceId }) {
|
|
|
17084
17088
|
}),
|
|
17085
17089
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GridProfileReport, {
|
|
17086
17090
|
offers: loaded.profiles,
|
|
17087
|
-
cameras
|
|
17091
|
+
cameras,
|
|
17092
|
+
onToggle: (profile) => {
|
|
17093
|
+
if (draft === null) return;
|
|
17094
|
+
const current = loaded.profiles.filter((row) => row.published).map((row) => row.profile);
|
|
17095
|
+
save({ publishedProfiles: current.includes(profile) ? current.filter((entry) => entry !== profile) : [...current, profile] });
|
|
17096
|
+
}
|
|
17088
17097
|
}),
|
|
17089
17098
|
draft.cells[selected] !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CellInspector, {
|
|
17090
17099
|
cell: draft.cells[selected],
|
|
@@ -17114,7 +17123,7 @@ function CameraGridLayoutEditor({ deviceId }) {
|
|
|
17114
17123
|
* Read-only on purpose: what is on offer is decided by the source cameras'
|
|
17115
17124
|
* assignments, and a control here would be a switch that writes nothing (D62).
|
|
17116
17125
|
*/
|
|
17117
|
-
function GridProfileReport({ offers, cameras }) {
|
|
17126
|
+
function GridProfileReport({ offers, cameras, onToggle }) {
|
|
17118
17127
|
const nameOf = (deviceId) => cameras.find((camera) => camera.id === deviceId)?.name ?? `#${String(deviceId)}`;
|
|
17119
17128
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
17120
17129
|
className: "flex flex-col gap-1 rounded border border-border p-2 text-[10.5px]",
|
|
@@ -17123,20 +17132,33 @@ function GridProfileReport({ offers, cameras }) {
|
|
|
17123
17132
|
children: "Published profiles"
|
|
17124
17133
|
}), offers.map((offer) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
17125
17134
|
className: "flex items-baseline gap-2",
|
|
17126
|
-
children: [
|
|
17127
|
-
|
|
17128
|
-
|
|
17129
|
-
|
|
17130
|
-
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
|
|
17134
|
-
|
|
17135
|
-
|
|
17136
|
-
|
|
17137
|
-
|
|
17138
|
-
|
|
17139
|
-
|
|
17135
|
+
children: [
|
|
17136
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
17137
|
+
type: "checkbox",
|
|
17138
|
+
checked: offer.published,
|
|
17139
|
+
disabled: !offer.offered,
|
|
17140
|
+
"aria-label": `publish ${offer.profile}`,
|
|
17141
|
+
onChange: () => {
|
|
17142
|
+
onToggle(offer.profile);
|
|
17143
|
+
}
|
|
17144
|
+
}),
|
|
17145
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
17146
|
+
className: "w-10 uppercase",
|
|
17147
|
+
children: offer.profile
|
|
17148
|
+
}),
|
|
17149
|
+
offer.offered ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
17150
|
+
className: "text-foreground-subtle",
|
|
17151
|
+
children: [
|
|
17152
|
+
offer.canvas,
|
|
17153
|
+
" —",
|
|
17154
|
+
" ",
|
|
17155
|
+
offer.published ? `composed from every source’s ${offer.profile}` : "serviceable, not published"
|
|
17156
|
+
]
|
|
17157
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
17158
|
+
className: "text-danger",
|
|
17159
|
+
children: offer.missingSources.length === 0 ? "not offered — this grid has no cells yet" : `not offered — no ${offer.profile} on ${offer.missingSources.map(nameOf).join(", ")}`
|
|
17160
|
+
})
|
|
17161
|
+
]
|
|
17140
17162
|
}, offer.profile))]
|
|
17141
17163
|
});
|
|
17142
17164
|
}
|
|
@@ -26956,6 +26978,580 @@ async function measureDownlinkMbps(origin, deps) {
|
|
|
26956
26978
|
}
|
|
26957
26979
|
}
|
|
26958
26980
|
//#endregion
|
|
26981
|
+
//#region src/composites/stream-debug/webrtc-debug-stats.ts
|
|
26982
|
+
/** Beyond this the log is a scroll, not a signal. */
|
|
26983
|
+
var RESOLUTION_LOG_MAX = 8;
|
|
26984
|
+
function num(value) {
|
|
26985
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
26986
|
+
}
|
|
26987
|
+
function str(value) {
|
|
26988
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
26989
|
+
}
|
|
26990
|
+
/** Δ of two cumulative counters. Null unless BOTH ends were measured; a
|
|
26991
|
+
* counter that went backwards (a reset) yields null rather than a negative. */
|
|
26992
|
+
function delta(now, before) {
|
|
26993
|
+
if (now === null || before === null) return null;
|
|
26994
|
+
const d = now - before;
|
|
26995
|
+
return d < 0 ? null : d;
|
|
26996
|
+
}
|
|
26997
|
+
function lossPercentOf(lost, received) {
|
|
26998
|
+
if (lost === null || received === null) return null;
|
|
26999
|
+
const total = lost + received;
|
|
27000
|
+
if (total <= 0) return null;
|
|
27001
|
+
return lost / total * 100;
|
|
27002
|
+
}
|
|
27003
|
+
function profileLevelIdOf(fmtp) {
|
|
27004
|
+
if (fmtp === null) return null;
|
|
27005
|
+
return /profile-level-id=([0-9a-fA-F]+)/.exec(fmtp)?.[1] ?? null;
|
|
27006
|
+
}
|
|
27007
|
+
function collect(report) {
|
|
27008
|
+
const out = {
|
|
27009
|
+
inbound: null,
|
|
27010
|
+
codecs: /* @__PURE__ */ new Map(),
|
|
27011
|
+
candidates: /* @__PURE__ */ new Map(),
|
|
27012
|
+
pair: null,
|
|
27013
|
+
selectedPairId: null
|
|
27014
|
+
};
|
|
27015
|
+
const pairs = [];
|
|
27016
|
+
report.forEach((raw) => {
|
|
27017
|
+
const s = raw;
|
|
27018
|
+
const id = str(s.id);
|
|
27019
|
+
switch (s.type) {
|
|
27020
|
+
case "inbound-rtp":
|
|
27021
|
+
if ((s.kind ?? s.mediaType) === "video") out.inbound = s;
|
|
27022
|
+
break;
|
|
27023
|
+
case "codec":
|
|
27024
|
+
if (id !== null) out.codecs.set(id, s);
|
|
27025
|
+
break;
|
|
27026
|
+
case "local-candidate":
|
|
27027
|
+
case "remote-candidate":
|
|
27028
|
+
if (id !== null) out.candidates.set(id, s);
|
|
27029
|
+
break;
|
|
27030
|
+
case "candidate-pair":
|
|
27031
|
+
pairs.push(s);
|
|
27032
|
+
break;
|
|
27033
|
+
case "transport":
|
|
27034
|
+
out.selectedPairId = str(s.selectedCandidatePairId);
|
|
27035
|
+
break;
|
|
27036
|
+
default: break;
|
|
27037
|
+
}
|
|
27038
|
+
});
|
|
27039
|
+
const byId = out.selectedPairId;
|
|
27040
|
+
out.pair = pairs.find((p) => str(p.id) === byId) ?? pairs.find((p) => p.selected === true) ?? pairs.find((p) => p.nominated === true && p.state === "succeeded") ?? pairs.find((p) => p.nominated === true) ?? pairs.find((p) => p.state === "succeeded") ?? null;
|
|
27041
|
+
return out;
|
|
27042
|
+
}
|
|
27043
|
+
function readCounters(inbound) {
|
|
27044
|
+
if (inbound === null) return {
|
|
27045
|
+
timestamp: null,
|
|
27046
|
+
bytesReceived: null,
|
|
27047
|
+
packetsReceived: null,
|
|
27048
|
+
packetsLost: null,
|
|
27049
|
+
framesDecoded: null,
|
|
27050
|
+
framesDropped: null,
|
|
27051
|
+
freezeCount: null,
|
|
27052
|
+
totalFreezesDurationS: null,
|
|
27053
|
+
totalDecodeTimeS: null,
|
|
27054
|
+
nackCount: null,
|
|
27055
|
+
pliCount: null
|
|
27056
|
+
};
|
|
27057
|
+
return {
|
|
27058
|
+
timestamp: num(inbound.timestamp),
|
|
27059
|
+
bytesReceived: num(inbound.bytesReceived),
|
|
27060
|
+
packetsReceived: num(inbound.packetsReceived),
|
|
27061
|
+
packetsLost: num(inbound.packetsLost),
|
|
27062
|
+
framesDecoded: num(inbound.framesDecoded),
|
|
27063
|
+
framesDropped: num(inbound.framesDropped),
|
|
27064
|
+
freezeCount: num(inbound.freezeCount),
|
|
27065
|
+
totalFreezesDurationS: num(inbound.totalFreezesDuration),
|
|
27066
|
+
totalDecodeTimeS: num(inbound.totalDecodeTime),
|
|
27067
|
+
nackCount: num(inbound.nackCount),
|
|
27068
|
+
pliCount: num(inbound.pliCount)
|
|
27069
|
+
};
|
|
27070
|
+
}
|
|
27071
|
+
var EMPTY_WINDOW = {
|
|
27072
|
+
spanMs: null,
|
|
27073
|
+
kbps: null,
|
|
27074
|
+
packetsReceived: null,
|
|
27075
|
+
packetsLost: null,
|
|
27076
|
+
lossPercent: null,
|
|
27077
|
+
framesDecoded: null,
|
|
27078
|
+
framesDropped: null,
|
|
27079
|
+
freezeCount: null,
|
|
27080
|
+
freezeMs: null,
|
|
27081
|
+
nackCount: null,
|
|
27082
|
+
pliCount: null,
|
|
27083
|
+
decodeMs: null
|
|
27084
|
+
};
|
|
27085
|
+
function windowOf(now, before) {
|
|
27086
|
+
if (before === null) return EMPTY_WINDOW;
|
|
27087
|
+
const spanMs = delta(now.timestamp, before.timestamp);
|
|
27088
|
+
if (spanMs === null || spanMs <= 0) return EMPTY_WINDOW;
|
|
27089
|
+
const bytes = delta(now.bytesReceived, before.bytesReceived);
|
|
27090
|
+
const lost = delta(now.packetsLost, before.packetsLost);
|
|
27091
|
+
const received = delta(now.packetsReceived, before.packetsReceived);
|
|
27092
|
+
const decoded = delta(now.framesDecoded, before.framesDecoded);
|
|
27093
|
+
const decodeS = delta(now.totalDecodeTimeS, before.totalDecodeTimeS);
|
|
27094
|
+
const freezeS = delta(now.totalFreezesDurationS, before.totalFreezesDurationS);
|
|
27095
|
+
return {
|
|
27096
|
+
spanMs,
|
|
27097
|
+
kbps: bytes === null ? null : Math.round(bytes * 8 / spanMs),
|
|
27098
|
+
packetsReceived: received,
|
|
27099
|
+
packetsLost: lost,
|
|
27100
|
+
lossPercent: lossPercentOf(lost, received),
|
|
27101
|
+
framesDecoded: decoded,
|
|
27102
|
+
framesDropped: delta(now.framesDropped, before.framesDropped),
|
|
27103
|
+
freezeCount: delta(now.freezeCount, before.freezeCount),
|
|
27104
|
+
freezeMs: freezeS === null ? null : Math.round(freezeS * 1e3),
|
|
27105
|
+
nackCount: delta(now.nackCount, before.nackCount),
|
|
27106
|
+
pliCount: delta(now.pliCount, before.pliCount),
|
|
27107
|
+
decodeMs: decodeS === null || decoded === null || decoded <= 0 ? null : decodeS / decoded * 1e3
|
|
27108
|
+
};
|
|
27109
|
+
}
|
|
27110
|
+
function resolutionLogOf(prev, width, height, at) {
|
|
27111
|
+
if (width === null || height === null) return prev;
|
|
27112
|
+
const last = prev.at(-1);
|
|
27113
|
+
if (last !== void 0 && last.width === width && last.height === height) return prev;
|
|
27114
|
+
const next = [...prev, {
|
|
27115
|
+
width,
|
|
27116
|
+
height,
|
|
27117
|
+
at
|
|
27118
|
+
}];
|
|
27119
|
+
return next.length > RESOLUTION_LOG_MAX ? next.slice(next.length - RESOLUTION_LOG_MAX) : next;
|
|
27120
|
+
}
|
|
27121
|
+
/**
|
|
27122
|
+
* Derive one debug sample. Immutable: `prev` is read, never mutated, and the
|
|
27123
|
+
* returned object is the input for the next call.
|
|
27124
|
+
*/
|
|
27125
|
+
function deriveWebrtcDebugStats(input) {
|
|
27126
|
+
const { inbound, codecs, candidates, pair } = collect(input.report);
|
|
27127
|
+
const raw = readCounters(inbound);
|
|
27128
|
+
const codecStat = inbound !== null ? codecs.get(str(inbound.codecId) ?? "") : void 0;
|
|
27129
|
+
const mime = str(codecStat?.mimeType);
|
|
27130
|
+
const fmtp = str(codecStat?.sdpFmtpLine);
|
|
27131
|
+
const localCand = pair !== null ? candidates.get(str(pair.localCandidateId) ?? "") : void 0;
|
|
27132
|
+
const remoteCand = pair !== null ? candidates.get(str(pair.remoteCandidateId) ?? "") : void 0;
|
|
27133
|
+
const localType = str(localCand?.candidateType);
|
|
27134
|
+
const remoteType = str(remoteCand?.candidateType);
|
|
27135
|
+
const width = num(inbound?.frameWidth);
|
|
27136
|
+
const height = num(inbound?.frameHeight);
|
|
27137
|
+
const totalFreezeS = raw.totalFreezesDurationS;
|
|
27138
|
+
const decodeS = raw.totalDecodeTimeS;
|
|
27139
|
+
const decodedTotal = raw.framesDecoded;
|
|
27140
|
+
return {
|
|
27141
|
+
at: input.now,
|
|
27142
|
+
hasInboundVideo: inbound !== null,
|
|
27143
|
+
width,
|
|
27144
|
+
height,
|
|
27145
|
+
fps: num(inbound?.framesPerSecond),
|
|
27146
|
+
codec: mime === null ? null : mime.split("/")[1] ?? mime,
|
|
27147
|
+
codecProfileLevelId: profileLevelIdOf(fmtp),
|
|
27148
|
+
jitterMs: jitterMsOf(inbound),
|
|
27149
|
+
packetsReceived: raw.packetsReceived,
|
|
27150
|
+
packetsLost: raw.packetsLost,
|
|
27151
|
+
lossPercent: lossPercentOf(raw.packetsLost, raw.packetsReceived),
|
|
27152
|
+
nackCount: raw.nackCount,
|
|
27153
|
+
pliCount: raw.pliCount,
|
|
27154
|
+
framesDecoded: decodedTotal,
|
|
27155
|
+
framesDropped: raw.framesDropped,
|
|
27156
|
+
freezeCount: raw.freezeCount,
|
|
27157
|
+
totalFreezeMs: totalFreezeS === null ? null : Math.round(totalFreezeS * 1e3),
|
|
27158
|
+
avgDecodeMs: decodeS === null || decodedTotal === null || decodedTotal <= 0 ? null : decodeS / decodedTotal * 1e3,
|
|
27159
|
+
rttMs: rttMsOf(pair),
|
|
27160
|
+
availableIncomingKbps: availableKbpsOf(pair),
|
|
27161
|
+
localCandidateType: localType,
|
|
27162
|
+
remoteCandidateType: remoteType,
|
|
27163
|
+
candidatePairType: localType === null && remoteType === null ? null : `${localType ?? "?"}/${remoteType ?? "?"}`,
|
|
27164
|
+
candidateProtocol: str(localCand?.protocol),
|
|
27165
|
+
iceConnectionState: input.iceConnectionState,
|
|
27166
|
+
dtlsState: input.dtlsState,
|
|
27167
|
+
window: windowOf(raw, input.prev?.raw ?? null),
|
|
27168
|
+
resolutionLog: resolutionLogOf(input.prev?.resolutionLog ?? [], width, height, input.now),
|
|
27169
|
+
raw
|
|
27170
|
+
};
|
|
27171
|
+
}
|
|
27172
|
+
function jitterMsOf(inbound) {
|
|
27173
|
+
const jitter = num(inbound?.jitter);
|
|
27174
|
+
return jitter === null ? null : jitter * 1e3;
|
|
27175
|
+
}
|
|
27176
|
+
function rttMsOf(pair) {
|
|
27177
|
+
const rtt = num(pair?.currentRoundTripTime);
|
|
27178
|
+
return rtt === null ? null : rtt * 1e3;
|
|
27179
|
+
}
|
|
27180
|
+
function availableKbpsOf(pair) {
|
|
27181
|
+
const bps = num(pair?.availableIncomingBitrate);
|
|
27182
|
+
return bps === null ? null : Math.round(bps / 1e3);
|
|
27183
|
+
}
|
|
27184
|
+
var VERDICTS = {
|
|
27185
|
+
unknown: "no window yet",
|
|
27186
|
+
decoding: "decoding",
|
|
27187
|
+
"decoder-stall": "DECODER STALL — bytes arriving, no frames out",
|
|
27188
|
+
"transport-stall": "TRANSPORT STALL — nothing arriving",
|
|
27189
|
+
"frozen-unknown": "frames stopped — byte rate unknown"
|
|
27190
|
+
};
|
|
27191
|
+
function freezeVerdict(stats) {
|
|
27192
|
+
const kind = freezeVerdictKind(stats);
|
|
27193
|
+
return {
|
|
27194
|
+
kind,
|
|
27195
|
+
label: VERDICTS[kind]
|
|
27196
|
+
};
|
|
27197
|
+
}
|
|
27198
|
+
function freezeVerdictKind(stats) {
|
|
27199
|
+
if (stats === null || !stats.hasInboundVideo) return "unknown";
|
|
27200
|
+
const decoded = stats.window.framesDecoded;
|
|
27201
|
+
if (decoded === null) return "unknown";
|
|
27202
|
+
if (decoded > 0) return "decoding";
|
|
27203
|
+
const kbps = stats.window.kbps;
|
|
27204
|
+
if (kbps === null) return "frozen-unknown";
|
|
27205
|
+
return kbps > 0 ? "decoder-stall" : "transport-stall";
|
|
27206
|
+
}
|
|
27207
|
+
//#endregion
|
|
27208
|
+
//#region src/composites/stream-debug/stream-debug-overlay.tsx
|
|
27209
|
+
/** `—` is the ONLY rendering of unknown. Never an empty cell, never 0. */
|
|
27210
|
+
var UNKNOWN = "—";
|
|
27211
|
+
function n(value, opts) {
|
|
27212
|
+
if (value === null || value === void 0 || !Number.isFinite(value)) return UNKNOWN;
|
|
27213
|
+
const digits = opts?.digits ?? 0;
|
|
27214
|
+
return `${value.toFixed(digits)}${opts?.suffix ?? ""}`;
|
|
27215
|
+
}
|
|
27216
|
+
function s(value) {
|
|
27217
|
+
return value === null || value === void 0 || value === "" ? UNKNOWN : value;
|
|
27218
|
+
}
|
|
27219
|
+
function b(value) {
|
|
27220
|
+
return value === null || value === void 0 ? UNKNOWN : value ? "yes" : "no";
|
|
27221
|
+
}
|
|
27222
|
+
/** Age of a measurement, in the form an operator reads at a glance. */
|
|
27223
|
+
function age(at, now) {
|
|
27224
|
+
if (at === null) return UNKNOWN;
|
|
27225
|
+
const ms = Math.max(0, now - at);
|
|
27226
|
+
if (ms < 1e3) return `${ms}ms ago`;
|
|
27227
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s ago`;
|
|
27228
|
+
return `${Math.floor(ms / 6e4)}m${Math.floor(ms % 6e4 / 1e3)}s ago`;
|
|
27229
|
+
}
|
|
27230
|
+
/** A measurement older than this is NOT live, and is said to be. */
|
|
27231
|
+
var STALE_AFTER_MS = 3e3;
|
|
27232
|
+
var VERDICT_CLASS = {
|
|
27233
|
+
unknown: "bg-white/15 text-white/70",
|
|
27234
|
+
decoding: "bg-emerald-500/25 text-emerald-200",
|
|
27235
|
+
"decoder-stall": "bg-amber-500/30 text-amber-100",
|
|
27236
|
+
"transport-stall": "bg-rose-500/30 text-rose-100",
|
|
27237
|
+
"frozen-unknown": "bg-white/15 text-white/70"
|
|
27238
|
+
};
|
|
27239
|
+
function Row({ label, value, strong = false }) {
|
|
27240
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
27241
|
+
className: "flex items-baseline justify-between gap-3 leading-tight",
|
|
27242
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
27243
|
+
className: "text-white/55 shrink-0",
|
|
27244
|
+
children: label
|
|
27245
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
27246
|
+
className: strong ? "text-white font-semibold tabular-nums" : "text-white/90 tabular-nums",
|
|
27247
|
+
children: value
|
|
27248
|
+
})]
|
|
27249
|
+
});
|
|
27250
|
+
}
|
|
27251
|
+
function SectionHeader({ title, measuredAt, now }) {
|
|
27252
|
+
const stale = measuredAt !== null && now - measuredAt > STALE_AFTER_MS;
|
|
27253
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
27254
|
+
className: "flex items-baseline justify-between gap-3 mt-2 first:mt-0 pb-0.5 border-b border-white/20",
|
|
27255
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
27256
|
+
className: "text-white/80 font-semibold tracking-wide uppercase text-[9px]",
|
|
27257
|
+
children: title
|
|
27258
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
27259
|
+
className: stale ? "text-amber-300 text-[9px]" : "text-white/45 text-[9px]",
|
|
27260
|
+
children: [stale ? "STALE · " : "", age(measuredAt, now)]
|
|
27261
|
+
})]
|
|
27262
|
+
});
|
|
27263
|
+
}
|
|
27264
|
+
function StreamDebugOverlay(props) {
|
|
27265
|
+
const { stats, server, serverError, serverWired, now, sessionId } = props;
|
|
27266
|
+
const verdict = freezeVerdict(stats);
|
|
27267
|
+
const win = stats?.window;
|
|
27268
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
27269
|
+
className: "absolute top-2 right-2 z-20 transform-gpu w-[15.5rem] max-h-[calc(100%-1rem)] overflow-y-auto rounded-md bg-neutral-950/95 ring-1 ring-white/25 shadow-lg px-2 py-1.5 text-[10px] font-mono text-white/90 select-text",
|
|
27270
|
+
"data-testid": "stream-debug-overlay",
|
|
27271
|
+
children: [
|
|
27272
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
27273
|
+
className: `rounded px-1.5 py-1 mb-1 text-[10px] font-semibold ${VERDICT_CLASS[verdict.kind]}`,
|
|
27274
|
+
children: verdict.label
|
|
27275
|
+
}),
|
|
27276
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionHeader, {
|
|
27277
|
+
title: "decode",
|
|
27278
|
+
measuredAt: stats?.at ?? null,
|
|
27279
|
+
now
|
|
27280
|
+
}),
|
|
27281
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27282
|
+
label: "framesDecoded",
|
|
27283
|
+
value: n(stats?.framesDecoded),
|
|
27284
|
+
strong: true
|
|
27285
|
+
}),
|
|
27286
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27287
|
+
label: "· this window",
|
|
27288
|
+
value: n(win?.framesDecoded),
|
|
27289
|
+
strong: true
|
|
27290
|
+
}),
|
|
27291
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27292
|
+
label: "freezeCount",
|
|
27293
|
+
value: n(stats?.freezeCount),
|
|
27294
|
+
strong: true
|
|
27295
|
+
}),
|
|
27296
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27297
|
+
label: "· total frozen",
|
|
27298
|
+
value: n(stats?.totalFreezeMs, { suffix: "ms" }),
|
|
27299
|
+
strong: true
|
|
27300
|
+
}),
|
|
27301
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27302
|
+
label: "· this window",
|
|
27303
|
+
value: n(win?.freezeMs, { suffix: "ms" })
|
|
27304
|
+
}),
|
|
27305
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27306
|
+
label: "pliCount",
|
|
27307
|
+
value: n(stats?.pliCount),
|
|
27308
|
+
strong: true
|
|
27309
|
+
}),
|
|
27310
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27311
|
+
label: "· this window",
|
|
27312
|
+
value: n(win?.pliCount)
|
|
27313
|
+
}),
|
|
27314
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27315
|
+
label: "framesDropped",
|
|
27316
|
+
value: n(stats?.framesDropped)
|
|
27317
|
+
}),
|
|
27318
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27319
|
+
label: "decode time",
|
|
27320
|
+
value: n(stats?.avgDecodeMs, {
|
|
27321
|
+
digits: 1,
|
|
27322
|
+
suffix: "ms"
|
|
27323
|
+
})
|
|
27324
|
+
}),
|
|
27325
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27326
|
+
label: "· this window",
|
|
27327
|
+
value: n(win?.decodeMs, {
|
|
27328
|
+
digits: 1,
|
|
27329
|
+
suffix: "ms"
|
|
27330
|
+
})
|
|
27331
|
+
}),
|
|
27332
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionHeader, {
|
|
27333
|
+
title: "picture",
|
|
27334
|
+
measuredAt: stats?.at ?? null,
|
|
27335
|
+
now
|
|
27336
|
+
}),
|
|
27337
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27338
|
+
label: "resolution",
|
|
27339
|
+
value: stats?.width !== null && stats?.width !== void 0 && stats.height !== null ? `${stats.width}×${stats.height}` : UNKNOWN
|
|
27340
|
+
}),
|
|
27341
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27342
|
+
label: "fps",
|
|
27343
|
+
value: n(stats?.fps)
|
|
27344
|
+
}),
|
|
27345
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27346
|
+
label: "codec",
|
|
27347
|
+
value: stats?.codec === null || stats?.codec === void 0 ? UNKNOWN : `${stats.codec}${stats.codecProfileLevelId !== null ? ` ${stats.codecProfileLevelId}` : ""}`
|
|
27348
|
+
}),
|
|
27349
|
+
(stats?.resolutionLog.length ?? 0) > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
27350
|
+
className: "mt-0.5 text-white/60 leading-tight",
|
|
27351
|
+
children: stats?.resolutionLog.map((mark) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
27352
|
+
className: "flex justify-between",
|
|
27353
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
27354
|
+
mark.width,
|
|
27355
|
+
"×",
|
|
27356
|
+
mark.height
|
|
27357
|
+
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
27358
|
+
className: "text-white/40",
|
|
27359
|
+
children: age(mark.at, now)
|
|
27360
|
+
})]
|
|
27361
|
+
}, `${mark.at}-${mark.width}x${mark.height}`))
|
|
27362
|
+
}),
|
|
27363
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionHeader, {
|
|
27364
|
+
title: "transport",
|
|
27365
|
+
measuredAt: stats?.at ?? null,
|
|
27366
|
+
now
|
|
27367
|
+
}),
|
|
27368
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27369
|
+
label: "bitrate",
|
|
27370
|
+
value: n(win?.kbps, { suffix: " kbps" })
|
|
27371
|
+
}),
|
|
27372
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27373
|
+
label: "rtt",
|
|
27374
|
+
value: n(stats?.rttMs, {
|
|
27375
|
+
digits: 0,
|
|
27376
|
+
suffix: "ms"
|
|
27377
|
+
})
|
|
27378
|
+
}),
|
|
27379
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27380
|
+
label: "jitter",
|
|
27381
|
+
value: n(stats?.jitterMs, {
|
|
27382
|
+
digits: 1,
|
|
27383
|
+
suffix: "ms"
|
|
27384
|
+
})
|
|
27385
|
+
}),
|
|
27386
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27387
|
+
label: "packetsLost",
|
|
27388
|
+
value: n(stats?.packetsLost)
|
|
27389
|
+
}),
|
|
27390
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27391
|
+
label: "· this window",
|
|
27392
|
+
value: n(win?.packetsLost)
|
|
27393
|
+
}),
|
|
27394
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27395
|
+
label: "loss",
|
|
27396
|
+
value: n(stats?.lossPercent, {
|
|
27397
|
+
digits: 2,
|
|
27398
|
+
suffix: "%"
|
|
27399
|
+
})
|
|
27400
|
+
}),
|
|
27401
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27402
|
+
label: "· this window",
|
|
27403
|
+
value: n(win?.lossPercent, {
|
|
27404
|
+
digits: 2,
|
|
27405
|
+
suffix: "%"
|
|
27406
|
+
})
|
|
27407
|
+
}),
|
|
27408
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27409
|
+
label: "nackCount",
|
|
27410
|
+
value: n(stats?.nackCount)
|
|
27411
|
+
}),
|
|
27412
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27413
|
+
label: "· this window",
|
|
27414
|
+
value: n(win?.nackCount)
|
|
27415
|
+
}),
|
|
27416
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27417
|
+
label: "candidate pair",
|
|
27418
|
+
value: s(stats?.candidatePairType)
|
|
27419
|
+
}),
|
|
27420
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27421
|
+
label: "protocol",
|
|
27422
|
+
value: s(stats?.candidateProtocol)
|
|
27423
|
+
}),
|
|
27424
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27425
|
+
label: "ice",
|
|
27426
|
+
value: s(stats?.iceConnectionState)
|
|
27427
|
+
}),
|
|
27428
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27429
|
+
label: "dtls",
|
|
27430
|
+
value: s(stats?.dtlsState)
|
|
27431
|
+
}),
|
|
27432
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionHeader, {
|
|
27433
|
+
title: "server",
|
|
27434
|
+
measuredAt: server?.observedAt ?? null,
|
|
27435
|
+
now
|
|
27436
|
+
}),
|
|
27437
|
+
!serverWired ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
27438
|
+
className: "text-white/50 leading-tight py-0.5",
|
|
27439
|
+
children: "not wired on this host"
|
|
27440
|
+
}) : serverError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
27441
|
+
className: "text-rose-300 leading-tight py-0.5 break-words",
|
|
27442
|
+
children: serverError
|
|
27443
|
+
}) : server === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
27444
|
+
className: "text-white/50 leading-tight py-0.5",
|
|
27445
|
+
children: "waiting for the first answer…"
|
|
27446
|
+
}) : !server.alive ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
27447
|
+
className: "text-amber-300 leading-tight py-0.5",
|
|
27448
|
+
children: "session not found on the server — it ended, or this node never had it"
|
|
27449
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
27450
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27451
|
+
label: "served rung",
|
|
27452
|
+
value: s(server.servedTier),
|
|
27453
|
+
strong: true
|
|
27454
|
+
}),
|
|
27455
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27456
|
+
label: "adaptive",
|
|
27457
|
+
value: b(server.adaptive)
|
|
27458
|
+
}),
|
|
27459
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27460
|
+
label: "source",
|
|
27461
|
+
value: s(server.camStreamId)
|
|
27462
|
+
}),
|
|
27463
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27464
|
+
label: "source kind",
|
|
27465
|
+
value: server.sourceKind
|
|
27466
|
+
}),
|
|
27467
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27468
|
+
label: "ingest node",
|
|
27469
|
+
value: s(server.ingestNodeId)
|
|
27470
|
+
}),
|
|
27471
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27472
|
+
label: "· remote",
|
|
27473
|
+
value: b(server.ingestIsRemote)
|
|
27474
|
+
}),
|
|
27475
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27476
|
+
label: "egress leg",
|
|
27477
|
+
value: server.egressLeg,
|
|
27478
|
+
strong: true
|
|
27479
|
+
}),
|
|
27480
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27481
|
+
label: "codec src→out",
|
|
27482
|
+
value: `${s(server.sourceCodec)}→${s(server.negotiatedCodec)}`
|
|
27483
|
+
}),
|
|
27484
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27485
|
+
label: "broker",
|
|
27486
|
+
value: s(server.brokerStatus)
|
|
27487
|
+
}),
|
|
27488
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27489
|
+
label: "· bitrate",
|
|
27490
|
+
value: n(server.brokerBitrateKbps, { suffix: " kbps" })
|
|
27491
|
+
}),
|
|
27492
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27493
|
+
label: "· input fps",
|
|
27494
|
+
value: n(server.brokerInputFps)
|
|
27495
|
+
}),
|
|
27496
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27497
|
+
label: "rtp sent",
|
|
27498
|
+
value: n(server.rtpPacketsSent)
|
|
27499
|
+
}),
|
|
27500
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27501
|
+
label: "track mode",
|
|
27502
|
+
value: s(server.trackMode)
|
|
27503
|
+
}),
|
|
27504
|
+
server.lastDecision === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27505
|
+
label: "ladder",
|
|
27506
|
+
value: "never moved"
|
|
27507
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
27508
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27509
|
+
label: "ladder move",
|
|
27510
|
+
value: `${server.lastDecision.cause}${server.lastDecision.tier !== null ? ` → ${server.lastDecision.tier}` : ""}`,
|
|
27511
|
+
strong: true
|
|
27512
|
+
}),
|
|
27513
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27514
|
+
label: "· when",
|
|
27515
|
+
value: age(server.lastDecision.at, now)
|
|
27516
|
+
}),
|
|
27517
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27518
|
+
label: "· lossRatio",
|
|
27519
|
+
value: n(server.lastDecision.lossRatio, { digits: 4 })
|
|
27520
|
+
}),
|
|
27521
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27522
|
+
label: "· clientKbps",
|
|
27523
|
+
value: n(server.lastDecision.clientKbps, { suffix: " kbps" })
|
|
27524
|
+
}),
|
|
27525
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27526
|
+
label: "· stalledFor",
|
|
27527
|
+
value: n(server.lastDecision.stalledForMs, { suffix: "ms" })
|
|
27528
|
+
}),
|
|
27529
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27530
|
+
label: "· serverSending",
|
|
27531
|
+
value: b(server.lastDecision.serverSending)
|
|
27532
|
+
}),
|
|
27533
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27534
|
+
label: "· transcode-down",
|
|
27535
|
+
value: b(server.lastDecision.forcedTranscodeDown)
|
|
27536
|
+
})
|
|
27537
|
+
] }),
|
|
27538
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27539
|
+
label: "promotions",
|
|
27540
|
+
value: n(server.upgradeCount)
|
|
27541
|
+
}),
|
|
27542
|
+
server.tierFailures.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
27543
|
+
label: "tier failures",
|
|
27544
|
+
value: server.tierFailures.map((f) => `${f.tier}:${f.failures}`).join(" ")
|
|
27545
|
+
})
|
|
27546
|
+
] }),
|
|
27547
|
+
sessionId !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
27548
|
+
className: "mt-1 pt-0.5 border-t border-white/15 text-white/40 break-all leading-tight",
|
|
27549
|
+
children: sessionId
|
|
27550
|
+
})
|
|
27551
|
+
]
|
|
27552
|
+
});
|
|
27553
|
+
}
|
|
27554
|
+
//#endregion
|
|
26959
27555
|
//#region src/composites/camera-stream-player.tsx
|
|
26960
27556
|
/**
|
|
26961
27557
|
* Silence (or restore) a live WebRTC stream FOR REAL.
|
|
@@ -27054,7 +27650,7 @@ function mergePlayerHints(autoHints, override, downlinkMbps, coldStart) {
|
|
|
27054
27650
|
return coldStart ? stripDownlinkForColdStart(merged) : merged;
|
|
27055
27651
|
}
|
|
27056
27652
|
var FIRST_FRAME_TIMEOUT_MS = 8e3;
|
|
27057
|
-
function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, muted: initialMuted = true, showControls = true, showStats = false, onPlaybackStats, onClientNetworkSample, onConnectTiming, className = "", onStateChange, onError, onReconnectAttempt, overlay, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, reoffer, posterUrl, hintsOverride, onAdaptiveHints, reconnectSignal, onControlChannel, onVideoElement }) {
|
|
27653
|
+
function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, muted: initialMuted = true, showControls = true, showStats = false, onPlaybackStats, onClientNetworkSample, onConnectTiming, className = "", onStateChange, onError, onReconnectAttempt, overlay, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, getSessionDebug, reoffer, posterUrl, hintsOverride, onAdaptiveHints, reconnectSignal, onControlChannel, onVideoElement }) {
|
|
27058
27654
|
const videoRef = (0, react$1.useRef)(null);
|
|
27059
27655
|
const containerRef = (0, react$1.useRef)(null);
|
|
27060
27656
|
const pcRef = (0, react$1.useRef)(null);
|
|
@@ -27951,55 +28547,77 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
27951
28547
|
const webkit = video !== null && typeof video.webkitSetPresentationMode === "function";
|
|
27952
28548
|
setIsPipSupported(standard || webkit);
|
|
27953
28549
|
}, []);
|
|
27954
|
-
const [
|
|
27955
|
-
const
|
|
28550
|
+
const [debugStats, setDebugStats] = (0, react$1.useState)(null);
|
|
28551
|
+
const [serverDebug, setServerDebug] = (0, react$1.useState)(null);
|
|
28552
|
+
const [serverDebugError, setServerDebugError] = (0, react$1.useState)(null);
|
|
28553
|
+
const [debugNow, setDebugNow] = (0, react$1.useState)(() => Date.now());
|
|
28554
|
+
(0, react$1.useEffect)(() => {
|
|
28555
|
+
if (!showStats) return;
|
|
28556
|
+
const id = setInterval(() => setDebugNow(Date.now()), 500);
|
|
28557
|
+
return () => clearInterval(id);
|
|
28558
|
+
}, [showStats]);
|
|
27956
28559
|
(0, react$1.useEffect)(() => {
|
|
27957
28560
|
if (!showStats) {
|
|
27958
|
-
|
|
27959
|
-
statsPrev.current = null;
|
|
28561
|
+
setDebugStats(null);
|
|
27960
28562
|
return;
|
|
27961
28563
|
}
|
|
28564
|
+
const prev = { current: null };
|
|
28565
|
+
let cancelled = false;
|
|
27962
28566
|
const tick = async () => {
|
|
27963
28567
|
const pc = pcRef.current;
|
|
27964
28568
|
if (!pc) return;
|
|
27965
28569
|
try {
|
|
27966
28570
|
const report = await pc.getStats();
|
|
27967
|
-
|
|
27968
|
-
|
|
27969
|
-
|
|
27970
|
-
|
|
27971
|
-
|
|
27972
|
-
|
|
27973
|
-
|
|
27974
|
-
const s = raw;
|
|
27975
|
-
if (s.type === "inbound-rtp" && (s.kind ?? s.mediaType) === "video") {
|
|
27976
|
-
bytes = s.bytesReceived ?? 0;
|
|
27977
|
-
ts = s.timestamp;
|
|
27978
|
-
fps = s.framesPerSecond ?? 0;
|
|
27979
|
-
w = s.frameWidth ?? 0;
|
|
27980
|
-
h = s.frameHeight ?? 0;
|
|
27981
|
-
} else if (s.type === "candidate-pair" && s.nominated && typeof s.currentRoundTripTime === "number") rtt = s.currentRoundTripTime;
|
|
28571
|
+
if (cancelled) return;
|
|
28572
|
+
const next = deriveWebrtcDebugStats({
|
|
28573
|
+
report,
|
|
28574
|
+
prev: prev.current,
|
|
28575
|
+
now: Date.now(),
|
|
28576
|
+
iceConnectionState: pc.iceConnectionState,
|
|
28577
|
+
dtlsState: pc.connectionState
|
|
27982
28578
|
});
|
|
27983
|
-
|
|
27984
|
-
|
|
27985
|
-
statsPrev.current = {
|
|
27986
|
-
bytes,
|
|
27987
|
-
ts
|
|
27988
|
-
};
|
|
27989
|
-
const parts = [];
|
|
27990
|
-
if (w && h) parts.push(`${w}×${h}`);
|
|
27991
|
-
if (fps) parts.push(`${Math.round(fps)}fps`);
|
|
27992
|
-
if (kbps > 0) parts.push(`${kbps} kbps`);
|
|
27993
|
-
if (rtt) parts.push(`${Math.round(rtt * 1e3)}ms`);
|
|
27994
|
-
setStatsText(parts.join(" · "));
|
|
28579
|
+
prev.current = next;
|
|
28580
|
+
setDebugStats(next);
|
|
27995
28581
|
} catch {}
|
|
27996
28582
|
};
|
|
27997
28583
|
tick();
|
|
27998
28584
|
const id = setInterval(() => {
|
|
27999
28585
|
tick();
|
|
28000
28586
|
}, 1e3);
|
|
28001
|
-
return () =>
|
|
28587
|
+
return () => {
|
|
28588
|
+
cancelled = true;
|
|
28589
|
+
clearInterval(id);
|
|
28590
|
+
};
|
|
28002
28591
|
}, [showStats]);
|
|
28592
|
+
(0, react$1.useEffect)(() => {
|
|
28593
|
+
if (!showStats || !getSessionDebug) {
|
|
28594
|
+
setServerDebug(null);
|
|
28595
|
+
setServerDebugError(null);
|
|
28596
|
+
return;
|
|
28597
|
+
}
|
|
28598
|
+
let cancelled = false;
|
|
28599
|
+
const tick = async () => {
|
|
28600
|
+
const sid = activeSessionIdRef.current;
|
|
28601
|
+
if (sid === null) return;
|
|
28602
|
+
try {
|
|
28603
|
+
const answer = await getSessionDebug(sid);
|
|
28604
|
+
if (cancelled) return;
|
|
28605
|
+
setServerDebug(answer);
|
|
28606
|
+
setServerDebugError(null);
|
|
28607
|
+
} catch (err) {
|
|
28608
|
+
if (cancelled) return;
|
|
28609
|
+
setServerDebugError(err instanceof Error ? err.message : String(err));
|
|
28610
|
+
}
|
|
28611
|
+
};
|
|
28612
|
+
tick();
|
|
28613
|
+
const id = setInterval(() => {
|
|
28614
|
+
tick();
|
|
28615
|
+
}, 1e3);
|
|
28616
|
+
return () => {
|
|
28617
|
+
cancelled = true;
|
|
28618
|
+
clearInterval(id);
|
|
28619
|
+
};
|
|
28620
|
+
}, [showStats, getSessionDebug]);
|
|
28003
28621
|
const netPrev = (0, react$1.useRef)(null);
|
|
28004
28622
|
(0, react$1.useEffect)(() => {
|
|
28005
28623
|
if (!onClientNetworkSample) {
|
|
@@ -28186,9 +28804,13 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
28186
28804
|
className: "absolute top-2 left-2 z-10 transform-gpu px-2 py-0.5 rounded bg-black/60 text-[10px] text-white font-medium",
|
|
28187
28805
|
children: label
|
|
28188
28806
|
}),
|
|
28189
|
-
showStats &&
|
|
28190
|
-
|
|
28191
|
-
|
|
28807
|
+
showStats && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StreamDebugOverlay, {
|
|
28808
|
+
stats: debugStats,
|
|
28809
|
+
server: serverDebug,
|
|
28810
|
+
serverError: serverDebugError,
|
|
28811
|
+
serverWired: getSessionDebug !== void 0,
|
|
28812
|
+
now: debugNow,
|
|
28813
|
+
sessionId: activeSessionIdRef.current
|
|
28192
28814
|
}),
|
|
28193
28815
|
overlay,
|
|
28194
28816
|
showControls && !stillImg && state === "connecting" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -51541,6 +52163,13 @@ function useDeviceWebrtc(trpc, deviceId, pollIntervalMs = 5e3) {
|
|
|
51541
52163
|
} catch {
|
|
51542
52164
|
return { pendingRenegotiation: null };
|
|
51543
52165
|
}
|
|
52166
|
+
}, [trpc, deviceId]),
|
|
52167
|
+
getSessionDebug: (0, react$1.useCallback)(async (sessionId) => {
|
|
52168
|
+
if (deviceId === null) throw new Error("getSessionDebug: no device bound to this player");
|
|
52169
|
+
return trpc.webrtcSession.getSessionDebug.query({
|
|
52170
|
+
deviceId,
|
|
52171
|
+
sessionId
|
|
52172
|
+
});
|
|
51544
52173
|
}, [trpc, deviceId])
|
|
51545
52174
|
};
|
|
51546
52175
|
}
|
|
@@ -52057,6 +52686,7 @@ exports.StepTimings = StepTimings;
|
|
|
52057
52686
|
exports.StepTreeMaster = StepTreeMaster;
|
|
52058
52687
|
exports.Stepper = Stepper;
|
|
52059
52688
|
exports.StreamBrokerSelector = StreamBrokerSelector;
|
|
52689
|
+
exports.StreamDebugOverlay = StreamDebugOverlay;
|
|
52060
52690
|
exports.StreamPanel = StreamPanel;
|
|
52061
52691
|
exports.Switch = Switch;
|
|
52062
52692
|
exports.SwitchHeroCard = SwitchHeroCard;
|
|
@@ -52122,6 +52752,7 @@ exports.cursorFractionFor = cursorFractionFor;
|
|
|
52122
52752
|
exports.darkColors = require_theme_index.darkColors$1;
|
|
52123
52753
|
exports.defaultTheme = require_theme_index.defaultTheme$1;
|
|
52124
52754
|
exports.deriveDeviceKind = deriveDeviceKind;
|
|
52755
|
+
exports.deriveWebrtcDebugStats = deriveWebrtcDebugStats;
|
|
52125
52756
|
exports.describeDeviceSelector = describeDeviceSelector;
|
|
52126
52757
|
exports.deviceMatchesFilter = deviceMatchesFilter;
|
|
52127
52758
|
exports.deviceOptionLabel = deviceOptionLabel;
|
|
@@ -52138,6 +52769,7 @@ exports.findTimezone = _camstack_types.findTimezone;
|
|
|
52138
52769
|
exports.formatControlDateTime = formatControlDateTime;
|
|
52139
52770
|
exports.formatLastSeen = formatLastSeen;
|
|
52140
52771
|
exports.formatNumeric = formatNumeric;
|
|
52772
|
+
exports.freezeVerdict = freezeVerdict;
|
|
52141
52773
|
exports.fuzzyMatch = fuzzyMatch;
|
|
52142
52774
|
exports.getClassColor = getClassColor;
|
|
52143
52775
|
exports.getPhaseVisual = getPhaseVisual;
|
|
@@ -53307,6 +53939,7 @@ exports.useWebrtcSessionAddIceCandidate = useWebrtcSessionAddIceCandidate;
|
|
|
53307
53939
|
exports.useWebrtcSessionCloseSession = useWebrtcSessionCloseSession;
|
|
53308
53940
|
exports.useWebrtcSessionCreateSession = useWebrtcSessionCreateSession;
|
|
53309
53941
|
exports.useWebrtcSessionGetIceCandidates = useWebrtcSessionGetIceCandidates;
|
|
53942
|
+
exports.useWebrtcSessionGetSessionDebug = useWebrtcSessionGetSessionDebug;
|
|
53310
53943
|
exports.useWebrtcSessionGetSessionState = useWebrtcSessionGetSessionState;
|
|
53311
53944
|
exports.useWebrtcSessionHandleAnswer = useWebrtcSessionHandleAnswer;
|
|
53312
53945
|
exports.useWebrtcSessionHandleOffer = useWebrtcSessionHandleOffer;
|