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