@camstack/ui-library 1.2.200 → 1.2.202
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/adaptive-downlink.d.ts +99 -7
- package/dist/composites/camera-stream-player.d.ts +11 -11
- package/dist/composites/client-network-sample.d.ts +56 -0
- package/dist/generated/system-hooks.d.ts +2 -0
- package/dist/index.cjs +280 -66
- package/dist/index.js +280 -67
- package/dist/lib/format-last-seen.d.ts +12 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7046,6 +7046,8 @@ var useDeviceManagerGetBindings = trpc.deviceManager.getBindings.useQuery;
|
|
|
7046
7046
|
var useDeviceManagerGetBindingsBatch = trpc.deviceManager.getBindingsBatch.useQuery;
|
|
7047
7047
|
/** Generated alias around `trpc.deviceManager.getAllBindings.useQuery`. */
|
|
7048
7048
|
var useDeviceManagerGetAllBindings = trpc.deviceManager.getAllBindings.useQuery;
|
|
7049
|
+
/** Generated alias around `trpc.deviceManager.listDeviceIdsByCaps.useQuery`. */
|
|
7050
|
+
var useDeviceManagerListDeviceIdsByCaps = trpc.deviceManager.listDeviceIdsByCaps.useQuery;
|
|
7049
7051
|
/** Generated alias around `trpc.deviceManager.setWrapperActive.useMutation`. */
|
|
7050
7052
|
var useDeviceManagerSetWrapperActive = trpc.deviceManager.setWrapperActive.useMutation;
|
|
7051
7053
|
/** Generated alias around `trpc.deviceManager.listWrappersForCap.useQuery`. */
|
|
@@ -29544,9 +29546,26 @@ function nextReconnectAction(attempt, random = Math.random) {
|
|
|
29544
29546
|
* Used as `hints.downlinkMbps` so adaptive selection is based on a measured
|
|
29545
29547
|
* path, not viewport size or IP class. Failures return null (caller keeps
|
|
29546
29548
|
* the default-low / ceiling path and may stay on the lowest rung).
|
|
29549
|
+
*
|
|
29550
|
+
* D635 (2026-09-25): the probe used to time a 128 KiB blob from the REQUEST,
|
|
29551
|
+
* fired in parallel with `createSession` on a hub at ~80 % CPU. It read the
|
|
29552
|
+
* LAN as 1.2–1.46 Mbps — the elapsed time was mostly waiting for the first
|
|
29553
|
+
* byte, i.e. latency. It now (1) times the body from its FIRST byte, (2) reads
|
|
29554
|
+
* a 1 MiB blob so the transfer dominates timer jitter, and (3) runs after the
|
|
29555
|
+
* session's first decoded frame, never during session setup.
|
|
29547
29556
|
*/
|
|
29548
29557
|
var ADAPTIVE_PROBE_PATH = "/adaptive-probe";
|
|
29558
|
+
/**
|
|
29559
|
+
* Whole-probe deadline, from the request. A probe cut here is still a
|
|
29560
|
+
* measurement of whatever body arrived (see {@link measureDownlinkMbps}).
|
|
29561
|
+
*/
|
|
29549
29562
|
var ADAPTIVE_PROBE_TIMEOUT_MS = 2500;
|
|
29563
|
+
/** How long the probe waits for the session's first decoded frame. */
|
|
29564
|
+
var FIRST_FRAME_PROBE_TIMEOUT_MS = 15e3;
|
|
29565
|
+
/** How often the first-frame wait looks at the video element. */
|
|
29566
|
+
var FIRST_FRAME_POLL_MS = 250;
|
|
29567
|
+
/** `HTMLMediaElement.HAVE_CURRENT_DATA`: a frame is decoded and presentable. */
|
|
29568
|
+
var HAVE_CURRENT_DATA = 2;
|
|
29550
29569
|
/** Map a measured downlink onto the adaptive ladder. Mid is a real rung. */
|
|
29551
29570
|
function suggestedAdaptiveTier(mbps) {
|
|
29552
29571
|
if (mbps >= 8) return "high";
|
|
@@ -29620,27 +29639,153 @@ function adaptiveHintsKey(report) {
|
|
|
29620
29639
|
report.devicePixelRatio ?? "-",
|
|
29621
29640
|
rung,
|
|
29622
29641
|
report.prefersTier ?? "-",
|
|
29623
|
-
report.probeOrigin ?? "unreachable"
|
|
29642
|
+
report.probeOrigin ?? "unreachable",
|
|
29643
|
+
report.probeStatus ?? "-"
|
|
29624
29644
|
].join("|");
|
|
29625
29645
|
}
|
|
29646
|
+
function mbpsOf(bytes, elapsedMs) {
|
|
29647
|
+
if (bytes < 65536 || !(elapsedMs > 0)) return null;
|
|
29648
|
+
return bytes * 8 / elapsedMs / 1e3;
|
|
29649
|
+
}
|
|
29650
|
+
/**
|
|
29651
|
+
* Stream the body and time it from the FIRST chunk to the last one. The first
|
|
29652
|
+
* chunk's own bytes are excluded — they arrived at the instant the clock
|
|
29653
|
+
* started. A read that fails (the deadline abort) ends the measurement with
|
|
29654
|
+
* what DID arrive: a slow link cut at 2.5 s is still a measured slow link.
|
|
29655
|
+
*/
|
|
29656
|
+
async function timeStreamedBody(reader, now) {
|
|
29657
|
+
let firstAt = null;
|
|
29658
|
+
let lastAt = 0;
|
|
29659
|
+
let bytes = 0;
|
|
29660
|
+
try {
|
|
29661
|
+
for (;;) {
|
|
29662
|
+
const chunk = await reader.read();
|
|
29663
|
+
if (chunk.done) break;
|
|
29664
|
+
const size = chunk.value?.byteLength ?? 0;
|
|
29665
|
+
const at = now();
|
|
29666
|
+
if (firstAt === null) firstAt = at;
|
|
29667
|
+
else bytes += size;
|
|
29668
|
+
lastAt = at;
|
|
29669
|
+
}
|
|
29670
|
+
} catch {
|
|
29671
|
+
reader.cancel().catch(() => void 0);
|
|
29672
|
+
}
|
|
29673
|
+
if (firstAt === null) return null;
|
|
29674
|
+
return mbpsOf(bytes, lastAt - firstAt);
|
|
29675
|
+
}
|
|
29626
29676
|
async function measureDownlinkMbps(origin, deps) {
|
|
29627
29677
|
const base = origin.replace(/\/+$/, "");
|
|
29628
29678
|
const ctrl = new AbortController();
|
|
29629
29679
|
const timer = setTimeout(() => ctrl.abort(), ADAPTIVE_PROBE_TIMEOUT_MS);
|
|
29630
|
-
const started = deps.now();
|
|
29631
29680
|
try {
|
|
29632
29681
|
const res = await deps.fetchImpl(`${base}${ADAPTIVE_PROBE_PATH}`, { signal: ctrl.signal });
|
|
29633
|
-
if (!res.ok)
|
|
29634
|
-
|
|
29635
|
-
|
|
29636
|
-
|
|
29637
|
-
|
|
29682
|
+
if (!res.ok) {
|
|
29683
|
+
if (res.status !== void 0) deps.onRefused?.(res.status);
|
|
29684
|
+
return null;
|
|
29685
|
+
}
|
|
29686
|
+
const reader = res.body?.getReader() ?? null;
|
|
29687
|
+
if (reader !== null) return await timeStreamedBody(reader, deps.now);
|
|
29688
|
+
const headersAt = deps.now();
|
|
29689
|
+
return mbpsOf((await res.arrayBuffer()).byteLength, deps.now() - headersAt);
|
|
29638
29690
|
} catch {
|
|
29639
29691
|
return null;
|
|
29640
29692
|
} finally {
|
|
29641
29693
|
clearTimeout(timer);
|
|
29642
29694
|
}
|
|
29643
29695
|
}
|
|
29696
|
+
/**
|
|
29697
|
+
* Resolve once the session presents its first decoded frame — the moment
|
|
29698
|
+
* session setup (createSession, ICE, DTLS, the first IDR) is over and a probe
|
|
29699
|
+
* no longer competes with it for the hub or the link.
|
|
29700
|
+
*/
|
|
29701
|
+
async function waitForFirstFrame(deps, timeoutMs = FIRST_FRAME_PROBE_TIMEOUT_MS) {
|
|
29702
|
+
const deadline = deps.now() + timeoutMs;
|
|
29703
|
+
for (;;) {
|
|
29704
|
+
if (deps.signal.aborted) return "aborted";
|
|
29705
|
+
if ((deps.readyState() ?? 0) >= HAVE_CURRENT_DATA) return "frame";
|
|
29706
|
+
if (deps.now() >= deadline) return "timeout";
|
|
29707
|
+
await deps.sleep(FIRST_FRAME_POLL_MS);
|
|
29708
|
+
}
|
|
29709
|
+
}
|
|
29710
|
+
async function probeAfterFirstFrame(deps) {
|
|
29711
|
+
if (await deps.waitFrame() !== "frame") return void 0;
|
|
29712
|
+
return deps.measure();
|
|
29713
|
+
}
|
|
29714
|
+
/** App-wide probe cache + single-flight, one per origin. */
|
|
29715
|
+
function createDownlinkProbe(deps) {
|
|
29716
|
+
const readings = /* @__PURE__ */ new Map();
|
|
29717
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
29718
|
+
const refusals = /* @__PURE__ */ new Set();
|
|
29719
|
+
const cached = (origin) => {
|
|
29720
|
+
const r = readings.get(origin);
|
|
29721
|
+
return r !== void 0 && deps.now() - r.at <= deps.ttlMs ? r.mbps : void 0;
|
|
29722
|
+
};
|
|
29723
|
+
return {
|
|
29724
|
+
cached,
|
|
29725
|
+
firstRefusal: (origin, status) => {
|
|
29726
|
+
const key = `${origin}|${String(status)}`;
|
|
29727
|
+
if (refusals.has(key)) return false;
|
|
29728
|
+
refusals.add(key);
|
|
29729
|
+
return true;
|
|
29730
|
+
},
|
|
29731
|
+
latest: (origin) => {
|
|
29732
|
+
const r = readings.get(origin);
|
|
29733
|
+
return r === void 0 ? null : {
|
|
29734
|
+
mbps: r.mbps,
|
|
29735
|
+
ageMs: Math.max(0, deps.now() - r.at)
|
|
29736
|
+
};
|
|
29737
|
+
},
|
|
29738
|
+
measured: (origin, measure) => {
|
|
29739
|
+
const hit = cached(origin);
|
|
29740
|
+
if (hit !== void 0) return Promise.resolve(hit);
|
|
29741
|
+
const running = inFlight.get(origin);
|
|
29742
|
+
if (running !== void 0) return running;
|
|
29743
|
+
const probe = measure().then((mbps) => {
|
|
29744
|
+
if (mbps === null || !Number.isFinite(mbps)) return void 0;
|
|
29745
|
+
readings.set(origin, {
|
|
29746
|
+
mbps,
|
|
29747
|
+
at: deps.now()
|
|
29748
|
+
});
|
|
29749
|
+
return mbps;
|
|
29750
|
+
}).catch(() => void 0).finally(() => {
|
|
29751
|
+
inFlight.delete(origin);
|
|
29752
|
+
});
|
|
29753
|
+
inFlight.set(origin, probe);
|
|
29754
|
+
return probe;
|
|
29755
|
+
}
|
|
29756
|
+
};
|
|
29757
|
+
}
|
|
29758
|
+
//#endregion
|
|
29759
|
+
//#region src/composites/client-network-sample.ts
|
|
29760
|
+
/** Schema bound of `estimatedBandwidthKbps` / `packetLossPercent`. */
|
|
29761
|
+
var MAX_LOSS_PERCENT = 100;
|
|
29762
|
+
function nextClientSample(prev, cur, ctx) {
|
|
29763
|
+
if (ctx.renegotiating) return {
|
|
29764
|
+
baseline: null,
|
|
29765
|
+
sample: null
|
|
29766
|
+
};
|
|
29767
|
+
if (prev === null || prev.pc !== cur.pc || cur.ts <= prev.ts) return {
|
|
29768
|
+
baseline: cur,
|
|
29769
|
+
sample: null
|
|
29770
|
+
};
|
|
29771
|
+
const dLost = Math.max(0, cur.lost - prev.lost);
|
|
29772
|
+
const denom = dLost + Math.max(0, cur.recv - prev.recv);
|
|
29773
|
+
const packetLossPercent = denom > 0 ? Math.min(MAX_LOSS_PERCENT, dLost / denom * 100) : 0;
|
|
29774
|
+
const estimatedBandwidthKbps = Math.max(0, Math.round((cur.bytes - prev.bytes) * 8 / (cur.ts - prev.ts)));
|
|
29775
|
+
return {
|
|
29776
|
+
baseline: cur,
|
|
29777
|
+
sample: {
|
|
29778
|
+
rttMs: Math.round(cur.rttS * 1e3),
|
|
29779
|
+
jitterMs: Math.round(cur.jitterS * 1e3),
|
|
29780
|
+
estimatedBandwidthKbps,
|
|
29781
|
+
packetLossPercent,
|
|
29782
|
+
...ctx.probe !== null ? {
|
|
29783
|
+
downlinkMbps: ctx.probe.mbps,
|
|
29784
|
+
downlinkAgeMs: ctx.probe.ageMs
|
|
29785
|
+
} : {}
|
|
29786
|
+
}
|
|
29787
|
+
};
|
|
29788
|
+
}
|
|
29644
29789
|
//#endregion
|
|
29645
29790
|
//#region src/composites/stream-debug/webrtc-debug-stats.ts
|
|
29646
29791
|
/** Beyond this the log is a scroll, not a signal. */
|
|
@@ -30184,11 +30329,11 @@ function StreamDebugOverlay(props) {
|
|
|
30184
30329
|
}),
|
|
30185
30330
|
server.lastDecision === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
30186
30331
|
label: "ladder",
|
|
30187
|
-
value: "never moved"
|
|
30332
|
+
value: server.climbHold ? "held" : "never moved"
|
|
30188
30333
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
30189
30334
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
30190
30335
|
label: "ladder move",
|
|
30191
|
-
value: `${server.lastDecision.cause}${server.lastDecision.tier !== null ? ` → ${server.lastDecision.tier}` : ""}`,
|
|
30336
|
+
value: `${server.lastDecision.causeDetail ?? server.lastDecision.cause}${server.lastDecision.tier !== null ? ` → ${server.lastDecision.tier}` : ""}`,
|
|
30192
30337
|
strong: true
|
|
30193
30338
|
}),
|
|
30194
30339
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
@@ -30216,6 +30361,10 @@ function StreamDebugOverlay(props) {
|
|
|
30216
30361
|
value: b(server.lastDecision.forcedTranscodeDown)
|
|
30217
30362
|
})
|
|
30218
30363
|
] }),
|
|
30364
|
+
server.climbHold ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
30365
|
+
label: "climb",
|
|
30366
|
+
value: server.climbHold.summary
|
|
30367
|
+
}) : null,
|
|
30219
30368
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
30220
30369
|
label: "promotions",
|
|
30221
30370
|
value: n(server.upgradeCount)
|
|
@@ -30294,8 +30443,15 @@ function computeClientHints(container) {
|
|
|
30294
30443
|
}
|
|
30295
30444
|
return hints;
|
|
30296
30445
|
}
|
|
30297
|
-
|
|
30298
|
-
|
|
30446
|
+
/**
|
|
30447
|
+
* ONE probe store for the whole app: every player of every camera shares the
|
|
30448
|
+
* cache and the probe in flight (review I6 — a grid decoding together fired
|
|
30449
|
+
* N concurrent 1 MiB probes, each reading the link ÷ N).
|
|
30450
|
+
*/
|
|
30451
|
+
var downlinkProbe = createDownlinkProbe({
|
|
30452
|
+
now: () => Date.now(),
|
|
30453
|
+
ttlMs: 3e4
|
|
30454
|
+
});
|
|
30299
30455
|
/**
|
|
30300
30456
|
* Measure the downlink against the HUB, not the page. `serverUrl` is the hub
|
|
30301
30457
|
* this player is already signalling to; the page origin is only a fallback,
|
|
@@ -30306,21 +30462,44 @@ var DOWNLINK_TTL_MS = 3e4;
|
|
|
30306
30462
|
function probeOriginFor(serverUrl) {
|
|
30307
30463
|
return resolveProbeOrigin(serverUrl, typeof window !== "undefined" ? hubOrigin() : void 0);
|
|
30308
30464
|
}
|
|
30309
|
-
|
|
30465
|
+
function measuredDownlinkMbps(serverUrl, authToken, onRefused) {
|
|
30310
30466
|
const origin = probeOriginFor(serverUrl);
|
|
30311
|
-
if (origin === null) return void 0;
|
|
30312
|
-
|
|
30313
|
-
|
|
30314
|
-
|
|
30467
|
+
if (origin === null) return Promise.resolve(void 0);
|
|
30468
|
+
return downlinkProbe.measured(origin, () => measureDownlinkMbps(origin, {
|
|
30469
|
+
onRefused: (status) => {
|
|
30470
|
+
if (downlinkProbe.firstRefusal(origin, status)) onRefused(origin, status);
|
|
30471
|
+
},
|
|
30472
|
+
fetchImpl: (url, init) => fetch(url, {
|
|
30473
|
+
...init,
|
|
30474
|
+
...authToken !== null ? { headers: { authorization: `Bearer ${authToken}` } } : {}
|
|
30475
|
+
}),
|
|
30315
30476
|
now: () => Date.now()
|
|
30477
|
+
}));
|
|
30478
|
+
}
|
|
30479
|
+
/** The cached probe only — never measures (review I5: a renegotiation must not
|
|
30480
|
+
* push 1 MiB into the link that just stalled, nor wait 2.5 s for it). */
|
|
30481
|
+
function cachedDownlinkMbps(serverUrl) {
|
|
30482
|
+
const origin = probeOriginFor(serverUrl);
|
|
30483
|
+
return origin === null ? void 0 : downlinkProbe.cached(origin);
|
|
30484
|
+
}
|
|
30485
|
+
/** The newest probe whatever it read — carried in the telemetry (review I4). */
|
|
30486
|
+
function latestProbeReading(serverUrl) {
|
|
30487
|
+
const origin = probeOriginFor(serverUrl);
|
|
30488
|
+
return origin === null ? null : downlinkProbe.latest(origin);
|
|
30489
|
+
}
|
|
30490
|
+
function probeAfterThisStreamsFirstFrame(args) {
|
|
30491
|
+
return probeAfterFirstFrame({
|
|
30492
|
+
waitFrame: () => waitForFirstFrame({
|
|
30493
|
+
readyState: () => {
|
|
30494
|
+
const v = args.video();
|
|
30495
|
+
return v !== null && v.srcObject === args.stream ? v.readyState : null;
|
|
30496
|
+
},
|
|
30497
|
+
signal: args.signal,
|
|
30498
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
30499
|
+
now: () => Date.now()
|
|
30500
|
+
}),
|
|
30501
|
+
measure: () => measuredDownlinkMbps(args.serverUrl, args.authToken, args.onRefused)
|
|
30316
30502
|
});
|
|
30317
|
-
if (mbps === null || !Number.isFinite(mbps)) return void 0;
|
|
30318
|
-
downlinkCache = {
|
|
30319
|
-
origin,
|
|
30320
|
-
mbps,
|
|
30321
|
-
at: Date.now()
|
|
30322
|
-
};
|
|
30323
|
-
return mbps;
|
|
30324
30503
|
}
|
|
30325
30504
|
function mergePlayerHints(autoHints, override, downlinkMbps, coldStart) {
|
|
30326
30505
|
const merged = {
|
|
@@ -30331,7 +30510,7 @@ function mergePlayerHints(autoHints, override, downlinkMbps, coldStart) {
|
|
|
30331
30510
|
return coldStart ? stripDownlinkForColdStart(merged) : merged;
|
|
30332
30511
|
}
|
|
30333
30512
|
var FIRST_FRAME_TIMEOUT_MS = 8e3;
|
|
30334
|
-
function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, muted: initialMuted = true, showControls = true, showStats = false, onDebugStats, onPlaybackStats, onClientNetworkSample, onConnectTiming, className = "", onStateChange, onError, onReconnectAttempt, overlay, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, getSessionDebug, reoffer, posterUrl, hintsOverride, onAdaptiveHints, reconnectSignal, onControlChannel, onVideoElement }) {
|
|
30513
|
+
function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, muted: initialMuted = true, showControls = true, showStats = false, onDebugStats, onPlaybackStats, onClientNetworkSample, probeAuthToken, onConnectTiming, className = "", onStateChange, onError, onReconnectAttempt, overlay, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, getSessionDebug, reoffer, posterUrl, hintsOverride, onAdaptiveHints, reconnectSignal, onControlChannel, onVideoElement }) {
|
|
30335
30514
|
const videoRef = (0, react$1.useRef)(null);
|
|
30336
30515
|
const containerRef = (0, react$1.useRef)(null);
|
|
30337
30516
|
const pcRef = (0, react$1.useRef)(null);
|
|
@@ -30379,6 +30558,19 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
30379
30558
|
lastAdaptiveKeyRef.current = key;
|
|
30380
30559
|
cb(report);
|
|
30381
30560
|
}, []);
|
|
30561
|
+
/**
|
|
30562
|
+
* A refused probe (401 without a session, 429) goes out on the SAME channel
|
|
30563
|
+
* as the adaptive decisions — hosts already forward it over their log bridge
|
|
30564
|
+
* — once per origin app-wide (`firstRefusal`), so a missing `probeAuthToken`
|
|
30565
|
+
* is visible instead of reading like a failed probe (review N3).
|
|
30566
|
+
*/
|
|
30567
|
+
const reportProbeRefused = (0, react$1.useCallback)((origin, status) => {
|
|
30568
|
+
onAdaptiveHintsRef.current?.({
|
|
30569
|
+
phase: "probe-refused",
|
|
30570
|
+
probeOrigin: origin,
|
|
30571
|
+
probeStatus: status
|
|
30572
|
+
});
|
|
30573
|
+
}, []);
|
|
30382
30574
|
/** The live session being polled for `pendingRenegotiation` (client-offer). */
|
|
30383
30575
|
const activeSessionIdRef = (0, react$1.useRef)(null);
|
|
30384
30576
|
/** Timer for the session-state (renegotiation) poll loop. */
|
|
@@ -30501,7 +30693,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
30501
30693
|
tick();
|
|
30502
30694
|
}, SESSION_STATE_POLL_MS);
|
|
30503
30695
|
}, [getSessionState, reoffer]);
|
|
30504
|
-
const connectServerOffer = (0, react$1.useCallback)(async (seamless = false, withDownlink = seamless) => {
|
|
30696
|
+
const connectServerOffer = (0, react$1.useCallback)(async (seamless = false, withDownlink = seamless, knownDownlinkMbps) => {
|
|
30505
30697
|
if (!mountedRef.current || !createSession || !sendAnswer) return;
|
|
30506
30698
|
const oldPc = seamless ? pcRef.current : null;
|
|
30507
30699
|
const oldSessions = seamless ? new Set(sessionIdsRef.current) : null;
|
|
@@ -30511,18 +30703,10 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
30511
30703
|
const abort = new AbortController();
|
|
30512
30704
|
connectAbortRef.current = abort;
|
|
30513
30705
|
try {
|
|
30514
|
-
const
|
|
30515
|
-
const mergedHints = mergePlayerHints(computeClientHints(containerRef.current), hintsOverride, withDownlink ? await measuredDownlinkMbps(serverUrl) : void 0, !withDownlink);
|
|
30706
|
+
const mergedHints = mergePlayerHints(computeClientHints(containerRef.current), hintsOverride, withDownlink ? knownDownlinkMbps ?? await measuredDownlinkMbps(serverUrl, probeAuthToken ?? null, reportProbeRefused) : void 0, !withDownlink);
|
|
30516
30707
|
reportAdaptiveHints(withDownlink ? "probe-climb" : "cold-start", mergedHints, probeOriginFor(serverUrl));
|
|
30517
30708
|
const { sessionId, sdpOffer, iceServers } = await createSession(mergedHints);
|
|
30518
30709
|
sessionIdsRef.current.add(sessionId);
|
|
30519
|
-
if (probePromise !== void 0) probePromise.then((mbps) => {
|
|
30520
|
-
if (mbps === void 0 || !shouldUpgradeAfterProbe(mbps)) return;
|
|
30521
|
-
if (!mountedRef.current || abort.signal.aborted) return;
|
|
30522
|
-
if (probeUpgradeRef.current) return;
|
|
30523
|
-
probeUpgradeRef.current = true;
|
|
30524
|
-
connectServerOffer(true, true);
|
|
30525
|
-
});
|
|
30526
30710
|
if (!mountedRef.current || abort.signal.aborted) {
|
|
30527
30711
|
closeSession?.(sessionId).catch(() => {});
|
|
30528
30712
|
sessionIdsRef.current.delete(sessionId);
|
|
@@ -30531,6 +30715,20 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
30531
30715
|
const pc = new RTCPeerConnection({ iceServers: [...iceServers ?? [], { urls: "stun:stun.l.google.com:19302" }] });
|
|
30532
30716
|
if (!seamless) pcRef.current = pc;
|
|
30533
30717
|
const combinedStream = new MediaStream();
|
|
30718
|
+
if (!withDownlink) probeAfterThisStreamsFirstFrame({
|
|
30719
|
+
video: () => videoRef.current,
|
|
30720
|
+
stream: combinedStream,
|
|
30721
|
+
signal: abort.signal,
|
|
30722
|
+
serverUrl,
|
|
30723
|
+
authToken: probeAuthToken ?? null,
|
|
30724
|
+
onRefused: reportProbeRefused
|
|
30725
|
+
}).then((mbps) => {
|
|
30726
|
+
if (mbps === void 0 || !shouldUpgradeAfterProbe(mbps)) return;
|
|
30727
|
+
if (!mountedRef.current || abort.signal.aborted) return;
|
|
30728
|
+
if (probeUpgradeRef.current) return;
|
|
30729
|
+
probeUpgradeRef.current = true;
|
|
30730
|
+
connectServerOffer(true, true, mbps);
|
|
30731
|
+
});
|
|
30534
30732
|
let swappedIn = false;
|
|
30535
30733
|
const swapInOnce = () => {
|
|
30536
30734
|
if (swappedIn || !mountedRef.current) return;
|
|
@@ -30630,7 +30828,9 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
30630
30828
|
updateState,
|
|
30631
30829
|
onError,
|
|
30632
30830
|
hintsOverride,
|
|
30633
|
-
reportAdaptiveHints
|
|
30831
|
+
reportAdaptiveHints,
|
|
30832
|
+
probeAuthToken,
|
|
30833
|
+
reportProbeRefused
|
|
30634
30834
|
]);
|
|
30635
30835
|
const connectWhep = (0, react$1.useCallback)(async () => {
|
|
30636
30836
|
if (!mountedRef.current) return;
|
|
@@ -30693,7 +30893,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
30693
30893
|
updateState,
|
|
30694
30894
|
onError
|
|
30695
30895
|
]);
|
|
30696
|
-
const connectClientOffer = (0, react$1.useCallback)(async (seamless = false, withDownlink = seamless) => {
|
|
30896
|
+
const connectClientOffer = (0, react$1.useCallback)(async (seamless = false, withDownlink = seamless, knownDownlinkMbps) => {
|
|
30697
30897
|
if (!mountedRef.current || !handleOffer) return;
|
|
30698
30898
|
const oldPc = seamless ? pcRef.current : null;
|
|
30699
30899
|
const oldSessions = seamless ? new Set(sessionIdsRef.current) : null;
|
|
@@ -30706,13 +30906,26 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
30706
30906
|
const ms = () => Math.round(performance.now() - t0);
|
|
30707
30907
|
const timing = {};
|
|
30708
30908
|
try {
|
|
30709
|
-
const probePromise = !withDownlink ? measuredDownlinkMbps(serverUrl) : void 0;
|
|
30710
30909
|
const iceServers = await getIceServers?.();
|
|
30711
30910
|
timing.iceServersMs = ms();
|
|
30712
30911
|
if (!mountedRef.current || abort.signal.aborted) return;
|
|
30713
30912
|
const pc = new RTCPeerConnection({ iceServers: [...iceServers ?? [], { urls: "stun:stun.l.google.com:19302" }] });
|
|
30714
30913
|
if (!seamless) pcRef.current = pc;
|
|
30715
30914
|
const combinedStream = new MediaStream();
|
|
30915
|
+
if (!withDownlink) probeAfterThisStreamsFirstFrame({
|
|
30916
|
+
video: () => videoRef.current,
|
|
30917
|
+
stream: combinedStream,
|
|
30918
|
+
signal: abort.signal,
|
|
30919
|
+
serverUrl,
|
|
30920
|
+
authToken: probeAuthToken ?? null,
|
|
30921
|
+
onRefused: reportProbeRefused
|
|
30922
|
+
}).then((mbps) => {
|
|
30923
|
+
if (mbps === void 0 || !shouldUpgradeAfterProbe(mbps)) return;
|
|
30924
|
+
if (!mountedRef.current || abort.signal.aborted) return;
|
|
30925
|
+
if (probeUpgradeRef.current) return;
|
|
30926
|
+
probeUpgradeRef.current = true;
|
|
30927
|
+
connectClientOffer(true, true, mbps);
|
|
30928
|
+
});
|
|
30716
30929
|
let swappedIn = false;
|
|
30717
30930
|
const swapInOnce = () => {
|
|
30718
30931
|
if (swappedIn || !mountedRef.current) return;
|
|
@@ -30814,7 +31027,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
30814
31027
|
await pc.setLocalDescription(offer);
|
|
30815
31028
|
timing.localSetMs = ms();
|
|
30816
31029
|
if (!mountedRef.current || abort.signal.aborted) return;
|
|
30817
|
-
const downlinkMbps = withDownlink ? await measuredDownlinkMbps(serverUrl) : void 0;
|
|
31030
|
+
const downlinkMbps = withDownlink ? knownDownlinkMbps ?? await measuredDownlinkMbps(serverUrl, probeAuthToken ?? null, reportProbeRefused) : void 0;
|
|
30818
31031
|
const clientOfferHints = mergePlayerHints(computeClientHints(containerRef.current), hintsOverride, downlinkMbps, !withDownlink);
|
|
30819
31032
|
reportAdaptiveHints(withDownlink ? "probe-climb" : "cold-start", clientOfferHints, probeOriginFor(serverUrl));
|
|
30820
31033
|
const signalingStart = ms();
|
|
@@ -30822,13 +31035,6 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
30822
31035
|
timing.signalingRttMs = ms() - signalingStart;
|
|
30823
31036
|
sessionId = sid;
|
|
30824
31037
|
sessionIdsRef.current.add(sid);
|
|
30825
|
-
if (probePromise !== void 0) probePromise.then((mbps) => {
|
|
30826
|
-
if (mbps === void 0 || !shouldUpgradeAfterProbe(mbps)) return;
|
|
30827
|
-
if (!mountedRef.current || abort.signal.aborted) return;
|
|
30828
|
-
if (probeUpgradeRef.current) return;
|
|
30829
|
-
probeUpgradeRef.current = true;
|
|
30830
|
-
connectClientOffer(true, true);
|
|
30831
|
-
});
|
|
30832
31038
|
activeSessionIdRef.current = sid;
|
|
30833
31039
|
if (!mountedRef.current || abort.signal.aborted) {
|
|
30834
31040
|
closeSession?.(sid).catch(() => {});
|
|
@@ -30889,7 +31095,9 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
30889
31095
|
onError,
|
|
30890
31096
|
startSessionStatePoll,
|
|
30891
31097
|
hintsOverride,
|
|
30892
|
-
reportAdaptiveHints
|
|
31098
|
+
reportAdaptiveHints,
|
|
31099
|
+
probeAuthToken,
|
|
31100
|
+
reportProbeRefused
|
|
30893
31101
|
]);
|
|
30894
31102
|
renegotiateRef.current = (0, react$1.useCallback)(async (sessionId, target) => {
|
|
30895
31103
|
const reofferFn = reoffer;
|
|
@@ -30996,7 +31204,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
30996
31204
|
pc.close();
|
|
30997
31205
|
return;
|
|
30998
31206
|
}
|
|
30999
|
-
const downlinkMbps =
|
|
31207
|
+
const downlinkMbps = cachedDownlinkMbps(serverUrl);
|
|
31000
31208
|
const reofferHints = {
|
|
31001
31209
|
...computeClientHints(containerRef.current),
|
|
31002
31210
|
...downlinkMbps !== void 0 ? { downlinkMbps } : {},
|
|
@@ -31355,31 +31563,27 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
31355
31563
|
jitterS = s.jitter ?? 0;
|
|
31356
31564
|
} else if (s.type === "candidate-pair" && s.nominated && typeof s.currentRoundTripTime === "number") rttS = s.currentRoundTripTime;
|
|
31357
31565
|
});
|
|
31358
|
-
const
|
|
31359
|
-
|
|
31566
|
+
const step = nextClientSample(netPrev.current, {
|
|
31567
|
+
pc,
|
|
31360
31568
|
lost,
|
|
31361
31569
|
recv,
|
|
31362
31570
|
bytes,
|
|
31363
|
-
ts
|
|
31364
|
-
|
|
31365
|
-
|
|
31366
|
-
|
|
31367
|
-
|
|
31368
|
-
|
|
31369
|
-
const estimatedBandwidthKbps = Math.max(0, Math.round((bytes - prev.bytes) * 8 / (ts - prev.ts)));
|
|
31370
|
-
onClientNetworkSample({
|
|
31371
|
-
rttMs: Math.round(rttS * 1e3),
|
|
31372
|
-
jitterMs: Math.round(jitterS * 1e3),
|
|
31373
|
-
estimatedBandwidthKbps,
|
|
31374
|
-
packetLossPercent
|
|
31571
|
+
ts,
|
|
31572
|
+
rttS,
|
|
31573
|
+
jitterS
|
|
31574
|
+
}, {
|
|
31575
|
+
renegotiating: renegotiatingRef.current,
|
|
31576
|
+
probe: latestProbeReading(serverUrl)
|
|
31375
31577
|
});
|
|
31578
|
+
netPrev.current = step.baseline;
|
|
31579
|
+
if (step.sample !== null) onClientNetworkSample(step.sample);
|
|
31376
31580
|
} catch {}
|
|
31377
31581
|
};
|
|
31378
31582
|
const id = setInterval(() => {
|
|
31379
31583
|
tick();
|
|
31380
31584
|
}, 2e3);
|
|
31381
31585
|
return () => clearInterval(id);
|
|
31382
|
-
}, [onClientNetworkSample]);
|
|
31586
|
+
}, [onClientNetworkSample, serverUrl]);
|
|
31383
31587
|
const pbPrev = (0, react$1.useRef)(null);
|
|
31384
31588
|
(0, react$1.useEffect)(() => {
|
|
31385
31589
|
if (!onPlaybackStats) {
|
|
@@ -45811,9 +46015,11 @@ function resolveControlAlign(type, placement) {
|
|
|
45811
46015
|
* Relative "last seen" formatter for the offline affordance.
|
|
45812
46016
|
*
|
|
45813
46017
|
* Renders a short relative-time label from a millisecond epoch:
|
|
45814
|
-
* "just now", "5m ago", "3h ago", "2d ago". Returns `null` for
|
|
45815
|
-
*
|
|
45816
|
-
*
|
|
46018
|
+
* "just now", "5m ago", "3h ago", "2d ago". Returns `null` for missing
|
|
46019
|
+
* inputs, or inputs below {@link MIN_PLAUSIBLE_TIMESTAMP_MS} (a
|
|
46020
|
+
* placeholder or a seeded "known value, unknown change time" stamp), so
|
|
46021
|
+
* callers can render nothing rather than a misleading "—", "NaN ago", or
|
|
46022
|
+
* (for a seeded `1`) "20000 days ago".
|
|
45817
46023
|
*
|
|
45818
46024
|
* Future timestamps (clock skew between the device and the browser)
|
|
45819
46025
|
* collapse to "just now" instead of a negative duration.
|
|
@@ -45823,8 +46029,15 @@ var MINUTE_MS = 60 * SECOND_MS;
|
|
|
45823
46029
|
var HOUR_MS = 60 * MINUTE_MS;
|
|
45824
46030
|
var DAY_MS = 24 * HOUR_MS;
|
|
45825
46031
|
var JUST_NOW_MS = 30 * SECOND_MS;
|
|
46032
|
+
/**
|
|
46033
|
+
* The earliest timestamp a real "last seen" could plausibly carry. Below
|
|
46034
|
+
* this, a value is a placeholder or a seeded "known value, unknown change
|
|
46035
|
+
* time" stamp (e.g. Homematic's `HM_SEEDED_CHANGED_AT = 1`) — never a real
|
|
46036
|
+
* instant. Year 2000 predates every device this renders.
|
|
46037
|
+
*/
|
|
46038
|
+
var MIN_PLAUSIBLE_TIMESTAMP_MS = Date.UTC(2e3, 0, 1);
|
|
45826
46039
|
function formatLastSeen(timestampMs) {
|
|
45827
|
-
if (timestampMs === null || timestampMs === void 0 || timestampMs
|
|
46040
|
+
if (timestampMs === null || timestampMs === void 0 || timestampMs < MIN_PLAUSIBLE_TIMESTAMP_MS) return null;
|
|
45828
46041
|
const delta = Date.now() - timestampMs;
|
|
45829
46042
|
if (delta < JUST_NOW_MS) return "just now";
|
|
45830
46043
|
if (delta < HOUR_MS) return `${String(Math.floor(delta / MINUTE_MS))}m ago`;
|
|
@@ -56450,6 +56663,7 @@ exports.useDeviceManagerGetStreamSources = useDeviceManagerGetStreamSources;
|
|
|
56450
56663
|
exports.useDeviceManagerGetWireableFields = useDeviceManagerGetWireableFields;
|
|
56451
56664
|
exports.useDeviceManagerListAll = useDeviceManagerListAll;
|
|
56452
56665
|
exports.useDeviceManagerListBindableCapsForDeviceType = useDeviceManagerListBindableCapsForDeviceType;
|
|
56666
|
+
exports.useDeviceManagerListDeviceIdsByCaps = useDeviceManagerListDeviceIdsByCaps;
|
|
56453
56667
|
exports.useDeviceManagerListLocationDescriptions = useDeviceManagerListLocationDescriptions;
|
|
56454
56668
|
exports.useDeviceManagerListLocationIcons = useDeviceManagerListLocationIcons;
|
|
56455
56669
|
exports.useDeviceManagerListLocations = useDeviceManagerListLocations;
|