@camstack/ui-library 1.2.182 → 1.2.184

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. */
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { CamProfile, StationaryOverlayBox } from '@camstack/types';
2
+ import { CamProfile, WebrtcSessionDebug, StationaryOverlayBox } from '@camstack/types';
3
3
  import { SignalingResult, ClientOfferResult, ClientStreamHints, SessionSignalingState } from './camera-stream-player';
4
4
  import { DeviceDetections } from '../hooks/use-device-detections';
5
5
  /**
@@ -69,6 +69,12 @@ export interface StreamPanelProps {
69
69
  * adaptive re-offer (steady-state behaviour is unchanged).
70
70
  */
71
71
  readonly getSessionState?: (sessionId: string) => Promise<SessionSignalingState>;
72
+ /**
73
+ * The server half of the stream-debug overlay. Optional, and when it is
74
+ * absent the overlay SAYS so rather than showing an empty section — a panel
75
+ * that silently omits half its answer is worse than one that admits it.
76
+ */
77
+ readonly getSessionDebug?: (sessionId: string) => Promise<WebrtcSessionDebug>;
72
78
  /**
73
79
  * When set, the panel switches to VOD mode: it renders an `HlsVideo`
74
80
  * over this master `.m3u8` URL INSTEAD of the live WebRTC player, and
@@ -196,4 +202,4 @@ export interface StreamPanelProps {
196
202
  readonly chromeless?: boolean;
197
203
  readonly className?: string;
198
204
  }
199
- export declare function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, playbackUrl, playbackAuthToken, onExitPlayback, posterUrl, streams, activeStreamId: controlledStreamId, onStreamChange, deviceName, phase, pipelineMetrics, detections, stationaryObjects, defaultShowDetections, defaultShowMotion, streamStats, autoPlay, showPlayStop, snapshotSrc, snapshotLoading, onRefreshSnapshot, showStreamStats, children, extraOverlay, ptzAvailable, ptzShown, ptzOverlay, onPtzToggle, intercomAvailable, intercomShown, onIntercomToggle, chromeless, className, }: StreamPanelProps): import("react").JSX.Element;
205
+ export declare function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, getSessionDebug, playbackUrl, playbackAuthToken, onExitPlayback, posterUrl, streams, activeStreamId: controlledStreamId, onStreamChange, deviceName, phase, pipelineMetrics, detections, stationaryObjects, defaultShowDetections, defaultShowMotion, streamStats, autoPlay, showPlayStop, snapshotSrc, snapshotLoading, onRefreshSnapshot, showStreamStats, children, extraOverlay, ptzAvailable, ptzShown, ptzOverlay, onPtzToggle, intercomAvailable, intercomShown, onIntercomToggle, chromeless, className, }: StreamPanelProps): import("react").JSX.Element;
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",
@@ -27434,9 +27444,16 @@ function StreamDebugOverlay(props) {
27434
27444
  measuredAt: server?.observedAt ?? null,
27435
27445
  now
27436
27446
  }),
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"
27447
+ !serverWired ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
27448
+ className: "text-amber-300/90 leading-tight py-0.5",
27449
+ children: [
27450
+ "not wired on this surface — this player was mounted without",
27451
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
27452
+ className: "text-white/80",
27453
+ children: " getSessionDebug"
27454
+ }),
27455
+ ", so the server half cannot be asked"
27456
+ ]
27440
27457
  }) : serverError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
27441
27458
  className: "text-rose-300 leading-tight py-0.5 break-words",
27442
27459
  children: serverError
@@ -28598,7 +28615,10 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
28598
28615
  let cancelled = false;
28599
28616
  const tick = async () => {
28600
28617
  const sid = activeSessionIdRef.current;
28601
- if (sid === null) return;
28618
+ if (sid === null) {
28619
+ setServerDebugError("no session id on this player yet — nothing to ask the server about");
28620
+ return;
28621
+ }
28602
28622
  try {
28603
28623
  const answer = await getSessionDebug(sid);
28604
28624
  if (cancelled) return;
@@ -51062,7 +51082,7 @@ function ImageOffIcon({ className }) {
51062
51082
  ]
51063
51083
  });
51064
51084
  }
51065
- function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, playbackUrl, playbackAuthToken, onExitPlayback, posterUrl, streams, activeStreamId: controlledStreamId, onStreamChange, deviceName, phase, pipelineMetrics, detections, stationaryObjects, defaultShowDetections = true, defaultShowMotion = false, streamStats, autoPlay = false, showPlayStop = false, snapshotSrc, snapshotLoading = false, onRefreshSnapshot, showStreamStats = false, children, extraOverlay, ptzAvailable = false, ptzShown = false, ptzOverlay, onPtzToggle, intercomAvailable = false, intercomShown = false, onIntercomToggle, chromeless = false, className }) {
51085
+ function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, getSessionDebug, playbackUrl, playbackAuthToken, onExitPlayback, posterUrl, streams, activeStreamId: controlledStreamId, onStreamChange, deviceName, phase, pipelineMetrics, detections, stationaryObjects, defaultShowDetections = true, defaultShowMotion = false, streamStats, autoPlay = false, showPlayStop = false, snapshotSrc, snapshotLoading = false, onRefreshSnapshot, showStreamStats = false, children, extraOverlay, ptzAvailable = false, ptzShown = false, ptzOverlay, onPtzToggle, intercomAvailable = false, intercomShown = false, onIntercomToggle, chromeless = false, className }) {
51066
51086
  const registeredButtons = usePlayerToolbarButtons();
51067
51087
  const recordedPlayback = useRecordedPlayback();
51068
51088
  const [isPlaying, setIsPlaying] = (0, react$1.useState)(autoPlay);
@@ -51380,6 +51400,7 @@ function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIce
51380
51400
  handleOffer: playerHandleOffer,
51381
51401
  reoffer: playerReoffer,
51382
51402
  getSessionState,
51403
+ ...getSessionDebug ? { getSessionDebug } : {},
51383
51404
  getIceServers,
51384
51405
  addIceCandidate,
51385
51406
  getIceCandidates,
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",
@@ -27410,9 +27420,16 @@ function StreamDebugOverlay(props) {
27410
27420
  measuredAt: server?.observedAt ?? null,
27411
27421
  now
27412
27422
  }),
27413
- !serverWired ? /* @__PURE__ */ jsx("div", {
27414
- className: "text-white/50 leading-tight py-0.5",
27415
- children: "not wired on this host"
27423
+ !serverWired ? /* @__PURE__ */ jsxs("div", {
27424
+ className: "text-amber-300/90 leading-tight py-0.5",
27425
+ children: [
27426
+ "not wired on this surface — this player was mounted without",
27427
+ /* @__PURE__ */ jsx("span", {
27428
+ className: "text-white/80",
27429
+ children: " getSessionDebug"
27430
+ }),
27431
+ ", so the server half cannot be asked"
27432
+ ]
27416
27433
  }) : serverError !== null ? /* @__PURE__ */ jsx("div", {
27417
27434
  className: "text-rose-300 leading-tight py-0.5 break-words",
27418
27435
  children: serverError
@@ -28574,7 +28591,10 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
28574
28591
  let cancelled = false;
28575
28592
  const tick = async () => {
28576
28593
  const sid = activeSessionIdRef.current;
28577
- if (sid === null) return;
28594
+ if (sid === null) {
28595
+ setServerDebugError("no session id on this player yet — nothing to ask the server about");
28596
+ return;
28597
+ }
28578
28598
  try {
28579
28599
  const answer = await getSessionDebug(sid);
28580
28600
  if (cancelled) return;
@@ -51038,7 +51058,7 @@ function ImageOffIcon({ className }) {
51038
51058
  ]
51039
51059
  });
51040
51060
  }
51041
- function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, playbackUrl, playbackAuthToken, onExitPlayback, posterUrl, streams, activeStreamId: controlledStreamId, onStreamChange, deviceName, phase, pipelineMetrics, detections, stationaryObjects, defaultShowDetections = true, defaultShowMotion = false, streamStats, autoPlay = false, showPlayStop = false, snapshotSrc, snapshotLoading = false, onRefreshSnapshot, showStreamStats = false, children, extraOverlay, ptzAvailable = false, ptzShown = false, ptzOverlay, onPtzToggle, intercomAvailable = false, intercomShown = false, onIntercomToggle, chromeless = false, className }) {
51061
+ function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, getSessionDebug, playbackUrl, playbackAuthToken, onExitPlayback, posterUrl, streams, activeStreamId: controlledStreamId, onStreamChange, deviceName, phase, pipelineMetrics, detections, stationaryObjects, defaultShowDetections = true, defaultShowMotion = false, streamStats, autoPlay = false, showPlayStop = false, snapshotSrc, snapshotLoading = false, onRefreshSnapshot, showStreamStats = false, children, extraOverlay, ptzAvailable = false, ptzShown = false, ptzOverlay, onPtzToggle, intercomAvailable = false, intercomShown = false, onIntercomToggle, chromeless = false, className }) {
51042
51062
  const registeredButtons = usePlayerToolbarButtons();
51043
51063
  const recordedPlayback = useRecordedPlayback();
51044
51064
  const [isPlaying, setIsPlaying] = useState(autoPlay);
@@ -51356,6 +51376,7 @@ function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIce
51356
51376
  handleOffer: playerHandleOffer,
51357
51377
  reoffer: playerReoffer,
51358
51378
  getSessionState,
51379
+ ...getSessionDebug ? { getSessionDebug } : {},
51359
51380
  getIceServers,
51360
51381
  addIceCandidate,
51361
51382
  getIceCandidates,
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.184",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",