@camstack/addon-post-analysis 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/enrichment-engine/index.js +1 -1
- package/dist/enrichment-engine/index.mjs +1 -1
- package/dist/{index-DafwGlkQ.js → index-B0RhVv1c.js} +3940 -807
- package/dist/index-B0RhVv1c.js.map +1 -0
- package/dist/{index-CIJfmsWX.mjs → index-ot5PeFg_.mjs} +3943 -810
- package/dist/index-ot5PeFg_.mjs.map +1 -0
- package/dist/pipeline-analytics/@mf-types.zip +0 -0
- package/dist/pipeline-analytics/{__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-h5aXOPSA.mjs → __mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-lantnv8e.mjs} +1 -1
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-BD3oMNGB.mjs +29 -0
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-BgOHCakr.mjs +18 -0
- package/dist/pipeline-analytics/{__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs-D-USVuHq.mjs → __mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs-D1qPKjvR.mjs} +3 -1
- package/dist/pipeline-analytics/{__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-qQCPW8pT.mjs → __mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-B5X50Xa4.mjs} +1 -1
- package/dist/pipeline-analytics/{__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-Bv9bYz9E.mjs → __mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-B10b5k5J.mjs} +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -3
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-B3kCe2qM.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DWB3apaJ.mjs} +6 -6
- package/dist/pipeline-analytics/{client-DHmQcIWy.mjs → client-C6xdgLZU.mjs} +2 -2
- package/dist/pipeline-analytics/{hostInit-CuWzic_f.mjs → hostInit-3cyL9eyG.mjs} +12 -12
- package/dist/pipeline-analytics/{index-BA65ZJOW.mjs → index-BCTHeI2m.mjs} +254 -268
- package/dist/pipeline-analytics/{index-Crs1D0Uu.mjs → index-BuWLz0GG.mjs} +1 -1
- package/dist/pipeline-analytics/{index-gpelkpEE.mjs → index-CIwq-tQL.mjs} +1 -1
- package/dist/pipeline-analytics/{index-CHnXxMRA.mjs → index-CWBMDbou.mjs} +1 -1
- package/dist/pipeline-analytics/index-CZhagnlH.mjs +67784 -0
- package/dist/pipeline-analytics/{index-DicaGC31.mjs → index-D883Q5B8.mjs} +1 -1
- package/dist/pipeline-analytics/index-DtOI1aTU.mjs +18504 -0
- package/dist/pipeline-analytics/index.js +605 -42
- package/dist/pipeline-analytics/index.js.map +1 -1
- package/dist/pipeline-analytics/index.mjs +604 -42
- package/dist/pipeline-analytics/index.mjs.map +1 -1
- package/dist/pipeline-analytics/{jsx-runtime-Wcfyyyt4.mjs → jsx-runtime-DdLhuHmJ.mjs} +1 -1
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/dist/pipeline-analytics/{schemas-ChN4Ih0h.mjs → schemas-B7L0qZtq.mjs} +530 -515
- package/package.json +12 -27
- package/dist/ffmpeg-config-DRONlBsj.mjs +0 -56
- package/dist/ffmpeg-config-DRONlBsj.mjs.map +0 -1
- package/dist/ffmpeg-config-uANz3sV5.js +0 -73
- package/dist/ffmpeg-config-uANz3sV5.js.map +0 -1
- package/dist/index-CIJfmsWX.mjs.map +0 -1
- package/dist/index-DafwGlkQ.js.map +0 -1
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-d8PmLbO2.mjs +0 -19
- package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-BcWYbuKp.mjs +0 -18
- package/dist/pipeline-analytics/index-CUXiTSWS.mjs +0 -13883
- package/dist/pipeline-analytics/index-gbflFMEY.mjs +0 -36403
- package/dist/playlist-generator-EhPaB7Hn.js +0 -48
- package/dist/playlist-generator-EhPaB7Hn.js.map +0 -1
- package/dist/playlist-generator-VTkgn53O.mjs +0 -48
- package/dist/playlist-generator-VTkgn53O.mjs.map +0 -1
- package/dist/recording/index.js +0 -257
- package/dist/recording/index.js.map +0 -1
- package/dist/recording/index.mjs +0 -235
- package/dist/recording/index.mjs.map +0 -1
- package/dist/recording-coordinator-BKsM_JGg.js +0 -1052
- package/dist/recording-coordinator-BKsM_JGg.js.map +0 -1
- package/dist/recording-coordinator-Bw3N1gYu.mjs +0 -1012
- package/dist/recording-coordinator-Bw3N1gYu.mjs.map +0 -1
- package/dist/recording-db-gOgaoQh0.js +0 -348
- package/dist/recording-db-gOgaoQh0.js.map +0 -1
- package/dist/recording-db-lIkSMTLq.mjs +0 -348
- package/dist/recording-db-lIkSMTLq.mjs.map +0 -1
- package/dist/recording-service-facade-B9lG6OFn.mjs +0 -123
- package/dist/recording-service-facade-B9lG6OFn.mjs.map +0 -1
- package/dist/recording-service-facade-Do1PKlAL.js +0 -123
- package/dist/recording-service-facade-Do1PKlAL.js.map +0 -1
- package/dist/storage-estimator-CRpoQc9j.js +0 -72
- package/dist/storage-estimator-CRpoQc9j.js.map +0 -1
- package/dist/storage-estimator-DzD8gWJH.mjs +0 -72
- package/dist/storage-estimator-DzD8gWJH.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"recording-coordinator-BKsM_JGg.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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"}
|