@camstack/addon-pipeline 1.2.275 → 1.2.276
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/recorder/index.js +77 -5
- package/dist/recorder/index.mjs +77 -5
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BrNGbvkl.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D0A4Ra4h.mjs} +1 -1
- package/dist/stream-broker/{hostInit-CSJt3rp0.mjs → hostInit-C8TnCvWj.mjs} +1 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
package/dist/recorder/index.js
CHANGED
|
@@ -13798,6 +13798,73 @@ function clusterFootageVisits(ranges, gapMs = require_dist.VISIT_MERGE_GAP_MS) {
|
|
|
13798
13798
|
visits.push(finishVisit(pieces, endMs));
|
|
13799
13799
|
return visits;
|
|
13800
13800
|
}
|
|
13801
|
+
/**
|
|
13802
|
+
* A visit per BURST OF EVENTS, clamped to the footage that exists behind it.
|
|
13803
|
+
*
|
|
13804
|
+
* ## Why this replaced clustering the availability
|
|
13805
|
+
*
|
|
13806
|
+
* {@link VISIT_MERGE_GAP_MS}'s own docblock states the claim: 30 s "glues
|
|
13807
|
+
* those intra-visit holes without joining visits hours apart". It was applied
|
|
13808
|
+
* to the wrong input. {@link clusterFootageVisits} clusters AVAILABILITY, and
|
|
13809
|
+
* availability only has gaps on an EVENTS-MODE camera, where the writer opens
|
|
13810
|
+
* and closes around activity. A camera that records continuously has none —
|
|
13811
|
+
* so the visit became the recording session.
|
|
13812
|
+
*
|
|
13813
|
+
* Measured on 592, 2026-09-21, 24 h: seven rows covering the whole day, one of
|
|
13814
|
+
* them 7 h 58 m. Clustering the same day's EVENTS at the same 30 s gives 46
|
|
13815
|
+
* visits overnight and 36 in the day, median 17 s, longest 99 s. The constant
|
|
13816
|
+
* was right; the input was not. No duration cap: it would split by the clock
|
|
13817
|
+
* rather than by what happened, and nothing here needs splitting that way.
|
|
13818
|
+
*
|
|
13819
|
+
* ## What the footage is still for
|
|
13820
|
+
*
|
|
13821
|
+
* It CLAMPS, it does not define. A visit keeps only the pieces it actually
|
|
13822
|
+
* covers, because {@link stillInstantMs} takes the midpoint of the longest
|
|
13823
|
+
* piece — handed the whole session's range it would put the still hours from
|
|
13824
|
+
* the event. A burst with no footage behind it is dropped: an event the
|
|
13825
|
+
* recorder never kept is not a clip, and listing it is a row that opens onto
|
|
13826
|
+
* nothing.
|
|
13827
|
+
*/
|
|
13828
|
+
function clusterEventVisits(events, ranges, gapMs, pad) {
|
|
13829
|
+
if (events.length === 0 || ranges.length === 0) return [];
|
|
13830
|
+
const times = [...events].map((e) => e.timestamp).toSorted((a, b) => a - b);
|
|
13831
|
+
const sortedRanges = [...ranges].toSorted((a, b) => a.startMs - b.startMs);
|
|
13832
|
+
const bursts = [];
|
|
13833
|
+
let first = times[0];
|
|
13834
|
+
let last = times[0];
|
|
13835
|
+
for (let i = 1; i < times.length; i++) {
|
|
13836
|
+
const t = times[i];
|
|
13837
|
+
if (t - last <= gapMs) {
|
|
13838
|
+
last = t;
|
|
13839
|
+
continue;
|
|
13840
|
+
}
|
|
13841
|
+
bursts.push({
|
|
13842
|
+
startMs: first - pad.preMs,
|
|
13843
|
+
endMs: last + pad.postMs
|
|
13844
|
+
});
|
|
13845
|
+
first = t;
|
|
13846
|
+
last = t;
|
|
13847
|
+
}
|
|
13848
|
+
bursts.push({
|
|
13849
|
+
startMs: first - pad.preMs,
|
|
13850
|
+
endMs: last + pad.postMs
|
|
13851
|
+
});
|
|
13852
|
+
const visits = [];
|
|
13853
|
+
for (const burst of bursts) {
|
|
13854
|
+
const pieces = [];
|
|
13855
|
+
for (const range of sortedRanges) {
|
|
13856
|
+
const startMs = Math.max(range.startMs, burst.startMs);
|
|
13857
|
+
const endMs = Math.min(range.endMs, burst.endMs);
|
|
13858
|
+
if (endMs > startMs) pieces.push({
|
|
13859
|
+
startMs,
|
|
13860
|
+
endMs
|
|
13861
|
+
});
|
|
13862
|
+
}
|
|
13863
|
+
if (pieces.length === 0) continue;
|
|
13864
|
+
visits.push(finishVisit(pieces, pieces[pieces.length - 1].endMs));
|
|
13865
|
+
}
|
|
13866
|
+
return visits;
|
|
13867
|
+
}
|
|
13801
13868
|
function finishVisit(pieces, endMs) {
|
|
13802
13869
|
const holes = [];
|
|
13803
13870
|
for (let i = 1; i < pieces.length; i++) {
|
|
@@ -13972,13 +14039,17 @@ function eventsToClips(events, pad, vouched) {
|
|
|
13972
14039
|
...events.audio.map((e) => make(e, "audio"))
|
|
13973
14040
|
].toSorted((a, b) => b.timeRange.startMs - a.timeRange.startMs);
|
|
13974
14041
|
}
|
|
13975
|
-
/** Every
|
|
13976
|
-
function
|
|
14042
|
+
/** Every event in the three buckets, in the order the composers tag them. */
|
|
14043
|
+
function allEvents(events) {
|
|
13977
14044
|
return [
|
|
13978
14045
|
...events.motion,
|
|
13979
14046
|
...events.object,
|
|
13980
14047
|
...events.audio
|
|
13981
|
-
]
|
|
14048
|
+
];
|
|
14049
|
+
}
|
|
14050
|
+
/** Every id in the three buckets, in the order the composers tag them. */
|
|
14051
|
+
function allEventIds(events) {
|
|
14052
|
+
return allEvents(events).map((e) => e.id);
|
|
13982
14053
|
}
|
|
13983
14054
|
/**
|
|
13984
14055
|
* Resolve the event ids whose `mediaUrl` is backed by real bytes, mirroring
|
|
@@ -14095,11 +14166,12 @@ function createVideoclipsProvider(deps) {
|
|
|
14095
14166
|
}
|
|
14096
14167
|
let clips = eventsToClips(events, deps.defaultPad, vouched);
|
|
14097
14168
|
if (deps.fetchAvailability) try {
|
|
14098
|
-
|
|
14169
|
+
const ranges = await deps.fetchAvailability({
|
|
14099
14170
|
deviceId,
|
|
14100
14171
|
since: readSince,
|
|
14101
14172
|
until
|
|
14102
|
-
})
|
|
14173
|
+
});
|
|
14174
|
+
clips = visitsToClips(clusterEventVisits(allEvents(events), ranges, require_dist.VISIT_MERGE_GAP_MS, deps.defaultPad), events, deps.defaultPad, vouched);
|
|
14103
14175
|
} catch (err) {
|
|
14104
14176
|
deps.logWarn?.("videoclips: availability read failed — listing unfiltered", {
|
|
14105
14177
|
deviceId,
|
package/dist/recorder/index.mjs
CHANGED
|
@@ -13791,6 +13791,73 @@ function clusterFootageVisits(ranges, gapMs = VISIT_MERGE_GAP_MS) {
|
|
|
13791
13791
|
visits.push(finishVisit(pieces, endMs));
|
|
13792
13792
|
return visits;
|
|
13793
13793
|
}
|
|
13794
|
+
/**
|
|
13795
|
+
* A visit per BURST OF EVENTS, clamped to the footage that exists behind it.
|
|
13796
|
+
*
|
|
13797
|
+
* ## Why this replaced clustering the availability
|
|
13798
|
+
*
|
|
13799
|
+
* {@link VISIT_MERGE_GAP_MS}'s own docblock states the claim: 30 s "glues
|
|
13800
|
+
* those intra-visit holes without joining visits hours apart". It was applied
|
|
13801
|
+
* to the wrong input. {@link clusterFootageVisits} clusters AVAILABILITY, and
|
|
13802
|
+
* availability only has gaps on an EVENTS-MODE camera, where the writer opens
|
|
13803
|
+
* and closes around activity. A camera that records continuously has none —
|
|
13804
|
+
* so the visit became the recording session.
|
|
13805
|
+
*
|
|
13806
|
+
* Measured on 592, 2026-09-21, 24 h: seven rows covering the whole day, one of
|
|
13807
|
+
* them 7 h 58 m. Clustering the same day's EVENTS at the same 30 s gives 46
|
|
13808
|
+
* visits overnight and 36 in the day, median 17 s, longest 99 s. The constant
|
|
13809
|
+
* was right; the input was not. No duration cap: it would split by the clock
|
|
13810
|
+
* rather than by what happened, and nothing here needs splitting that way.
|
|
13811
|
+
*
|
|
13812
|
+
* ## What the footage is still for
|
|
13813
|
+
*
|
|
13814
|
+
* It CLAMPS, it does not define. A visit keeps only the pieces it actually
|
|
13815
|
+
* covers, because {@link stillInstantMs} takes the midpoint of the longest
|
|
13816
|
+
* piece — handed the whole session's range it would put the still hours from
|
|
13817
|
+
* the event. A burst with no footage behind it is dropped: an event the
|
|
13818
|
+
* recorder never kept is not a clip, and listing it is a row that opens onto
|
|
13819
|
+
* nothing.
|
|
13820
|
+
*/
|
|
13821
|
+
function clusterEventVisits(events, ranges, gapMs, pad) {
|
|
13822
|
+
if (events.length === 0 || ranges.length === 0) return [];
|
|
13823
|
+
const times = [...events].map((e) => e.timestamp).toSorted((a, b) => a - b);
|
|
13824
|
+
const sortedRanges = [...ranges].toSorted((a, b) => a.startMs - b.startMs);
|
|
13825
|
+
const bursts = [];
|
|
13826
|
+
let first = times[0];
|
|
13827
|
+
let last = times[0];
|
|
13828
|
+
for (let i = 1; i < times.length; i++) {
|
|
13829
|
+
const t = times[i];
|
|
13830
|
+
if (t - last <= gapMs) {
|
|
13831
|
+
last = t;
|
|
13832
|
+
continue;
|
|
13833
|
+
}
|
|
13834
|
+
bursts.push({
|
|
13835
|
+
startMs: first - pad.preMs,
|
|
13836
|
+
endMs: last + pad.postMs
|
|
13837
|
+
});
|
|
13838
|
+
first = t;
|
|
13839
|
+
last = t;
|
|
13840
|
+
}
|
|
13841
|
+
bursts.push({
|
|
13842
|
+
startMs: first - pad.preMs,
|
|
13843
|
+
endMs: last + pad.postMs
|
|
13844
|
+
});
|
|
13845
|
+
const visits = [];
|
|
13846
|
+
for (const burst of bursts) {
|
|
13847
|
+
const pieces = [];
|
|
13848
|
+
for (const range of sortedRanges) {
|
|
13849
|
+
const startMs = Math.max(range.startMs, burst.startMs);
|
|
13850
|
+
const endMs = Math.min(range.endMs, burst.endMs);
|
|
13851
|
+
if (endMs > startMs) pieces.push({
|
|
13852
|
+
startMs,
|
|
13853
|
+
endMs
|
|
13854
|
+
});
|
|
13855
|
+
}
|
|
13856
|
+
if (pieces.length === 0) continue;
|
|
13857
|
+
visits.push(finishVisit(pieces, pieces[pieces.length - 1].endMs));
|
|
13858
|
+
}
|
|
13859
|
+
return visits;
|
|
13860
|
+
}
|
|
13794
13861
|
function finishVisit(pieces, endMs) {
|
|
13795
13862
|
const holes = [];
|
|
13796
13863
|
for (let i = 1; i < pieces.length; i++) {
|
|
@@ -13965,13 +14032,17 @@ function eventsToClips(events, pad, vouched) {
|
|
|
13965
14032
|
...events.audio.map((e) => make(e, "audio"))
|
|
13966
14033
|
].toSorted((a, b) => b.timeRange.startMs - a.timeRange.startMs);
|
|
13967
14034
|
}
|
|
13968
|
-
/** Every
|
|
13969
|
-
function
|
|
14035
|
+
/** Every event in the three buckets, in the order the composers tag them. */
|
|
14036
|
+
function allEvents(events) {
|
|
13970
14037
|
return [
|
|
13971
14038
|
...events.motion,
|
|
13972
14039
|
...events.object,
|
|
13973
14040
|
...events.audio
|
|
13974
|
-
]
|
|
14041
|
+
];
|
|
14042
|
+
}
|
|
14043
|
+
/** Every id in the three buckets, in the order the composers tag them. */
|
|
14044
|
+
function allEventIds(events) {
|
|
14045
|
+
return allEvents(events).map((e) => e.id);
|
|
13975
14046
|
}
|
|
13976
14047
|
/**
|
|
13977
14048
|
* Resolve the event ids whose `mediaUrl` is backed by real bytes, mirroring
|
|
@@ -14088,11 +14159,12 @@ function createVideoclipsProvider(deps) {
|
|
|
14088
14159
|
}
|
|
14089
14160
|
let clips = eventsToClips(events, deps.defaultPad, vouched);
|
|
14090
14161
|
if (deps.fetchAvailability) try {
|
|
14091
|
-
|
|
14162
|
+
const ranges = await deps.fetchAvailability({
|
|
14092
14163
|
deviceId,
|
|
14093
14164
|
since: readSince,
|
|
14094
14165
|
until
|
|
14095
|
-
})
|
|
14166
|
+
});
|
|
14167
|
+
clips = visitsToClips(clusterEventVisits(allEvents(events), ranges, VISIT_MERGE_GAP_MS, deps.defaultPad), events, deps.defaultPad, vouched);
|
|
14096
14168
|
} catch (err) {
|
|
14097
14169
|
deps.logWarn?.("videoclips: availability read failed — listing unfiltered", {
|
|
14098
14170
|
deviceId,
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D0A4Ra4h.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.276",
|
|
4
4
|
"description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|