@camstack/system 1.2.263 → 1.2.265
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/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/camera-grid/addon.d.ts +20 -0
- package/dist/builtins/camera-grid/grid-activity-mirror.d.ts +32 -0
- package/dist/builtins/camera-grid/grid-activity.d.ts +50 -0
- package/dist/builtins/camera-grid/grid-camera-device.d.ts +11 -1
- package/dist/builtins/camera-grid/grid-canvas.d.ts +83 -0
- package/dist/builtins/camera-grid/grid-compositor-child.d.ts +3 -0
- package/dist/builtins/camera-grid/grid-compositor-invocation.d.ts +19 -0
- package/dist/builtins/camera-grid/grid-compositor.d.ts +36 -0
- package/dist/builtins/camera-grid/grid-detection-mapping.d.ts +31 -0
- package/dist/builtins/camera-grid/grid-tile-feed.d.ts +38 -0
- package/dist/builtins/camera-grid/grid-tile-plan.d.ts +31 -0
- package/dist/builtins/camera-grid/index.js +1199 -232
- package/dist/builtins/camera-grid/index.mjs +1199 -232
- package/dist/builtins/camera-grid/silence-analysis.d.ts +7 -0
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-6hiXbrNY.js → dist-BbPssgI6.js} +85 -15
- package/dist/{dist-CQ7ItBIG.mjs → dist-Ck0rjYOQ.mjs} +72 -8
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{retired-settings-keys-CCXmaFTK.mjs → retired-settings-keys-BZpDuJVh.mjs} +1 -1
- package/dist/{retired-settings-keys-AmmGrCp1.js → retired-settings-keys-Dg_69cOY.js} +1 -1
- package/package.json +4 -1
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BbPssgI6.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { P as addonPagesCapability, Pn as EventCategory, an as errMsg, sn as BaseAddon } from "../../dist-Ck0rjYOQ.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BbPssgI6.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { I as addonWidgetsCapability,
|
|
1
|
+
import { I as addonWidgetsCapability, Pn as EventCategory, an as errMsg, sn as BaseAddon } from "../../dist-Ck0rjYOQ.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BbPssgI6.js");
|
|
7
7
|
//#region src/builtins/alerts/prune-policy.ts
|
|
8
8
|
/**
|
|
9
9
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as alertsCapability,
|
|
1
|
+
import { L as alertsCapability, Pn as EventCategory, an as errMsg, bn as createEvent, n as AlertSchema, sn as BaseAddon } from "../../dist-Ck0rjYOQ.mjs";
|
|
2
2
|
//#region src/builtins/alerts/prune-policy.ts
|
|
3
3
|
/**
|
|
4
4
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BbPssgI6.js");
|
|
7
7
|
//#region src/builtins/autotrack/autotrack-config.ts
|
|
8
8
|
var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
|
|
9
9
|
deadZoneFraction: .5,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as bareAddonId,
|
|
1
|
+
import { B as bareAddonId, En as isEvent, It as ptzAutotrackCapability, Pn as EventCategory, an as errMsg, h as FIRST_LEVEL_MACRO_CLASSES, sn as BaseAddon } from "../../dist-Ck0rjYOQ.mjs";
|
|
2
2
|
//#region src/builtins/autotrack/autotrack-config.ts
|
|
3
3
|
var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
|
|
4
4
|
deadZoneFraction: .5,
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BbPssgI6.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
let buffer = require("buffer");
|
|
9
9
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Dt as mayWriteToLocation,
|
|
1
|
+
import { Dt as mayWriteToLocation, Pn as EventCategory, Zt as storageOccupancyCapability, sn as BaseAddon, z as backupCapability } from "../../dist-Ck0rjYOQ.mjs";
|
|
2
2
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
3
3
|
import { Buffer as Buffer$1 } from "buffer";
|
|
4
4
|
import * as fsp from "node:fs/promises";
|
|
@@ -23,6 +23,7 @@ export declare class CameraGridAddon extends BaseAddon<CameraGridAddonConfig> {
|
|
|
23
23
|
* — that only changes when an operator changes it.
|
|
24
24
|
*/
|
|
25
25
|
private readonly planByInstanceId;
|
|
26
|
+
private activity;
|
|
26
27
|
private snapshots;
|
|
27
28
|
private frameSampler;
|
|
28
29
|
private ffmpegBinaryPath;
|
|
@@ -43,6 +44,25 @@ export declare class CameraGridAddon extends BaseAddon<CameraGridAddonConfig> {
|
|
|
43
44
|
private replaceTombstones;
|
|
44
45
|
private reconcile;
|
|
45
46
|
private silenceAnalysis;
|
|
47
|
+
/**
|
|
48
|
+
* Listen to the SOURCES, so the grid can say the same things about itself.
|
|
49
|
+
*
|
|
50
|
+
* Filtered by source id inside the handler rather than by subscribing per
|
|
51
|
+
* camera: the bus filter is per category, the set of sources changes whenever
|
|
52
|
+
* an operator drags a cell, and a per-camera subscription would have to be
|
|
53
|
+
* torn down and rebuilt on every layout save.
|
|
54
|
+
*
|
|
55
|
+
* Every handler is TOTAL — a malformed payload is ignored, never thrown out
|
|
56
|
+
* of. These are telemetry events on a shared bus (D8/D11), and a throw here
|
|
57
|
+
* would take the grid's forwarding down for every grid because one camera
|
|
58
|
+
* emitted something unexpected.
|
|
59
|
+
*/
|
|
60
|
+
private subscribeSourceActivity;
|
|
61
|
+
/**
|
|
62
|
+
* Which grids show this camera. Empty for the overwhelming majority of
|
|
63
|
+
* events, which is why it is the FIRST thing every handler asks.
|
|
64
|
+
*/
|
|
65
|
+
private gridsForSource;
|
|
46
66
|
/**
|
|
47
67
|
* Which stream each grid reads each of its sources at, asked of the BROKER.
|
|
48
68
|
*
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** The grid's own motion answer, in the shape the `motion` cap publishes. */
|
|
2
|
+
export interface GridMotionState {
|
|
3
|
+
readonly detected: boolean;
|
|
4
|
+
/** Ms epoch of the most recent rising edge on ANY source. Null if never. */
|
|
5
|
+
readonly lastDetectedAt: number | null;
|
|
6
|
+
}
|
|
7
|
+
export declare class GridActivityMirror {
|
|
8
|
+
private readonly motion;
|
|
9
|
+
private readonly audio;
|
|
10
|
+
private lastDetectedAt;
|
|
11
|
+
/** One source's motion push. `detected: false` is as important as `true`. */
|
|
12
|
+
noteMotion(sourceDeviceId: number, detected: boolean, atMs: number): void;
|
|
13
|
+
/** One source's audio window. `dbfs` is the level, on the analyzer's scale. */
|
|
14
|
+
noteAudio(sourceDeviceId: number, dbfs: number, atMs: number): void;
|
|
15
|
+
/**
|
|
16
|
+
* Drop every source this grid no longer composes.
|
|
17
|
+
*
|
|
18
|
+
* A cell removed while its camera was moving would otherwise hold the grid
|
|
19
|
+
* lit until the expiry, for a picture the source is not even in.
|
|
20
|
+
*/
|
|
21
|
+
keepOnly(sourceDeviceIds: readonly number[]): void;
|
|
22
|
+
/** At least one source moving, as of `nowMs`. */
|
|
23
|
+
motionAt(nowMs: number): GridMotionState;
|
|
24
|
+
/**
|
|
25
|
+
* The loudest CURRENT reading, or `null` when nothing fresh has been heard.
|
|
26
|
+
*
|
|
27
|
+
* Null and not `-Infinity`: "nobody has reported a level" is a different
|
|
28
|
+
* answer from "it is silent", and a number here would have the viewer draw a
|
|
29
|
+
* level for a grid whose sources are not being analysed at all.
|
|
30
|
+
*/
|
|
31
|
+
loudestAt(nowMs: number): number | null;
|
|
32
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { FrameResult, IEventBus, IScopedLogger } from '@camstack/types';
|
|
2
|
+
import { GridMotionState } from './grid-activity-mirror.js';
|
|
3
|
+
import { GridMappingCell } from './grid-detection-mapping.js';
|
|
4
|
+
/** Everything this needs to know about one grid, re-read on every event. */
|
|
5
|
+
export interface GridActivityTarget {
|
|
6
|
+
readonly deviceId: number;
|
|
7
|
+
readonly canvas: {
|
|
8
|
+
readonly width: number;
|
|
9
|
+
readonly height: number;
|
|
10
|
+
};
|
|
11
|
+
/** Every cell this source appears in. Empty ⇒ the grid does not show it. */
|
|
12
|
+
cellsForSource(sourceDeviceId: number): readonly GridMappingCell[];
|
|
13
|
+
/** Every source this grid composes, for the mirror's housekeeping. */
|
|
14
|
+
readonly sourceDeviceIds: readonly number[];
|
|
15
|
+
}
|
|
16
|
+
export interface GridActivityDeps {
|
|
17
|
+
readonly eventBus: IEventBus;
|
|
18
|
+
readonly logger: IScopedLogger;
|
|
19
|
+
readonly addonId: string;
|
|
20
|
+
/** The grids that contain this source, or empty. */
|
|
21
|
+
gridsForSource(sourceDeviceId: number): readonly GridActivityTarget[];
|
|
22
|
+
readonly now: () => number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* One mirror per grid, and the emission that follows an update.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately NOT one mirror per source: the question is always "is THIS grid
|
|
28
|
+
* lit", and a source shared by two grids answers it twice, differently, because
|
|
29
|
+
* the other cells differ.
|
|
30
|
+
*/
|
|
31
|
+
export declare class GridActivityForwarder {
|
|
32
|
+
private readonly deps;
|
|
33
|
+
private readonly mirrors;
|
|
34
|
+
constructor(deps: GridActivityDeps);
|
|
35
|
+
/** A source camera's motion push. */
|
|
36
|
+
onSourceMotion(sourceDeviceId: number, detected: boolean, atMs: number): void;
|
|
37
|
+
/** A source camera's audio window. */
|
|
38
|
+
onSourceAudio(sourceDeviceId: number, dbfs: number, atMs: number): void;
|
|
39
|
+
/** A source camera's detection frame, with its boxes in SOURCE pixels. */
|
|
40
|
+
onSourceDetections(frame: FrameResult): void;
|
|
41
|
+
/** The grid's own motion answer, for `motion.getStatus`. */
|
|
42
|
+
motionStatusFor(deviceId: number, nowMs: number): GridMotionState;
|
|
43
|
+
/** Drop the mirror of a grid that no longer exists. */
|
|
44
|
+
forget(deviceId: number): void;
|
|
45
|
+
private mirrorFor;
|
|
46
|
+
private source;
|
|
47
|
+
private emitMotion;
|
|
48
|
+
private emitAudio;
|
|
49
|
+
private emitDetections;
|
|
50
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseDevice, CamStreamDescriptor, DeviceContext, DeviceFeature, GridLayoutPatch, GridLayoutView } from '@camstack/types';
|
|
1
|
+
import { BaseDevice, CamStreamDescriptor, DeviceContext, DeviceFeature, GridLayoutPatch, GridLayoutView, MotionStatus } from '@camstack/types';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { GridSnapshotImage } from './grid-snapshot.js';
|
|
4
4
|
declare const gridCameraSchema: z.ZodObject<{
|
|
@@ -21,6 +21,16 @@ export interface GridCameraRuntime {
|
|
|
21
21
|
saveLayout(patch: GridLayoutPatch): Promise<GridLayoutView>;
|
|
22
22
|
/** A frame of the composition, or `null`. Never starts one. */
|
|
23
23
|
getSnapshot(deviceId: number): Promise<GridSnapshotImage | null>;
|
|
24
|
+
/**
|
|
25
|
+
* This grid's own motion, derived from its sources (`grid-activity-mirror`).
|
|
26
|
+
*
|
|
27
|
+
* The device answers `motion.getStatus` from it because the VIEWER polls that
|
|
28
|
+
* as its backstop — it subscribes to `motion.on-motion-changed` and polls the
|
|
29
|
+
* status in case an event was missed. A grid that emitted the event but could
|
|
30
|
+
* not answer the poll would light up and then, on any dropped edge, be
|
|
31
|
+
* unrecoverably dark until the next one.
|
|
32
|
+
*/
|
|
33
|
+
motionStatus(deviceId: number): MotionStatus;
|
|
24
34
|
}
|
|
25
35
|
export declare function installGridCameraRuntime(next: GridCameraRuntime | null): void;
|
|
26
36
|
export declare class GridCameraDevice extends BaseDevice<typeof gridCameraSchema> {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The composed picture as a buffer this addon owns.
|
|
3
|
+
*
|
|
4
|
+
* ## Why a canvas instead of ffmpeg's `overlay`
|
|
5
|
+
*
|
|
6
|
+
* `overlay` pairs its inputs BY PTS, and ffmpeg gives every input pts 0 at ITS
|
|
7
|
+
* OWN first frame. Two sources that joined at different moments therefore
|
|
8
|
+
* compose different moments, for the life of the child, with nothing to correct
|
|
9
|
+
* it. Measured on this hub at 4-5 s between two tiles, drifting over hours as
|
|
10
|
+
* the sources' key-frame phases slide through each other (0.5 s at 13:05, 3 s
|
|
11
|
+
* at 13:50 on the same pair, 2026-09-18).
|
|
12
|
+
*
|
|
13
|
+
* It is not reachable from a flag. `-use_wallclock_as_timestamps` re-times by
|
|
14
|
+
* ARRIVAL and wrecked the pacing instead (ten stalls over a second in an 18 s
|
|
15
|
+
* run, against none). `-copyts` changes nothing, because the broker re-bases
|
|
16
|
+
* each session — both restreams report `pts_time:0` — so there is no common
|
|
17
|
+
* timeline to trim against. Serving each dial from the newest key frame it
|
|
18
|
+
* already holds does not help either: those key frames are of different ages,
|
|
19
|
+
* and pts 0 still lands on whatever arrives first.
|
|
20
|
+
*
|
|
21
|
+
* A canvas has no pairing to get wrong. When the output tick fires, each
|
|
22
|
+
* rectangle holds whatever arrived last from its source, and the OUTPUT owns
|
|
23
|
+
* the clock. The gap is zero by construction rather than by tuning, and a cell
|
|
24
|
+
* that has produced nothing yet is simply black — so the picture appears at
|
|
25
|
+
* once instead of waiting for the slowest source's first key frame.
|
|
26
|
+
*
|
|
27
|
+
* ## Why yuv420p, and why everything here must be even
|
|
28
|
+
*
|
|
29
|
+
* yuv420p is the one pixel format every consumer of a CamStack profile reads,
|
|
30
|
+
* and a plane copy in it is a strided memcpy — no arithmetic per pixel, which
|
|
31
|
+
* is the whole reason compositing can be cheaper than the filter graph it
|
|
32
|
+
* replaces.
|
|
33
|
+
*
|
|
34
|
+
* Its chroma planes are half resolution in BOTH axes. So an odd origin or an
|
|
35
|
+
* odd size would put a tile's chroma half a sample off its luma, and the result
|
|
36
|
+
* is a tile that is subtly TINTED rather than visibly broken — the kind of
|
|
37
|
+
* fault that survives review. Every rectangle is therefore required to be even,
|
|
38
|
+
* and a blit that is not is refused rather than rounded: rounding would move a
|
|
39
|
+
* cell the operator placed.
|
|
40
|
+
*/
|
|
41
|
+
export interface PixelSize {
|
|
42
|
+
readonly width: number;
|
|
43
|
+
readonly height: number;
|
|
44
|
+
}
|
|
45
|
+
export interface PixelOrigin {
|
|
46
|
+
readonly x: number;
|
|
47
|
+
readonly y: number;
|
|
48
|
+
}
|
|
49
|
+
/** Bytes one yuv420p frame of this size occupies. */
|
|
50
|
+
export declare function yuv420pSize(width: number, height: number): number;
|
|
51
|
+
/**
|
|
52
|
+
* Round DOWN to even.
|
|
53
|
+
*
|
|
54
|
+
* Down and not nearest: a tile that rounded UP would claim a row of samples the
|
|
55
|
+
* decoder was never asked to produce, and the copy would either overrun or be
|
|
56
|
+
* refused at the moment the picture is wanted.
|
|
57
|
+
*/
|
|
58
|
+
export declare function evenDown(value: number): number;
|
|
59
|
+
export declare class GridCanvas {
|
|
60
|
+
readonly width: number;
|
|
61
|
+
readonly height: number;
|
|
62
|
+
readonly buffer: Buffer;
|
|
63
|
+
private readonly lumaBytes;
|
|
64
|
+
private readonly chromaBytes;
|
|
65
|
+
constructor(width: number, height: number);
|
|
66
|
+
/**
|
|
67
|
+
* Legible black — Y 0, chroma 128 (neutral).
|
|
68
|
+
*
|
|
69
|
+
* `Buffer.alloc` already zeroes, and zeroed CHROMA is not black: it is a
|
|
70
|
+
* strong green. A cell nobody has filled yet has to look like an empty cell,
|
|
71
|
+
* not like a fault.
|
|
72
|
+
*/
|
|
73
|
+
clear(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Copy one decoded tile in at `origin`.
|
|
76
|
+
*
|
|
77
|
+
* Returns false — never throws, never writes a partial tile — when the tile
|
|
78
|
+
* does not fit, is the wrong size for the dimensions it claims, or sits on an
|
|
79
|
+
* odd boundary. This runs on the output tick, so a bad frame from one source
|
|
80
|
+
* must cost that source's rectangle and nothing else.
|
|
81
|
+
*/
|
|
82
|
+
blit(tile: Buffer, size: PixelSize, origin: PixelOrigin): boolean;
|
|
83
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { GridChildContext } from './grid-child.js';
|
|
2
|
+
import { GridSourceHandle, GridStreamChild, GridStreamPlan } from './grid-stream-session.js';
|
|
3
|
+
export declare function startGridCompositorChild(ctx: GridChildContext, plan: GridStreamPlan, sources: readonly GridSourceHandle[], onEnded: (reason: string) => void): Promise<GridStreamChild>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FfmpegInvocation, FfmpegVideoEncoderId } from '@camstack/types';
|
|
2
|
+
import { GridTile } from './grid-tile-plan.js';
|
|
3
|
+
import { PixelSize } from './grid-canvas.js';
|
|
4
|
+
export interface GridTileDecoderInput {
|
|
5
|
+
readonly tile: GridTile;
|
|
6
|
+
/** A broker restream URL, acquired through `streamBroker.getStreamWithCodec`. */
|
|
7
|
+
readonly url: string;
|
|
8
|
+
readonly fps: number;
|
|
9
|
+
}
|
|
10
|
+
/** One tile's decoder: dial in, raw yuv420p frames out. */
|
|
11
|
+
export declare function gridTileDecoderInvocation(input: GridTileDecoderInput): FfmpegInvocation;
|
|
12
|
+
export interface GridCanvasEncoderInput {
|
|
13
|
+
readonly canvas: PixelSize;
|
|
14
|
+
readonly fps: number;
|
|
15
|
+
readonly encoder: FfmpegVideoEncoderId;
|
|
16
|
+
readonly bitrateKbps: number;
|
|
17
|
+
}
|
|
18
|
+
/** The canvas encoder: raw frames on stdin, one compressed stream out. */
|
|
19
|
+
export declare function gridCanvasEncoderInvocation(input: GridCanvasEncoderInput): FfmpegInvocation;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { GridTilePlan } from './grid-tile-plan.js';
|
|
2
|
+
export interface GridCompositorDeps {
|
|
3
|
+
readonly plan: GridTilePlan;
|
|
4
|
+
/**
|
|
5
|
+
* Hand one composed frame to the encoder. `false` means the sink is not
|
|
6
|
+
* draining — the caller slows down rather than queueing raw frames, which at
|
|
7
|
+
* 345 KB each is how a stalled encoder becomes an OOM.
|
|
8
|
+
*/
|
|
9
|
+
readonly write: (frame: Buffer) => boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare class GridCompositor {
|
|
12
|
+
private readonly deps;
|
|
13
|
+
private readonly canvas;
|
|
14
|
+
/** Keyed by tile id — unique per CELL, because one camera can hold two. */
|
|
15
|
+
private readonly placed;
|
|
16
|
+
private closed;
|
|
17
|
+
constructor(deps: GridCompositorDeps);
|
|
18
|
+
/**
|
|
19
|
+
* Raw bytes from one decoder output.
|
|
20
|
+
*
|
|
21
|
+
* A tile this composition has no rectangle for is IGNORED, never thrown on: a
|
|
22
|
+
* child that outlives a layout change would otherwise take the tick down with
|
|
23
|
+
* it, and the tick is what every other cell depends on.
|
|
24
|
+
*/
|
|
25
|
+
onTile(tileId: string, chunk: Buffer): void;
|
|
26
|
+
/**
|
|
27
|
+
* Compose and emit one frame. Returns what the sink said about back-pressure.
|
|
28
|
+
*
|
|
29
|
+
* Every tile is redrawn, fresh or not: a tile that produced nothing this tick
|
|
30
|
+
* is a still picture, not a hole, and clearing it would make a late source
|
|
31
|
+
* flicker. The canvas is never cleared between ticks for the same reason.
|
|
32
|
+
*/
|
|
33
|
+
tick(): boolean;
|
|
34
|
+
/** After this, a tick emits nothing — a dead child must not keep writing. */
|
|
35
|
+
close(): void;
|
|
36
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { DetectionBoundingBox } from '@camstack/types';
|
|
2
|
+
import { NormalizedRect } from './grid-filter-graph.js';
|
|
3
|
+
/** One place a source appears: the part taken, and where that part lands. */
|
|
4
|
+
export interface GridMappingCell {
|
|
5
|
+
/** The part of the source this cell shows, normalized against the SOURCE. */
|
|
6
|
+
readonly source: NormalizedRect;
|
|
7
|
+
/** Where it lands, normalized against the CANVAS. */
|
|
8
|
+
readonly cell: NormalizedRect;
|
|
9
|
+
}
|
|
10
|
+
export interface GridFrameSize {
|
|
11
|
+
readonly width: number;
|
|
12
|
+
readonly height: number;
|
|
13
|
+
}
|
|
14
|
+
export interface MapDetectionInput {
|
|
15
|
+
/** The detection's box, in ABSOLUTE pixels of the source frame. */
|
|
16
|
+
readonly bbox: DetectionBoundingBox;
|
|
17
|
+
/** The frame those pixels are measured against (`FrameResult.width/height`). */
|
|
18
|
+
readonly sourceFrame: GridFrameSize;
|
|
19
|
+
/** The composed canvas the result is measured against. */
|
|
20
|
+
readonly canvas: GridFrameSize;
|
|
21
|
+
/** Every cell this source appears in. */
|
|
22
|
+
readonly cells: readonly GridMappingCell[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Where this detection lands on the canvas — once per cell that shows it.
|
|
26
|
+
*
|
|
27
|
+
* Empty is a real answer: the subject is in a part of the source this grid does
|
|
28
|
+
* not display, or the frame arrived with no size to measure against. Neither is
|
|
29
|
+
* a reason to guess.
|
|
30
|
+
*/
|
|
31
|
+
export declare function mapDetectionToCanvas(input: MapDetectionInput): readonly DetectionBoundingBox[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One source's decoded tiles, of which only the LATEST is ever wanted.
|
|
3
|
+
*
|
|
4
|
+
* ## Why dropping is the point, not a compromise
|
|
5
|
+
*
|
|
6
|
+
* ffmpeg's `overlay` consumes one frame per input per output frame, so an input
|
|
7
|
+
* that got ahead of another stays ahead for the life of the child — that is the
|
|
8
|
+
* tile gap. A canvas asks a different question: "what does this camera look
|
|
9
|
+
* like NOW". Everything behind the newest frame is not a backlog to work
|
|
10
|
+
* through, it is history, and dropping it is what keeps every tile at the same
|
|
11
|
+
* moment however unevenly their frames arrive.
|
|
12
|
+
*
|
|
13
|
+
* It is also the only bound on memory here. A decoder child that outruns the
|
|
14
|
+
* output tick — or a tick that stalls — would otherwise grow an unbounded queue
|
|
15
|
+
* of raw frames, and a raw frame is 345 KB at 640x360.
|
|
16
|
+
*
|
|
17
|
+
* ## The last frame is HELD
|
|
18
|
+
*
|
|
19
|
+
* A tick that finds nothing new redraws what was there. A tile must not blink
|
|
20
|
+
* because a source was 40 ms late, and a source that has genuinely stopped is a
|
|
21
|
+
* still picture — which is legible, and which the snapshot path already treats
|
|
22
|
+
* as the honest answer for a camera that is not sending.
|
|
23
|
+
*/
|
|
24
|
+
export declare class GridTileFeed {
|
|
25
|
+
private readonly frameBytes;
|
|
26
|
+
private partial;
|
|
27
|
+
private newest;
|
|
28
|
+
private fresh;
|
|
29
|
+
constructor(frameBytes: number);
|
|
30
|
+
/** Feed bytes from the decoder. Any whole frames inside replace the newest. */
|
|
31
|
+
push(chunk: Buffer): void;
|
|
32
|
+
/** The newest whole frame, or `null` while none has arrived. */
|
|
33
|
+
latest(): Buffer | null;
|
|
34
|
+
/** Has a new frame arrived since the last {@link latest}? */
|
|
35
|
+
hasFresh(): boolean;
|
|
36
|
+
/** For the bound in the spec: one frame plus at most a partial one. */
|
|
37
|
+
bufferedBytes(): number;
|
|
38
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PixelOrigin, PixelSize } from './grid-canvas.js';
|
|
2
|
+
import { NormalizedRect } from './grid-filter-graph.js';
|
|
3
|
+
/** The one pixel format the canvas is in, so a blit is a copy and not a convert. */
|
|
4
|
+
export declare const TILE_PIXEL_FORMAT = "yuv420p";
|
|
5
|
+
export interface GridTilePlanCell {
|
|
6
|
+
readonly deviceId: number;
|
|
7
|
+
/** The part of the source to take, normalized against the SOURCE. */
|
|
8
|
+
readonly source: NormalizedRect;
|
|
9
|
+
/** Where it lands, normalized against the CANVAS. */
|
|
10
|
+
readonly cell: NormalizedRect;
|
|
11
|
+
}
|
|
12
|
+
export interface GridTilePlanInput {
|
|
13
|
+
readonly canvas: PixelSize;
|
|
14
|
+
readonly cells: readonly GridTilePlanCell[];
|
|
15
|
+
}
|
|
16
|
+
export interface GridTile {
|
|
17
|
+
/** Unique within this composition — the key its frames arrive under. */
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly deviceId: number;
|
|
20
|
+
readonly size: PixelSize;
|
|
21
|
+
readonly origin: PixelOrigin;
|
|
22
|
+
/** Bytes one frame of this tile occupies — the feed's frame length. */
|
|
23
|
+
readonly frameBytes: number;
|
|
24
|
+
/** The `-vf` chain its decoder runs: crop (when cropped), scale, format. */
|
|
25
|
+
readonly filter: string;
|
|
26
|
+
}
|
|
27
|
+
export interface GridTilePlan {
|
|
28
|
+
readonly canvas: PixelSize;
|
|
29
|
+
readonly tiles: readonly GridTile[];
|
|
30
|
+
}
|
|
31
|
+
export declare function buildGridTilePlan(input: GridTilePlanInput): GridTilePlan;
|