@camstack/ui-library 1.2.181 → 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/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`. */
@@ -26976,6 +26978,580 @@ async function measureDownlinkMbps(origin, deps) {
26976
26978
  }
26977
26979
  }
26978
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
26979
27555
  //#region src/composites/camera-stream-player.tsx
26980
27556
  /**
26981
27557
  * Silence (or restore) a live WebRTC stream FOR REAL.
@@ -27074,7 +27650,7 @@ function mergePlayerHints(autoHints, override, downlinkMbps, coldStart) {
27074
27650
  return coldStart ? stripDownlinkForColdStart(merged) : merged;
27075
27651
  }
27076
27652
  var FIRST_FRAME_TIMEOUT_MS = 8e3;
27077
- 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 }) {
27078
27654
  const videoRef = (0, react$1.useRef)(null);
27079
27655
  const containerRef = (0, react$1.useRef)(null);
27080
27656
  const pcRef = (0, react$1.useRef)(null);
@@ -27971,55 +28547,77 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
27971
28547
  const webkit = video !== null && typeof video.webkitSetPresentationMode === "function";
27972
28548
  setIsPipSupported(standard || webkit);
27973
28549
  }, []);
27974
- const [statsText, setStatsText] = (0, react$1.useState)("");
27975
- const statsPrev = (0, react$1.useRef)(null);
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]);
27976
28559
  (0, react$1.useEffect)(() => {
27977
28560
  if (!showStats) {
27978
- setStatsText("");
27979
- statsPrev.current = null;
28561
+ setDebugStats(null);
27980
28562
  return;
27981
28563
  }
28564
+ const prev = { current: null };
28565
+ let cancelled = false;
27982
28566
  const tick = async () => {
27983
28567
  const pc = pcRef.current;
27984
28568
  if (!pc) return;
27985
28569
  try {
27986
28570
  const report = await pc.getStats();
27987
- let bytes = 0;
27988
- let ts = 0;
27989
- let fps = 0;
27990
- let w = 0;
27991
- let h = 0;
27992
- let rtt = 0;
27993
- report.forEach((raw) => {
27994
- const s = raw;
27995
- if (s.type === "inbound-rtp" && (s.kind ?? s.mediaType) === "video") {
27996
- bytes = s.bytesReceived ?? 0;
27997
- ts = s.timestamp;
27998
- fps = s.framesPerSecond ?? 0;
27999
- w = s.frameWidth ?? 0;
28000
- h = s.frameHeight ?? 0;
28001
- } 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
28002
28578
  });
28003
- let kbps = 0;
28004
- if (statsPrev.current && ts > statsPrev.current.ts) kbps = Math.round((bytes - statsPrev.current.bytes) * 8 / (ts - statsPrev.current.ts));
28005
- statsPrev.current = {
28006
- bytes,
28007
- ts
28008
- };
28009
- const parts = [];
28010
- if (w && h) parts.push(`${w}×${h}`);
28011
- if (fps) parts.push(`${Math.round(fps)}fps`);
28012
- if (kbps > 0) parts.push(`${kbps} kbps`);
28013
- if (rtt) parts.push(`${Math.round(rtt * 1e3)}ms`);
28014
- setStatsText(parts.join(" · "));
28579
+ prev.current = next;
28580
+ setDebugStats(next);
28015
28581
  } catch {}
28016
28582
  };
28017
28583
  tick();
28018
28584
  const id = setInterval(() => {
28019
28585
  tick();
28020
28586
  }, 1e3);
28021
- return () => clearInterval(id);
28587
+ return () => {
28588
+ cancelled = true;
28589
+ clearInterval(id);
28590
+ };
28022
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]);
28023
28621
  const netPrev = (0, react$1.useRef)(null);
28024
28622
  (0, react$1.useEffect)(() => {
28025
28623
  if (!onClientNetworkSample) {
@@ -28206,9 +28804,13 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
28206
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",
28207
28805
  children: label
28208
28806
  }),
28209
- showStats && statsText && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
28210
- className: "absolute top-2 right-2 z-10 transform-gpu px-2 py-0.5 rounded bg-black/60 text-[10px] text-white font-mono",
28211
- children: statsText
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
28212
28814
  }),
28213
28815
  overlay,
28214
28816
  showControls && !stillImg && state === "connecting" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -51561,6 +52163,13 @@ function useDeviceWebrtc(trpc, deviceId, pollIntervalMs = 5e3) {
51561
52163
  } catch {
51562
52164
  return { pendingRenegotiation: null };
51563
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
+ });
51564
52173
  }, [trpc, deviceId])
51565
52174
  };
51566
52175
  }
@@ -52077,6 +52686,7 @@ exports.StepTimings = StepTimings;
52077
52686
  exports.StepTreeMaster = StepTreeMaster;
52078
52687
  exports.Stepper = Stepper;
52079
52688
  exports.StreamBrokerSelector = StreamBrokerSelector;
52689
+ exports.StreamDebugOverlay = StreamDebugOverlay;
52080
52690
  exports.StreamPanel = StreamPanel;
52081
52691
  exports.Switch = Switch;
52082
52692
  exports.SwitchHeroCard = SwitchHeroCard;
@@ -52142,6 +52752,7 @@ exports.cursorFractionFor = cursorFractionFor;
52142
52752
  exports.darkColors = require_theme_index.darkColors$1;
52143
52753
  exports.defaultTheme = require_theme_index.defaultTheme$1;
52144
52754
  exports.deriveDeviceKind = deriveDeviceKind;
52755
+ exports.deriveWebrtcDebugStats = deriveWebrtcDebugStats;
52145
52756
  exports.describeDeviceSelector = describeDeviceSelector;
52146
52757
  exports.deviceMatchesFilter = deviceMatchesFilter;
52147
52758
  exports.deviceOptionLabel = deviceOptionLabel;
@@ -52158,6 +52769,7 @@ exports.findTimezone = _camstack_types.findTimezone;
52158
52769
  exports.formatControlDateTime = formatControlDateTime;
52159
52770
  exports.formatLastSeen = formatLastSeen;
52160
52771
  exports.formatNumeric = formatNumeric;
52772
+ exports.freezeVerdict = freezeVerdict;
52161
52773
  exports.fuzzyMatch = fuzzyMatch;
52162
52774
  exports.getClassColor = getClassColor;
52163
52775
  exports.getPhaseVisual = getPhaseVisual;
@@ -53327,6 +53939,7 @@ exports.useWebrtcSessionAddIceCandidate = useWebrtcSessionAddIceCandidate;
53327
53939
  exports.useWebrtcSessionCloseSession = useWebrtcSessionCloseSession;
53328
53940
  exports.useWebrtcSessionCreateSession = useWebrtcSessionCreateSession;
53329
53941
  exports.useWebrtcSessionGetIceCandidates = useWebrtcSessionGetIceCandidates;
53942
+ exports.useWebrtcSessionGetSessionDebug = useWebrtcSessionGetSessionDebug;
53330
53943
  exports.useWebrtcSessionGetSessionState = useWebrtcSessionGetSessionState;
53331
53944
  exports.useWebrtcSessionHandleAnswer = useWebrtcSessionHandleAnswer;
53332
53945
  exports.useWebrtcSessionHandleOffer = useWebrtcSessionHandleOffer;