@camstack/addon-post-analysis 0.1.10 → 0.1.12

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.
Files changed (34) hide show
  1. package/assets/icon.svg +7 -0
  2. package/dist/embedding-encoder/index.js +23 -17
  3. package/dist/embedding-encoder/index.js.map +1 -1
  4. package/dist/embedding-encoder/index.mjs +18 -12
  5. package/dist/embedding-encoder/index.mjs.map +1 -1
  6. package/dist/enrichment-engine/index.js +46 -47
  7. package/dist/enrichment-engine/index.js.map +1 -1
  8. package/dist/enrichment-engine/index.mjs +46 -47
  9. package/dist/enrichment-engine/index.mjs.map +1 -1
  10. package/dist/index-BJKSB953.js +13565 -0
  11. package/dist/index-BJKSB953.js.map +1 -0
  12. package/dist/index-BThK2F-p.mjs +13566 -0
  13. package/dist/index-BThK2F-p.mjs.map +1 -0
  14. package/dist/pipeline-analytics/@mf-types.zip +0 -0
  15. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-ChoHjdk6.mjs +17 -0
  16. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-B9D0yX32.mjs +15 -0
  17. package/dist/pipeline-analytics/_stub.js +1 -1
  18. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-SishfP0w.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Vw8HvV_Q.mjs} +6 -6
  19. package/dist/pipeline-analytics/{hostInit-BrI-2jV9.mjs → hostInit-PQYMQJ_C.mjs} +6 -6
  20. package/dist/pipeline-analytics/{index-BWpyiREe.mjs → index-BSmxqDqD.mjs} +67 -44
  21. package/dist/pipeline-analytics/{index-BQ_NNiX0.mjs → index-Bpv0NSqI.mjs} +5414 -5170
  22. package/dist/pipeline-analytics/{index-ClZbgOhl.mjs → index-D7qTzYFz.mjs} +4249 -4164
  23. package/dist/pipeline-analytics/index.js +21 -21
  24. package/dist/pipeline-analytics/index.mjs +1 -1
  25. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  26. package/dist/recording/index.js +5 -5
  27. package/dist/recording/index.mjs +2 -2
  28. package/dist/{recording-coordinator-DuP3BUTV.mjs → recording-coordinator-CvJtVs3m.mjs} +4 -4
  29. package/dist/{recording-coordinator-DuP3BUTV.mjs.map → recording-coordinator-CvJtVs3m.mjs.map} +1 -1
  30. package/dist/{recording-coordinator-C2sATEhe.js → recording-coordinator-DH1gmm5G.js} +13 -13
  31. package/dist/{recording-coordinator-C2sATEhe.js.map → recording-coordinator-DH1gmm5G.js.map} +1 -1
  32. package/package.json +15 -6
  33. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-ezH__dL2.mjs +0 -17
  34. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-BMHQXgd2.mjs +0 -15
@@ -22,7 +22,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  mod
23
23
  ));
24
24
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
25
- const types = require("@camstack/types");
25
+ const index = require("./index-BJKSB953.js");
26
26
  const ffmpegConfig = require("./ffmpeg-config-uANz3sV5.js");
27
27
  const node_crypto = require("node:crypto");
28
28
  const node_child_process = require("node:child_process");
@@ -254,7 +254,7 @@ class SegmentWriter {
254
254
  id: `rec-err-${now}`,
255
255
  timestamp: /* @__PURE__ */ new Date(),
256
256
  source: { type: "addon", id: "recording-engine" },
257
- category: types.EventCategory.RecordingError,
257
+ category: index.EventCategory.RecordingError,
258
258
  data: {
259
259
  deviceId: this.config.deviceId,
260
260
  streamId: this.config.streamId,
@@ -270,7 +270,7 @@ class SegmentWriter {
270
270
  id: `rec-degraded-${now}`,
271
271
  timestamp: /* @__PURE__ */ new Date(),
272
272
  source: { type: "addon", id: "recording-engine" },
273
- category: types.EventCategory.RecordingHealthDegraded,
273
+ category: index.EventCategory.RecordingHealthDegraded,
274
274
  data: {
275
275
  deviceId: this.config.deviceId,
276
276
  streamId: this.config.streamId
@@ -390,7 +390,7 @@ class SegmentWriter {
390
390
  id: `storage-critical-${Date.now()}`,
391
391
  timestamp: /* @__PURE__ */ new Date(),
392
392
  source: { type: "addon", id: "recording-engine" },
393
- category: types.EventCategory.RecordingStorageCritical,
393
+ category: index.EventCategory.RecordingStorageCritical,
394
394
  data: {
395
395
  storageId: this.config.storageName,
396
396
  availableGB: diskCheck.availableGb
@@ -430,7 +430,7 @@ class SegmentWriter {
430
430
  id: `seg-${seg.id}`,
431
431
  timestamp: /* @__PURE__ */ new Date(),
432
432
  source: { type: "addon", id: "recording-engine" },
433
- category: types.EventCategory.RecordingSegmentWritten,
433
+ category: index.EventCategory.RecordingSegmentWritten,
434
434
  data: {
435
435
  deviceId: this.config.deviceId,
436
436
  streamId: this.config.streamId,
@@ -475,7 +475,7 @@ class SegmentWriter {
475
475
  id: `seg-${segId}`,
476
476
  timestamp: /* @__PURE__ */ new Date(),
477
477
  source: { type: "addon", id: "recording-engine" },
478
- category: types.EventCategory.RecordingSegmentWritten,
478
+ category: index.EventCategory.RecordingSegmentWritten,
479
479
  data: {
480
480
  deviceId: this.config.deviceId,
481
481
  streamId: this.config.streamId,
@@ -640,7 +640,7 @@ class RetentionManager {
640
640
  id: `retention-${Date.now()}`,
641
641
  timestamp: /* @__PURE__ */ new Date(),
642
642
  source: { type: "addon", id: "recording-engine" },
643
- category: types.EventCategory.RecordingRetentionCompleted,
643
+ category: index.EventCategory.RecordingRetentionCompleted,
644
644
  data: {
645
645
  freedMB: Math.round(totalFreedBytes / 1024 / 1024),
646
646
  deletedSegments: totalDeletedSegments
@@ -793,7 +793,7 @@ class RecordingCoordinator {
793
793
  const storageConfig = this.db.resolveStorageConfig(deviceId, `recording:${sp.streamId}`);
794
794
  const storageName = storageConfig?.storageName ?? "recordings";
795
795
  const subDirectory = storageConfig?.subDirectory ?? `recordings/${sp.streamId}`;
796
- const resolvedStoragePath = this.storageProvider.resolve({ location: storageName, relativePath: "" });
796
+ const resolvedStoragePath = await this.storageProvider.resolve({ location: storageName, relativePath: "" });
797
797
  const writerConfig = {
798
798
  deviceId,
799
799
  streamId: sp.streamId,
@@ -822,7 +822,7 @@ class RecordingCoordinator {
822
822
  const thumbStorageName = thumbStorageConfig?.storageName ?? "recordings";
823
823
  const thumbConfig = {
824
824
  deviceId,
825
- storagePath: this.storageProvider.resolve({ location: thumbStorageName, relativePath: "" }),
825
+ storagePath: await this.storageProvider.resolve({ location: thumbStorageName, relativePath: "" }),
826
826
  storageName: thumbStorageName,
827
827
  subDirectory: thumbStorageConfig?.subDirectory ?? "thumbnails/scrub",
828
828
  maxWidthPx: 160,
@@ -865,7 +865,7 @@ class RecordingCoordinator {
865
865
  id: `recording-policy-fallback-${deviceId}-${Date.now()}`,
866
866
  timestamp: /* @__PURE__ */ new Date(),
867
867
  source: { type: "addon", id: "recording-engine" },
868
- category: types.EventCategory.RecordingPolicyFallback,
868
+ category: index.EventCategory.RecordingPolicyFallback,
869
869
  data: {
870
870
  deviceId,
871
871
  originalMode: "motion",
@@ -885,7 +885,7 @@ class RecordingCoordinator {
885
885
  id: `recording-started-${deviceId}-${Date.now()}`,
886
886
  timestamp: /* @__PURE__ */ new Date(),
887
887
  source: { type: "addon", id: "recording-engine" },
888
- category: types.EventCategory.RecordingStarted,
888
+ category: index.EventCategory.RecordingStarted,
889
889
  data: {
890
890
  deviceId,
891
891
  mode: policy.mode,
@@ -915,7 +915,7 @@ class RecordingCoordinator {
915
915
  id: `recording-stopped-${deviceId}-${Date.now()}`,
916
916
  timestamp: /* @__PURE__ */ new Date(),
917
917
  source: { type: "addon", id: "recording-engine" },
918
- category: types.EventCategory.RecordingStopped,
918
+ category: index.EventCategory.RecordingStopped,
919
919
  data: {
920
920
  deviceId,
921
921
  segmentCount: totalSegmentCount,
@@ -1049,4 +1049,4 @@ class RecordingCoordinator {
1049
1049
  }
1050
1050
  }
1051
1051
  exports.RecordingCoordinator = RecordingCoordinator;
1052
- //# sourceMappingURL=recording-coordinator-C2sATEhe.js.map
1052
+ //# sourceMappingURL=recording-coordinator-DH1gmm5G.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"recording-coordinator-C2sATEhe.js","sources":["../src/recording/recording/segment-writer.ts","../src/recording/recording/thumbnail-extractor.ts","../src/recording/recording/retention-manager.ts","../src/recording/recording/recording-coordinator.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto'\nimport { spawn, type ChildProcess } from 'node:child_process'\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport { EventCategory } from '@camstack/types'\nimport type { IAddonFileStorage } from '@camstack/types'\nimport type { IScopedLogger, IEventBus, INetworkQualityTracker, FfmpegConfig } from '@camstack/types'\nimport { buildFfmpegInputArgs, buildFfmpegOutputArgs } from './ffmpeg-config.js'\nimport type { RecordingDb } from './recording-db.js'\nimport type { SegmentWriterState, RecordingSegment } from './types.js'\n\n// --- Ring Buffer for motion pre-buffer (E2) ---\n\nexport interface BufferedSegment {\n readonly data: Buffer\n readonly startTime: number\n readonly duration: number\n}\n\nexport class SegmentRingBuffer {\n private segments: BufferedSegment[] = []\n private totalDurationSec = 0\n\n constructor(private readonly maxDurationSec: number) {}\n\n push(segment: BufferedSegment): void {\n this.segments.push(segment)\n this.totalDurationSec += segment.duration\n while (this.totalDurationSec > this.maxDurationSec && this.segments.length > 1) {\n const evicted = this.segments.shift()!\n this.totalDurationSec -= evicted.duration\n }\n }\n\n flush(): BufferedSegment[] {\n const result = [...this.segments]\n this.segments = []\n this.totalDurationSec = 0\n return result\n }\n\n get memoryEstimateBytes(): number {\n return this.segments.reduce((sum, s) => sum + s.data.length, 0)\n }\n}\n\n// --- Config ---\n\nexport type SegmentWriterMode = 'continuous' | 'buffer'\n\nexport interface SegmentWriterConfig {\n readonly deviceId: number\n readonly streamId: string\n readonly segmentDurationSec: number\n readonly storagePath: string\n readonly storageName: string\n readonly subDirectory: string\n readonly ffmpeg: FfmpegConfig\n readonly mode: SegmentWriterMode\n readonly preBufferSec: number\n /**\n * File storage abstraction for segment persistence.\n * Used by writeBufferedSegmentToDisk for persisting buffered segments.\n * Note: ffmpeg still writes to storagePath directly (needs real filesystem paths).\n */\n readonly fileStorage?: IAddonFileStorage\n}\n\n// --- Disk space check result ---\n\ninterface DiskSpaceResult {\n readonly ok: boolean\n readonly availableGb: number\n}\n\n// --- Active segment tracking ---\n\ninterface ActiveSegment {\n readonly id: string\n readonly path: string\n readonly startTime: number\n}\n\n// --- statfs signature for dependency injection ---\n\ntype StatfsFn = (path: string) => Promise<{ bfree: number; bsize: number }>\n\n// --- SegmentWriter ---\n\nexport class SegmentWriter {\n private _state: SegmentWriterState = 'idle'\n private _mode: SegmentWriterMode\n private ffmpeg: ChildProcess | null = null\n private activeSegment: ActiveSegment | null = null\n private restartCount = 0\n private restartWindowStart = 0\n private healthTimer: ReturnType<typeof setInterval> | null = null\n private lastDataTime = 0\n private ringBuffer: SegmentRingBuffer\n private restartTimeout: ReturnType<typeof setTimeout> | null = null\n private pendingFinalization: Promise<void> | null = null\n private paused = false\n private detectedCodec: 'h264' | 'h265' = 'h264'\n private detectedHasAudio = false\n\n private static readonly MAX_RESTARTS = 10\n private static readonly RESTART_WINDOW_MS = 5 * 60 * 1000\n private static readonly HEALTH_CHECK_INTERVAL_MS = 5000\n private static readonly DATA_TIMEOUT_MS = 15000\n private static readonly MIN_SEGMENT_DURATION_SEC = 0.5\n private static readonly CRITICAL_DISK_GB = 1\n\n constructor(\n private readonly config: SegmentWriterConfig,\n private readonly logger: IScopedLogger,\n private readonly eventBus: IEventBus,\n private readonly db: RecordingDb,\n _networkTracker: INetworkQualityTracker,\n ) {\n this._mode = config.mode\n this.ringBuffer = new SegmentRingBuffer(config.preBufferSec)\n }\n\n get state(): SegmentWriterState {\n return this._state\n }\n\n get mode(): SegmentWriterMode {\n return this._mode\n }\n\n get isPaused(): boolean {\n return this.paused\n }\n\n // --- Public API ---\n\n async start(rtspUrl: string): Promise<void> {\n if (this._state !== 'idle') return\n\n const segmentDir = path.join(\n this.config.storagePath,\n this.config.subDirectory,\n String(this.config.deviceId),\n )\n await fs.mkdir(segmentDir, { recursive: true })\n\n this._state = 'recording'\n this.lastDataTime = Date.now()\n this.restartCount = 0\n this.restartWindowStart = Date.now()\n\n const segmentPattern = path.join(segmentDir, '%d.mp4')\n const args = SegmentWriter.buildSegmentationArgs(\n this.config.ffmpeg,\n rtspUrl,\n segmentPattern,\n this.config.segmentDurationSec,\n )\n\n this.spawnFfmpeg(args, rtspUrl)\n this.startHealthCheck(rtspUrl)\n }\n\n async stop(): Promise<void> {\n if (this._state === 'idle') return\n this._state = 'stopping'\n this.stopHealthCheck()\n this.clearRestartTimeout()\n this.killFfmpeg()\n this.finalizeActiveSegment()\n if (this.pendingFinalization) {\n await this.pendingFinalization\n }\n this._state = 'idle'\n }\n\n resume(rtspUrl: string): void {\n if (!this.paused) return\n this.paused = false\n this.logger.info('Resuming recording after disk space freed', {\n tags: { deviceId: this.config.deviceId },\n })\n this._state = 'idle'\n void this.start(rtspUrl)\n }\n\n async flushAndContinue(): Promise<void> {\n if (this._mode !== 'buffer') return\n\n const buffered = this.ringBuffer.flush()\n this.logger.info('Flushing buffered segments to disk', {\n tags: { deviceId: this.config.deviceId },\n meta: { count: buffered.length },\n })\n\n for (const seg of buffered) {\n await this.writeBufferedSegmentToDisk(seg)\n }\n\n this._mode = 'continuous'\n }\n\n switchToBuffer(): void {\n this._mode = 'buffer'\n this.ringBuffer = new SegmentRingBuffer(this.config.preBufferSec)\n }\n\n // --- Static helpers ---\n\n static generateSegmentId(deviceId: number, streamId: string, startTime: number): string {\n const suffix = randomBytes(2).toString('hex')\n return `${deviceId}_${streamId}_${startTime}_${suffix}`\n }\n\n static buildSegmentationArgs(\n config: FfmpegConfig,\n inputUrl: string,\n outputPattern: string,\n segmentDuration: number,\n ): string[] {\n const inputArgs = buildFfmpegInputArgs(config, inputUrl)\n const outputArgs = buildFfmpegOutputArgs(config)\n\n const segmentArgs = [\n '-f', 'segment',\n '-segment_time', String(segmentDuration),\n '-segment_format', 'mp4',\n '-movflags', '+frag_keyframe+empty_moov+default_base_moof',\n '-reset_timestamps', '1',\n '-strftime', '0',\n ]\n\n return [...inputArgs, ...outputArgs, ...segmentArgs, outputPattern]\n }\n\n static async checkDiskSpace(\n storagePath: string,\n statfsFn?: StatfsFn,\n ): Promise<DiskSpaceResult> {\n const doStatfs = statfsFn ?? (async (p: string) => {\n const { statfs: nodeStatfs } = await import('node:fs/promises')\n return nodeStatfs(p)\n })\n\n try {\n const stats = await doStatfs(storagePath)\n const availableBytes = stats.bfree * stats.bsize\n const availableGb = availableBytes / (1024 * 1024 * 1024)\n return { ok: availableGb >= SegmentWriter.CRITICAL_DISK_GB, availableGb }\n } catch {\n return { ok: true, availableGb: -1 }\n }\n }\n\n // --- Private: ffmpeg process management ---\n\n private spawnFfmpeg(args: string[], rtspUrl: string): void {\n this.ffmpeg = spawn(this.config.ffmpeg.path, args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n\n this.ffmpeg.stdout?.on('data', () => {\n this.lastDataTime = Date.now()\n })\n\n this.ffmpeg.stderr?.on('data', (data: Buffer) => {\n this.lastDataTime = Date.now()\n const msg = data.toString().trim()\n if (msg) {\n this.logger.debug('ffmpeg stderr', { meta: { msg } })\n this.parseSegmentOutput(msg)\n }\n })\n\n this.ffmpeg.on('error', (err) => {\n this.logger.warn('ffmpeg process error', { meta: { error: err.message } })\n this.handleCrash(rtspUrl)\n })\n\n this.ffmpeg.on('exit', (code) => {\n if (code !== 0 && code !== null && this._state === 'recording') {\n this.logger.warn('ffmpeg exited with non-zero code', { meta: { code } })\n this.handleCrash(rtspUrl)\n }\n })\n }\n\n private handleCrash(rtspUrl: string): void {\n this.ffmpeg = null\n const prevFinalization = this.pendingFinalization\n this.pendingFinalization = (prevFinalization ?? Promise.resolve()).then(() => {\n return this.finalizeActiveSegment()\n })\n\n if (this._state !== 'recording') return\n if (this.paused) return\n\n const now = Date.now()\n if (now - this.restartWindowStart > SegmentWriter.RESTART_WINDOW_MS) {\n this.restartCount = 0\n this.restartWindowStart = now\n }\n\n this.restartCount++\n\n this.eventBus.emit({\n id: `rec-err-${now}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingError,\n data: {\n deviceId: this.config.deviceId,\n streamId: this.config.streamId,\n restartAttempt: this.restartCount,\n },\n })\n\n if (this.restartCount > SegmentWriter.MAX_RESTARTS) {\n this.logger.error('Max restarts exceeded', {\n tags: { deviceId: this.config.deviceId, streamId: this.config.streamId },\n })\n this._state = 'idle'\n this.eventBus.emit({\n id: `rec-degraded-${now}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingHealthDegraded,\n data: {\n deviceId: this.config.deviceId,\n streamId: this.config.streamId,\n },\n })\n return\n }\n\n const backoffMs = Math.min(30000, 1000 * Math.pow(2, this.restartCount - 1))\n this.logger.info('Restarting ffmpeg', { meta: { backoffMs, attempt: this.restartCount } })\n\n this.restartTimeout = setTimeout(() => {\n this.restartTimeout = null\n if (this._state === 'recording') {\n const segmentDir = path.join(\n this.config.storagePath,\n this.config.subDirectory,\n String(this.config.deviceId),\n )\n const segmentPattern = path.join(segmentDir, '%d.mp4')\n const args = SegmentWriter.buildSegmentationArgs(\n this.config.ffmpeg,\n rtspUrl,\n segmentPattern,\n this.config.segmentDurationSec,\n )\n this.spawnFfmpeg(args, rtspUrl)\n }\n }, backoffMs)\n }\n\n // --- Private: health monitoring ---\n\n private startHealthCheck(rtspUrl: string): void {\n this.healthTimer = setInterval(() => {\n if (this._state !== 'recording') return\n const elapsed = Date.now() - this.lastDataTime\n if (elapsed > SegmentWriter.DATA_TIMEOUT_MS) {\n this.logger.warn('No data received, restarting ffmpeg', { meta: { elapsedMs: elapsed } })\n this.killFfmpeg()\n this.handleCrash(rtspUrl)\n }\n }, SegmentWriter.HEALTH_CHECK_INTERVAL_MS)\n }\n\n private stopHealthCheck(): void {\n if (this.healthTimer) {\n clearInterval(this.healthTimer)\n this.healthTimer = null\n }\n }\n\n private clearRestartTimeout(): void {\n if (this.restartTimeout) {\n clearTimeout(this.restartTimeout)\n this.restartTimeout = null\n }\n }\n\n private killFfmpeg(): void {\n if (this.ffmpeg) {\n this.ffmpeg.kill('SIGTERM')\n this.ffmpeg = null\n }\n }\n\n // --- Private: segment parsing and finalization ---\n\n private parseSegmentOutput(msg: string): void {\n const videoMatch = msg.match(/Stream\\s+#\\d+:\\d+.*Video:\\s+(h264|hevc|h265)/i)\n if (videoMatch) {\n const codec = videoMatch[1]!.toLowerCase()\n this.detectedCodec = (codec === 'hevc' || codec === 'h265') ? 'h265' : 'h264'\n }\n\n const audioMatch = msg.match(/Stream\\s+#\\d+:\\d+.*Audio:/i)\n if (audioMatch) {\n this.detectedHasAudio = true\n }\n\n const openMatch = msg.match(/Opening '(.+\\.mp4)' for writing/)\n if (openMatch) {\n const prevFinalization = this.pendingFinalization\n this.pendingFinalization = (prevFinalization ?? Promise.resolve()).then(() => {\n return this.finalizeActiveSegment()\n })\n\n const absolutePath = openMatch[1]!\n const segPath = absolutePath.startsWith(this.config.storagePath)\n ? absolutePath.slice(this.config.storagePath.length).replace(/^\\//, '')\n : absolutePath\n this.activeSegment = {\n id: SegmentWriter.generateSegmentId(\n this.config.deviceId,\n this.config.streamId,\n Date.now(),\n ),\n path: segPath,\n startTime: Date.now(),\n }\n }\n }\n\n private async finalizeActiveSegment(): Promise<void> {\n if (!this.activeSegment) return\n const seg = this.activeSegment\n this.activeSegment = null\n\n const endTime = Date.now()\n const duration = (endTime - seg.startTime) / 1000\n\n if (duration < SegmentWriter.MIN_SEGMENT_DURATION_SEC) return\n\n if (this._mode === 'buffer') {\n await this.bufferSegmentFromDisk(seg, endTime, duration)\n return\n }\n\n await this.finalizeSegmentToDisk(seg, endTime, duration)\n }\n\n private async bufferSegmentFromDisk(\n seg: ActiveSegment,\n _endTime: number,\n duration: number,\n ): Promise<void> {\n try {\n const data = await fs.readFile(seg.path)\n this.ringBuffer.push({ data, startTime: seg.startTime, duration })\n await fs.unlink(seg.path).catch(() => {})\n } catch (err) {\n this.logger.warn('Failed to buffer segment', { meta: { error: String(err) } })\n }\n }\n\n private async finalizeSegmentToDisk(\n seg: ActiveSegment,\n endTime: number,\n duration: number,\n ): Promise<void> {\n try {\n const diskCheck = await SegmentWriter.checkDiskSpace(this.config.storagePath)\n\n if (!diskCheck.ok) {\n this.eventBus.emit({\n id: `storage-critical-${Date.now()}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingStorageCritical,\n data: {\n storageId: this.config.storageName,\n availableGB: diskCheck.availableGb,\n },\n })\n this.logger.error('Disk space critically low, pausing recording')\n this.paused = true\n this.killFfmpeg()\n this._state = 'idle'\n return\n }\n\n let sizeBytes = 0\n try {\n const fileStat = await fs.stat(seg.path)\n sizeBytes = fileStat.size\n } catch {\n // File may not exist yet or was removed\n }\n\n const codec = this.detectedCodec\n const hasAudio = this.detectedHasAudio\n\n const segment: RecordingSegment = {\n id: seg.id,\n deviceId: this.config.deviceId,\n streamId: this.config.streamId,\n startTime: seg.startTime,\n endTime,\n duration,\n path: seg.path,\n storageName: this.config.storageName,\n subDirectory: this.config.subDirectory,\n sizeBytes,\n codec,\n hasAudio,\n }\n\n try {\n this.db.insertSegment(segment)\n this.eventBus.emit({\n id: `seg-${seg.id}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingSegmentWritten,\n data: {\n deviceId: this.config.deviceId,\n streamId: this.config.streamId,\n segmentId: seg.id,\n duration,\n sizeBytes,\n },\n })\n } catch (err) {\n this.logger.error('Failed to insert segment', { meta: { error: String(err) } })\n }\n } catch (err) {\n this.logger.error('Disk space check failed', { meta: { error: String(err) } })\n }\n }\n\n private async writeBufferedSegmentToDisk(buffered: BufferedSegment): Promise<void> {\n const segId = SegmentWriter.generateSegmentId(\n this.config.deviceId,\n this.config.streamId,\n buffered.startTime,\n )\n const relativePath = `${this.config.subDirectory}/${this.config.deviceId}/${segId}.mp4`\n\n try {\n await this.config.fileStorage?.writeFile(relativePath, buffered.data)\n const sizeBytes = buffered.data.length\n\n const segment: RecordingSegment = {\n id: segId,\n deviceId: this.config.deviceId,\n streamId: this.config.streamId,\n startTime: buffered.startTime,\n endTime: buffered.startTime + buffered.duration * 1000,\n duration: buffered.duration,\n path: relativePath,\n storageName: this.config.storageName,\n subDirectory: this.config.subDirectory,\n sizeBytes,\n codec: this.detectedCodec,\n hasAudio: this.detectedHasAudio,\n }\n\n this.db.insertSegment(segment)\n this.eventBus.emit({\n id: `seg-${segId}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingSegmentWritten,\n data: {\n deviceId: this.config.deviceId,\n streamId: this.config.streamId,\n segmentId: segId,\n duration: buffered.duration,\n sizeBytes,\n },\n })\n } catch (err) {\n this.logger.error('Failed to write buffered segment to disk', { meta: { error: String(err) } })\n }\n }\n}\n","import sharp from 'sharp'\nimport type { IAddonFileStorage } from '@camstack/types'\nimport type { IScopedLogger, ICameraPipeline, IPipelineConsumer, FrameSubscriptionOptions, VideoFrame } from '@camstack/types'\nimport type { RecordingDb } from './recording-db.js'\n\nexport interface ThumbnailExtractorConfig {\n readonly deviceId: number\n readonly storagePath: string\n readonly storageName: string\n readonly subDirectory: string\n readonly maxWidthPx: number\n readonly jpegQuality: number\n /**\n * File storage abstraction for thumbnail persistence.\n * Thumbnails are written via this interface.\n */\n readonly fileStorage?: IAddonFileStorage\n}\n\nexport class ThumbnailExtractor implements IPipelineConsumer {\n readonly id = 'thumbnail-extractor'\n readonly name = 'Thumbnail Extractor'\n readonly needsAudio = false\n\n readonly videoRequirements: FrameSubscriptionOptions = {\n keyframeOnly: true,\n maxFps: 1,\n format: 'jpeg',\n }\n\n private unsubscribe: (() => void) | null = null\n private active = false\n\n constructor(\n private readonly config: ThumbnailExtractorConfig,\n private readonly logger: IScopedLogger,\n private readonly db: RecordingDb,\n ) {}\n\n attachToPipeline(pipeline: ICameraPipeline, _deviceId: number): void {\n this.active = true\n\n this.unsubscribe = pipeline.onVideoFrame(\n (frame) => { this.handleFrame(frame).catch((err) => this.logger.debug('Thumbnail error', { meta: { error: String(err) } })) },\n this.videoRequirements,\n )\n\n this.logger.info('ThumbnailExtractor attached', { tags: { deviceId: this.config.deviceId } })\n }\n\n detachFromPipeline(_deviceId: number): void {\n this.active = false\n if (this.unsubscribe) {\n this.unsubscribe()\n this.unsubscribe = null\n }\n this.logger.info('ThumbnailExtractor detached', { tags: { deviceId: this.config.deviceId } })\n }\n\n setActive(active: boolean): void {\n this.active = active\n }\n\n private async handleFrame(frame: VideoFrame): Promise<void> {\n if (!this.active) return\n\n const timestamp = frame.timestamp || Date.now()\n const relativePath = ThumbnailExtractor.thumbnailPath(\n this.config.subDirectory,\n this.config.deviceId,\n timestamp,\n )\n\n const resized = await sharp(frame.data)\n .resize({ width: this.config.maxWidthPx, withoutEnlargement: true })\n .jpeg({ quality: this.config.jpegQuality })\n .toBuffer()\n\n await this.config.fileStorage?.writeFile(relativePath, resized)\n\n this.db.insertThumbnail({\n deviceId: this.config.deviceId,\n timestamp,\n path: relativePath,\n storageName: this.config.storageName,\n subDirectory: this.config.subDirectory,\n sizeBytes: resized.length,\n category: 'scrub',\n })\n }\n\n static thumbnailPath(subDirectory: string, deviceId: number, timestamp: number): string {\n return `${subDirectory}/${deviceId}/${timestamp}.jpg`\n }\n}\n","import { EventCategory } from '@camstack/types'\nimport type { IScopedLogger, IEventBus, IStorageProvider } from '@camstack/types'\nimport type { RecordingDb } from './recording-db.js'\nimport type { DataCategory } from './types.js'\n\nconst NORMAL_INTERVAL_MS = 5 * 60 * 1000\nconst HIGH_USAGE_INTERVAL_MS = 30 * 1000\nconst STORAGE_WARNING_THRESHOLD = 0.80\nconst STORAGE_CRITICAL_THRESHOLD = 0.95\nconst STORAGE_HIGH_USAGE_THRESHOLD = 0.90\n\nexport class RetentionManager {\n private timer: ReturnType<typeof setTimeout> | null = null\n\n constructor(\n private readonly db: RecordingDb,\n private readonly logger: IScopedLogger,\n private readonly eventBus: IEventBus,\n private readonly storageProvider: IStorageProvider,\n ) {}\n\n start(): void {\n this.scheduleNextCycle(NORMAL_INTERVAL_MS)\n }\n\n stop(): void {\n if (this.timer) {\n clearTimeout(this.timer)\n this.timer = null\n }\n }\n\n async runCycle(): Promise<boolean> {\n this.db.resetStaleCleanups()\n\n const policies = this.db.getEnabledPolicies()\n let totalFreedBytes = 0\n let totalDeletedSegments = 0\n let highUsage = false\n\n for (const policy of policies) {\n for (const sp of policy.streams) {\n const category = `recording:${sp.streamId}` as DataCategory\n const config = this.db.resolveStorageConfig(policy.deviceId, category)\n if (!config) continue\n\n if (config.retentionDays !== null) {\n const cutoff = Date.now() - config.retentionDays * 86400000\n const deleted = this.db.deleteSegmentsBefore(policy.deviceId, sp.streamId, cutoff)\n totalDeletedSegments += deleted.length\n for (const seg of deleted) {\n totalFreedBytes += seg.sizeBytes\n await this.deleteFile(seg.path)\n }\n this.db.deleteThumbnailsBefore(policy.deviceId, cutoff)\n }\n\n if (config.retentionGb !== null) {\n const maxBytes = config.retentionGb * 1024 * 1024 * 1024\n let usage = this.db.getStorageUsage(policy.deviceId, sp.streamId)\n\n const usageRatio = usage.totalBytes / maxBytes\n if (usageRatio > STORAGE_CRITICAL_THRESHOLD) {\n this.emitStorageEvent('recording.storage.critical', policy.deviceId, sp.streamId, usageRatio)\n } else if (usageRatio > STORAGE_WARNING_THRESHOLD) {\n this.emitStorageEvent('recording.storage.warning', policy.deviceId, sp.streamId, usageRatio)\n }\n if (usageRatio > STORAGE_HIGH_USAGE_THRESHOLD) {\n highUsage = true\n }\n\n while (usage.totalBytes > maxBytes && usage.segmentCount > 0) {\n const oldest = this.db.getOldestSegments(policy.deviceId, sp.streamId, 10)\n if (oldest.length === 0) break\n for (const seg of oldest) {\n this.db.deleteSegmentsBefore(policy.deviceId, sp.streamId, seg.endTime + 1)\n totalFreedBytes += seg.sizeBytes\n totalDeletedSegments++\n await this.deleteFile(seg.path)\n }\n usage = this.db.getStorageUsage(policy.deviceId, sp.streamId)\n }\n }\n }\n }\n\n const pending = this.db.getPendingCleanups()\n for (const entry of pending) {\n this.db.markCleanupInProgress(entry.deviceId)\n try {\n const deleted = this.db.deleteSegmentsForDevice(entry.deviceId)\n for (const seg of deleted) {\n totalFreedBytes += seg.sizeBytes\n totalDeletedSegments++\n await this.deleteFile(seg.path)\n }\n this.db.deleteThumbnailsForDevice(entry.deviceId)\n this.db.markCleanupCompleted(entry.deviceId)\n } catch (err) {\n this.logger.error('Cleanup failed', { tags: { deviceId: entry.deviceId }, meta: { error: String(err) } })\n }\n }\n\n if (totalDeletedSegments > 0) {\n this.eventBus.emit({\n id: `retention-${Date.now()}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingRetentionCompleted,\n data: {\n freedMB: Math.round(totalFreedBytes / 1024 / 1024),\n deletedSegments: totalDeletedSegments,\n },\n })\n }\n\n return highUsage\n }\n\n private scheduleNextCycle(intervalMs: number): void {\n this.timer = setTimeout(async () => {\n try {\n const storageHighUsage = await this.runCycle()\n const nextInterval = storageHighUsage ? HIGH_USAGE_INTERVAL_MS : NORMAL_INTERVAL_MS\n this.scheduleNextCycle(nextInterval)\n } catch (err) {\n this.logger.error('Retention cycle error', { meta: { error: String(err) } })\n this.scheduleNextCycle(NORMAL_INTERVAL_MS)\n }\n }, intervalMs)\n }\n\n private emitStorageEvent(category: string, deviceId: number, streamId: string, usageRatio: number): void {\n this.eventBus.emit({\n id: `${category}-${deviceId}-${Date.now()}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category,\n data: {\n deviceId,\n streamId,\n usagePercent: Math.round(usageRatio * 100),\n },\n })\n }\n\n private async deleteFile(filePath: string): Promise<void> {\n try {\n await this.storageProvider.delete({ location: 'recordings', relativePath: filePath })\n } catch {\n // File may already be deleted\n }\n }\n}\n","import { EventCategory } from '@camstack/types'\nimport type { IScopedLogger, IEventBus, SystemEvent, IStreamingEngine, IPipelineManager, INetworkQualityTracker, FfmpegConfig, IStorageProvider } from '@camstack/types'\nimport { resolveFfmpegConfig } from './ffmpeg-config.js'\nimport type { RecordingDb } from './recording-db.js'\nimport type {\n RecordingPolicy, RecordingEnableConfig, ScheduleRule, DataCategory,\n} from './types.js'\nimport { SegmentWriter, type SegmentWriterConfig } from './segment-writer.js'\nimport { ThumbnailExtractor, type ThumbnailExtractorConfig } from './thumbnail-extractor.js'\nimport { RetentionManager } from './retention-manager.js'\nimport { PlaylistGenerator } from './playlist-generator.js'\nimport { StorageEstimator } from './storage-estimator.js'\n\n// --- Per-device recording state ---\n\ninterface DeviceRecordingState {\n readonly deviceId: number\n readonly policy: RecordingPolicy\n readonly writers: readonly SegmentWriter[]\n readonly thumbnailExtractor: ThumbnailExtractor\n readonly motionUnsubscribe: (() => void) | null\n motionActive: boolean\n motionTimeout: ReturnType<typeof setTimeout> | null\n motionFallbackTimeout: ReturnType<typeof setTimeout> | null\n motionReceived: boolean\n}\n\n// --- Coordinator config ---\n\n/** Default segment duration when not configured (seconds). */\nconst DEFAULT_SEGMENT_DURATION_SEC = 4\n\nexport interface RecordingCoordinatorConfig {\n readonly db: RecordingDb\n readonly logger: IScopedLogger\n readonly eventBus: IEventBus\n readonly streamingEngine: IStreamingEngine\n readonly pipelineManager: IPipelineManager\n readonly networkTracker: INetworkQualityTracker\n /**\n * The capability-based storage provider. Used for ALL file operations:\n * - `resolve('recordings', relativePath)` for FFmpeg output paths\n * - `read('recordings', relativePath)` for buffer mode\n * - `delete('recordings', relativePath)` for retention cleanup\n * - `write('recordings', relativePath, data)` for buffered segment flush\n *\n * Session 7 D.4: replaces legacy `fileStorage` + `storagePath` entirely.\n */\n readonly storageProvider: IStorageProvider\n readonly globalFfmpegConfig: Partial<FfmpegConfig>\n readonly detectedFfmpegConfig: Partial<FfmpegConfig>\n /** Global segment duration from system settings (recording.segmentDurationSeconds). */\n readonly segmentDurationSec?: number\n}\n\n// --- Policy evaluation interval ---\n\nconst POLICY_EVAL_INTERVAL_MS = 1000\n\nconst MOTION_FALLBACK_TIMEOUT_MS = 60_000\n\n// --- RecordingCoordinator ---\n\nexport class RecordingCoordinator {\n private readonly db: RecordingDb\n private readonly logger: IScopedLogger\n private readonly eventBus: IEventBus\n private readonly streamingEngine: IStreamingEngine\n private readonly pipelineManager: IPipelineManager\n private readonly networkTracker: INetworkQualityTracker\n private readonly storageProvider: IStorageProvider\n private readonly globalFfmpegConfig: Partial<FfmpegConfig>\n private readonly detectedFfmpegConfig: Partial<FfmpegConfig>\n private readonly segmentDurationSec: number\n\n private readonly recordings = new Map<number, DeviceRecordingState>()\n private policyTimer: ReturnType<typeof setInterval> | null = null\n private readonly retentionManager: RetentionManager\n\n readonly playlistGenerator: PlaylistGenerator\n readonly storageEstimator: StorageEstimator\n\n constructor(config: RecordingCoordinatorConfig) {\n this.db = config.db\n this.logger = config.logger\n this.eventBus = config.eventBus\n this.streamingEngine = config.streamingEngine\n this.pipelineManager = config.pipelineManager\n this.networkTracker = config.networkTracker\n this.storageProvider = config.storageProvider\n this.globalFfmpegConfig = config.globalFfmpegConfig\n this.detectedFfmpegConfig = config.detectedFfmpegConfig\n this.segmentDurationSec = config.segmentDurationSec ?? DEFAULT_SEGMENT_DURATION_SEC\n\n this.retentionManager = new RetentionManager(\n this.db,\n this.logger.child('retention'),\n this.eventBus,\n this.storageProvider,\n )\n this.playlistGenerator = new PlaylistGenerator(this.db)\n this.storageEstimator = new StorageEstimator(this.db, this.networkTracker)\n }\n\n async start(): Promise<void> {\n this.logger.info('RecordingCoordinator starting')\n this.retentionManager.start()\n\n const enabledPolicies = this.db.getEnabledPolicies()\n for (const policy of enabledPolicies) {\n try {\n await this.enableRecording(policy.deviceId, {\n policy: {\n mode: policy.mode,\n streams: policy.streams,\n enabled: policy.enabled,\n preBufferSec: policy.preBufferSec,\n postBufferSec: policy.postBufferSec,\n scheduleRules: policy.scheduleRules,\n },\n })\n } catch (err) {\n this.logger.error('Failed to start recording', { tags: { deviceId: policy.deviceId }, meta: { error: String(err) } })\n }\n }\n\n this.policyTimer = setInterval(() => {\n this.evaluatePolicies()\n }, POLICY_EVAL_INTERVAL_MS)\n\n this.logger.info('RecordingCoordinator started')\n }\n\n stop(): void {\n this.logger.info('RecordingCoordinator stopping')\n\n if (this.policyTimer) {\n clearInterval(this.policyTimer)\n this.policyTimer = null\n }\n\n this.retentionManager.stop()\n\n for (const [deviceId] of this.recordings) {\n this.stopRecordingInternal(deviceId)\n }\n this.recordings.clear()\n\n this.logger.info('RecordingCoordinator stopped')\n }\n\n async enableRecording(deviceId: number, config: RecordingEnableConfig): Promise<void> {\n if (this.recordings.has(deviceId)) {\n this.stopRecordingInternal(deviceId)\n this.recordings.delete(deviceId)\n }\n\n const policy: RecordingPolicy = {\n deviceId,\n mode: config.policy.mode,\n streams: config.policy.streams,\n enabled: config.policy.enabled,\n preBufferSec: config.policy.preBufferSec,\n postBufferSec: config.policy.postBufferSec,\n scheduleRules: config.policy.scheduleRules,\n }\n\n this.db.upsertPolicy({\n deviceId,\n enabled: policy.enabled,\n mode: policy.mode,\n streams: policy.streams,\n preBufferSec: policy.preBufferSec,\n postBufferSec: policy.postBufferSec,\n scheduleRules: policy.scheduleRules,\n })\n\n this.db.cancelCleanup(deviceId)\n\n const ffmpegConfig = resolveFfmpegConfig(\n config.ffmpegOverrides,\n this.globalFfmpegConfig,\n this.detectedFfmpegConfig,\n )\n\n const writerMode = policy.mode === 'motion' ? 'buffer' as const : 'continuous' as const\n\n const writers: SegmentWriter[] = []\n for (const sp of policy.streams) {\n const storageConfig = this.db.resolveStorageConfig(deviceId, `recording:${sp.streamId}` as DataCategory)\n const storageName = storageConfig?.storageName ?? 'recordings'\n const subDirectory = storageConfig?.subDirectory ?? `recordings/${sp.streamId}`\n\n // storagePath resolved dynamically via storageProvider (D.4).\n // storageName may not be a valid StorageLocationType — cast for now,\n // the full location-type refactor is tracked as D.4 follow-up.\n const resolvedStoragePath = this.storageProvider.resolve({ location: storageName as 'recordings', relativePath: '' })\n\n const writerConfig: SegmentWriterConfig = {\n deviceId,\n streamId: sp.streamId,\n segmentDurationSec: this.segmentDurationSec,\n storagePath: resolvedStoragePath,\n storageName,\n subDirectory,\n ffmpeg: ffmpegConfig,\n mode: writerMode,\n preBufferSec: policy.preBufferSec,\n }\n\n const writer = new SegmentWriter(\n writerConfig,\n this.logger.child(`writer:${deviceId}:${sp.streamId}`),\n this.eventBus,\n this.db,\n this.networkTracker,\n )\n\n const rtspUrl = this.streamingEngine.getStreamUrl(`${policy.deviceId}_${sp.streamId}`, 'rtsp')\n if (rtspUrl) {\n await writer.start(rtspUrl)\n }\n\n writers.push(writer)\n }\n\n const thumbStorageConfig = this.db.resolveStorageConfig(deviceId, 'thumbnail:scrub')\n const thumbStorageName = thumbStorageConfig?.storageName ?? 'recordings'\n const thumbConfig: ThumbnailExtractorConfig = {\n deviceId,\n storagePath: this.storageProvider.resolve({ location: thumbStorageName as 'recordings', relativePath: '' }),\n storageName: thumbStorageName,\n subDirectory: thumbStorageConfig?.subDirectory ?? 'thumbnails/scrub',\n maxWidthPx: 160,\n jpegQuality: 65,\n }\n\n const thumbnailExtractor = new ThumbnailExtractor(\n thumbConfig,\n this.logger.child(`thumb:${deviceId}`),\n this.db,\n )\n\n const pipeline = this.pipelineManager.getPipeline(deviceId)\n if (pipeline) {\n thumbnailExtractor.attachToPipeline(pipeline, deviceId)\n }\n\n if (policy.mode === 'motion') {\n thumbnailExtractor.setActive(false)\n }\n\n const motionUnsubscribe = this.subscribeToMotionEvents(deviceId, policy)\n\n const state: DeviceRecordingState = {\n deviceId,\n policy,\n writers,\n thumbnailExtractor,\n motionUnsubscribe,\n motionActive: false,\n motionTimeout: null,\n motionFallbackTimeout: null,\n motionReceived: false,\n }\n\n this.recordings.set(deviceId, state)\n\n if (policy.mode === 'motion') {\n state.motionFallbackTimeout = setTimeout(() => {\n const currentState = this.recordings.get(deviceId)\n if (!currentState || currentState.motionReceived) return\n\n this.logger.warn('No motion events received — falling back to continuous recording', {\n tags: { deviceId },\n meta: { timeoutSec: MOTION_FALLBACK_TIMEOUT_MS / 1000 },\n })\n\n this.eventBus.emit({\n id: `recording-policy-fallback-${deviceId}-${Date.now()}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingPolicyFallback,\n data: {\n deviceId,\n originalMode: 'motion',\n fallbackMode: 'continuous',\n reason: 'no_motion_events',\n },\n })\n\n for (const writer of currentState.writers) {\n writer.flushAndContinue().catch(err => {\n this.logger.error('Failed to flush buffer during fallback', { tags: { deviceId }, meta: { error: String(err) } })\n })\n }\n\n currentState.thumbnailExtractor.setActive(true)\n }, MOTION_FALLBACK_TIMEOUT_MS)\n }\n\n this.eventBus.emit({\n id: `recording-started-${deviceId}-${Date.now()}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingStarted,\n data: {\n deviceId,\n mode: policy.mode,\n streams: policy.streams.map(s => s.streamId),\n },\n })\n\n this.logger.info('Recording enabled', { tags: { deviceId }, meta: { mode: policy.mode } })\n }\n\n async disableRecording(deviceId: number): Promise<void> {\n const state = this.recordings.get(deviceId)\n if (!state) {\n this.logger.warn('No active recording', { tags: { deviceId } })\n return\n }\n\n let totalSegmentCount = 0\n let totalSizeBytes = 0\n for (const sp of state.policy.streams) {\n const usage = this.db.getStorageUsage(deviceId, sp.streamId)\n totalSegmentCount += usage.segmentCount\n totalSizeBytes += usage.totalBytes\n }\n const totalMB = Math.round(totalSizeBytes / 1024 / 1024)\n\n this.stopRecordingInternal(deviceId)\n this.recordings.delete(deviceId)\n\n this.db.addToCleanupQueue(deviceId, Date.now())\n\n this.eventBus.emit({\n id: `recording-stopped-${deviceId}-${Date.now()}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingStopped,\n data: {\n deviceId,\n segmentCount: totalSegmentCount,\n totalMB,\n },\n })\n\n this.logger.info('Recording disabled', { tags: { deviceId }, meta: { segmentCount: totalSegmentCount, totalMB } })\n }\n\n isRecording(deviceId: number): boolean {\n return this.recordings.has(deviceId)\n }\n\n /** Number of devices currently being recorded. */\n getActiveCount(): number {\n return this.recordings.size\n }\n\n evaluatePolicies(): void {\n const now = new Date()\n\n for (const [_deviceId, state] of this.recordings) {\n const { policy } = state\n\n if (policy.mode === 'scheduled' || policy.mode === 'composite') {\n if (!policy.scheduleRules || policy.scheduleRules.length === 0) continue\n\n const matchingRule = policy.scheduleRules.find(rule =>\n RecordingCoordinator.evaluateScheduleRule(rule, now),\n )\n\n if (matchingRule) {\n const targetMode = matchingRule.mode === 'motion' ? 'buffer' as const : 'continuous' as const\n for (const writer of state.writers) {\n if (writer.mode !== targetMode) {\n if (targetMode === 'buffer') {\n writer.switchToBuffer()\n }\n }\n }\n } else {\n for (const writer of state.writers) {\n if (writer.mode !== 'buffer') {\n writer.switchToBuffer()\n }\n }\n }\n }\n }\n }\n\n static evaluateScheduleRule(rule: ScheduleRule, date: Date): boolean {\n const dayOfWeek = date.getDay()\n const timeMinutes = date.getHours() * 60 + date.getMinutes()\n\n const [startH, startM] = rule.startTime.split(':').map(Number) as [number, number]\n const [endH, endM] = rule.endTime.split(':').map(Number) as [number, number]\n const startMinutes = startH * 60 + startM\n const endMinutes = endH * 60 + endM\n\n if (endMinutes > startMinutes) {\n return rule.days.includes(dayOfWeek)\n && timeMinutes >= startMinutes\n && timeMinutes < endMinutes\n }\n\n if (rule.days.includes(dayOfWeek) && timeMinutes >= startMinutes) {\n return true\n }\n\n const previousDay = (dayOfWeek + 6) % 7\n if (rule.days.includes(previousDay) && timeMinutes < endMinutes) {\n return true\n }\n\n return false\n }\n\n private subscribeToMotionEvents(deviceId: number, policy: RecordingPolicy): (() => void) | null {\n if (policy.mode !== 'motion' && policy.mode !== 'composite') {\n return null\n }\n\n return this.eventBus.subscribe(\n { category: `motion.${deviceId}` },\n (event: SystemEvent) => {\n this.handleMotionEvent(deviceId, event)\n },\n )\n }\n\n private handleMotionEvent(deviceId: number, event: SystemEvent): void {\n const state = this.recordings.get(deviceId)\n if (!state) return\n\n if (!state.motionReceived) {\n state.motionReceived = true\n if (state.motionFallbackTimeout) {\n clearTimeout(state.motionFallbackTimeout)\n state.motionFallbackTimeout = null\n }\n }\n\n const motionDetected = event.data.active === true || event.data.type === 'start'\n\n if (motionDetected) {\n state.motionActive = true\n\n if (state.motionTimeout) {\n clearTimeout(state.motionTimeout)\n state.motionTimeout = null\n }\n\n for (const writer of state.writers) {\n writer.flushAndContinue().catch(err => {\n this.logger.error('Failed to flush buffer', { tags: { deviceId }, meta: { error: String(err) } })\n })\n }\n\n state.thumbnailExtractor.setActive(true)\n } else {\n if (state.motionTimeout) {\n clearTimeout(state.motionTimeout)\n }\n\n state.motionTimeout = setTimeout(() => {\n state.motionActive = false\n state.motionTimeout = null\n\n for (const writer of state.writers) {\n writer.switchToBuffer()\n }\n\n if (state.policy.mode === 'motion') {\n state.thumbnailExtractor.setActive(false)\n }\n }, state.policy.postBufferSec * 1000)\n }\n }\n\n private stopRecordingInternal(deviceId: number): void {\n const state = this.recordings.get(deviceId)\n if (!state) return\n\n for (const writer of state.writers) {\n writer.stop()\n }\n\n state.thumbnailExtractor.detachFromPipeline(deviceId)\n\n if (state.motionUnsubscribe) {\n state.motionUnsubscribe()\n }\n\n if (state.motionTimeout) {\n clearTimeout(state.motionTimeout)\n state.motionTimeout = null\n }\n\n if (state.motionFallbackTimeout) {\n clearTimeout(state.motionFallbackTimeout)\n state.motionFallbackTimeout = null\n }\n }\n}\n"],"names":["path","fs","randomBytes","buildFfmpegInputArgs","buildFfmpegOutputArgs","spawn","EventCategory","PlaylistGenerator","StorageEstimator","ffmpegConfig","resolveFfmpegConfig"],"mappings":"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"}
1
+ {"version":3,"file":"recording-coordinator-DH1gmm5G.js","sources":["../src/recording/recording/segment-writer.ts","../src/recording/recording/thumbnail-extractor.ts","../src/recording/recording/retention-manager.ts","../src/recording/recording/recording-coordinator.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto'\nimport { spawn, type ChildProcess } from 'node:child_process'\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport { EventCategory } from '@camstack/types'\nimport type { IAddonFileStorage } from '@camstack/types'\nimport type { IScopedLogger, IEventBus, INetworkQualityTracker, FfmpegConfig } from '@camstack/types'\nimport { buildFfmpegInputArgs, buildFfmpegOutputArgs } from './ffmpeg-config.js'\nimport type { RecordingDb } from './recording-db.js'\nimport type { SegmentWriterState, RecordingSegment } from './types.js'\n\n// --- Ring Buffer for motion pre-buffer (E2) ---\n\nexport interface BufferedSegment {\n readonly data: Buffer\n readonly startTime: number\n readonly duration: number\n}\n\nexport class SegmentRingBuffer {\n private segments: BufferedSegment[] = []\n private totalDurationSec = 0\n\n constructor(private readonly maxDurationSec: number) {}\n\n push(segment: BufferedSegment): void {\n this.segments.push(segment)\n this.totalDurationSec += segment.duration\n while (this.totalDurationSec > this.maxDurationSec && this.segments.length > 1) {\n const evicted = this.segments.shift()!\n this.totalDurationSec -= evicted.duration\n }\n }\n\n flush(): BufferedSegment[] {\n const result = [...this.segments]\n this.segments = []\n this.totalDurationSec = 0\n return result\n }\n\n get memoryEstimateBytes(): number {\n return this.segments.reduce((sum, s) => sum + s.data.length, 0)\n }\n}\n\n// --- Config ---\n\nexport type SegmentWriterMode = 'continuous' | 'buffer'\n\nexport interface SegmentWriterConfig {\n readonly deviceId: number\n readonly streamId: string\n readonly segmentDurationSec: number\n readonly storagePath: string\n readonly storageName: string\n readonly subDirectory: string\n readonly ffmpeg: FfmpegConfig\n readonly mode: SegmentWriterMode\n readonly preBufferSec: number\n /**\n * File storage abstraction for segment persistence.\n * Used by writeBufferedSegmentToDisk for persisting buffered segments.\n * Note: ffmpeg still writes to storagePath directly (needs real filesystem paths).\n */\n readonly fileStorage?: IAddonFileStorage\n}\n\n// --- Disk space check result ---\n\ninterface DiskSpaceResult {\n readonly ok: boolean\n readonly availableGb: number\n}\n\n// --- Active segment tracking ---\n\ninterface ActiveSegment {\n readonly id: string\n readonly path: string\n readonly startTime: number\n}\n\n// --- statfs signature for dependency injection ---\n\ntype StatfsFn = (path: string) => Promise<{ bfree: number; bsize: number }>\n\n// --- SegmentWriter ---\n\nexport class SegmentWriter {\n private _state: SegmentWriterState = 'idle'\n private _mode: SegmentWriterMode\n private ffmpeg: ChildProcess | null = null\n private activeSegment: ActiveSegment | null = null\n private restartCount = 0\n private restartWindowStart = 0\n private healthTimer: ReturnType<typeof setInterval> | null = null\n private lastDataTime = 0\n private ringBuffer: SegmentRingBuffer\n private restartTimeout: ReturnType<typeof setTimeout> | null = null\n private pendingFinalization: Promise<void> | null = null\n private paused = false\n private detectedCodec: 'h264' | 'h265' = 'h264'\n private detectedHasAudio = false\n\n private static readonly MAX_RESTARTS = 10\n private static readonly RESTART_WINDOW_MS = 5 * 60 * 1000\n private static readonly HEALTH_CHECK_INTERVAL_MS = 5000\n private static readonly DATA_TIMEOUT_MS = 15000\n private static readonly MIN_SEGMENT_DURATION_SEC = 0.5\n private static readonly CRITICAL_DISK_GB = 1\n\n constructor(\n private readonly config: SegmentWriterConfig,\n private readonly logger: IScopedLogger,\n private readonly eventBus: IEventBus,\n private readonly db: RecordingDb,\n _networkTracker: INetworkQualityTracker,\n ) {\n this._mode = config.mode\n this.ringBuffer = new SegmentRingBuffer(config.preBufferSec)\n }\n\n get state(): SegmentWriterState {\n return this._state\n }\n\n get mode(): SegmentWriterMode {\n return this._mode\n }\n\n get isPaused(): boolean {\n return this.paused\n }\n\n // --- Public API ---\n\n async start(rtspUrl: string): Promise<void> {\n if (this._state !== 'idle') return\n\n const segmentDir = path.join(\n this.config.storagePath,\n this.config.subDirectory,\n String(this.config.deviceId),\n )\n await fs.mkdir(segmentDir, { recursive: true })\n\n this._state = 'recording'\n this.lastDataTime = Date.now()\n this.restartCount = 0\n this.restartWindowStart = Date.now()\n\n const segmentPattern = path.join(segmentDir, '%d.mp4')\n const args = SegmentWriter.buildSegmentationArgs(\n this.config.ffmpeg,\n rtspUrl,\n segmentPattern,\n this.config.segmentDurationSec,\n )\n\n this.spawnFfmpeg(args, rtspUrl)\n this.startHealthCheck(rtspUrl)\n }\n\n async stop(): Promise<void> {\n if (this._state === 'idle') return\n this._state = 'stopping'\n this.stopHealthCheck()\n this.clearRestartTimeout()\n this.killFfmpeg()\n this.finalizeActiveSegment()\n if (this.pendingFinalization) {\n await this.pendingFinalization\n }\n this._state = 'idle'\n }\n\n resume(rtspUrl: string): void {\n if (!this.paused) return\n this.paused = false\n this.logger.info('Resuming recording after disk space freed', {\n tags: { deviceId: this.config.deviceId },\n })\n this._state = 'idle'\n void this.start(rtspUrl)\n }\n\n async flushAndContinue(): Promise<void> {\n if (this._mode !== 'buffer') return\n\n const buffered = this.ringBuffer.flush()\n this.logger.info('Flushing buffered segments to disk', {\n tags: { deviceId: this.config.deviceId },\n meta: { count: buffered.length },\n })\n\n for (const seg of buffered) {\n await this.writeBufferedSegmentToDisk(seg)\n }\n\n this._mode = 'continuous'\n }\n\n switchToBuffer(): void {\n this._mode = 'buffer'\n this.ringBuffer = new SegmentRingBuffer(this.config.preBufferSec)\n }\n\n // --- Static helpers ---\n\n static generateSegmentId(deviceId: number, streamId: string, startTime: number): string {\n const suffix = randomBytes(2).toString('hex')\n return `${deviceId}_${streamId}_${startTime}_${suffix}`\n }\n\n static buildSegmentationArgs(\n config: FfmpegConfig,\n inputUrl: string,\n outputPattern: string,\n segmentDuration: number,\n ): string[] {\n const inputArgs = buildFfmpegInputArgs(config, inputUrl)\n const outputArgs = buildFfmpegOutputArgs(config)\n\n const segmentArgs = [\n '-f', 'segment',\n '-segment_time', String(segmentDuration),\n '-segment_format', 'mp4',\n '-movflags', '+frag_keyframe+empty_moov+default_base_moof',\n '-reset_timestamps', '1',\n '-strftime', '0',\n ]\n\n return [...inputArgs, ...outputArgs, ...segmentArgs, outputPattern]\n }\n\n static async checkDiskSpace(\n storagePath: string,\n statfsFn?: StatfsFn,\n ): Promise<DiskSpaceResult> {\n const doStatfs = statfsFn ?? (async (p: string) => {\n const { statfs: nodeStatfs } = await import('node:fs/promises')\n return nodeStatfs(p)\n })\n\n try {\n const stats = await doStatfs(storagePath)\n const availableBytes = stats.bfree * stats.bsize\n const availableGb = availableBytes / (1024 * 1024 * 1024)\n return { ok: availableGb >= SegmentWriter.CRITICAL_DISK_GB, availableGb }\n } catch {\n return { ok: true, availableGb: -1 }\n }\n }\n\n // --- Private: ffmpeg process management ---\n\n private spawnFfmpeg(args: string[], rtspUrl: string): void {\n this.ffmpeg = spawn(this.config.ffmpeg.path, args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n\n this.ffmpeg.stdout?.on('data', () => {\n this.lastDataTime = Date.now()\n })\n\n this.ffmpeg.stderr?.on('data', (data: Buffer) => {\n this.lastDataTime = Date.now()\n const msg = data.toString().trim()\n if (msg) {\n this.logger.debug('ffmpeg stderr', { meta: { msg } })\n this.parseSegmentOutput(msg)\n }\n })\n\n this.ffmpeg.on('error', (err) => {\n this.logger.warn('ffmpeg process error', { meta: { error: err.message } })\n this.handleCrash(rtspUrl)\n })\n\n this.ffmpeg.on('exit', (code) => {\n if (code !== 0 && code !== null && this._state === 'recording') {\n this.logger.warn('ffmpeg exited with non-zero code', { meta: { code } })\n this.handleCrash(rtspUrl)\n }\n })\n }\n\n private handleCrash(rtspUrl: string): void {\n this.ffmpeg = null\n const prevFinalization = this.pendingFinalization\n this.pendingFinalization = (prevFinalization ?? Promise.resolve()).then(() => {\n return this.finalizeActiveSegment()\n })\n\n if (this._state !== 'recording') return\n if (this.paused) return\n\n const now = Date.now()\n if (now - this.restartWindowStart > SegmentWriter.RESTART_WINDOW_MS) {\n this.restartCount = 0\n this.restartWindowStart = now\n }\n\n this.restartCount++\n\n this.eventBus.emit({\n id: `rec-err-${now}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingError,\n data: {\n deviceId: this.config.deviceId,\n streamId: this.config.streamId,\n restartAttempt: this.restartCount,\n },\n })\n\n if (this.restartCount > SegmentWriter.MAX_RESTARTS) {\n this.logger.error('Max restarts exceeded', {\n tags: { deviceId: this.config.deviceId, streamId: this.config.streamId },\n })\n this._state = 'idle'\n this.eventBus.emit({\n id: `rec-degraded-${now}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingHealthDegraded,\n data: {\n deviceId: this.config.deviceId,\n streamId: this.config.streamId,\n },\n })\n return\n }\n\n const backoffMs = Math.min(30000, 1000 * Math.pow(2, this.restartCount - 1))\n this.logger.info('Restarting ffmpeg', { meta: { backoffMs, attempt: this.restartCount } })\n\n this.restartTimeout = setTimeout(() => {\n this.restartTimeout = null\n if (this._state === 'recording') {\n const segmentDir = path.join(\n this.config.storagePath,\n this.config.subDirectory,\n String(this.config.deviceId),\n )\n const segmentPattern = path.join(segmentDir, '%d.mp4')\n const args = SegmentWriter.buildSegmentationArgs(\n this.config.ffmpeg,\n rtspUrl,\n segmentPattern,\n this.config.segmentDurationSec,\n )\n this.spawnFfmpeg(args, rtspUrl)\n }\n }, backoffMs)\n }\n\n // --- Private: health monitoring ---\n\n private startHealthCheck(rtspUrl: string): void {\n this.healthTimer = setInterval(() => {\n if (this._state !== 'recording') return\n const elapsed = Date.now() - this.lastDataTime\n if (elapsed > SegmentWriter.DATA_TIMEOUT_MS) {\n this.logger.warn('No data received, restarting ffmpeg', { meta: { elapsedMs: elapsed } })\n this.killFfmpeg()\n this.handleCrash(rtspUrl)\n }\n }, SegmentWriter.HEALTH_CHECK_INTERVAL_MS)\n }\n\n private stopHealthCheck(): void {\n if (this.healthTimer) {\n clearInterval(this.healthTimer)\n this.healthTimer = null\n }\n }\n\n private clearRestartTimeout(): void {\n if (this.restartTimeout) {\n clearTimeout(this.restartTimeout)\n this.restartTimeout = null\n }\n }\n\n private killFfmpeg(): void {\n if (this.ffmpeg) {\n this.ffmpeg.kill('SIGTERM')\n this.ffmpeg = null\n }\n }\n\n // --- Private: segment parsing and finalization ---\n\n private parseSegmentOutput(msg: string): void {\n const videoMatch = msg.match(/Stream\\s+#\\d+:\\d+.*Video:\\s+(h264|hevc|h265)/i)\n if (videoMatch) {\n const codec = videoMatch[1]!.toLowerCase()\n this.detectedCodec = (codec === 'hevc' || codec === 'h265') ? 'h265' : 'h264'\n }\n\n const audioMatch = msg.match(/Stream\\s+#\\d+:\\d+.*Audio:/i)\n if (audioMatch) {\n this.detectedHasAudio = true\n }\n\n const openMatch = msg.match(/Opening '(.+\\.mp4)' for writing/)\n if (openMatch) {\n const prevFinalization = this.pendingFinalization\n this.pendingFinalization = (prevFinalization ?? Promise.resolve()).then(() => {\n return this.finalizeActiveSegment()\n })\n\n const absolutePath = openMatch[1]!\n const segPath = absolutePath.startsWith(this.config.storagePath)\n ? absolutePath.slice(this.config.storagePath.length).replace(/^\\//, '')\n : absolutePath\n this.activeSegment = {\n id: SegmentWriter.generateSegmentId(\n this.config.deviceId,\n this.config.streamId,\n Date.now(),\n ),\n path: segPath,\n startTime: Date.now(),\n }\n }\n }\n\n private async finalizeActiveSegment(): Promise<void> {\n if (!this.activeSegment) return\n const seg = this.activeSegment\n this.activeSegment = null\n\n const endTime = Date.now()\n const duration = (endTime - seg.startTime) / 1000\n\n if (duration < SegmentWriter.MIN_SEGMENT_DURATION_SEC) return\n\n if (this._mode === 'buffer') {\n await this.bufferSegmentFromDisk(seg, endTime, duration)\n return\n }\n\n await this.finalizeSegmentToDisk(seg, endTime, duration)\n }\n\n private async bufferSegmentFromDisk(\n seg: ActiveSegment,\n _endTime: number,\n duration: number,\n ): Promise<void> {\n try {\n const data = await fs.readFile(seg.path)\n this.ringBuffer.push({ data, startTime: seg.startTime, duration })\n await fs.unlink(seg.path).catch(() => {})\n } catch (err) {\n this.logger.warn('Failed to buffer segment', { meta: { error: String(err) } })\n }\n }\n\n private async finalizeSegmentToDisk(\n seg: ActiveSegment,\n endTime: number,\n duration: number,\n ): Promise<void> {\n try {\n const diskCheck = await SegmentWriter.checkDiskSpace(this.config.storagePath)\n\n if (!diskCheck.ok) {\n this.eventBus.emit({\n id: `storage-critical-${Date.now()}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingStorageCritical,\n data: {\n storageId: this.config.storageName,\n availableGB: diskCheck.availableGb,\n },\n })\n this.logger.error('Disk space critically low, pausing recording')\n this.paused = true\n this.killFfmpeg()\n this._state = 'idle'\n return\n }\n\n let sizeBytes = 0\n try {\n const fileStat = await fs.stat(seg.path)\n sizeBytes = fileStat.size\n } catch {\n // File may not exist yet or was removed\n }\n\n const codec = this.detectedCodec\n const hasAudio = this.detectedHasAudio\n\n const segment: RecordingSegment = {\n id: seg.id,\n deviceId: this.config.deviceId,\n streamId: this.config.streamId,\n startTime: seg.startTime,\n endTime,\n duration,\n path: seg.path,\n storageName: this.config.storageName,\n subDirectory: this.config.subDirectory,\n sizeBytes,\n codec,\n hasAudio,\n }\n\n try {\n this.db.insertSegment(segment)\n this.eventBus.emit({\n id: `seg-${seg.id}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingSegmentWritten,\n data: {\n deviceId: this.config.deviceId,\n streamId: this.config.streamId,\n segmentId: seg.id,\n duration,\n sizeBytes,\n },\n })\n } catch (err) {\n this.logger.error('Failed to insert segment', { meta: { error: String(err) } })\n }\n } catch (err) {\n this.logger.error('Disk space check failed', { meta: { error: String(err) } })\n }\n }\n\n private async writeBufferedSegmentToDisk(buffered: BufferedSegment): Promise<void> {\n const segId = SegmentWriter.generateSegmentId(\n this.config.deviceId,\n this.config.streamId,\n buffered.startTime,\n )\n const relativePath = `${this.config.subDirectory}/${this.config.deviceId}/${segId}.mp4`\n\n try {\n await this.config.fileStorage?.writeFile(relativePath, buffered.data)\n const sizeBytes = buffered.data.length\n\n const segment: RecordingSegment = {\n id: segId,\n deviceId: this.config.deviceId,\n streamId: this.config.streamId,\n startTime: buffered.startTime,\n endTime: buffered.startTime + buffered.duration * 1000,\n duration: buffered.duration,\n path: relativePath,\n storageName: this.config.storageName,\n subDirectory: this.config.subDirectory,\n sizeBytes,\n codec: this.detectedCodec,\n hasAudio: this.detectedHasAudio,\n }\n\n this.db.insertSegment(segment)\n this.eventBus.emit({\n id: `seg-${segId}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingSegmentWritten,\n data: {\n deviceId: this.config.deviceId,\n streamId: this.config.streamId,\n segmentId: segId,\n duration: buffered.duration,\n sizeBytes,\n },\n })\n } catch (err) {\n this.logger.error('Failed to write buffered segment to disk', { meta: { error: String(err) } })\n }\n }\n}\n","import sharp from 'sharp'\nimport type { IAddonFileStorage } from '@camstack/types'\nimport type { IScopedLogger, ICameraPipeline, IPipelineConsumer, FrameSubscriptionOptions, VideoFrame } from '@camstack/types'\nimport type { RecordingDb } from './recording-db.js'\n\nexport interface ThumbnailExtractorConfig {\n readonly deviceId: number\n readonly storagePath: string\n readonly storageName: string\n readonly subDirectory: string\n readonly maxWidthPx: number\n readonly jpegQuality: number\n /**\n * File storage abstraction for thumbnail persistence.\n * Thumbnails are written via this interface.\n */\n readonly fileStorage?: IAddonFileStorage\n}\n\nexport class ThumbnailExtractor implements IPipelineConsumer {\n readonly id = 'thumbnail-extractor'\n readonly name = 'Thumbnail Extractor'\n readonly needsAudio = false\n\n readonly videoRequirements: FrameSubscriptionOptions = {\n keyframeOnly: true,\n maxFps: 1,\n format: 'jpeg',\n }\n\n private unsubscribe: (() => void) | null = null\n private active = false\n\n constructor(\n private readonly config: ThumbnailExtractorConfig,\n private readonly logger: IScopedLogger,\n private readonly db: RecordingDb,\n ) {}\n\n attachToPipeline(pipeline: ICameraPipeline, _deviceId: number): void {\n this.active = true\n\n this.unsubscribe = pipeline.onVideoFrame(\n (frame) => { this.handleFrame(frame).catch((err) => this.logger.debug('Thumbnail error', { meta: { error: String(err) } })) },\n this.videoRequirements,\n )\n\n this.logger.info('ThumbnailExtractor attached', { tags: { deviceId: this.config.deviceId } })\n }\n\n detachFromPipeline(_deviceId: number): void {\n this.active = false\n if (this.unsubscribe) {\n this.unsubscribe()\n this.unsubscribe = null\n }\n this.logger.info('ThumbnailExtractor detached', { tags: { deviceId: this.config.deviceId } })\n }\n\n setActive(active: boolean): void {\n this.active = active\n }\n\n private async handleFrame(frame: VideoFrame): Promise<void> {\n if (!this.active) return\n\n const timestamp = frame.timestamp || Date.now()\n const relativePath = ThumbnailExtractor.thumbnailPath(\n this.config.subDirectory,\n this.config.deviceId,\n timestamp,\n )\n\n const resized = await sharp(frame.data)\n .resize({ width: this.config.maxWidthPx, withoutEnlargement: true })\n .jpeg({ quality: this.config.jpegQuality })\n .toBuffer()\n\n await this.config.fileStorage?.writeFile(relativePath, resized)\n\n this.db.insertThumbnail({\n deviceId: this.config.deviceId,\n timestamp,\n path: relativePath,\n storageName: this.config.storageName,\n subDirectory: this.config.subDirectory,\n sizeBytes: resized.length,\n category: 'scrub',\n })\n }\n\n static thumbnailPath(subDirectory: string, deviceId: number, timestamp: number): string {\n return `${subDirectory}/${deviceId}/${timestamp}.jpg`\n }\n}\n","import { EventCategory } from '@camstack/types'\nimport type { IScopedLogger, IEventBus, IStorageProvider } from '@camstack/types'\nimport type { RecordingDb } from './recording-db.js'\nimport type { DataCategory } from './types.js'\n\nconst NORMAL_INTERVAL_MS = 5 * 60 * 1000\nconst HIGH_USAGE_INTERVAL_MS = 30 * 1000\nconst STORAGE_WARNING_THRESHOLD = 0.80\nconst STORAGE_CRITICAL_THRESHOLD = 0.95\nconst STORAGE_HIGH_USAGE_THRESHOLD = 0.90\n\nexport class RetentionManager {\n private timer: ReturnType<typeof setTimeout> | null = null\n\n constructor(\n private readonly db: RecordingDb,\n private readonly logger: IScopedLogger,\n private readonly eventBus: IEventBus,\n private readonly storageProvider: IStorageProvider,\n ) {}\n\n start(): void {\n this.scheduleNextCycle(NORMAL_INTERVAL_MS)\n }\n\n stop(): void {\n if (this.timer) {\n clearTimeout(this.timer)\n this.timer = null\n }\n }\n\n async runCycle(): Promise<boolean> {\n this.db.resetStaleCleanups()\n\n const policies = this.db.getEnabledPolicies()\n let totalFreedBytes = 0\n let totalDeletedSegments = 0\n let highUsage = false\n\n for (const policy of policies) {\n for (const sp of policy.streams) {\n const category = `recording:${sp.streamId}` as DataCategory\n const config = this.db.resolveStorageConfig(policy.deviceId, category)\n if (!config) continue\n\n if (config.retentionDays !== null) {\n const cutoff = Date.now() - config.retentionDays * 86400000\n const deleted = this.db.deleteSegmentsBefore(policy.deviceId, sp.streamId, cutoff)\n totalDeletedSegments += deleted.length\n for (const seg of deleted) {\n totalFreedBytes += seg.sizeBytes\n await this.deleteFile(seg.path)\n }\n this.db.deleteThumbnailsBefore(policy.deviceId, cutoff)\n }\n\n if (config.retentionGb !== null) {\n const maxBytes = config.retentionGb * 1024 * 1024 * 1024\n let usage = this.db.getStorageUsage(policy.deviceId, sp.streamId)\n\n const usageRatio = usage.totalBytes / maxBytes\n if (usageRatio > STORAGE_CRITICAL_THRESHOLD) {\n this.emitStorageEvent('recording.storage.critical', policy.deviceId, sp.streamId, usageRatio)\n } else if (usageRatio > STORAGE_WARNING_THRESHOLD) {\n this.emitStorageEvent('recording.storage.warning', policy.deviceId, sp.streamId, usageRatio)\n }\n if (usageRatio > STORAGE_HIGH_USAGE_THRESHOLD) {\n highUsage = true\n }\n\n while (usage.totalBytes > maxBytes && usage.segmentCount > 0) {\n const oldest = this.db.getOldestSegments(policy.deviceId, sp.streamId, 10)\n if (oldest.length === 0) break\n for (const seg of oldest) {\n this.db.deleteSegmentsBefore(policy.deviceId, sp.streamId, seg.endTime + 1)\n totalFreedBytes += seg.sizeBytes\n totalDeletedSegments++\n await this.deleteFile(seg.path)\n }\n usage = this.db.getStorageUsage(policy.deviceId, sp.streamId)\n }\n }\n }\n }\n\n const pending = this.db.getPendingCleanups()\n for (const entry of pending) {\n this.db.markCleanupInProgress(entry.deviceId)\n try {\n const deleted = this.db.deleteSegmentsForDevice(entry.deviceId)\n for (const seg of deleted) {\n totalFreedBytes += seg.sizeBytes\n totalDeletedSegments++\n await this.deleteFile(seg.path)\n }\n this.db.deleteThumbnailsForDevice(entry.deviceId)\n this.db.markCleanupCompleted(entry.deviceId)\n } catch (err) {\n this.logger.error('Cleanup failed', { tags: { deviceId: entry.deviceId }, meta: { error: String(err) } })\n }\n }\n\n if (totalDeletedSegments > 0) {\n this.eventBus.emit({\n id: `retention-${Date.now()}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingRetentionCompleted,\n data: {\n freedMB: Math.round(totalFreedBytes / 1024 / 1024),\n deletedSegments: totalDeletedSegments,\n },\n })\n }\n\n return highUsage\n }\n\n private scheduleNextCycle(intervalMs: number): void {\n this.timer = setTimeout(async () => {\n try {\n const storageHighUsage = await this.runCycle()\n const nextInterval = storageHighUsage ? HIGH_USAGE_INTERVAL_MS : NORMAL_INTERVAL_MS\n this.scheduleNextCycle(nextInterval)\n } catch (err) {\n this.logger.error('Retention cycle error', { meta: { error: String(err) } })\n this.scheduleNextCycle(NORMAL_INTERVAL_MS)\n }\n }, intervalMs)\n }\n\n private emitStorageEvent(category: string, deviceId: number, streamId: string, usageRatio: number): void {\n this.eventBus.emit({\n id: `${category}-${deviceId}-${Date.now()}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category,\n data: {\n deviceId,\n streamId,\n usagePercent: Math.round(usageRatio * 100),\n },\n })\n }\n\n private async deleteFile(filePath: string): Promise<void> {\n try {\n await this.storageProvider.delete({ location: 'recordings', relativePath: filePath })\n } catch {\n // File may already be deleted\n }\n }\n}\n","import { EventCategory } from '@camstack/types'\nimport type { IScopedLogger, IEventBus, SystemEvent, IStreamingEngine, IPipelineManager, INetworkQualityTracker, FfmpegConfig, IStorageProvider } from '@camstack/types'\nimport { resolveFfmpegConfig } from './ffmpeg-config.js'\nimport type { RecordingDb } from './recording-db.js'\nimport type {\n RecordingPolicy, RecordingEnableConfig, ScheduleRule, DataCategory,\n} from './types.js'\nimport { SegmentWriter, type SegmentWriterConfig } from './segment-writer.js'\nimport { ThumbnailExtractor, type ThumbnailExtractorConfig } from './thumbnail-extractor.js'\nimport { RetentionManager } from './retention-manager.js'\nimport { PlaylistGenerator } from './playlist-generator.js'\nimport { StorageEstimator } from './storage-estimator.js'\n\n// --- Per-device recording state ---\n\ninterface DeviceRecordingState {\n readonly deviceId: number\n readonly policy: RecordingPolicy\n readonly writers: readonly SegmentWriter[]\n readonly thumbnailExtractor: ThumbnailExtractor\n readonly motionUnsubscribe: (() => void) | null\n motionActive: boolean\n motionTimeout: ReturnType<typeof setTimeout> | null\n motionFallbackTimeout: ReturnType<typeof setTimeout> | null\n motionReceived: boolean\n}\n\n// --- Coordinator config ---\n\n/** Default segment duration when not configured (seconds). */\nconst DEFAULT_SEGMENT_DURATION_SEC = 4\n\nexport interface RecordingCoordinatorConfig {\n readonly db: RecordingDb\n readonly logger: IScopedLogger\n readonly eventBus: IEventBus\n readonly streamingEngine: IStreamingEngine\n readonly pipelineManager: IPipelineManager\n readonly networkTracker: INetworkQualityTracker\n /**\n * The capability-based storage provider. Used for ALL file operations:\n * - `resolve('recordings', relativePath)` for FFmpeg output paths\n * - `read('recordings', relativePath)` for buffer mode\n * - `delete('recordings', relativePath)` for retention cleanup\n * - `write('recordings', relativePath, data)` for buffered segment flush\n *\n * Session 7 D.4: replaces legacy `fileStorage` + `storagePath` entirely.\n */\n readonly storageProvider: IStorageProvider\n readonly globalFfmpegConfig: Partial<FfmpegConfig>\n readonly detectedFfmpegConfig: Partial<FfmpegConfig>\n /** Global segment duration from system settings (recording.segmentDurationSeconds). */\n readonly segmentDurationSec?: number\n}\n\n// --- Policy evaluation interval ---\n\nconst POLICY_EVAL_INTERVAL_MS = 1000\n\nconst MOTION_FALLBACK_TIMEOUT_MS = 60_000\n\n// --- RecordingCoordinator ---\n\nexport class RecordingCoordinator {\n private readonly db: RecordingDb\n private readonly logger: IScopedLogger\n private readonly eventBus: IEventBus\n private readonly streamingEngine: IStreamingEngine\n private readonly pipelineManager: IPipelineManager\n private readonly networkTracker: INetworkQualityTracker\n private readonly storageProvider: IStorageProvider\n private readonly globalFfmpegConfig: Partial<FfmpegConfig>\n private readonly detectedFfmpegConfig: Partial<FfmpegConfig>\n private readonly segmentDurationSec: number\n\n private readonly recordings = new Map<number, DeviceRecordingState>()\n private policyTimer: ReturnType<typeof setInterval> | null = null\n private readonly retentionManager: RetentionManager\n\n readonly playlistGenerator: PlaylistGenerator\n readonly storageEstimator: StorageEstimator\n\n constructor(config: RecordingCoordinatorConfig) {\n this.db = config.db\n this.logger = config.logger\n this.eventBus = config.eventBus\n this.streamingEngine = config.streamingEngine\n this.pipelineManager = config.pipelineManager\n this.networkTracker = config.networkTracker\n this.storageProvider = config.storageProvider\n this.globalFfmpegConfig = config.globalFfmpegConfig\n this.detectedFfmpegConfig = config.detectedFfmpegConfig\n this.segmentDurationSec = config.segmentDurationSec ?? DEFAULT_SEGMENT_DURATION_SEC\n\n this.retentionManager = new RetentionManager(\n this.db,\n this.logger.child('retention'),\n this.eventBus,\n this.storageProvider,\n )\n this.playlistGenerator = new PlaylistGenerator(this.db)\n this.storageEstimator = new StorageEstimator(this.db, this.networkTracker)\n }\n\n async start(): Promise<void> {\n this.logger.info('RecordingCoordinator starting')\n this.retentionManager.start()\n\n const enabledPolicies = this.db.getEnabledPolicies()\n for (const policy of enabledPolicies) {\n try {\n await this.enableRecording(policy.deviceId, {\n policy: {\n mode: policy.mode,\n streams: policy.streams,\n enabled: policy.enabled,\n preBufferSec: policy.preBufferSec,\n postBufferSec: policy.postBufferSec,\n scheduleRules: policy.scheduleRules,\n },\n })\n } catch (err) {\n this.logger.error('Failed to start recording', { tags: { deviceId: policy.deviceId }, meta: { error: String(err) } })\n }\n }\n\n this.policyTimer = setInterval(() => {\n this.evaluatePolicies()\n }, POLICY_EVAL_INTERVAL_MS)\n\n this.logger.info('RecordingCoordinator started')\n }\n\n stop(): void {\n this.logger.info('RecordingCoordinator stopping')\n\n if (this.policyTimer) {\n clearInterval(this.policyTimer)\n this.policyTimer = null\n }\n\n this.retentionManager.stop()\n\n for (const [deviceId] of this.recordings) {\n this.stopRecordingInternal(deviceId)\n }\n this.recordings.clear()\n\n this.logger.info('RecordingCoordinator stopped')\n }\n\n async enableRecording(deviceId: number, config: RecordingEnableConfig): Promise<void> {\n if (this.recordings.has(deviceId)) {\n this.stopRecordingInternal(deviceId)\n this.recordings.delete(deviceId)\n }\n\n const policy: RecordingPolicy = {\n deviceId,\n mode: config.policy.mode,\n streams: config.policy.streams,\n enabled: config.policy.enabled,\n preBufferSec: config.policy.preBufferSec,\n postBufferSec: config.policy.postBufferSec,\n scheduleRules: config.policy.scheduleRules,\n }\n\n this.db.upsertPolicy({\n deviceId,\n enabled: policy.enabled,\n mode: policy.mode,\n streams: policy.streams,\n preBufferSec: policy.preBufferSec,\n postBufferSec: policy.postBufferSec,\n scheduleRules: policy.scheduleRules,\n })\n\n this.db.cancelCleanup(deviceId)\n\n const ffmpegConfig = resolveFfmpegConfig(\n config.ffmpegOverrides,\n this.globalFfmpegConfig,\n this.detectedFfmpegConfig,\n )\n\n const writerMode = policy.mode === 'motion' ? 'buffer' as const : 'continuous' as const\n\n const writers: SegmentWriter[] = []\n for (const sp of policy.streams) {\n const storageConfig = this.db.resolveStorageConfig(deviceId, `recording:${sp.streamId}` as DataCategory)\n const storageName = storageConfig?.storageName ?? 'recordings'\n const subDirectory = storageConfig?.subDirectory ?? `recordings/${sp.streamId}`\n\n // storagePath resolved dynamically via storageProvider (D.4).\n // storageName may not be a valid StorageLocationType — cast for now,\n // the full location-type refactor is tracked as D.4 follow-up.\n const resolvedStoragePath = await this.storageProvider.resolve({ location: storageName as 'recordings', relativePath: '' })\n\n const writerConfig: SegmentWriterConfig = {\n deviceId,\n streamId: sp.streamId,\n segmentDurationSec: this.segmentDurationSec,\n storagePath: resolvedStoragePath,\n storageName,\n subDirectory,\n ffmpeg: ffmpegConfig,\n mode: writerMode,\n preBufferSec: policy.preBufferSec,\n }\n\n const writer = new SegmentWriter(\n writerConfig,\n this.logger.child(`writer:${deviceId}:${sp.streamId}`),\n this.eventBus,\n this.db,\n this.networkTracker,\n )\n\n const rtspUrl = this.streamingEngine.getStreamUrl(`${policy.deviceId}_${sp.streamId}`, 'rtsp')\n if (rtspUrl) {\n await writer.start(rtspUrl)\n }\n\n writers.push(writer)\n }\n\n const thumbStorageConfig = this.db.resolveStorageConfig(deviceId, 'thumbnail:scrub')\n const thumbStorageName = thumbStorageConfig?.storageName ?? 'recordings'\n const thumbConfig: ThumbnailExtractorConfig = {\n deviceId,\n storagePath: await this.storageProvider.resolve({ location: thumbStorageName as 'recordings', relativePath: '' }),\n storageName: thumbStorageName,\n subDirectory: thumbStorageConfig?.subDirectory ?? 'thumbnails/scrub',\n maxWidthPx: 160,\n jpegQuality: 65,\n }\n\n const thumbnailExtractor = new ThumbnailExtractor(\n thumbConfig,\n this.logger.child(`thumb:${deviceId}`),\n this.db,\n )\n\n const pipeline = this.pipelineManager.getPipeline(deviceId)\n if (pipeline) {\n thumbnailExtractor.attachToPipeline(pipeline, deviceId)\n }\n\n if (policy.mode === 'motion') {\n thumbnailExtractor.setActive(false)\n }\n\n const motionUnsubscribe = this.subscribeToMotionEvents(deviceId, policy)\n\n const state: DeviceRecordingState = {\n deviceId,\n policy,\n writers,\n thumbnailExtractor,\n motionUnsubscribe,\n motionActive: false,\n motionTimeout: null,\n motionFallbackTimeout: null,\n motionReceived: false,\n }\n\n this.recordings.set(deviceId, state)\n\n if (policy.mode === 'motion') {\n state.motionFallbackTimeout = setTimeout(() => {\n const currentState = this.recordings.get(deviceId)\n if (!currentState || currentState.motionReceived) return\n\n this.logger.warn('No motion events received — falling back to continuous recording', {\n tags: { deviceId },\n meta: { timeoutSec: MOTION_FALLBACK_TIMEOUT_MS / 1000 },\n })\n\n this.eventBus.emit({\n id: `recording-policy-fallback-${deviceId}-${Date.now()}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingPolicyFallback,\n data: {\n deviceId,\n originalMode: 'motion',\n fallbackMode: 'continuous',\n reason: 'no_motion_events',\n },\n })\n\n for (const writer of currentState.writers) {\n writer.flushAndContinue().catch(err => {\n this.logger.error('Failed to flush buffer during fallback', { tags: { deviceId }, meta: { error: String(err) } })\n })\n }\n\n currentState.thumbnailExtractor.setActive(true)\n }, MOTION_FALLBACK_TIMEOUT_MS)\n }\n\n this.eventBus.emit({\n id: `recording-started-${deviceId}-${Date.now()}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingStarted,\n data: {\n deviceId,\n mode: policy.mode,\n streams: policy.streams.map(s => s.streamId),\n },\n })\n\n this.logger.info('Recording enabled', { tags: { deviceId }, meta: { mode: policy.mode } })\n }\n\n async disableRecording(deviceId: number): Promise<void> {\n const state = this.recordings.get(deviceId)\n if (!state) {\n this.logger.warn('No active recording', { tags: { deviceId } })\n return\n }\n\n let totalSegmentCount = 0\n let totalSizeBytes = 0\n for (const sp of state.policy.streams) {\n const usage = this.db.getStorageUsage(deviceId, sp.streamId)\n totalSegmentCount += usage.segmentCount\n totalSizeBytes += usage.totalBytes\n }\n const totalMB = Math.round(totalSizeBytes / 1024 / 1024)\n\n this.stopRecordingInternal(deviceId)\n this.recordings.delete(deviceId)\n\n this.db.addToCleanupQueue(deviceId, Date.now())\n\n this.eventBus.emit({\n id: `recording-stopped-${deviceId}-${Date.now()}`,\n timestamp: new Date(),\n source: { type: 'addon', id: 'recording-engine' },\n category: EventCategory.RecordingStopped,\n data: {\n deviceId,\n segmentCount: totalSegmentCount,\n totalMB,\n },\n })\n\n this.logger.info('Recording disabled', { tags: { deviceId }, meta: { segmentCount: totalSegmentCount, totalMB } })\n }\n\n isRecording(deviceId: number): boolean {\n return this.recordings.has(deviceId)\n }\n\n /** Number of devices currently being recorded. */\n getActiveCount(): number {\n return this.recordings.size\n }\n\n evaluatePolicies(): void {\n const now = new Date()\n\n for (const [_deviceId, state] of this.recordings) {\n const { policy } = state\n\n if (policy.mode === 'scheduled' || policy.mode === 'composite') {\n if (!policy.scheduleRules || policy.scheduleRules.length === 0) continue\n\n const matchingRule = policy.scheduleRules.find(rule =>\n RecordingCoordinator.evaluateScheduleRule(rule, now),\n )\n\n if (matchingRule) {\n const targetMode = matchingRule.mode === 'motion' ? 'buffer' as const : 'continuous' as const\n for (const writer of state.writers) {\n if (writer.mode !== targetMode) {\n if (targetMode === 'buffer') {\n writer.switchToBuffer()\n }\n }\n }\n } else {\n for (const writer of state.writers) {\n if (writer.mode !== 'buffer') {\n writer.switchToBuffer()\n }\n }\n }\n }\n }\n }\n\n static evaluateScheduleRule(rule: ScheduleRule, date: Date): boolean {\n const dayOfWeek = date.getDay()\n const timeMinutes = date.getHours() * 60 + date.getMinutes()\n\n const [startH, startM] = rule.startTime.split(':').map(Number) as [number, number]\n const [endH, endM] = rule.endTime.split(':').map(Number) as [number, number]\n const startMinutes = startH * 60 + startM\n const endMinutes = endH * 60 + endM\n\n if (endMinutes > startMinutes) {\n return rule.days.includes(dayOfWeek)\n && timeMinutes >= startMinutes\n && timeMinutes < endMinutes\n }\n\n if (rule.days.includes(dayOfWeek) && timeMinutes >= startMinutes) {\n return true\n }\n\n const previousDay = (dayOfWeek + 6) % 7\n if (rule.days.includes(previousDay) && timeMinutes < endMinutes) {\n return true\n }\n\n return false\n }\n\n private subscribeToMotionEvents(deviceId: number, policy: RecordingPolicy): (() => void) | null {\n if (policy.mode !== 'motion' && policy.mode !== 'composite') {\n return null\n }\n\n return this.eventBus.subscribe(\n { category: `motion.${deviceId}` },\n (event: SystemEvent) => {\n this.handleMotionEvent(deviceId, event)\n },\n )\n }\n\n private handleMotionEvent(deviceId: number, event: SystemEvent): void {\n const state = this.recordings.get(deviceId)\n if (!state) return\n\n if (!state.motionReceived) {\n state.motionReceived = true\n if (state.motionFallbackTimeout) {\n clearTimeout(state.motionFallbackTimeout)\n state.motionFallbackTimeout = null\n }\n }\n\n const motionDetected = event.data.active === true || event.data.type === 'start'\n\n if (motionDetected) {\n state.motionActive = true\n\n if (state.motionTimeout) {\n clearTimeout(state.motionTimeout)\n state.motionTimeout = null\n }\n\n for (const writer of state.writers) {\n writer.flushAndContinue().catch(err => {\n this.logger.error('Failed to flush buffer', { tags: { deviceId }, meta: { error: String(err) } })\n })\n }\n\n state.thumbnailExtractor.setActive(true)\n } else {\n if (state.motionTimeout) {\n clearTimeout(state.motionTimeout)\n }\n\n state.motionTimeout = setTimeout(() => {\n state.motionActive = false\n state.motionTimeout = null\n\n for (const writer of state.writers) {\n writer.switchToBuffer()\n }\n\n if (state.policy.mode === 'motion') {\n state.thumbnailExtractor.setActive(false)\n }\n }, state.policy.postBufferSec * 1000)\n }\n }\n\n private stopRecordingInternal(deviceId: number): void {\n const state = this.recordings.get(deviceId)\n if (!state) return\n\n for (const writer of state.writers) {\n writer.stop()\n }\n\n state.thumbnailExtractor.detachFromPipeline(deviceId)\n\n if (state.motionUnsubscribe) {\n state.motionUnsubscribe()\n }\n\n if (state.motionTimeout) {\n clearTimeout(state.motionTimeout)\n state.motionTimeout = null\n }\n\n if (state.motionFallbackTimeout) {\n clearTimeout(state.motionFallbackTimeout)\n state.motionFallbackTimeout = null\n }\n }\n}\n"],"names":["path","fs","randomBytes","buildFfmpegInputArgs","buildFfmpegOutputArgs","spawn","EventCategory","PlaylistGenerator","StorageEstimator","ffmpegConfig","resolveFfmpegConfig"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-post-analysis",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "CamStack Post-Analysis bundle — recording, enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping 4 addons that consume pipeline output.",
5
5
  "keywords": [
6
6
  "camstack",
@@ -38,7 +38,8 @@
38
38
  "./package.json": "./package.json"
39
39
  },
40
40
  "files": [
41
- "dist"
41
+ "dist",
42
+ "assets"
42
43
  ],
43
44
  "camstack": {
44
45
  "displayName": "Post-Analysis",
@@ -65,7 +66,9 @@
65
66
  {
66
67
  "name": "recording-engine"
67
68
  }
68
- ]
69
+ ],
70
+ "icon": "assets/icon.svg",
71
+ "color": "#9333ea"
69
72
  },
70
73
  {
71
74
  "id": "enrichment-engine",
@@ -76,7 +79,9 @@
76
79
  "placement": "any"
77
80
  },
78
81
  "protected": true,
79
- "capabilities": []
82
+ "capabilities": [],
83
+ "icon": "assets/icon.svg",
84
+ "color": "#9333ea"
80
85
  },
81
86
  {
82
87
  "id": "embedding-encoder",
@@ -91,7 +96,9 @@
91
96
  {
92
97
  "name": "embedding-encoder"
93
98
  }
94
- ]
99
+ ],
100
+ "icon": "assets/icon.svg",
101
+ "color": "#9333ea"
95
102
  },
96
103
  {
97
104
  "id": "pipeline-analytics",
@@ -115,7 +122,9 @@
115
122
  {
116
123
  "name": "addon-widgets-source"
117
124
  }
118
- ]
125
+ ],
126
+ "icon": "assets/icon.svg",
127
+ "color": "#9333ea"
119
128
  }
120
129
  ]
121
130
  },
@@ -1,17 +0,0 @@
1
- const e = "__mf_module_cache__";
2
- globalThis[e] ||= { share: {}, remote: {} };
3
- globalThis[e].share ||= {};
4
- globalThis[e].remote ||= {};
5
- const m = globalThis[e], _ = m.share["@camstack/types"];
6
- if (_ === void 0)
7
- throw new Error("[Module Federation] Shared module @camstack/types was imported before federation bootstrap finished.");
8
- _.__esModule ? _.default : _.default;
9
- const { ACCESSORY_LABEL: a, APPLE_SA_TO_MACRO: t, AUDIO_BACKEND_CHOICES: i, AUDIO_MACRO_LABELS: f, AccessoriesStatusSchema: o, AccessoryKind: c, AddonAutoUpdateSchema: S, AddonListItemSchema: r, AddonPageDeclarationSchema: n, AddonPageInfoSchema: s, AgentLoadSummarySchema: h, AlertSchema: l, AlertSeveritySchema: d, AlertSourceSchema: u, AlertStatusSchema: C, ApiKeyRecordSchema: p, ApiKeySummarySchema: y, ArchiveEntrySchema: A, ArchiveManifestSchema: E, AudioAnalysisResultSchema: b, AudioAnalysisSettingsSchema: g, AudioChunkInputSchema: I, AudioClassSummarySchema: P, AudioClassificationLabelSchema: T, AudioClassificationResultSchema: R, AudioCodecInfoSchema: O, AudioDecodeSessionConfigSchema: D, AudioEncodeSessionConfigSchema: v, AudioEncodedChunkSchema: M, AudioEventSchema: L, AudioLevelSchema: N, AudioMetricsHistoryPointSchema: k, AudioMetricsHistorySchema: B, AudioMetricsSnapshotSchema: U, AudioPcmChunkSchema: F, AuthProviderInfoSchema: w, AuthResultSchema: W, AutoUpdateSettingsSchema: H, AvailableIntegrationTypeSchema: z, BATTERY_DEVICE_PROFILE: K, BackupDestinationInfoSchema: Y, BackupEntrySchema: x, BatteryStatusSchema: G, BoundingBoxSchema: V, BrightnessStatusSchema: Z, BrokerAudioClientSchema: j, BrokerClientsSchema: J, BrokerDecodedClientSchema: Q, BrokerRtspClientSchema: q, BrokerStatsSchema: X, BrokerStatusSchema: $, CAM_PROFILE_ORDER: __, CamProfileSchema: e_, CamStreamKindSchema: m_, CamStreamResolutionSchema: a_, CameraCredentialsSchema: t_, CameraCredentialsStatusSchema: i_, CameraMetricsSchema: f_, CameraMetricsWithDeviceIdSchema: o_, CameraStreamSchema: c_, CapScopeSchema: S_, CapabilityBindingsSchema: r_, ChargingStatus: n_, ClientNetworkStatsSchema: s_, ClusterAddonNodeDeploymentSchema: h_, ClusterAddonStatusEntrySchema: l_, CollectionColumnSchema: d_, CollectionIndexSchema: u_, ConfigEntrySchema: C_, ConfigSectionWithValuesSchema: p_, ConfigTabDeclarationSchema: y_, CreateApiKeyInputSchema: A_, CreateApiKeyResultSchema: E_, CreateIntegrationInputSchema: b_, CreateScopedTokenInputSchema: g_, CreateScopedTokenResultSchema: I_, CreateUserInputSchema: P_, CustomActionInputSchema: T_, DEFAULT_AUDIO_ANALYZER_CONFIG: R_, DEFAULT_DECODER_HWACCEL_CONFIG: O_, DEVICE_PROFILES: D_, DEVICE_SETTINGS_CONTRIBUTION_METHODS: v_, DEVICE_STATUS_METHOD: M_, DecodedFrameSchema: L_, DecoderAssignmentSchema: N_, DecoderSessionConfigSchema: k_, DecoderStatsSchema: B_, DeleteIntegrationResultSchema: U_, DetectorOutputSchema: F_, DeviceDiscoveryStatusSchema: w_, DeviceFeature: W_, DeviceInfoSchema: H_, DeviceNetworkStatsSchema: z_, DeviceRole: K_, DeviceStatusSchema: Y_, DeviceType: x_, DiscoveredChildDeviceSchema: G_, DiscoveredChildStatusSchema: V_, DiscoveredDeviceSchema: Z_, DoorbellPressEventSchema: j_, DoorbellStatusSchema: J_, EmbeddingInfoSchema: Q_, EmbeddingResultSchema: q_, EncodedPacketSchema: X_, EnrichedWidgetMetadataSchema: $_, EventItemSchema: _e, EventKindSchema: ee, ExposedResourceSchema: me, FeatureManifestSchema: ae, FeatureProbeStatusSchema: te, FrameInputSchema: ie, GlobalMetricsSchema: fe, HWACCEL_OPTIONS: oe, HaServiceCallSchema: ce, HaStateSchema: Se, HaStatusSchema: re, HealthStatusSchema: ne, HistoryPointSchema: se, HistoryResolutionEnum: he, InstalledPackageSchema: le, IntegrationLiteSchema: de, IntegrationWithStateSchema: ue, IntercomAbilitySchema: Ce, IntercomStatusSchema: pe, LocationStatSchema: ye, LogEntrySchema: Ae, LogLevelSchema: Ee, LogStreamEntrySchema: be, MODEL_FORMATS: ge, MediaFileSchema: Ie, MethodAccessSchema: Pe, MotionAnalysisResultSchema: Te, MotionEventSchema: Re, MotionOnMotionChangedDataSchema: Oe, MotionRegionSchema: De, MotionSourceEnum: ve, MotionSourcesSchema: Me, MotionStatusSchema: Le, MotionTriggerRuntimeStateSchema: Ne, MotionTriggerStatusSchema: ke, MqttStatusSchema: Be, NativeDetectionSchema: Ue, NativeObjectClassEnum: Fe, NativeObjectDetectionStatusSchema: we, NetworkAccessStatusSchema: We, NetworkAddressSchema: He, NetworkEndpointSchema: ze, NotificationHistoryEntrySchema: Ke, NotificationRuleSchema: Ye, NotificationSchema: xe, ObjectEventSchema: Ge, OrchestratorMetricsSchema: Ve, OsdOverlayKindEnum: Ze, OsdOverlayPatchSchema: je, OsdOverlaySchema: Je, OsdPositionEnum: Qe, OsdStatusSchema: qe, PIPELINE_FLOW_CAPABILITY_NAMES: Xe, PIPELINE_OWNER_CAPABILITY_NAMES: $e, PackageUpdateSchema: _m, PackageVersionInfoSchema: em, PasskeySummarySchema: mm, PcmSampleFormatSchema: am, PerScopeBreakdownSchema: tm, PipelineAssignmentSchema: im, PipelineDefaultStepSchema: fm, PipelineEngineChoiceSchema: om, PipelineRunResultBridge: cm, PipelineStepInputSchema: Sm, PlaceholderReasonSchema: rm, PolygonPointSchema: nm, ProfileSlotSchema: sm, ProfileSlotStatusSchema: hm, ProviderStatusSchema: lm, PtzAutotrackRuntimeStateSchema: dm, PtzAutotrackSettingsSchema: um, PtzAutotrackStatusSchema: Cm, PtzAutotrackTargetOptionSchema: pm, PtzMoveCommandSchema: ym, PtzPositionSchema: Am, PtzPresetSchema: Em, PublishInputSchema: bm, QueryFilterSchema: gm, RegisteredStreamSchema: Im, RemoteAccessEndpointSchema: Pm, RemoteAccessProviderInfoSchema: Tm, ReportMotionInputSchema: Rm, RtspRestreamEntrySchema: Om, RunnerCameraConfigSchema: Dm, RunnerCameraDeviceUIFields: vm, RunnerLocalLoadSchema: Mm, RunnerLocalMetricsSchema: Lm, STORAGE_LOCATION_CARDINALITY: Nm, ScopedTokenSchema: km, ScopedTokenSummarySchema: Bm, SearchResultSchema: Um, SegmentSchema: Fm, SendEmailInputSchema: wm, SendEmailResultSchema: Wm, SettingsPatchSchema: Hm, SettingsRecordSchema: zm, SettingsSchemaWithValuesSchema: Km, SettingsUpdateResultSchema: Ym, SmtpStatusSchema: xm, SnapshotImageSchema: Gm, SpatialDetectionSchema: Vm, SsoBridgeClaimsSchema: Zm, StorageAbortUploadInputSchema: jm, StorageBeginDownloadInputSchema: Jm, StorageBeginDownloadResultSchema: Qm, StorageBeginUploadInputSchema: qm, StorageBeginUploadResultSchema: Xm, StorageEndDownloadInputSchema: $m, StorageFinalizeUploadInputSchema: _a, StorageLocationRefSchema: ea, StorageLocationSchema: ma, StorageLocationTypeSchema: aa, StorageProviderInfoSchema: ta, StorageReadChunkInputSchema: ia, StorageTestLocationResultSchema: fa, StorageWriteChunkInputSchema: oa, StreamFormatSchema: ca, StreamInfoSchema: Sa, StreamNetworkStatsSchema: ra, StreamSourceEntrySchema: na, StreamSourceSchema: sa, SubscribeInputSchema: ha, SubscriptionInfoSchema: la, SwitchStatusSchema: da, SystemMetricsSchema: ua, TestConnectionResultSchema: Ca, ToastSchema: pa, TokenScopeSchema: ya, TopologyNodeSchema: Aa, TopologyProcessSchema: Ea, TopologyServiceSchema: ba, TrackSchema: ga, TrackStateSchema: Ia, TrackedDetectionSchema: Pa, TurnProviderInfoSchema: Ta, TurnServerSchema: Ra, UpdateIntegrationInputSchema: Oa, UpdateUserInputSchema: Da, UserRecordSchema: va, UserSummarySchema: Ma, WELL_KNOWN_TABS: La, WELL_KNOWN_TAB_MAP: Na, WebrtcStreamChoiceSchema: ka, WebrtcStreamTargetSchema: Ba, WidgetHostEnum: Ua, WidgetMetadataSchema: Fa, WidgetSizeEnum: wa, YAMNET_TO_MACRO: Wa, ZoneKindEnum: Ha, ZoneRuleModeEnum: za, ZoneRuleSchema: Ka, ZoneRuleStageEnum: Ya, ZoneRulesArraySchema: xa, ZoneSchema: Ga, ZoneScopeBreakdownSchema: Va, accessoriesCapability: Za, accessoryStableId: ja, addonPagesCapability: Ja, addonPagesSourceCapability: Qa, addonRoutesCapability: qa, addonSettingsCapability: Xa, addonWidgetsCapability: $a, addonWidgetsSourceCapability: _t, addonsCapability: et, adminUiCapability: mt, advancedNotifierCapability: at, alertsCapability: tt, audioAnalysisCapability: it, audioAnalyzerCapability: ft, audioCodecCapability: ot, audioMetricsCapability: ct, authProviderCapability: St, authenticationCapability: rt, backupCapability: nt, batteryCapability: st, brightnessCapability: ht, cameraCredentialsCapability: lt, cameraStreamsCapability: dt, decoderCapability: ut, detectionPipelineCapability: Ct, deviceDiscoveryCapability: pt, deviceManagerCapability: yt, deviceMatchesProfile: At, deviceOpsCapability: Et, deviceProviderCapability: bt, deviceStateCapability: gt, deviceStatusCapability: It, doorbellCapability: Pt, embeddingEncoderCapability: Tt, errMsg: Rt, event: Ot, eventsCapability: Dt, expandCapMethods: vt, featureProbeCapability: Mt, getAudioMacroClassIds: Lt, homeAssistantCapability: Nt, hydrateSchema: kt, integrationsCapability: Bt, intercomCapability: Ut, localNetworkCapability: Ft, logDestinationCapability: wt, mapAudioLabelToMacro: Wt, meshNetworkCapability: Ht, meshOrchestratorCapability: zt, method: Kt, metricsProviderCapability: Yt, motionCapability: xt, motionDetectionCapability: Gt, motionTriggerCapability: Vt, mqttProviderCapability: Zt, nativeObjectDetectionCapability: jt, networkAccessCapability: Jt, networkQualityCapability: Qt, nodesCapability: qt, notificationOutputCapability: Xt, osdCapability: $t, pipelineAnalyticsCapability: _i, pipelineExecutorCapability: ei, pipelineOrchestratorCapability: mi, pipelineRunnerCapability: ai, platformProbeCapability: ti, ptzAutotrackCapability: ii, ptzCapability: fi, rebootCapability: oi, recordingCapability: ci, recordingEngineCapability: Si, remoteAccessCapability: ri, resolveDeviceProfile: ni, restreamerCapability: si, settingsStoreCapability: hi, smtpProviderCapability: li, snapshotCapability: di, snapshotProviderCapability: ui, ssoBridgeCapability: Ci, storageCapability: pi, storageProviderCapability: yi, streamBrokerCapability: Ai, streamingEngineCapability: Ei, switchCapability: bi, systemCapability: gi, toastCapability: Ii, turnOrchestratorCapability: Pi, turnProviderCapability: Ti, userManagementCapability: Ri, userPasskeysCapability: Oi, webrtcCapability: Di, webrtcClientHintsSchema: vi, webrtcSessionCapability: Mi, zoneAnalyticsCapability: Li, zoneRulesCapability: Ni, zonesCapability: ki, ALL_CAPABILITY_DEFINITIONS: Bi, BACKEND_TO_FORMAT: Ui, BaseAddon: Fi, BaseDevice: wi, BaseDeviceProvider: Wi, CAPABILITY_NAMES: Hi, CAPABILITY_ROUTER_KEYS: zi, CAP_NAMES_WITH_STATUS: Ki, COCO_80_LABELS: Yi, COCO_TO_MACRO: xi, DEFAULT_ADDON_GROUP: Gi, DEFAULT_ADDON_PLACEMENT: Vi, DEFAULT_FEATURES: Zi, DEFAULT_LOCATION_SUBDIRS: ji, DEFAULT_RETENTION: Ji, DEVICE_CAP_NAMES: Qi, DEVICE_TYPE_INFO: qi, DeviceConfig: Xi, DeviceRuntimeState: $i, DisposerChain: _f, ElementConfigStore: ef, EventCategory: mf, EventSourceType: af, HF_BASE_URL: tf, HF_REPO: ff, KNOWN_CAP_NAMES: of, MACRO_LABELS: cf, METHOD_ACCESS_MAP: Sf, PYTHON_SCRIPT: rf, RECOGNITION_TYPES: nf, RUNTIME_DEFAULTS: sf, RUNTIME_TO_FORMAT: hf, ReadinessRegistry: lf, ReadinessTimeoutError: df, RingBuffer: uf, SCOPE_PRESETS: Cf, STORAGE_LOCATION_TYPES: pf, STREAM_QUALITY_LABELS: yf, SUB_DETECTION_TYPES: Af, SYSTEM_CAP_NAMES: Ef, SystemMirror: bf, asBoolean: gf, asJsonArray: If, asJsonObject: Pf, asNumber: Tf, asString: Rf, autoAssignProfiles: Of, bindAddonActions: Df, buildAddonRouteProvider: vf, classifyStream: Mf, classifyStreams: Lf, cosineSimilarity: Nf, createDeviceProxy: kf, createEvent: Bf, createLazyTrpcSource: Uf, createMirrorSource: Ff, createRuntimeStateBridge: wf, createSliceHandle: Wf, createSystemProxy: Hf, customAction: zf, defineCustomActions: Kf, emitDownForOwnedCaps: Yf, emitReadiness: xf, evaluateZoneRules: Gf, formatForBackend: Vf, formatForRuntime: Zf, hfModelUrl: jf, isAgentOnlyPlacement: Jf, isDeployableToAgent: Qf, isEvent: qf, maskUrlCredentials: Xf, normalizeAddonInitResult: $f, parseCameraStreamConfig: _o, parseJsonArray: eo, parseJsonObject: mo, parseJsonUnknown: ao, pythonScriptForBackend: to, readinessKey: io, requiresPython: fo, resolveAddonExecution: oo, resolveAddonGroup: co, resolveAddonPlacement: So, resolveAddonRuntime: ro, resolveDetectionRuntime: no, resolveModelFormat: so, runInferenceStep: ho, scopeKey: lo, streamPixels: uo, streamQualityLabel: Co, toDeviceSummary: po, toStreamSourceEntry: yo, zodEntriesToConfigUI: Ao } = _;
10
- export {
11
- Hf as _,
12
- bf as a,
13
- of as b,
14
- mf as c,
15
- kf as d,
16
- Bi as e
17
- };
@@ -1,15 +0,0 @@
1
- const _ = "__mf_module_cache__";
2
- globalThis[_] ||= { share: {}, remote: {} };
3
- globalThis[_].share ||= {};
4
- globalThis[_].remote ||= {};
5
- const t = globalThis[_], e = t.share["@camstack/ui-library"];
6
- if (e === void 0)
7
- throw new Error("[Module Federation] Shared module @camstack/ui-library was imported before federation bootstrap finished.");
8
- e.__esModule ? e.default : e.default;
9
- const { AddonGlobalSettingsForm: s, AgentStepEditor: i, AppShell: r, AudioClassificationList: o, AudioLevelWaveform: m, AudioWaveform: n, BTN_COMPACT: a, BTN_COMPACT_DANGER: u, BTN_COMPACT_PRIMARY: f, BTN_COMPACT_WARNING: c, Badge: l, BatteryBadge: d, BottomSheet: S, Breadcrumb: g, Button: p, CHIP_ACTIVE: v, CHIP_BASE: P, CHIP_INACTIVE: D, CLASS_COLORS: A, CameraStreamPlayer: C, Card: G, Checkbox: h, CodeBlock: M, CollapsibleCard: E, ConfigFormBuilder: R, ConfigFormField: T, ConfigSchemaField: y, ConfirmActionButton: L, ConfirmDialogProvider: b, CustomFieldRenderersProvider: k, DEFAULT_COLOR: O, DataTable: B, DetectionCanvas: I, DetectionOverlay: U, DetectionResultTree: N, DevShell: x, DeviceActivityPanel: F, DeviceCard: w, DeviceContextProvider: W, DeviceGrid: H, DeviceItem: z, DeviceList: Z, Dialog: V, DialogContent: K, DialogDescription: Q, DialogFooter: X, DialogHeader: j, DialogTitle: Y, DialogTrigger: J, DiscoveryPanel: q, DoorbellRecentPanel: $, Dropdown: ee, DropdownContent: _e, DropdownItem: te, DropdownTrigger: se, EmptyState: ie, EventStream: re, FilterBar: oe, FloatingEventStream: me, FloatingLogStream: ne, FloatingPanel: ae, FormField: ue, GRID_GAP: fe, GRID_PAIRED: ce, GRID_QUICK_STATS: le, INPUT_COMPACT: de, IconButton: Se, ImageSelector: ge, InferenceConfigSelector: pe, Input: ve, KebabMenu: Pe, KeyValueList: De, LIST_ROW: Ae, Label: Ce, LogStream: Ge, LoginForm: he, MobileDrawer: Me, NodeMultiSelectField: Ee, NodePicker: Re, NodeSelectField: Te, PHASE_CONFIG: ye, PTZOverlay: Le, PageHeader: be, PhaseIcon: ke, PipelineBuilder: Oe, PipelineRuntimeSelector: Be, PipelineStep: Ie, PipelineTreeMatrix: Ue, PlayerOverlaysProvider: Ne, Popover: xe, PopoverContent: Fe, PopoverTrigger: we, ProviderBadge: We, ResponseLog: He, SECTION_BODY: ze, SECTION_CARD: Ze, SECTION_HEADER: Ve, SPLIT_PANEL_OUTER: Ke, SPLIT_PANEL_SIDE: Qe, STACK_GAP: Xe, ScopePicker: je, ScrollArea: Ye, Select: Je, SemanticBadge: qe, Separator: $e, Sidebar: e_, SidebarItem: __, Skeleton: t_, SlideOverPanel: s_, SnapshotButton: i_, StatCard: r_, StateValuesStream: o_, StatusBadge: m_, StepTimings: n_, StepTreeMaster: a_, StreamPanel: u_, Switch: f_, SystemProvider: c_, TEXT_FIELD_LABEL: l_, TEXT_HINT: d_, TEXT_METRIC: S_, TEXT_SECTION_LABEL: g_, TEXT_VALUE: p_, Tabs: v_, TabsContent: P_, TabsList: D_, TabsTrigger: A_, ThemeProvider: C_, Tooltip: G_, TooltipContent: h_, TooltipTrigger: M_, VersionBadge: E_, WidgetRegistryProvider: R_, WidgetSlot: T_, ZoneEditingProvider: y_, __toESM: L_, buildStepTreeFromSchema: b_, cn: k_, createSharedContext: O_, createTheme: B_, darkColors: I_, defaultTheme: U_, deriveDeviceKind: N_, ensureMfHostInit: x_, getClassColor: F_, getPhaseVisual: w_, isFieldVisible: W_, lightColors: H_, mirror: z_, mountAddonPage: Z_, providerIcons: V_, statusIcons: K_, themeToCss: Q_, trpc: X_, useAccessoriesGetStatus: j_, useAddonPagesListPages: Y_, useAddonSettingsGetDeviceSettings: J_, useAddonSettingsGetGlobalSettings: q_, useAddonSettingsUpdateDeviceSettings: $_, useAddonSettingsUpdateGlobalSettings: et, useAddonWidgetsListWidgets: _t, useAddonsApplyAutoUpdateToAll: tt, useAddonsCustom: st, useAddonsForceRefresh: it, useAddonsGetAddonAutoUpdate: rt, useAddonsGetAutoUpdateSettings: ot, useAddonsGetLogs: mt, useAddonsGetVersions: nt, useAddonsInstallFromWorkspace: at, useAddonsInstallPackage: ut, useAddonsIsWorkspaceAvailable: ft, useAddonsList: ct, useAddonsListPackages: lt, useAddonsListUpdates: dt, useAddonsListWorkspacePackages: St, useAddonsOnAddonLogs: gt, useAddonsReloadPackages: pt, useAddonsRestartAddon: vt, useAddonsRestartServer: Pt, useAddonsRetryLoad: Dt, useAddonsRollbackPackage: At, useAddonsSearchAvailable: Ct, useAddonsSetAddonAutoUpdate: Gt, useAddonsSetAutoUpdateSettings: ht, useAddonsUninstallPackage: Mt, useAddonsUpdatePackage: Et, useAlertsDismiss: Rt, useAlertsEmit: Tt, useAlertsGetUnreadCount: yt, useAlertsList: Lt, useAlertsMarkAllRead: bt, useAlertsMarkRead: kt, useAlertsUpdate: Ot, useAllWidgets: Bt, useAudioAnalysisApplyDeviceSettingsPatch: It, useAudioAnalysisGetDeviceLiveContribution: Ut, useAudioAnalysisGetDeviceSettingsContribution: Nt, useAudioAnalysisResolveDeviceSettings: xt, useAudioAnalyzerAnalyseChunk: Ft, useAudioAnalyzerClassify: wt, useAudioAnalyzerDispose: Wt, useAudioAnalyzerIsReady: Ht, useAudioAnalyzerReprobeAudioEngine: zt, useAudioCodecCanHandle: Zt, useAudioCodecCloseSession: Vt, useAudioCodecCreateDecodeSession: Kt, useAudioCodecCreateEncodeSession: Qt, useAudioCodecFlushEncode: Xt, useAudioCodecListActiveSessions: jt, useAudioCodecListSupportedCodecs: Yt, useAudioCodecPullEncoded: Jt, useAudioCodecPullPcm: qt, useAudioCodecPushEncodedFrame: $t, useAudioCodecPushPcm: es, useAudioMetricsGetCurrentSnapshot: _s, useAudioMetricsGetHistory: ts, useAuthenticationListProviders: ss, useAuthenticationSetProviderEnabled: is, useBackupDelete: rs, useBackupGetEntries: os, useBackupList: ms, useBackupListArchives: ns, useBackupListDestinations: as, useBackupListLocations: us, useBackupPreviewSchedule: fs, useBackupRestore: cs, useBackupTrigger: ls, useBackupUpsertDestinationPolicy: ds, useBatteryGetStatus: Ss, useBrightnessGetStatus: gs, useBrightnessSetBrightness: ps, useCameraStreamsGetBrokerStreams: vs, useCameraStreamsGetCameraStreams: Ps, useCameraStreamsGetRtspEntries: Ds, useClusterNodes: As, useConfirm: Cs, useCustomFieldRenderer: Gs, useDebouncedString: hs, useDecoderCreateSession: Ms, useDecoderDestroySession: Es, useDecoderGetInfo: Rs, useDecoderGetStats: Ts, useDecoderListActiveSessions: ys, useDecoderOpenStream: Ls, useDecoderPullFrames: bs, useDecoderPushPacket: ks, useDecoderReprobeHwaccel: Os, useDecoderSupportsCodec: Bs, useDecoderUpdateConfig: Is, useDetectionPipelineApplyDeviceSettingsPatch: Us, useDetectionPipelineGetDeviceLiveContribution: Ns, useDetectionPipelineGetDeviceSettingsContribution: xs, useDevShell: Fs, useDevice: ws, useDeviceBattery: Ws, useDeviceCapability: Hs, useDeviceDetections: zs, useDeviceDiscoveryAdoptDevice: Zs, useDeviceDiscoveryGetStatus: Vs, useDeviceDiscoveryListDiscovered: Ks, useDeviceDiscoveryRefreshDiscovery: Qs, useDeviceDiscoveryReleaseDevice: Xs, useDeviceId: js, useDeviceManagerAddLocation: Ys, useDeviceManagerAdoptDevice: Js, useDeviceManagerAllocateDeviceId: qs, useDeviceManagerCreateDevice: $s, useDeviceManagerDisable: ei, useDeviceManagerDiscoverDevices: _i, useDeviceManagerEnable: ti, useDeviceManagerGetAllBindings: si, useDeviceManagerGetBindings: ii, useDeviceManagerGetChildren: ri, useDeviceManagerGetConfigSchema: oi, useDeviceManagerGetCreationSchema: mi, useDeviceManagerGetDevice: ni, useDeviceManagerGetDeviceAggregate: ai, useDeviceManagerGetDeviceLiveInfoAggregate: ui, useDeviceManagerGetDeviceSettingsAggregate: fi, useDeviceManagerGetDeviceStatusAggregate: ci, useDeviceManagerGetSettingsSchema: li, useDeviceManagerGetStreamProfileMap: di, useDeviceManagerGetStreamSources: Si, useDeviceManagerListAll: gi, useDeviceManagerListBindableCapsForDeviceType: pi, useDeviceManagerListLocations: vi, useDeviceManagerListPersistedByAddon: Pi, useDeviceManagerListWrappersForCap: Di, useDeviceManagerLoadConfig: Ai, useDeviceManagerLoadMeta: Ci, useDeviceManagerLoadRuntimeState: Gi, useDeviceManagerPersistConfig: hi, useDeviceManagerProbeStreams: Mi, useDeviceManagerRegisterDevice: Ei, useDeviceManagerRemove: Ri, useDeviceManagerRemoveDevice: Ti, useDeviceManagerRemoveLocation: yi, useDeviceManagerSetDisabled: Li, useDeviceManagerSetLocation: bi, useDeviceManagerSetMetadata: ki, useDeviceManagerSetName: Oi, useDeviceManagerSetStreamProfileMap: Bi, useDeviceManagerSetWrapperActive: Ii, useDeviceManagerTestCreationField: Ui, useDeviceManagerTestField: Ni, useDeviceManagerUpdateConfig: xi, useDeviceManagerUpdateDeviceField: Fi, useDeviceManagerUpdateDeviceFieldsBatch: wi, useDeviceOpsGetConfigEntries: Wi, useDeviceOpsGetSettingsSchema: Hi, useDeviceOpsGetStreamSources: zi, useDeviceOpsRemoveDevice: Zi, useDeviceOpsSetConfig: Vi, useDeviceProviderAdoptDiscoveredDevice: Ki, useDeviceProviderCreateDevice: Qi, useDeviceProviderDiscoverDevices: Xi, useDeviceProviderGetChildCreationSchema: ji, useDeviceProviderGetDevices: Yi, useDeviceProviderGetStatus: Ji, useDeviceProviderStart: qi, useDeviceProviderStop: $i, useDeviceProviderSupportsDiscovery: er, useDeviceProviderSupportsManualCreation: _r, useDeviceProviderTestCreationField: tr, useDeviceProxy: sr, useDeviceSnapshot: ir, useDeviceSnapshotImage: rr, useDeviceState: or, useDeviceStateGetAllSnapshots: mr, useDeviceStateGetCapSlice: nr, useDeviceStateGetSnapshot: ar, useDeviceStateSetCapSlice: ur, useDeviceStateSlice: fr, useDeviceWebrtc: cr, useDevices: lr, useDoorbellEvents: dr, useDoorbellGetStatus: Sr, useEventInvalidation: gr, useEventStreamLatest: pr, useEventStreamMap: vr, useEventsGetEventClipUrl: Pr, useEventsGetEventThumbnail: Dr, useEventsGetEvents: Ar, useIntegrationsCreate: Cr, useIntegrationsDelete: Gr, useIntegrationsGet: hr, useIntegrationsGetAvailableTypes: Mr, useIntegrationsGetByAddonId: Er, useIntegrationsGetSettings: Rr, useIntegrationsList: Tr, useIntegrationsSetSettings: yr, useIntegrationsTestConnection: Lr, useIntegrationsUpdate: br, useIntercomGetStatus: kr, useIntercomHandleAnswer: Or, useIntercomStartSession: Br, useIntercomStopSession: Ir, useIsMidWidth: Ur, useIsMobile: Nr, useLiveBuffer: xr, useLiveEvent: Fr, useLocalNetworkGetAllowedAddresses: wr, useLocalNetworkGetConnectionEndpoints: Wr, useLocalNetworkGetPreferred: Hr, useLocalNetworkList: zr, useLocalNetworkResetAllowlistToBestMatch: Zr, useLocalNetworkSetAllowedAddresses: Vr, useMeshOrchestratorJoinProvider: Kr, useMeshOrchestratorLeaveProvider: Qr, useMeshOrchestratorListProviders: Xr, useMetricsProviderCollectSnapshot: jr, useMetricsProviderGetAddonStats: Yr, useMetricsProviderGetCached: Jr, useMetricsProviderGetCpuTemperature: qr, useMetricsProviderGetCurrent: $r, useMetricsProviderGetDiskSpace: eo, useMetricsProviderGetGpuInfo: _o, useMetricsProviderGetProcessStats: to, useMetricsProviderKillProcess: so, useMetricsProviderListAddonInstances: io, useMetricsProviderListNodeProcesses: ro, useMotionDetectionAnalyze: oo, useMotionDetectionApplyDeviceSettingsPatch: mo, useMotionDetectionGetDeviceLiveContribution: no, useMotionDetectionGetDeviceSettingsContribution: ao, useMotionDetectionRemoveCamera: uo, useMotionDetectionReset: fo, useMotionGetStatus: co, useMotionIsDetected: lo, useMotionTriggerGetStatus: So, useMotionTriggerSetMotionTrigger: go, useNativeObjectDetectionGetStatus: po, useNetworkQualityGetAllStats: vo, useNetworkQualityGetDeviceStats: Po, useNetworkQualityReportClientStats: Do, useNodesClusterAddonStatus: Ao, useNodesDeployAddon: Co, useNodesExecuteQuery: Go, useNodesRenameNode: ho, useNodesRestartAddon: Mo, useNodesRestartNode: Eo, useNodesRestartProcess: Ro, useNodesSetProcessLogLevel: To, useNodesShutdownNode: yo, useNodesTopology: Lo, useNodesUndeployAddon: bo, useNotificationOutputSend: ko, useNotificationOutputSendTest: Oo, useOptionalSystem: Bo, useOptionalWidgetRegistry: Io, useOsdGetStatus: Uo, useOsdSetOverlay: No, usePTZ: xo, usePipelineAnalyticsApplyDeviceSettingsPatch: Fo, usePipelineAnalyticsClearTracks: wo, usePipelineAnalyticsGetActiveTracks: Wo, usePipelineAnalyticsGetAudioEvents: Ho, usePipelineAnalyticsGetDeviceLiveContribution: zo, usePipelineAnalyticsGetDeviceSettingsContribution: Zo, usePipelineAnalyticsGetEventMedia: Vo, usePipelineAnalyticsGetMotionEvents: Ko, usePipelineAnalyticsGetObjectEvents: Qo, usePipelineAnalyticsGetTrack: Xo, usePipelineAnalyticsGetTrackMedia: jo, usePipelineAnalyticsListTracks: Yo, usePipelineExecutorCacheFrameInPool: Jo, usePipelineExecutorDeleteModel: qo, usePipelineExecutorDeleteTemplate: $o, usePipelineExecutorDetect: em, usePipelineExecutorDownloadModel: _m, usePipelineExecutorGetAddonModels: tm, usePipelineExecutorGetAudioCapabilities: sm, usePipelineExecutorGetAvailableEngines: im, usePipelineExecutorGetCapabilities: rm, usePipelineExecutorGetDefaultSteps: om, usePipelineExecutorGetDetectionConfigSchema: mm, usePipelineExecutorGetEffectiveTuning: nm, usePipelineExecutorGetGlobalPipelineConfig: am, usePipelineExecutorGetGlobalSteps: um, usePipelineExecutorGetOrchestratorConfigSchema: fm, usePipelineExecutorGetReferenceAudio: cm, usePipelineExecutorGetReferenceAudioFiles: lm, usePipelineExecutorGetReferenceImage: dm, usePipelineExecutorGetSchema: Sm, usePipelineExecutorGetSelectedEngine: gm, usePipelineExecutorGetVideoPipelineSteps: pm, usePipelineExecutorInferCached: vm, usePipelineExecutorKillEngine: Pm, usePipelineExecutorListLoadedEngines: Dm, usePipelineExecutorListReferenceImages: Am, usePipelineExecutorListTemplates: Cm, usePipelineExecutorReprobeEngine: Gm, usePipelineExecutorRunAudioTest: hm, usePipelineExecutorRunPipeline: Mm, usePipelineExecutorRunPipelineBatch: Em, usePipelineExecutorSaveTemplate: Rm, usePipelineExecutorSetVideoPipelineSteps: Tm, usePipelineExecutorSpinEngine: ym, usePipelineExecutorUncacheFrame: Lm, usePipelineExecutorUpdateTemplate: bm, usePipelineOrchestratorApplyDeviceSettingsPatch: km, usePipelineOrchestratorAssignAudio: Om, usePipelineOrchestratorAssignDecoder: Bm, usePipelineOrchestratorAssignPipeline: Im, usePipelineOrchestratorDeleteTemplate: Um, usePipelineOrchestratorGetAgentLoad: Nm, usePipelineOrchestratorGetAgentSettings: xm, usePipelineOrchestratorGetAudioAssignment: Fm, usePipelineOrchestratorGetAudioAssignments: wm, usePipelineOrchestratorGetAudioNodeLoad: Wm, usePipelineOrchestratorGetCameraMetrics: Hm, usePipelineOrchestratorGetCameraSettings: zm, usePipelineOrchestratorGetCameraStepOverrides: Zm, usePipelineOrchestratorGetCapabilityBindings: Vm, usePipelineOrchestratorGetDecoderAssignment: Km, usePipelineOrchestratorGetDecoderAssignments: Qm, usePipelineOrchestratorGetDeviceLiveContribution: Xm, usePipelineOrchestratorGetDeviceSettingsContribution: jm, usePipelineOrchestratorGetGlobalMetrics: Ym, usePipelineOrchestratorGetPipelineAssignment: Jm, usePipelineOrchestratorGetPipelineAssignments: qm, usePipelineOrchestratorListAgentSettings: $m, usePipelineOrchestratorListTemplates: en, usePipelineOrchestratorRebalance: _n, usePipelineOrchestratorRemoveAgentSettings: tn, usePipelineOrchestratorResolvePipeline: sn, usePipelineOrchestratorSaveTemplate: rn, usePipelineOrchestratorSetAgentAddonDefaults: on, usePipelineOrchestratorSetCameraPipelineForAgent: mn, usePipelineOrchestratorSetCameraStepOverride: nn, usePipelineOrchestratorSetCameraStepToggle: an, usePipelineOrchestratorSetCapabilityBinding: un, usePipelineOrchestratorUnassignAudio: fn, usePipelineOrchestratorUnassignDecoder: cn, usePipelineOrchestratorUnassignPipeline: ln, usePipelineOrchestratorUpdateTemplate: dn, usePipelineRunnerAttachCamera: Sn, usePipelineRunnerDetachCamera: gn, usePipelineRunnerGetAllCameraMetrics: pn, usePipelineRunnerGetCameraMetrics: vn, usePipelineRunnerGetLocalCameras: Pn, usePipelineRunnerGetLocalLoad: Dn, usePipelineRunnerGetLocalMetrics: An, usePipelineRunnerReportMotion: Cn, usePlatformProbeGetCapabilities: Gn, usePlatformProbeGetHardware: hn, usePlatformProbeResolveHwAccel: Mn, usePlatformProbeResolveInferenceConfig: En, usePlayerOverlayLayer: Rn, usePlayerOverlayLayers: Tn, usePlayerToolbarButton: yn, usePlayerToolbarButtons: Ln, usePtzAutotrackGetSettings: bn, usePtzAutotrackGetStatus: kn, usePtzAutotrackSetEnabled: On, usePtzAutotrackSetSettings: Bn, usePtzContinuousMove: In, usePtzGetPosition: Un, usePtzGetPresets: Nn, usePtzGetStatus: xn, usePtzGoHome: Fn, usePtzGoToPreset: wn, usePtzMove: Wn, usePtzStop: Hn, useRebootReboot: zn, useRecordingEngineDisable: Zn, useRecordingEngineEnable: Vn, useRecordingEngineEstimateGlobalStorage: Kn, useRecordingEngineEstimateStorage: Qn, useRecordingEngineGetAvailability: Xn, useRecordingEngineGetConfig: jn, useRecordingEngineGetMotionStats: Yn, useRecordingEngineGetPlaylist: Jn, useRecordingEngineGetPolicy: qn, useRecordingEngineGetPolicyStatus: $n, useRecordingEngineGetRetentionConfig: ea, useRecordingEngineGetSegments: _a, useRecordingEngineGetStatus: ta, useRecordingEngineGetStorageUsage: sa, useRecordingEngineGetThumbnail: ia, useRecordingEngineSetPolicy: ra, useRecordingEngineUpdateConfig: oa, useRecordingEngineUpdateRetentionConfig: ma, useRecordingGetPlaybackUrl: na, useRecordingGetSegments: aa, useRecordingGetThumbnailAt: ua, useRemoteAccessListProviders: fa, useRemoteAccessStartProvider: ca, useRemoteAccessStopProvider: la, useSettingsStoreCount: da, useSettingsStoreDeclareCollection: Sa, useSettingsStoreDelete: ga, useSettingsStoreGet: pa, useSettingsStoreInsert: va, useSettingsStoreIsEmpty: Pa, useSettingsStoreQuery: Da, useSettingsStoreSet: Aa, useSettingsStoreUpdate: Ca, useSnapshotApplyDeviceSettingsPatch: Ga, useSnapshotGetDeviceLiveContribution: ha, useSnapshotGetDeviceSettingsContribution: Ma, useSnapshotGetSnapshot: Ea, useSnapshotGetStatus: Ra, useSnapshotInvalidateCache: Ta, useSnapshotProviderGetSnapshot: ya, useSnapshotProviderSupportsDevice: La, useStorageAbortUpload: ba, useStorageBeginDownload: ka, useStorageBeginUpload: Oa, useStorageDelete: Ba, useStorageDeleteLocation: Ia, useStorageEndDownload: Ua, useStorageExists: Na, useStorageFinalizeUpload: xa, useStorageGetAvailableSpace: Fa, useStorageGetDefaultLocation: wa, useStorageList: Wa, useStorageListLocations: Ha, useStorageListProviders: za, useStorageRead: Za, useStorageReadChunk: Va, useStorageResolve: Ka, useStorageTestConfig: Qa, useStorageTestLocation: Xa, useStorageUpsertLocation: ja, useStorageWrite: Ya, useStorageWriteChunk: Ja, useStreamBrokerApplyDeviceSettingsPatch: qa, useStreamBrokerAssignProfile: $a, useStreamBrokerGetAllRtspEntries: eu, useStreamBrokerGetBroker: _u, useStreamBrokerGetBrokerStats: tu, useStreamBrokerGetDeviceLiveContribution: su, useStreamBrokerGetDeviceSettingsContribution: iu, useStreamBrokerGetPreBufferInfo: ru, useStreamBrokerGetRtspEntry: ou, useStreamBrokerGetRtspPort: mu, useStreamBrokerGetStreamUrl: nu, useStreamBrokerIsRtspEnabled: au, useStreamBrokerKillClient: uu, useStreamBrokerListAllCameraStreams: fu, useStreamBrokerListAllProfileSlots: cu, useStreamBrokerListClients: lu, useStreamBrokerPublishCameraStream: du, useStreamBrokerRegenerateRtspToken: Su, useStreamBrokerRestartProfile: gu, useStreamBrokerRetractCameraStream: pu, useStreamBrokerSetPreBufferDuration: vu, useStreamBrokerSetRtspEnabled: Pu, useStreamBrokerUnassignProfile: Du, useSwitchGetStatus: Au, useSwitchSetState: Cu, useSystem: Gu, useSystemFeatureFlags: hu, useSystemForceRetentionCleanup: Mu, useSystemGetRetentionConfig: Eu, useSystemHealth: Ru, useSystemInfo: Tu, useSystemMutation: yu, useSystemNetworkAddresses: Lu, useSystemQuery: bu, useSystemSetRetentionConfig: ku, useThemeMode: Ou, useToastOnToast: Bu, useTurnOrchestratorGetAllServers: Iu, useTurnOrchestratorListProviders: Uu, useTurnOrchestratorSetProviderEnabled: Nu, useTurnProviderGetTurnServers: xu, useUserManagementConfirmTotp: Fu, useUserManagementCreateApiKey: wu, useUserManagementCreateScopedToken: Wu, useUserManagementCreateUser: Hu, useUserManagementDeleteUser: zu, useUserManagementDisableTotp: Zu, useUserManagementGetTotpStatus: Vu, useUserManagementListApiKeys: Ku, useUserManagementListScopedTokens: Qu, useUserManagementListUsers: Xu, useUserManagementResetPassword: ju, useUserManagementRevokeApiKey: Yu, useUserManagementRevokeScopedToken: Ju, useUserManagementSetUserScopes: qu, useUserManagementSetupTotp: $u, useUserManagementUpdateUser: ef, useUserManagementValidateApiKey: _f, useUserManagementValidateCredentials: tf, useUserManagementValidateScopedToken: sf, useUserManagementVerifyTotp: rf, useWebrtcSessionCloseSession: of, useWebrtcSessionCreateSession: mf, useWebrtcSessionHandleAnswer: nf, useWebrtcSessionHasAdaptiveBitrate: af, useWebrtcSessionListStreams: uf, useWidget: ff, useWidgetMetadata: cf, useWidgetRegistry: lf, useZoneAnalyticsGetCameraHistory: df, useZoneAnalyticsGetCurrentSnapshot: Sf, useZoneAnalyticsGetUnzonedHistory: gf, useZoneAnalyticsGetZoneHistory: pf, useZoneEditing: vf, useZoneRulesListRules: Pf, useZoneRulesSetRules: Df, useZonesAddZone: Af, useZonesListZones: Cf, useZonesRemoveZone: Gf, useZonesUpdateZone: hf, validateScopes: Mf } = e;
10
- export {
11
- Gu as _,
12
- sr as a,
13
- or as b,
14
- fr as c
15
- };