@camstack/ui-library 1.1.13 → 1.1.15
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.
|
@@ -13,9 +13,10 @@ interface TimelineControlsProps {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Date selector + zoom-in / zoom-out buttons for the recording timeline.
|
|
16
|
-
* Zoom cycles through ZOOM_LEVELS (widest → narrowest),
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* Zoom cycles through ZOOM_LEVELS (widest → narrowest), anchored on the view's
|
|
17
|
+
* RIGHT edge — so on today's timeline "now" stays pinned to the right instead
|
|
18
|
+
* of drifting to a midday range. Horizontal pan (wheel) then moves the window
|
|
19
|
+
* back/forward in time. Pan is wired via wheel events in RecordingTimeline.
|
|
19
20
|
*/
|
|
20
21
|
export declare function TimelineControls({ day, onDayChange, view, dayBounds, onViewChange, }: TimelineControlsProps): import("react").JSX.Element;
|
|
21
22
|
export {};
|
|
@@ -58,10 +58,17 @@ export declare function dayWindow(date: Date, nowMs: number): DayWindow;
|
|
|
58
58
|
/** Available zoom levels in ms, from widest (24h) to narrowest (15 min). */
|
|
59
59
|
export declare const ZOOM_LEVELS: readonly number[];
|
|
60
60
|
/**
|
|
61
|
-
* Produce a `levelMs`-wide
|
|
62
|
-
* `
|
|
61
|
+
* Produce a `levelMs`-wide window whose RIGHT edge stays anchored at
|
|
62
|
+
* `rightEdgeMs` (clamped within `dayBounds`), rather than centring on the view
|
|
63
|
+
* midpoint. On today's timeline the view's right edge is "now", so zooming this
|
|
64
|
+
* way keeps the most recent footage pinned to the right instead of drifting to
|
|
65
|
+
* a midday range that no longer contains now. Panning then moves back/forward.
|
|
66
|
+
*
|
|
67
|
+
* Falls back to `dayBounds` when `levelMs` meets or exceeds the day span (can't
|
|
68
|
+
* show a wider window than the day holds — e.g. early morning with little
|
|
69
|
+
* elapsed time).
|
|
63
70
|
*/
|
|
64
|
-
export declare function
|
|
71
|
+
export declare function zoomWindowAnchoredRight(dayBounds: DayWindow, levelMs: number, rightEdgeMs: number): DayWindow;
|
|
65
72
|
/**
|
|
66
73
|
* Shift `win` by `deltaMs`, preserving width, clamped within `dayBounds`.
|
|
67
74
|
*/
|
package/dist/index.cjs
CHANGED
|
@@ -32695,23 +32695,21 @@ var ZOOM_LEVELS = [
|
|
|
32695
32695
|
15 * 6e4
|
|
32696
32696
|
];
|
|
32697
32697
|
/**
|
|
32698
|
-
* Produce a `levelMs`-wide
|
|
32699
|
-
* `
|
|
32698
|
+
* Produce a `levelMs`-wide window whose RIGHT edge stays anchored at
|
|
32699
|
+
* `rightEdgeMs` (clamped within `dayBounds`), rather than centring on the view
|
|
32700
|
+
* midpoint. On today's timeline the view's right edge is "now", so zooming this
|
|
32701
|
+
* way keeps the most recent footage pinned to the right instead of drifting to
|
|
32702
|
+
* a midday range that no longer contains now. Panning then moves back/forward.
|
|
32703
|
+
*
|
|
32704
|
+
* Falls back to `dayBounds` when `levelMs` meets or exceeds the day span (can't
|
|
32705
|
+
* show a wider window than the day holds — e.g. early morning with little
|
|
32706
|
+
* elapsed time).
|
|
32700
32707
|
*/
|
|
32701
|
-
function
|
|
32708
|
+
function zoomWindowAnchoredRight(dayBounds, levelMs, rightEdgeMs) {
|
|
32702
32709
|
if (levelMs >= dayBounds.toMs - dayBounds.fromMs) return dayBounds;
|
|
32703
|
-
|
|
32704
|
-
let toMs = centerMs + levelMs / 2;
|
|
32705
|
-
if (fromMs < dayBounds.fromMs) {
|
|
32706
|
-
fromMs = dayBounds.fromMs;
|
|
32707
|
-
toMs = fromMs + levelMs;
|
|
32708
|
-
}
|
|
32709
|
-
if (toMs > dayBounds.toMs) {
|
|
32710
|
-
toMs = dayBounds.toMs;
|
|
32711
|
-
fromMs = toMs - levelMs;
|
|
32712
|
-
}
|
|
32710
|
+
const toMs = Math.min(Math.max(rightEdgeMs, dayBounds.fromMs + levelMs), dayBounds.toMs);
|
|
32713
32711
|
return {
|
|
32714
|
-
fromMs,
|
|
32712
|
+
fromMs: toMs - levelMs,
|
|
32715
32713
|
toMs
|
|
32716
32714
|
};
|
|
32717
32715
|
}
|
|
@@ -33026,9 +33024,10 @@ function parseDateInput(value) {
|
|
|
33026
33024
|
}
|
|
33027
33025
|
/**
|
|
33028
33026
|
* Date selector + zoom-in / zoom-out buttons for the recording timeline.
|
|
33029
|
-
* Zoom cycles through ZOOM_LEVELS (widest → narrowest),
|
|
33030
|
-
*
|
|
33031
|
-
*
|
|
33027
|
+
* Zoom cycles through ZOOM_LEVELS (widest → narrowest), anchored on the view's
|
|
33028
|
+
* RIGHT edge — so on today's timeline "now" stays pinned to the right instead
|
|
33029
|
+
* of drifting to a midday range. Horizontal pan (wheel) then moves the window
|
|
33030
|
+
* back/forward in time. Pan is wired via wheel events in RecordingTimeline.
|
|
33032
33031
|
*/
|
|
33033
33032
|
function TimelineControls({ day, onDayChange, view, dayBounds, onViewChange }) {
|
|
33034
33033
|
const handleDateChange = (e) => {
|
|
@@ -33050,16 +33049,16 @@ function TimelineControls({ day, onDayChange, view, dayBounds, onViewChange }) {
|
|
|
33050
33049
|
}
|
|
33051
33050
|
return best;
|
|
33052
33051
|
})();
|
|
33053
|
-
const
|
|
33052
|
+
const rightEdgeMs = view.toMs;
|
|
33054
33053
|
const handleZoomIn = () => {
|
|
33055
33054
|
const nextLevel = ZOOM_LEVELS[Math.min(currentLevelIdx + 1, ZOOM_LEVELS.length - 1)];
|
|
33056
33055
|
if (nextLevel === void 0) return;
|
|
33057
|
-
onViewChange(
|
|
33056
|
+
onViewChange(zoomWindowAnchoredRight(dayBounds, nextLevel, rightEdgeMs));
|
|
33058
33057
|
};
|
|
33059
33058
|
const handleZoomOut = () => {
|
|
33060
33059
|
const nextLevel = ZOOM_LEVELS[Math.max(currentLevelIdx - 1, 0)];
|
|
33061
33060
|
if (nextLevel === void 0) return;
|
|
33062
|
-
onViewChange(
|
|
33061
|
+
onViewChange(zoomWindowAnchoredRight(dayBounds, nextLevel, rightEdgeMs));
|
|
33063
33062
|
};
|
|
33064
33063
|
const canZoomIn = currentLevelIdx < ZOOM_LEVELS.length - 1;
|
|
33065
33064
|
const canZoomOut = currentLevelIdx > 0;
|
|
@@ -37361,8 +37360,12 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
37361
37360
|
updateState("connecting");
|
|
37362
37361
|
const abort = new AbortController();
|
|
37363
37362
|
connectAbortRef.current = abort;
|
|
37363
|
+
const t0 = performance.now();
|
|
37364
|
+
const ms = () => Math.round(performance.now() - t0);
|
|
37365
|
+
const timing = {};
|
|
37364
37366
|
try {
|
|
37365
37367
|
const iceServers = await getIceServers?.();
|
|
37368
|
+
timing.iceServersMs = ms();
|
|
37366
37369
|
if (!mountedRef.current || abort.signal.aborted) return;
|
|
37367
37370
|
const pc = new RTCPeerConnection({ iceServers: [...iceServers ?? [], { urls: "stun:stun.l.google.com:19302" }] });
|
|
37368
37371
|
if (!seamless) pcRef.current = pc;
|
|
@@ -37385,6 +37388,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
37385
37388
|
}
|
|
37386
37389
|
};
|
|
37387
37390
|
pc.ontrack = (event) => {
|
|
37391
|
+
if (timing.firstTrackMs === void 0) timing.firstTrackMs = ms();
|
|
37388
37392
|
console.debug("[WebRTC] ontrack (client-offer)", event.track.kind, event.track.readyState);
|
|
37389
37393
|
combinedStream.addTrack(event.track);
|
|
37390
37394
|
if (seamless) {
|
|
@@ -37396,11 +37400,20 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
37396
37400
|
videoRef.current.play().catch(() => {});
|
|
37397
37401
|
}
|
|
37398
37402
|
};
|
|
37403
|
+
let timingLogged = false;
|
|
37399
37404
|
pc.oniceconnectionstatechange = () => {
|
|
37400
37405
|
if (!mountedRef.current) return;
|
|
37401
37406
|
const iceState = pc.iceConnectionState;
|
|
37402
37407
|
console.debug("[WebRTC] ICE state (client-offer):", iceState);
|
|
37403
37408
|
if (iceState === "connected" || iceState === "completed") {
|
|
37409
|
+
if (!timingLogged) {
|
|
37410
|
+
timingLogged = true;
|
|
37411
|
+
timing.iceConnectedMs = ms();
|
|
37412
|
+
console.info("[WebRTC timing] client-offer connected", {
|
|
37413
|
+
streamKey,
|
|
37414
|
+
...timing
|
|
37415
|
+
});
|
|
37416
|
+
}
|
|
37404
37417
|
reconnectAttemptsRef.current = 0;
|
|
37405
37418
|
if (seamless) swapInOnce();
|
|
37406
37419
|
updateState("playing");
|
|
@@ -37428,9 +37441,13 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
37428
37441
|
onControlChannelRef.current(control);
|
|
37429
37442
|
}
|
|
37430
37443
|
const offer = await pc.createOffer();
|
|
37444
|
+
timing.offerCreatedMs = ms();
|
|
37431
37445
|
await pc.setLocalDescription(offer);
|
|
37446
|
+
timing.localSetMs = ms();
|
|
37432
37447
|
if (!mountedRef.current || abort.signal.aborted) return;
|
|
37448
|
+
const signalingStart = ms();
|
|
37433
37449
|
const { sessionId: sid, sdpAnswer } = await handleOffer(pc.localDescription?.sdp ?? offer.sdp ?? "");
|
|
37450
|
+
timing.signalingRttMs = ms() - signalingStart;
|
|
37434
37451
|
sessionId = sid;
|
|
37435
37452
|
sessionIdsRef.current.add(sid);
|
|
37436
37453
|
activeSessionIdRef.current = sid;
|
|
@@ -37445,6 +37462,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
37445
37462
|
type: "answer",
|
|
37446
37463
|
sdp: sdpAnswer
|
|
37447
37464
|
});
|
|
37465
|
+
timing.answerSetMs = ms();
|
|
37448
37466
|
console.debug("[WebRTC] Client-offer answer set (trickle)", { sessionId: sid.slice(0, 8) });
|
|
37449
37467
|
if (getIceCandidates) {
|
|
37450
37468
|
const seen = /* @__PURE__ */ new Set();
|
package/dist/index.js
CHANGED
|
@@ -32671,23 +32671,21 @@ var ZOOM_LEVELS = [
|
|
|
32671
32671
|
15 * 6e4
|
|
32672
32672
|
];
|
|
32673
32673
|
/**
|
|
32674
|
-
* Produce a `levelMs`-wide
|
|
32675
|
-
* `
|
|
32674
|
+
* Produce a `levelMs`-wide window whose RIGHT edge stays anchored at
|
|
32675
|
+
* `rightEdgeMs` (clamped within `dayBounds`), rather than centring on the view
|
|
32676
|
+
* midpoint. On today's timeline the view's right edge is "now", so zooming this
|
|
32677
|
+
* way keeps the most recent footage pinned to the right instead of drifting to
|
|
32678
|
+
* a midday range that no longer contains now. Panning then moves back/forward.
|
|
32679
|
+
*
|
|
32680
|
+
* Falls back to `dayBounds` when `levelMs` meets or exceeds the day span (can't
|
|
32681
|
+
* show a wider window than the day holds — e.g. early morning with little
|
|
32682
|
+
* elapsed time).
|
|
32676
32683
|
*/
|
|
32677
|
-
function
|
|
32684
|
+
function zoomWindowAnchoredRight(dayBounds, levelMs, rightEdgeMs) {
|
|
32678
32685
|
if (levelMs >= dayBounds.toMs - dayBounds.fromMs) return dayBounds;
|
|
32679
|
-
|
|
32680
|
-
let toMs = centerMs + levelMs / 2;
|
|
32681
|
-
if (fromMs < dayBounds.fromMs) {
|
|
32682
|
-
fromMs = dayBounds.fromMs;
|
|
32683
|
-
toMs = fromMs + levelMs;
|
|
32684
|
-
}
|
|
32685
|
-
if (toMs > dayBounds.toMs) {
|
|
32686
|
-
toMs = dayBounds.toMs;
|
|
32687
|
-
fromMs = toMs - levelMs;
|
|
32688
|
-
}
|
|
32686
|
+
const toMs = Math.min(Math.max(rightEdgeMs, dayBounds.fromMs + levelMs), dayBounds.toMs);
|
|
32689
32687
|
return {
|
|
32690
|
-
fromMs,
|
|
32688
|
+
fromMs: toMs - levelMs,
|
|
32691
32689
|
toMs
|
|
32692
32690
|
};
|
|
32693
32691
|
}
|
|
@@ -33002,9 +33000,10 @@ function parseDateInput(value) {
|
|
|
33002
33000
|
}
|
|
33003
33001
|
/**
|
|
33004
33002
|
* Date selector + zoom-in / zoom-out buttons for the recording timeline.
|
|
33005
|
-
* Zoom cycles through ZOOM_LEVELS (widest → narrowest),
|
|
33006
|
-
*
|
|
33007
|
-
*
|
|
33003
|
+
* Zoom cycles through ZOOM_LEVELS (widest → narrowest), anchored on the view's
|
|
33004
|
+
* RIGHT edge — so on today's timeline "now" stays pinned to the right instead
|
|
33005
|
+
* of drifting to a midday range. Horizontal pan (wheel) then moves the window
|
|
33006
|
+
* back/forward in time. Pan is wired via wheel events in RecordingTimeline.
|
|
33008
33007
|
*/
|
|
33009
33008
|
function TimelineControls({ day, onDayChange, view, dayBounds, onViewChange }) {
|
|
33010
33009
|
const handleDateChange = (e) => {
|
|
@@ -33026,16 +33025,16 @@ function TimelineControls({ day, onDayChange, view, dayBounds, onViewChange }) {
|
|
|
33026
33025
|
}
|
|
33027
33026
|
return best;
|
|
33028
33027
|
})();
|
|
33029
|
-
const
|
|
33028
|
+
const rightEdgeMs = view.toMs;
|
|
33030
33029
|
const handleZoomIn = () => {
|
|
33031
33030
|
const nextLevel = ZOOM_LEVELS[Math.min(currentLevelIdx + 1, ZOOM_LEVELS.length - 1)];
|
|
33032
33031
|
if (nextLevel === void 0) return;
|
|
33033
|
-
onViewChange(
|
|
33032
|
+
onViewChange(zoomWindowAnchoredRight(dayBounds, nextLevel, rightEdgeMs));
|
|
33034
33033
|
};
|
|
33035
33034
|
const handleZoomOut = () => {
|
|
33036
33035
|
const nextLevel = ZOOM_LEVELS[Math.max(currentLevelIdx - 1, 0)];
|
|
33037
33036
|
if (nextLevel === void 0) return;
|
|
33038
|
-
onViewChange(
|
|
33037
|
+
onViewChange(zoomWindowAnchoredRight(dayBounds, nextLevel, rightEdgeMs));
|
|
33039
33038
|
};
|
|
33040
33039
|
const canZoomIn = currentLevelIdx < ZOOM_LEVELS.length - 1;
|
|
33041
33040
|
const canZoomOut = currentLevelIdx > 0;
|
|
@@ -37337,8 +37336,12 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
37337
37336
|
updateState("connecting");
|
|
37338
37337
|
const abort = new AbortController();
|
|
37339
37338
|
connectAbortRef.current = abort;
|
|
37339
|
+
const t0 = performance.now();
|
|
37340
|
+
const ms = () => Math.round(performance.now() - t0);
|
|
37341
|
+
const timing = {};
|
|
37340
37342
|
try {
|
|
37341
37343
|
const iceServers = await getIceServers?.();
|
|
37344
|
+
timing.iceServersMs = ms();
|
|
37342
37345
|
if (!mountedRef.current || abort.signal.aborted) return;
|
|
37343
37346
|
const pc = new RTCPeerConnection({ iceServers: [...iceServers ?? [], { urls: "stun:stun.l.google.com:19302" }] });
|
|
37344
37347
|
if (!seamless) pcRef.current = pc;
|
|
@@ -37361,6 +37364,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
37361
37364
|
}
|
|
37362
37365
|
};
|
|
37363
37366
|
pc.ontrack = (event) => {
|
|
37367
|
+
if (timing.firstTrackMs === void 0) timing.firstTrackMs = ms();
|
|
37364
37368
|
console.debug("[WebRTC] ontrack (client-offer)", event.track.kind, event.track.readyState);
|
|
37365
37369
|
combinedStream.addTrack(event.track);
|
|
37366
37370
|
if (seamless) {
|
|
@@ -37372,11 +37376,20 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
37372
37376
|
videoRef.current.play().catch(() => {});
|
|
37373
37377
|
}
|
|
37374
37378
|
};
|
|
37379
|
+
let timingLogged = false;
|
|
37375
37380
|
pc.oniceconnectionstatechange = () => {
|
|
37376
37381
|
if (!mountedRef.current) return;
|
|
37377
37382
|
const iceState = pc.iceConnectionState;
|
|
37378
37383
|
console.debug("[WebRTC] ICE state (client-offer):", iceState);
|
|
37379
37384
|
if (iceState === "connected" || iceState === "completed") {
|
|
37385
|
+
if (!timingLogged) {
|
|
37386
|
+
timingLogged = true;
|
|
37387
|
+
timing.iceConnectedMs = ms();
|
|
37388
|
+
console.info("[WebRTC timing] client-offer connected", {
|
|
37389
|
+
streamKey,
|
|
37390
|
+
...timing
|
|
37391
|
+
});
|
|
37392
|
+
}
|
|
37380
37393
|
reconnectAttemptsRef.current = 0;
|
|
37381
37394
|
if (seamless) swapInOnce();
|
|
37382
37395
|
updateState("playing");
|
|
@@ -37404,9 +37417,13 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
37404
37417
|
onControlChannelRef.current(control);
|
|
37405
37418
|
}
|
|
37406
37419
|
const offer = await pc.createOffer();
|
|
37420
|
+
timing.offerCreatedMs = ms();
|
|
37407
37421
|
await pc.setLocalDescription(offer);
|
|
37422
|
+
timing.localSetMs = ms();
|
|
37408
37423
|
if (!mountedRef.current || abort.signal.aborted) return;
|
|
37424
|
+
const signalingStart = ms();
|
|
37409
37425
|
const { sessionId: sid, sdpAnswer } = await handleOffer(pc.localDescription?.sdp ?? offer.sdp ?? "");
|
|
37426
|
+
timing.signalingRttMs = ms() - signalingStart;
|
|
37410
37427
|
sessionId = sid;
|
|
37411
37428
|
sessionIdsRef.current.add(sid);
|
|
37412
37429
|
activeSessionIdRef.current = sid;
|
|
@@ -37421,6 +37438,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
37421
37438
|
type: "answer",
|
|
37422
37439
|
sdp: sdpAnswer
|
|
37423
37440
|
});
|
|
37441
|
+
timing.answerSetMs = ms();
|
|
37424
37442
|
console.debug("[WebRTC] Client-offer answer set (trickle)", { sessionId: sid.slice(0, 8) });
|
|
37425
37443
|
if (getIceCandidates) {
|
|
37426
37444
|
const seen = /* @__PURE__ */ new Set();
|