@camstack/ui-library 1.2.12 → 1.2.13
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/RecordingBandsEditor.d.ts +16 -0
- package/dist/composites/cap-settings/RecordingSettings.d.ts +5 -1
- package/dist/composites/cap-settings/index.d.ts +1 -1
- package/dist/composites/cap-settings/recording-config-form.d.ts +36 -20
- package/dist/generated/system-hooks.d.ts +22 -0
- package/dist/hooks/use-device-webrtc.d.ts +0 -7
- package/dist/index.cjs +409 -443
- package/dist/index.js +400 -445
- package/package.json +1 -1
- package/src/tailwind/camstack-theme.css +15 -0
- package/dist/composites/cap-settings/RecordingRulesEditor.d.ts +0 -7
- package/dist/composites/cap-settings/ScheduleBandsEditor.d.ts +0 -7
package/dist/index.cjs
CHANGED
|
@@ -12644,7 +12644,7 @@ var buttonVariants = cva("inline-flex items-center justify-center rounded-md fon
|
|
|
12644
12644
|
secondary: "bg-surface text-foreground hover:bg-surface-hover",
|
|
12645
12645
|
ghost: "hover:bg-surface-hover text-foreground-muted",
|
|
12646
12646
|
danger: "bg-danger text-white hover:bg-danger/90",
|
|
12647
|
-
outline: "border border-border bg-transparent hover:bg-surface-hover"
|
|
12647
|
+
outline: "border border-border bg-transparent text-foreground hover:bg-surface-hover"
|
|
12648
12648
|
},
|
|
12649
12649
|
size: {
|
|
12650
12650
|
sm: "h-7 px-2.5 text-xs gap-1.5",
|
|
@@ -12949,7 +12949,7 @@ function DialogTrigger({ children, ...props }) {
|
|
|
12949
12949
|
* `w-full` (which would overflow/clip a centered dialog); the per-variant
|
|
12950
12950
|
* `max-w-*` keeps small dialogs small while the cap prevents overflow.
|
|
12951
12951
|
*/
|
|
12952
|
-
var contentVariants = cva("fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 m-0 w-[calc(100vw-2rem)] bg-background-elevated border border-border rounded-lg p-4 backdrop:bg-black/50 backdrop:backdrop-blur-sm max-h-[calc(100dvh-2rem)] overflow-y-auto [&:not([open])]:hidden", {
|
|
12952
|
+
var contentVariants = cva("fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 m-0 w-[calc(100vw-2rem)] bg-background-elevated text-foreground border border-border rounded-lg p-4 backdrop:bg-black/50 backdrop:backdrop-blur-sm max-h-[calc(100dvh-2rem)] overflow-y-auto [&:not([open])]:hidden", {
|
|
12953
12953
|
variants: {
|
|
12954
12954
|
width: {
|
|
12955
12955
|
sm: "max-w-sm",
|
|
@@ -18728,6 +18728,10 @@ var useLocalNetworkList = trpc.localNetwork.list.useQuery;
|
|
|
18728
18728
|
var useLocalNetworkGetPreferred = trpc.localNetwork.getPreferred.useQuery;
|
|
18729
18729
|
/** Generated alias around `trpc.localNetwork.getConnectionEndpoints.useQuery`. */
|
|
18730
18730
|
var useLocalNetworkGetConnectionEndpoints = trpc.localNetwork.getConnectionEndpoints.useQuery;
|
|
18731
|
+
/** Generated alias around `trpc.localNetwork.getNotificationEndpoint.useQuery`. */
|
|
18732
|
+
var useLocalNetworkGetNotificationEndpoint = trpc.localNetwork.getNotificationEndpoint.useQuery;
|
|
18733
|
+
/** Generated alias around `trpc.localNetwork.setNotificationEndpoint.useMutation`. */
|
|
18734
|
+
var useLocalNetworkSetNotificationEndpoint = trpc.localNetwork.setNotificationEndpoint.useMutation;
|
|
18731
18735
|
/** Generated alias around `trpc.localNetwork.getAllowedAddresses.useQuery`. */
|
|
18732
18736
|
var useLocalNetworkGetAllowedAddresses = trpc.localNetwork.getAllowedAddresses.useQuery;
|
|
18733
18737
|
/** Generated alias around `trpc.localNetwork.setAllowedAddresses.useMutation`. */
|
|
@@ -19000,6 +19004,12 @@ var usePipelineAnalyticsGetEventStoreFootprint = trpc.pipelineAnalytics.getEvent
|
|
|
19000
19004
|
var usePipelineAnalyticsPruneEvents = trpc.pipelineAnalytics.pruneEvents.useMutation;
|
|
19001
19005
|
/** Generated alias around `trpc.pipelineAnalytics.deleteDeviceEvents.useMutation`. */
|
|
19002
19006
|
var usePipelineAnalyticsDeleteDeviceEvents = trpc.pipelineAnalytics.deleteDeviceEvents.useMutation;
|
|
19007
|
+
/** Generated alias around `trpc.pipelineAnalytics.relocateMedia.useMutation`. */
|
|
19008
|
+
var usePipelineAnalyticsRelocateMedia = trpc.pipelineAnalytics.relocateMedia.useMutation;
|
|
19009
|
+
/** Generated alias around `trpc.pipelineAnalytics.getMediaRelocateStatus.useQuery`. */
|
|
19010
|
+
var usePipelineAnalyticsGetMediaRelocateStatus = trpc.pipelineAnalytics.getMediaRelocateStatus.useQuery;
|
|
19011
|
+
/** Generated alias around `trpc.pipelineAnalytics.cancelMediaRelocate.useMutation`. */
|
|
19012
|
+
var usePipelineAnalyticsCancelMediaRelocate = trpc.pipelineAnalytics.cancelMediaRelocate.useMutation;
|
|
19003
19013
|
/** Generated alias around `trpc.pipelineAnalytics.listOpsLog.useQuery`. */
|
|
19004
19014
|
var usePipelineAnalyticsListOpsLog = trpc.pipelineAnalytics.listOpsLog.useQuery;
|
|
19005
19015
|
/** Generated alias around `trpc.pipelineAnalytics.getEventMedia.useQuery`. */
|
|
@@ -19296,6 +19306,16 @@ var useRecordingPruneFootage = trpc.recording.pruneFootage.useMutation;
|
|
|
19296
19306
|
var useRecordingDeleteFootprint = trpc.recording.deleteFootprint.useMutation;
|
|
19297
19307
|
/** Generated alias around `trpc.recording.listOpsLog.useQuery`. */
|
|
19298
19308
|
var useRecordingListOpsLog = trpc.recording.listOpsLog.useQuery;
|
|
19309
|
+
/** Generated alias around `trpc.recording.renderGif.useMutation`. */
|
|
19310
|
+
var useRecordingRenderGif = trpc.recording.renderGif.useMutation;
|
|
19311
|
+
/** Generated alias around `trpc.recording.renderClip.useMutation`. */
|
|
19312
|
+
var useRecordingRenderClip = trpc.recording.renderClip.useMutation;
|
|
19313
|
+
/** Generated alias around `trpc.recording.relocateFootage.useMutation`. */
|
|
19314
|
+
var useRecordingRelocateFootage = trpc.recording.relocateFootage.useMutation;
|
|
19315
|
+
/** Generated alias around `trpc.recording.getRelocateStatus.useQuery`. */
|
|
19316
|
+
var useRecordingGetRelocateStatus = trpc.recording.getRelocateStatus.useQuery;
|
|
19317
|
+
/** Generated alias around `trpc.recording.cancelRelocate.useMutation`. */
|
|
19318
|
+
var useRecordingCancelRelocate = trpc.recording.cancelRelocate.useMutation;
|
|
19299
19319
|
/** Generated alias around `trpc.recording.getStatus.useQuery`. */
|
|
19300
19320
|
var useRecordingGetStatus = trpc.recording.getStatus.useQuery;
|
|
19301
19321
|
/** Generated alias around `trpc.recording.getDeviceSettingsContribution.useQuery`. */
|
|
@@ -19436,6 +19456,8 @@ var useStreamBrokerRetractCameraStream = trpc.streamBroker.retractCameraStream.u
|
|
|
19436
19456
|
var useStreamBrokerAssignProfile = trpc.streamBroker.assignProfile.useMutation;
|
|
19437
19457
|
/** Generated alias around `trpc.streamBroker.unassignProfile.useMutation`. */
|
|
19438
19458
|
var useStreamBrokerUnassignProfile = trpc.streamBroker.unassignProfile.useMutation;
|
|
19459
|
+
/** Generated alias around `trpc.streamBroker.renderPreBufferClip.useMutation`. */
|
|
19460
|
+
var useStreamBrokerRenderPreBufferClip = trpc.streamBroker.renderPreBufferClip.useMutation;
|
|
19439
19461
|
/** Generated alias around `trpc.streamBroker.listAllCameraStreams.useQuery`. */
|
|
19440
19462
|
var useStreamBrokerListAllCameraStreams = trpc.streamBroker.listAllCameraStreams.useQuery;
|
|
19441
19463
|
/** Generated alias around `trpc.streamBroker.listAllProfileSlots.useQuery`. */
|
|
@@ -34735,315 +34757,108 @@ function AutotrackSection({ deviceId }) {
|
|
|
34735
34757
|
}
|
|
34736
34758
|
//#endregion
|
|
34737
34759
|
//#region src/composites/cap-settings/recording-config-form.ts
|
|
34738
|
-
|
|
34760
|
+
/** Every weekday — the `days` of a band that covers the whole week. */
|
|
34761
|
+
var ALL_WEEKDAYS = [
|
|
34762
|
+
0,
|
|
34763
|
+
1,
|
|
34764
|
+
2,
|
|
34765
|
+
3,
|
|
34766
|
+
4,
|
|
34767
|
+
5,
|
|
34768
|
+
6
|
|
34769
|
+
];
|
|
34770
|
+
/** The full-day span of an always-on band (`HH:MM`, 23:59 = end of day). */
|
|
34771
|
+
var ALL_DAY_START = "00:00";
|
|
34772
|
+
var ALL_DAY_END = "23:59";
|
|
34773
|
+
/** A band covering every day, all day, in `mode` — what the Base tab authors. */
|
|
34774
|
+
function alwaysBand(mode, rest) {
|
|
34739
34775
|
return {
|
|
34740
|
-
|
|
34741
|
-
|
|
34742
|
-
|
|
34743
|
-
|
|
34744
|
-
|
|
34776
|
+
days: [...ALL_WEEKDAYS],
|
|
34777
|
+
start: ALL_DAY_START,
|
|
34778
|
+
end: ALL_DAY_END,
|
|
34779
|
+
mode,
|
|
34780
|
+
...rest
|
|
34745
34781
|
};
|
|
34746
34782
|
}
|
|
34747
|
-
|
|
34748
|
-
|
|
34783
|
+
/** A fresh row for the Advanced band table: every day, office hours, continuous. */
|
|
34784
|
+
function emptyBand() {
|
|
34785
|
+
return {
|
|
34786
|
+
days: [...ALL_WEEKDAYS],
|
|
34787
|
+
start: "09:00",
|
|
34788
|
+
end: "18:00",
|
|
34789
|
+
mode: "continuous"
|
|
34790
|
+
};
|
|
34749
34791
|
}
|
|
34750
|
-
function
|
|
34751
|
-
return
|
|
34752
|
-
...
|
|
34792
|
+
function withBandAt(bands, index, patch) {
|
|
34793
|
+
return bands.map((band, i) => i === index ? {
|
|
34794
|
+
...band,
|
|
34753
34795
|
...patch
|
|
34754
|
-
} :
|
|
34796
|
+
} : band);
|
|
34797
|
+
}
|
|
34798
|
+
function removeBandAt(bands, index) {
|
|
34799
|
+
return bands.filter((_, i) => i !== index);
|
|
34755
34800
|
}
|
|
34756
|
-
|
|
34757
|
-
|
|
34801
|
+
/** Does this band cover the whole week, all day? (the Base-tab shape) */
|
|
34802
|
+
function isAlwaysBand(band) {
|
|
34803
|
+
return (band.days.length === 0 || ALL_WEEKDAYS.every((d) => band.days.includes(d))) && band.start === "00:00" && band.end === "23:59";
|
|
34758
34804
|
}
|
|
34759
|
-
/**
|
|
34760
|
-
*
|
|
34761
|
-
|
|
34762
|
-
|
|
34763
|
-
|
|
34764
|
-
|
|
34805
|
+
/**
|
|
34806
|
+
* A config is "advanced" when its bands cannot be expressed by the Base tab —
|
|
34807
|
+
* i.e. more than one band, or a single band restricted in time or weekday. Such
|
|
34808
|
+
* a config opens directly in the Advanced band table so nothing is silently
|
|
34809
|
+
* flattened by the simpler form.
|
|
34810
|
+
*/
|
|
34811
|
+
function isAdvancedConfig(cfg) {
|
|
34812
|
+
if (cfg.bands.length > 1) return true;
|
|
34813
|
+
const [only] = cfg.bands;
|
|
34814
|
+
return only !== void 0 && !isAlwaysBand(only);
|
|
34765
34815
|
}
|
|
34766
|
-
/**
|
|
34767
|
-
*
|
|
34816
|
+
/**
|
|
34817
|
+
* Read a persisted config into the Basic form state. `mode` is DERIVED from the
|
|
34818
|
+
* bands by the same function the recorder stamps `config.mode` with; the
|
|
34819
|
+
* `events` parameters come from the first events band, so re-opening the form
|
|
34820
|
+
* shows what is actually recorded.
|
|
34821
|
+
*/
|
|
34768
34822
|
function formStateFromConfig(cfg) {
|
|
34823
|
+
const eventsBand = cfg.bands.find((band) => band.mode === "events");
|
|
34769
34824
|
return {
|
|
34770
|
-
mode:
|
|
34825
|
+
mode: (0, _camstack_types.deriveRecordingMode)(cfg),
|
|
34771
34826
|
profiles: cfg.profiles,
|
|
34772
34827
|
segmentSeconds: cfg.segmentSeconds,
|
|
34773
|
-
|
|
34774
|
-
|
|
34775
|
-
|
|
34776
|
-
postBufferSec: cfg.postBufferSec,
|
|
34828
|
+
triggers: eventsBand?.triggers ?? {},
|
|
34829
|
+
preBufferSec: eventsBand?.preBufferSec,
|
|
34830
|
+
postBufferSec: eventsBand?.postBufferSec,
|
|
34777
34831
|
retention: cfg.retention,
|
|
34778
34832
|
scrubThumbnails: cfg.scrubThumbnails
|
|
34779
34833
|
};
|
|
34780
34834
|
}
|
|
34781
|
-
/**
|
|
34782
|
-
*
|
|
34783
|
-
*
|
|
34835
|
+
/**
|
|
34836
|
+
* Serialize the Basic form state to the wire config: one always-on band of the
|
|
34837
|
+
* chosen mode, none when `off`. `enabled` follows the mode; `mode` itself is
|
|
34838
|
+
* NOT sent — the recorder stamps it as the derived summary of the bands.
|
|
34839
|
+
*/
|
|
34784
34840
|
function configFromFormState(state) {
|
|
34785
34841
|
return {
|
|
34786
34842
|
enabled: state.mode !== "off",
|
|
34787
|
-
|
|
34843
|
+
bands: bandsFromFormState(state),
|
|
34788
34844
|
profiles: state.profiles ? [...state.profiles] : void 0,
|
|
34789
34845
|
segmentSeconds: state.segmentSeconds,
|
|
34790
|
-
schedules: state.schedules.length > 0 ? [...state.schedules] : void 0,
|
|
34791
|
-
triggers: Object.keys(state.triggers).length > 0 ? state.triggers : void 0,
|
|
34792
|
-
preBufferSec: state.preBufferSec,
|
|
34793
|
-
postBufferSec: state.postBufferSec,
|
|
34794
34846
|
retention: state.retention,
|
|
34795
34847
|
scrubThumbnails: state.scrubThumbnails
|
|
34796
34848
|
};
|
|
34797
34849
|
}
|
|
34798
|
-
/**
|
|
34799
|
-
|
|
34800
|
-
|
|
34801
|
-
|
|
34802
|
-
|
|
34803
|
-
|
|
34804
|
-
|
|
34805
|
-
}
|
|
34806
|
-
|
|
34807
|
-
//#region src/composites/cap-settings/RecordingRulesEditor.tsx
|
|
34808
|
-
var DAYS = [
|
|
34809
|
-
{
|
|
34810
|
-
d: 0,
|
|
34811
|
-
label: "S"
|
|
34812
|
-
},
|
|
34813
|
-
{
|
|
34814
|
-
d: 1,
|
|
34815
|
-
label: "M"
|
|
34816
|
-
},
|
|
34817
|
-
{
|
|
34818
|
-
d: 2,
|
|
34819
|
-
label: "T"
|
|
34820
|
-
},
|
|
34821
|
-
{
|
|
34822
|
-
d: 3,
|
|
34823
|
-
label: "W"
|
|
34824
|
-
},
|
|
34825
|
-
{
|
|
34826
|
-
d: 4,
|
|
34827
|
-
label: "T"
|
|
34828
|
-
},
|
|
34829
|
-
{
|
|
34830
|
-
d: 5,
|
|
34831
|
-
label: "F"
|
|
34832
|
-
},
|
|
34833
|
-
{
|
|
34834
|
-
d: 6,
|
|
34835
|
-
label: "S"
|
|
34836
|
-
}
|
|
34837
|
-
];
|
|
34838
|
-
/** Converts a select string value to RecordingMode without a cast. */
|
|
34839
|
-
function toMode(v) {
|
|
34840
|
-
return v === "onMotion" || v === "onAudioThreshold" ? v : "continuous";
|
|
34841
|
-
}
|
|
34842
|
-
/**
|
|
34843
|
-
* The rule patch to apply when the mode changes, normalising the fields that
|
|
34844
|
-
* only apply to certain modes so a saved rule never carries stale values:
|
|
34845
|
-
* `continuous` drops the pre/post buffers + threshold; trigger modes drop the
|
|
34846
|
-
* threshold unless it's `onAudioThreshold`, which is seeded to -30 dBFS when
|
|
34847
|
-
* unset so the displayed default is the value actually persisted.
|
|
34848
|
-
*/
|
|
34849
|
-
function modePatch(rule, mode) {
|
|
34850
|
-
if (mode === "onAudioThreshold") return {
|
|
34851
|
-
mode,
|
|
34852
|
-
thresholdDbfs: rule.thresholdDbfs ?? -30
|
|
34853
|
-
};
|
|
34854
|
-
if (mode === "onMotion") return {
|
|
34855
|
-
mode,
|
|
34856
|
-
thresholdDbfs: void 0
|
|
34857
|
-
};
|
|
34858
|
-
return {
|
|
34859
|
-
mode,
|
|
34860
|
-
preBufferSec: 0,
|
|
34861
|
-
postBufferSec: 0,
|
|
34862
|
-
thresholdDbfs: void 0
|
|
34863
|
-
};
|
|
34864
|
-
}
|
|
34865
|
-
function TimeOfDayRow({ schedule, onChange }) {
|
|
34866
|
-
const toggleDay = (d) => {
|
|
34867
|
-
const cur = schedule.days ?? [
|
|
34868
|
-
0,
|
|
34869
|
-
1,
|
|
34870
|
-
2,
|
|
34871
|
-
3,
|
|
34872
|
-
4,
|
|
34873
|
-
5,
|
|
34874
|
-
6
|
|
34875
|
-
];
|
|
34876
|
-
const days = cur.includes(d) ? cur.filter((x) => x !== d) : [...cur, d].toSorted((a, b) => a - b);
|
|
34877
|
-
onChange({
|
|
34878
|
-
...schedule,
|
|
34879
|
-
days
|
|
34880
|
-
});
|
|
34881
|
-
};
|
|
34882
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34883
|
-
className: "flex flex-wrap items-center gap-2 text-xs",
|
|
34884
|
-
children: [
|
|
34885
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
34886
|
-
type: "time",
|
|
34887
|
-
value: schedule.start,
|
|
34888
|
-
onChange: (e) => onChange({
|
|
34889
|
-
...schedule,
|
|
34890
|
-
start: e.target.value
|
|
34891
|
-
}),
|
|
34892
|
-
className: "rounded-md border border-border bg-background px-2 py-1 text-xs"
|
|
34893
|
-
}),
|
|
34894
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "–" }),
|
|
34895
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
34896
|
-
type: "time",
|
|
34897
|
-
value: schedule.end,
|
|
34898
|
-
onChange: (e) => onChange({
|
|
34899
|
-
...schedule,
|
|
34900
|
-
end: e.target.value
|
|
34901
|
-
}),
|
|
34902
|
-
className: "rounded-md border border-border bg-background px-2 py-1 text-xs"
|
|
34903
|
-
}),
|
|
34904
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
34905
|
-
className: "flex gap-1",
|
|
34906
|
-
children: DAYS.map(({ d, label }) => {
|
|
34907
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
34908
|
-
type: "button",
|
|
34909
|
-
onClick: () => toggleDay(d),
|
|
34910
|
-
className: `h-6 w-6 rounded text-[10px] ${!schedule.days || schedule.days.includes(d) ? "bg-primary text-primary-foreground" : "bg-surface-hover text-foreground-subtle"}`,
|
|
34911
|
-
children: label
|
|
34912
|
-
}, d);
|
|
34913
|
-
})
|
|
34914
|
-
})
|
|
34915
|
-
]
|
|
34916
|
-
});
|
|
34917
|
-
}
|
|
34918
|
-
function RecordingRulesEditor({ rules, onChange }) {
|
|
34919
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34920
|
-
className: "space-y-2",
|
|
34921
|
-
children: [
|
|
34922
|
-
rules.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
34923
|
-
className: "text-xs text-foreground-subtle",
|
|
34924
|
-
children: "No rules — recording is continuous (always) when enabled."
|
|
34925
|
-
}) : null,
|
|
34926
|
-
rules.map((rule, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34927
|
-
className: "space-y-2 rounded-lg border border-border bg-surface p-3",
|
|
34928
|
-
children: [
|
|
34929
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34930
|
-
className: "flex items-center justify-between gap-2",
|
|
34931
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
34932
|
-
className: "rounded-md border border-border bg-background px-2 py-1 text-xs",
|
|
34933
|
-
value: rule.schedule.kind,
|
|
34934
|
-
onChange: (e) => {
|
|
34935
|
-
onChange(withRuleAt(rules, i, { schedule: e.target.value === "timeOfDay" ? {
|
|
34936
|
-
kind: "timeOfDay",
|
|
34937
|
-
start: "00:00",
|
|
34938
|
-
end: "23:59"
|
|
34939
|
-
} : { kind: "always" } }));
|
|
34940
|
-
},
|
|
34941
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
34942
|
-
value: "always",
|
|
34943
|
-
children: "Always"
|
|
34944
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
34945
|
-
value: "timeOfDay",
|
|
34946
|
-
children: "Time of day"
|
|
34947
|
-
})]
|
|
34948
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
34949
|
-
type: "button",
|
|
34950
|
-
onClick: () => onChange(removeRuleAt(rules, i)),
|
|
34951
|
-
className: "text-foreground-subtle hover:text-red-500",
|
|
34952
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(X, { className: "h-4 w-4" })
|
|
34953
|
-
})]
|
|
34954
|
-
}),
|
|
34955
|
-
rule.schedule.kind === "timeOfDay" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimeOfDayRow, {
|
|
34956
|
-
schedule: rule.schedule,
|
|
34957
|
-
onChange: (schedule) => onChange(withRuleAt(rules, i, { schedule }))
|
|
34958
|
-
}) : null,
|
|
34959
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34960
|
-
className: "flex flex-wrap items-center gap-2 text-xs",
|
|
34961
|
-
children: [
|
|
34962
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
34963
|
-
className: "rounded-md border border-border bg-background px-2 py-1 text-xs",
|
|
34964
|
-
value: rule.mode,
|
|
34965
|
-
onChange: (e) => onChange(withRuleAt(rules, i, modePatch(rule, toMode(e.target.value)))),
|
|
34966
|
-
children: [
|
|
34967
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
34968
|
-
value: "continuous",
|
|
34969
|
-
children: "Continuous"
|
|
34970
|
-
}),
|
|
34971
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
34972
|
-
value: "onMotion",
|
|
34973
|
-
children: "On motion"
|
|
34974
|
-
}),
|
|
34975
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
34976
|
-
value: "onAudioThreshold",
|
|
34977
|
-
children: "On audio"
|
|
34978
|
-
})
|
|
34979
|
-
]
|
|
34980
|
-
}),
|
|
34981
|
-
isTriggerMode(rule.mode) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
34982
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
34983
|
-
className: "flex items-center gap-1",
|
|
34984
|
-
children: [
|
|
34985
|
-
"pre",
|
|
34986
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
34987
|
-
type: "number",
|
|
34988
|
-
min: 0,
|
|
34989
|
-
value: rule.preBufferSec,
|
|
34990
|
-
onChange: (e) => onChange(withRuleAt(rules, i, { preBufferSec: Number(e.target.value) })),
|
|
34991
|
-
className: "w-14 rounded-md border border-border bg-background px-1 py-1 text-xs"
|
|
34992
|
-
}),
|
|
34993
|
-
"s"
|
|
34994
|
-
]
|
|
34995
|
-
}),
|
|
34996
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
34997
|
-
className: "flex items-center gap-1",
|
|
34998
|
-
children: [
|
|
34999
|
-
"post",
|
|
35000
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35001
|
-
type: "number",
|
|
35002
|
-
min: 0,
|
|
35003
|
-
value: rule.postBufferSec,
|
|
35004
|
-
onChange: (e) => onChange(withRuleAt(rules, i, { postBufferSec: Number(e.target.value) })),
|
|
35005
|
-
className: "w-14 rounded-md border border-border bg-background px-1 py-1 text-xs"
|
|
35006
|
-
}),
|
|
35007
|
-
"s"
|
|
35008
|
-
]
|
|
35009
|
-
}),
|
|
35010
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
35011
|
-
className: "flex items-center gap-1",
|
|
35012
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35013
|
-
type: "checkbox",
|
|
35014
|
-
checked: rule.resetTimeoutOnNewEvent,
|
|
35015
|
-
onChange: (e) => onChange(withRuleAt(rules, i, { resetTimeoutOnNewEvent: e.target.checked }))
|
|
35016
|
-
}), "reset"]
|
|
35017
|
-
})
|
|
35018
|
-
] }) : null,
|
|
35019
|
-
rule.mode === "onAudioThreshold" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
35020
|
-
className: "flex items-center gap-1",
|
|
35021
|
-
children: [
|
|
35022
|
-
"thr",
|
|
35023
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35024
|
-
type: "number",
|
|
35025
|
-
value: rule.thresholdDbfs ?? -30,
|
|
35026
|
-
onChange: (e) => onChange(withRuleAt(rules, i, { thresholdDbfs: Number(e.target.value) })),
|
|
35027
|
-
className: "w-16 rounded-md border border-border bg-background px-1 py-1 text-xs"
|
|
35028
|
-
}),
|
|
35029
|
-
"dBFS"
|
|
35030
|
-
]
|
|
35031
|
-
}) : null
|
|
35032
|
-
]
|
|
35033
|
-
})
|
|
35034
|
-
]
|
|
35035
|
-
}, i)),
|
|
35036
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
35037
|
-
type: "button",
|
|
35038
|
-
onClick: () => onChange([...rules, emptyRule()]),
|
|
35039
|
-
className: "flex items-center gap-1 rounded-md border border-dashed border-border px-3 py-1.5 text-xs text-foreground-subtle hover:border-primary hover:text-foreground",
|
|
35040
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plus, { className: "h-3.5 w-3.5" }), " Add rule"]
|
|
35041
|
-
})
|
|
35042
|
-
]
|
|
35043
|
-
});
|
|
34850
|
+
/** The Base tab's bands: one always-on band of the chosen mode (none = off). */
|
|
34851
|
+
function bandsFromFormState(state) {
|
|
34852
|
+
if (state.mode === "off") return [];
|
|
34853
|
+
if (state.mode === "continuous") return [alwaysBand("continuous")];
|
|
34854
|
+
return [alwaysBand("events", {
|
|
34855
|
+
...Object.keys(state.triggers).length > 0 ? { triggers: state.triggers } : {},
|
|
34856
|
+
...state.preBufferSec !== void 0 ? { preBufferSec: state.preBufferSec } : {},
|
|
34857
|
+
...state.postBufferSec !== void 0 ? { postBufferSec: state.postBufferSec } : {}
|
|
34858
|
+
})];
|
|
35044
34859
|
}
|
|
35045
34860
|
//#endregion
|
|
35046
|
-
//#region src/composites/cap-settings/
|
|
34861
|
+
//#region src/composites/cap-settings/RecordingBandsEditor.tsx
|
|
35047
34862
|
/** Weekday chips in display order (Mon→Sun); value is `Date.getDay()` (0=Sun). */
|
|
35048
34863
|
var WEEKDAYS = [
|
|
35049
34864
|
{
|
|
@@ -35075,115 +34890,177 @@ var WEEKDAYS = [
|
|
|
35075
34890
|
label: "Sun"
|
|
35076
34891
|
}
|
|
35077
34892
|
];
|
|
35078
|
-
|
|
35079
|
-
|
|
35080
|
-
|
|
35081
|
-
|
|
35082
|
-
|
|
35083
|
-
|
|
35084
|
-
}
|
|
35085
|
-
|
|
35086
|
-
|
|
35087
|
-
|
|
35088
|
-
|
|
34893
|
+
var BAND_MODES = [{
|
|
34894
|
+
value: "continuous",
|
|
34895
|
+
label: "Continuous"
|
|
34896
|
+
}, {
|
|
34897
|
+
value: "events",
|
|
34898
|
+
label: "Events"
|
|
34899
|
+
}];
|
|
34900
|
+
/**
|
|
34901
|
+
* The band table — the ONLY authoring surface for recording intent since the
|
|
34902
|
+
* legacy mode/schedules/triggers/rules model was retired (2026-07-30).
|
|
34903
|
+
*
|
|
34904
|
+
* Each row is one `RecordingBand`: when it applies (weekdays + `HH:MM` span,
|
|
34905
|
+
* `end <= start` wraps past midnight) and how it records (continuous, or events
|
|
34906
|
+
* with its own triggers and pre/post buffers). No band covering *now* = nothing
|
|
34907
|
+
* is recorded then; "off" is the absence of a band, never a band value.
|
|
34908
|
+
*/
|
|
34909
|
+
function RecordingBandsEditor({ bands, onChange }) {
|
|
35089
34910
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35090
34911
|
className: "space-y-2",
|
|
35091
|
-
children: [
|
|
34912
|
+
children: [bands.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
35092
34913
|
className: "text-[11px] text-foreground-subtle",
|
|
35093
|
-
children: "
|
|
35094
|
-
}) :
|
|
34914
|
+
children: "No bands — nothing is recorded. Add one to start."
|
|
34915
|
+
}) : bands.map((band, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BandRow, {
|
|
35095
34916
|
band,
|
|
35096
|
-
onChange: (
|
|
35097
|
-
onRemove: () =>
|
|
34917
|
+
onChange: (patch) => onChange(withBandAt(bands, i, patch)),
|
|
34918
|
+
onRemove: () => onChange(removeBandAt(bands, i))
|
|
35098
34919
|
}, i)), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
35099
34920
|
type: "button",
|
|
35100
|
-
onClick:
|
|
34921
|
+
onClick: () => onChange([...bands, emptyBand()]),
|
|
35101
34922
|
className: "flex items-center gap-1 rounded-md bg-surface-hover px-2 py-1 text-[11px] text-foreground-subtle hover:text-foreground",
|
|
35102
34923
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plus, { className: "h-3 w-3" }), " Add band"]
|
|
35103
34924
|
})]
|
|
35104
34925
|
});
|
|
35105
34926
|
}
|
|
34927
|
+
function numOrUndef(value) {
|
|
34928
|
+
return value === "" ? void 0 : Number(value);
|
|
34929
|
+
}
|
|
35106
34930
|
function BandRow({ band, onChange, onRemove }) {
|
|
35107
|
-
if (band.kind === "always") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35108
|
-
className: "flex items-center gap-2 text-xs",
|
|
35109
|
-
children: [
|
|
35110
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35111
|
-
className: "flex-1",
|
|
35112
|
-
children: "Always"
|
|
35113
|
-
}),
|
|
35114
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35115
|
-
type: "button",
|
|
35116
|
-
onClick: () => onChange(newBand()),
|
|
35117
|
-
className: "text-foreground-subtle hover:text-foreground underline",
|
|
35118
|
-
children: "Set band"
|
|
35119
|
-
}),
|
|
35120
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35121
|
-
type: "button",
|
|
35122
|
-
onClick: onRemove,
|
|
35123
|
-
"aria-label": "Remove band",
|
|
35124
|
-
className: "text-foreground-subtle hover:text-red-500",
|
|
35125
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Trash2, { className: "h-3.5 w-3.5" })
|
|
35126
|
-
})
|
|
35127
|
-
]
|
|
35128
|
-
});
|
|
35129
|
-
const days = band.days ?? [];
|
|
35130
34931
|
const toggleDay = (value) => {
|
|
35131
|
-
const
|
|
35132
|
-
onChange({
|
|
35133
|
-
...band,
|
|
35134
|
-
days: next.length > 0 ? next : void 0
|
|
35135
|
-
});
|
|
34932
|
+
const current = band.days.length === 0 ? WEEKDAYS.map((d) => d.value) : band.days;
|
|
34933
|
+
onChange({ days: current.includes(value) ? current.filter((d) => d !== value) : [...current, value] });
|
|
35136
34934
|
};
|
|
34935
|
+
const setTriggers = (patch) => onChange({ triggers: {
|
|
34936
|
+
...band.triggers,
|
|
34937
|
+
...patch
|
|
34938
|
+
} });
|
|
35137
34939
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35138
34940
|
className: "space-y-1.5 rounded-md border border-border p-2",
|
|
35139
|
-
children: [
|
|
35140
|
-
|
|
35141
|
-
|
|
35142
|
-
|
|
35143
|
-
|
|
35144
|
-
|
|
35145
|
-
|
|
35146
|
-
|
|
35147
|
-
start: e.target.value
|
|
34941
|
+
children: [
|
|
34942
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34943
|
+
className: "flex flex-wrap items-center gap-2 text-xs",
|
|
34944
|
+
children: [
|
|
34945
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
34946
|
+
type: "time",
|
|
34947
|
+
"aria-label": "Band start",
|
|
34948
|
+
value: band.start,
|
|
34949
|
+
onChange: (e) => onChange({ start: e.target.value }),
|
|
34950
|
+
className: "rounded-md border border-border bg-background px-1 py-0.5"
|
|
35148
34951
|
}),
|
|
35149
|
-
|
|
35150
|
-
|
|
35151
|
-
|
|
35152
|
-
className: "text-foreground-subtle",
|
|
35153
|
-
children: "→"
|
|
35154
|
-
}),
|
|
35155
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35156
|
-
type: "time",
|
|
35157
|
-
value: band.end,
|
|
35158
|
-
onChange: (e) => onChange({
|
|
35159
|
-
...band,
|
|
35160
|
-
end: e.target.value
|
|
34952
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34953
|
+
className: "text-foreground-subtle",
|
|
34954
|
+
children: "→"
|
|
35161
34955
|
}),
|
|
35162
|
-
|
|
35163
|
-
|
|
35164
|
-
|
|
35165
|
-
|
|
35166
|
-
|
|
35167
|
-
|
|
35168
|
-
|
|
35169
|
-
|
|
35170
|
-
|
|
35171
|
-
|
|
35172
|
-
|
|
35173
|
-
|
|
35174
|
-
|
|
35175
|
-
|
|
35176
|
-
|
|
35177
|
-
|
|
35178
|
-
|
|
35179
|
-
|
|
35180
|
-
|
|
35181
|
-
|
|
35182
|
-
|
|
35183
|
-
|
|
35184
|
-
|
|
35185
|
-
|
|
35186
|
-
|
|
34956
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
34957
|
+
type: "time",
|
|
34958
|
+
"aria-label": "Band end",
|
|
34959
|
+
value: band.end,
|
|
34960
|
+
onChange: (e) => onChange({ end: e.target.value }),
|
|
34961
|
+
className: "rounded-md border border-border bg-background px-1 py-0.5"
|
|
34962
|
+
}),
|
|
34963
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34964
|
+
className: "flex gap-1",
|
|
34965
|
+
children: BAND_MODES.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
34966
|
+
type: "button",
|
|
34967
|
+
onClick: () => onChange({ mode: m.value }),
|
|
34968
|
+
className: `rounded px-2 py-0.5 ${band.mode === m.value ? "bg-primary text-primary-foreground" : "bg-surface-hover text-foreground-subtle"}`,
|
|
34969
|
+
children: m.label
|
|
34970
|
+
}, m.value))
|
|
34971
|
+
}),
|
|
34972
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "flex-1" }),
|
|
34973
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
34974
|
+
type: "button",
|
|
34975
|
+
onClick: onRemove,
|
|
34976
|
+
"aria-label": "Remove band",
|
|
34977
|
+
className: "text-foreground-subtle hover:text-red-500",
|
|
34978
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Trash2, { className: "h-3.5 w-3.5" })
|
|
34979
|
+
})
|
|
34980
|
+
]
|
|
34981
|
+
}),
|
|
34982
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34983
|
+
className: "flex flex-wrap gap-1",
|
|
34984
|
+
children: [WEEKDAYS.map((d) => {
|
|
34985
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
34986
|
+
type: "button",
|
|
34987
|
+
onClick: () => toggleDay(d.value),
|
|
34988
|
+
className: `rounded px-1.5 py-0.5 text-[10px] ${band.days.length === 0 || band.days.includes(d.value) ? "bg-primary text-primary-foreground" : "bg-surface-hover text-foreground-subtle"}`,
|
|
34989
|
+
children: d.label
|
|
34990
|
+
}, d.value);
|
|
34991
|
+
}), band.days.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34992
|
+
className: "self-center text-[10px] text-foreground-subtle",
|
|
34993
|
+
children: "every day"
|
|
34994
|
+
}) : null]
|
|
34995
|
+
}),
|
|
34996
|
+
band.mode === "events" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34997
|
+
className: "space-y-1.5 border-t border-border pt-1.5",
|
|
34998
|
+
children: [
|
|
34999
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
35000
|
+
className: "flex items-center gap-2 text-xs",
|
|
35001
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35002
|
+
type: "checkbox",
|
|
35003
|
+
checked: band.triggers?.motion ?? false,
|
|
35004
|
+
onChange: (e) => setTriggers({ motion: e.target.checked ? true : void 0 })
|
|
35005
|
+
}), "Motion"]
|
|
35006
|
+
}),
|
|
35007
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
35008
|
+
className: "flex items-center gap-2 text-xs",
|
|
35009
|
+
children: [
|
|
35010
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35011
|
+
type: "checkbox",
|
|
35012
|
+
checked: band.triggers?.audioThresholdDbfs != null,
|
|
35013
|
+
onChange: (e) => setTriggers({ audioThresholdDbfs: e.target.checked ? -30 : void 0 })
|
|
35014
|
+
}),
|
|
35015
|
+
"Audio above threshold",
|
|
35016
|
+
band.triggers?.audioThresholdDbfs != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35017
|
+
type: "number",
|
|
35018
|
+
"aria-label": "Audio threshold dBFS",
|
|
35019
|
+
value: band.triggers.audioThresholdDbfs,
|
|
35020
|
+
onChange: (e) => setTriggers({ audioThresholdDbfs: numOrUndef(e.target.value) }),
|
|
35021
|
+
className: "w-16 rounded-md border border-border bg-background px-1 py-0.5 text-xs"
|
|
35022
|
+
}) : null,
|
|
35023
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35024
|
+
className: "text-foreground-subtle",
|
|
35025
|
+
children: "dBFS"
|
|
35026
|
+
})
|
|
35027
|
+
]
|
|
35028
|
+
}),
|
|
35029
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35030
|
+
className: "flex flex-wrap items-center gap-3 text-xs",
|
|
35031
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
35032
|
+
className: "flex items-center gap-1",
|
|
35033
|
+
children: [
|
|
35034
|
+
"Pre-buffer",
|
|
35035
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35036
|
+
type: "number",
|
|
35037
|
+
min: 0,
|
|
35038
|
+
value: band.preBufferSec ?? "",
|
|
35039
|
+
placeholder: "0",
|
|
35040
|
+
onChange: (e) => onChange({ preBufferSec: numOrUndef(e.target.value) }),
|
|
35041
|
+
className: "w-16 rounded-md border border-border bg-background px-1 py-1 text-xs"
|
|
35042
|
+
}),
|
|
35043
|
+
"s"
|
|
35044
|
+
]
|
|
35045
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
35046
|
+
className: "flex items-center gap-1",
|
|
35047
|
+
children: [
|
|
35048
|
+
"Post-buffer",
|
|
35049
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35050
|
+
type: "number",
|
|
35051
|
+
min: 0,
|
|
35052
|
+
value: band.postBufferSec ?? "",
|
|
35053
|
+
placeholder: "0",
|
|
35054
|
+
onChange: (e) => onChange({ postBufferSec: numOrUndef(e.target.value) }),
|
|
35055
|
+
className: "w-16 rounded-md border border-border bg-background px-1 py-1 text-xs"
|
|
35056
|
+
}),
|
|
35057
|
+
"s"
|
|
35058
|
+
]
|
|
35059
|
+
})]
|
|
35060
|
+
})
|
|
35061
|
+
]
|
|
35062
|
+
}) : null
|
|
35063
|
+
]
|
|
35187
35064
|
});
|
|
35188
35065
|
}
|
|
35189
35066
|
//#endregion
|
|
@@ -35207,16 +35084,15 @@ var MODES = [
|
|
|
35207
35084
|
label: "Continuous"
|
|
35208
35085
|
}
|
|
35209
35086
|
];
|
|
35210
|
-
function RecordingSettings({ initial, saving, onSave }) {
|
|
35087
|
+
function RecordingSettings({ initial, saving, onSave, onRegenerateStrips, regeneratingStrips }) {
|
|
35211
35088
|
const [tab, setTab] = (0, react$1.useState)(isAdvancedConfig(initial) ? "advanced" : "base");
|
|
35212
35089
|
const [base, setBase] = (0, react$1.useState)(formStateFromConfig(initial));
|
|
35213
|
-
const [
|
|
35214
|
-
const [rules, setRules] = (0, react$1.useState)(initial.rules ? [...initial.rules] : []);
|
|
35090
|
+
const [bands, setBands] = (0, react$1.useState)([...initial.bands]);
|
|
35215
35091
|
const [common, setCommon] = (0, react$1.useState)({
|
|
35216
35092
|
profiles: initial.profiles,
|
|
35217
35093
|
segmentSeconds: initial.segmentSeconds,
|
|
35218
35094
|
retention: initial.retention,
|
|
35219
|
-
|
|
35095
|
+
stripsEnabled: initial.stripsEnabled
|
|
35220
35096
|
});
|
|
35221
35097
|
const ret = common.retention ?? {};
|
|
35222
35098
|
const setRet = (patch) => setCommon({
|
|
@@ -35227,10 +35103,6 @@ function RecordingSettings({ initial, saving, onSave }) {
|
|
|
35227
35103
|
}
|
|
35228
35104
|
});
|
|
35229
35105
|
const numOrUndef = (v) => v === "" ? void 0 : Number(v);
|
|
35230
|
-
const parsePreset = (v) => {
|
|
35231
|
-
const parsed = _camstack_types.ScrubThumbnailPresetSchema.safeParse(v);
|
|
35232
|
-
return parsed.success ? parsed.data : _camstack_types.DEFAULT_SCRUB_THUMBNAIL_PRESET;
|
|
35233
|
-
};
|
|
35234
35106
|
const toggleProfile = (p) => {
|
|
35235
35107
|
const cur = common.profiles ?? [...PROFILES];
|
|
35236
35108
|
const next = cur.includes(p) ? cur.filter((x) => x !== p) : [...cur, p];
|
|
@@ -35247,24 +35119,31 @@ function RecordingSettings({ initial, saving, onSave }) {
|
|
|
35247
35119
|
}
|
|
35248
35120
|
});
|
|
35249
35121
|
const switchTab = (next) => {
|
|
35250
|
-
if (next === "advanced"
|
|
35122
|
+
if (next === "advanced") setBands(bandsFromFormState(base));
|
|
35123
|
+
else setBase(formStateFromConfig({
|
|
35124
|
+
enabled: bands.length > 0,
|
|
35125
|
+
bands
|
|
35126
|
+
}));
|
|
35251
35127
|
setTab(next);
|
|
35252
35128
|
};
|
|
35253
35129
|
const handleSave = () => {
|
|
35254
|
-
|
|
35255
|
-
...base,
|
|
35256
|
-
profiles: common.profiles,
|
|
35257
|
-
segmentSeconds: common.segmentSeconds,
|
|
35258
|
-
retention: common.retention,
|
|
35259
|
-
scrubThumbnails: common.scrubThumbnails
|
|
35260
|
-
}));
|
|
35261
|
-
else onSave({
|
|
35262
|
-
enabled: advEnabled,
|
|
35263
|
-
rules,
|
|
35130
|
+
const shared = {
|
|
35264
35131
|
profiles: common.profiles ? [...common.profiles] : void 0,
|
|
35265
35132
|
segmentSeconds: common.segmentSeconds,
|
|
35266
35133
|
retention: common.retention,
|
|
35267
|
-
|
|
35134
|
+
...common.stripsEnabled === void 0 ? {} : { stripsEnabled: common.stripsEnabled }
|
|
35135
|
+
};
|
|
35136
|
+
if (tab === "base") onSave({
|
|
35137
|
+
...configFromFormState({
|
|
35138
|
+
...base,
|
|
35139
|
+
...shared
|
|
35140
|
+
}),
|
|
35141
|
+
...shared
|
|
35142
|
+
});
|
|
35143
|
+
else onSave({
|
|
35144
|
+
enabled: bands.length > 0,
|
|
35145
|
+
bands,
|
|
35146
|
+
...shared
|
|
35268
35147
|
});
|
|
35269
35148
|
};
|
|
35270
35149
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -35276,7 +35155,7 @@ function RecordingSettings({ initial, saving, onSave }) {
|
|
|
35276
35155
|
type: "button",
|
|
35277
35156
|
onClick: () => switchTab(t),
|
|
35278
35157
|
className: `rounded px-2.5 py-1 ${tab === t ? "bg-primary text-primary-foreground" : "bg-surface-hover text-foreground-subtle"}`,
|
|
35279
|
-
children: t === "base" ? "Base" : "Advanced (
|
|
35158
|
+
children: t === "base" ? "Base" : "Advanced (bands)"
|
|
35280
35159
|
}, t))
|
|
35281
35160
|
}),
|
|
35282
35161
|
tab === "base" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -35297,16 +35176,17 @@ function RecordingSettings({ initial, saving, onSave }) {
|
|
|
35297
35176
|
children: m.label
|
|
35298
35177
|
}, m.value))]
|
|
35299
35178
|
}),
|
|
35300
|
-
base.mode !== "off" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
35301
|
-
className: "
|
|
35302
|
-
children:
|
|
35303
|
-
|
|
35304
|
-
|
|
35305
|
-
|
|
35306
|
-
|
|
35307
|
-
|
|
35308
|
-
|
|
35309
|
-
|
|
35179
|
+
base.mode !== "off" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
35180
|
+
className: "text-[11px] text-foreground-subtle",
|
|
35181
|
+
children: [
|
|
35182
|
+
"Records 24/7. Use ",
|
|
35183
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35184
|
+
className: "font-medium",
|
|
35185
|
+
children: "Advanced (bands)"
|
|
35186
|
+
}),
|
|
35187
|
+
" to restrict hours or weekdays, or to mix continuous and event windows."
|
|
35188
|
+
]
|
|
35189
|
+
}) : null,
|
|
35310
35190
|
base.mode === "events" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35311
35191
|
className: "space-y-2",
|
|
35312
35192
|
children: [
|
|
@@ -35384,22 +35264,22 @@ function RecordingSettings({ initial, saving, onSave }) {
|
|
|
35384
35264
|
]
|
|
35385
35265
|
}) : null
|
|
35386
35266
|
]
|
|
35387
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.
|
|
35267
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35388
35268
|
className: "space-y-4",
|
|
35389
|
-
children:
|
|
35390
|
-
|
|
35391
|
-
|
|
35392
|
-
|
|
35393
|
-
|
|
35394
|
-
|
|
35395
|
-
|
|
35396
|
-
|
|
35397
|
-
|
|
35398
|
-
|
|
35399
|
-
|
|
35400
|
-
|
|
35401
|
-
|
|
35402
|
-
|
|
35269
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
35270
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35271
|
+
className: "mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-foreground-subtle",
|
|
35272
|
+
children: "Recording bands"
|
|
35273
|
+
}),
|
|
35274
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RecordingBandsEditor, {
|
|
35275
|
+
bands,
|
|
35276
|
+
onChange: setBands
|
|
35277
|
+
}),
|
|
35278
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
35279
|
+
className: "mt-1.5 text-[11px] text-foreground-subtle",
|
|
35280
|
+
children: "A band says WHEN to record and HOW. Nothing is recorded outside every band — an empty table means this camera is off."
|
|
35281
|
+
})
|
|
35282
|
+
] })
|
|
35403
35283
|
}),
|
|
35404
35284
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35405
35285
|
className: "flex flex-wrap items-center gap-4 text-xs",
|
|
@@ -35438,27 +35318,30 @@ function RecordingSettings({ initial, saving, onSave }) {
|
|
|
35438
35318
|
}),
|
|
35439
35319
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
35440
35320
|
className: "flex items-center gap-1",
|
|
35441
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
35442
|
-
|
|
35443
|
-
|
|
35444
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
35445
|
-
value: common.scrubThumbnails ?? _camstack_types.DEFAULT_SCRUB_THUMBNAIL_PRESET,
|
|
35321
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35322
|
+
type: "checkbox",
|
|
35323
|
+
checked: common.stripsEnabled === true,
|
|
35446
35324
|
onChange: (e) => setCommon({
|
|
35447
35325
|
...common,
|
|
35448
|
-
|
|
35449
|
-
})
|
|
35450
|
-
|
|
35451
|
-
|
|
35452
|
-
|
|
35453
|
-
children: _camstack_types.SCRUB_THUMBNAIL_PRESET_LABELS[p]
|
|
35454
|
-
}, p))
|
|
35326
|
+
stripsEnabled: e.target.checked
|
|
35327
|
+
})
|
|
35328
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35329
|
+
className: "text-foreground-subtle",
|
|
35330
|
+
children: "Scrub thumbnail strips"
|
|
35455
35331
|
})]
|
|
35456
|
-
})
|
|
35332
|
+
}),
|
|
35333
|
+
onRegenerateStrips ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35334
|
+
type: "button",
|
|
35335
|
+
onClick: onRegenerateStrips,
|
|
35336
|
+
disabled: regeneratingStrips === true,
|
|
35337
|
+
className: "rounded-md border border-border bg-surface-hover px-2 py-1 text-xs text-foreground-subtle transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
35338
|
+
children: regeneratingStrips === true ? "Regenerating…" : "Regenerate strips"
|
|
35339
|
+
}) : null
|
|
35457
35340
|
]
|
|
35458
35341
|
}),
|
|
35459
35342
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
35460
35343
|
className: "text-[11px] text-foreground-subtle",
|
|
35461
|
-
children: "
|
|
35344
|
+
children: "OPT-IN: save every keyframe of the low recording as a JPEG strip for fluid fast scrubbing. Costs disk (a derived cache, reclaimed with the footage). Regenerate clears and rebuilds the recent days from the segments already on disk."
|
|
35462
35345
|
}),
|
|
35463
35346
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35464
35347
|
className: "mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-foreground-subtle",
|
|
@@ -36707,6 +36590,14 @@ function RecordingPanel({ deviceId }) {
|
|
|
36707
36590
|
const configQuery = useRecordingGetDeviceConfig({ deviceId });
|
|
36708
36591
|
const setConfig = useRecordingSetDeviceConfig();
|
|
36709
36592
|
const rescanStorage = useRecordingRescanStorage();
|
|
36593
|
+
const customAction = useAddonsCustom();
|
|
36594
|
+
const regenerateStrips = (0, react$1.useCallback)(() => {
|
|
36595
|
+
customAction.mutate({
|
|
36596
|
+
addonId: "recorder",
|
|
36597
|
+
action: "regenerateStrips",
|
|
36598
|
+
input: { deviceId }
|
|
36599
|
+
});
|
|
36600
|
+
}, [customAction, deviceId]);
|
|
36710
36601
|
const saveConfig = (config) => {
|
|
36711
36602
|
setConfig.mutate({
|
|
36712
36603
|
deviceId,
|
|
@@ -36748,7 +36639,9 @@ function RecordingPanel({ deviceId }) {
|
|
|
36748
36639
|
children: resolvedConfig ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RecordingSettings, {
|
|
36749
36640
|
initial: resolvedConfig,
|
|
36750
36641
|
saving: setConfig.isPending,
|
|
36751
|
-
onSave: saveConfig
|
|
36642
|
+
onSave: saveConfig,
|
|
36643
|
+
onRegenerateStrips: regenerateStrips,
|
|
36644
|
+
regeneratingStrips: customAction.isPending
|
|
36752
36645
|
}, JSON.stringify(resolvedConfig)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
36753
36646
|
className: "px-1 py-2 text-xs text-foreground-subtle",
|
|
36754
36647
|
children: "Loading settings…"
|
|
@@ -40314,6 +40207,21 @@ function DetectionBoxes({ detections, frameWidth, frameHeight }) {
|
|
|
40314
40207
|
}
|
|
40315
40208
|
//#endregion
|
|
40316
40209
|
//#region src/composites/camera-stream-player.tsx
|
|
40210
|
+
/**
|
|
40211
|
+
* Silence (or restore) a live WebRTC stream FOR REAL.
|
|
40212
|
+
*
|
|
40213
|
+
* `video.muted` alone is not enough: iOS WebKit keeps playing a
|
|
40214
|
+
* `MediaStream`'s audio track through a muted element, which is why live audio
|
|
40215
|
+
* kept sounding under recorded playback on the viewer. Gating the audio TRACK
|
|
40216
|
+
* (`track.enabled`) is what actually stops it, so both are set together — and
|
|
40217
|
+
* re-applied at every stream swap, because a freshly-attached stream arrives
|
|
40218
|
+
* with its tracks enabled whatever the element says.
|
|
40219
|
+
*/
|
|
40220
|
+
function applyStreamMute(video, muted) {
|
|
40221
|
+
video.muted = muted;
|
|
40222
|
+
const stream = video.srcObject;
|
|
40223
|
+
if (stream instanceof MediaStream) for (const track of stream.getAudioTracks()) track.enabled = !muted;
|
|
40224
|
+
}
|
|
40317
40225
|
function isTouchDevice() {
|
|
40318
40226
|
if (typeof window === "undefined") return false;
|
|
40319
40227
|
return window.matchMedia("(hover: none) and (pointer: coarse)").matches || typeof navigator !== "undefined" && navigator.maxTouchPoints > 0 || "ontouchstart" in window;
|
|
@@ -40396,6 +40304,11 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40396
40304
|
const [state, setState] = (0, react$1.useState)("idle");
|
|
40397
40305
|
const [errorMessage, setErrorMessage] = (0, react$1.useState)("");
|
|
40398
40306
|
const [isMuted, setIsMuted] = (0, react$1.useState)(initialMuted);
|
|
40307
|
+
/** Read by the async stream-swap closures: a freshly-promoted stream must
|
|
40308
|
+
* arrive already respecting the CURRENT mute, not the one captured at
|
|
40309
|
+
* connect time. */
|
|
40310
|
+
const isMutedRef = (0, react$1.useRef)(isMuted);
|
|
40311
|
+
isMutedRef.current = isMuted;
|
|
40399
40312
|
const [isFullscreen, setIsFullscreen] = (0, react$1.useState)(false);
|
|
40400
40313
|
const [isPipSupported, setIsPipSupported] = (0, react$1.useState)(false);
|
|
40401
40314
|
const isTouch = isTouchDevice();
|
|
@@ -40525,6 +40438,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40525
40438
|
pcRef.current = pc;
|
|
40526
40439
|
if (videoRef.current) {
|
|
40527
40440
|
videoRef.current.srcObject = combinedStream;
|
|
40441
|
+
applyStreamMute(videoRef.current, isMutedRef.current);
|
|
40528
40442
|
videoRef.current.play().catch(() => {});
|
|
40529
40443
|
}
|
|
40530
40444
|
if (oldPc && oldPc !== pc) try {
|
|
@@ -40544,6 +40458,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40544
40458
|
}
|
|
40545
40459
|
if (videoRef.current) {
|
|
40546
40460
|
videoRef.current.srcObject = combinedStream;
|
|
40461
|
+
applyStreamMute(videoRef.current, isMutedRef.current);
|
|
40547
40462
|
videoRef.current.play().catch(() => {});
|
|
40548
40463
|
}
|
|
40549
40464
|
};
|
|
@@ -40630,6 +40545,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40630
40545
|
whepStream.addTrack(event.track);
|
|
40631
40546
|
if (videoRef.current) {
|
|
40632
40547
|
videoRef.current.srcObject = whepStream;
|
|
40548
|
+
applyStreamMute(videoRef.current, isMutedRef.current);
|
|
40633
40549
|
videoRef.current.play().catch(() => {});
|
|
40634
40550
|
}
|
|
40635
40551
|
};
|
|
@@ -40702,6 +40618,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40702
40618
|
pcRef.current = pc;
|
|
40703
40619
|
if (videoRef.current) {
|
|
40704
40620
|
videoRef.current.srcObject = combinedStream;
|
|
40621
|
+
applyStreamMute(videoRef.current, isMutedRef.current);
|
|
40705
40622
|
videoRef.current.play().catch(() => {});
|
|
40706
40623
|
}
|
|
40707
40624
|
if (oldPc && oldPc !== pc) try {
|
|
@@ -40722,6 +40639,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40722
40639
|
}
|
|
40723
40640
|
if (videoRef.current) {
|
|
40724
40641
|
videoRef.current.srcObject = combinedStream;
|
|
40642
|
+
applyStreamMute(videoRef.current, isMutedRef.current);
|
|
40725
40643
|
videoRef.current.play().catch(() => {});
|
|
40726
40644
|
}
|
|
40727
40645
|
};
|
|
@@ -41046,7 +40964,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
41046
40964
|
mountedRef.current = false;
|
|
41047
40965
|
cleanup();
|
|
41048
40966
|
};
|
|
41049
|
-
}, [
|
|
40967
|
+
}, []);
|
|
41050
40968
|
const hasSignaledRef = (0, react$1.useRef)(false);
|
|
41051
40969
|
(0, react$1.useEffect)(() => {
|
|
41052
40970
|
if (!autoPlay) return;
|
|
@@ -41084,6 +41002,23 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
41084
41002
|
}
|
|
41085
41003
|
connect();
|
|
41086
41004
|
};
|
|
41005
|
+
const prevServerUrlRef = (0, react$1.useRef)(serverUrl);
|
|
41006
|
+
const prevStreamKeyRef = (0, react$1.useRef)(streamKey);
|
|
41007
|
+
(0, react$1.useEffect)(() => {
|
|
41008
|
+
const serverChanged = prevServerUrlRef.current !== serverUrl;
|
|
41009
|
+
const keyChanged = prevStreamKeyRef.current !== streamKey;
|
|
41010
|
+
prevServerUrlRef.current = serverUrl;
|
|
41011
|
+
prevStreamKeyRef.current = streamKey;
|
|
41012
|
+
if (!serverChanged && !keyChanged) return;
|
|
41013
|
+
if (!mountedRef.current) return;
|
|
41014
|
+
reconnectAttemptsRef.current = 0;
|
|
41015
|
+
if (serverChanged) {
|
|
41016
|
+
cleanup();
|
|
41017
|
+
connect();
|
|
41018
|
+
return;
|
|
41019
|
+
}
|
|
41020
|
+
handleSeamlessReconnect();
|
|
41021
|
+
}, [serverUrl, streamKey]);
|
|
41087
41022
|
const prevReconnectSignalRef = (0, react$1.useRef)(reconnectSignal);
|
|
41088
41023
|
(0, react$1.useEffect)(() => {
|
|
41089
41024
|
if (prevReconnectSignalRef.current === reconnectSignal) return;
|
|
@@ -41093,8 +41028,9 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
41093
41028
|
}, [reconnectSignal]);
|
|
41094
41029
|
const toggleMute = () => {
|
|
41095
41030
|
if (videoRef.current) {
|
|
41096
|
-
|
|
41097
|
-
|
|
41031
|
+
const next = !videoRef.current.muted;
|
|
41032
|
+
applyStreamMute(videoRef.current, next);
|
|
41033
|
+
setIsMuted(next);
|
|
41098
41034
|
}
|
|
41099
41035
|
};
|
|
41100
41036
|
const toggleFullscreen = async () => {
|
|
@@ -41322,7 +41258,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
41322
41258
|
setIsMuted(initialMuted);
|
|
41323
41259
|
const video = videoRef.current;
|
|
41324
41260
|
if (!video) return;
|
|
41325
|
-
video
|
|
41261
|
+
applyStreamMute(video, initialMuted);
|
|
41326
41262
|
if (!initialMuted) video.play().catch(() => {});
|
|
41327
41263
|
}, [initialMuted]);
|
|
41328
41264
|
const showToolbarBriefly = () => {
|
|
@@ -42434,7 +42370,7 @@ function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIce
|
|
|
42434
42370
|
extraOverlay,
|
|
42435
42371
|
ptzOverlayVisible && ptzOverlay
|
|
42436
42372
|
] }) : void 0
|
|
42437
|
-
}
|
|
42373
|
+
})
|
|
42438
42374
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SnapshotPreview, {
|
|
42439
42375
|
src: snapshotSrc ?? null,
|
|
42440
42376
|
isLoading: snapshotLoading,
|
|
@@ -46815,6 +46751,15 @@ function useDeviceDetections(trpc, deviceId) {
|
|
|
46815
46751
|
* @param deviceId - numeric device ID (null = disabled)
|
|
46816
46752
|
* @param pollIntervalMs - how often to refresh profile slots (default: 5000)
|
|
46817
46753
|
*/
|
|
46754
|
+
/** TTL for the TURN/STUN credential cache. Short enough that rotated creds
|
|
46755
|
+
* are picked up promptly, long enough to cover a connect + several profile
|
|
46756
|
+
* switches without re-paying the fetch. */
|
|
46757
|
+
var TURN_CACHE_TTL_MS = 5 * 6e4;
|
|
46758
|
+
/** Keyed on the caller's trpc client object (stable per connected system —
|
|
46759
|
+
* NOT on `trpc.turnProvider`, which is a proxy minted fresh on every property
|
|
46760
|
+
* access), so two admin tabs pointed at different hubs never cross-serve
|
|
46761
|
+
* credentials. An unstable key degrades to a cache miss, never to wrong creds. */
|
|
46762
|
+
var turnServersCache = /* @__PURE__ */ new WeakMap();
|
|
46818
46763
|
function useDeviceWebrtc(trpc, deviceId, pollIntervalMs = 5e3) {
|
|
46819
46764
|
const [remoteStreams, setRemoteStreams] = (0, react$1.useState)([]);
|
|
46820
46765
|
(0, react$1.useEffect)(() => {
|
|
@@ -46884,18 +46829,28 @@ function useDeviceWebrtc(trpc, deviceId, pollIntervalMs = 5e3) {
|
|
|
46884
46829
|
}, [deviceId, remoteStreams]);
|
|
46885
46830
|
const getIceServers = (0, react$1.useCallback)(async () => {
|
|
46886
46831
|
if (!trpc.turnProvider) return void 0;
|
|
46832
|
+
const cached = turnServersCache.get(trpc);
|
|
46833
|
+
if (cached && Date.now() - cached.fetchedAt < TURN_CACHE_TTL_MS) return cached.servers;
|
|
46887
46834
|
try {
|
|
46888
|
-
|
|
46835
|
+
const mapped = (await trpc.turnProvider.getTurnServers.query()).map((s) => {
|
|
46889
46836
|
return {
|
|
46890
46837
|
urls: typeof s.urls === "string" ? s.urls : [...s.urls],
|
|
46891
46838
|
...s.username !== void 0 ? { username: s.username } : {},
|
|
46892
46839
|
...s.credential !== void 0 ? { credential: s.credential } : {}
|
|
46893
46840
|
};
|
|
46894
46841
|
});
|
|
46842
|
+
turnServersCache.set(trpc, {
|
|
46843
|
+
servers: mapped,
|
|
46844
|
+
fetchedAt: Date.now()
|
|
46845
|
+
});
|
|
46846
|
+
return mapped;
|
|
46895
46847
|
} catch {
|
|
46896
|
-
return;
|
|
46848
|
+
return cached?.servers;
|
|
46897
46849
|
}
|
|
46898
46850
|
}, [trpc]);
|
|
46851
|
+
(0, react$1.useEffect)(() => {
|
|
46852
|
+
getIceServers();
|
|
46853
|
+
}, [getIceServers]);
|
|
46899
46854
|
const createSession = (0, react$1.useCallback)(async (target, hints) => {
|
|
46900
46855
|
if (deviceId === null) throw new Error("No device selected");
|
|
46901
46856
|
const res = await trpc.webrtcSession.createSession.mutate({
|
|
@@ -47993,10 +47948,12 @@ exports.useLlmTestProfile = useLlmTestProfile;
|
|
|
47993
47948
|
exports.useLlmUpsertProfile = useLlmUpsertProfile;
|
|
47994
47949
|
exports.useLocalNetworkGetAllowedAddresses = useLocalNetworkGetAllowedAddresses;
|
|
47995
47950
|
exports.useLocalNetworkGetConnectionEndpoints = useLocalNetworkGetConnectionEndpoints;
|
|
47951
|
+
exports.useLocalNetworkGetNotificationEndpoint = useLocalNetworkGetNotificationEndpoint;
|
|
47996
47952
|
exports.useLocalNetworkGetPreferred = useLocalNetworkGetPreferred;
|
|
47997
47953
|
exports.useLocalNetworkList = useLocalNetworkList;
|
|
47998
47954
|
exports.useLocalNetworkResetAllowlistToBestMatch = useLocalNetworkResetAllowlistToBestMatch;
|
|
47999
47955
|
exports.useLocalNetworkSetAllowedAddresses = useLocalNetworkSetAllowedAddresses;
|
|
47956
|
+
exports.useLocalNetworkSetNotificationEndpoint = useLocalNetworkSetNotificationEndpoint;
|
|
48000
47957
|
exports.useLockControlGetStatus = useLockControlGetStatus;
|
|
48001
47958
|
exports.useLockControlLock = useLockControlLock;
|
|
48002
47959
|
exports.useLockControlOpen = useLockControlOpen;
|
|
@@ -48116,6 +48073,7 @@ exports.usePetFeederSetFeedSound = usePetFeederSetFeedSound;
|
|
|
48116
48073
|
exports.usePetFeederSetIndicatorLight = usePetFeederSetIndicatorLight;
|
|
48117
48074
|
exports.usePetFeederSetVolume = usePetFeederSetVolume;
|
|
48118
48075
|
exports.usePipelineAnalyticsApplyDeviceSettingsPatch = usePipelineAnalyticsApplyDeviceSettingsPatch;
|
|
48076
|
+
exports.usePipelineAnalyticsCancelMediaRelocate = usePipelineAnalyticsCancelMediaRelocate;
|
|
48119
48077
|
exports.usePipelineAnalyticsClearTracks = usePipelineAnalyticsClearTracks;
|
|
48120
48078
|
exports.usePipelineAnalyticsDeleteDeviceEvents = usePipelineAnalyticsDeleteDeviceEvents;
|
|
48121
48079
|
exports.usePipelineAnalyticsDeleteTracks = usePipelineAnalyticsDeleteTracks;
|
|
@@ -48127,6 +48085,7 @@ exports.usePipelineAnalyticsGetEventDensity = usePipelineAnalyticsGetEventDensit
|
|
|
48127
48085
|
exports.usePipelineAnalyticsGetEventMedia = usePipelineAnalyticsGetEventMedia;
|
|
48128
48086
|
exports.usePipelineAnalyticsGetEventStoreFootprint = usePipelineAnalyticsGetEventStoreFootprint;
|
|
48129
48087
|
exports.usePipelineAnalyticsGetKeyEvents = usePipelineAnalyticsGetKeyEvents;
|
|
48088
|
+
exports.usePipelineAnalyticsGetMediaRelocateStatus = usePipelineAnalyticsGetMediaRelocateStatus;
|
|
48130
48089
|
exports.usePipelineAnalyticsGetMotionEvents = usePipelineAnalyticsGetMotionEvents;
|
|
48131
48090
|
exports.usePipelineAnalyticsGetObjectEvents = usePipelineAnalyticsGetObjectEvents;
|
|
48132
48091
|
exports.usePipelineAnalyticsGetSensorEvents = usePipelineAnalyticsGetSensorEvents;
|
|
@@ -48139,6 +48098,7 @@ exports.usePipelineAnalyticsListTracks = usePipelineAnalyticsListTracks;
|
|
|
48139
48098
|
exports.usePipelineAnalyticsPruneEvents = usePipelineAnalyticsPruneEvents;
|
|
48140
48099
|
exports.usePipelineAnalyticsPruneEventsBefore = usePipelineAnalyticsPruneEventsBefore;
|
|
48141
48100
|
exports.usePipelineAnalyticsPruneTracksBefore = usePipelineAnalyticsPruneTracksBefore;
|
|
48101
|
+
exports.usePipelineAnalyticsRelocateMedia = usePipelineAnalyticsRelocateMedia;
|
|
48142
48102
|
exports.usePipelineAnalyticsSearchObjectEvents = usePipelineAnalyticsSearchObjectEvents;
|
|
48143
48103
|
exports.usePipelineAnalyticsWipeAllAnalytics = usePipelineAnalyticsWipeAllAnalytics;
|
|
48144
48104
|
exports.usePipelineExecutorCacheFrameInPool = usePipelineExecutorCacheFrameInPool;
|
|
@@ -48276,6 +48236,7 @@ exports.usePtzStop = usePtzStop;
|
|
|
48276
48236
|
exports.useRebootReboot = useRebootReboot;
|
|
48277
48237
|
exports.useRecordedPlayback = useRecordedPlayback;
|
|
48278
48238
|
exports.useRecordingApplyDeviceSettingsPatch = useRecordingApplyDeviceSettingsPatch;
|
|
48239
|
+
exports.useRecordingCancelRelocate = useRecordingCancelRelocate;
|
|
48279
48240
|
exports.useRecordingDeleteFootprint = useRecordingDeleteFootprint;
|
|
48280
48241
|
exports.useRecordingExportCancelExport = useRecordingExportCancelExport;
|
|
48281
48242
|
exports.useRecordingExportCreateExport = useRecordingExportCreateExport;
|
|
@@ -48289,12 +48250,16 @@ exports.useRecordingGetDeviceConfig = useRecordingGetDeviceConfig;
|
|
|
48289
48250
|
exports.useRecordingGetDeviceLiveContribution = useRecordingGetDeviceLiveContribution;
|
|
48290
48251
|
exports.useRecordingGetDeviceSettingsContribution = useRecordingGetDeviceSettingsContribution;
|
|
48291
48252
|
exports.useRecordingGetPlaybackManifest = useRecordingGetPlaybackManifest;
|
|
48253
|
+
exports.useRecordingGetRelocateStatus = useRecordingGetRelocateStatus;
|
|
48292
48254
|
exports.useRecordingGetStatus = useRecordingGetStatus;
|
|
48293
48255
|
exports.useRecordingGetStorageUsage = useRecordingGetStorageUsage;
|
|
48294
48256
|
exports.useRecordingListOpsLog = useRecordingListOpsLog;
|
|
48295
48257
|
exports.useRecordingLocateSegment = useRecordingLocateSegment;
|
|
48296
48258
|
exports.useRecordingPruneFootage = useRecordingPruneFootage;
|
|
48297
48259
|
exports.useRecordingReadSegmentBytes = useRecordingReadSegmentBytes;
|
|
48260
|
+
exports.useRecordingRelocateFootage = useRecordingRelocateFootage;
|
|
48261
|
+
exports.useRecordingRenderClip = useRecordingRenderClip;
|
|
48262
|
+
exports.useRecordingRenderGif = useRecordingRenderGif;
|
|
48298
48263
|
exports.useRecordingRescanStorage = useRecordingRescanStorage;
|
|
48299
48264
|
exports.useRecordingSetDeviceConfig = useRecordingSetDeviceConfig;
|
|
48300
48265
|
exports.useRemoteComponent = useRemoteComponent;
|
|
@@ -48377,6 +48342,7 @@ exports.useStreamBrokerPullAudioChunks = useStreamBrokerPullAudioChunks;
|
|
|
48377
48342
|
exports.useStreamBrokerPullFrameHandles = useStreamBrokerPullFrameHandles;
|
|
48378
48343
|
exports.useStreamBrokerRegenerateRtspToken = useStreamBrokerRegenerateRtspToken;
|
|
48379
48344
|
exports.useStreamBrokerReleaseStreamWithCodec = useStreamBrokerReleaseStreamWithCodec;
|
|
48345
|
+
exports.useStreamBrokerRenderPreBufferClip = useStreamBrokerRenderPreBufferClip;
|
|
48380
48346
|
exports.useStreamBrokerRestartProfile = useStreamBrokerRestartProfile;
|
|
48381
48347
|
exports.useStreamBrokerRetractCameraStream = useStreamBrokerRetractCameraStream;
|
|
48382
48348
|
exports.useStreamBrokerSetPreBufferDuration = useStreamBrokerSetPreBufferDuration;
|