@camstack/ui-library 1.2.182 → 1.2.183

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.
@@ -17,6 +17,16 @@ import { WebrtcDebugStats } from './webrtc-debug-stats.js';
17
17
  * (D393). In a tool whose whole job is telling a stalled decoder from a
18
18
  * stalled transport, "nobody measured" and "measured nothing" are
19
19
  * opposite conclusions.
20
+ * 4. It must not cover the picture, or the operator turns it off and the tool
21
+ * is worth nothing. So it is a one-line PILL by default — the verdict and
22
+ * the three numbers that carry it — and the full panel only when asked.
23
+ * Collapsed it is small enough to live alongside the app's own overlays
24
+ * (the audio meter, the box layer), which the operator positions freely
25
+ * and which this used to sit on top of.
26
+ *
27
+ * Density follows from the same rule: a cumulative counter and its window
28
+ * share ONE row (`12345 +30`), because two rows saying almost the same thing
29
+ * is how a panel grows until it covers the video it is describing.
20
30
  */
21
31
  export interface StreamDebugOverlayProps {
22
32
  /** The client half. Null before the first successful poll. */
package/dist/index.cjs CHANGED
@@ -27236,15 +27236,21 @@ var VERDICT_CLASS = {
27236
27236
  "transport-stall": "bg-rose-500/30 text-rose-100",
27237
27237
  "frozen-unknown": "bg-white/15 text-white/70"
27238
27238
  };
27239
- function Row({ label, value, strong = false }) {
27239
+ function Row({ label, value, window, strong = false }) {
27240
27240
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
27241
- className: "flex items-baseline justify-between gap-3 leading-tight",
27241
+ className: "flex items-baseline justify-between gap-2 leading-[1.25]",
27242
27242
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
27243
27243
  className: "text-white/55 shrink-0",
27244
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
27245
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
27246
+ className: "flex items-baseline gap-1 text-right",
27247
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
27248
+ className: strong ? "text-white font-semibold tabular-nums" : "text-white/90 tabular-nums",
27249
+ children: value
27250
+ }), window !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
27251
+ className: "text-sky-300/80 tabular-nums",
27252
+ children: window
27253
+ })]
27248
27254
  })]
27249
27255
  });
27250
27256
  }
@@ -27261,16 +27267,45 @@ function SectionHeader({ title, measuredAt, now }) {
27261
27267
  })]
27262
27268
  });
27263
27269
  }
27270
+ /** The pill's dot: the verdict, in the one place that is always visible. */
27271
+ var VERDICT_DOT = {
27272
+ unknown: "bg-white/50",
27273
+ decoding: "bg-emerald-400",
27274
+ "decoder-stall": "bg-amber-400",
27275
+ "transport-stall": "bg-rose-400",
27276
+ "frozen-unknown": "bg-white/50"
27277
+ };
27264
27278
  function StreamDebugOverlay(props) {
27265
27279
  const { stats, server, serverError, serverWired, now, sessionId } = props;
27266
27280
  const verdict = freezeVerdict(stats);
27267
27281
  const win = stats?.window;
27282
+ const [expanded, setExpanded] = (0, react$1.useState)(false);
27283
+ const stale = stats !== null && now - stats.at > STALE_AFTER_MS;
27284
+ if (!expanded) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
27285
+ type: "button",
27286
+ onClick: () => setExpanded(true),
27287
+ className: "absolute bottom-10 left-2 z-20 transform-gpu flex items-center gap-1.5 rounded bg-neutral-950/90 ring-1 ring-white/20 px-1.5 py-0.5 text-[9px] font-mono text-white/85 tabular-nums select-text",
27288
+ "data-testid": "stream-debug-pill",
27289
+ title: verdict.label,
27290
+ children: [
27291
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `h-1.5 w-1.5 rounded-full ${VERDICT_DOT[verdict.kind]}` }),
27292
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: stats?.width !== null && stats?.width !== void 0 ? `${stats.width}×${stats.height}` : UNKNOWN }),
27293
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [n(stats?.fps), "fps"] }),
27294
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [n(win?.kbps), "k"] }),
27295
+ stale && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
27296
+ className: "text-amber-300",
27297
+ children: "stale"
27298
+ })
27299
+ ]
27300
+ });
27268
27301
  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",
27302
+ className: "absolute bottom-10 left-2 z-20 transform-gpu w-[13.5rem] max-h-[70%] overflow-y-auto rounded-md bg-neutral-950/95 ring-1 ring-white/25 shadow-lg px-1.5 py-1 text-[9px] font-mono text-white/90 select-text",
27270
27303
  "data-testid": "stream-debug-overlay",
27271
27304
  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]}`,
27305
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
27306
+ type: "button",
27307
+ onClick: () => setExpanded(false),
27308
+ className: `w-full text-left rounded px-1.5 py-0.5 mb-1 text-[9px] font-semibold ${VERDICT_CLASS[verdict.kind]}`,
27274
27309
  children: verdict.label
27275
27310
  }),
27276
27311
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionHeader, {
@@ -27281,11 +27316,7 @@ function StreamDebugOverlay(props) {
27281
27316
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
27282
27317
  label: "framesDecoded",
27283
27318
  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),
27319
+ window: win?.framesDecoded === null ? void 0 : `+${n(win?.framesDecoded)}`,
27289
27320
  strong: true
27290
27321
  }),
27291
27322
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
@@ -27294,40 +27325,28 @@ function StreamDebugOverlay(props) {
27294
27325
  strong: true
27295
27326
  }),
27296
27327
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
27297
- label: "· total frozen",
27328
+ label: "frozen",
27298
27329
  value: n(stats?.totalFreezeMs, { suffix: "ms" }),
27330
+ window: win?.freezeMs === null ? void 0 : `+${n(win?.freezeMs)}ms`,
27299
27331
  strong: true
27300
27332
  }),
27301
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
27302
- label: "· this window",
27303
- value: n(win?.freezeMs, { suffix: "ms" })
27304
- }),
27305
27333
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
27306
27334
  label: "pliCount",
27307
27335
  value: n(stats?.pliCount),
27336
+ window: win?.pliCount === null ? void 0 : `+${n(win?.pliCount)}`,
27308
27337
  strong: true
27309
27338
  }),
27310
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
27311
- label: "· this window",
27312
- value: n(win?.pliCount)
27313
- }),
27314
27339
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
27315
27340
  label: "framesDropped",
27316
27341
  value: n(stats?.framesDropped)
27317
27342
  }),
27318
27343
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
27319
- label: "decode time",
27344
+ label: "decode",
27320
27345
  value: n(stats?.avgDecodeMs, {
27321
27346
  digits: 1,
27322
27347
  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
- })
27348
+ }),
27349
+ window: win?.decodeMs === null ? void 0 : `${n(win?.decodeMs, { digits: 1 })}ms`
27331
27350
  }),
27332
27351
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionHeader, {
27333
27352
  title: "picture",
@@ -27385,33 +27404,24 @@ function StreamDebugOverlay(props) {
27385
27404
  }),
27386
27405
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
27387
27406
  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)
27407
+ value: n(stats?.packetsLost),
27408
+ window: win?.packetsLost === null ? void 0 : `+${n(win?.packetsLost)}`
27393
27409
  }),
27394
27410
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
27395
27411
  label: "loss",
27396
27412
  value: n(stats?.lossPercent, {
27397
27413
  digits: 2,
27398
27414
  suffix: "%"
27399
- })
27400
- }),
27401
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
27402
- label: "· this window",
27403
- value: n(win?.lossPercent, {
27415
+ }),
27416
+ window: win?.lossPercent === null ? void 0 : n(win?.lossPercent, {
27404
27417
  digits: 2,
27405
27418
  suffix: "%"
27406
27419
  })
27407
27420
  }),
27408
27421
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
27409
27422
  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)
27423
+ value: n(stats?.nackCount),
27424
+ window: win?.nackCount === null ? void 0 : `+${n(win?.nackCount)}`
27415
27425
  }),
27416
27426
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
27417
27427
  label: "candidate pair",
package/dist/index.js CHANGED
@@ -27212,15 +27212,21 @@ var VERDICT_CLASS = {
27212
27212
  "transport-stall": "bg-rose-500/30 text-rose-100",
27213
27213
  "frozen-unknown": "bg-white/15 text-white/70"
27214
27214
  };
27215
- function Row({ label, value, strong = false }) {
27215
+ function Row({ label, value, window, strong = false }) {
27216
27216
  return /* @__PURE__ */ jsxs("div", {
27217
- className: "flex items-baseline justify-between gap-3 leading-tight",
27217
+ className: "flex items-baseline justify-between gap-2 leading-[1.25]",
27218
27218
  children: [/* @__PURE__ */ jsx("span", {
27219
27219
  className: "text-white/55 shrink-0",
27220
27220
  children: label
27221
- }), /* @__PURE__ */ jsx("span", {
27222
- className: strong ? "text-white font-semibold tabular-nums" : "text-white/90 tabular-nums",
27223
- children: value
27221
+ }), /* @__PURE__ */ jsxs("span", {
27222
+ className: "flex items-baseline gap-1 text-right",
27223
+ children: [/* @__PURE__ */ jsx("span", {
27224
+ className: strong ? "text-white font-semibold tabular-nums" : "text-white/90 tabular-nums",
27225
+ children: value
27226
+ }), window !== void 0 && /* @__PURE__ */ jsx("span", {
27227
+ className: "text-sky-300/80 tabular-nums",
27228
+ children: window
27229
+ })]
27224
27230
  })]
27225
27231
  });
27226
27232
  }
@@ -27237,16 +27243,45 @@ function SectionHeader({ title, measuredAt, now }) {
27237
27243
  })]
27238
27244
  });
27239
27245
  }
27246
+ /** The pill's dot: the verdict, in the one place that is always visible. */
27247
+ var VERDICT_DOT = {
27248
+ unknown: "bg-white/50",
27249
+ decoding: "bg-emerald-400",
27250
+ "decoder-stall": "bg-amber-400",
27251
+ "transport-stall": "bg-rose-400",
27252
+ "frozen-unknown": "bg-white/50"
27253
+ };
27240
27254
  function StreamDebugOverlay(props) {
27241
27255
  const { stats, server, serverError, serverWired, now, sessionId } = props;
27242
27256
  const verdict = freezeVerdict(stats);
27243
27257
  const win = stats?.window;
27258
+ const [expanded, setExpanded] = useState(false);
27259
+ const stale = stats !== null && now - stats.at > STALE_AFTER_MS;
27260
+ if (!expanded) return /* @__PURE__ */ jsxs("button", {
27261
+ type: "button",
27262
+ onClick: () => setExpanded(true),
27263
+ className: "absolute bottom-10 left-2 z-20 transform-gpu flex items-center gap-1.5 rounded bg-neutral-950/90 ring-1 ring-white/20 px-1.5 py-0.5 text-[9px] font-mono text-white/85 tabular-nums select-text",
27264
+ "data-testid": "stream-debug-pill",
27265
+ title: verdict.label,
27266
+ children: [
27267
+ /* @__PURE__ */ jsx("span", { className: `h-1.5 w-1.5 rounded-full ${VERDICT_DOT[verdict.kind]}` }),
27268
+ /* @__PURE__ */ jsx("span", { children: stats?.width !== null && stats?.width !== void 0 ? `${stats.width}×${stats.height}` : UNKNOWN }),
27269
+ /* @__PURE__ */ jsxs("span", { children: [n(stats?.fps), "fps"] }),
27270
+ /* @__PURE__ */ jsxs("span", { children: [n(win?.kbps), "k"] }),
27271
+ stale && /* @__PURE__ */ jsx("span", {
27272
+ className: "text-amber-300",
27273
+ children: "stale"
27274
+ })
27275
+ ]
27276
+ });
27244
27277
  return /* @__PURE__ */ jsxs("div", {
27245
- 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",
27278
+ className: "absolute bottom-10 left-2 z-20 transform-gpu w-[13.5rem] max-h-[70%] overflow-y-auto rounded-md bg-neutral-950/95 ring-1 ring-white/25 shadow-lg px-1.5 py-1 text-[9px] font-mono text-white/90 select-text",
27246
27279
  "data-testid": "stream-debug-overlay",
27247
27280
  children: [
27248
- /* @__PURE__ */ jsx("div", {
27249
- className: `rounded px-1.5 py-1 mb-1 text-[10px] font-semibold ${VERDICT_CLASS[verdict.kind]}`,
27281
+ /* @__PURE__ */ jsx("button", {
27282
+ type: "button",
27283
+ onClick: () => setExpanded(false),
27284
+ className: `w-full text-left rounded px-1.5 py-0.5 mb-1 text-[9px] font-semibold ${VERDICT_CLASS[verdict.kind]}`,
27250
27285
  children: verdict.label
27251
27286
  }),
27252
27287
  /* @__PURE__ */ jsx(SectionHeader, {
@@ -27257,11 +27292,7 @@ function StreamDebugOverlay(props) {
27257
27292
  /* @__PURE__ */ jsx(Row, {
27258
27293
  label: "framesDecoded",
27259
27294
  value: n(stats?.framesDecoded),
27260
- strong: true
27261
- }),
27262
- /* @__PURE__ */ jsx(Row, {
27263
- label: "· this window",
27264
- value: n(win?.framesDecoded),
27295
+ window: win?.framesDecoded === null ? void 0 : `+${n(win?.framesDecoded)}`,
27265
27296
  strong: true
27266
27297
  }),
27267
27298
  /* @__PURE__ */ jsx(Row, {
@@ -27270,40 +27301,28 @@ function StreamDebugOverlay(props) {
27270
27301
  strong: true
27271
27302
  }),
27272
27303
  /* @__PURE__ */ jsx(Row, {
27273
- label: "· total frozen",
27304
+ label: "frozen",
27274
27305
  value: n(stats?.totalFreezeMs, { suffix: "ms" }),
27306
+ window: win?.freezeMs === null ? void 0 : `+${n(win?.freezeMs)}ms`,
27275
27307
  strong: true
27276
27308
  }),
27277
- /* @__PURE__ */ jsx(Row, {
27278
- label: "· this window",
27279
- value: n(win?.freezeMs, { suffix: "ms" })
27280
- }),
27281
27309
  /* @__PURE__ */ jsx(Row, {
27282
27310
  label: "pliCount",
27283
27311
  value: n(stats?.pliCount),
27312
+ window: win?.pliCount === null ? void 0 : `+${n(win?.pliCount)}`,
27284
27313
  strong: true
27285
27314
  }),
27286
- /* @__PURE__ */ jsx(Row, {
27287
- label: "· this window",
27288
- value: n(win?.pliCount)
27289
- }),
27290
27315
  /* @__PURE__ */ jsx(Row, {
27291
27316
  label: "framesDropped",
27292
27317
  value: n(stats?.framesDropped)
27293
27318
  }),
27294
27319
  /* @__PURE__ */ jsx(Row, {
27295
- label: "decode time",
27320
+ label: "decode",
27296
27321
  value: n(stats?.avgDecodeMs, {
27297
27322
  digits: 1,
27298
27323
  suffix: "ms"
27299
- })
27300
- }),
27301
- /* @__PURE__ */ jsx(Row, {
27302
- label: "· this window",
27303
- value: n(win?.decodeMs, {
27304
- digits: 1,
27305
- suffix: "ms"
27306
- })
27324
+ }),
27325
+ window: win?.decodeMs === null ? void 0 : `${n(win?.decodeMs, { digits: 1 })}ms`
27307
27326
  }),
27308
27327
  /* @__PURE__ */ jsx(SectionHeader, {
27309
27328
  title: "picture",
@@ -27361,33 +27380,24 @@ function StreamDebugOverlay(props) {
27361
27380
  }),
27362
27381
  /* @__PURE__ */ jsx(Row, {
27363
27382
  label: "packetsLost",
27364
- value: n(stats?.packetsLost)
27365
- }),
27366
- /* @__PURE__ */ jsx(Row, {
27367
- label: "· this window",
27368
- value: n(win?.packetsLost)
27383
+ value: n(stats?.packetsLost),
27384
+ window: win?.packetsLost === null ? void 0 : `+${n(win?.packetsLost)}`
27369
27385
  }),
27370
27386
  /* @__PURE__ */ jsx(Row, {
27371
27387
  label: "loss",
27372
27388
  value: n(stats?.lossPercent, {
27373
27389
  digits: 2,
27374
27390
  suffix: "%"
27375
- })
27376
- }),
27377
- /* @__PURE__ */ jsx(Row, {
27378
- label: "· this window",
27379
- value: n(win?.lossPercent, {
27391
+ }),
27392
+ window: win?.lossPercent === null ? void 0 : n(win?.lossPercent, {
27380
27393
  digits: 2,
27381
27394
  suffix: "%"
27382
27395
  })
27383
27396
  }),
27384
27397
  /* @__PURE__ */ jsx(Row, {
27385
27398
  label: "nackCount",
27386
- value: n(stats?.nackCount)
27387
- }),
27388
- /* @__PURE__ */ jsx(Row, {
27389
- label: "· this window",
27390
- value: n(win?.nackCount)
27399
+ value: n(stats?.nackCount),
27400
+ window: win?.nackCount === null ? void 0 : `+${n(win?.nackCount)}`
27391
27401
  }),
27392
27402
  /* @__PURE__ */ jsx(Row, {
27393
27403
  label: "candidate pair",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/ui-library",
3
- "version": "1.2.182",
3
+ "version": "1.2.183",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",