@camstack/ui-library 1.1.15 → 1.1.17
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/cap-settings/CoverageTrack.d.ts +12 -1
- package/dist/contexts/recorded-playback.d.ts +23 -0
- package/dist/generated/system-hooks.d.ts +14 -2
- package/dist/index.cjs +535 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.js +516 -11
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/recorded-control-protocol.d.ts +54 -0
- package/dist/lib/scrub-controller.d.ts +115 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7710,6 +7710,240 @@ function ensureMfHostInit() {
|
|
|
7710
7710
|
});
|
|
7711
7711
|
}
|
|
7712
7712
|
//#endregion
|
|
7713
|
+
//#region src/lib/recorded-control-protocol.ts
|
|
7714
|
+
/** Serialize a command for `RTCDataChannel.send` (matches the server schema). */
|
|
7715
|
+
function serializeRecordedCommand(cmd) {
|
|
7716
|
+
return JSON.stringify(cmd);
|
|
7717
|
+
}
|
|
7718
|
+
/**
|
|
7719
|
+
* Playback mode reported by the server session. `live` = the shared live feed
|
|
7720
|
+
* owns the track; every other value describes the recorded feeder.
|
|
7721
|
+
*/
|
|
7722
|
+
var RECORDED_PLAYBACK_MODES = [
|
|
7723
|
+
"live",
|
|
7724
|
+
"loading",
|
|
7725
|
+
"playing",
|
|
7726
|
+
"paused",
|
|
7727
|
+
"gap",
|
|
7728
|
+
"ended",
|
|
7729
|
+
"error"
|
|
7730
|
+
];
|
|
7731
|
+
function isRecordedPlaybackMode(value) {
|
|
7732
|
+
return typeof value === "string" && RECORDED_PLAYBACK_MODES.includes(value);
|
|
7733
|
+
}
|
|
7734
|
+
/**
|
|
7735
|
+
* Parse a raw server→client control frame. Returns `null` on any malformed
|
|
7736
|
+
* payload (bad JSON, unknown discriminator, wrong field types).
|
|
7737
|
+
*/
|
|
7738
|
+
function parseRecordedServerMessage(raw) {
|
|
7739
|
+
let parsed;
|
|
7740
|
+
try {
|
|
7741
|
+
parsed = JSON.parse(raw);
|
|
7742
|
+
} catch {
|
|
7743
|
+
return null;
|
|
7744
|
+
}
|
|
7745
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
7746
|
+
const obj = { ...parsed };
|
|
7747
|
+
if (obj.t === "state" && isRecordedPlaybackMode(obj.state)) return {
|
|
7748
|
+
kind: "state",
|
|
7749
|
+
state: obj.state
|
|
7750
|
+
};
|
|
7751
|
+
if (obj.t === "position" && typeof obj.epochMs === "number") return {
|
|
7752
|
+
kind: "position",
|
|
7753
|
+
epochMs: obj.epochMs
|
|
7754
|
+
};
|
|
7755
|
+
return null;
|
|
7756
|
+
}
|
|
7757
|
+
//#endregion
|
|
7758
|
+
//#region src/lib/scrub-controller.ts
|
|
7759
|
+
/**
|
|
7760
|
+
* scrub-controller — shared scrub-position state for timeline surfaces (R9).
|
|
7761
|
+
*
|
|
7762
|
+
* Ported from the proven srcV2 viewer implementation
|
|
7763
|
+
* (camstack/srcV2/recording/scrub-controller.ts): the same throttle,
|
|
7764
|
+
* commit-dedupe, and live→recorded-bypass semantics, now driving the
|
|
7765
|
+
* admin-ui `RecordingTimeline` through the `camstack-control` datachannel.
|
|
7766
|
+
*
|
|
7767
|
+
* Two layers:
|
|
7768
|
+
* 1. Pure reducer + helpers (unit-tested, no React deps):
|
|
7769
|
+
* scrubReducer, shouldEmit, shouldCommit, cursorFractionFor,
|
|
7770
|
+
* makeScrubBridge
|
|
7771
|
+
* 2. useScrubController hook: thin React glue that bridges the reducer
|
|
7772
|
+
* to a PlaybackControlSink (e.g. the recorded-playback datachannel).
|
|
7773
|
+
*/
|
|
7774
|
+
var initialScrubState = {
|
|
7775
|
+
scrubbing: false,
|
|
7776
|
+
scrubEpoch: null,
|
|
7777
|
+
playheadEpoch: null,
|
|
7778
|
+
lastSource: null
|
|
7779
|
+
};
|
|
7780
|
+
function scrubReducer(state, action) {
|
|
7781
|
+
switch (action.type) {
|
|
7782
|
+
case "begin": return {
|
|
7783
|
+
...state,
|
|
7784
|
+
scrubbing: true,
|
|
7785
|
+
scrubEpoch: null,
|
|
7786
|
+
lastSource: action.source
|
|
7787
|
+
};
|
|
7788
|
+
case "update": return {
|
|
7789
|
+
...state,
|
|
7790
|
+
scrubbing: true,
|
|
7791
|
+
scrubEpoch: action.epoch,
|
|
7792
|
+
lastSource: action.source
|
|
7793
|
+
};
|
|
7794
|
+
case "end": return {
|
|
7795
|
+
...state,
|
|
7796
|
+
scrubbing: false,
|
|
7797
|
+
scrubEpoch: null,
|
|
7798
|
+
playheadEpoch: action.epoch,
|
|
7799
|
+
lastSource: action.source
|
|
7800
|
+
};
|
|
7801
|
+
case "seek": return {
|
|
7802
|
+
...state,
|
|
7803
|
+
scrubbing: false,
|
|
7804
|
+
scrubEpoch: null,
|
|
7805
|
+
playheadEpoch: action.epoch,
|
|
7806
|
+
lastSource: action.source
|
|
7807
|
+
};
|
|
7808
|
+
case "playhead":
|
|
7809
|
+
if (state.scrubbing) return state;
|
|
7810
|
+
return {
|
|
7811
|
+
...state,
|
|
7812
|
+
playheadEpoch: action.epoch
|
|
7813
|
+
};
|
|
7814
|
+
case "reset": return initialScrubState;
|
|
7815
|
+
default: return state;
|
|
7816
|
+
}
|
|
7817
|
+
}
|
|
7818
|
+
/**
|
|
7819
|
+
* Throttle decision for continuous `update` emits (default ~70 ms).
|
|
7820
|
+
* Returns true when enough time has elapsed since the last emit.
|
|
7821
|
+
*/
|
|
7822
|
+
function shouldEmit(lastEmitMs, now, throttleMs = 70) {
|
|
7823
|
+
return now - lastEmitMs >= throttleMs;
|
|
7824
|
+
}
|
|
7825
|
+
/** Epochs within this many ms of the last commit count as the SAME target. */
|
|
7826
|
+
var COMMIT_DEDUPE_TOLERANCE_MS = 750;
|
|
7827
|
+
/** Only dedupe against a commit made within this recent window. */
|
|
7828
|
+
var COMMIT_DEDUPE_WINDOW_MS = 2e3;
|
|
7829
|
+
/**
|
|
7830
|
+
* Decide whether a `seek`/`scrubCommit` should actually hit the playback sink.
|
|
7831
|
+
* Each commit reloads the server feeder (loading→playing) and visibly stutters
|
|
7832
|
+
* playback; firing the same (or a near-identical) target twice in quick
|
|
7833
|
+
* succession produces a redundant reload. Drop a commit only when it targets
|
|
7834
|
+
* essentially the SAME epoch as the last commit AND that commit was recent — a
|
|
7835
|
+
* genuinely different target, or a re-seek to the same spot after the window,
|
|
7836
|
+
* always commits.
|
|
7837
|
+
*/
|
|
7838
|
+
function shouldCommit(last, epoch, now, toleranceMs = 750, windowMs = COMMIT_DEDUPE_WINDOW_MS) {
|
|
7839
|
+
if (last === null) return true;
|
|
7840
|
+
const sameTarget = Math.abs(epoch - last.epoch) <= toleranceMs;
|
|
7841
|
+
const recent = now - last.at <= windowMs;
|
|
7842
|
+
return !(sameTarget && recent);
|
|
7843
|
+
}
|
|
7844
|
+
/**
|
|
7845
|
+
* The active cursor position (scrub target while scrubbing, else playhead)
|
|
7846
|
+
* expressed as a 0..1 fraction across the day window.
|
|
7847
|
+
* Returns null when no position is known or the day window is degenerate.
|
|
7848
|
+
*/
|
|
7849
|
+
function cursorFractionFor(state, day) {
|
|
7850
|
+
const epoch = state.scrubbing ? state.scrubEpoch : state.playheadEpoch;
|
|
7851
|
+
if (epoch === null) return null;
|
|
7852
|
+
const span = day.to - day.from;
|
|
7853
|
+
if (span <= 0) return null;
|
|
7854
|
+
return Math.min(1, Math.max(0, (epoch - day.from) / span));
|
|
7855
|
+
}
|
|
7856
|
+
/**
|
|
7857
|
+
* Builds the always-active bridge callbacks from a sink, a dispatch, and the
|
|
7858
|
+
* throttle ref. Pure (no React) so it can be unit-tested directly: the hook
|
|
7859
|
+
* just wraps the result in `useMemo`.
|
|
7860
|
+
*
|
|
7861
|
+
* Every gesture drives the sink immediately — there is no VOD-active gate.
|
|
7862
|
+
*/
|
|
7863
|
+
function makeScrubBridge(sink, dispatch, lastEmitRef, lastCommitRef = { current: null }, isPlayerLive = () => false) {
|
|
7864
|
+
const commit = (epoch, fire) => {
|
|
7865
|
+
const now = Date.now();
|
|
7866
|
+
if (isPlayerLive()) {
|
|
7867
|
+
lastCommitRef.current = {
|
|
7868
|
+
epoch,
|
|
7869
|
+
at: now
|
|
7870
|
+
};
|
|
7871
|
+
fire(epoch);
|
|
7872
|
+
return;
|
|
7873
|
+
}
|
|
7874
|
+
if (!shouldCommit(lastCommitRef.current, epoch, now)) return;
|
|
7875
|
+
lastCommitRef.current = {
|
|
7876
|
+
epoch,
|
|
7877
|
+
at: now
|
|
7878
|
+
};
|
|
7879
|
+
fire(epoch);
|
|
7880
|
+
};
|
|
7881
|
+
return {
|
|
7882
|
+
beginScrub: (source) => {
|
|
7883
|
+
dispatch({
|
|
7884
|
+
type: "begin",
|
|
7885
|
+
source
|
|
7886
|
+
});
|
|
7887
|
+
sink.scrubStart();
|
|
7888
|
+
},
|
|
7889
|
+
updateScrub: (epoch, source) => {
|
|
7890
|
+
dispatch({
|
|
7891
|
+
type: "update",
|
|
7892
|
+
epoch,
|
|
7893
|
+
source
|
|
7894
|
+
});
|
|
7895
|
+
const now = Date.now();
|
|
7896
|
+
if (!shouldEmit(lastEmitRef.current, now)) return;
|
|
7897
|
+
lastEmitRef.current = now;
|
|
7898
|
+
sink.scrubTo(epoch);
|
|
7899
|
+
},
|
|
7900
|
+
endScrub: (epoch, source) => {
|
|
7901
|
+
dispatch({
|
|
7902
|
+
type: "end",
|
|
7903
|
+
epoch,
|
|
7904
|
+
source
|
|
7905
|
+
});
|
|
7906
|
+
commit(epoch, sink.scrubCommit);
|
|
7907
|
+
},
|
|
7908
|
+
seek: (epoch, source) => {
|
|
7909
|
+
dispatch({
|
|
7910
|
+
type: "seek",
|
|
7911
|
+
epoch,
|
|
7912
|
+
source
|
|
7913
|
+
});
|
|
7914
|
+
commit(epoch, sink.seek);
|
|
7915
|
+
},
|
|
7916
|
+
setPlayhead: (epoch) => dispatch({
|
|
7917
|
+
type: "playhead",
|
|
7918
|
+
epoch
|
|
7919
|
+
}),
|
|
7920
|
+
reset: () => dispatch({ type: "reset" })
|
|
7921
|
+
};
|
|
7922
|
+
}
|
|
7923
|
+
/**
|
|
7924
|
+
* Owns the shared scrub position and bridges it to a playback control sink.
|
|
7925
|
+
*
|
|
7926
|
+
* The bridge is always active: any gesture (drag or tap) drives the sink
|
|
7927
|
+
* immediately. There is no VOD-active gate — the timeline always drives
|
|
7928
|
+
* playback while frame-push mode is on.
|
|
7929
|
+
*
|
|
7930
|
+
* @param sink Always-active playback control surface (e.g. the datachannel)
|
|
7931
|
+
* @param isPlayerLive Synchronous getter for "is the player on the live
|
|
7932
|
+
* edge?" — lets a live→recorded commit bypass dedupe so the seek always
|
|
7933
|
+
* reaches the server.
|
|
7934
|
+
*/
|
|
7935
|
+
function useScrubController(sink, isPlayerLive = () => false) {
|
|
7936
|
+
const [state, dispatch] = (0, react$1.useReducer)(scrubReducer, initialScrubState);
|
|
7937
|
+
const lastEmitRef = (0, react$1.useRef)(0);
|
|
7938
|
+
const lastCommitRef = (0, react$1.useRef)(null);
|
|
7939
|
+
const isPlayerLiveRef = (0, react$1.useRef)(isPlayerLive);
|
|
7940
|
+
isPlayerLiveRef.current = isPlayerLive;
|
|
7941
|
+
return {
|
|
7942
|
+
state,
|
|
7943
|
+
...(0, react$1.useMemo)(() => makeScrubBridge(sink, dispatch, lastEmitRef, lastCommitRef, () => isPlayerLiveRef.current()), [sink])
|
|
7944
|
+
};
|
|
7945
|
+
}
|
|
7946
|
+
//#endregion
|
|
7713
7947
|
//#region node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.js
|
|
7714
7948
|
/**
|
|
7715
7949
|
* @license lucide-react v0.576.0 - ISC
|
|
@@ -17765,10 +17999,22 @@ var useNodesGetNodeAddons = trpc.nodes.getNodeAddons.useQuery;
|
|
|
17765
17999
|
var useNodesSetProcessLogLevel = trpc.nodes.setProcessLogLevel.useMutation;
|
|
17766
18000
|
/** Generated alias around `trpc.nodes.executeQuery.useMutation`. */
|
|
17767
18001
|
var useNodesExecuteQuery = trpc.nodes.executeQuery.useMutation;
|
|
18002
|
+
/** Generated alias around `trpc.notificationOutput.listTargetKinds.useQuery`. */
|
|
18003
|
+
var useNotificationOutputListTargetKinds = trpc.notificationOutput.listTargetKinds.useQuery;
|
|
18004
|
+
/** Generated alias around `trpc.notificationOutput.listTargets.useQuery`. */
|
|
18005
|
+
var useNotificationOutputListTargets = trpc.notificationOutput.listTargets.useQuery;
|
|
18006
|
+
/** Generated alias around `trpc.notificationOutput.discoverTargets.useQuery`. */
|
|
18007
|
+
var useNotificationOutputDiscoverTargets = trpc.notificationOutput.discoverTargets.useQuery;
|
|
17768
18008
|
/** Generated alias around `trpc.notificationOutput.send.useMutation`. */
|
|
17769
18009
|
var useNotificationOutputSend = trpc.notificationOutput.send.useMutation;
|
|
17770
|
-
/** Generated alias around `trpc.notificationOutput.
|
|
17771
|
-
var
|
|
18010
|
+
/** Generated alias around `trpc.notificationOutput.testTarget.useMutation`. */
|
|
18011
|
+
var useNotificationOutputTestTarget = trpc.notificationOutput.testTarget.useMutation;
|
|
18012
|
+
/** Generated alias around `trpc.notificationOutput.upsertTarget.useMutation`. */
|
|
18013
|
+
var useNotificationOutputUpsertTarget = trpc.notificationOutput.upsertTarget.useMutation;
|
|
18014
|
+
/** Generated alias around `trpc.notificationOutput.deleteTarget.useMutation`. */
|
|
18015
|
+
var useNotificationOutputDeleteTarget = trpc.notificationOutput.deleteTarget.useMutation;
|
|
18016
|
+
/** Generated alias around `trpc.notificationOutput.setTargetEnabled.useMutation`. */
|
|
18017
|
+
var useNotificationOutputSetTargetEnabled = trpc.notificationOutput.setTargetEnabled.useMutation;
|
|
17772
18018
|
/** Generated alias around `trpc.notifier.send.useMutation`. */
|
|
17773
18019
|
var useNotifierSend = trpc.notifier.send.useMutation;
|
|
17774
18020
|
/** Generated alias around `trpc.notifier.cancel.useMutation`. */
|
|
@@ -32536,6 +32782,100 @@ function useVodPlayback() {
|
|
|
32536
32782
|
return (0, react$1.useContext)(VodPlaybackContext);
|
|
32537
32783
|
}
|
|
32538
32784
|
//#endregion
|
|
32785
|
+
//#region src/contexts/recorded-playback.tsx
|
|
32786
|
+
/**
|
|
32787
|
+
* RecordedPlayback — shared context wiring the hero player's
|
|
32788
|
+
* `camstack-control` RTCDataChannel to the Recording timeline (R9).
|
|
32789
|
+
*
|
|
32790
|
+
* The hero `StreamPanel` (host side) BINDS the channel that
|
|
32791
|
+
* `CameraStreamPlayer` opens on the live WebRTC session; the Recording
|
|
32792
|
+
* widget's `RecordingTimeline` (controller side) SENDS frame-push playback
|
|
32793
|
+
* commands over it (seek / scrubCommit / setRate / goLive — the broker's
|
|
32794
|
+
* `TimelineSession` drives the `RecordedFeeder` on the SAME session, so
|
|
32795
|
+
* recorded frames arrive on the same `<video>` element) and OBSERVES the
|
|
32796
|
+
* server's playback state + ~1 Hz position reports.
|
|
32797
|
+
*
|
|
32798
|
+
* This is the admin-ui port of the shipped viewer's default recorded path
|
|
32799
|
+
* (srcV2 → embed → `camstack-control`), replacing "manifest-window HLS only".
|
|
32800
|
+
* The HLS VOD path (VodPlayback context) remains available as a fallback.
|
|
32801
|
+
*
|
|
32802
|
+
* Built on `createSharedContext` (NOT a plain `createContext`) so it crosses
|
|
32803
|
+
* the Module-Federation boundary — same mechanism as `vod-playback`.
|
|
32804
|
+
* Consumers outside a provider get the inert default: `bindControlChannel`
|
|
32805
|
+
* is null (StreamPanel then opens NO datachannel — plain live player) and
|
|
32806
|
+
* `channelOpen` is false (RecordingTimeline stays on the HLS path).
|
|
32807
|
+
*/
|
|
32808
|
+
var RecordedPlaybackContext = createSharedContext("camstack:recorded-playback", {
|
|
32809
|
+
bindControlChannel: null,
|
|
32810
|
+
channelOpen: false,
|
|
32811
|
+
mode: "live",
|
|
32812
|
+
positionMs: null,
|
|
32813
|
+
send: () => false
|
|
32814
|
+
});
|
|
32815
|
+
function RecordedPlaybackProvider({ children }) {
|
|
32816
|
+
const channelRef = (0, react$1.useRef)(null);
|
|
32817
|
+
const [channelOpen, setChannelOpen] = (0, react$1.useState)(false);
|
|
32818
|
+
const [mode, setMode] = (0, react$1.useState)("live");
|
|
32819
|
+
const [positionMs, setPositionMs] = (0, react$1.useState)(null);
|
|
32820
|
+
const bindControlChannel = (0, react$1.useCallback)((channel) => {
|
|
32821
|
+
channelRef.current = channel;
|
|
32822
|
+
const markClosed = () => {
|
|
32823
|
+
if (channelRef.current !== channel) return;
|
|
32824
|
+
channelRef.current = null;
|
|
32825
|
+
setChannelOpen(false);
|
|
32826
|
+
setMode("live");
|
|
32827
|
+
setPositionMs(null);
|
|
32828
|
+
};
|
|
32829
|
+
channel.onopen = () => {
|
|
32830
|
+
if (channelRef.current === channel) setChannelOpen(true);
|
|
32831
|
+
};
|
|
32832
|
+
channel.onclose = markClosed;
|
|
32833
|
+
channel.onerror = markClosed;
|
|
32834
|
+
channel.onmessage = (e) => {
|
|
32835
|
+
if (channelRef.current !== channel) return;
|
|
32836
|
+
const data = e.data;
|
|
32837
|
+
if (typeof data !== "string") return;
|
|
32838
|
+
const msg = parseRecordedServerMessage(data);
|
|
32839
|
+
if (!msg) return;
|
|
32840
|
+
if (msg.kind === "position") setPositionMs(msg.epochMs);
|
|
32841
|
+
else {
|
|
32842
|
+
setMode(msg.state);
|
|
32843
|
+
if (msg.state === "live") setPositionMs(null);
|
|
32844
|
+
}
|
|
32845
|
+
};
|
|
32846
|
+
if (channel.readyState === "open") setChannelOpen(true);
|
|
32847
|
+
else if (channel.readyState === "closed" || channel.readyState === "closing") markClosed();
|
|
32848
|
+
else setChannelOpen(false);
|
|
32849
|
+
}, []);
|
|
32850
|
+
const send = (0, react$1.useCallback)((cmd) => {
|
|
32851
|
+
const channel = channelRef.current;
|
|
32852
|
+
if (!channel || channel.readyState !== "open") return false;
|
|
32853
|
+
channel.send(serializeRecordedCommand(cmd));
|
|
32854
|
+
return true;
|
|
32855
|
+
}, []);
|
|
32856
|
+
const value = (0, react$1.useMemo)(() => ({
|
|
32857
|
+
bindControlChannel,
|
|
32858
|
+
channelOpen,
|
|
32859
|
+
mode,
|
|
32860
|
+
positionMs,
|
|
32861
|
+
send
|
|
32862
|
+
}), [
|
|
32863
|
+
bindControlChannel,
|
|
32864
|
+
channelOpen,
|
|
32865
|
+
mode,
|
|
32866
|
+
positionMs,
|
|
32867
|
+
send
|
|
32868
|
+
]);
|
|
32869
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RecordedPlaybackContext.Provider, {
|
|
32870
|
+
value,
|
|
32871
|
+
children
|
|
32872
|
+
});
|
|
32873
|
+
}
|
|
32874
|
+
/** @returns the recorded-playback control surface (inert outside a provider). */
|
|
32875
|
+
function useRecordedPlayback() {
|
|
32876
|
+
return (0, react$1.useContext)(RecordedPlaybackContext);
|
|
32877
|
+
}
|
|
32878
|
+
//#endregion
|
|
32539
32879
|
//#region src/composites/cap-settings/recording-spans.ts
|
|
32540
32880
|
/**
|
|
32541
32881
|
* Merge availability ranges (across every profile) into the union of distinct
|
|
@@ -32781,8 +33121,12 @@ function axisTicks(fromMs, toMs, count) {
|
|
|
32781
33121
|
}
|
|
32782
33122
|
//#endregion
|
|
32783
33123
|
//#region src/composites/cap-settings/CoverageTrack.tsx
|
|
32784
|
-
function CoverageTrack({ windowFrom, windowTo, spans, playheadMs, onSeek }) {
|
|
33124
|
+
function CoverageTrack({ windowFrom, windowTo, spans, playheadMs, onSeek, onScrubStart, onScrubMove, onScrubEnd }) {
|
|
32785
33125
|
const [hoverInfo, setHoverInfo] = (0, react$1.useState)(null);
|
|
33126
|
+
const scrubEnabled = Boolean(onScrubEnd);
|
|
33127
|
+
const [dragging, setDragging] = (0, react$1.useState)(false);
|
|
33128
|
+
/** Last epoch reported during the drag — commit target on cancel/release. */
|
|
33129
|
+
const lastDragMsRef = (0, react$1.useRef)(null);
|
|
32786
33130
|
const msFromEvent = (e) => {
|
|
32787
33131
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
32788
33132
|
if (rect.width <= 0) return null;
|
|
@@ -32790,10 +33134,43 @@ function CoverageTrack({ windowFrom, windowTo, spans, playheadMs, onSeek }) {
|
|
|
32790
33134
|
return windowFrom + Math.min(1, Math.max(0, ratio)) * (windowTo - windowFrom);
|
|
32791
33135
|
};
|
|
32792
33136
|
const handleClick = (e) => {
|
|
33137
|
+
if (scrubEnabled) return;
|
|
32793
33138
|
const ms = msFromEvent(e);
|
|
32794
33139
|
if (ms === null) return;
|
|
32795
33140
|
onSeek(ms);
|
|
32796
33141
|
};
|
|
33142
|
+
const handlePointerDown = (e) => {
|
|
33143
|
+
if (!scrubEnabled) return;
|
|
33144
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
33145
|
+
setDragging(true);
|
|
33146
|
+
onScrubStart?.();
|
|
33147
|
+
const ms = msFromEvent(e);
|
|
33148
|
+
if (ms !== null) {
|
|
33149
|
+
lastDragMsRef.current = ms;
|
|
33150
|
+
onScrubMove?.(ms);
|
|
33151
|
+
}
|
|
33152
|
+
};
|
|
33153
|
+
const handlePointerMove = (e) => {
|
|
33154
|
+
if (!scrubEnabled || !dragging) return;
|
|
33155
|
+
const ms = msFromEvent(e);
|
|
33156
|
+
if (ms === null) return;
|
|
33157
|
+
lastDragMsRef.current = ms;
|
|
33158
|
+
onScrubMove?.(ms);
|
|
33159
|
+
};
|
|
33160
|
+
const handlePointerUp = (e) => {
|
|
33161
|
+
if (!scrubEnabled || !dragging) return;
|
|
33162
|
+
setDragging(false);
|
|
33163
|
+
const ms = msFromEvent(e) ?? lastDragMsRef.current;
|
|
33164
|
+
lastDragMsRef.current = null;
|
|
33165
|
+
if (ms !== null) onScrubEnd?.(ms);
|
|
33166
|
+
};
|
|
33167
|
+
const handlePointerCancel = () => {
|
|
33168
|
+
if (!dragging) return;
|
|
33169
|
+
setDragging(false);
|
|
33170
|
+
const ms = lastDragMsRef.current;
|
|
33171
|
+
lastDragMsRef.current = null;
|
|
33172
|
+
if (ms !== null) onScrubEnd?.(ms);
|
|
33173
|
+
};
|
|
32797
33174
|
const handleMouseMove = (e) => {
|
|
32798
33175
|
const ms = msFromEvent(e);
|
|
32799
33176
|
if (ms === null) return;
|
|
@@ -32817,10 +33194,15 @@ function CoverageTrack({ windowFrom, windowTo, spans, playheadMs, onSeek }) {
|
|
|
32817
33194
|
className: "relative",
|
|
32818
33195
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
32819
33196
|
"data-testid": "coverage-track",
|
|
33197
|
+
"data-scrubbing": dragging ? "true" : "false",
|
|
32820
33198
|
onClick: handleClick,
|
|
33199
|
+
onPointerDown: handlePointerDown,
|
|
33200
|
+
onPointerMove: handlePointerMove,
|
|
33201
|
+
onPointerUp: handlePointerUp,
|
|
33202
|
+
onPointerCancel: handlePointerCancel,
|
|
32821
33203
|
onMouseMove: handleMouseMove,
|
|
32822
33204
|
onMouseLeave: handleMouseLeave,
|
|
32823
|
-
className:
|
|
33205
|
+
className: `relative h-5 overflow-hidden rounded bg-background/40 ${scrubEnabled ? "cursor-ew-resize touch-none select-none" : "cursor-pointer"}`,
|
|
32824
33206
|
role: "group",
|
|
32825
33207
|
"aria-label": "Recorded coverage timeline",
|
|
32826
33208
|
children: [spans.map((s) => {
|
|
@@ -33119,6 +33501,15 @@ var PAN_MS_PER_PX = 18e3 / 100;
|
|
|
33119
33501
|
var EVENT_PRE_MS = 5e3;
|
|
33120
33502
|
var EVENT_POST_MS = 1e4;
|
|
33121
33503
|
var EMPTY_RANGES = [];
|
|
33504
|
+
var MODE_LABELS = {
|
|
33505
|
+
live: "Live",
|
|
33506
|
+
loading: "Loading…",
|
|
33507
|
+
playing: "Playing",
|
|
33508
|
+
paused: "Paused",
|
|
33509
|
+
gap: "No footage",
|
|
33510
|
+
ended: "Ended",
|
|
33511
|
+
error: "Error"
|
|
33512
|
+
};
|
|
33122
33513
|
function RecordingTimeline({ deviceId }) {
|
|
33123
33514
|
const system = useSystem();
|
|
33124
33515
|
const vod = useVodPlayback();
|
|
@@ -33126,6 +33517,51 @@ function RecordingTimeline({ deviceId }) {
|
|
|
33126
33517
|
const [playheadMs, setPlayheadMs] = (0, react$1.useState)(null);
|
|
33127
33518
|
const [busyKey, setBusyKey] = (0, react$1.useState)(null);
|
|
33128
33519
|
const [playbackError, setPlaybackError] = (0, react$1.useState)(null);
|
|
33520
|
+
const recorded = useRecordedPlayback();
|
|
33521
|
+
const { send: sendControl, mode: recordedMode, positionMs: recordedPositionMs } = recorded;
|
|
33522
|
+
const [forceHls, setForceHls] = (0, react$1.useState)(false);
|
|
33523
|
+
const framePush = recorded.channelOpen && !forceHls;
|
|
33524
|
+
const framePushRef = (0, react$1.useRef)(framePush);
|
|
33525
|
+
framePushRef.current = framePush;
|
|
33526
|
+
const sink = (0, react$1.useMemo)(() => ({
|
|
33527
|
+
scrubStart: () => {},
|
|
33528
|
+
scrubTo: () => {},
|
|
33529
|
+
scrubCommit: (epochMs) => {
|
|
33530
|
+
sendControl({
|
|
33531
|
+
t: "scrubCommit",
|
|
33532
|
+
epoch: epochMs
|
|
33533
|
+
});
|
|
33534
|
+
},
|
|
33535
|
+
seek: (epochMs) => {
|
|
33536
|
+
sendControl({
|
|
33537
|
+
t: "seek",
|
|
33538
|
+
epoch: epochMs
|
|
33539
|
+
});
|
|
33540
|
+
}
|
|
33541
|
+
}), [sendControl]);
|
|
33542
|
+
const recordedModeRef = (0, react$1.useRef)(recordedMode);
|
|
33543
|
+
recordedModeRef.current = recordedMode;
|
|
33544
|
+
const { state: scrubState, beginScrub, updateScrub, endScrub, seek: scrubSeek, setPlayhead, reset: resetScrub } = useScrubController(sink, (0, react$1.useCallback)(() => recordedModeRef.current === "live", []));
|
|
33545
|
+
(0, react$1.useEffect)(() => {
|
|
33546
|
+
if (recordedPositionMs !== null) setPlayhead(recordedPositionMs);
|
|
33547
|
+
}, [recordedPositionMs, setPlayhead]);
|
|
33548
|
+
(0, react$1.useEffect)(() => {
|
|
33549
|
+
if (!recorded.channelOpen || recordedMode === "live") resetScrub();
|
|
33550
|
+
}, [
|
|
33551
|
+
recorded.channelOpen,
|
|
33552
|
+
recordedMode,
|
|
33553
|
+
resetScrub
|
|
33554
|
+
]);
|
|
33555
|
+
const handleGoLive = (0, react$1.useCallback)(() => {
|
|
33556
|
+
sendControl({ t: "goLive" });
|
|
33557
|
+
resetScrub();
|
|
33558
|
+
}, [sendControl, resetScrub]);
|
|
33559
|
+
const handleRateToggle = (0, react$1.useCallback)(() => {
|
|
33560
|
+
sendControl({
|
|
33561
|
+
t: "setRate",
|
|
33562
|
+
rate: recordedModeRef.current === "paused" ? 1 : 0
|
|
33563
|
+
});
|
|
33564
|
+
}, [sendControl]);
|
|
33129
33565
|
const [enabledKinds, setEnabledKinds] = (0, react$1.useState)(() => new Set(ALL_KINDS));
|
|
33130
33566
|
const toggleKind = (0, react$1.useCallback)((kind) => {
|
|
33131
33567
|
setEnabledKinds((current) => {
|
|
@@ -33279,6 +33715,10 @@ function RecordingTimeline({ deviceId }) {
|
|
|
33279
33715
|
const handleSeek = (0, react$1.useCallback)((clickMs) => {
|
|
33280
33716
|
const target = seekTargetForSpans(clickMs, spans);
|
|
33281
33717
|
if (!target) return;
|
|
33718
|
+
if (framePushRef.current) {
|
|
33719
|
+
scrubSeek(target.fromMs, "timeline");
|
|
33720
|
+
return;
|
|
33721
|
+
}
|
|
33282
33722
|
const label = `Recording · ${new Date(target.fromMs).toLocaleString(void 0, {
|
|
33283
33723
|
month: "short",
|
|
33284
33724
|
day: "numeric",
|
|
@@ -33286,14 +33726,31 @@ function RecordingTimeline({ deviceId }) {
|
|
|
33286
33726
|
minute: "2-digit"
|
|
33287
33727
|
})}`;
|
|
33288
33728
|
playWindow(`seek:${target.fromMs}`, target.fromMs, target.toMs, label, target.fromMs);
|
|
33289
|
-
}, [
|
|
33729
|
+
}, [
|
|
33730
|
+
spans,
|
|
33731
|
+
playWindow,
|
|
33732
|
+
scrubSeek
|
|
33733
|
+
]);
|
|
33734
|
+
const handleScrubStart = (0, react$1.useCallback)(() => {
|
|
33735
|
+
beginScrub("timeline");
|
|
33736
|
+
}, [beginScrub]);
|
|
33737
|
+
const handleScrubMove = (0, react$1.useCallback)((ms) => {
|
|
33738
|
+
updateScrub(ms, "timeline");
|
|
33739
|
+
}, [updateScrub]);
|
|
33740
|
+
const handleScrubEnd = (0, react$1.useCallback)((ms) => {
|
|
33741
|
+
endScrub(seekTargetForSpans(ms, spans)?.fromMs ?? ms, "timeline");
|
|
33742
|
+
}, [spans, endScrub]);
|
|
33290
33743
|
const handlePlayClip = (0, react$1.useCallback)((clip) => {
|
|
33744
|
+
if (framePushRef.current) {
|
|
33745
|
+
scrubSeek(clip.timestamp - EVENT_PRE_MS, "timeline");
|
|
33746
|
+
return;
|
|
33747
|
+
}
|
|
33291
33748
|
const label = `${clip.label} · ${new Date(clip.timestamp).toLocaleTimeString(void 0, {
|
|
33292
33749
|
hour: "2-digit",
|
|
33293
33750
|
minute: "2-digit"
|
|
33294
33751
|
})}`;
|
|
33295
33752
|
playWindow(`event:${clip.id}`, clip.timestamp - EVENT_PRE_MS, clip.timestamp + EVENT_POST_MS, label, clip.timestamp);
|
|
33296
|
-
}, [playWindow]);
|
|
33753
|
+
}, [playWindow, scrubSeek]);
|
|
33297
33754
|
const handleSelectBucket = (0, react$1.useCallback)((index) => {
|
|
33298
33755
|
setSelectedBucket((cur) => cur === index ? null : index);
|
|
33299
33756
|
}, []);
|
|
@@ -33301,6 +33758,8 @@ function RecordingTimeline({ deviceId }) {
|
|
|
33301
33758
|
const densityLoading = eventDensityQuery.isLoading;
|
|
33302
33759
|
const drillLoading = (objectDrill.isLoading || motionDrill.isLoading || audioDrill.isLoading) && drillEnabled;
|
|
33303
33760
|
const ticks = (0, react$1.useMemo)(() => axisTicks(view.fromMs, view.toMs, 5), [view.fromMs, view.toMs]);
|
|
33761
|
+
const displayPlayheadMs = framePush ? scrubState.scrubbing ? scrubState.scrubEpoch : scrubState.playheadEpoch : playheadMs;
|
|
33762
|
+
const showRateToggle = framePush && (recordedMode === "playing" || recordedMode === "paused");
|
|
33304
33763
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
33305
33764
|
className: "space-y-3",
|
|
33306
33765
|
children: [
|
|
@@ -33319,6 +33778,45 @@ function RecordingTimeline({ deviceId }) {
|
|
|
33319
33778
|
dayBounds: computedDayBounds,
|
|
33320
33779
|
onViewChange: handleViewChange
|
|
33321
33780
|
}),
|
|
33781
|
+
recorded.channelOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
33782
|
+
"data-testid": "frame-push-status-row",
|
|
33783
|
+
className: "flex flex-wrap items-center gap-2 text-[10px]",
|
|
33784
|
+
children: [
|
|
33785
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
33786
|
+
"data-testid": "frame-push-mode-chip",
|
|
33787
|
+
className: `inline-flex items-center gap-1 rounded px-1.5 py-0.5 font-semibold uppercase tracking-wider ${framePush && recordedMode !== "live" ? recordedMode === "error" ? "bg-rose-500/15 text-rose-400" : "bg-primary/15 text-primary" : "bg-background/40 text-foreground-subtle"}`,
|
|
33788
|
+
children: framePush ? MODE_LABELS[recordedMode] : "HLS mode"
|
|
33789
|
+
}),
|
|
33790
|
+
framePush && recordedPositionMs !== null && !scrubState.scrubbing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
33791
|
+
className: "tabular-nums text-foreground-subtle",
|
|
33792
|
+
children: new Date(recordedPositionMs).toLocaleTimeString()
|
|
33793
|
+
}) : null,
|
|
33794
|
+
showRateToggle ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
33795
|
+
type: "button",
|
|
33796
|
+
"data-testid": "frame-push-rate-toggle",
|
|
33797
|
+
onClick: handleRateToggle,
|
|
33798
|
+
className: "inline-flex items-center gap-1 rounded px-1.5 py-0.5 font-medium text-foreground-subtle transition-colors hover:bg-surface-hover hover:text-foreground",
|
|
33799
|
+
title: recordedMode === "paused" ? "Resume playback" : "Pause playback",
|
|
33800
|
+
children: [recordedMode === "paused" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-3 w-3" }), recordedMode === "paused" ? "Resume" : "Pause"]
|
|
33801
|
+
}) : null,
|
|
33802
|
+
framePush && recordedMode !== "live" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
33803
|
+
type: "button",
|
|
33804
|
+
"data-testid": "frame-push-go-live",
|
|
33805
|
+
onClick: handleGoLive,
|
|
33806
|
+
className: "inline-flex items-center gap-1 rounded bg-danger/10 px-1.5 py-0.5 font-medium text-danger transition-colors hover:bg-danger/20",
|
|
33807
|
+
title: "Return to the live stream",
|
|
33808
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Radio, { className: "h-3 w-3" }), "Go live"]
|
|
33809
|
+
}) : null,
|
|
33810
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
33811
|
+
type: "button",
|
|
33812
|
+
"data-testid": "frame-push-hls-toggle",
|
|
33813
|
+
onClick: () => setForceHls((v) => !v),
|
|
33814
|
+
className: `ml-auto inline-flex items-center gap-1 rounded px-1.5 py-0.5 font-medium transition-colors ${forceHls ? "bg-primary/15 text-primary" : "text-foreground-subtle hover:bg-surface-hover hover:text-foreground"}`,
|
|
33815
|
+
title: "Play recordings through the HLS player instead of the live WebRTC session",
|
|
33816
|
+
children: "HLS"
|
|
33817
|
+
})
|
|
33818
|
+
]
|
|
33819
|
+
}) : null,
|
|
33322
33820
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
33323
33821
|
ref: trackAreaRef,
|
|
33324
33822
|
onWheel: handleWheel,
|
|
@@ -33336,8 +33834,13 @@ function RecordingTimeline({ deviceId }) {
|
|
|
33336
33834
|
windowFrom: view.fromMs,
|
|
33337
33835
|
windowTo: view.toMs,
|
|
33338
33836
|
spans,
|
|
33339
|
-
playheadMs,
|
|
33340
|
-
onSeek: handleSeek
|
|
33837
|
+
playheadMs: displayPlayheadMs,
|
|
33838
|
+
onSeek: handleSeek,
|
|
33839
|
+
...framePush ? {
|
|
33840
|
+
onScrubStart: handleScrubStart,
|
|
33841
|
+
onScrubMove: handleScrubMove,
|
|
33842
|
+
onScrubEnd: handleScrubEnd
|
|
33843
|
+
} : {}
|
|
33341
33844
|
})]
|
|
33342
33845
|
}),
|
|
33343
33846
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -33374,7 +33877,7 @@ function RecordingTimeline({ deviceId }) {
|
|
|
33374
33877
|
onLoadMore: handleLoadMore
|
|
33375
33878
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
33376
33879
|
className: "text-[11px] text-foreground-subtle",
|
|
33377
|
-
children: "Click the coverage bar to play from any point, or a density bar to browse that window
|
|
33880
|
+
children: framePush ? "Click or drag the coverage bar to play from any point (frames play in the main player above), or click a density bar to browse that window’s events." : "Click the coverage bar to play from any point, or a density bar to browse that window’s events."
|
|
33378
33881
|
})
|
|
33379
33882
|
]
|
|
33380
33883
|
});
|
|
@@ -38662,6 +39165,7 @@ function ImageOffIcon({ className }) {
|
|
|
38662
39165
|
}
|
|
38663
39166
|
function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, playbackUrl, playbackAuthToken, onExitPlayback, posterUrl, streams, activeStreamId: controlledStreamId, onStreamChange, deviceName, phase, pipelineMetrics, detections, 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 }) {
|
|
38664
39167
|
const registeredButtons = usePlayerToolbarButtons();
|
|
39168
|
+
const recordedPlayback = useRecordedPlayback();
|
|
38665
39169
|
const [isPlaying, setIsPlaying] = (0, react$1.useState)(autoPlay);
|
|
38666
39170
|
const [showDetections, setShowDetections] = (0, react$1.useState)(defaultShowDetections);
|
|
38667
39171
|
const [showMotion, setShowMotion] = (0, react$1.useState)(defaultShowMotion);
|
|
@@ -38967,6 +39471,7 @@ function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIce
|
|
|
38967
39471
|
addIceCandidate,
|
|
38968
39472
|
getIceCandidates,
|
|
38969
39473
|
closeSession,
|
|
39474
|
+
...recordedPlayback.bindControlChannel ? { onControlChannel: recordedPlayback.bindControlChannel } : {},
|
|
38970
39475
|
...posterUrl ? { posterUrl } : {},
|
|
38971
39476
|
overlay: anyOverlay || extraOverlay || ptzOverlayVisible ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
38972
39477
|
anyOverlay && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetectionOverlay, {
|
|
@@ -43610,6 +44115,8 @@ exports.CHIP_INACTIVE = CHIP_INACTIVE;
|
|
|
43610
44115
|
exports.CLASS_COLORS = CLASS_COLORS;
|
|
43611
44116
|
exports.COLUMN_BREAKPOINT_CLASS = COLUMN_BREAKPOINT_CLASS;
|
|
43612
44117
|
exports.COLUMN_PRIORITY = COLUMN_PRIORITY;
|
|
44118
|
+
exports.COMMIT_DEDUPE_TOLERANCE_MS = COMMIT_DEDUPE_TOLERANCE_MS;
|
|
44119
|
+
exports.COMMIT_DEDUPE_WINDOW_MS = COMMIT_DEDUPE_WINDOW_MS;
|
|
43613
44120
|
exports.CONTROL_CAP_NAMES = CONTROL_CAP_NAMES;
|
|
43614
44121
|
exports.CONTROL_FILLS = CONTROL_FILLS;
|
|
43615
44122
|
exports.CameraStreamPlayer = CameraStreamPlayer;
|
|
@@ -43744,9 +44251,11 @@ exports.PrivacyMaskSettings = PrivacyMaskSettings;
|
|
|
43744
44251
|
exports.ProviderBadge = ProviderBadge;
|
|
43745
44252
|
exports.PtzPanel = PtzPanel;
|
|
43746
44253
|
exports.QrCode = QrCode;
|
|
44254
|
+
exports.RECORDED_PLAYBACK_MODES = RECORDED_PLAYBACK_MODES;
|
|
43747
44255
|
exports.RIGHT = RIGHT;
|
|
43748
44256
|
exports.ROLE_DESCRIPTOR = ROLE_DESCRIPTOR;
|
|
43749
44257
|
exports.RadialGauge = RadialGauge;
|
|
44258
|
+
exports.RecordedPlaybackProvider = RecordedPlaybackProvider;
|
|
43750
44259
|
exports.RecordingPanel = RecordingPanel;
|
|
43751
44260
|
exports.ResponseLog = ResponseLog;
|
|
43752
44261
|
exports.SECTION_BODY = SECTION_BODY;
|
|
@@ -43830,6 +44339,7 @@ exports.coverHighlight = coverHighlight;
|
|
|
43830
44339
|
exports.createLucideIcon = createLucideIcon;
|
|
43831
44340
|
exports.createSharedContext = createSharedContext;
|
|
43832
44341
|
exports.createTheme = require_theme_index.createTheme$1;
|
|
44342
|
+
exports.cursorFractionFor = cursorFractionFor;
|
|
43833
44343
|
exports.darkColors = require_theme_index.darkColors$1;
|
|
43834
44344
|
exports.defaultTheme = require_theme_index.defaultTheme$1;
|
|
43835
44345
|
exports.deriveDeviceKind = deriveDeviceKind;
|
|
@@ -43847,12 +44357,14 @@ exports.getPhaseVisual = getPhaseVisual;
|
|
|
43847
44357
|
exports.groupChildrenByLayout = groupChildrenByLayout;
|
|
43848
44358
|
exports.hardwareLabel = hardwareLabel;
|
|
43849
44359
|
exports.humidifierTint = humidifierTint;
|
|
44360
|
+
exports.initialScrubState = initialScrubState;
|
|
43850
44361
|
exports.isAbsentProvider = isAbsentProvider;
|
|
43851
44362
|
exports.isAbsentProvider$1 = isAbsentProvider;
|
|
43852
44363
|
exports.isFieldVisible = isFieldVisible;
|
|
43853
44364
|
exports.lawnMowerActivityMeta = lawnMowerActivityMeta;
|
|
43854
44365
|
exports.lightColors = require_theme_index.lightColors$1;
|
|
43855
44366
|
exports.loadRemoteBundle = loadRemoteBundle;
|
|
44367
|
+
exports.makeScrubBridge = makeScrubBridge;
|
|
43856
44368
|
exports.metadataEntries = metadataEntries;
|
|
43857
44369
|
exports.metadataString = metadataString;
|
|
43858
44370
|
exports.mirror = mirror;
|
|
@@ -43860,11 +44372,16 @@ exports.mountAddonPage = mountAddonPage;
|
|
|
43860
44372
|
exports.nextSort = nextSort;
|
|
43861
44373
|
exports.normalizeForSearch = normalizeForSearch;
|
|
43862
44374
|
exports.overrideEntityIdFromLink = overrideEntityIdFromLink;
|
|
44375
|
+
exports.parseRecordedServerMessage = parseRecordedServerMessage;
|
|
43863
44376
|
exports.providerIcons = providerIcons;
|
|
43864
44377
|
exports.resolveContainerPrimary = resolveContainerPrimary;
|
|
43865
44378
|
exports.resolveControlAlign = resolveControlAlign;
|
|
43866
44379
|
exports.resolveDeviceControl = resolveDeviceControl;
|
|
43867
44380
|
exports.resolvePrimaryChild = resolvePrimaryChild;
|
|
44381
|
+
exports.scrubReducer = scrubReducer;
|
|
44382
|
+
exports.serializeRecordedCommand = serializeRecordedCommand;
|
|
44383
|
+
exports.shouldCommit = shouldCommit;
|
|
44384
|
+
exports.shouldEmit = shouldEmit;
|
|
43868
44385
|
exports.sortRows = sortRows;
|
|
43869
44386
|
exports.statusIcons = statusIcons;
|
|
43870
44387
|
exports.tankAlert = tankAlert;
|
|
@@ -44320,8 +44837,14 @@ exports.useNodesSetProcessLogLevel = useNodesSetProcessLogLevel;
|
|
|
44320
44837
|
exports.useNodesShutdownNode = useNodesShutdownNode;
|
|
44321
44838
|
exports.useNodesTopology = useNodesTopology;
|
|
44322
44839
|
exports.useNodesUndeployAddon = useNodesUndeployAddon;
|
|
44840
|
+
exports.useNotificationOutputDeleteTarget = useNotificationOutputDeleteTarget;
|
|
44841
|
+
exports.useNotificationOutputDiscoverTargets = useNotificationOutputDiscoverTargets;
|
|
44842
|
+
exports.useNotificationOutputListTargetKinds = useNotificationOutputListTargetKinds;
|
|
44843
|
+
exports.useNotificationOutputListTargets = useNotificationOutputListTargets;
|
|
44323
44844
|
exports.useNotificationOutputSend = useNotificationOutputSend;
|
|
44324
|
-
exports.
|
|
44845
|
+
exports.useNotificationOutputSetTargetEnabled = useNotificationOutputSetTargetEnabled;
|
|
44846
|
+
exports.useNotificationOutputTestTarget = useNotificationOutputTestTarget;
|
|
44847
|
+
exports.useNotificationOutputUpsertTarget = useNotificationOutputUpsertTarget;
|
|
44325
44848
|
exports.useNotifierCancel = useNotifierCancel;
|
|
44326
44849
|
exports.useNotifierGetStatus = useNotifierGetStatus;
|
|
44327
44850
|
exports.useNotifierSend = useNotifierSend;
|
|
@@ -44464,6 +44987,7 @@ exports.usePtzSavePreset = usePtzSavePreset;
|
|
|
44464
44987
|
exports.usePtzSetAutofocus = usePtzSetAutofocus;
|
|
44465
44988
|
exports.usePtzStop = usePtzStop;
|
|
44466
44989
|
exports.useRebootReboot = useRebootReboot;
|
|
44990
|
+
exports.useRecordedPlayback = useRecordedPlayback;
|
|
44467
44991
|
exports.useRecordingApplyDeviceSettingsPatch = useRecordingApplyDeviceSettingsPatch;
|
|
44468
44992
|
exports.useRecordingGetAvailability = useRecordingGetAvailability;
|
|
44469
44993
|
exports.useRecordingGetDaysWithRecordings = useRecordingGetDaysWithRecordings;
|
|
@@ -44482,6 +45006,7 @@ exports.useRemoteComponent = useRemoteComponent;
|
|
|
44482
45006
|
exports.useScriptRunnerGetStatus = useScriptRunnerGetStatus;
|
|
44483
45007
|
exports.useScriptRunnerRun = useScriptRunnerRun;
|
|
44484
45008
|
exports.useScriptRunnerStop = useScriptRunnerStop;
|
|
45009
|
+
exports.useScrubController = useScrubController;
|
|
44485
45010
|
exports.useSettingsStoreCount = useSettingsStoreCount;
|
|
44486
45011
|
exports.useSettingsStoreDeclareCollection = useSettingsStoreDeclareCollection;
|
|
44487
45012
|
exports.useSettingsStoreDelete = useSettingsStoreDelete;
|