@camstack/addon-pipeline 0.1.0 → 0.1.2
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/{addon-CwDFZWAb.d.cts → addon-DK7eQ0PN.d.cts} +3 -4
- package/dist/{addon-CwDFZWAb.d.ts → addon-DK7eQ0PN.d.ts} +3 -4
- package/dist/addon.cjs +27 -72
- package/dist/addon.cjs.map +1 -1
- package/dist/addon.d.cts +1 -1
- package/dist/addon.d.ts +1 -1
- package/dist/addon.js +27 -72
- package/dist/addon.js.map +1 -1
- package/dist/index.cjs +29 -3304
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -921
- package/dist/index.d.ts +4 -921
- package/dist/index.js +28 -3271
- package/dist/index.js.map +1 -1
- package/package.json +34 -17
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/recording/ffmpeg-config.ts","../src/recording/recording-db.ts","../src/recording/segment-writer.ts","../src/recording/thumbnail-extractor.ts","../src/recording/retention-manager.ts","../src/recording/playlist-generator.ts","../src/recording/storage-estimator.ts","../src/recording/recording-coordinator.ts","../src/stream-broker/decoder-registry.ts","../src/stream-broker/ffmpeg-decoder-session.ts","../src/stream-broker/frame-dropper.ts","../src/stream-broker/ffmpeg-decoder-provider.ts","../src/stream-broker/stream-broker.ts","../src/stream-broker/stream-pipe-server.ts","../src/stream-broker/stream-broker-manager.ts","../src/persistence/event-persistence.ts","../src/persistence/known-faces.ts","../src/persistence/session-tracker.ts","../src/persistence/retention.ts","../src/persistence/track-trail.ts","../src/addon.ts","../src/index.ts","../src/analysis/analysis-addon.ts","../src/webrtc/types.ts","../src/webrtc/nal-utils.ts","../src/webrtc/h264-utils.ts","../src/webrtc/h265-utils.ts","../src/webrtc/fanout.ts","../src/webrtc/ffmpeg-process.ts","../src/webrtc/frame-source.ts","../src/webrtc/ffmpeg-source.ts","../src/webrtc/adaptive-controller.ts","../src/webrtc/session.ts","../src/webrtc/rtsp-relay.ts","../src/webrtc/shared-session.ts","../src/webrtc/server.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process'\nimport * as os from 'node:os'\nimport type { FfmpegConfig } from '@camstack/types'\n\n/**\n * Probe ffmpeg binary and detect optimal hwaccel for the host platform.\n * Called once at server startup, result cached.\n */\nexport function detectPlatformDefaults(ffmpegPath: string = 'ffmpeg'): Partial<FfmpegConfig> {\n let hwaccels: string[] = []\n try {\n const output = execFileSync(ffmpegPath, ['-hwaccels', '-hide_banner'], { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] })\n hwaccels = output.split('\\n').map(l => l.trim()).filter(l => l && l !== 'Hardware acceleration methods:')\n } catch {}\n\n const platform = os.platform()\n if (platform === 'darwin' && hwaccels.includes('videotoolbox')) return { hwaccel: 'videotoolbox', threads: 0 }\n if (platform === 'linux') {\n for (const hw of ['vaapi', 'qsv', 'cuda', 'v4l2m2m'] as const) {\n if (hwaccels.includes(hw)) return { hwaccel: hw, threads: 0 }\n }\n }\n return { hwaccel: 'none', threads: Math.max(1, Math.floor(os.cpus().length / 2)) }\n}\n\n/**\n * Resolve ffmpeg config for a device.\n * Merge order: detected <- global <- per-device (most specific wins).\n */\nexport function resolveFfmpegConfig(\n deviceConfig: Partial<FfmpegConfig> | undefined,\n globalConfig: Partial<FfmpegConfig>,\n detected: Partial<FfmpegConfig>,\n): FfmpegConfig {\n return {\n path: deviceConfig?.path ?? globalConfig.path ?? detected.path ?? 'ffmpeg',\n hwaccel: deviceConfig?.hwaccel ?? globalConfig.hwaccel ?? detected.hwaccel ?? 'none',\n inputArgs: deviceConfig?.inputArgs ?? globalConfig.inputArgs,\n outputArgs: deviceConfig?.outputArgs ?? globalConfig.outputArgs,\n videoCodec: deviceConfig?.videoCodec ?? globalConfig.videoCodec ?? 'copy',\n audioCodec: deviceConfig?.audioCodec ?? globalConfig.audioCodec ?? 'copy',\n threads: deviceConfig?.threads ?? globalConfig.threads ?? detected.threads,\n }\n}\n\n/**\n * Build common ffmpeg args from a resolved config.\n */\nexport function buildFfmpegInputArgs(config: FfmpegConfig, inputUrl: string): string[] {\n const args: string[] = ['-hide_banner', '-loglevel', 'warning']\n if (config.hwaccel && config.hwaccel !== 'none') args.push('-hwaccel', config.hwaccel)\n if (config.threads !== undefined) args.push('-threads', String(config.threads))\n if (config.inputArgs?.length) args.push(...config.inputArgs)\n args.push('-rtsp_transport', 'tcp', '-i', inputUrl)\n return args\n}\n\nexport function buildFfmpegOutputArgs(config: FfmpegConfig): string[] {\n const args: string[] = ['-c:v', config.videoCodec ?? 'copy', '-c:a', config.audioCodec ?? 'copy']\n if (config.outputArgs?.length) args.push(...config.outputArgs)\n return args\n}\n","import type Database from 'better-sqlite3'\nimport type {\n RecordingSegment, RecordingThumbnail, RecordingStorageConfig,\n RecordingPolicy, RecordingMode, StreamPolicy, ScheduleRule,\n CleanupQueueEntry, CleanupStatus, DataCategory,\n} from './types'\n\nexport interface StorageUsage {\n readonly totalBytes: number\n readonly segmentCount: number\n}\n\nexport interface AvailabilityRange {\n readonly startTime: number\n readonly endTime: number\n readonly streams: readonly string[]\n}\n\ninterface PolicyRow {\n readonly device_id: string\n readonly enabled: number\n readonly mode: string\n readonly streams_json: string\n readonly schedule_json: string | null\n readonly pre_buffer_sec: number\n readonly post_buffer_sec: number\n}\n\nexport class RecordingDb {\n constructor(private readonly db: Database.Database) {}\n\n initialize(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS recording_segments (\n id TEXT PRIMARY KEY,\n device_id TEXT NOT NULL,\n stream_id TEXT NOT NULL,\n start_time INTEGER NOT NULL,\n end_time INTEGER NOT NULL,\n duration REAL NOT NULL,\n path TEXT NOT NULL,\n storage_name TEXT NOT NULL,\n sub_directory TEXT NOT NULL,\n size_bytes INTEGER NOT NULL,\n codec TEXT NOT NULL,\n has_audio INTEGER NOT NULL DEFAULT 0\n );\n CREATE INDEX IF NOT EXISTS idx_segments_device_time ON recording_segments(device_id, stream_id, start_time);\n CREATE INDEX IF NOT EXISTS idx_segments_start_time ON recording_segments(start_time);\n\n CREATE TABLE IF NOT EXISTS recording_thumbnails (\n device_id TEXT NOT NULL,\n timestamp INTEGER NOT NULL,\n path TEXT NOT NULL,\n storage_name TEXT NOT NULL,\n sub_directory TEXT NOT NULL,\n size_bytes INTEGER NOT NULL,\n category TEXT NOT NULL,\n PRIMARY KEY (device_id, timestamp, category)\n );\n\n CREATE TABLE IF NOT EXISTS recording_policies (\n device_id TEXT PRIMARY KEY,\n enabled INTEGER NOT NULL DEFAULT 0,\n mode TEXT NOT NULL,\n streams_json TEXT NOT NULL,\n schedule_json TEXT,\n pre_buffer_sec INTEGER NOT NULL DEFAULT 5,\n post_buffer_sec INTEGER NOT NULL DEFAULT 10,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS recording_storage_config (\n device_id TEXT NOT NULL,\n data_category TEXT NOT NULL,\n storage_name TEXT NOT NULL,\n sub_directory TEXT NOT NULL,\n retention_days INTEGER,\n retention_gb REAL,\n PRIMARY KEY (device_id, data_category)\n );\n\n CREATE TABLE IF NOT EXISTS recording_cleanup_queue (\n device_id TEXT PRIMARY KEY,\n disabled_at INTEGER NOT NULL,\n cleanup_after INTEGER NOT NULL,\n status TEXT NOT NULL,\n started_at INTEGER\n );\n `)\n }\n\n // --- Segments ---\n\n insertSegment(seg: RecordingSegment): void {\n this.db.prepare(`\n INSERT INTO recording_segments (id, device_id, stream_id, start_time, end_time, duration, path, storage_name, sub_directory, size_bytes, codec, has_audio)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(seg.id, seg.deviceId, seg.streamId, seg.startTime, seg.endTime, seg.duration, seg.path, seg.storageName, seg.subDirectory, seg.sizeBytes, seg.codec, seg.hasAudio ? 1 : 0)\n }\n\n querySegments(deviceId: string, streamId: string, startTime: number, endTime: number): readonly RecordingSegment[] {\n const rows = this.db.prepare(`\n SELECT * FROM recording_segments\n WHERE device_id = ? AND stream_id = ? AND start_time < ? AND end_time > ?\n ORDER BY start_time ASC\n `).all(deviceId, streamId, endTime, startTime) as Array<Record<string, unknown>>\n return rows.map(rowToSegment)\n }\n\n deleteSegmentsBefore(deviceId: string, streamId: string, beforeTime: number): readonly RecordingSegment[] {\n const toDelete = this.db.prepare(`\n SELECT * FROM recording_segments WHERE device_id = ? AND stream_id = ? AND start_time < ?\n `).all(deviceId, streamId, beforeTime) as Array<Record<string, unknown>>\n this.db.prepare(`DELETE FROM recording_segments WHERE device_id = ? AND stream_id = ? AND start_time < ?`).run(deviceId, streamId, beforeTime)\n return toDelete.map(rowToSegment)\n }\n\n deleteSegmentsForDevice(deviceId: string): readonly RecordingSegment[] {\n const toDelete = this.db.prepare(`SELECT * FROM recording_segments WHERE device_id = ?`).all(deviceId) as Array<Record<string, unknown>>\n this.db.prepare(`DELETE FROM recording_segments WHERE device_id = ?`).run(deviceId)\n return toDelete.map(rowToSegment)\n }\n\n getStorageUsage(deviceId: string, streamId: string): StorageUsage {\n const row = this.db.prepare(`\n SELECT COALESCE(SUM(size_bytes), 0) as total_bytes, COUNT(*) as segment_count\n FROM recording_segments WHERE device_id = ? AND stream_id = ?\n `).get(deviceId, streamId) as { total_bytes: number; segment_count: number }\n return { totalBytes: row.total_bytes, segmentCount: row.segment_count }\n }\n\n getOldestSegments(deviceId: string, streamId: string, limit: number): readonly RecordingSegment[] {\n const rows = this.db.prepare(`\n SELECT * FROM recording_segments WHERE device_id = ? AND stream_id = ?\n ORDER BY start_time ASC LIMIT ?\n `).all(deviceId, streamId, limit) as Array<Record<string, unknown>>\n return rows.map(rowToSegment)\n }\n\n getAvailability(deviceId: string, startTime: number, endTime: number): readonly AvailabilityRange[] {\n const rows = this.db.prepare(`\n SELECT start_time, end_time, stream_id FROM recording_segments\n WHERE device_id = ? AND end_time >= ? AND start_time <= ?\n ORDER BY start_time ASC\n `).all(deviceId, startTime, endTime) as Array<{ start_time: number; end_time: number; stream_id: string }>\n\n if (rows.length === 0) return []\n\n const ranges: Array<{ startTime: number; endTime: number; streams: Set<string> }> = []\n let current = { startTime: rows[0]!.start_time, endTime: rows[0]!.end_time, streams: new Set([rows[0]!.stream_id]) }\n\n for (let i = 1; i < rows.length; i++) {\n const row = rows[i]!\n if (row.start_time <= current.endTime) {\n current.endTime = Math.max(current.endTime, row.end_time)\n current.streams.add(row.stream_id)\n } else {\n ranges.push(current)\n current = { startTime: row.start_time, endTime: row.end_time, streams: new Set([row.stream_id]) }\n }\n }\n ranges.push(current)\n\n return ranges.map(r => ({ startTime: r.startTime, endTime: r.endTime, streams: [...r.streams] }))\n }\n\n getMotionStats(deviceId: string, startTime: number, endTime: number): {\n readonly totalEvents: number\n readonly avgDurationSec: number\n readonly avgEventsPerDay: number\n readonly dutyCyclePercent: number\n } {\n const row = this.db.prepare(`\n SELECT COUNT(*) as total_events, COALESCE(AVG(duration), 0) as avg_duration, COALESCE(SUM(duration), 0) as total_duration\n FROM recording_segments\n WHERE device_id = ? AND start_time >= ? AND end_time <= ?\n `).get(deviceId, startTime, endTime) as { total_events: number; avg_duration: number; total_duration: number }\n\n const timeRangeMs = endTime - startTime\n const timeRangeDays = Math.max(timeRangeMs / (24 * 60 * 60 * 1000), 1)\n const timeRangeSec = Math.max(timeRangeMs / 1000, 1)\n\n return {\n totalEvents: row.total_events,\n avgDurationSec: Math.round(row.avg_duration * 100) / 100,\n avgEventsPerDay: Math.round((row.total_events / timeRangeDays) * 100) / 100,\n dutyCyclePercent: Math.round((row.total_duration / timeRangeSec) * 10000) / 100,\n }\n }\n\n // --- Thumbnails ---\n\n insertThumbnail(thumb: RecordingThumbnail): void {\n this.db.prepare(`\n INSERT OR REPLACE INTO recording_thumbnails (device_id, timestamp, path, storage_name, sub_directory, size_bytes, category)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `).run(thumb.deviceId, thumb.timestamp, thumb.path, thumb.storageName, thumb.subDirectory, thumb.sizeBytes, thumb.category)\n }\n\n findNearestThumbnail(deviceId: string, timestamp: number, category: string): RecordingThumbnail | null {\n const row = this.db.prepare(`\n SELECT * FROM recording_thumbnails\n WHERE device_id = ? AND category = ?\n ORDER BY ABS(timestamp - ?) ASC LIMIT 1\n `).get(deviceId, category, timestamp) as Record<string, unknown> | undefined\n return row ? rowToThumbnail(row) : null\n }\n\n deleteThumbnailsBefore(deviceId: string, beforeTime: number): number {\n const result = this.db.prepare(`DELETE FROM recording_thumbnails WHERE device_id = ? AND timestamp < ?`).run(deviceId, beforeTime)\n return result.changes\n }\n\n deleteThumbnailsForDevice(deviceId: string): number {\n const result = this.db.prepare(`DELETE FROM recording_thumbnails WHERE device_id = ?`).run(deviceId)\n return result.changes\n }\n\n // --- Policies ---\n\n upsertPolicy(policy: { deviceId: string; enabled: boolean; mode: RecordingMode; streams: readonly StreamPolicy[]; preBufferSec: number; postBufferSec: number; scheduleRules?: readonly ScheduleRule[] }): void {\n const now = Date.now()\n this.db.prepare(`\n INSERT INTO recording_policies (device_id, enabled, mode, streams_json, schedule_json, pre_buffer_sec, post_buffer_sec, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(device_id) DO UPDATE SET enabled=?, mode=?, streams_json=?, schedule_json=?, pre_buffer_sec=?, post_buffer_sec=?, updated_at=?\n `).run(\n policy.deviceId, policy.enabled ? 1 : 0, policy.mode, JSON.stringify(policy.streams), policy.scheduleRules ? JSON.stringify(policy.scheduleRules) : null, policy.preBufferSec, policy.postBufferSec, now, now,\n policy.enabled ? 1 : 0, policy.mode, JSON.stringify(policy.streams), policy.scheduleRules ? JSON.stringify(policy.scheduleRules) : null, policy.preBufferSec, policy.postBufferSec, now,\n )\n }\n\n getPolicy(deviceId: string): RecordingPolicy | null {\n const row = this.db.prepare(`SELECT * FROM recording_policies WHERE device_id = ?`).get(deviceId) as PolicyRow | undefined\n return row ? rowToPolicy(row) : null\n }\n\n getEnabledPolicies(): readonly RecordingPolicy[] {\n const rows = this.db.prepare(`SELECT * FROM recording_policies WHERE enabled = 1`).all() as PolicyRow[]\n return rows.map(rowToPolicy)\n }\n\n deletePolicy(deviceId: string): void {\n this.db.prepare(`DELETE FROM recording_policies WHERE device_id = ?`).run(deviceId)\n }\n\n // --- Storage Config ---\n\n upsertStorageConfig(config: RecordingStorageConfig): void {\n this.db.prepare(`\n INSERT INTO recording_storage_config (device_id, data_category, storage_name, sub_directory, retention_days, retention_gb)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(device_id, data_category) DO UPDATE SET storage_name=?, sub_directory=?, retention_days=?, retention_gb=?\n `).run(config.deviceId, config.dataCategory, config.storageName, config.subDirectory, config.retentionDays, config.retentionGb,\n config.storageName, config.subDirectory, config.retentionDays, config.retentionGb)\n }\n\n resolveStorageConfig(deviceId: string, category: DataCategory): RecordingStorageConfig | null {\n const specific = this.db.prepare(`SELECT * FROM recording_storage_config WHERE device_id = ? AND data_category = ?`).get(deviceId, category) as Record<string, unknown> | undefined\n if (specific) return rowToStorageConfig(specific)\n const global = this.db.prepare(`SELECT * FROM recording_storage_config WHERE device_id = '*' AND data_category = ?`).get(category) as Record<string, unknown> | undefined\n return global ? rowToStorageConfig(global) : null\n }\n\n // --- Cleanup Queue ---\n\n addToCleanupQueue(deviceId: string, disabledAt: number): void {\n const cleanupAfter = disabledAt + 24 * 60 * 60 * 1000\n this.db.prepare(`\n INSERT OR REPLACE INTO recording_cleanup_queue (device_id, disabled_at, cleanup_after, status, started_at)\n VALUES (?, ?, ?, 'pending', NULL)\n `).run(deviceId, disabledAt, cleanupAfter)\n }\n\n cancelCleanup(deviceId: string): void {\n this.db.prepare(`UPDATE recording_cleanup_queue SET status = 'cancelled' WHERE device_id = ? AND status = 'pending'`).run(deviceId)\n }\n\n getCleanupEntry(deviceId: string): CleanupQueueEntry | null {\n const row = this.db.prepare(`SELECT * FROM recording_cleanup_queue WHERE device_id = ?`).get(deviceId) as Record<string, unknown> | undefined\n return row ? rowToCleanup(row) : null\n }\n\n getPendingCleanups(): readonly CleanupQueueEntry[] {\n const now = Date.now()\n const rows = this.db.prepare(`SELECT * FROM recording_cleanup_queue WHERE status = 'pending' AND cleanup_after <= ?`).all(now) as Array<Record<string, unknown>>\n return rows.map(rowToCleanup)\n }\n\n resetStaleCleanups(maxAgeMs: number = 3600000): void {\n const cutoff = Date.now() - maxAgeMs\n this.db.prepare(`UPDATE recording_cleanup_queue SET status = 'pending', started_at = NULL WHERE status = 'in_progress' AND started_at < ?`).run(cutoff)\n }\n\n markCleanupInProgress(deviceId: string): void {\n this.db.prepare(`UPDATE recording_cleanup_queue SET status = 'in_progress', started_at = ? WHERE device_id = ?`).run(Date.now(), deviceId)\n }\n\n markCleanupCompleted(deviceId: string): void {\n this.db.prepare(`UPDATE recording_cleanup_queue SET status = 'completed' WHERE device_id = ?`).run(deviceId)\n }\n}\n\n// --- Row mappers ---\n\nfunction rowToSegment(row: Record<string, unknown>): RecordingSegment {\n return {\n id: row.id as string,\n deviceId: row.device_id as string,\n streamId: row.stream_id as string,\n startTime: row.start_time as number,\n endTime: row.end_time as number,\n duration: row.duration as number,\n path: row.path as string,\n storageName: row.storage_name as string,\n subDirectory: row.sub_directory as string,\n sizeBytes: row.size_bytes as number,\n codec: row.codec as 'h264' | 'h265',\n hasAudio: (row.has_audio as number) === 1,\n }\n}\n\nfunction rowToThumbnail(row: Record<string, unknown>): RecordingThumbnail {\n return {\n deviceId: row.device_id as string,\n timestamp: row.timestamp as number,\n path: row.path as string,\n storageName: row.storage_name as string,\n subDirectory: row.sub_directory as string,\n sizeBytes: row.size_bytes as number,\n category: row.category as 'scrub' | 'event',\n }\n}\n\nfunction rowToPolicy(row: PolicyRow): RecordingPolicy {\n return {\n deviceId: row.device_id,\n enabled: row.enabled === 1,\n mode: row.mode as RecordingMode,\n streams: JSON.parse(row.streams_json) as StreamPolicy[],\n preBufferSec: row.pre_buffer_sec,\n postBufferSec: row.post_buffer_sec,\n scheduleRules: row.schedule_json ? JSON.parse(row.schedule_json) as ScheduleRule[] : undefined,\n }\n}\n\nfunction rowToStorageConfig(row: Record<string, unknown>): RecordingStorageConfig {\n return {\n deviceId: row.device_id as string,\n dataCategory: row.data_category as DataCategory,\n storageName: row.storage_name as string,\n subDirectory: row.sub_directory as string,\n retentionDays: row.retention_days as number | null,\n retentionGb: row.retention_gb as number | null,\n }\n}\n\nfunction rowToCleanup(row: Record<string, unknown>): CleanupQueueEntry {\n return {\n deviceId: row.device_id as string,\n disabledAt: row.disabled_at as number,\n cleanupAfter: row.cleanup_after as number,\n status: row.status as CleanupStatus,\n startedAt: row.started_at as number | null,\n }\n}\n","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 type { IAddonFileStorage } from '@camstack/types'\nimport type { IScopedLogger, IEventBus, INetworkQualityTracker, FfmpegConfig } from '@camstack/types'\nimport { buildFfmpegInputArgs, buildFfmpegOutputArgs } from './ffmpeg-config'\nimport type { RecordingDb } from './recording-db'\nimport type { SegmentWriterState, RecordingSegment } from './types'\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: string\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 private readonly _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 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 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.length} buffered segments to disk`, {\n deviceId: this.config.deviceId,\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: string, 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: ${msg}`)\n this.parseSegmentOutput(msg)\n }\n })\n\n this.ffmpeg.on('error', (err) => {\n this.logger.warn(`ffmpeg process 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 code ${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: 'recording.error',\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(\n `Max restarts exceeded for ${this.config.deviceId}/${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: 'recording.health.degraded',\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 in ${backoffMs}ms (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 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 for ${elapsed}ms, restarting ffmpeg`)\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: ${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: 'recording.storage.critical',\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: 'recording.segment.written',\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: ${err}`)\n }\n } catch (err) {\n this.logger.error(`Disk space check failed: ${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: 'recording.segment.written',\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: ${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'\n\nexport interface ThumbnailExtractorConfig {\n readonly deviceId: string\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: string): void {\n this.active = true\n\n this.unsubscribe = pipeline.onVideoFrame(\n (frame) => { this.handleFrame(frame).catch((err) => this.logger.debug(`Thumbnail error: ${err}`)) },\n this.videoRequirements,\n )\n\n this.logger.info(`ThumbnailExtractor attached for ${this.config.deviceId}`)\n }\n\n detachFromPipeline(_deviceId: string): void {\n this.active = false\n if (this.unsubscribe) {\n this.unsubscribe()\n this.unsubscribe = null\n }\n this.logger.info(`ThumbnailExtractor detached for ${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: string, timestamp: number): string {\n return `${subDirectory}/${deviceId}/${timestamp}.jpg`\n }\n}\n","import type { IScopedLogger, IEventBus, IFileStorage } from '@camstack/types'\nimport type { RecordingDb } from './recording-db'\nimport type { DataCategory } from './types'\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 fileStorage: IFileStorage,\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 for ${entry.deviceId}: ${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: 'recording.retention.completed',\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: ${err}`)\n this.scheduleNextCycle(NORMAL_INTERVAL_MS)\n }\n }, intervalMs)\n }\n\n private emitStorageEvent(category: string, deviceId: string, 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.fileStorage.deleteFile(filePath)\n } catch {\n // File may already be deleted\n }\n }\n}\n","import type { RecordingDb } from './recording-db'\nimport type { RecordingSegment } from './types'\n\ninterface PlaylistOptions {\n readonly live?: boolean\n}\n\nconst FALLBACK_STREAMS = ['sub', 'mid', 'main'] as const\n\nexport class PlaylistGenerator {\n constructor(private readonly db: RecordingDb) {}\n\n generate(deviceId: string, streamId: string, startTime: number, endTime: number, options?: PlaylistOptions): string {\n const segments = this.resolveSegments(deviceId, streamId, startTime, endTime)\n return this.buildPlaylist(segments, options)\n }\n\n private resolveSegments(deviceId: string, streamId: string, startTime: number, endTime: number): readonly RecordingSegment[] {\n const segments = this.db.querySegments(deviceId, streamId, startTime, endTime)\n if (segments.length > 0) {\n return segments\n }\n\n const fallbacks = FALLBACK_STREAMS.filter(s => s !== streamId)\n for (const fb of fallbacks) {\n const fallbackSegments = this.db.querySegments(deviceId, fb, startTime, endTime)\n if (fallbackSegments.length > 0) {\n return fallbackSegments\n }\n }\n\n return []\n }\n\n private buildPlaylist(segments: readonly RecordingSegment[], options?: PlaylistOptions): string {\n const targetDuration = segments.length > 0\n ? Math.ceil(Math.max(...segments.map(s => s.duration)))\n : 4\n\n const lines: string[] = [\n '#EXTM3U',\n '#EXT-X-VERSION:7',\n `#EXT-X-TARGETDURATION:${targetDuration}`,\n '#EXT-X-MEDIA-SEQUENCE:0',\n ]\n\n if (!options?.live) {\n lines.push('#EXT-X-PLAYLIST-TYPE:VOD')\n }\n\n for (const seg of segments) {\n lines.push(`#EXTINF:${seg.duration.toFixed(3)},`)\n lines.push(seg.path)\n }\n\n if (!options?.live) {\n lines.push('#EXT-X-ENDLIST')\n }\n\n return lines.join('\\n')\n }\n}\n","import type { INetworkQualityTracker } from '@camstack/types'\nimport type { RecordingDb } from './recording-db'\nimport type { StorageEstimate, StreamEstimate } from './types'\n\ninterface MotionEstimateInput {\n readonly avgEventsPerDay: number\n readonly avgDurationSec: number\n}\n\nexport class StorageEstimator {\n constructor(\n private readonly db: RecordingDb,\n private readonly networkTracker: INetworkQualityTracker,\n ) {}\n\n estimateForDevice(deviceId: string, motionInput?: MotionEstimateInput): StorageEstimate {\n const policy = this.db.getPolicy(deviceId)\n if (!policy) return { perStream: {}, thumbnails: { estimatedGb: 0 }, totalEstimatedGb: 0 }\n\n const stats = this.networkTracker.getDeviceStats(deviceId)\n const perStream: Record<string, StreamEstimate> = {}\n let totalGb = 0\n\n const motionEstimate = motionInput ? {\n avgEventsPerDay: motionInput.avgEventsPerDay,\n avgDurationSec: motionInput.avgDurationSec,\n dutyCyclePercent: (motionInput.avgEventsPerDay * motionInput.avgDurationSec / 86400) * 100,\n } : undefined\n\n for (const sp of policy.streams) {\n const category = `recording:${sp.streamId}` as any\n const config = this.db.resolveStorageConfig(deviceId, category)\n const retentionDays = config?.retentionDays ?? null\n const retentionGb = config?.retentionGb ?? null\n\n const streamStats = stats?.streams[sp.streamId]\n const observedBitrate = streamStats?.observedBitrateKbps ?? 0\n const bitrateKbps = observedBitrate > 0\n ? observedBitrate\n : (streamStats?.nominalBitrateKbps ?? 4000)\n\n let estimatedGb = 0\n if (retentionDays !== null) {\n const retentionSeconds = retentionDays * 86400\n estimatedGb = (bitrateKbps * retentionSeconds) / 8 / 1024 / 1024 / 1024\n }\n\n if (policy.mode === 'motion' && motionEstimate) {\n estimatedGb = estimatedGb * motionEstimate.dutyCyclePercent / 100\n }\n\n let estimatedDaysAtCapacity: number | null = null\n if (retentionGb !== null && estimatedGb > retentionGb) {\n estimatedDaysAtCapacity = retentionDays !== null ? retentionDays * (retentionGb / estimatedGb) : null\n estimatedGb = retentionGb\n }\n\n perStream[sp.streamId] = { bitrateKbps, retentionDays, retentionGb, estimatedGb, estimatedDaysAtCapacity }\n totalGb += estimatedGb\n }\n\n const thumbRetentionDays = policy.streams[0]\n ? (this.db.resolveStorageConfig(deviceId, 'thumbnail:scrub' as any)?.retentionDays ?? 7)\n : 7\n const thumbGb = (2 * 24 * thumbRetentionDays) / 1024\n totalGb += thumbGb\n\n return { perStream, thumbnails: { estimatedGb: thumbGb }, totalEstimatedGb: totalGb, motionEstimate }\n }\n}\n","import type { IScopedLogger, IEventBus, SystemEvent, IStreamingEngine, IPipelineManager, INetworkQualityTracker, IFileStorage, FfmpegConfig } from '@camstack/types'\nimport { resolveFfmpegConfig } from './ffmpeg-config'\nimport type { RecordingDb } from './recording-db'\nimport type {\n RecordingPolicy, RecordingEnableConfig, ScheduleRule,\n SegmentWriterState,\n} from './types'\nimport { SegmentWriter, type SegmentWriterConfig } from './segment-writer'\nimport { ThumbnailExtractor, type ThumbnailExtractorConfig } from './thumbnail-extractor'\nimport { RetentionManager } from './retention-manager'\nimport { PlaylistGenerator } from './playlist-generator'\nimport { StorageEstimator } from './storage-estimator'\n\n// --- Per-device recording state ---\n\ninterface DeviceRecordingState {\n readonly deviceId: string\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\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 readonly fileStorage: IFileStorage\n readonly storagePath: string\n readonly globalFfmpegConfig: Partial<FfmpegConfig>\n readonly detectedFfmpegConfig: Partial<FfmpegConfig>\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 fileStorage: IFileStorage\n private readonly storagePath: string\n private readonly globalFfmpegConfig: Partial<FfmpegConfig>\n private readonly detectedFfmpegConfig: Partial<FfmpegConfig>\n\n private readonly recordings = new Map<string, 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.fileStorage = config.fileStorage\n this.storagePath = config.storagePath\n this.globalFfmpegConfig = config.globalFfmpegConfig\n this.detectedFfmpegConfig = config.detectedFfmpegConfig\n\n this.retentionManager = new RetentionManager(\n this.db,\n this.logger.child('retention'),\n this.eventBus,\n this.fileStorage,\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 for ${policy.deviceId}: ${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: string, 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 any)\n const storageName = storageConfig?.storageName ?? 'recordings'\n const subDirectory = storageConfig?.subDirectory ?? `recordings/${sp.streamId}`\n\n const writerConfig: SegmentWriterConfig = {\n deviceId,\n streamId: sp.streamId,\n segmentDurationSec: 4,\n storagePath: this.storagePath,\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 thumbConfig: ThumbnailExtractorConfig = {\n deviceId,\n storagePath: this.storagePath,\n storageName: thumbStorageConfig?.storageName ?? 'recordings',\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 for ${deviceId} within ${MOTION_FALLBACK_TIMEOUT_MS / 1000}s — falling back to continuous recording`)\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: 'recording.policy.fallback',\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 for ${deviceId} during fallback: ${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: 'recording.started',\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 for ${deviceId}`, { mode: policy.mode })\n }\n\n async disableRecording(deviceId: string): Promise<void> {\n const state = this.recordings.get(deviceId)\n if (!state) {\n this.logger.warn(`No active recording for ${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: 'recording.stopped',\n data: {\n deviceId,\n segmentCount: totalSegmentCount,\n totalMB,\n },\n })\n\n this.logger.info(`Recording disabled for ${deviceId}`, { segmentCount: totalSegmentCount, totalMB })\n }\n\n isRecording(deviceId: string): boolean {\n return this.recordings.has(deviceId)\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: string, 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: string, 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 for ${deviceId}: ${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: string): 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","import type { IDecoderProvider } from '@camstack/types'\n\ninterface RegisteredProvider {\n readonly provider: IDecoderProvider\n readonly priority: number\n}\n\nexport class DecoderRegistry {\n private readonly entries: RegisteredProvider[] = []\n\n register(provider: IDecoderProvider, priority: number): void {\n this.entries.push({ provider, priority })\n this.entries.sort((a, b) => a.priority - b.priority)\n }\n\n getDecoder(codec: string): IDecoderProvider | undefined {\n return this.entries.find((e) => e.provider.supportsCodec(codec))?.provider\n }\n}\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport type { IDecoderSession, DecoderSessionConfig, DecoderStats, EncodedPacket, DecodedFrame, Unsubscribe } from '@camstack/types'\nimport { FrameDropper } from './frame-dropper'\n\nconst SOI = Buffer.from([0xff, 0xd8])\nconst EOI = Buffer.from([0xff, 0xd9])\n\nfunction codecToInputFormat(codec: string): string {\n switch (codec) {\n case 'h265':\n case 'hevc':\n return 'hevc'\n case 'mjpeg':\n return 'mjpeg'\n case 'h264':\n default:\n return 'h264'\n }\n}\n\nfunction buildFfmpegArgs(config: DecoderSessionConfig): string[] {\n const inputFormat = codecToInputFormat(config.codec)\n const args = ['-hide_banner', '-loglevel', 'error', '-f', inputFormat, '-i', 'pipe:0']\n\n if (config.scale > 1) {\n args.push('-vf', `scale=iw/${config.scale}:ih/${config.scale}`)\n }\n\n args.push('-f', 'image2pipe', '-vcodec', 'mjpeg', '-q:v', '3', 'pipe:1')\n return args\n}\n\nexport class FfmpegDecoderSession implements IDecoderSession {\n private config: DecoderSessionConfig\n private frameDropper: FrameDropper\n private process: ChildProcess | null = null\n private frameCallbacks = new Set<(frame: DecodedFrame) => void>()\n private outputBuffer = Buffer.alloc(0)\n private destroyed = false\n\n // Stats tracking\n private inputPackets = 0\n private outputFrames = 0\n private droppedFrames = 0\n private totalDecodeTimeMs = 0\n private decodeCount = 0\n private startTime = Date.now()\n\n constructor(config: DecoderSessionConfig) {\n this.config = { ...config }\n this.frameDropper = new FrameDropper(config.maxFps)\n this.spawnFfmpeg()\n }\n\n private spawnFfmpeg(): void {\n if (this.destroyed) return\n\n this.killFfmpeg()\n this.outputBuffer = Buffer.alloc(0)\n\n const args = buildFfmpegArgs(this.config)\n this.process = spawn('ffmpeg', args)\n\n // Swallow EPIPE errors on stdin during shutdown\n this.process.stdin?.on('error', () => {})\n\n this.process.stdout?.on('data', (chunk: Buffer) => {\n this.handleOutputData(chunk)\n })\n\n this.process.stderr?.on('data', (data: Buffer) => {\n // ffmpeg logs errors to stderr; swallow in production, could log here if needed\n void data\n })\n\n this.process.on('error', (_err: Error) => {\n // process failed to spawn or crashed; could handle reconnect logic here\n })\n\n this.process.on('close', () => {\n if (!this.destroyed) {\n this.process = null\n }\n })\n }\n\n private killFfmpeg(): void {\n if (this.process) {\n try {\n this.process.kill('SIGKILL')\n } catch {\n // already dead\n }\n this.process = null\n }\n }\n\n private handleOutputData(chunk: Buffer): void {\n this.outputBuffer = Buffer.concat([this.outputBuffer, chunk])\n\n // Extract complete JPEG frames from the buffer\n let searchFrom = 0\n while (true) {\n const soiIndex = this.outputBuffer.indexOf(SOI, searchFrom)\n if (soiIndex === -1) break\n\n const eoiIndex = this.outputBuffer.indexOf(EOI, soiIndex + 2)\n if (eoiIndex === -1) break\n\n const frameEnd = eoiIndex + 2\n const jpegData = this.outputBuffer.slice(soiIndex, frameEnd)\n\n // Advance past the consumed frame\n searchFrom = frameEnd\n\n this.emitFrame(Buffer.from(jpegData))\n }\n\n // Keep only unprocessed tail\n if (searchFrom > 0) {\n this.outputBuffer = Buffer.from(this.outputBuffer.slice(searchFrom))\n }\n }\n\n private emitFrame(data: Buffer): void {\n const decodeStart = Date.now()\n\n if (!this.frameDropper.shouldKeep()) {\n this.droppedFrames++\n return\n }\n\n const decodeTime = Date.now() - decodeStart\n this.totalDecodeTimeMs += decodeTime\n this.decodeCount++\n this.outputFrames++\n\n const frame: DecodedFrame = {\n data,\n width: 0,\n height: 0,\n format: 'jpeg',\n timestamp: Date.now(),\n }\n\n for (const cb of this.frameCallbacks) {\n cb(frame)\n }\n }\n\n pushPacket(packet: EncodedPacket): void {\n if (this.destroyed || !this.process?.stdin) return\n this.inputPackets++\n try {\n this.process.stdin.write(packet.data)\n } catch {\n // stdin may be closed if ffmpeg crashed\n }\n }\n\n onFrame(callback: (frame: DecodedFrame) => void): Unsubscribe {\n this.frameCallbacks.add(callback)\n return () => {\n this.frameCallbacks.delete(callback)\n }\n }\n\n updateConfig(update: Partial<DecoderSessionConfig>): void {\n const needsRestart =\n (update.scale !== undefined && update.scale !== this.config.scale) ||\n (update.outputFormat !== undefined && update.outputFormat !== this.config.outputFormat) ||\n (update.codec !== undefined && update.codec !== this.config.codec)\n\n this.config = { ...this.config, ...update }\n\n if (update.maxFps !== undefined) {\n this.frameDropper.setMaxFps(update.maxFps)\n }\n\n if (needsRestart) {\n this.spawnFfmpeg()\n }\n }\n\n async destroy(): Promise<void> {\n if (this.destroyed) return\n this.destroyed = true\n this.killFfmpeg()\n this.frameCallbacks.clear()\n }\n\n getStats(): DecoderStats {\n const uptimeSec = Math.max((Date.now() - this.startTime) / 1000, 1)\n return {\n inputFps: this.inputPackets / uptimeSec,\n outputFps: this.outputFrames / uptimeSec,\n avgDecodeTimeMs: this.decodeCount > 0 ? this.totalDecodeTimeMs / this.decodeCount : 0,\n droppedFrames: this.droppedFrames,\n }\n }\n}\n","export class FrameDropper {\n private intervalMs: number\n private lastPassedAt = -Infinity\n\n constructor(maxFps: number) {\n this.intervalMs = maxFps > 0 ? 1000 / maxFps : 0\n }\n\n shouldKeep(): boolean {\n if (this.intervalMs === 0) return true\n\n const now = Date.now()\n if (now - this.lastPassedAt >= this.intervalMs) {\n this.lastPassedAt = now\n return true\n }\n return false\n }\n\n setMaxFps(maxFps: number): void {\n this.intervalMs = maxFps > 0 ? 1000 / maxFps : 0\n }\n}\n","import type { IDecoderProvider, DecoderSessionConfig, IDecoderSession } from '@camstack/types'\nimport { FfmpegDecoderSession } from './ffmpeg-decoder-session'\n\nconst SUPPORTED_CODECS = new Set(['h264', 'h265', 'hevc', 'mjpeg'])\n\nexport class FfmpegDecoderProvider implements IDecoderProvider {\n readonly id = 'ffmpeg'\n readonly name = 'FFmpeg Decoder'\n\n supportsCodec(codec: string): boolean {\n return SUPPORTED_CODECS.has(codec)\n }\n\n createSession(config: DecoderSessionConfig): IDecoderSession {\n return new FfmpegDecoderSession(config)\n }\n}\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport type {\n IStreamBroker,\n StreamSource,\n EncodedPacket,\n DecodedFrame,\n DecodedAudioChunk,\n DecodeOptions,\n BrokerStatus,\n BrokerStats,\n Unsubscribe,\n IDecoderSession,\n IRestreamer,\n IScopedLogger,\n} from '@camstack/types'\nimport type { DecoderRegistry } from './decoder-registry'\nimport { FrameDropper } from './frame-dropper'\nimport { StreamPipeServer } from './stream-pipe-server'\n\nconst DEFAULT_MAX_FPS = 5\nconst DEFAULT_FORMAT = 'jpeg' as const\nconst DEFAULT_SCALE = 1\n\nconst INITIAL_RECONNECT_DELAY_MS = 1_000\nconst MAX_RECONNECT_DELAY_MS = 30_000\nconst DECODER_TEARDOWN_GRACE_MS = 2_000\n\n/** H.264 NAL unit type 5 = IDR frame (keyframe) */\nconst H264_IDR_NAL_TYPE = 5\n/** H.265 NAL unit types 16-21 are IRAP (keyframe) pictures */\nconst H265_IRAP_NAL_TYPE_MIN = 16\nconst H265_IRAP_NAL_TYPE_MAX = 21\n\ninterface DecodedSubscriber {\n readonly callback: (frame: DecodedFrame) => void\n readonly frameDropper: FrameDropper\n}\n\nexport class StreamBroker implements IStreamBroker {\n readonly deviceId: string\n\n private _status: BrokerStatus = 'idle'\n private source: StreamSource | undefined\n private decoderSession: IDecoderSession | null = null\n private decoderUnsubscribe: Unsubscribe | null = null\n private readonly decoderRegistry: DecoderRegistry\n private readonly logger: IScopedLogger | undefined\n private readonly startedAt: number = Date.now()\n\n private ffmpegProcess: ChildProcess | undefined\n private reconnectTimer: ReturnType<typeof setTimeout> | undefined\n private reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n private manualStop = false\n private stopping = false\n private decoderTeardownTimer: ReturnType<typeof setTimeout> | undefined\n\n private restreamers: readonly IRestreamer[] = []\n private readonly pipeServer: StreamPipeServer\n\n private readonly encodedCallbacks = new Set<(packet: EncodedPacket) => void>()\n private readonly decodedSubscribers = new Map<symbol, DecodedSubscriber>()\n private readonly audioChunkCallbacks = new Set<(chunk: DecodedAudioChunk) => void>()\n\n constructor(deviceId: string, decoderRegistry: DecoderRegistry, logger?: IScopedLogger) {\n this.deviceId = deviceId\n this.decoderRegistry = decoderRegistry\n this.logger = logger\n this.pipeServer = new StreamPipeServer(logger?.child('pipe-server'))\n }\n\n get status(): BrokerStatus {\n return this._status\n }\n\n setRestreamers(restreamers: readonly IRestreamer[]): void {\n this.restreamers = restreamers\n }\n\n async start(source: StreamSource): Promise<void> {\n this.source = source\n this.manualStop = false\n this.stopping = false\n this._status = 'connecting'\n\n await this.pipeServer.start()\n this.logger?.info(\n `Pipe server started for ${this.deviceId} at ${this.pipeServer.getUrl()}`,\n )\n\n if (source.type === 'rtsp') {\n this.startRtspReader(source)\n } else {\n // For non-RTSP sources (raw TCP, pipe), data is pushed externally\n this._status = 'streaming'\n }\n }\n\n async stop(): Promise<void> {\n this.stopping = true\n this.manualStop = true\n this._status = 'stopped'\n\n this.killFfmpeg()\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = undefined\n }\n\n if (this.decoderTeardownTimer) {\n clearTimeout(this.decoderTeardownTimer)\n this.decoderTeardownTimer = undefined\n }\n\n if (this.decoderSession) {\n await this.destroyDecoder()\n }\n\n await this.pipeServer.stop()\n\n this.encodedCallbacks.clear()\n this.decodedSubscribers.clear()\n this.audioChunkCallbacks.clear()\n }\n\n pushEncodedPacket(packet: EncodedPacket): void {\n if (this.stopping) return\n\n for (const cb of this.encodedCallbacks) {\n cb(packet)\n }\n\n // Broadcast raw video bytes to TCP pipe clients (restreamers)\n if (packet.type === 'video') {\n this.pipeServer.broadcast(packet.data)\n }\n\n for (const restreamer of this.restreamers) {\n const streamId = `${this.deviceId}/video`\n restreamer.pushPacket(streamId, packet)\n }\n\n if (packet.type === 'video' && this.decoderSession) {\n this.decoderSession.pushPacket(packet)\n }\n }\n\n onEncodedData(callback: (packet: EncodedPacket) => void): Unsubscribe {\n this.encodedCallbacks.add(callback)\n\n return () => {\n this.encodedCallbacks.delete(callback)\n }\n }\n\n onDecodedFrame(callback: (frame: DecodedFrame) => void, options?: DecodeOptions): Unsubscribe {\n const maxFps = options?.maxFps ?? DEFAULT_MAX_FPS\n const subscriberId = Symbol('decoded-subscriber')\n const frameDropper = new FrameDropper(maxFps)\n\n const subscriber: DecodedSubscriber = { callback, frameDropper }\n this.decodedSubscribers.set(subscriberId, subscriber)\n\n // Cancel pending teardown if a new subscriber joins while grace period is active\n if (this.decoderTeardownTimer) {\n clearTimeout(this.decoderTeardownTimer)\n this.decoderTeardownTimer = undefined\n }\n\n // Create shared decoder on first subscriber (or reuse existing)\n if (!this.decoderSession && this.source) {\n this.createSharedDecoderSession(options)\n }\n\n return () => {\n this.decodedSubscribers.delete(subscriberId)\n\n // Schedule decoder teardown with grace period when last subscriber leaves\n if (this.decodedSubscribers.size === 0 && this.decoderSession) {\n this.decoderTeardownTimer = setTimeout(() => {\n this.decoderTeardownTimer = undefined\n if (this.decodedSubscribers.size === 0 && this.decoderSession) {\n this.destroyDecoder()\n }\n }, DECODER_TEARDOWN_GRACE_MS)\n }\n }\n }\n\n onDecodedAudioChunk(callback: (chunk: DecodedAudioChunk) => void): Unsubscribe {\n this.audioChunkCallbacks.add(callback)\n\n return () => {\n this.audioChunkCallbacks.delete(callback)\n }\n }\n\n getStats(): BrokerStats {\n const decoderStats = this.decoderSession?.getStats()\n\n return {\n status: this._status,\n inputFps: decoderStats?.inputFps ?? 0,\n decodeFps: decoderStats?.outputFps ?? 0,\n encodedSubscribers: this.encodedCallbacks.size,\n decodedSubscribers: this.decodedSubscribers.size,\n uptimeMs: Date.now() - this.startedAt,\n }\n }\n\n /**\n * Returns the local TCP URL that restreamers should connect to\n * instead of the camera's RTSP URL. This ensures the broker is\n * the sole reader from the camera.\n */\n getLocalStreamUrl(): string {\n return this.pipeServer.getUrl()\n }\n\n /** Returns the number of TCP pipe clients currently connected */\n getPipeClientCount(): number {\n return this.pipeServer.getClientCount()\n }\n\n private startRtspReader(source: StreamSource): void {\n const codec = source.videoCodec ?? 'h264'\n const isHevc = codec === 'h265' || codec === 'hevc'\n\n const args = [\n '-hide_banner',\n '-loglevel', 'error',\n '-rtsp_transport', 'tcp',\n '-i', source.url,\n '-c:v', 'copy',\n '-f', isHevc ? 'hevc' : 'h264',\n '-bsf:v', isHevc ? 'hevc_mp4toannexb' : 'h264_mp4toannexb',\n 'pipe:1',\n ]\n\n this.logger?.debug(`Spawning ffmpeg RTSP reader for ${this.deviceId}`, {\n url: source.url,\n codec,\n })\n\n const proc = spawn('ffmpeg', args, {\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n this.ffmpegProcess = proc\n\n // Swallow EPIPE errors on stdin during shutdown\n proc.stdin?.on('error', () => {})\n\n proc.stdout?.on('data', (chunk: Buffer) => {\n if (this.stopping) return\n\n this._status = 'streaming'\n this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n\n const keyframe = isHevc\n ? isHevcKeyframe(chunk)\n : isH264Keyframe(chunk)\n\n const packet: EncodedPacket = {\n type: 'video',\n data: chunk,\n pts: Date.now(),\n dts: Date.now(),\n keyframe,\n codec,\n }\n\n this.pushEncodedPacket(packet)\n })\n\n proc.stderr?.on('data', (data: Buffer) => {\n const msg = data.toString().trim()\n if (msg) {\n this.logger?.warn(`ffmpeg RTSP reader stderr [${this.deviceId}]: ${msg}`)\n }\n })\n\n proc.on('exit', (code, signal) => {\n this.ffmpegProcess = undefined\n\n if (this.manualStop) {\n return\n }\n\n this.logger?.warn(`ffmpeg RTSP reader exited for ${this.deviceId}`, {\n code,\n signal,\n })\n\n this._status = 'error'\n this.scheduleReconnect()\n })\n\n proc.on('error', (err) => {\n this.ffmpegProcess = undefined\n\n if (this.manualStop) {\n return\n }\n\n this.logger?.error(`ffmpeg RTSP reader error for ${this.deviceId}: ${err.message}`)\n this._status = 'error'\n this.scheduleReconnect()\n })\n }\n\n private scheduleReconnect(): void {\n if (this.manualStop || !this.source) {\n return\n }\n\n this.logger?.info(\n `Reconnecting RTSP reader for ${this.deviceId} in ${this.reconnectDelayMs}ms`,\n )\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = undefined\n if (!this.manualStop && this.source) {\n this._status = 'connecting'\n this.startRtspReader(this.source)\n }\n }, this.reconnectDelayMs)\n\n this.reconnectDelayMs = Math.min(\n this.reconnectDelayMs * 2,\n MAX_RECONNECT_DELAY_MS,\n )\n }\n\n private destroyDecoder(): Promise<void> {\n if (this.decoderUnsubscribe) {\n this.decoderUnsubscribe()\n this.decoderUnsubscribe = null\n }\n const session = this.decoderSession\n this.decoderSession = null\n return session?.destroy() ?? Promise.resolve()\n }\n\n private killFfmpeg(): void {\n if (this.ffmpegProcess) {\n this.ffmpegProcess.kill('SIGTERM')\n this.ffmpegProcess = undefined\n }\n }\n\n private createSharedDecoderSession(options?: DecodeOptions): void {\n const codec = this.source?.videoCodec ?? 'h264'\n const provider = this.decoderRegistry.getDecoder(codec)\n\n if (!provider) {\n return\n }\n\n // Shared decoder runs at unlimited FPS — each subscriber throttles locally\n const config = {\n codec,\n maxFps: 0,\n outputFormat: options?.format ?? DEFAULT_FORMAT,\n scale: options?.scale ?? DEFAULT_SCALE,\n }\n\n this.decoderSession = provider.createSession(config)\n\n // Single onFrame listener that fans out to all subscribers with per-subscriber throttling\n this.decoderUnsubscribe = this.decoderSession.onFrame((frame: DecodedFrame) => {\n for (const subscriber of this.decodedSubscribers.values()) {\n if (subscriber.frameDropper.shouldKeep()) {\n subscriber.callback(frame)\n }\n }\n })\n }\n}\n\n/**\n * Detect H.264 keyframe by scanning for IDR NAL unit (type 5)\n * in an Annex-B bitstream chunk.\n */\nfunction isH264Keyframe(data: Buffer): boolean {\n for (let i = 0; i < data.length - 4; i++) {\n // Look for start code: 0x00 0x00 0x01 or 0x00 0x00 0x00 0x01\n if (data[i] === 0 && data[i + 1] === 0) {\n let nalStart = -1\n if (data[i + 2] === 1) {\n nalStart = i + 3\n } else if (data[i + 2] === 0 && data[i + 3] === 1) {\n nalStart = i + 4\n }\n if (nalStart >= 0 && nalStart < data.length) {\n const nalType = data[nalStart]! & 0x1f\n if (nalType === H264_IDR_NAL_TYPE) {\n return true\n }\n }\n }\n }\n return false\n}\n\n/**\n * Detect H.265/HEVC keyframe by scanning for IRAP NAL units (types 16-21)\n * in an Annex-B bitstream chunk.\n */\nfunction isHevcKeyframe(data: Buffer): boolean {\n for (let i = 0; i < data.length - 5; i++) {\n if (data[i] === 0 && data[i + 1] === 0) {\n let nalStart = -1\n if (data[i + 2] === 1) {\n nalStart = i + 3\n } else if (data[i + 2] === 0 && data[i + 3] === 1) {\n nalStart = i + 4\n }\n if (nalStart >= 0 && nalStart < data.length) {\n const nalType = (data[nalStart]! >> 1) & 0x3f\n if (nalType >= H265_IRAP_NAL_TYPE_MIN && nalType <= H265_IRAP_NAL_TYPE_MAX) {\n return true\n }\n }\n }\n }\n return false\n}\n","import net from 'node:net'\nimport type { IScopedLogger } from '@camstack/types'\n\n/**\n * A lightweight TCP server that broadcasts raw encoded video bytes\n * (H.264/H.265 Annex-B) to all connected clients.\n *\n * Each StreamBroker owns one StreamPipeServer. Restreamers (e.g. go2rtc)\n * connect via `tcp://127.0.0.1:{port}` and receive the same byte stream\n * that the broker reads from the camera, eliminating duplicate RTSP\n * connections.\n */\nexport class StreamPipeServer {\n private readonly server: net.Server\n private readonly clients: Set<net.Socket> = new Set()\n private port = 0\n private started = false\n private readonly logger: IScopedLogger | undefined\n\n constructor(logger?: IScopedLogger) {\n this.logger = logger\n this.server = net.createServer((socket) => {\n this.handleConnection(socket)\n })\n\n this.server.on('error', (err) => {\n this.logger?.error(`StreamPipeServer error: ${err.message}`)\n })\n }\n\n async start(): Promise<void> {\n if (this.started) {\n return\n }\n\n await new Promise<void>((resolve, reject) => {\n this.server.once('error', reject)\n this.server.listen(0, '127.0.0.1', () => {\n this.server.removeListener('error', reject)\n const address = this.server.address() as net.AddressInfo\n this.port = address.port\n this.started = true\n this.logger?.debug(`StreamPipeServer listening on port ${this.port}`)\n resolve()\n })\n })\n }\n\n /**\n * Broadcast raw encoded bytes to all connected clients.\n * Silently drops data for clients that are slow or disconnected.\n */\n broadcast(data: Buffer): void {\n for (const client of this.clients) {\n if (client.destroyed) {\n this.clients.delete(client)\n continue\n }\n\n client.write(data, (err) => {\n if (err) {\n this.logger?.debug(\n `StreamPipeServer write error, removing client: ${err.message}`,\n )\n this.removeClient(client)\n }\n })\n }\n }\n\n getPort(): number {\n return this.port\n }\n\n getUrl(): string {\n return `tcp://127.0.0.1:${this.port}`\n }\n\n getClientCount(): number {\n return this.clients.size\n }\n\n isStarted(): boolean {\n return this.started\n }\n\n async stop(): Promise<void> {\n if (!this.started) {\n return\n }\n\n this.started = false\n\n for (const client of this.clients) {\n client.destroy()\n }\n this.clients.clear()\n\n await new Promise<void>((resolve) => {\n this.server.close(() => {\n this.logger?.debug('StreamPipeServer stopped')\n resolve()\n })\n })\n }\n\n private handleConnection(socket: net.Socket): void {\n this.clients.add(socket)\n this.logger?.debug(\n `StreamPipeServer client connected (total: ${this.clients.size})`,\n )\n\n socket.on('close', () => {\n this.removeClient(socket)\n })\n\n socket.on('error', () => {\n this.removeClient(socket)\n })\n }\n\n private removeClient(socket: net.Socket): void {\n const existed = this.clients.delete(socket)\n if (existed) {\n this.logger?.debug(\n `StreamPipeServer client disconnected (total: ${this.clients.size})`,\n )\n }\n if (!socket.destroyed) {\n socket.destroy()\n }\n }\n}\n","import type { StreamSource, IRestreamer, IScopedLogger } from '@camstack/types'\nimport { StreamBroker } from './stream-broker'\nimport type { DecoderRegistry } from './decoder-registry'\n\nexport class StreamBrokerManager {\n private readonly brokers = new Map<string, StreamBroker>()\n private restreamers: readonly IRestreamer[] = []\n private readonly logger: IScopedLogger\n\n constructor(\n private readonly decoderRegistry: DecoderRegistry,\n logger: IScopedLogger,\n ) {\n this.logger = logger\n }\n\n setRestreamers(restreamers: readonly IRestreamer[]): void {\n this.restreamers = restreamers\n\n for (const broker of this.brokers.values()) {\n broker.setRestreamers(restreamers)\n }\n\n this.logger.info(`Updated restreamers (count: ${restreamers.length})`)\n }\n\n getRestreamers(): readonly IRestreamer[] {\n return this.restreamers\n }\n\n /**\n * Create and start a broker.\n * brokerId can be a simple deviceId (backward compat) or a compound key like `${deviceId}/${streamId}`.\n */\n async createBroker(brokerId: string, source: StreamSource): Promise<StreamBroker> {\n const brokerLogger = this.logger.child(`broker:${brokerId}`)\n const broker = new StreamBroker(brokerId, this.decoderRegistry, brokerLogger)\n broker.setRestreamers(this.restreamers)\n await broker.start(source)\n this.brokers.set(brokerId, broker)\n return broker\n }\n\n getBroker(brokerId: string): StreamBroker | undefined {\n return this.brokers.get(brokerId)\n }\n\n async destroyBroker(brokerId: string): Promise<void> {\n const broker = this.brokers.get(brokerId)\n\n if (broker) {\n await broker.stop()\n this.brokers.delete(brokerId)\n }\n }\n\n /** Destroy all brokers matching a prefix (e.g., `${deviceId}/` to destroy all streams for a device) */\n async destroyBrokersForDevice(deviceId: string): Promise<void> {\n const prefix = `${deviceId}/`\n const toDestroy: string[] = []\n\n for (const brokerId of this.brokers.keys()) {\n if (brokerId === deviceId || brokerId.startsWith(prefix)) {\n toDestroy.push(brokerId)\n }\n }\n\n await Promise.all(toDestroy.map((id) => this.destroyBroker(id)))\n }\n\n listBrokers(): readonly StreamBroker[] {\n return [...this.brokers.values()]\n }\n\n async destroyAll(): Promise<void> {\n const stopPromises = [...this.brokers.values()].map((broker) => broker.stop())\n await Promise.all(stopPromises)\n this.brokers.clear()\n }\n}\n","/**\n * Event Persistence Service — unified persistence for detection events.\n *\n * A detection event is ONE entity with:\n * - Metadata (DB): id, timestamp, device, class, score, severity, description\n * - Media (filesystem): original frame, annotated frame, crops, faces, plates\n *\n * Both are stored together, flushed together, and deleted together by retention.\n *\n * Buffered I/O prevents filesystem flooding:\n * - Events buffered, flushed every 5s or at 50 events\n * - Media buffered, flushed every 3s or at 50MB\n * - High-priority media (thumbnails) flushed first\n */\n\nimport type { IScopedLogger, IStorageLocation, SystemEvent, VideoFrame } from '@camstack/types'\n\n/**\n * Inlined from the removed stages/object-snapshot-stage.ts.\n * These types are used by the persistence layer only.\n */\nexport interface ObjectSnapshotResult {\n readonly trackId: string\n readonly thumbnail?: Buffer\n readonly fullCrop?: Buffer\n}\n\nexport interface AnnotatedSnapshotResult {\n readonly thumbnail: Buffer\n readonly full: Buffer\n}\n\n// --- Types ---\n\nexport interface PersistableEvent {\n readonly id: string\n readonly timestamp: number\n readonly deviceId: string\n readonly category: string\n readonly className: string\n readonly score: number\n readonly trackId: string\n readonly severity: string\n readonly description: string\n readonly data: Record<string, unknown>\n /** IDs of associated media files (for cleanup) */\n readonly mediaFiles: readonly string[]\n}\n\ninterface MediaEntry {\n readonly path: string\n readonly data: Buffer\n readonly priority: 'high' | 'normal'\n readonly eventId: string\n}\n\nexport interface EventBufferStatus {\n readonly eventCount: number\n readonly mediaCount: number\n readonly mediaSizeMB: number\n}\n\nexport type TrackMediaType =\n | 'crop-thumb'\n | 'crop-full'\n | 'debug-annotated-thumb'\n | 'debug-annotated-full'\n | 'original'\n | 'original-thumb'\n | 'inline-crop'\n | 'unknown'\n\nexport interface TrackMediaFile {\n readonly path: string\n readonly type: TrackMediaType\n readonly data: Buffer\n /** Where the data was read from */\n readonly source: 'buffer' | 'storage'\n}\n\nfunction classifyMediaPath(path: string): TrackMediaType {\n if (path.endsWith('-thumb.jpg') && path.includes('/crops/')) return 'crop-thumb'\n if (path.endsWith('-full.jpg') && path.includes('/crops/')) return 'crop-full'\n if (path.includes('debug-annotated-thumb.jpg')) return 'debug-annotated-thumb'\n if (path.includes('debug-annotated-full.jpg')) return 'debug-annotated-full'\n if (path.includes('original-thumb.jpg')) return 'original-thumb'\n if (path.includes('original.jpg')) return 'original'\n if (path.includes('crop.jpg')) return 'inline-crop'\n return 'unknown'\n}\n\n// --- Dependencies ---\n\nexport interface EventPersistenceDeps {\n readonly getStorageLocation: (name: string) => IStorageLocation\n readonly subscribe: (filter: { category?: string }, handler: (event: SystemEvent) => void) => () => void\n readonly logger: IScopedLogger\n}\n\n// --- Service ---\n\nexport class EventPersistenceService {\n private eventBuffer: PersistableEvent[] = []\n private mediaBuffer: MediaEntry[] = []\n\n private eventFlushTimer?: ReturnType<typeof setInterval>\n private mediaFlushTimer?: ReturnType<typeof setInterval>\n\n private readonly EVENT_FLUSH_INTERVAL_MS = 5000\n private readonly MEDIA_FLUSH_INTERVAL_MS = 3000\n private readonly MAX_EVENT_BUFFER = 50\n private readonly MAX_MEDIA_BUFFER_MB = 50\n\n private readonly logger: IScopedLogger\n private readonly getStorageLocation: (name: string) => IStorageLocation\n private unsubscribe?: () => void\n\n constructor(private readonly deps: EventPersistenceDeps) {\n this.logger = deps.logger\n this.getStorageLocation = deps.getStorageLocation\n }\n\n start(): void {\n this.unsubscribe = this.deps.subscribe(\n { category: 'detection.event' },\n (event) => this.bufferEvent(event),\n )\n\n this.eventFlushTimer = setInterval(() => {\n this.flushEvents().catch(e => this.logger.warn(`Event flush failed: ${e}`))\n }, this.EVENT_FLUSH_INTERVAL_MS)\n\n this.mediaFlushTimer = setInterval(() => {\n this.flushMedia().catch(e => this.logger.warn(`Media flush failed: ${e}`))\n }, this.MEDIA_FLUSH_INTERVAL_MS)\n }\n\n stop(): void {\n this.unsubscribe?.()\n clearInterval(this.eventFlushTimer)\n clearInterval(this.mediaFlushTimer)\n this.flushEvents().catch(() => {})\n this.flushMedia().catch(() => {})\n }\n\n // --- Public: save media associated with an event ---\n\n /** Save object crops (thumbnail + full-res) for an event */\n saveDetectionCrops(eventId: string, crops: readonly ObjectSnapshotResult[]): void {\n for (const crop of crops) {\n if (crop.thumbnail) {\n this.bufferMedia(eventId, `events/${eventId}/crops/${crop.trackId}-thumb.jpg`, crop.thumbnail, 'high')\n }\n if (crop.fullCrop) {\n this.bufferMedia(eventId, `events/${eventId}/crops/${crop.trackId}-full.jpg`, crop.fullCrop, 'normal')\n }\n }\n }\n\n /** Save annotated frame (thumbnail + full-res) for an event */\n saveAnnotatedFrame(eventId: string, result: AnnotatedSnapshotResult): void {\n this.bufferMedia(eventId, `events/${eventId}/debug-annotated-thumb.jpg`, result.thumbnail, 'high')\n this.bufferMedia(eventId, `events/${eventId}/debug-annotated-full.jpg`, result.full, 'normal')\n }\n\n /** Save original frame for an event */\n async saveOriginalFrame(eventId: string, frame: VideoFrame): Promise<void> {\n const sharp = (await import('sharp')).default\n\n const jpeg = await sharp(frame.data, {\n raw: { width: frame.width, height: frame.height, channels: 3 },\n }).jpeg({ quality: 85 }).toBuffer()\n\n this.bufferMedia(eventId, `events/${eventId}/original.jpg`, jpeg, 'normal')\n\n const thumb = await sharp(jpeg)\n .resize(320, 240, { fit: 'inside' })\n .jpeg({ quality: 75 })\n .toBuffer()\n\n this.bufferMedia(eventId, `events/${eventId}/original-thumb.jpg`, thumb, 'high')\n }\n\n // --- Public: query track media ---\n\n /**\n * Get all media files for a specific event (from buffer or storage).\n * Returns immediately — serves from in-memory buffer if not yet flushed.\n */\n async getEventMedia(eventId: string): Promise<readonly TrackMediaFile[]> {\n const results: TrackMediaFile[] = []\n\n // 1. Check in-memory buffer first (not yet flushed)\n for (const entry of this.mediaBuffer) {\n if (entry.eventId === eventId) {\n results.push({\n path: entry.path,\n type: classifyMediaPath(entry.path),\n data: entry.data,\n source: 'buffer',\n })\n }\n }\n\n // 2. Check storage (already flushed)\n try {\n const location = this.getStorageLocation('events')\n if (location.files) {\n const files = await location.files.listFiles(`events/${eventId}/`)\n for (const file of files) {\n // Skip if already served from buffer\n if (results.some(r => r.path === file)) continue\n try {\n const data = await location.files.readFile(file)\n results.push({\n path: file,\n type: classifyMediaPath(file),\n data,\n source: 'storage',\n })\n } catch {\n // File may have been deleted by retention\n }\n }\n }\n } catch {\n // Storage not available\n }\n\n return results\n }\n\n /**\n * Get all media for a specific track across all events.\n * Useful for building a track timeline with all snapshots.\n */\n async getTrackMedia(trackId: string): Promise<readonly TrackMediaFile[]> {\n const results: TrackMediaFile[] = []\n\n // 1. Buffer: find media matching trackId in path\n for (const entry of this.mediaBuffer) {\n if (entry.path.includes(trackId)) {\n results.push({\n path: entry.path,\n type: classifyMediaPath(entry.path),\n data: entry.data,\n source: 'buffer',\n })\n }\n }\n\n // 2. Find events for this trackId in buffer\n const bufferedEventIds = this.eventBuffer\n .filter(e => e.trackId === trackId)\n .map(e => e.id)\n\n // 3. Find events for this trackId in storage\n try {\n const location = this.getStorageLocation('events')\n if (location.structured) {\n const stored = await location.structured.query('detection_events', {\n where: { trackId },\n orderBy: { field: 'timestamp', direction: 'asc' },\n limit: 100,\n })\n\n const allEventIds = new Set([\n ...bufferedEventIds,\n ...stored.map(r => r.id),\n ])\n\n // Get media for each event\n for (const eventId of allEventIds) {\n const eventMedia = await this.getEventMedia(eventId)\n for (const media of eventMedia) {\n if (!results.some(r => r.path === media.path)) {\n results.push(media)\n }\n }\n }\n }\n } catch {\n // Storage not available — serve from buffer only\n }\n\n return results\n }\n\n /**\n * Get all media for a device within a time range.\n */\n async getDeviceMedia(\n deviceId: string,\n since?: number,\n until?: number,\n ): Promise<readonly TrackMediaFile[]> {\n const results: TrackMediaFile[] = []\n\n try {\n const location = this.getStorageLocation('events')\n if (!location.structured) return results\n\n const events = await location.structured.query('detection_events', {\n where: { deviceId },\n whereBetween: since || until\n ? { timestamp: [since ?? 0, until ?? Date.now()] }\n : undefined,\n orderBy: { field: 'timestamp', direction: 'desc' },\n limit: 50,\n })\n\n for (const event of events) {\n const media = await this.getEventMedia(event.id)\n results.push(...media)\n }\n } catch {\n // Storage not available\n }\n\n return results\n }\n\n // --- Public: status ---\n\n getBufferStatus(): EventBufferStatus {\n return {\n eventCount: this.eventBuffer.length,\n mediaCount: this.mediaBuffer.length,\n mediaSizeMB: this.mediaBuffer.reduce((sum, m) => sum + m.data.length, 0) / (1024 * 1024),\n }\n }\n\n /** Force flush everything */\n async flush(): Promise<void> {\n await this.flushEvents()\n await this.flushMedia()\n }\n\n // --- Internal: buffering ---\n\n private bufferEvent(event: SystemEvent): void {\n // Collect paths of any media being buffered for this event\n const mediaPaths = this.mediaBuffer\n .filter(m => m.eventId === event.id)\n .map(m => m.path)\n\n const persistable: PersistableEvent = {\n id: event.id,\n timestamp: Date.now(),\n deviceId: (event.data.deviceId as string) ?? '',\n category: event.category,\n className: (event.data.className as string) ?? '',\n score: (event.data.score as number) ?? 0,\n trackId: (event.data.trackId as string) ?? '',\n severity: (event.data.severity as string) ?? '',\n description: (event.data.description as string) ?? '',\n data: event.data,\n mediaFiles: mediaPaths,\n }\n\n this.eventBuffer.push(persistable)\n\n // Buffer inline crop if present\n const detection = event.data.detection as Record<string, unknown> | undefined\n if (detection?.crop && Buffer.isBuffer(detection.crop)) {\n this.bufferMedia(event.id, `events/${event.id}/crop.jpg`, detection.crop as Buffer, 'high')\n }\n\n if (this.eventBuffer.length >= this.MAX_EVENT_BUFFER) {\n this.flushEvents().catch(e => this.logger.warn(`Auto-flush failed: ${e}`))\n }\n }\n\n private bufferMedia(eventId: string, path: string, data: Buffer, priority: 'high' | 'normal'): void {\n this.mediaBuffer.push({ eventId, path, data, priority })\n\n const totalMB = this.mediaBuffer.reduce((sum, m) => sum + m.data.length, 0) / (1024 * 1024)\n if (totalMB >= this.MAX_MEDIA_BUFFER_MB) {\n this.flushMedia().catch(e => this.logger.warn(`Media overflow flush failed: ${e}`))\n }\n }\n\n // --- Internal: flushing ---\n\n private async flushEvents(): Promise<void> {\n if (this.eventBuffer.length === 0) return\n\n const events = [...this.eventBuffer]\n this.eventBuffer = []\n\n try {\n const location = this.getStorageLocation('events')\n if (location.structured) {\n for (const event of events) {\n await location.structured.insert({\n collection: 'detection_events',\n id: event.id,\n data: event as unknown as Record<string, unknown>,\n })\n }\n this.logger.debug(`Flushed ${events.length} events`)\n }\n } catch (err) {\n this.logger.warn(`Failed to flush events: ${err}`)\n if (this.eventBuffer.length < this.MAX_EVENT_BUFFER * 2) {\n this.eventBuffer.push(...events)\n }\n }\n }\n\n private async flushMedia(): Promise<void> {\n if (this.mediaBuffer.length === 0) return\n\n // Sort: high priority first\n const sorted = [...this.mediaBuffer].sort((a, b) => {\n if (a.priority === 'high' && b.priority !== 'high') return -1\n if (a.priority !== 'high' && b.priority === 'high') return 1\n return 0\n })\n this.mediaBuffer = []\n\n try {\n const location = this.getStorageLocation('events')\n if (location.files) {\n for (const entry of sorted) {\n await location.files.writeFile(entry.path, entry.data)\n }\n const totalMB = sorted.reduce((s, e) => s + e.data.length, 0) / (1024 * 1024)\n this.logger.debug(`Flushed ${sorted.length} media files (${totalMB.toFixed(1)}MB)`)\n }\n } catch (err) {\n this.logger.warn(`Failed to flush media: ${err}`)\n }\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type { IScopedLogger, IStructuredStorage } from '@camstack/types'\n\n/**\n * Inlined from the removed sub-detectors/clip-recognizer.ts.\n * ClipRecognizer interface used by this service for embedding generation.\n */\nexport interface ClipRecognizer {\n getEmbedding(imageBuffer: Buffer): Promise<Float32Array>\n}\n\n/** Cosine similarity between two embedding vectors */\nexport function cosineSimilarity(a: Float32Array, b: Float32Array): number {\n if (a.length !== b.length) return 0\n let dotProduct = 0\n let normA = 0\n let normB = 0\n for (let i = 0; i < a.length; i++) {\n dotProduct += a[i]! * b[i]!\n normA += a[i]! * a[i]!\n normB += b[i]! * b[i]!\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB)\n return denom === 0 ? 0 : dotProduct / denom\n}\n\nexport interface KnownFaceEntry {\n readonly id: string\n readonly label: string\n readonly group?: string\n readonly embedding: readonly number[]\n readonly cropBase64: string\n readonly createdAt: number\n readonly updatedAt: number\n readonly source?: string\n readonly metadata?: Readonly<Record<string, unknown>>\n}\n\nexport interface KnownFacesDeps {\n readonly getStructuredStorage: () => IStructuredStorage\n readonly logger: IScopedLogger\n}\n\nconst COLLECTION = 'known_faces'\n\nexport class KnownFacesService {\n private readonly logger: IScopedLogger\n private readonly getStructuredStorage: () => IStructuredStorage\n\n constructor(deps: KnownFacesDeps) {\n this.logger = deps.logger\n this.getStructuredStorage = deps.getStructuredStorage\n }\n\n private getStorage(): IStructuredStorage {\n return this.getStructuredStorage()\n }\n\n /** Register a known face */\n async register(entry: KnownFaceEntry): Promise<void> {\n const storage = this.getStorage()\n await storage.insert({\n collection: COLLECTION,\n id: entry.id,\n data: {\n label: entry.label,\n group: entry.group ?? null,\n embedding: JSON.stringify(Array.from(entry.embedding)),\n cropBase64: entry.cropBase64,\n createdAt: entry.createdAt,\n updatedAt: entry.updatedAt,\n source: entry.source ?? null,\n metadata: entry.metadata ? JSON.stringify(entry.metadata) : null,\n },\n })\n this.logger.info(`Registered known face: ${entry.label} (${entry.id})`)\n }\n\n /** Get all known faces */\n async listAll(): Promise<readonly KnownFaceEntry[]> {\n const storage = this.getStorage()\n const records = await storage.query(COLLECTION)\n return records.map((r) => recordToEntry(r.id, r.data))\n }\n\n /** Delete a known face */\n async delete(id: string): Promise<void> {\n const storage = this.getStorage()\n await storage.delete(COLLECTION, id)\n this.logger.info(`Deleted known face: ${id}`)\n }\n\n /** Update a known face (reassign label or group) */\n async update(id: string, updates: Partial<Pick<KnownFaceEntry, 'label' | 'group'>>): Promise<void> {\n const storage = this.getStorage()\n const data: Record<string, unknown> = { updatedAt: Date.now() }\n if (updates.label !== undefined) data.label = updates.label\n if (updates.group !== undefined) data.group = updates.group\n await storage.update(COLLECTION, id, data)\n this.logger.info(`Updated known face ${id}: ${JSON.stringify(updates)}`)\n }\n\n /** Recalculate embedding for a face (re-run CLIP on the stored crop) */\n async recalculateEmbedding(id: string, clipRecognizer: ClipRecognizer): Promise<void> {\n const storage = this.getStorage()\n const records = await storage.query(COLLECTION, { where: { id } })\n if (records.length === 0) throw new Error(`Known face not found: ${id}`)\n\n const record = records[0]!\n const cropBase64 = record.data.cropBase64 as string\n const cropBuffer = Buffer.from(cropBase64, 'base64')\n\n const embedding = await clipRecognizer.getEmbedding(cropBuffer)\n await storage.update(COLLECTION, id, {\n embedding: JSON.stringify(Array.from(embedding)),\n updatedAt: Date.now(),\n })\n this.logger.info(`Recalculated embedding for known face: ${id}`)\n }\n\n /** Find best match for an embedding */\n async findMatch(\n embedding: Float32Array,\n threshold: number,\n ): Promise<{ readonly entry: KnownFaceEntry; readonly similarity: number } | null> {\n const entries = await this.listAll()\n let bestEntry: KnownFaceEntry | null = null\n let bestSimilarity = -1\n\n for (const entry of entries) {\n const knownEmbedding = new Float32Array(entry.embedding)\n const similarity = cosineSimilarity(embedding, knownEmbedding)\n if (similarity > bestSimilarity) {\n bestSimilarity = similarity\n bestEntry = entry\n }\n }\n\n if (bestEntry && bestSimilarity >= threshold) {\n return { entry: bestEntry, similarity: bestSimilarity }\n }\n\n return null\n }\n\n /** Batch import faces from crops */\n async batchRegister(\n entries: ReadonlyArray<{ readonly label: string; readonly cropBase64: string; readonly group?: string }>,\n clipRecognizer: ClipRecognizer,\n ): Promise<readonly KnownFaceEntry[]> {\n const results: KnownFaceEntry[] = []\n for (const input of entries) {\n const cropBuffer = Buffer.from(input.cropBase64, 'base64')\n const embedding = await clipRecognizer.getEmbedding(cropBuffer)\n const now = Date.now()\n const entry: KnownFaceEntry = {\n id: randomUUID(),\n label: input.label,\n group: input.group,\n embedding: Array.from(embedding),\n cropBase64: input.cropBase64,\n createdAt: now,\n updatedAt: now,\n }\n await this.register(entry)\n results.push(entry)\n }\n this.logger.info(`Batch registered ${results.length} known faces`)\n return results\n }\n}\n\nfunction recordToEntry(id: string, data: Record<string, unknown>): KnownFaceEntry {\n return {\n id,\n label: data.label as string,\n group: (data.group as string) ?? undefined,\n embedding: JSON.parse(data.embedding as string) as number[],\n cropBase64: data.cropBase64 as string,\n createdAt: data.createdAt as number,\n updatedAt: data.updatedAt as number,\n source: (data.source as string) ?? undefined,\n metadata: data.metadata ? JSON.parse(data.metadata as string) : undefined,\n }\n}\n","/**\n * Session Tracker Service — maintains persistent object tracking across\n * multiple analysis pipeline runs, linking tracked objects to their\n * CLIP embeddings for re-identification across cameras.\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type { IScopedLogger, IEventBus, AnalysisContext, ServerTrackedDetection as TrackedDetection } from '@camstack/types'\n\nexport interface SessionTrack {\n readonly trackId: string\n readonly deviceId: string\n readonly className: string\n readonly label?: string\n readonly firstSeen: number\n readonly lastSeen: number\n readonly totalFrames: number\n readonly lastDetection: TrackedDetection\n readonly state: string\n readonly positions: ReadonlyArray<{ readonly x: number; readonly y: number; readonly t: number }>\n readonly embedding?: Float32Array\n readonly globalId?: string\n readonly bestCrop?: Buffer\n}\n\nexport interface GlobalIdentity {\n readonly globalId: string\n readonly embedding: Float32Array\n readonly label?: string\n readonly firstSeen: number\n readonly lastSeen: number\n readonly deviceIds: ReadonlyArray<string>\n}\n\n/** Max positions to keep per track (~5 min at 1fps) */\nconst MAX_POSITIONS = 300\n\n/** Tracks not seen for this many ms are expired */\nconst TRACK_EXPIRY_MS = 30_000\n\nexport interface SessionTrackerDeps {\n readonly eventBus: IEventBus\n readonly logger: IScopedLogger\n}\n\nexport class SessionTrackerService {\n /** Active tracks per device: deviceId -> Map<trackId, SessionTrack> */\n private activeTracks: Map<string, Map<string, SessionTrack>> = new Map()\n\n /** Global re-id pool: globalId -> GlobalIdentity (same person across cameras) */\n private globalPool: Map<string, GlobalIdentity> = new Map()\n\n private readonly logger: IScopedLogger\n private readonly eventBus: IEventBus\n\n constructor(deps: SessionTrackerDeps) {\n this.logger = deps.logger\n this.eventBus = deps.eventBus\n }\n\n /** Update tracks from an analysis result */\n updateFromAnalysis(deviceId: string, ctx: AnalysisContext): void {\n const deviceTracks = this.getDeviceTracks(deviceId)\n const now = ctx.timestamp\n\n for (const td of ctx.trackedDetections) {\n const existing = deviceTracks.get(td.trackId)\n\n if (existing) {\n // Update existing track — create new object (immutable)\n const newPositions = [\n ...existing.positions,\n { x: td.detection.boundingBox?.[0] ?? 0, y: td.detection.boundingBox?.[1] ?? 0, t: now },\n ]\n const trimmedPositions = newPositions.length > MAX_POSITIONS\n ? newPositions.slice(newPositions.length - MAX_POSITIONS)\n : newPositions\n\n const updatedEmbedding = (td.recognitions.length > 0 && td.recognitions[0]?.embedding)\n ? new Float32Array(td.recognitions[0].embedding)\n : existing.embedding\n\n const updated: SessionTrack = {\n ...existing,\n lastSeen: now,\n totalFrames: existing.totalFrames + 1,\n lastDetection: td,\n state: td.tracking.state,\n positions: trimmedPositions,\n embedding: updatedEmbedding,\n }\n deviceTracks.set(td.trackId, updated)\n } else {\n // New track\n const track: SessionTrack = {\n trackId: td.trackId,\n deviceId,\n className: td.detection.className,\n label: td.detection.label,\n firstSeen: now,\n lastSeen: now,\n totalFrames: 1,\n lastDetection: td,\n state: td.tracking.state,\n positions: [{ x: td.detection.boundingBox?.[0] ?? 0, y: td.detection.boundingBox?.[1] ?? 0, t: now }],\n embedding: td.recognitions[0]?.embedding ? new Float32Array(td.recognitions[0].embedding) : undefined,\n globalId: undefined,\n bestCrop: td.crop,\n }\n\n deviceTracks.set(td.trackId, track)\n\n this.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'core', id: 'session-tracker' },\n category: 'session.track.new',\n data: { deviceId, trackId: td.trackId, className: td.detection.className },\n })\n }\n }\n\n // Expire old tracks (not seen for TRACK_EXPIRY_MS)\n for (const [trackId, track] of deviceTracks) {\n if (now - track.lastSeen > TRACK_EXPIRY_MS) {\n deviceTracks.delete(trackId)\n this.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'core', id: 'session-tracker' },\n category: 'session.track.expired',\n data: { deviceId, trackId, className: track.className, duration: now - track.firstSeen },\n })\n }\n }\n }\n\n /** Get all active tracks for a device */\n getActiveTracks(deviceId: string): readonly SessionTrack[] {\n return [...(this.activeTracks.get(deviceId)?.values() ?? [])]\n }\n\n /** Get all active tracks across all devices */\n getAllActiveTracks(): readonly SessionTrack[] {\n const all: SessionTrack[] = []\n for (const tracks of this.activeTracks.values()) {\n all.push(...tracks.values())\n }\n return all\n }\n\n /** Get a specific track */\n getTrack(deviceId: string, trackId: string): SessionTrack | null {\n return this.activeTracks.get(deviceId)?.get(trackId) ?? null\n }\n\n /** Get track count per device */\n getTrackCounts(): Record<string, { total: number; byClass: Record<string, number> }> {\n const counts: Record<string, { total: number; byClass: Record<string, number> }> = {}\n for (const [deviceId, tracks] of this.activeTracks) {\n const byClass: Record<string, number> = {}\n for (const track of tracks.values()) {\n byClass[track.className] = (byClass[track.className] ?? 0) + 1\n }\n counts[deviceId] = { total: tracks.size, byClass }\n }\n return counts\n }\n\n /** Clear all tracks for a device */\n clearDevice(deviceId: string): void {\n this.activeTracks.delete(deviceId)\n }\n\n /** Clear all tracks */\n clearAll(): void {\n this.activeTracks.clear()\n this.globalPool.clear()\n }\n\n /** Get the global identity pool (for diagnostics) */\n getGlobalPool(): ReadonlyMap<string, GlobalIdentity> {\n return this.globalPool\n }\n\n private getDeviceTracks(deviceId: string): Map<string, SessionTrack> {\n if (!this.activeTracks.has(deviceId)) {\n this.activeTracks.set(deviceId, new Map())\n }\n return this.activeTracks.get(deviceId)!\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type { IScopedLogger, IStorageLocation, IEventBus } from '@camstack/types'\n\n// ---------------------------------------------------------------------------\n// Config & Types\n// ---------------------------------------------------------------------------\n\nexport interface RetentionConfig {\n /** How often to run cleanup (ms). Default: 1 hour */\n readonly cleanupIntervalMs: number\n /** Detection events + associated media retention (days). Default: 30 */\n readonly detectionEventsDays: number\n /** Audio levels retention (days). Default: 7 */\n readonly audioLevelsDays: number\n /** DEPRECATED — snapshots are tied to events, use detectionEventsDays */\n readonly snapshotsDays: number\n /** Per-device overrides */\n readonly deviceOverrides?: Readonly<Record<string, Partial<Pick<RetentionConfig, 'detectionEventsDays' | 'audioLevelsDays' | 'snapshotsDays'>>>>\n}\n\nexport const DEFAULT_RETENTION: RetentionConfig = {\n cleanupIntervalMs: 60 * 60 * 1000, // 1 hour\n detectionEventsDays: 30,\n audioLevelsDays: 7,\n snapshotsDays: 14,\n}\n\nexport interface RetentionReport {\n readonly deletedEvents: number\n readonly deletedAudioRecords: number\n readonly deletedSnapshots: number\n}\n\n// ---------------------------------------------------------------------------\n// Dependencies\n// ---------------------------------------------------------------------------\n\nexport interface RetentionDeps {\n readonly getStorageLocation: (name: string) => IStorageLocation\n readonly getRetentionConfig: () => Partial<RetentionConfig> | undefined\n readonly eventBus: IEventBus\n readonly logger: IScopedLogger\n}\n\n// ---------------------------------------------------------------------------\n// Service\n// ---------------------------------------------------------------------------\n\nexport class RetentionService {\n private cleanupTimer?: ReturnType<typeof setInterval>\n private initialTimer?: ReturnType<typeof setTimeout>\n private config: RetentionConfig\n\n private readonly logger: IScopedLogger\n private readonly getStorageLocation: (name: string) => IStorageLocation\n private readonly eventBus: IEventBus\n\n constructor(private readonly deps: RetentionDeps) {\n this.logger = deps.logger\n this.getStorageLocation = deps.getStorageLocation\n this.eventBus = deps.eventBus\n\n // Load config from app configuration\n const retention = deps.getRetentionConfig()\n this.config = retention\n ? { ...DEFAULT_RETENTION, ...retention }\n : { ...DEFAULT_RETENTION }\n }\n\n start(): void {\n // Run initial cleanup after 5 minutes (let system boot first)\n this.initialTimer = setTimeout(() => {\n this.runCleanup().catch((err) => {\n this.logger.warn(`Initial cleanup failed: ${err}`)\n })\n }, 5 * 60 * 1000)\n\n // Schedule periodic cleanup\n this.cleanupTimer = setInterval(() => {\n this.runCleanup().catch((err) => {\n this.logger.warn(`Periodic cleanup failed: ${err}`)\n })\n }, this.config.cleanupIntervalMs)\n }\n\n stop(): void {\n if (this.initialTimer) {\n clearTimeout(this.initialTimer)\n this.initialTimer = undefined\n }\n if (this.cleanupTimer) {\n clearInterval(this.cleanupTimer)\n this.cleanupTimer = undefined\n }\n }\n\n /** Run all retention cleanup tasks */\n async runCleanup(): Promise<RetentionReport> {\n const report: RetentionReport = {\n deletedEvents: 0,\n deletedAudioRecords: 0,\n deletedSnapshots: 0,\n }\n\n try {\n // 1. Clean detection events + their associated media files\n const eventCutoff = Date.now() - this.config.detectionEventsDays * 24 * 60 * 60 * 1000\n const { dbDeleted: eventsDeleted, filesDeleted: snapshotsDeleted } =\n await this.cleanEventsWithMedia('detection_events', eventCutoff)\n\n // 2. Clean audio levels (DB only, no media)\n const audioCutoff = Date.now() - this.config.audioLevelsDays * 24 * 60 * 60 * 1000\n const audioDeleted = await this.cleanCollection('audio_levels', audioCutoff)\n\n // Build immutable report\n const finalReport: RetentionReport = {\n deletedEvents: eventsDeleted,\n deletedAudioRecords: audioDeleted,\n deletedSnapshots: snapshotsDeleted,\n }\n\n if (finalReport.deletedEvents > 0 || finalReport.deletedAudioRecords > 0 || finalReport.deletedSnapshots > 0) {\n this.logger.info(\n `Cleanup: ${finalReport.deletedEvents} events, ${finalReport.deletedAudioRecords} audio records, ${finalReport.deletedSnapshots} snapshots`,\n )\n\n this.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'core', id: 'retention' },\n category: 'retention.cleanup',\n data: finalReport as unknown as Record<string, unknown>,\n })\n }\n\n return finalReport\n } catch (err) {\n this.logger.warn(`Retention cleanup failed: ${err}`)\n }\n\n return report\n }\n\n /** Set retention config at runtime */\n setConfig(update: Partial<RetentionConfig>): void {\n this.config = { ...this.config, ...update }\n }\n\n /** Get current retention config */\n getConfig(): RetentionConfig {\n return { ...this.config }\n }\n\n /** Force immediate cleanup */\n async forceCleanup(): Promise<RetentionReport> {\n return this.runCleanup()\n }\n\n /**\n * Delete old events AND their associated media files.\n * Media is stored at events/{eventId}/ — delete the entire directory.\n */\n private async cleanEventsWithMedia(\n collection: string,\n cutoffTimestamp: number,\n ): Promise<{ dbDeleted: number; filesDeleted: number }> {\n let dbDeleted = 0\n let filesDeleted = 0\n\n try {\n const location = this.getStorageLocation('events')\n if (!location.structured) return { dbDeleted, filesDeleted }\n\n const old = await location.structured.query(collection, {\n whereBetween: { timestamp: [0, cutoffTimestamp] },\n limit: 1000,\n })\n\n for (const record of old) {\n const eventId = record.id\n\n // Delete associated media files\n if (location.files) {\n try {\n const files = await location.files.listFiles(`events/${eventId}/`)\n for (const file of files) {\n await location.files.deleteFile(file)\n filesDeleted++\n }\n } catch {\n // Media may not exist for every event\n }\n }\n\n // Delete DB record\n await location.structured.delete(collection, eventId)\n dbDeleted++\n }\n } catch {\n // Swallow errors — retention is best-effort\n }\n\n return { dbDeleted, filesDeleted }\n }\n\n private async cleanCollection(collection: string, cutoffTimestamp: number): Promise<number> {\n try {\n const location = this.getStorageLocation('events')\n if (!location.structured) return 0\n\n // Query old records\n const old = await location.structured.query(collection, {\n whereBetween: { timestamp: [0, cutoffTimestamp] },\n limit: 1000, // batch delete\n })\n\n // Delete each\n let deleted = 0\n for (const record of old) {\n await location.structured.delete(collection, record.id)\n deleted++\n }\n\n return deleted\n } catch {\n return 0\n }\n }\n}\n","/**\n * Track Trail Service — persists the complete trajectory of tracked objects.\n *\n * For each active track, records:\n * - Every position (x, y, timestamp) at detection FPS\n * - Periodic snapshot crops at configurable intervals\n * - Track metadata (class, first/last seen, total distance, zones visited)\n */\n\nimport type { IScopedLogger, IStorageLocation, AnalysisContext, ServerTrackedDetection as TrackedDetection } from '@camstack/types'\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport interface TrackCaptureConfig {\n /** Enable trail capture for this device (default: false) */\n readonly enabled: boolean\n /** How often to save a snapshot of the same track (ms, default: 2000 = every 2s) */\n readonly snapshotIntervalMs: number\n /** Max trail entries per track before pruning oldest (default: 500) */\n readonly maxTrailLength: number\n /** Save thumbnail crops in trail snapshots (default: true) */\n readonly saveThumbnails: boolean\n /** Thumbnail size (default: 120x120) */\n readonly thumbnailSize: { readonly width: number; readonly height: number }\n}\n\nconst DEFAULT_CONFIG: TrackCaptureConfig = {\n enabled: false,\n snapshotIntervalMs: 2000,\n maxTrailLength: 500,\n saveThumbnails: true,\n thumbnailSize: { width: 120, height: 120 },\n}\n\n// ---------------------------------------------------------------------------\n// Trail data structures\n// ---------------------------------------------------------------------------\n\nexport interface TrackPosition {\n readonly x: number\n readonly y: number\n readonly timestamp: number\n /** Bounding box at this position [x, y, w, h] normalized */\n readonly bbox: readonly [number, number, number, number]\n}\n\nexport interface TrackSnapshot {\n readonly timestamp: number\n readonly position: TrackPosition\n /** Path to saved thumbnail in storage */\n readonly thumbnailPath: string\n}\n\nexport interface TrackTrail {\n readonly trackId: string\n readonly deviceId: string\n readonly className: string\n readonly label?: string\n readonly firstSeen: number\n readonly lastSeen: number\n /** All recorded positions (at detection FPS) */\n readonly positions: readonly TrackPosition[]\n /** Periodic snapshots (at configured interval) */\n readonly snapshots: readonly TrackSnapshot[]\n /** Total distance traveled (normalized units, 0-1 = full frame width) */\n readonly totalDistance: number\n /** All zones this track has visited */\n readonly zonesVisited: readonly string[]\n /** Whether the track is still active */\n readonly active: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Dependencies\n// ---------------------------------------------------------------------------\n\nexport interface TrackTrailDeps {\n readonly getStorageLocation: (name: string) => IStorageLocation\n readonly logger: IScopedLogger\n}\n\n// ---------------------------------------------------------------------------\n// Service\n// ---------------------------------------------------------------------------\n\nexport class TrackTrailService {\n private readonly logger: IScopedLogger\n private readonly getStorageLocation: (name: string) => IStorageLocation\n\n /** Per-device config */\n private readonly deviceConfigs: Map<string, TrackCaptureConfig> = new Map()\n\n /** Active trails: trackId -> mutable trail state */\n private readonly activeTrails: Map<string, MutableTrail> = new Map()\n\n /** Last snapshot time per track (for interval throttling) */\n private readonly lastSnapshotTime: Map<string, number> = new Map()\n\n constructor(deps: TrackTrailDeps) {\n this.logger = deps.logger\n this.getStorageLocation = deps.getStorageLocation\n }\n\n // --- Config ---\n\n setConfig(deviceId: string, config: Partial<TrackCaptureConfig>): void {\n this.deviceConfigs.set(deviceId, { ...DEFAULT_CONFIG, ...config })\n }\n\n getConfig(deviceId: string): TrackCaptureConfig {\n return this.deviceConfigs.get(deviceId) ?? { ...DEFAULT_CONFIG }\n }\n\n // --- Called after each analysis pass ---\n\n /**\n * Record trail data from an analysis context.\n * Called by the analysis pipeline addon after each frame's analysis.\n */\n async recordFrame(ctx: AnalysisContext): Promise<void> {\n const config = this.getConfig(ctx.deviceId)\n if (!config.enabled) return\n\n const activeTrackIds = new Set<string>()\n\n for (const td of ctx.trackedDetections) {\n activeTrackIds.add(td.trackId)\n await this.updateTrail(ctx, td, config)\n }\n\n // Mark trails as inactive if their track is gone\n for (const [trackId, trail] of this.activeTrails) {\n if (trail.deviceId === ctx.deviceId && !activeTrackIds.has(trackId)) {\n trail.active = false\n // Persist final trail to storage\n await this.persistTrail(trail)\n this.activeTrails.delete(trackId)\n this.lastSnapshotTime.delete(trackId)\n }\n }\n }\n\n // --- Query ---\n\n /** Get the live trail for an active track (from memory) */\n getActiveTrail(trackId: string): TrackTrail | null {\n const trail = this.activeTrails.get(trackId)\n return trail ? this.toImmutable(trail) : null\n }\n\n /** Get all active trails for a device */\n getActiveTrails(deviceId: string): readonly TrackTrail[] {\n const results: TrackTrail[] = []\n for (const trail of this.activeTrails.values()) {\n if (trail.deviceId === deviceId) {\n results.push(this.toImmutable(trail))\n }\n }\n return results\n }\n\n /** Get a persisted trail from storage (after track ended) */\n async getPersistedTrail(trackId: string): Promise<TrackTrail | null> {\n try {\n const location = this.getStorageLocation('events')\n if (!location.structured) return null\n\n const results = await location.structured.query('track_trails', {\n where: { trackId },\n limit: 1,\n })\n\n if (results.length === 0) return null\n return results[0]!.data as unknown as TrackTrail\n } catch {\n return null\n }\n }\n\n /** Get trail from memory (active) or storage (persisted) */\n async getTrail(trackId: string): Promise<TrackTrail | null> {\n return this.getActiveTrail(trackId) ?? this.getPersistedTrail(trackId)\n }\n\n /** List recent persisted trails for a device */\n async listTrails(deviceId: string, options?: {\n since?: number\n until?: number\n limit?: number\n className?: string\n }): Promise<readonly TrackTrail[]> {\n const results: TrackTrail[] = []\n\n // Active trails\n for (const trail of this.activeTrails.values()) {\n if (trail.deviceId === deviceId) {\n if (options?.className && trail.className !== options.className) continue\n results.push(this.toImmutable(trail))\n }\n }\n\n // Persisted trails\n try {\n const location = this.getStorageLocation('events')\n if (location.structured) {\n const stored = await location.structured.query('track_trails', {\n where: { deviceId, ...(options?.className ? { className: options.className } : {}) },\n whereBetween: options?.since || options?.until\n ? { firstSeen: [options.since ?? 0, options.until ?? Date.now()] }\n : undefined,\n orderBy: { field: 'firstSeen', direction: 'desc' },\n limit: options?.limit ?? 50,\n })\n\n for (const record of stored) {\n const trail = record.data as unknown as TrackTrail\n // Don't duplicate active trails\n if (!this.activeTrails.has(trail.trackId)) {\n results.push(trail)\n }\n }\n }\n } catch {\n // Storage not available\n }\n\n return results\n }\n\n // --- Internal ---\n\n private async updateTrail(\n ctx: AnalysisContext,\n td: TrackedDetection,\n config: TrackCaptureConfig,\n ): Promise<void> {\n let trail = this.activeTrails.get(td.trackId)\n\n const bbox = td.detection.boundingBox\n if (!bbox || bbox.length < 4) return\n\n const center = { x: bbox[0]! + bbox[2]! / 2, y: bbox[1]! + bbox[3]! / 2 }\n\n const position: TrackPosition = {\n x: center.x,\n y: center.y,\n timestamp: ctx.timestamp,\n bbox: [bbox[0]!, bbox[1]!, bbox[2]!, bbox[3]!] as readonly [number, number, number, number],\n }\n\n if (!trail) {\n trail = {\n trackId: td.trackId,\n deviceId: ctx.deviceId,\n className: td.detection.className,\n label: td.detection.label,\n firstSeen: ctx.timestamp,\n lastSeen: ctx.timestamp,\n positions: [position],\n snapshots: [],\n totalDistance: 0,\n zonesVisited: [...td.zones],\n active: true,\n }\n this.activeTrails.set(td.trackId, trail)\n } else {\n // Compute distance from last position\n const lastPos = trail.positions[trail.positions.length - 1]\n const dist = lastPos\n ? Math.sqrt((center.x - lastPos.x) ** 2 + (center.y - lastPos.y) ** 2)\n : 0\n\n trail.lastSeen = ctx.timestamp\n trail.totalDistance += dist\n\n // Add position (respect max length)\n if (trail.positions.length >= config.maxTrailLength) {\n // Downsample: keep every other position in the first half\n const half = Math.floor(trail.positions.length / 2)\n const downsampled = trail.positions.filter((_, i) => i >= half || i % 2 === 0)\n trail.positions = [...downsampled, position]\n } else {\n trail.positions = [...trail.positions, position]\n }\n\n // Merge new zones\n for (const zone of td.zones) {\n if (!trail.zonesVisited.includes(zone)) {\n trail.zonesVisited = [...trail.zonesVisited, zone]\n }\n }\n }\n\n // Periodic snapshot capture\n const lastSnapshot = this.lastSnapshotTime.get(td.trackId) ?? 0\n if (ctx.timestamp - lastSnapshot >= config.snapshotIntervalMs && td.crop) {\n await this.captureSnapshot(trail, td, position, config)\n this.lastSnapshotTime.set(td.trackId, ctx.timestamp)\n }\n }\n\n private async captureSnapshot(\n trail: MutableTrail,\n td: TrackedDetection,\n position: TrackPosition,\n config: TrackCaptureConfig,\n ): Promise<void> {\n if (!td.crop || !config.saveThumbnails) return\n\n try {\n const sharp = (await import('sharp')).default\n const thumb = await sharp(td.crop)\n .resize(config.thumbnailSize.width, config.thumbnailSize.height, { fit: 'cover' })\n .jpeg({ quality: 75 })\n .toBuffer()\n\n const path = `debug-trails/${trail.trackId}/${position.timestamp}.jpg`\n\n // Write to storage\n const location = this.getStorageLocation('events')\n if (location.files) {\n await location.files.writeFile(path, thumb)\n }\n\n trail.snapshots = [...trail.snapshots, {\n timestamp: position.timestamp,\n position,\n thumbnailPath: path,\n }]\n } catch {\n // Snapshot capture failed — non-critical, continue\n }\n }\n\n private async persistTrail(trail: MutableTrail): Promise<void> {\n try {\n const location = this.getStorageLocation('events')\n if (!location.structured) return\n\n await location.structured.insert({\n collection: 'track_trails',\n id: trail.trackId,\n data: this.toImmutable(trail) as unknown as Record<string, unknown>,\n })\n\n this.logger.debug(\n `Trail persisted: ${trail.trackId} (${trail.positions.length} positions, ${trail.snapshots.length} snapshots, ${trail.totalDistance.toFixed(3)} distance)`,\n )\n } catch (err) {\n this.logger.warn(`Failed to persist trail ${trail.trackId}: ${err}`)\n }\n }\n\n private toImmutable(trail: MutableTrail): TrackTrail {\n return {\n trackId: trail.trackId,\n deviceId: trail.deviceId,\n className: trail.className,\n label: trail.label,\n firstSeen: trail.firstSeen,\n lastSeen: trail.lastSeen,\n positions: [...trail.positions],\n snapshots: [...trail.snapshots],\n totalDistance: trail.totalDistance,\n zonesVisited: [...trail.zonesVisited],\n active: trail.active,\n }\n }\n}\n\n// Mutable internal state (not exported)\ninterface MutableTrail {\n readonly trackId: string\n readonly deviceId: string\n readonly className: string\n readonly label?: string\n firstSeen: number\n lastSeen: number\n positions: TrackPosition[]\n snapshots: TrackSnapshot[]\n totalDistance: number\n zonesVisited: string[]\n active: boolean\n}\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n CapabilityProviderMap,\n IConfigurable,\n ConfigUISchema,\n FfmpegConfig,\n IStreamingEngine,\n IPipelineManager,\n INetworkQualityTracker,\n IScopedLogger,\n IAnalysisAddon,\n} from '@camstack/types'\nimport { DecoderRegistry } from './stream-broker/decoder-registry'\nimport { FfmpegDecoderProvider } from './stream-broker/ffmpeg-decoder-provider'\nimport { StreamBrokerManager } from './stream-broker/stream-broker-manager'\nimport { EventPersistenceService } from './persistence/event-persistence'\nimport { KnownFacesService } from './persistence/known-faces'\nimport { SessionTrackerService } from './persistence/session-tracker'\nimport { RetentionService } from './persistence/retention'\nimport { TrackTrailService } from './persistence/track-trail'\nimport type { IAnalysisDataPersistence } from './persistence/types'\n\n// Recording engine imports — dynamic to avoid hard dep on better-sqlite3 at load time\ntype RecordingCoordinator = import('./recording/recording-coordinator').RecordingCoordinator\ntype RecordingDb = import('./recording/recording-db').RecordingDb\n\nexport interface RecordingEngineDependencies {\n readonly streamingEngine: IStreamingEngine\n readonly pipelineManager: IPipelineManager\n readonly networkTracker: INetworkQualityTracker\n}\n\nexport type RecordingEngineV2Dependencies = RecordingEngineDependencies\n\nexport class PipelineAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'pipeline',\n name: 'CamStack Pipeline',\n version: '0.1.0',\n capabilities: [\n { name: 'stream-broker', mode: 'singleton' as const },\n { name: 'recording-engine', mode: 'singleton' as const },\n { name: 'analysis-pipeline', mode: 'singleton' as const },\n { name: 'analysis-data-persistence', mode: 'singleton' as const },\n { name: 'webrtc', mode: 'collection' as const },\n ],\n }\n\n // Stream broker\n private brokerManager: StreamBrokerManager | null = null\n\n // Recording engine\n private coordinator: RecordingCoordinator | null = null\n private recordingDb: RecordingDb | null = null\n private sqliteDb: import('better-sqlite3').Database | null = null\n private recordingDeps: RecordingEngineDependencies | null = null\n private currentRecordingConfig = {\n ffmpegPath: 'ffmpeg',\n hwaccel: undefined as FfmpegConfig['hwaccel'] | undefined,\n threads: undefined as number | undefined,\n }\n\n // Analysis pipeline\n private analysisPipeline: IAnalysisAddon | null = null\n private analysisLogger: IScopedLogger | null = null\n\n // Analysis persistence\n private persistenceFacade: IAnalysisDataPersistence | null = null\n\n setRecordingDependencies(deps: RecordingEngineDependencies): void {\n this.recordingDeps = deps\n }\n\n async initialize(context: AddonContext): Promise<void> {\n // --- 1. Stream broker ---\n const registry = new DecoderRegistry()\n registry.register(new FfmpegDecoderProvider(), 50)\n this.brokerManager = new StreamBrokerManager(registry, context.logger)\n context.logger.info('Stream broker manager initialized')\n\n // --- 2. Recording engine ---\n if (this.recordingDeps) {\n try {\n const Database = (await import('better-sqlite3')).default\n const path = await import('node:path')\n const { detectPlatformDefaults } = await import('./recording/ffmpeg-config')\n const { RecordingDb: RecDb } = await import('./recording/recording-db')\n const { RecordingCoordinator: RecCoord } = await import('./recording/recording-coordinator')\n\n const storagePath = context.locationPaths.recordings\n const dbPath = path.join(context.locationPaths.data, 'camstack.db')\n this.sqliteDb = new Database(dbPath)\n this.recordingDb = new RecDb(this.sqliteDb)\n this.recordingDb.initialize()\n\n const ffmpegPath = (context.addonConfig.ffmpegPath as string) ?? this.currentRecordingConfig.ffmpegPath\n const detectedFfmpegConfig = detectPlatformDefaults(ffmpegPath)\n\n const globalFfmpegConfig: Partial<FfmpegConfig> = {\n path: ffmpegPath,\n hwaccel: (context.addonConfig.hwaccel as FfmpegConfig['hwaccel']) ?? this.currentRecordingConfig.hwaccel,\n threads: (context.addonConfig.threads as number) ?? this.currentRecordingConfig.threads,\n }\n\n this.currentRecordingConfig = {\n ffmpegPath,\n hwaccel: globalFfmpegConfig.hwaccel,\n threads: globalFfmpegConfig.threads,\n }\n\n const fileStorage = context.storage.files\n if (!fileStorage) {\n throw new Error('PipelineAddon:file storage not available in addon context')\n }\n\n this.coordinator = new RecCoord({\n db: this.recordingDb,\n logger: context.logger,\n eventBus: context.eventBus,\n streamingEngine: this.recordingDeps.streamingEngine,\n pipelineManager: this.recordingDeps.pipelineManager,\n networkTracker: this.recordingDeps.networkTracker,\n fileStorage,\n storagePath,\n globalFfmpegConfig,\n detectedFfmpegConfig,\n })\n await this.coordinator.start()\n context.logger.info('Recording Engine initialized')\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n context.logger.warn(`Recording Engine failed to initialize: ${msg}`)\n }\n }\n\n // --- 3. Analysis pipeline ---\n this.analysisLogger = context.logger\n try {\n const mod = await import('@camstack/lib-pipeline-analysis')\n const instance = new mod.AnalysisPipeline()\n this.analysisPipeline = instance as unknown as IAnalysisAddon\n this.analysisLogger.info('Analysis pipeline loaded successfully')\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.analysisLogger.warn(`Analysis pipeline not available: ${msg} -- analysis features disabled`)\n }\n\n // --- 4. Analysis persistence ---\n const eventPersistence = new EventPersistenceService({\n getStorageLocation: () => context.storage,\n subscribe: (filter, handler) => context.eventBus.subscribe(filter, handler),\n logger: context.logger.child('EventPersistence'),\n })\n\n const knownFaces = new KnownFacesService({\n getStructuredStorage: () => {\n if (!context.storage.structured) {\n throw new Error('Structured storage not available for analysis persistence')\n }\n return context.storage.structured\n },\n logger: context.logger.child('KnownFaces'),\n })\n\n const sessionTracker = new SessionTrackerService({\n eventBus: context.eventBus,\n logger: context.logger.child('SessionTracker'),\n })\n\n const retention = new RetentionService({\n getStorageLocation: () => context.storage,\n getRetentionConfig: () => context.addonConfig.retention as Record<string, unknown> | undefined,\n eventBus: context.eventBus,\n logger: context.logger.child('Retention'),\n })\n\n const trackTrail = new TrackTrailService({\n getStorageLocation: () => context.storage,\n logger: context.logger.child('TrackTrail'),\n })\n\n eventPersistence.start()\n retention.start()\n\n this.persistenceFacade = {\n eventPersistence,\n knownFaces,\n sessionTracker,\n retention,\n trackTrail,\n }\n\n context.logger.info('Analysis persistence initialized')\n }\n\n async shutdown(): Promise<void> {\n // Stream broker\n await this.brokerManager?.destroyAll()\n this.brokerManager = null\n\n // Recording engine\n if (this.coordinator) {\n this.coordinator.stop()\n this.coordinator = null\n }\n if (this.sqliteDb) {\n this.sqliteDb.close()\n this.sqliteDb = null\n }\n this.recordingDb = null\n\n // Analysis pipeline\n this.analysisPipeline = null\n\n // Analysis persistence\n if (this.persistenceFacade) {\n this.persistenceFacade.eventPersistence.stop()\n this.persistenceFacade.retention.stop()\n this.persistenceFacade = null\n }\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n switch (name) {\n case 'stream-broker':\n return this.brokerManager as unknown as CapabilityProviderMap[K]\n case 'recording-engine':\n return this.coordinator as unknown as CapabilityProviderMap[K]\n case 'analysis-pipeline':\n return this.analysisPipeline as unknown as CapabilityProviderMap[K]\n case 'analysis-data-persistence':\n return this.persistenceFacade as unknown as CapabilityProviderMap[K]\n case 'webrtc':\n return null // WebRTC is provided externally or via collection\n default:\n return null\n }\n }\n\n // --- Recording engine accessors ---\n\n getCoordinator(): RecordingCoordinator {\n if (!this.coordinator) throw new Error('PipelineAddon recording not initialized')\n return this.coordinator\n }\n\n getRecordingDb(): RecordingDb {\n if (!this.recordingDb) throw new Error('PipelineAddon recording not initialized')\n return this.recordingDb\n }\n\n /** Whether the analysis pipeline package loaded successfully */\n isAnalysisAvailable(): boolean {\n return this.analysisPipeline !== null\n }\n\n // --- IConfigurable ---\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'ffmpeg',\n title: 'FFmpeg Settings',\n columns: 2,\n fields: [\n {\n type: 'text',\n key: 'ffmpegPath',\n label: 'FFmpeg Binary Path',\n description: 'Path to the ffmpeg executable, or just \"ffmpeg\" if it is in your PATH',\n placeholder: 'ffmpeg',\n },\n {\n type: 'select',\n key: 'hwaccel',\n label: 'Hardware Acceleration',\n description: 'Enable GPU-accelerated video encoding if supported by your hardware',\n options: [\n { value: '', label: 'None (software)', description: 'CPU-only encoding' },\n { value: 'nvenc', label: 'NVIDIA NVENC', description: 'NVIDIA GPU encoding' },\n { value: 'vaapi', label: 'Intel VAAPI', description: 'Intel GPU encoding (Linux)' },\n { value: 'videotoolbox', label: 'Apple VideoToolbox', description: 'macOS hardware encoding' },\n ],\n },\n {\n type: 'number',\n key: 'threads',\n label: 'FFmpeg Threads',\n description: 'Number of CPU threads for software encoding (0 = auto)',\n min: 0,\n max: 64,\n step: 1,\n unit: 'threads',\n },\n ],\n },\n ],\n }\n }\n\n getConfig(): Record<string, unknown> {\n return { ...this.currentRecordingConfig }\n }\n\n async onConfigChange(config: Record<string, unknown>): Promise<void> {\n this.currentRecordingConfig = {\n ffmpegPath: (config.ffmpegPath as string) ?? this.currentRecordingConfig.ffmpegPath,\n hwaccel: (config.hwaccel as FfmpegConfig['hwaccel']) ?? this.currentRecordingConfig.hwaccel,\n threads: (config.threads as number) ?? this.currentRecordingConfig.threads,\n }\n }\n}\n\nexport default PipelineAddon\n","// --- Unified PipelineAddon ---\nexport { PipelineAddon } from './addon'\nexport type { RecordingEngineDependencies, RecordingEngineV2Dependencies } from './addon'\n\n// --- Stream broker ---\nexport { StreamBrokerManager } from './stream-broker/stream-broker-manager'\nexport { StreamBroker } from './stream-broker/stream-broker'\nexport { DecoderRegistry } from './stream-broker/decoder-registry'\nexport { FfmpegDecoderProvider } from './stream-broker/ffmpeg-decoder-provider'\nexport { FfmpegDecoderSession } from './stream-broker/ffmpeg-decoder-session'\nexport { FrameDropper } from './stream-broker/frame-dropper'\nexport { StreamPipeServer } from './stream-broker/stream-pipe-server'\nexport type { BrokerStats, BrokerStatus } from '@camstack/types'\n\n// --- Recording engine ---\nexport { RecordingCoordinator } from './recording/recording-coordinator'\nexport { RecordingDb } from './recording/recording-db'\nexport type { StorageUsage, AvailabilityRange } from './recording/recording-db'\nexport type * from './recording/types'\n\n// --- Analysis persistence ---\nexport type { IAnalysisDataPersistence } from './persistence/types'\nexport { EventPersistenceService } from './persistence/event-persistence'\nexport type {\n EventPersistenceDeps,\n PersistableEvent,\n EventBufferStatus,\n TrackMediaType,\n TrackMediaFile,\n ObjectSnapshotResult,\n AnnotatedSnapshotResult,\n} from './persistence/event-persistence'\nexport { KnownFacesService, cosineSimilarity } from './persistence/known-faces'\nexport type { KnownFacesDeps, KnownFaceEntry, ClipRecognizer } from './persistence/known-faces'\nexport { SessionTrackerService } from './persistence/session-tracker'\nexport type { SessionTrackerDeps, SessionTrack, GlobalIdentity } from './persistence/session-tracker'\nexport { RetentionService, DEFAULT_RETENTION } from './persistence/retention'\nexport type { RetentionDeps, RetentionConfig, RetentionReport } from './persistence/retention'\nexport { TrackTrailService } from './persistence/track-trail'\nexport type {\n TrackTrailDeps,\n TrackCaptureConfig,\n TrackPosition,\n TrackSnapshot,\n TrackTrail,\n} from './persistence/track-trail'\n\n// --- Analysis pipeline ---\nexport { BuiltinAnalysisAddon } from './analysis/analysis-addon'\n\n// --- WebRTC adaptive ---\nexport type {\n VideoCodec,\n AudioCodec,\n VideoFrame as WebRtcVideoFrame,\n AudioFrame,\n MediaFrame,\n FrameSource,\n FrameSourceFactory,\n Logger as WebRtcLogger,\n LoggerLike,\n} from './webrtc/types'\nexport { asLogger, createNullLogger } from './webrtc/types'\nexport {\n hasStartCodes,\n splitAnnexBToNals,\n prependStartCode,\n joinNalsToAnnexB,\n detectVideoCodecFromNal,\n} from './webrtc/nal-utils'\nexport {\n convertH264ToAnnexB,\n isH264KeyframeAnnexB,\n isH264IdrAccessUnit,\n extractH264ParamSets,\n H264RtpDepacketizer,\n} from './webrtc/h264-utils'\nexport {\n convertH265ToAnnexB,\n getH265NalType,\n isH265Irap,\n isH265KeyframeAnnexB,\n isH265IrapAccessUnit,\n extractH265ParamSets,\n H265RtpDepacketizer,\n} from './webrtc/h265-utils'\nexport type { StreamFanoutOptions } from './webrtc/fanout'\nexport { AsyncBoundedQueue, StreamFanout } from './webrtc/fanout'\nexport type { FfmpegProcessOptions } from './webrtc/ffmpeg-process'\nexport { FfmpegProcess } from './webrtc/ffmpeg-process'\nexport { fromEventEmitter, fromPushCallback, fromNativeStream } from './webrtc/frame-source'\nexport type {\n AudioMode,\n EncodingParams,\n AdaptiveFfmpegSourceOptions,\n} from './webrtc/ffmpeg-source'\nexport { AdaptiveFfmpegSource } from './webrtc/ffmpeg-source'\nexport type {\n QualityTier,\n QualityProfile,\n StreamStats,\n AdaptiveControllerOptions,\n} from './webrtc/adaptive-controller'\nexport { AdaptiveController } from './webrtc/adaptive-controller'\nexport type {\n AdaptiveSessionOptions,\n SessionStats,\n SessionInfo,\n} from './webrtc/session'\nexport { AdaptiveSession } from './webrtc/session'\nexport type { AdaptiveRtspRelayOptions } from './webrtc/rtsp-relay'\nexport { AdaptiveRtspRelay } from './webrtc/rtsp-relay'\nexport type { SharedSessionOptions } from './webrtc/shared-session'\nexport { SharedSession } from './webrtc/shared-session'\nexport type {\n AdaptiveStreamServerOptions,\n CameraConfig,\n} from './webrtc/server'\nexport {\n AdaptiveStreamServer,\n createDefaultProfiles,\n} from './webrtc/server'\n","// packages/addon-pipeline-analysis/src/addon.ts\nimport type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n CapabilityProviderMap,\n IAnalysisAddon,\n IScopedLogger,\n} from '@camstack/types'\n\n/**\n * Addon wrapper around @camstack/lib-pipeline-analysis.\n * Dynamically imports the analysis package (tolerates it being absent).\n * Declares the 'analysis-pipeline' singleton capability.\n */\nexport class BuiltinAnalysisAddon implements ICamstackAddon {\n readonly manifest: AddonManifest = {\n id: 'pipeline-analysis',\n name: 'Analysis Pipeline',\n version: '1.0.0',\n capabilities: ['analysis-pipeline'] as unknown as AddonManifest['capabilities'],\n }\n\n private pipeline: IAnalysisAddon | null = null\n private logger: IScopedLogger | null = null\n\n async initialize(context: AddonContext): Promise<void> {\n this.logger = context.logger ?? null\n\n try {\n const mod = await import('@camstack/lib-pipeline-analysis')\n const instance = new mod.AnalysisPipeline()\n // AnalysisPipeline implements IAnalysisAddon\n this.pipeline = instance as unknown as IAnalysisAddon\n this.logger?.info('pipeline-analysis loaded successfully')\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.logger?.warn(`pipeline-analysis not available: ${msg} -- analysis features disabled`)\n }\n }\n\n async shutdown(): Promise<void> {\n this.pipeline = null\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'analysis-pipeline') {\n return this.pipeline as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n /** Whether the pipeline-analysis package loaded successfully */\n isAvailable(): boolean {\n return this.pipeline !== null\n }\n}\n\nexport default BuiltinAnalysisAddon\n","/**\n * Core types for media stream servers.\n * Protocol-agnostic frame types used across all server implementations.\n */\n\nexport type VideoCodec = \"H264\" | \"H265\";\nexport type AudioCodec = \"Aac\" | \"Adpcm\" | \"Opus\" | \"Pcmu\" | \"Pcma\";\n\n/**\n * A single video frame in Annex-B format (with start codes).\n */\nexport interface VideoFrame {\n /** Raw video data in Annex-B format (NAL units with 0x00000001 start codes) */\n data: Buffer;\n /** Video codec type */\n codec: VideoCodec;\n /** True if this is a keyframe (IDR for H.264, IRAP for H.265) */\n isKeyframe: boolean;\n /** Presentation timestamp in microseconds (monotonic) */\n timestampMicros: number;\n /** Optional: frame width in pixels */\n width?: number;\n /** Optional: frame height in pixels */\n height?: number;\n}\n\n/**\n * A single audio frame.\n * For AAC: data should include ADTS headers.\n * For other codecs: raw audio samples.\n */\nexport interface AudioFrame {\n /** Raw audio data */\n data: Buffer;\n /** Audio codec */\n codec: AudioCodec;\n /** Sample rate in Hz */\n sampleRate: number;\n /** Number of channels */\n channels: number;\n /** Presentation timestamp in microseconds */\n timestampMicros: number;\n}\n\n/**\n * Tagged union for video/audio frames.\n */\nexport type MediaFrame =\n | { type: \"video\"; frame: VideoFrame }\n | { type: \"audio\"; frame: AudioFrame };\n\n/**\n * Primary frame source interface.\n * Yields MediaFrame objects. Returning from the generator signals end of stream.\n * Throwing signals an error that should trigger reconnection.\n */\nexport type FrameSource = AsyncGenerator<MediaFrame, void, unknown>;\n\n/**\n * Factory that creates a new FrameSource.\n * Called each time the server needs to start/restart the stream.\n * The server is responsible for calling return() to stop the source.\n */\nexport type FrameSourceFactory = () => FrameSource;\n\n/**\n * Minimal logger interface compatible with console.\n */\nexport interface Logger {\n log(message?: unknown, ...args: unknown[]): void;\n info(message?: unknown, ...args: unknown[]): void;\n warn(message?: unknown, ...args: unknown[]): void;\n error(message?: unknown, ...args: unknown[]): void;\n debug(message?: unknown, ...args: unknown[]): void;\n}\n\n/**\n * Accept Console or partial Logger implementations.\n */\nexport type LoggerLike = Partial<Logger> | Console;\n\n/**\n * Wrap a LoggerLike into a full Logger (no-op for missing methods).\n */\nexport function asLogger(logger?: LoggerLike): Logger {\n if (!logger) return createNullLogger();\n const noop = () => {};\n return {\n log: (logger as Logger).log?.bind(logger) ?? noop,\n info: (logger as Logger).info?.bind(logger) ?? noop,\n warn: (logger as Logger).warn?.bind(logger) ?? noop,\n error: (logger as Logger).error?.bind(logger) ?? noop,\n debug: (logger as Logger).debug?.bind(logger) ?? noop,\n };\n}\n\n/**\n * Create a logger that discards all output.\n */\nexport function createNullLogger(): Logger {\n const noop = () => {};\n return { log: noop, info: noop, warn: noop, error: noop, debug: noop };\n}\n","/**\n * NAL unit parsing utilities.\n * Protocol-agnostic functions for working with Annex-B NAL units.\n */\n\nimport type { VideoCodec } from \"./types.js\";\n\nconst NAL_START_CODE_4B = Buffer.from([0x00, 0x00, 0x00, 0x01]);\nconst NAL_START_CODE_3B = Buffer.from([0x00, 0x00, 0x01]);\n\n/**\n * Returns true if the buffer starts with an Annex-B start code (0x00000001 or 0x000001).\n */\nexport function hasStartCodes(data: Buffer): boolean {\n if (data.length < 4) return false;\n if (data.subarray(0, 4).equals(NAL_START_CODE_4B)) return true;\n if (data.subarray(0, 3).equals(NAL_START_CODE_3B)) return true;\n return false;\n}\n\n/**\n * Split Annex-B data into individual NAL unit payloads (without start codes).\n * Works for both H.264 and H.265.\n */\nexport function splitAnnexBToNals(annexB: Buffer): Buffer[] {\n const nals: Buffer[] = [];\n const len = annexB.length;\n\n const isStartCodeAt = (i: number): number => {\n if (i + 3 <= len && annexB[i] === 0x00 && annexB[i + 1] === 0x00) {\n if (annexB[i + 2] === 0x01) return 3;\n if (i + 4 <= len && annexB[i + 2] === 0x00 && annexB[i + 3] === 0x01)\n return 4;\n }\n return 0;\n };\n\n let i = 0;\n // Find first start code\n while (i < len) {\n const sc = isStartCodeAt(i);\n if (sc) break;\n i++;\n }\n\n while (i < len) {\n const sc = isStartCodeAt(i);\n if (!sc) {\n i++;\n continue;\n }\n const nalStart = i + sc;\n let j = nalStart;\n while (j < len) {\n const sc2 = isStartCodeAt(j);\n if (sc2) break;\n j++;\n }\n if (nalStart < j) {\n const nal = annexB.subarray(nalStart, j);\n if (nal.length > 0) nals.push(nal);\n }\n i = j;\n }\n\n return nals;\n}\n\n/**\n * Prepend a 4-byte Annex-B start code to a NAL payload.\n */\nexport function prependStartCode(nal: Buffer): Buffer {\n return Buffer.concat([NAL_START_CODE_4B, nal]);\n}\n\n/**\n * Join multiple NAL payloads (without start codes) into an Annex-B access unit.\n */\nexport function joinNalsToAnnexB(\n ...nals: Array<Buffer | null | undefined>\n): Buffer | undefined {\n const present = nals.filter((n): n is Buffer => !!n && n.length > 0);\n if (!present.length) return;\n const parts: Buffer[] = [];\n for (const nal of present) {\n parts.push(NAL_START_CODE_4B, nal);\n }\n return Buffer.concat(parts);\n}\n\n/**\n * Detect the actual video codec from raw NAL data.\n * Some cameras report wrong codec (e.g. \"H264\" but send H.265 data).\n * Analyzes the NAL header to determine the real codec.\n *\n * @param data - Raw video data (either Annex-B or length-prefixed)\n * @returns Detected codec type or null if detection fails\n */\nexport function detectVideoCodecFromNal(data: Buffer): VideoCodec | null {\n if (!data || data.length < 5) return null;\n\n // Find start code (00 00 00 01 or 00 00 01)\n let nalStart = -1;\n for (let i = 0; i < Math.min(data.length - 4, 100); i++) {\n if (data[i] === 0 && data[i + 1] === 0) {\n if (data[i + 2] === 0 && data[i + 3] === 1) {\n nalStart = i + 4;\n break;\n }\n if (data[i + 2] === 1) {\n nalStart = i + 3;\n break;\n }\n }\n }\n\n // If no start code, try length-prefixed (AVCC/HVCC)\n if (nalStart < 0 && data.length >= 5) {\n const len = data.readUInt32BE(0);\n if (len > 0 && len <= data.length - 4) {\n nalStart = 4;\n }\n }\n\n if (nalStart < 0 || nalStart >= data.length) return null;\n\n const nalByte = data[nalStart];\n if (nalByte === undefined) return null;\n\n // Check H.264 FIRST because H.264 NAL bytes can be misinterpreted as H.265.\n const forbiddenBit264 = (nalByte >> 7) & 1;\n const h264Type = nalByte & 0x1f;\n\n if (forbiddenBit264 === 0 && h264Type > 0 && h264Type <= 12) {\n if (h264Type === 7 || h264Type === 8) return \"H264\"; // SPS, PPS\n if (h264Type === 5) return \"H264\"; // IDR\n if (h264Type === 1) {\n const nalRefIdc = (nalByte >> 5) & 0x03;\n if (nalRefIdc >= 1) return \"H264\";\n }\n }\n\n // H.265/HEVC detection\n if (nalStart + 1 < data.length) {\n const nalByte2 = data[nalStart + 1];\n if (nalByte2 !== undefined) {\n const forbiddenBit = (nalByte >> 7) & 1;\n const hevcType = (nalByte >> 1) & 0x3f;\n const temporalId = nalByte2 & 0x07;\n\n if (forbiddenBit === 0 && temporalId > 0 && hevcType <= 40) {\n if (hevcType === 32 || hevcType === 33 || hevcType === 34)\n return \"H265\"; // VPS, SPS, PPS\n if (hevcType === 19 || hevcType === 20 || hevcType === 21)\n return \"H265\"; // IDR, CRA\n if (hevcType <= 1 && nalByte <= 0x03) return \"H265\"; // TRAIL\n }\n }\n }\n\n return null;\n}\n\nexport { NAL_START_CODE_4B, NAL_START_CODE_3B };\n","/**\n * H.264/AVC utilities.\n * AVCC to Annex-B conversion, SPS/PPS extraction, keyframe detection, RTP depacketization.\n */\n\nimport { NAL_START_CODE_4B, hasStartCodes, splitAnnexBToNals } from \"./nal-utils.js\";\n\n// --- AVCC to Annex-B conversion helpers ---\n\nfunction tryConvertWithLengthReader(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 4 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 4;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader16(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 2 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 2;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader24(\n data: Buffer,\n endian: \"be\" | \"le\",\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n const readLen24 = (buf: Buffer, at: number): number => {\n if (at + 3 > buf.length) return 0;\n const b0 = buf[at]!;\n const b1 = buf[at + 1]!;\n const b2 = buf[at + 2]!;\n return endian === \"be\"\n ? ((b0 << 16) | (b1 << 8) | b2) >>> 0\n : ((b2 << 16) | (b1 << 8) | b0) >>> 0;\n };\n while (offset < data.length) {\n if (offset + 3 > data.length) return null;\n const nalLength = readLen24(data, offset);\n offset += 3;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction looksLikeSingleH264Nal(nalPayload: Buffer): boolean {\n if (nalPayload.length < 1) return false;\n const b0 = nalPayload[0];\n if (b0 === undefined) return false;\n if ((b0 & 0x80) !== 0) return false;\n const nalType = b0 & 0x1f;\n return nalType >= 1 && nalType <= 23;\n}\n\nfunction depacketizeRtpAggregationToAnnexB(payload: Buffer): Buffer | null {\n if (payload.length < 1) return null;\n const nalHeader = payload[0]!;\n const nalType = nalHeader & 0x1f;\n const out: Buffer[] = [];\n const pushNal = (nal: Buffer) => {\n if (nal.length === 0) return;\n out.push(NAL_START_CODE_4B, nal);\n };\n\n // STAP-A (24)\n if (nalType === 24) {\n let off = 1;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n // STAP-B (25)\n if (nalType === 25) {\n let off = 1 + 2;\n if (off > payload.length) return null;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n // MTAP16 (26)\n if (nalType === 26) {\n let off = 1 + 2;\n if (off > payload.length) return null;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (off + 1 + 2 > payload.length) return null;\n off += 1 + 2;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n // MTAP24 (27)\n if (nalType === 27) {\n let off = 1 + 2;\n if (off > payload.length) return null;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (off + 1 + 3 > payload.length) return null;\n off += 1 + 3;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n return null;\n}\n\n// --- Public API ---\n\n/**\n * Convert H.264 data from length-prefixed (AVCC) to Annex-B (start codes).\n * If already Annex-B, returns as-is.\n */\nexport function convertH264ToAnnexB(data: Buffer): Buffer {\n if (hasStartCodes(data)) return data;\n\n // Some models prepend a small header before an Annex-B access unit.\n const sc4 = Buffer.from([0x00, 0x00, 0x00, 0x01]);\n const sc3 = Buffer.from([0x00, 0x00, 0x01]);\n const maxScan = Math.min(64, data.length);\n const idx4 = data.subarray(0, maxScan).indexOf(sc4);\n if (idx4 > 0) return data.subarray(idx4);\n const idx3 = data.subarray(0, maxScan).indexOf(sc3);\n if (idx3 > 0) return data.subarray(idx3);\n\n // Try AVCC → AnnexB conversion (BE then LE)\n const be = tryConvertWithLengthReader(data, (b, o) => b.readUInt32BE(o));\n if (be) return be;\n const le = tryConvertWithLengthReader(data, (b, o) => b.readUInt32LE(o));\n if (le) return le;\n\n // 3-byte lengths\n const be24 = tryConvertWithLengthReader24(data, \"be\");\n if (be24) return be24;\n const le24 = tryConvertWithLengthReader24(data, \"le\");\n if (le24) return le24;\n\n // 2-byte lengths\n const be16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16BE(o));\n if (be16) return be16;\n const le16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16LE(o));\n if (le16) return le16;\n\n // RTP aggregation payload\n const agg = depacketizeRtpAggregationToAnnexB(data);\n if (agg) return agg;\n\n // Single NAL without start code\n if (looksLikeSingleH264Nal(data)) {\n return Buffer.concat([NAL_START_CODE_4B, data]);\n }\n\n return data;\n}\n\n/**\n * Check if an H.264 Annex-B access unit is a keyframe (IDR + SPS + PPS).\n */\nexport function isH264KeyframeAnnexB(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n let hasSps = false;\n let hasPps = false;\n let hasIdr = false;\n for (const nal of nals) {\n const t = (nal[0] ?? 0) & 0x1f;\n if (t === 7) hasSps = true;\n if (t === 8) hasPps = true;\n if (t === 5) hasIdr = true;\n }\n return hasIdr && hasSps && hasPps;\n}\n\n/**\n * Check if an H.264 Annex-B access unit contains an IDR slice.\n */\nexport function isH264IdrAccessUnit(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n for (const nal of nals) {\n if (nal.length < 1) continue;\n const t = (nal[0] ?? 0) & 0x1f;\n if (t === 5) return true;\n }\n return false;\n}\n\n/**\n * Extract SPS and PPS from an H.264 Annex-B access unit.\n */\nexport function extractH264ParamSets(annexB: Buffer): {\n sps?: Buffer;\n pps?: Buffer;\n profileLevelId?: string;\n} {\n const nals = splitAnnexBToNals(annexB);\n let sps: Buffer | undefined;\n let pps: Buffer | undefined;\n let profileLevelId: string | undefined;\n\n for (const nal of nals) {\n if (nal.length < 1) continue;\n const nalType = nal[0]! & 0x1f;\n if (nalType === 7) {\n sps = nal;\n if (nal.length >= 4) {\n profileLevelId = Buffer.from([nal[1]!, nal[2]!, nal[3]!]).toString(\n \"hex\",\n );\n }\n } else if (nalType === 8) {\n pps = nal;\n }\n }\n\n const out: { sps?: Buffer; pps?: Buffer; profileLevelId?: string } = {};\n if (sps) out.sps = sps;\n if (pps) out.pps = pps;\n if (profileLevelId) out.profileLevelId = profileLevelId;\n return out;\n}\n\n/**\n * H.264 RTP depacketizer (RFC 6184).\n * Handles single NAL units, STAP-A aggregation, and FU-A/FU-B fragmentation.\n * Returns complete NAL units in Annex-B format (with start codes).\n */\nexport class H264RtpDepacketizer {\n private fuNalHeader: number | null = null;\n private fuParts: Buffer[] = [];\n\n private static parseRtpPayload(packet: Buffer): Buffer | null {\n if (!packet || packet.length < 12) return null;\n const version = (packet[0]! >> 6) & 0x03;\n if (version !== 2) return null;\n const padding = (packet[0]! & 0x20) !== 0;\n const extension = (packet[0]! & 0x10) !== 0;\n const csrcCount = packet[0]! & 0x0f;\n let offset = 12 + csrcCount * 4;\n if (offset > packet.length) return null;\n if (extension) {\n if (offset + 4 > packet.length) return null;\n const extLenWords = packet.readUInt16BE(offset + 2);\n offset += 4 + extLenWords * 4;\n if (offset > packet.length) return null;\n }\n let end = packet.length;\n if (padding) {\n const padLen = packet[packet.length - 1]!;\n if (padLen <= 0 || padLen > packet.length) return null;\n end = packet.length - padLen;\n if (end < offset) return null;\n }\n if (end <= offset) return null;\n return packet.subarray(offset, end);\n }\n\n reset(): void {\n this.fuNalHeader = null;\n this.fuParts = [];\n }\n\n push(payload: Buffer): Buffer[] {\n if (payload.length === 0) return [];\n\n const rtpPayload = H264RtpDepacketizer.parseRtpPayload(payload);\n if (rtpPayload) payload = rtpPayload;\n\n if (hasStartCodes(payload)) return [payload];\n\n const b0 = payload[0]!;\n if ((b0 & 0x80) !== 0) return [];\n const nalType = b0 & 0x1f;\n\n // Single NAL unit\n if (nalType >= 1 && nalType <= 23) {\n return [Buffer.concat([NAL_START_CODE_4B, payload])];\n }\n\n // STAP-A (24)\n if (nalType === 24) {\n if (payload.length < 1 + 2) return [];\n let off = 1;\n const out: Buffer[] = [];\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return [];\n const nal = payload.subarray(off, off + size);\n off += size;\n if (nal.length < 1) return [];\n if ((nal[0]! & 0x80) !== 0) return [];\n const t = nal[0]! & 0x1f;\n if (t === 0 || t >= 24) return [];\n out.push(Buffer.concat([NAL_START_CODE_4B, nal]));\n }\n return out;\n }\n\n // FU-A (28) / FU-B (29)\n if (nalType === 28 || nalType === 29) {\n if (payload.length < 2) return [];\n const fuIndicator = payload[0]!;\n const fuHeader = payload[1]!;\n const start = (fuHeader & 0x80) !== 0;\n const end = (fuHeader & 0x40) !== 0;\n const origType = fuHeader & 0x1f;\n const reconstructedHeader = (fuIndicator & 0xe0) | origType;\n\n let off = 2;\n if (nalType === 29) {\n if (payload.length < off + 2) return [];\n off += 2;\n }\n const frag = payload.subarray(off);\n\n if (start) {\n this.fuNalHeader = reconstructedHeader;\n this.fuParts = [frag];\n } else if (this.fuNalHeader != null) {\n this.fuParts.push(frag);\n } else {\n return [];\n }\n\n if (end && this.fuNalHeader != null) {\n const nal = Buffer.concat([\n Buffer.from([this.fuNalHeader]),\n ...this.fuParts,\n ]);\n this.reset();\n return [Buffer.concat([NAL_START_CODE_4B, nal])];\n }\n return [];\n }\n\n return [];\n }\n}\n","/**\n * H.265/HEVC utilities.\n * HVCC to Annex-B conversion, VPS/SPS/PPS extraction, keyframe detection, RTP depacketization.\n */\n\nimport { NAL_START_CODE_4B, hasStartCodes, splitAnnexBToNals } from \"./nal-utils.js\";\n\n// --- HVCC to Annex-B conversion helpers ---\n\nfunction tryConvertWithLengthReader(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 4 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 4;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader16(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 2 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 2;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader24(\n data: Buffer,\n endian: \"be\" | \"le\",\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n const readLen24 = (buf: Buffer, at: number): number => {\n if (at + 3 > buf.length) return 0;\n const b0 = buf[at]!;\n const b1 = buf[at + 1]!;\n const b2 = buf[at + 2]!;\n return endian === \"be\"\n ? ((b0 << 16) | (b1 << 8) | b2) >>> 0\n : ((b2 << 16) | (b1 << 8) | b0) >>> 0;\n };\n while (offset < data.length) {\n if (offset + 3 > data.length) return null;\n const nalLength = readLen24(data, offset);\n offset += 3;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction looksLikeSingleH265Nal(nalPayload: Buffer): boolean {\n if (nalPayload.length < 2) return false;\n const b0 = nalPayload[0];\n if (b0 === undefined) return false;\n if ((b0 & 0x80) !== 0) return false;\n const nalType = (b0 >> 1) & 0x3f;\n return nalType <= 40;\n}\n\n// --- Public API ---\n\n/**\n * Convert H.265 data from length-prefixed (HVCC) to Annex-B (start codes).\n * If already Annex-B, returns as-is.\n */\nexport function convertH265ToAnnexB(data: Buffer): Buffer {\n if (hasStartCodes(data)) return data;\n\n const sc4 = Buffer.from([0x00, 0x00, 0x00, 0x01]);\n const sc3 = Buffer.from([0x00, 0x00, 0x01]);\n const maxScan = Math.min(64, data.length);\n const idx4 = data.subarray(0, maxScan).indexOf(sc4);\n if (idx4 > 0) return data.subarray(idx4);\n const idx3 = data.subarray(0, maxScan).indexOf(sc3);\n if (idx3 > 0) return data.subarray(idx3);\n\n // Try HVCC → AnnexB conversion\n const be = tryConvertWithLengthReader(data, (b, o) => b.readUInt32BE(o));\n if (be) return be;\n const le = tryConvertWithLengthReader(data, (b, o) => b.readUInt32LE(o));\n if (le) return le;\n\n const be24 = tryConvertWithLengthReader24(data, \"be\");\n if (be24) return be24;\n const le24 = tryConvertWithLengthReader24(data, \"le\");\n if (le24) return le24;\n\n const be16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16BE(o));\n if (be16) return be16;\n const le16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16LE(o));\n if (le16) return le16;\n\n if (looksLikeSingleH265Nal(data)) {\n return Buffer.concat([NAL_START_CODE_4B, data]);\n }\n return data;\n}\n\n/**\n * Get H.265 NAL unit type from a NAL payload (without start code).\n */\nexport function getH265NalType(nalPayload: Buffer): number | null {\n if (nalPayload.length < 1) return null;\n const b0 = nalPayload[0];\n if (b0 === undefined) return null;\n if ((b0 & 0x80) !== 0) return null;\n return (b0 >> 1) & 0x3f;\n}\n\n/**\n * Check if an H.265 NAL unit type is an IRAP (Intra Random Access Point) picture.\n * IRAP types: BLA (16-18), IDR (19-20), CRA (21).\n */\nexport function isH265Irap(nalType: number): boolean {\n return nalType >= 16 && nalType <= 23;\n}\n\n/**\n * Check if an H.265 Annex-B access unit is a keyframe (IRAP + VPS + SPS + PPS).\n */\nexport function isH265KeyframeAnnexB(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n let hasVps = false;\n let hasSps = false;\n let hasPps = false;\n let hasIrap = false;\n\n for (const nal of nals) {\n const nalType = getH265NalType(nal);\n if (nalType === null) continue;\n if (nalType === 32) hasVps = true;\n if (nalType === 33) hasSps = true;\n if (nalType === 34) hasPps = true;\n if (isH265Irap(nalType)) hasIrap = true;\n }\n\n return hasIrap && hasVps && hasSps && hasPps;\n}\n\n/**\n * Check if an H.265 Annex-B access unit contains an IRAP picture.\n */\nexport function isH265IrapAccessUnit(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n for (const nal of nals) {\n if (nal.length < 2) continue;\n const b0 = nal[0];\n if (b0 === undefined) continue;\n if ((b0 & 0x80) !== 0) continue;\n const nalType = (b0 >> 1) & 0x3f;\n if (isH265Irap(nalType)) return true;\n }\n return false;\n}\n\n/**\n * Extract VPS, SPS, and PPS from an H.265 Annex-B access unit.\n */\nexport function extractH265ParamSets(annexB: Buffer): {\n vps?: Buffer;\n sps?: Buffer;\n pps?: Buffer;\n} {\n const nals = splitAnnexBToNals(annexB);\n let vps: Buffer | undefined;\n let sps: Buffer | undefined;\n let pps: Buffer | undefined;\n\n for (const nal of nals) {\n if (nal.length < 2) continue;\n const nalType = (nal[0]! >> 1) & 0x3f;\n if (nalType === 32) vps = nal;\n else if (nalType === 33) sps = nal;\n else if (nalType === 34) pps = nal;\n }\n\n const out: { vps?: Buffer; sps?: Buffer; pps?: Buffer } = {};\n if (vps) out.vps = vps;\n if (sps) out.sps = sps;\n if (pps) out.pps = pps;\n return out;\n}\n\n/**\n * H.265 RTP depacketizer (RFC 7798).\n * Handles single NAL units, AP aggregation, and FU fragmentation.\n * Returns complete NAL units in Annex-B format (with start codes).\n */\nexport class H265RtpDepacketizer {\n private fuParts: Buffer[] | null = null;\n\n private static parseRtpPayload(packet: Buffer): Buffer | null {\n if (!packet || packet.length < 12) return null;\n const version = (packet[0]! >> 6) & 0x03;\n if (version !== 2) return null;\n const padding = (packet[0]! & 0x20) !== 0;\n const extension = (packet[0]! & 0x10) !== 0;\n const csrcCount = packet[0]! & 0x0f;\n let offset = 12 + csrcCount * 4;\n if (offset > packet.length) return null;\n if (extension) {\n if (offset + 4 > packet.length) return null;\n const extLenWords = packet.readUInt16BE(offset + 2);\n offset += 4 + extLenWords * 4;\n if (offset > packet.length) return null;\n }\n let end = packet.length;\n if (padding) {\n const padLen = packet[packet.length - 1]!;\n if (padLen <= 0 || padLen > packet.length) return null;\n end = packet.length - padLen;\n if (end < offset) return null;\n }\n if (end <= offset) return null;\n return packet.subarray(offset, end);\n }\n\n reset(): void {\n this.fuParts = null;\n }\n\n push(payload: Buffer): Buffer[] {\n if (!payload || payload.length < 2) return [];\n\n const rtpPayload = H265RtpDepacketizer.parseRtpPayload(payload);\n if (rtpPayload) payload = rtpPayload;\n\n const h0 = payload[0]!;\n const h1 = payload[1]!;\n if ((h0 & 0x80) !== 0) return [];\n const nalType = (h0 >> 1) & 0x3f;\n\n // AP (48): aggregation packet\n if (nalType === 48) {\n let off = 2;\n const out: Buffer[] = [];\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return [];\n const nal = payload.subarray(off, off + size);\n off += size;\n if (nal.length) out.push(NAL_START_CODE_4B, nal);\n }\n return out.length ? [Buffer.concat(out)] : [];\n }\n\n // FU (49): fragmentation unit\n if (nalType === 49) {\n if (payload.length < 3) return [];\n const fuHeader = payload[2]!;\n const start = (fuHeader & 0x80) !== 0;\n const end = (fuHeader & 0x40) !== 0;\n const origType = fuHeader & 0x3f;\n const orig0 = (h0 & 0x81) | ((origType & 0x3f) << 1);\n const orig1 = h1;\n const frag = payload.subarray(3);\n\n if (start) {\n this.fuParts = [NAL_START_CODE_4B, Buffer.from([orig0, orig1]), frag];\n } else {\n if (!this.fuParts) return [];\n this.fuParts.push(frag);\n }\n\n if (end) {\n if (!this.fuParts) return [];\n const out = Buffer.concat(this.fuParts);\n this.fuParts = null;\n return [out];\n }\n\n return [];\n }\n\n // Single NAL unit\n return [Buffer.concat([NAL_START_CODE_4B, payload])];\n }\n}\n","/**\n * Stream fan-out utilities.\n * Distribute a single source stream to multiple consumers with bounded queues.\n */\n\n/**\n * Async bounded queue with push/pull interface.\n * When the queue is full, the oldest items are dropped (tail-drop).\n */\nexport class AsyncBoundedQueue<T> {\n private readonly maxItems: number;\n private readonly queue: T[] = [];\n private waiting:\n | { resolve: (r: IteratorResult<T>) => void }\n | undefined;\n private closed = false;\n\n constructor(maxItems: number) {\n this.maxItems = Math.max(1, maxItems | 0);\n }\n\n push(item: T): void {\n if (this.closed) return;\n if (this.waiting) {\n const { resolve } = this.waiting;\n this.waiting = undefined;\n resolve({ value: item, done: false });\n return;\n }\n this.queue.push(item);\n if (this.queue.length > this.maxItems) {\n this.queue.splice(0, this.queue.length - this.maxItems);\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n if (this.waiting) {\n const { resolve } = this.waiting;\n this.waiting = undefined;\n resolve({ value: undefined as never, done: true });\n }\n }\n\n async next(): Promise<IteratorResult<T>> {\n // Drain buffered items even after close\n const item = this.queue.shift();\n if (item !== undefined) return { value: item, done: false };\n if (this.closed) return { value: undefined as never, done: true };\n return await new Promise<IteratorResult<T>>((resolve) => {\n this.waiting = { resolve };\n });\n }\n\n isClosed(): boolean {\n return this.closed;\n }\n\n size(): number {\n return this.queue.length;\n }\n}\n\nexport interface StreamFanoutOptions<T> {\n /** Maximum items per subscriber queue before tail-drop */\n maxQueueItems: number;\n /** Factory to create the source stream */\n createSource: () => AsyncGenerator<T, void, unknown>;\n /** Optional callback for each frame (e.g. for extracting metadata) */\n onFrame?: (frame: T) => void;\n /** Optional error handler */\n onError?: (error: unknown) => void;\n}\n\n/**\n * Fan-out a single async generator source to multiple subscribers.\n * The source stream is started when start() is called and frames are\n * distributed to all active subscriber queues.\n */\nexport class StreamFanout<T> {\n private readonly opts: StreamFanoutOptions<T>;\n private readonly queues = new Map<string, AsyncBoundedQueue<T>>();\n private source: AsyncGenerator<T, void, unknown> | null = null;\n private running = false;\n private pumpPromise: Promise<void> | null = null;\n\n constructor(opts: StreamFanoutOptions<T>) {\n this.opts = opts;\n }\n\n /** Start pumping frames from the source to all subscribers. */\n start(): void {\n if (this.running) return;\n this.running = true;\n this.source = this.opts.createSource();\n\n this.pumpPromise = (async () => {\n try {\n for await (const frame of this.source!) {\n try {\n this.opts.onFrame?.(frame);\n } catch {\n // ignore observer errors\n }\n for (const q of this.queues.values()) {\n q.push(frame);\n }\n }\n } catch (e) {\n this.opts.onError?.(e);\n } finally {\n this.running = false;\n for (const q of this.queues.values()) q.close();\n this.queues.clear();\n }\n })();\n }\n\n /**\n * Create a subscriber async generator.\n * Returns an async generator that yields frames from the shared source.\n * The generator terminates when the source ends or unsubscribe is called.\n */\n subscribe(id: string): AsyncGenerator<T, void, unknown> {\n const q = new AsyncBoundedQueue<T>(this.opts.maxQueueItems);\n if (!this.running) {\n // Pump already finished — close immediately so the subscriber terminates\n q.close();\n } else {\n this.queues.set(id, q);\n }\n const self = this;\n return (async function* () {\n try {\n while (true) {\n const r = await q.next();\n if (r.done) return;\n yield r.value;\n }\n } finally {\n q.close();\n self.queues.delete(id);\n }\n })();\n }\n\n /** Unsubscribe a specific subscriber. */\n unsubscribe(id: string): void {\n const q = this.queues.get(id);\n if (q) {\n q.close();\n this.queues.delete(id);\n }\n }\n\n /** Stop the source and close all subscriber queues. */\n async stop(): Promise<void> {\n if (!this.running) return;\n this.running = false;\n const src = this.source;\n this.source = null;\n for (const q of this.queues.values()) q.close();\n this.queues.clear();\n\n // Best-effort source termination with timeout to avoid hanging\n // if the source generator is stuck in an uninterruptible await\n const STOP_TIMEOUT = 3_000;\n const timeout = new Promise<void>((r) => setTimeout(r, STOP_TIMEOUT));\n try {\n await Promise.race([\n (async () => {\n try {\n await src?.return(undefined as never);\n } catch {\n // ignore\n }\n try {\n await this.pumpPromise;\n } catch {\n // ignore\n }\n })(),\n timeout,\n ]);\n } catch {\n // ignore\n }\n this.pumpPromise = null;\n }\n\n /** Returns true if the fan-out is running. */\n isRunning(): boolean {\n return this.running;\n }\n\n /** Returns the number of active subscribers. */\n subscriberCount(): number {\n return this.queues.size;\n }\n}\n","/**\n * FFmpeg process lifecycle management.\n * Spawn and manage FFmpeg processes with proper cleanup.\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport type { Logger } from \"./types.js\";\n\nexport interface FfmpegProcessOptions {\n /** FFmpeg binary path (default: \"ffmpeg\") */\n ffmpegPath?: string;\n /** FFmpeg arguments */\n args: string[];\n /** Logger instance */\n logger?: Logger;\n /** Label for log messages */\n label?: string;\n /** Additional stdio configuration (e.g. [\"pipe\"] for pipe:3) */\n extraStdio?: Array<\"pipe\" | \"ignore\" | \"inherit\">;\n /** Callback when process exits */\n onExit?: (code: number | null, signal: string | null) => void;\n /** Callback for stderr output */\n onStderr?: (data: string) => void;\n}\n\n/**\n * Managed FFmpeg process with proper lifecycle handling.\n */\nexport class FfmpegProcess {\n private process: ChildProcess | null = null;\n private killed = false;\n private readonly logger: Logger | undefined;\n private readonly label: string;\n\n constructor(private readonly options: FfmpegProcessOptions) {\n this.logger = options.logger;\n this.label = options.label ?? \"ffmpeg\";\n }\n\n /** Spawn the FFmpeg process. Returns stdin writable stream. */\n start(): NodeJS.WritableStream | null {\n if (this.process) {\n throw new Error(`[${this.label}] FFmpeg process already started`);\n }\n\n const ffmpegPath = this.options.ffmpegPath ?? \"ffmpeg\";\n const stdio: Array<\"pipe\" | \"ignore\" | \"inherit\"> = [\n \"pipe\",\n \"ignore\",\n \"pipe\",\n ...(this.options.extraStdio ?? []),\n ];\n\n this.process = spawn(ffmpegPath, this.options.args, { stdio });\n\n this.process.on(\"error\", (error) => {\n this.logger?.error(`[${this.label}] Failed to spawn FFmpeg:`, error);\n });\n\n this.process.on(\"close\", (code, signal) => {\n this.options.onExit?.(code, signal);\n });\n\n // Handle stderr\n this.process.stderr?.on(\"data\", (data: Buffer) => {\n const output = data.toString();\n this.options.onStderr?.(output);\n });\n\n // Prevent unhandled errors on stdin\n this.process.stdin?.on(\"error\", (error: NodeJS.ErrnoException) => {\n const code = error?.code;\n if (code === \"EPIPE\" || code === \"ERR_STREAM_WRITE_AFTER_END\") return;\n this.logger?.error(`[${this.label}] FFmpeg stdin error:`, error);\n });\n\n return this.process.stdin;\n }\n\n /** Get a specific stdio stream by fd index (e.g. 3 for pipe:3). */\n getStdio(fd: number): NodeJS.ReadableStream | NodeJS.WritableStream | null {\n if (!this.process) return null;\n return (this.process.stdio?.[fd] as NodeJS.ReadableStream | NodeJS.WritableStream) ?? null;\n }\n\n /** Get the underlying ChildProcess. */\n getProcess(): ChildProcess | null {\n return this.process;\n }\n\n /** Kill the FFmpeg process gracefully (SIGTERM then SIGKILL after timeout). */\n async kill(timeoutMs = 3000): Promise<void> {\n if (this.killed || !this.process) return;\n this.killed = true;\n\n const proc = this.process;\n this.process = null;\n\n try {\n proc.stdin?.end();\n } catch {\n // ignore\n }\n\n try {\n proc.kill(\"SIGTERM\");\n } catch {\n // ignore\n }\n\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n try {\n proc.kill(\"SIGKILL\");\n } catch {\n // ignore\n }\n resolve();\n }, timeoutMs);\n\n proc.on(\"close\", () => {\n clearTimeout(timer);\n resolve();\n });\n });\n }\n\n /** Check if the process is running. */\n isRunning(): boolean {\n return this.process !== null && !this.killed;\n }\n}\n","/**\n * Frame source adapters.\n * Convert various input patterns (EventEmitter, push callbacks) into FrameSource async generators.\n */\n\nimport { EventEmitter } from \"node:events\";\nimport type {\n AudioCodec,\n AudioFrame,\n FrameSource,\n MediaFrame,\n VideoCodec,\n VideoFrame,\n} from \"./types.js\";\n\n/**\n * Convert an EventEmitter-based source into a FrameSource async generator.\n *\n * @param emitter - The event emitter that fires video/audio frames\n * @param videoEvent - Event name for video frames (default: \"videoFrame\")\n * @param audioEvent - Event name for audio frames (default: \"audioFrame\")\n */\nexport function fromEventEmitter(\n emitter: EventEmitter,\n videoEvent = \"videoFrame\",\n audioEvent = \"audioFrame\",\n): FrameSource {\n const queue: MediaFrame[] = [];\n let resolve: ((value: IteratorResult<MediaFrame>) => void) | null = null;\n let done = false;\n\n const onVideo = (frame: VideoFrame) => {\n const mf: MediaFrame = { type: \"video\", frame };\n if (resolve) {\n const r = resolve;\n resolve = null;\n r({ value: mf, done: false });\n } else {\n queue.push(mf);\n if (queue.length > 500) queue.splice(0, queue.length - 500);\n }\n };\n\n const onAudio = (frame: AudioFrame) => {\n const mf: MediaFrame = { type: \"audio\", frame };\n if (resolve) {\n const r = resolve;\n resolve = null;\n r({ value: mf, done: false });\n } else {\n queue.push(mf);\n if (queue.length > 500) queue.splice(0, queue.length - 500);\n }\n };\n\n const cleanup = () => {\n done = true;\n emitter.removeListener(videoEvent, onVideo);\n emitter.removeListener(audioEvent, onAudio);\n if (resolve) {\n const r = resolve;\n resolve = null;\n r({ value: undefined as never, done: true });\n }\n };\n\n emitter.on(videoEvent, onVideo);\n emitter.on(audioEvent, onAudio);\n emitter.once(\"close\", cleanup);\n emitter.once(\"end\", cleanup);\n\n return (async function* () {\n try {\n while (true) {\n const item = queue.shift();\n if (item) {\n yield item;\n continue;\n }\n if (done) return;\n const result = await new Promise<IteratorResult<MediaFrame>>((r) => {\n resolve = r;\n });\n if (result.done) return;\n yield result.value;\n }\n } finally {\n cleanup();\n }\n })();\n}\n\n/**\n * Create a push-based frame source.\n * Returns a FrameSource async generator and push functions to deliver frames.\n */\nexport function fromPushCallback(): {\n source: FrameSource;\n pushVideo: (frame: VideoFrame) => void;\n pushAudio: (frame: AudioFrame) => void;\n close: () => void;\n} {\n const queue: MediaFrame[] = [];\n let resolve: ((value: IteratorResult<MediaFrame>) => void) | null = null;\n let closed = false;\n\n const push = (mf: MediaFrame) => {\n if (closed) return;\n if (resolve) {\n const r = resolve;\n resolve = null;\n r({ value: mf, done: false });\n } else {\n queue.push(mf);\n if (queue.length > 500) queue.splice(0, queue.length - 500);\n }\n };\n\n const source: FrameSource = (async function* () {\n try {\n while (true) {\n const item = queue.shift();\n if (item) {\n yield item;\n continue;\n }\n if (closed) return;\n const result = await new Promise<IteratorResult<MediaFrame>>((r) => {\n resolve = r;\n });\n if (result.done) return;\n yield result.value;\n }\n } finally {\n closed = true;\n }\n })();\n\n return {\n source,\n pushVideo: (frame: VideoFrame) => push({ type: \"video\", frame }),\n pushAudio: (frame: AudioFrame) => push({ type: \"audio\", frame }),\n close: () => {\n closed = true;\n if (resolve) {\n const r = resolve;\n resolve = null;\n r({ value: undefined as never, done: true });\n }\n },\n };\n}\n\n/**\n * Create a FrameSource from a raw async generator of { audio, data, codec, videoType, ... } frames.\n * This is the format used by reolink-baichuan-js's createNativeStream().\n */\nexport function fromNativeStream(\n native: AsyncGenerator<\n {\n audio: boolean;\n data: Buffer;\n codec: string | null;\n sampleRate: number | null;\n microseconds: number | null;\n videoType?: string;\n isKeyframe?: boolean;\n },\n void,\n unknown\n >,\n): FrameSource {\n return (async function* () {\n for await (const frame of native) {\n if (frame.audio) {\n yield {\n type: \"audio\" as const,\n frame: {\n data: frame.data,\n codec: (frame.codec === \"aac\" ? \"Aac\" : \"Adpcm\") as AudioCodec,\n sampleRate: frame.sampleRate ?? 8000,\n channels: 1,\n timestampMicros: frame.microseconds ?? Date.now() * 1000,\n },\n };\n } else {\n yield {\n type: \"video\" as const,\n frame: {\n data: frame.data,\n codec: ((frame.videoType as VideoCodec) ?? \"H264\") as VideoCodec,\n isKeyframe: frame.isKeyframe ?? false,\n timestampMicros: frame.microseconds ?? Date.now() * 1000,\n },\n };\n }\n }\n })();\n}\n","/**\n * Adaptive FFmpeg source — RTSP→FrameSource pipeline with hot-swappable encoding params.\n *\n * Spawns ffmpeg to transcode an RTSP source to raw H.264 Annex-B on stdout.\n * Supports dynamic bitrate/resolution changes by restarting ffmpeg with new params,\n * gating the transition on the next keyframe to avoid visual artifacts.\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport type { FrameSource, Logger, MediaFrame, VideoFrame, AudioFrame } from \"./types.js\";\nimport { isH264IdrAccessUnit } from \"./h264-utils.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface EncodingParams {\n /** Max video bitrate in kbps (e.g. 8000 for 8 Mbps). */\n maxBitrateKbps: number;\n /** Output width (0 = keep source). */\n width: number;\n /** Output height (0 = keep source). */\n height: number;\n /** H.264 preset (default: \"ultrafast\"). */\n preset?: string;\n}\n\n/** Audio mode: \"copy\" = passthrough G.711, \"opus\" = transcode to Opus, \"off\" = no audio */\nexport type AudioMode = \"copy\" | \"opus\" | \"off\";\n\nexport interface AdaptiveFfmpegSourceOptions {\n /** RTSP source URL. */\n rtspUrl: string;\n /** Initial encoding parameters. */\n initialParams: EncodingParams;\n /** Audio mode (default: \"copy\"). */\n audioMode?: AudioMode;\n /** FFmpeg binary path (default: \"ffmpeg\"). */\n ffmpegPath?: string;\n /** Logger instance. */\n logger?: Logger;\n /** Label for logging. */\n label?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Annex-B access unit splitter from raw byte stream\n// ---------------------------------------------------------------------------\n\n/**\n * Buffer incoming Annex-B data from ffmpeg stdout and yield complete access units.\n *\n * ffmpeg pipes a continuous byte stream — NAL units are split across arbitrary\n * 8KB chunks. We accumulate data and split on Access Unit Delimiters (NAL type 9),\n * which ffmpeg emits with `-x264opts aud=1`.\n *\n * Each AUD marks the start of a new access unit (= one video frame).\n */\nclass AnnexBAccessUnitAssembler {\n private buffer: Buffer = Buffer.alloc(0);\n\n /** Feed data from ffmpeg stdout. Returns complete access units (one per frame). */\n feed(data: Buffer): Buffer[] {\n this.buffer = this.buffer.length > 0\n ? Buffer.concat([this.buffer, data])\n : data;\n\n const aus: Buffer[] = [];\n\n // Find AUD boundaries (NAL type 9) — each AUD starts a new access unit\n // An AUD is: start_code + 0x09 + 1 byte\n const audPositions: number[] = [];\n for (let i = 0; i < this.buffer.length - 5; i++) {\n if (this.buffer[i] === 0 && this.buffer[i + 1] === 0) {\n let scLen = 0;\n if (this.buffer[i + 2] === 0 && this.buffer[i + 3] === 1) scLen = 4;\n else if (this.buffer[i + 2] === 1) scLen = 3;\n if (scLen > 0) {\n const nalType = this.buffer[i + scLen]! & 0x1f;\n if (nalType === 9) { // AUD\n audPositions.push(i);\n }\n }\n }\n }\n\n // Need at least 2 AUD positions to extract a complete AU (between two AUDs)\n if (audPositions.length < 2) return aus;\n\n // Extract complete AUs (between consecutive AUDs)\n for (let j = 0; j < audPositions.length - 1; j++) {\n const au = this.buffer.subarray(audPositions[j]!, audPositions[j + 1]!);\n if (au.length > 4) aus.push(au);\n }\n\n // Keep data from the last AUD onwards (incomplete AU)\n this.buffer = this.buffer.subarray(audPositions[audPositions.length - 1]!);\n\n return aus;\n }\n\n /** Flush any remaining buffered data as a final access unit. */\n flush(): Buffer | null {\n if (this.buffer.length <= 4) return null;\n const au = this.buffer;\n this.buffer = Buffer.alloc(0);\n return au;\n }\n}\n\n// ---------------------------------------------------------------------------\n// AdaptiveFfmpegSource\n// ---------------------------------------------------------------------------\n\nexport class AdaptiveFfmpegSource {\n private readonly rtspUrl: string;\n private readonly ffmpegPath: string;\n private readonly logger: Logger | undefined;\n private readonly label: string;\n private readonly audioMode: AudioMode;\n\n private currentParams: EncodingParams;\n private proc: ChildProcess | null = null;\n private audioProc: ChildProcess | null = null;\n private closed = false;\n\n /** Push callback for the frame source. */\n private pushFrame: ((frame: MediaFrame) => void) | null = null;\n private closeSource: (() => void) | null = null;\n\n /** The FrameSource async generator. Created once, survives ffmpeg restarts. */\n readonly source: FrameSource;\n\n constructor(options: AdaptiveFfmpegSourceOptions) {\n this.rtspUrl = options.rtspUrl;\n this.ffmpegPath = options.ffmpegPath ?? \"ffmpeg\";\n this.audioMode = options.audioMode ?? \"copy\";\n this.logger = options.logger;\n this.label = options.label ?? \"adaptive-ffmpeg\";\n this.currentParams = { ...options.initialParams };\n\n // Create a push-based FrameSource that survives ffmpeg restarts\n const queue: MediaFrame[] = [];\n let resolve: ((value: IteratorResult<MediaFrame>) => void) | null = null;\n let done = false;\n\n this.pushFrame = (mf: MediaFrame) => {\n if (done) return;\n if (resolve) {\n const r = resolve;\n resolve = null;\n r({ value: mf, done: false });\n } else {\n queue.push(mf);\n if (queue.length > 120) queue.splice(0, queue.length - 60);\n }\n };\n\n this.closeSource = () => {\n done = true;\n if (resolve) {\n const r = resolve;\n resolve = null;\n r({ value: undefined as never, done: true });\n }\n };\n\n this.source = (async function* () {\n try {\n while (true) {\n const item = queue.shift();\n if (item) { yield item; continue; }\n if (done) return;\n const result = await new Promise<IteratorResult<MediaFrame>>((r) => {\n resolve = r;\n });\n if (result.done) return;\n yield result.value;\n }\n } finally {\n done = true;\n }\n })();\n }\n\n /** Start the ffmpeg process with current encoding params. */\n async start(): Promise<void> {\n if (this.closed) return;\n this.spawnFfmpeg();\n }\n\n /** Get the current encoding parameters. */\n getParams(): Readonly<EncodingParams> {\n return { ...this.currentParams };\n }\n\n /**\n * Hot-swap encoding parameters.\n * Stops the current ffmpeg and starts a new one with updated params.\n * The FrameSource continues seamlessly — the new ffmpeg's first keyframe\n * is gated internally so consumers see a clean transition.\n */\n async updateParams(params: Partial<EncodingParams>): Promise<void> {\n const prev = { ...this.currentParams };\n if (params.maxBitrateKbps !== undefined) this.currentParams.maxBitrateKbps = params.maxBitrateKbps;\n if (params.width !== undefined) this.currentParams.width = params.width;\n if (params.height !== undefined) this.currentParams.height = params.height;\n if (params.preset !== undefined) this.currentParams.preset = params.preset;\n\n // No change → skip restart\n if (\n prev.maxBitrateKbps === this.currentParams.maxBitrateKbps &&\n prev.width === this.currentParams.width &&\n prev.height === this.currentParams.height\n ) return;\n\n this.logger?.info(\n `[${this.label}] Updating params: ${prev.maxBitrateKbps}kbps ${prev.width}x${prev.height} → ` +\n `${this.currentParams.maxBitrateKbps}kbps ${this.currentParams.width}x${this.currentParams.height}`,\n );\n\n // Kill old ffmpeg and start new one\n await this.killFfmpeg();\n if (!this.closed) {\n this.spawnFfmpeg();\n }\n }\n\n /** Stop the source and kill ffmpeg. */\n async stop(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n await this.killFfmpeg();\n this.closeSource?.();\n }\n\n // -----------------------------------------------------------------------\n // Private\n // -----------------------------------------------------------------------\n\n private spawnFfmpeg(): void {\n const { maxBitrateKbps, width, height, preset } = this.currentParams;\n\n const args: string[] = [\n \"-hide_banner\",\n \"-loglevel\", \"error\",\n \"-fflags\", \"+nobuffer\",\n \"-flags\", \"+low_delay\",\n \"-rtsp_transport\", \"tcp\",\n \"-i\", this.rtspUrl,\n \"-c:v\", \"libx264\",\n \"-preset\", preset ?? \"ultrafast\",\n \"-tune\", \"zerolatency\",\n \"-crf\", \"28\",\n \"-maxrate\", `${maxBitrateKbps}k`,\n \"-bufsize\", `${Math.round(maxBitrateKbps * 0.5)}k`,\n \"-g\", \"50\",\n \"-keyint_min\", \"25\",\n \"-x264opts\", \"aud=1:sliced-threads=1\",\n \"-flush_packets\", \"1\",\n ];\n\n // Scale filter\n if (width > 0 && height > 0) {\n args.push(\"-vf\", `scale=${width}:${height}`);\n }\n\n // Video-only output on stdout\n args.push(\n \"-an\",\n \"-f\", \"h264\",\n \"-\",\n );\n\n this.proc = spawn(this.ffmpegPath, args, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n this.proc.on(\"error\", (err) => {\n this.logger?.error(`[${this.label}] ffmpeg spawn error: ${err.message}`);\n });\n\n this.proc.on(\"close\", (code, signal) => {\n this.logger?.debug(`[${this.label}] ffmpeg exited code=${code} signal=${signal}`);\n this.proc = null;\n if (!this.closed) {\n setTimeout(() => {\n if (!this.closed) this.spawnFfmpeg();\n }, 2000);\n }\n });\n\n this.proc.stderr?.on(\"data\", (data: Buffer) => {\n const s = data.toString();\n if (s.includes(\"error\") || s.includes(\"Error\") || s.includes(\"fatal\")) {\n this.logger?.error(`[${this.label}] ffmpeg: ${s.trim()}`);\n }\n });\n\n if (!this.proc.stdout) {\n this.logger?.error(`[${this.label}] ffmpeg stdout not available`);\n return;\n }\n\n // Parse raw H.264 Annex-B from stdout into access units\n const assembler = new AnnexBAccessUnitAssembler();\n const startTime = Date.now();\n\n this.proc.stdout.on(\"data\", (data: Buffer) => {\n if (this.closed) return;\n const aus = assembler.feed(data);\n for (const au of aus) {\n if (au.length < 4) continue;\n const isKeyframe = isH264IdrAccessUnit(au);\n const timestampMicros = (Date.now() - startTime) * 1000;\n\n const vf: VideoFrame = {\n data: au,\n codec: \"H264\",\n isKeyframe,\n timestampMicros,\n };\n\n this.pushFrame?.({ type: \"video\", frame: vf });\n }\n });\n\n this.proc.stdout.on(\"end\", () => {\n const remaining = assembler.flush();\n if (remaining && remaining.length > 4 && this.pushFrame) {\n const vf: VideoFrame = {\n data: remaining,\n codec: \"H264\",\n isKeyframe: isH264IdrAccessUnit(remaining),\n timestampMicros: (Date.now() - startTime) * 1000,\n };\n this.pushFrame({ type: \"video\", frame: vf });\n }\n });\n\n // Audio process (separate from video — survives video ffmpeg restarts)\n if (this.audioMode !== \"off\") {\n const audioArgs = [\n \"-hide_banner\", \"-loglevel\", \"error\",\n \"-fflags\", \"+nobuffer+flush_packets\",\n \"-rtsp_transport\", \"tcp\",\n \"-analyzeduration\", \"500000\",\n \"-probesize\", \"500000\",\n \"-i\", this.rtspUrl,\n \"-vn\",\n ];\n\n let audioCodecLabel: string;\n let frameSize: number;\n let sampleRate: number;\n let codecName: string;\n\n if (this.audioMode === \"opus\") {\n // Transcode to Opus — better quality, uses CPU\n audioArgs.push(\"-c:a\", \"libopus\", \"-ar\", \"48000\", \"-ac\", \"2\", \"-b:a\", \"64k\", \"-f\", \"ogg\", \"-\");\n audioCodecLabel = \"opus\";\n frameSize = 960; // 20ms at 48kHz, 2 channels, but OGG needs parsing — use raw instead\n sampleRate = 48000;\n codecName = \"Opus\";\n // Actually OGG is complex to parse. Use raw s16le + re-encode... too complex.\n // For Opus mode, just use G.711 for now and transcode later when we have a proper parser.\n // TODO: implement proper Opus transcoding\n audioArgs.length = 0;\n audioArgs.push(\n \"-hide_banner\", \"-loglevel\", \"error\",\n \"-rtsp_transport\", \"tcp\", \"-i\", this.rtspUrl,\n \"-vn\", \"-c:a\", \"pcm_mulaw\", \"-ar\", \"8000\", \"-ac\", \"1\", \"-f\", \"mulaw\", \"-\",\n );\n audioCodecLabel = \"pcmu(opus-fallback)\";\n frameSize = 160;\n sampleRate = 8000;\n codecName = \"Pcmu\";\n } else {\n // Copy mode: passthrough G.711 µ-law (zero CPU, native WebRTC support)\n audioArgs.push(\"-c:a\", \"pcm_mulaw\", \"-ar\", \"8000\", \"-ac\", \"1\", \"-f\", \"mulaw\", \"-\");\n audioCodecLabel = \"pcmu\";\n frameSize = 160; // 20ms at 8kHz\n sampleRate = 8000;\n codecName = \"Pcmu\";\n }\n\n this.audioProc = spawn(this.ffmpegPath, audioArgs, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n this.audioProc.on(\"error\", () => {});\n this.audioProc.on(\"close\", () => { this.audioProc = null; });\n\n if (this.audioProc.stdout) {\n let audioBuf: Buffer = Buffer.alloc(0);\n this.audioProc.stdout.on(\"data\", (data: Buffer) => {\n if (this.closed || !this.pushFrame) return;\n audioBuf = audioBuf.length > 0 ? Buffer.concat([audioBuf, data]) as Buffer<ArrayBuffer> : data;\n while (audioBuf.length >= frameSize) {\n const audioFrame = audioBuf.subarray(0, frameSize);\n audioBuf = audioBuf.subarray(frameSize);\n this.pushFrame!({\n type: \"audio\",\n frame: {\n data: Buffer.from(audioFrame),\n codec: codecName as any,\n sampleRate,\n channels: 1,\n timestampMicros: (Date.now() - startTime) * 1000,\n },\n });\n }\n });\n }\n\n this.logger?.info(\n `[${this.label}] Started: ${maxBitrateKbps}kbps ` +\n (width > 0 ? `${width}x${height}` : \"native\") +\n ` +audio(${audioCodecLabel})`,\n );\n } else {\n this.logger?.info(\n `[${this.label}] Started: ${maxBitrateKbps}kbps ` +\n (width > 0 ? `${width}x${height}` : \"native\") +\n \" (no audio)\",\n );\n }\n }\n\n private async killFfmpeg(): Promise<void> {\n // Kill video process\n const proc = this.proc;\n if (proc) {\n this.proc = null;\n try { proc.kill(\"SIGTERM\"); } catch { /* */ }\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => { try { proc.kill(\"SIGKILL\"); } catch { /* */ } resolve(); }, 3000);\n proc.on(\"close\", () => { clearTimeout(timer); resolve(); });\n });\n }\n\n // Kill audio process\n const audioProc = this.audioProc;\n if (audioProc) {\n this.audioProc = null;\n try { audioProc.kill(\"SIGTERM\"); } catch { /* */ }\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => { try { audioProc.kill(\"SIGKILL\"); } catch { /* */ } resolve(); }, 1000);\n audioProc.on(\"close\", () => { clearTimeout(timer); resolve(); });\n });\n }\n }\n}\n","/**\n * Adaptive quality controller — state machine for bitrate/resolution/source adaptation.\n *\n * Monitors packet loss, jitter, and RTT from RTCP reports and client-reported stats.\n * Makes quality decisions using a three-tier degradation/recovery model:\n *\n * Level 1 — Reduce bitrate cap (ffmpeg -maxrate)\n * Level 2 — Downscale resolution (ffmpeg -vf scale=)\n * Level 3 — Switch source stream (main → sub via replaceTrack)\n *\n * Hysteresis prevents oscillation: degradation requires 2 consecutive bad intervals,\n * recovery requires 3 consecutive good intervals.\n */\n\nimport type { Logger } from \"./types.js\";\nimport type { EncodingParams } from \"./ffmpeg-source.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type QualityTier = \"high\" | \"medium\" | \"low\";\n\nexport interface QualityProfile {\n tier: QualityTier;\n /** Encoding params to apply. */\n encoding: EncodingParams;\n /** Source stream profile (\"main\" | \"sub\"). */\n sourceProfile: \"main\" | \"sub\";\n}\n\nexport interface StreamStats {\n /** Packet loss ratio (0.0 – 1.0). */\n packetLoss: number;\n /** Jitter in milliseconds. */\n jitterMs: number;\n /** Round-trip time in milliseconds. */\n rttMs: number;\n /** Timestamp of this measurement. */\n timestamp: number;\n}\n\nexport interface AdaptiveControllerOptions {\n /** Available quality profiles, ordered from highest to lowest. */\n profiles: QualityProfile[];\n /** Packet loss threshold to degrade (default: 0.02 = 2%). */\n degradeThreshold?: number;\n /** Packet loss threshold to recover (default: 0.005 = 0.5%). */\n recoverThreshold?: number;\n /** Consecutive bad intervals before degradation (default: 2). */\n degradeCount?: number;\n /** Consecutive good intervals before recovery (default: 3). */\n recoverCount?: number;\n /** Callback when quality should change. */\n onQualityChange: (from: QualityProfile, to: QualityProfile) => void | Promise<void>;\n /** Logger. */\n logger?: Logger;\n}\n\n// ---------------------------------------------------------------------------\n// EWMA (Exponentially Weighted Moving Average)\n// ---------------------------------------------------------------------------\n\nclass EWMA {\n private value: number | null = null;\n private readonly alpha: number;\n\n constructor(alpha = 0.3) {\n this.alpha = alpha;\n }\n\n update(sample: number): number {\n if (this.value === null) {\n this.value = sample;\n } else {\n this.value = this.alpha * sample + (1 - this.alpha) * this.value;\n }\n return this.value;\n }\n\n get(): number {\n return this.value ?? 0;\n }\n\n reset(): void {\n this.value = null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// AdaptiveController\n// ---------------------------------------------------------------------------\n\nexport class AdaptiveController {\n private readonly profiles: readonly QualityProfile[];\n private readonly degradeThreshold: number;\n private readonly recoverThreshold: number;\n private readonly degradeCount: number;\n private readonly recoverCount: number;\n private readonly onQualityChange: AdaptiveControllerOptions[\"onQualityChange\"];\n private readonly logger: Logger | undefined;\n\n private currentIndex: number;\n private consecutiveBad = 0;\n private consecutiveGood = 0;\n private switching = false;\n\n /** Smoothed stats per session (aggregated for decisions). */\n private readonly sessionStats = new Map<string, {\n loss: EWMA;\n jitter: EWMA;\n rtt: EWMA;\n lastUpdate: number;\n }>();\n\n /** Manual override tier (null = auto). */\n private forcedTier: QualityTier | null = null;\n\n constructor(options: AdaptiveControllerOptions) {\n if (options.profiles.length === 0) {\n throw new Error(\"At least one quality profile is required\");\n }\n this.profiles = options.profiles;\n this.degradeThreshold = options.degradeThreshold ?? 0.02;\n this.recoverThreshold = options.recoverThreshold ?? 0.005;\n this.degradeCount = options.degradeCount ?? 2;\n this.recoverCount = options.recoverCount ?? 3;\n this.onQualityChange = options.onQualityChange;\n this.logger = options.logger;\n this.currentIndex = 0; // Start at highest quality\n }\n\n /** Get the current quality profile. */\n get currentProfile(): QualityProfile {\n return this.profiles[this.currentIndex]!;\n }\n\n /** Get the current quality tier. */\n get currentTier(): QualityTier {\n return this.currentProfile.tier;\n }\n\n /** Get aggregated stats summary. */\n getAggregatedStats(): { packetLoss: number; jitterMs: number; rttMs: number } {\n if (this.sessionStats.size === 0) {\n return { packetLoss: 0, jitterMs: 0, rttMs: 0 };\n }\n\n let totalLoss = 0;\n let totalJitter = 0;\n let totalRtt = 0;\n let count = 0;\n\n for (const stats of this.sessionStats.values()) {\n // Skip stale sessions (>30s without update)\n if (Date.now() - stats.lastUpdate > 30_000) continue;\n totalLoss += stats.loss.get();\n totalJitter += stats.jitter.get();\n totalRtt += stats.rtt.get();\n count++;\n }\n\n if (count === 0) return { packetLoss: 0, jitterMs: 0, rttMs: 0 };\n\n return {\n packetLoss: totalLoss / count,\n jitterMs: totalJitter / count,\n rttMs: totalRtt / count,\n };\n }\n\n /**\n * Report stats from a session (RTCP or client-reported).\n * Call this periodically (e.g. every 3–5 seconds).\n */\n reportStats(sessionId: string, stats: StreamStats): void {\n let entry = this.sessionStats.get(sessionId);\n if (!entry) {\n entry = {\n loss: new EWMA(0.3),\n jitter: new EWMA(0.3),\n rtt: new EWMA(0.3),\n lastUpdate: 0,\n };\n this.sessionStats.set(sessionId, entry);\n }\n\n entry.loss.update(stats.packetLoss);\n entry.jitter.update(stats.jitterMs);\n entry.rtt.update(stats.rttMs);\n entry.lastUpdate = stats.timestamp;\n\n // Evaluate quality after stats update\n this.evaluate();\n }\n\n /** Remove a session's stats (call on session close). */\n removeSession(sessionId: string): void {\n this.sessionStats.delete(sessionId);\n }\n\n /** Force a specific quality tier (null = auto). */\n forceQuality(tier: QualityTier | null): void {\n this.forcedTier = tier;\n\n if (tier === null) {\n // Back to auto — reset counters\n this.consecutiveBad = 0;\n this.consecutiveGood = 0;\n return;\n }\n\n const targetIdx = this.profiles.findIndex((p) => p.tier === tier);\n if (targetIdx >= 0 && targetIdx !== this.currentIndex) {\n void this.switchTo(targetIdx);\n }\n }\n\n /** Check if auto-adaptation is active (not forced). */\n get isAuto(): boolean {\n return this.forcedTier === null;\n }\n\n // -----------------------------------------------------------------------\n // Private\n // -----------------------------------------------------------------------\n\n private evaluate(): void {\n if (this.forcedTier !== null || this.switching) return;\n\n const { packetLoss } = this.getAggregatedStats();\n\n if (packetLoss > this.degradeThreshold) {\n this.consecutiveGood = 0;\n this.consecutiveBad++;\n\n if (this.consecutiveBad >= this.degradeCount) {\n this.consecutiveBad = 0;\n this.degrade();\n }\n } else if (packetLoss < this.recoverThreshold) {\n this.consecutiveBad = 0;\n this.consecutiveGood++;\n\n if (this.consecutiveGood >= this.recoverCount) {\n this.consecutiveGood = 0;\n this.recover();\n }\n } else {\n // In between thresholds — reset both counters\n this.consecutiveBad = 0;\n this.consecutiveGood = 0;\n }\n }\n\n private degrade(): void {\n if (this.currentIndex >= this.profiles.length - 1) {\n this.logger?.debug(\"[adaptive] Already at lowest quality, cannot degrade further\");\n return;\n }\n void this.switchTo(this.currentIndex + 1);\n }\n\n private recover(): void {\n if (this.currentIndex <= 0) {\n this.logger?.debug(\"[adaptive] Already at highest quality, cannot recover further\");\n return;\n }\n void this.switchTo(this.currentIndex - 1);\n }\n\n private async switchTo(newIndex: number): Promise<void> {\n if (this.switching) return;\n if (newIndex === this.currentIndex) return;\n if (newIndex < 0 || newIndex >= this.profiles.length) return;\n\n this.switching = true;\n const from = this.profiles[this.currentIndex]!;\n const to = this.profiles[newIndex]!;\n\n this.logger?.info(\n `[adaptive] Quality change: ${from.tier} → ${to.tier} ` +\n `(${from.encoding.maxBitrateKbps}kbps → ${to.encoding.maxBitrateKbps}kbps)`,\n );\n\n try {\n await this.onQualityChange(from, to);\n this.currentIndex = newIndex;\n } catch (err) {\n this.logger?.error(\"[adaptive] Quality change failed:\", err);\n } finally {\n this.switching = false;\n }\n }\n}\n","/**\n * Adaptive WebRTC session — extends the base session pattern with:\n * - RTCP Receiver Report monitoring (packet loss, jitter, RTT)\n * - Source replacement (replaceTrack for seamless quality switching)\n * - Stats emission for the AdaptiveController\n *\n * Uses werift (optional peer dependency) for server-side WebRTC.\n */\n\nimport type {\n FrameSource,\n Logger,\n VideoCodec,\n AudioCodec,\n MediaFrame,\n} from \"./types.js\";\nimport { splitAnnexBToNals } from \"./nal-utils.js\";\nimport { convertH264ToAnnexB, isH264IdrAccessUnit } from \"./h264-utils.js\";\nimport { convertH265ToAnnexB, isH265IrapAccessUnit } from \"./h265-utils.js\";\n\n// ---------------------------------------------------------------------------\n// Werift type aliases (lazy-loaded optional peer dep)\n// ---------------------------------------------------------------------------\ntype WeriftPC = any;\ntype WeriftTrack = any;\n\nlet _werift: any | undefined;\n\nasync function loadWerift(): Promise<any> {\n if (_werift) return _werift;\n try {\n const moduleName = \"werift\";\n _werift = await (Function(\"m\", \"return import(m)\")(moduleName) as Promise<any>);\n return _werift;\n } catch {\n throw new Error(\n \"The 'werift' package is required for WebRTC support but is not installed. \" +\n \"Install it with: npm install werift\",\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface AdaptiveSessionOptions {\n sessionId: string;\n source: FrameSource;\n intercom?: {\n onAudioReceived: (data: Buffer, format: AudioCodec) => void | Promise<void>;\n };\n iceConfig?: {\n stunServers?: string[];\n turnServers?: Array<{ urls: string; username?: string; credential?: string }>;\n portRange?: [number, number];\n additionalHostAddresses?: string[];\n };\n /** Callback for RTCP stats (called every ~3s). */\n onStats?: (stats: SessionStats) => void;\n /** Enable verbose frame/RTP logging. */\n debug?: boolean;\n logger: Logger;\n}\n\nexport interface SessionStats {\n sessionId: string;\n /** Fraction of packets lost (0.0–1.0). */\n packetLoss: number;\n /** Interarrival jitter in ms. */\n jitterMs: number;\n /** Round-trip time in ms (from RTCP SR/RR). */\n rttMs: number;\n /** Total packets received. */\n packetsReceived: number;\n /** Total packets lost. */\n packetsLost: number;\n /** Timestamp. */\n timestamp: number;\n}\n\nexport interface SessionInfo {\n sessionId: string;\n state: \"new\" | \"connecting\" | \"connected\" | \"disconnected\" | \"closed\";\n createdAt: number;\n}\n\n// ---------------------------------------------------------------------------\n// AdaptiveSession\n// ---------------------------------------------------------------------------\n\nexport class AdaptiveSession {\n private readonly sessionId: string;\n private source: FrameSource;\n private readonly logger: Logger;\n private readonly intercom: AdaptiveSessionOptions[\"intercom\"];\n private readonly iceConfig: AdaptiveSessionOptions[\"iceConfig\"];\n private readonly onStats: AdaptiveSessionOptions[\"onStats\"];\n readonly debug: boolean;\n private readonly createdAt: number;\n\n private state: SessionInfo[\"state\"] = \"new\";\n private pc: WeriftPC | null = null;\n private videoTrack: WeriftTrack | null = null;\n private audioTrack: WeriftTrack | null = null;\n /** Transceiver senders for direct sendRtp (more reliable than track.writeRtp) */\n private videoSender: any = null;\n private audioSender: any = null;\n private feedAbort: AbortController | null = null;\n private closed = false;\n private statsTimer: ReturnType<typeof setInterval> | null = null;\n\n /** RTP sequence number counter (must increment per packet). */\n private videoSeqNum = 0;\n private audioSeqNum = 0;\n\n /** Previous RTCP stats for delta calculation. */\n private prevPacketsReceived = 0;\n private prevPacketsLost = 0;\n\n constructor(options: AdaptiveSessionOptions) {\n this.sessionId = options.sessionId;\n this.source = options.source;\n this.logger = options.logger;\n this.intercom = options.intercom;\n this.iceConfig = options.iceConfig;\n this.onStats = options.onStats;\n this.debug = options.debug ?? false;\n this.createdAt = Date.now();\n }\n\n /** Build PeerConnection options including H.264 codec config. */\n private async buildPcOptions(): Promise<{ werift: any; pcOptions: any }> {\n const werift = await loadWerift();\n\n const iceServers: any[] = [];\n if (this.iceConfig?.stunServers) {\n for (const url of this.iceConfig.stunServers) iceServers.push({ urls: url });\n }\n if (this.iceConfig?.turnServers) {\n for (const turn of this.iceConfig.turnServers) {\n iceServers.push({ urls: turn.urls, username: turn.username, credential: turn.credential });\n }\n }\n\n const pcOptions: any = {\n // H.264 + Opus codecs with RTCP feedback (matching Scrypted's proven config)\n codecs: {\n video: [\n new werift.RTCRtpCodecParameters({\n mimeType: \"video/H264\",\n clockRate: 90000,\n payloadType: 96,\n parameters: \"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\",\n rtcpFeedback: [\n { type: \"transport-cc\" },\n { type: \"ccm\", parameter: \"fir\" },\n { type: \"nack\" },\n { type: \"nack\", parameter: \"pli\" },\n { type: \"goog-remb\" },\n ],\n }),\n ],\n audio: [\n new werift.RTCRtpCodecParameters({\n mimeType: \"audio/PCMU\",\n clockRate: 8000,\n payloadType: 0,\n channels: 1,\n parameters: \"\",\n }),\n ],\n },\n };\n if (iceServers.length > 0) pcOptions.iceServers = iceServers;\n if (this.iceConfig?.portRange) pcOptions.icePortRange = this.iceConfig.portRange;\n if (this.iceConfig?.additionalHostAddresses) {\n pcOptions.iceAdditionalHostAddresses = this.iceConfig.additionalHostAddresses;\n }\n\n return { werift, pcOptions };\n }\n\n /** Create offer SDP (server → client). */\n async createOffer(): Promise<{ sdp: string; type: \"offer\" }> {\n const { werift, pcOptions } = await this.buildPcOptions();\n\n this.pc = new werift.RTCPeerConnection(pcOptions);\n\n // ICE state monitoring\n this.pc.iceConnectionStateChange.subscribe((state: string) => {\n this.logger.debug(`[session:${this.sessionId}] ICE: ${state}`);\n if (state === \"connected\") {\n this.state = \"connected\";\n this.startStatsCollection();\n } else if (state === \"disconnected\" || state === \"failed\" || state === \"closed\") {\n this.state = state === \"disconnected\" ? \"disconnected\" : \"closed\";\n void this.close();\n }\n });\n\n // Video track (sendonly) — save transceiver sender for sendRtp\n this.videoTrack = new werift.MediaStreamTrack({ kind: \"video\" });\n const videoTransceiver = this.pc.addTransceiver(this.videoTrack, { direction: \"sendonly\" });\n this.videoSender = videoTransceiver.sender;\n\n // Audio track (sendrecv if intercom, sendonly otherwise)\n this.audioTrack = new werift.MediaStreamTrack({ kind: \"audio\" });\n const audioDir = this.intercom ? \"sendrecv\" : \"sendonly\";\n const audioTransceiver = this.pc.addTransceiver(this.audioTrack, { direction: audioDir });\n this.audioSender = audioTransceiver.sender;\n\n // Intercom: listen for incoming audio\n if (this.intercom) {\n const cb = this.intercom.onAudioReceived;\n audioTransceiver.onTrack.subscribe((track: any) => {\n track.onReceiveRtp.subscribe((pkt: any) => {\n try {\n const payload = pkt.payload as Buffer;\n if (payload?.length > 0) void cb(payload, \"Opus\");\n } catch (err) {\n this.logger.error(`[session:${this.sessionId}] Intercom error:`, err);\n }\n });\n });\n }\n\n const offer = await this.pc.createOffer();\n await this.pc.setLocalDescription(offer);\n\n // Wait for ICE gathering to complete so the offer includes candidates.\n // Without this, the offer has no candidates and the client can't connect.\n await new Promise<void>((resolve) => {\n if (this.pc.iceGatheringState === \"complete\") { resolve(); return; }\n this.pc.iceGatheringStateChange.subscribe((state: string) => {\n if (state === \"complete\") resolve();\n });\n // Timeout: don't wait forever if STUN is unreachable\n setTimeout(resolve, 5000);\n });\n\n // Use the local description which now includes gathered ICE candidates.\n // Force a=setup:actpass (let browser choose role) — werift defaults may cause DTLS issues.\n let finalSdp = this.pc.localDescription?.sdp ?? offer.sdp;\n // Ensure actpass so browser can be either active or passive\n finalSdp = finalSdp.replace(/a=setup:active\\r?\\n/g, \"a=setup:actpass\\r\\n\");\n this.state = \"connecting\";\n this.logger.info(`[session:${this.sessionId}] Offer created`);\n return { sdp: finalSdp, type: \"offer\" };\n }\n\n /** Handle WHEP answer: client sends SDP answer, we set remote description and start feeding. */\n async handleAnswer(answer: { sdp: string; type: \"answer\" }): Promise<void> {\n if (!this.pc) throw new Error(\"Call createOffer() first\");\n const werift = await loadWerift();\n const desc = new werift.RTCSessionDescription(answer.sdp, answer.type);\n await this.pc.setRemoteDescription(desc);\n this.logger.info(`[session:${this.sessionId}] Answer set, feeding started`);\n this.startFeedingFrames();\n }\n\n /**\n * Handle WHEP offer: client sends SDP offer, we create answer.\n *\n * Uses the server-creates-offer pattern internally: we create our own offer\n * with sendonly tracks, then use the client's offer codecs to build a\n * compatible answer. This avoids werift transceiver direction issues.\n */\n async handleOffer(clientOffer: { sdp: string; type: \"offer\" }): Promise<{ sdp: string; type: \"answer\" }> {\n const { werift, pcOptions } = await this.buildPcOptions();\n\n this.pc = new werift.RTCPeerConnection(pcOptions);\n\n this.pc.iceConnectionStateChange.subscribe((state: string) => {\n this.logger.debug(`[session:${this.sessionId}] ICE: ${state}`);\n if (state === \"connected\") {\n this.state = \"connected\";\n this.startStatsCollection();\n } else if (state === \"disconnected\" || state === \"failed\" || state === \"closed\") {\n this.state = state === \"disconnected\" ? \"disconnected\" : \"closed\";\n void this.close();\n }\n });\n\n // Set the client's offer as remote description.\n // werift creates transceivers from the offer's m-lines.\n const remoteDesc = new werift.RTCSessionDescription(clientOffer.sdp, clientOffer.type);\n await this.pc.setRemoteDescription(remoteDesc);\n\n // Find the transceivers werift created from the offer and attach our tracks.\n const transceivers = this.pc.getTransceivers();\n for (const t of transceivers) {\n const kind = t.receiver?.track?.kind ?? t.kind;\n if (kind === \"video\" && !this.videoTrack) {\n this.videoTrack = new werift.MediaStreamTrack({ kind: \"video\" });\n await t.sender.replaceTrack(this.videoTrack);\n } else if (kind === \"audio\" && !this.audioTrack) {\n this.audioTrack = new werift.MediaStreamTrack({ kind: \"audio\" });\n await t.sender.replaceTrack(this.audioTrack);\n }\n }\n\n // Fallback: if no transceivers matched (shouldn't happen with valid offer)\n if (!this.videoTrack) {\n this.logger.warn(`[session:${this.sessionId}] No video transceiver found in offer, adding one`);\n this.videoTrack = new werift.MediaStreamTrack({ kind: \"video\" });\n this.pc.addTransceiver(this.videoTrack, { direction: \"sendonly\" });\n }\n if (!this.audioTrack) {\n this.logger.warn(`[session:${this.sessionId}] No audio transceiver found in offer, adding one`);\n this.audioTrack = new werift.MediaStreamTrack({ kind: \"audio\" });\n this.pc.addTransceiver(this.audioTrack, { direction: \"sendonly\" });\n }\n\n const answer = await this.pc.createAnswer();\n await this.pc.setLocalDescription(answer);\n this.state = \"connecting\";\n this.logger.info(`[session:${this.sessionId}] WHEP answer created`);\n\n this.startFeedingFrames();\n\n return { sdp: answer.sdp, type: \"answer\" };\n }\n\n /** Add ICE candidate. */\n async addIceCandidate(candidate: any): Promise<void> {\n if (!this.pc) throw new Error(\"Call createOffer() first\");\n const werift = await loadWerift();\n await this.pc.addIceCandidate(new werift.RTCIceCandidate(candidate));\n }\n\n /**\n * Detach the frame source (for connection pooling).\n * The session stays alive (ICE/DTLS connected) but stops feeding frames.\n * Call replaceSource() later to reattach a camera.\n */\n detachSource(): void {\n if (this.feedAbort) {\n this.feedAbort.abort();\n this.feedAbort = null;\n }\n this.logger.debug(`[session:${this.sessionId}] Source detached (idle)`);\n }\n\n /** Whether the session has an active feed (vs idle/pooled). */\n get isFeeding(): boolean {\n return this.feedAbort !== null && !this.feedAbort.signal.aborted;\n }\n\n /**\n * Replace the frame source (for seamless source switching).\n * The new source will take effect at the next keyframe.\n */\n replaceSource(newSource: FrameSource): void {\n this.source = newSource;\n // Abort old feed — the feeding loop will restart with new source\n if (this.feedAbort) {\n this.feedAbort.abort();\n this.feedAbort = null;\n }\n this.startFeedingFrames();\n }\n\n getInfo(): SessionInfo {\n return { sessionId: this.sessionId, state: this.state, createdAt: this.createdAt };\n }\n\n async close(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n this.state = \"closed\";\n this.logger.info(`[session:${this.sessionId}] Closing`);\n\n if (this.statsTimer) {\n clearInterval(this.statsTimer);\n this.statsTimer = null;\n }\n if (this.feedAbort) {\n this.feedAbort.abort();\n this.feedAbort = null;\n }\n try { await this.source.return(undefined); } catch { /* */ }\n if (this.pc) {\n try { await this.pc.close(); } catch { /* */ }\n this.pc = null;\n }\n this.videoTrack = null;\n this.audioTrack = null;\n }\n\n // -----------------------------------------------------------------------\n // Frame feeding\n // -----------------------------------------------------------------------\n\n private startFeedingFrames(): void {\n this.feedAbort = new AbortController();\n const { signal } = this.feedAbort;\n\n void (async () => {\n let gotKeyframe = false;\n let videoTimestampBase: number | null = null;\n let audioTimestampBase: number | null = null;\n let frameCount = 0;\n\n try {\n for await (const mediaFrame of this.source) {\n if (signal.aborted || this.closed) break;\n frameCount++;\n if (this.debug && (frameCount <= 5 || frameCount % 100 === 0)) {\n this.logger.debug(\n `[session:${this.sessionId}] Frame #${frameCount}: ${mediaFrame.type} ` +\n `size=${mediaFrame.frame.data.length} ` +\n (mediaFrame.type === \"video\" ? `key=${(mediaFrame.frame as any).isKeyframe}` : \"\"),\n );\n }\n\n if (mediaFrame.type === \"video\") {\n const frame = mediaFrame.frame;\n const annexB = frame.codec === \"H264\"\n ? convertH264ToAnnexB(frame.data)\n : convertH265ToAnnexB(frame.data);\n\n if (!gotKeyframe) {\n const isKey = frame.codec === \"H264\"\n ? isH264IdrAccessUnit(annexB)\n : isH265IrapAccessUnit(annexB);\n if (!isKey) continue;\n gotKeyframe = true;\n if (this.debug) {\n const iceState = this.pc?.iceConnectionState ?? \"unknown\";\n const connState = this.pc?.connectionState ?? \"unknown\";\n this.logger.info(\n `[session:${this.sessionId}] First keyframe at frame #${frameCount}, size=${annexB.length}, ` +\n `ICE=${iceState}, conn=${connState}`,\n );\n }\n }\n\n if (videoTimestampBase === null) videoTimestampBase = frame.timestampMicros;\n const rtpTs = Math.floor(\n ((frame.timestampMicros - videoTimestampBase) * 90000) / 1_000_000,\n ) >>> 0;\n\n // Filter out AUD (type 9) and SEI (type 6) — not needed in RTP\n const nals = splitAnnexBToNals(annexB).filter((n: Buffer) => {\n const t = n[0]! & 0x1f;\n return t !== 9 && t !== 6;\n });\n if (nals.length > 0 && this.videoTrack) {\n this.writeVideoNals(nals, rtpTs, frame.codec);\n if (this.debug && frameCount % 250 === 0) {\n this.logger.info(\n `[session:${this.sessionId}] ${frameCount} frames, ${this.rtpPacketsSent} RTP pkts, ` +\n `ICE=${this.pc?.iceConnectionState}, conn=${this.pc?.connectionState}`,\n );\n }\n }\n } else if (mediaFrame.type === \"audio\") {\n const frame = mediaFrame.frame;\n if (!this.audioSender) continue;\n\n // For G.711 (PCMU/PCMA): timestamp increments by frame size (160 samples per 20ms)\n // Using a simple counter is more reliable than wall-clock based timestamps\n if (audioTimestampBase === null) audioTimestampBase = 0;\n audioTimestampBase = ((audioTimestampBase as number) + frame.data.length) >>> 0;\n const rtpTs = audioTimestampBase as number;\n\n this.writeAudio(frame.data, rtpTs, frame.codec);\n }\n }\n } catch (err) {\n if (!signal.aborted && !this.closed) {\n this.logger.error(`[session:${this.sessionId}] Feed error:`, err);\n }\n } finally {\n if (!this.closed) {\n this.logger.info(`[session:${this.sessionId}] Feed ended`);\n void this.close();\n }\n }\n })();\n }\n\n /** Build a serialized RTP packet for sender.sendRtp(). */\n private buildRtpBuffer(\n weriftModule: any,\n payload: Buffer,\n rtpTs: number,\n payloadType: number,\n marker: boolean,\n isVideo: boolean,\n ): Buffer {\n const header = new weriftModule.RtpHeader();\n header.payloadType = payloadType;\n header.timestamp = rtpTs;\n header.marker = marker;\n // CRITICAL: sequence number MUST increment per packet.\n // werift adds seqOffset but does NOT auto-increment — if all packets\n // have seq=0, SRTP replay protection drops them as duplicates.\n header.sequenceNumber = isVideo\n ? (this.videoSeqNum = (this.videoSeqNum + 1) & 0xffff)\n : (this.audioSeqNum = (this.audioSeqNum + 1) & 0xffff);\n const pkt = new weriftModule.RtpPacket(header, payload);\n return pkt.serialize();\n }\n\n /** Max RTP payload size (MTU 1200 to stay under typical network MTU). */\n private static readonly MAX_RTP_PAYLOAD = 1200;\n\n private rtpPacketsSent = 0;\n\n private writeVideoNals(nals: Buffer[], rtpTs: number, codec: VideoCodec): void {\n if (!this.videoSender || !_werift) return;\n const pt = codec === \"H264\" ? 96 : 97;\n\n const sendPkt = (payload: Buffer, marker: boolean) => {\n try {\n const buf = this.buildRtpBuffer(_werift, payload, rtpTs, pt, marker, true);\n this.videoSender.sendRtp(buf);\n this.rtpPacketsSent++;\n } catch (err) {\n if (this.rtpPacketsSent <= 10) {\n this.logger.error(`[session:${this.sessionId}] sendRtp error #${this.rtpPacketsSent}:`, err);\n }\n }\n };\n\n for (let i = 0; i < nals.length; i++) {\n const nal = nals[i]!;\n const isLastNal = i === nals.length - 1;\n\n if (nal.length <= AdaptiveSession.MAX_RTP_PAYLOAD) {\n sendPkt(nal, isLastNal);\n } else {\n // FU-A fragmentation (RFC 6184 §5.8)\n const nalHeader = nal[0]!;\n const fnri = nalHeader & 0xe0;\n const nalType = nalHeader & 0x1f;\n const fuIndicator = fnri | 28;\n const nalBody = nal.subarray(1);\n\n let offset = 0;\n let isFirst = true;\n while (offset < nalBody.length) {\n const end = Math.min(offset + AdaptiveSession.MAX_RTP_PAYLOAD - 2, nalBody.length);\n const isLast = end >= nalBody.length;\n\n let fuHeader = nalType;\n if (isFirst) fuHeader |= 0x80;\n if (isLast) fuHeader |= 0x40;\n\n const fragment = Buffer.alloc(2 + (end - offset));\n fragment[0] = fuIndicator;\n fragment[1] = fuHeader;\n nalBody.copy(fragment, 2, offset, end);\n\n sendPkt(fragment, isLastNal && isLast);\n offset = end;\n isFirst = false;\n }\n }\n }\n }\n\n private writeAudio(data: Buffer, rtpTs: number, codec?: string): void {\n if (!this.audioSender || !_werift) return;\n // Use correct payload type: PCMU=0, Opus=111\n const pt = codec === \"Pcmu\" || codec === \"Pcma\" ? 0 : 111;\n try {\n const buf = this.buildRtpBuffer(_werift, data, rtpTs, pt, true, false);\n this.audioSender.sendRtp(buf);\n } catch (err) {\n this.logger.debug(`[session:${this.sessionId}] Audio write error:`, err);\n }\n }\n\n // -----------------------------------------------------------------------\n // RTCP stats collection\n // -----------------------------------------------------------------------\n\n private startStatsCollection(): void {\n if (this.statsTimer || !this.onStats) return;\n\n this.statsTimer = setInterval(() => {\n if (!this.pc || this.closed) return;\n this.collectStats();\n }, 3_000);\n }\n\n private collectStats(): void {\n if (!this.pc || !this.onStats) return;\n\n try {\n // werift exposes getReceivers() and getSenders() on RTCPeerConnection.\n // For video sender stats, we check RTCP RR feedback from the receiver.\n const senders = this.pc.getSenders?.() ?? [];\n\n for (const sender of senders) {\n const track = sender.track;\n if (!track || track.kind !== \"video\") continue;\n\n // werift tracks RTCP receiver reports via sender.rtcpReceiveReport\n // or via the transport's RTCP event. The exact API depends on werift version.\n // Fallback: use werift's built-in stats if available.\n const report = sender.lastReceiverReport ?? sender.rtcpReport;\n if (!report) continue;\n\n const fractionLost = report.fractionLost ?? 0;\n const packetsLost = report.packetsLost ?? report.cumulativeLost ?? 0;\n const jitter = report.jitter ?? 0;\n const rtt = report.roundTripTime ?? report.rtt ?? 0;\n\n const packetLoss = fractionLost / 256; // Fraction lost is 0–255\n\n this.onStats({\n sessionId: this.sessionId,\n packetLoss,\n jitterMs: jitter,\n rttMs: rtt * 1000, // seconds → ms\n packetsReceived: 0, // Not available from sender side\n packetsLost,\n timestamp: Date.now(),\n });\n return; // One video sender is enough\n }\n\n // Fallback: no RTCP report available yet — emit zeros\n // (Client-reported stats will supplement this)\n } catch {\n // RTCP not yet available — normal during early connection\n }\n }\n}\n","/**\n * Adaptive RTSP relay — transcodes an RTSP source via ffmpeg and outputs\n * a local RTSP stream that can be registered in go2rtc for WebRTC delivery.\n *\n * This avoids werift's SRTP/DTLS issues by letting go2rtc handle the\n * browser-facing WebRTC connection while we control the transcoding.\n *\n * Pipeline: Camera RTSP → ffmpeg (adaptive params) → RTSP localhost → go2rtc → WHEP → Browser\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport type { Logger } from \"./types.js\";\nimport type { EncodingParams } from \"./ffmpeg-source.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface AdaptiveRtspRelayOptions {\n /** RTSP source URL (camera or upstream go2rtc). */\n rtspUrl: string;\n /** Local RTSP output URL (e.g. rtsp://127.0.0.1:8554/adaptive_ingresso_main). */\n rtspOutputUrl: string;\n /** Initial encoding parameters. */\n initialParams: EncodingParams;\n /** FFmpeg binary path (default: \"ffmpeg\"). */\n ffmpegPath?: string;\n /** Logger. */\n logger?: Logger;\n /** Label for logging. */\n label?: string;\n}\n\n// ---------------------------------------------------------------------------\n// AdaptiveRtspRelay\n// ---------------------------------------------------------------------------\n\nexport class AdaptiveRtspRelay {\n private readonly rtspUrl: string;\n private readonly rtspOutputUrl: string;\n private readonly ffmpegPath: string;\n private readonly logger: Logger | undefined;\n private readonly label: string;\n\n private currentParams: EncodingParams;\n private proc: ChildProcess | null = null;\n private closed = false;\n\n constructor(options: AdaptiveRtspRelayOptions) {\n this.rtspUrl = options.rtspUrl;\n this.rtspOutputUrl = options.rtspOutputUrl;\n this.ffmpegPath = options.ffmpegPath ?? \"ffmpeg\";\n this.logger = options.logger;\n this.label = options.label ?? \"adaptive-rtsp\";\n this.currentParams = { ...options.initialParams };\n }\n\n getParams(): Readonly<EncodingParams> {\n return { ...this.currentParams };\n }\n\n /** Start the ffmpeg relay. */\n start(): void {\n if (this.closed) return;\n this.spawnFfmpeg();\n }\n\n /** Hot-swap encoding parameters by restarting ffmpeg. */\n async updateParams(params: Partial<EncodingParams>): Promise<void> {\n const prev = { ...this.currentParams };\n if (params.maxBitrateKbps !== undefined) this.currentParams.maxBitrateKbps = params.maxBitrateKbps;\n if (params.width !== undefined) this.currentParams.width = params.width;\n if (params.height !== undefined) this.currentParams.height = params.height;\n if (params.preset !== undefined) this.currentParams.preset = params.preset;\n\n if (\n prev.maxBitrateKbps === this.currentParams.maxBitrateKbps &&\n prev.width === this.currentParams.width &&\n prev.height === this.currentParams.height\n ) return;\n\n this.logger?.info(\n `[${this.label}] Updating: ${prev.maxBitrateKbps}kbps ${prev.width}x${prev.height} → ` +\n `${this.currentParams.maxBitrateKbps}kbps ${this.currentParams.width}x${this.currentParams.height}`,\n );\n\n await this.killFfmpeg();\n if (!this.closed) this.spawnFfmpeg();\n }\n\n /** Stop the relay. */\n async stop(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n await this.killFfmpeg();\n }\n\n /** Check if ffmpeg is running. */\n isRunning(): boolean {\n return this.proc !== null;\n }\n\n // -----------------------------------------------------------------------\n // Private\n // -----------------------------------------------------------------------\n\n private spawnFfmpeg(): void {\n const { maxBitrateKbps, width, height, preset } = this.currentParams;\n\n const args: string[] = [\n \"-hide_banner\",\n \"-loglevel\", \"error\",\n // Input\n \"-rtsp_transport\", \"tcp\",\n \"-i\", this.rtspUrl,\n // Video encoding\n \"-c:v\", \"libx264\",\n \"-preset\", preset ?? \"ultrafast\",\n \"-tune\", \"zerolatency\",\n \"-crf\", \"28\",\n \"-maxrate\", `${maxBitrateKbps}k`,\n \"-bufsize\", `${Math.round(maxBitrateKbps * 0.5)}k`,\n \"-g\", \"50\",\n \"-keyint_min\", \"25\",\n ];\n\n if (width > 0 && height > 0) {\n args.push(\"-vf\", `scale=${width}:${height}`);\n }\n\n // Audio: copy or transcode to AAC\n args.push(\"-c:a\", \"aac\", \"-b:a\", \"64k\");\n\n // Output: RTSP over TCP to local server (go2rtc)\n args.push(\n \"-f\", \"rtsp\",\n \"-rtsp_transport\", \"tcp\",\n this.rtspOutputUrl,\n );\n\n this.proc = spawn(this.ffmpegPath, args, {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n });\n\n this.proc.on(\"error\", (err) => {\n this.logger?.error(`[${this.label}] ffmpeg spawn error: ${err.message}`);\n });\n\n this.proc.on(\"close\", (code, signal) => {\n this.logger?.debug(`[${this.label}] ffmpeg exited code=${code} signal=${signal}`);\n this.proc = null;\n if (!this.closed) {\n setTimeout(() => { if (!this.closed) this.spawnFfmpeg(); }, 2000);\n }\n });\n\n this.proc.stderr?.on(\"data\", (data: Buffer) => {\n const s = data.toString();\n if (s.includes(\"error\") || s.includes(\"Error\") || s.includes(\"fatal\")) {\n this.logger?.error(`[${this.label}] ffmpeg: ${s.trim()}`);\n }\n });\n\n this.logger?.info(\n `[${this.label}] Started: ${maxBitrateKbps}kbps ` +\n (width > 0 ? `${width}x${height}` : \"native\") +\n ` → ${this.rtspOutputUrl}`,\n );\n }\n\n private async killFfmpeg(): Promise<void> {\n const proc = this.proc;\n if (!proc) return;\n this.proc = null;\n try { proc.kill(\"SIGTERM\"); } catch { /* */ }\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n try { proc.kill(\"SIGKILL\"); } catch { /* */ }\n resolve();\n }, 3000);\n proc.on(\"close\", () => { clearTimeout(timer); resolve(); });\n });\n }\n}\n","/**\n * SharedSession — single RTCPeerConnection with multiple dynamic video tracks.\n *\n * One connection per client. Tracks are added/removed via data channel messages.\n * Each track corresponds to one camera's ffmpeg source.\n *\n * Protocol (data channel JSON messages):\n *\n * Client → Server:\n * { type: \"addTrack\", cameraName: string, trackId: string }\n * { type: \"removeTrack\", trackId: string }\n * { type: \"answer\", sdp: string }\n *\n * Server → Client:\n * { type: \"offer\", sdp: string }\n * { type: \"trackReady\", trackId: string, mid: string }\n * { type: \"trackRemoved\", trackId: string }\n * { type: \"error\", message: string }\n */\n\nimport type { FrameSource, Logger, VideoCodec, MediaFrame } from \"./types.js\";\nimport { splitAnnexBToNals } from \"./nal-utils.js\";\nimport { convertH264ToAnnexB, isH264IdrAccessUnit } from \"./h264-utils.js\";\n\ntype WeriftPC = any;\ntype WeriftTrack = any;\n\nlet _werift: any | undefined;\n\nasync function loadWerift(): Promise<any> {\n if (_werift) return _werift;\n const moduleName = \"werift\";\n _werift = await (Function(\"m\", \"return import(m)\")(moduleName) as Promise<any>);\n return _werift;\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SharedSessionOptions {\n iceConfig?: {\n stunServers?: string[];\n turnServers?: Array<{ urls: string; username?: string; credential?: string }>;\n portRange?: [number, number];\n additionalHostAddresses?: string[];\n };\n /** Called when the server needs to attach a camera source for a track. */\n onTrackRequested: (cameraName: string) => FrameSource | null;\n /** Called when a track is removed. */\n onTrackReleased?: (cameraName: string) => void;\n /** Called when intercom audio is received from the client for a camera. */\n onIntercomAudio?: (cameraName: string, data: Buffer) => void;\n logger: Logger;\n}\n\ninterface ActiveTrack {\n trackId: string;\n cameraName: string;\n videoSender: any; // werift RTCRtpSender\n audioSender: any; // werift RTCRtpSender\n feedAbort: AbortController;\n videoSeqNum: number;\n audioSeqNum: number;\n}\n\n// ---------------------------------------------------------------------------\n// SharedSession\n// ---------------------------------------------------------------------------\n\nexport class SharedSession {\n private readonly logger: Logger;\n private readonly iceConfig: SharedSessionOptions[\"iceConfig\"];\n private readonly onTrackRequested: SharedSessionOptions[\"onTrackRequested\"];\n private readonly onTrackReleased: SharedSessionOptions[\"onTrackReleased\"];\n\n private pc: WeriftPC | null = null;\n private dataChannel: any = null;\n private readonly activeTracks = new Map<string, ActiveTrack>();\n private closed = false;\n private negotiating = false;\n\n constructor(private readonly options: SharedSessionOptions) {\n this.logger = options.logger;\n this.iceConfig = options.iceConfig;\n this.onTrackRequested = options.onTrackRequested;\n this.onTrackReleased = options.onTrackReleased;\n }\n\n /** Create the initial SDP offer (with data channel, no media tracks yet). */\n async createOffer(): Promise<string> {\n const werift = await loadWerift();\n\n const iceServers: any[] = [];\n if (this.iceConfig?.stunServers) {\n for (const url of this.iceConfig.stunServers) iceServers.push({ urls: url });\n }\n if (this.iceConfig?.turnServers) {\n for (const t of this.iceConfig.turnServers) {\n iceServers.push({ urls: t.urls, username: t.username, credential: t.credential });\n }\n }\n\n const pcOptions: any = {\n codecs: {\n video: [\n new werift.RTCRtpCodecParameters({\n mimeType: \"video/H264\",\n clockRate: 90000,\n payloadType: 96,\n parameters: \"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\",\n rtcpFeedback: [\n { type: \"transport-cc\" },\n { type: \"ccm\", parameter: \"fir\" },\n { type: \"nack\" },\n { type: \"nack\", parameter: \"pli\" },\n { type: \"goog-remb\" },\n ],\n }),\n ],\n audio: [\n new werift.RTCRtpCodecParameters({\n mimeType: \"audio/opus\",\n clockRate: 48000,\n payloadType: 111,\n channels: 2,\n parameters: \"minptime=10;useinbandfec=1\",\n }),\n ],\n },\n };\n if (iceServers.length > 0) pcOptions.iceServers = iceServers;\n if (this.iceConfig?.portRange) pcOptions.icePortRange = this.iceConfig.portRange;\n if (this.iceConfig?.additionalHostAddresses) {\n pcOptions.iceAdditionalHostAddresses = this.iceConfig.additionalHostAddresses;\n }\n\n this.pc = new werift.RTCPeerConnection(pcOptions);\n\n this.pc.iceConnectionStateChange.subscribe((state: string) => {\n this.logger.debug(`[shared] ICE: ${state}`);\n });\n\n // Create data channel for in-band signaling\n this.dataChannel = this.pc.createDataChannel(\"control\", { ordered: true });\n this.dataChannel.message.subscribe((msg: any) => {\n try {\n const data = JSON.parse(typeof msg === \"string\" ? msg : msg.toString());\n this.handleDataChannelMessage(data);\n } catch (err) {\n this.logger.error(\"[shared] DC message parse error:\", err);\n }\n });\n\n // Create initial offer (data channel only, no media tracks)\n const offer = await this.pc.createOffer();\n await this.pc.setLocalDescription(offer);\n\n // Wait for ICE gathering\n await new Promise<void>((resolve) => {\n if (this.pc.iceGatheringState === \"complete\") { resolve(); return; }\n this.pc.iceGatheringStateChange.subscribe((state: string) => {\n if (state === \"complete\") resolve();\n });\n setTimeout(resolve, 5000);\n });\n\n const sdp = this.pc.localDescription?.sdp ?? offer.sdp;\n this.logger.info(\"[shared] Initial offer created (data channel only)\");\n return sdp;\n }\n\n /** Handle the client's SDP answer. */\n async handleAnswer(sdpAnswer: string): Promise<void> {\n const werift = await loadWerift();\n const desc = new werift.RTCSessionDescription(sdpAnswer, \"answer\");\n await this.pc.setRemoteDescription(desc);\n this.logger.info(\"[shared] Answer set, connection ready\");\n }\n\n /** Close the shared session and all tracks. */\n async close(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n\n for (const [, track] of this.activeTracks) {\n track.feedAbort.abort();\n this.onTrackReleased?.(track.cameraName);\n }\n this.activeTracks.clear();\n\n if (this.pc) {\n try { await this.pc.close(); } catch { /* */ }\n this.pc = null;\n }\n this.logger.info(\"[shared] Session closed\");\n }\n\n // -----------------------------------------------------------------------\n // Data channel message handling\n // -----------------------------------------------------------------------\n\n private async handleDataChannelMessage(msg: any): Promise<void> {\n try {\n switch (msg.type) {\n case \"addTrack\":\n await this.handleAddTrack(msg.cameraName, msg.trackId);\n break;\n case \"removeTrack\":\n await this.handleRemoveTrack(msg.trackId);\n break;\n case \"answer\":\n await this.handleRenegotiationAnswer(msg.sdp);\n break;\n default:\n this.logger.warn(\"[shared] Unknown DC message type:\", msg.type);\n }\n } catch (err) {\n this.logger.error(\"[shared] DC handler error:\", err);\n this.sendDC({ type: \"error\", message: (err as Error).message });\n }\n }\n\n private async handleAddTrack(cameraName: string, trackId: string): Promise<void> {\n if (this.activeTracks.has(trackId)) {\n this.sendDC({ type: \"error\", message: `Track ${trackId} already exists` });\n return;\n }\n\n const werift = await loadWerift();\n\n // Create video + audio transceivers for this camera\n const videoTrack = new werift.MediaStreamTrack({ kind: \"video\" });\n const videoTransceiver = this.pc.addTransceiver(videoTrack, { direction: \"sendonly\" });\n\n const audioTrack = new werift.MediaStreamTrack({ kind: \"audio\" });\n const audioDirection = this.options.onIntercomAudio ? \"sendrecv\" : \"sendonly\";\n const audioTransceiver = this.pc.addTransceiver(audioTrack, { direction: audioDirection });\n\n // Listen for intercom audio from client\n if (this.options.onIntercomAudio) {\n const intercomCb = this.options.onIntercomAudio;\n audioTransceiver.onTrack.subscribe((incomingTrack: any) => {\n incomingTrack.onReceiveRtp.subscribe((rtpPacket: any) => {\n const payload = rtpPacket.payload as Buffer;\n if (payload?.length > 0) {\n intercomCb(cameraName, payload);\n }\n });\n });\n }\n\n // Renegotiate to include the new tracks\n await this.renegotiate();\n\n const videoMid = videoTransceiver.mid;\n const audioMid = audioTransceiver.mid;\n\n // Get the frame source for this camera\n const source = this.onTrackRequested(cameraName);\n if (!source) {\n this.sendDC({ type: \"error\", message: `Camera not found: ${cameraName}` });\n return;\n }\n\n // Start feeding frames\n const feedAbort = new AbortController();\n const activeTrack: ActiveTrack = {\n trackId,\n cameraName,\n videoSender: videoTransceiver.sender,\n audioSender: audioTransceiver.sender,\n feedAbort,\n videoSeqNum: 0,\n audioSeqNum: 0,\n };\n this.activeTracks.set(trackId, activeTrack);\n\n this.sendDC({ type: \"trackReady\", trackId, videoMid, audioMid });\n\n // Start feeding in background\n this.startFeeding(activeTrack, source);\n\n this.logger.info(`[shared] Track \"${trackId}\" added for camera \"${cameraName}\" (video=${videoMid}, audio=${audioMid})`);\n }\n\n private async handleRemoveTrack(trackId: string): Promise<void> {\n const track = this.activeTracks.get(trackId);\n if (!track) return;\n\n track.feedAbort.abort();\n this.activeTracks.delete(trackId);\n this.onTrackReleased?.(track.cameraName);\n\n // Renegotiate to remove the track\n // Note: werift doesn't support removeTrack directly, so we stop sending\n // The transceiver stays but with direction inactive\n await this.renegotiate();\n\n this.sendDC({ type: \"trackRemoved\", trackId });\n this.logger.info(`[shared] Track \"${trackId}\" removed`);\n }\n\n private async handleRenegotiationAnswer(sdp: string): Promise<void> {\n const werift = await loadWerift();\n const desc = new werift.RTCSessionDescription(sdp, \"answer\");\n await this.pc.setRemoteDescription(desc);\n this.negotiating = false;\n this.logger.debug(\"[shared] Renegotiation answer set\");\n }\n\n // -----------------------------------------------------------------------\n // SDP renegotiation\n // -----------------------------------------------------------------------\n\n private async renegotiate(): Promise<void> {\n if (!this.pc || !this.dataChannel) return;\n\n this.negotiating = true;\n const offer = await this.pc.createOffer();\n await this.pc.setLocalDescription(offer);\n\n // Wait for ICE gathering\n await new Promise<void>((resolve) => {\n if (this.pc.iceGatheringState === \"complete\") { resolve(); return; }\n this.pc.iceGatheringStateChange.subscribe((state: string) => {\n if (state === \"complete\") resolve();\n });\n setTimeout(resolve, 3000);\n });\n\n const sdp = this.pc.localDescription?.sdp ?? offer.sdp;\n this.sendDC({ type: \"offer\", sdp });\n\n // Wait for the client to send an answer\n await new Promise<void>((resolve) => {\n const check = setInterval(() => {\n if (!this.negotiating) { clearInterval(check); resolve(); }\n }, 50);\n setTimeout(() => { clearInterval(check); resolve(); }, 10000);\n });\n }\n\n // -----------------------------------------------------------------------\n // Frame feeding\n // -----------------------------------------------------------------------\n\n private startFeeding(track: ActiveTrack, source: FrameSource): void {\n const { signal } = track.feedAbort;\n const werift = _werift;\n if (!werift) return;\n\n void (async () => {\n let gotKeyframe = false;\n let videoTimestampBase: number | null = null;\n let audioTimestampBase: number | null = null;\n\n try {\n for await (const mediaFrame of source) {\n if (signal.aborted || this.closed) break;\n\n // Audio frames\n if (mediaFrame.type === \"audio\") {\n const frame = mediaFrame.frame;\n if (audioTimestampBase === null) audioTimestampBase = frame.timestampMicros;\n const rtpTs = Math.floor(\n ((frame.timestampMicros - audioTimestampBase) * (frame.sampleRate || 48000)) / 1_000_000,\n ) >>> 0;\n track.audioSeqNum = (track.audioSeqNum + 1) & 0xffff;\n const header = new werift.RtpHeader();\n header.payloadType = 111;\n header.timestamp = rtpTs;\n header.marker = true;\n header.sequenceNumber = track.audioSeqNum;\n const pkt = new werift.RtpPacket(header, frame.data);\n try { track.audioSender.sendRtp(pkt.serialize()); } catch { /* */ }\n continue;\n }\n\n if (mediaFrame.type !== \"video\") continue;\n\n const frame = mediaFrame.frame;\n const annexB = convertH264ToAnnexB(frame.data);\n\n if (!gotKeyframe) {\n if (!isH264IdrAccessUnit(annexB)) continue;\n gotKeyframe = true;\n }\n\n if (videoTimestampBase === null) videoTimestampBase = frame.timestampMicros;\n const rtpTs = Math.floor(\n ((frame.timestampMicros - videoTimestampBase) * 90000) / 1_000_000,\n ) >>> 0;\n\n // Filter AUD and SEI\n const nals = splitAnnexBToNals(annexB).filter((n: Buffer) => {\n const t = n[0]! & 0x1f;\n return t !== 9 && t !== 6;\n });\n\n // Send NALs with FU-A fragmentation\n for (let i = 0; i < nals.length; i++) {\n const nal = nals[i]!;\n const isLastNal = i === nals.length - 1;\n\n if (nal.length <= 1200) {\n track.videoSeqNum = (track.videoSeqNum + 1) & 0xffff;\n const header = new werift.RtpHeader();\n header.payloadType = 96;\n header.timestamp = rtpTs;\n header.marker = isLastNal;\n header.sequenceNumber = track.videoSeqNum;\n const pkt = new werift.RtpPacket(header, nal);\n try { track.videoSender.sendRtp(pkt.serialize()); } catch { /* */ }\n } else {\n const nalHeader = nal[0]!;\n const fnri = nalHeader & 0xe0;\n const nalType = nalHeader & 0x1f;\n const fuIndicator = fnri | 28;\n const nalBody = nal.subarray(1);\n let offset = 0;\n let isFirst = true;\n while (offset < nalBody.length) {\n const end = Math.min(offset + 1198, nalBody.length);\n const isLast = end >= nalBody.length;\n let fuHeader = nalType;\n if (isFirst) fuHeader |= 0x80;\n if (isLast) fuHeader |= 0x40;\n const frag = Buffer.alloc(2 + (end - offset));\n frag[0] = fuIndicator;\n frag[1] = fuHeader;\n nalBody.copy(frag, 2, offset, end);\n\n track.videoSeqNum = (track.videoSeqNum + 1) & 0xffff;\n const header = new werift.RtpHeader();\n header.payloadType = 96;\n header.timestamp = rtpTs;\n header.marker = isLastNal && isLast;\n header.sequenceNumber = track.videoSeqNum;\n const pkt = new werift.RtpPacket(header, frag);\n try { track.videoSender.sendRtp(pkt.serialize()); } catch { /* */ }\n\n offset = end;\n isFirst = false;\n }\n }\n }\n }\n } catch (err) {\n if (!signal.aborted) {\n this.logger.error(`[shared] Feed error for track \"${track.trackId}\":`, err);\n }\n }\n })();\n }\n\n // -----------------------------------------------------------------------\n // Helpers\n // -----------------------------------------------------------------------\n\n private sendDC(msg: object): void {\n if (this.dataChannel?.readyState === \"open\") {\n this.dataChannel.send(JSON.stringify(msg));\n }\n }\n\n get isConnected(): boolean {\n return this.pc?.iceConnectionState === \"connected\" && !this.closed;\n }\n\n get trackCount(): number {\n return this.activeTracks.size;\n }\n}\n","/**\n * Adaptive WebRTC streaming server — multi-camera manager with quality adaptation.\n *\n * For each camera:\n * - Spawns AdaptiveFfmpegSource to transcode RTSP → H.264 with configurable params\n * - Uses StreamFanout to distribute frames to multiple viewer sessions\n * - AdaptiveController monitors stats and adjusts quality per-camera\n *\n * Usage:\n * const server = new AdaptiveStreamServer({ ... });\n * server.addCamera(\"front_door\", { rtspUrl: \"rtsp://...\", profiles: [...] });\n * const { sessionId, answer } = await server.handleWhepOffer(\"front_door\", sdpOffer);\n */\n\nimport crypto from \"node:crypto\";\nimport { EventEmitter } from \"node:events\";\nimport type { FrameSource, FrameSourceFactory, Logger, MediaFrame } from \"./types.js\";\nimport { asLogger, createNullLogger } from \"./types.js\";\nimport { StreamFanout } from \"./fanout.js\";\nimport { AdaptiveFfmpegSource, type EncodingParams } from \"./ffmpeg-source.js\";\nimport {\n AdaptiveController,\n type QualityProfile,\n type QualityTier,\n type StreamStats,\n} from \"./adaptive-controller.js\";\nimport { AdaptiveSession, type SessionInfo, type SessionStats } from \"./session.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface AdaptiveStreamServerOptions {\n /** FFmpeg binary path (default: \"ffmpeg\"). */\n ffmpegPath?: string;\n /** STUN servers for ICE. */\n stunServers?: string[];\n /** TURN servers for ICE. */\n turnServers?: Array<{ urls: string; username?: string; credential?: string }>;\n /** ICE port range. */\n icePortRange?: [number, number];\n /** Additional host IPs to announce. */\n iceAdditionalHostAddresses?: string[];\n /** Logger. */\n logger?: Logger;\n}\n\nexport interface CameraConfig {\n /** Primary RTSP source URL (typically main stream). */\n rtspUrl: string;\n /** Secondary RTSP source URL (typically sub stream, for Level 3 switching). */\n subRtspUrl?: string;\n /** Quality profiles ordered from highest to lowest quality. */\n profiles: QualityProfile[];\n /** Audio mode: \"copy\" (G.711 passthrough), \"opus\" (transcode), \"off\" (no audio). Default: \"copy\". */\n audioMode?: \"copy\" | \"opus\" | \"off\";\n}\n\n/** Per-camera internal state. */\ninterface CameraState {\n config: CameraConfig;\n /** Main stream ffmpeg source + fanout. */\n mainFfmpegSource: AdaptiveFfmpegSource;\n mainFanout: StreamFanout<MediaFrame>;\n /** Sub stream ffmpeg source + fanout (created lazily on first source switch). */\n subFfmpegSource: AdaptiveFfmpegSource | null;\n subFanout: StreamFanout<MediaFrame> | null;\n /** Which source profile is currently active (\"main\" or \"sub\"). */\n activeSourceProfile: \"main\" | \"sub\";\n controller: AdaptiveController;\n sessions: Map<string, AdaptiveSession>;\n autoStopTimer: ReturnType<typeof setTimeout> | null;\n /** Guard against concurrent source switches. */\n switching: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Default quality profiles\n// ---------------------------------------------------------------------------\n\n/** Default quality profiles for adaptive streaming. */\nexport function createDefaultProfiles(): QualityProfile[] {\n return [\n {\n tier: \"high\",\n encoding: { maxBitrateKbps: 6000, width: 0, height: 0 }, // native resolution\n sourceProfile: \"main\",\n },\n {\n tier: \"medium\",\n encoding: { maxBitrateKbps: 2500, width: 1280, height: 720 },\n sourceProfile: \"main\",\n },\n {\n tier: \"low\",\n encoding: { maxBitrateKbps: 1000, width: 640, height: 360 },\n sourceProfile: \"sub\",\n },\n ];\n}\n\n// ---------------------------------------------------------------------------\n// AdaptiveStreamServer\n// ---------------------------------------------------------------------------\n\nexport class AdaptiveStreamServer extends EventEmitter {\n private readonly ffmpegPath: string;\n private readonly stunServers: string[] | undefined;\n private readonly turnServers: AdaptiveStreamServerOptions[\"turnServers\"];\n private readonly icePortRange: [number, number] | undefined;\n private readonly iceAdditionalHostAddresses: string[] | undefined;\n private readonly logger: Logger;\n\n private readonly cameras = new Map<string, CameraState>();\n private readonly sessionCamera = new Map<string, string>();\n private stopped = false;\n\n constructor(options: AdaptiveStreamServerOptions = {}) {\n super();\n this.ffmpegPath = options.ffmpegPath ?? \"ffmpeg\";\n this.stunServers = options.stunServers;\n this.turnServers = options.turnServers;\n this.icePortRange = options.icePortRange;\n this.iceAdditionalHostAddresses = options.iceAdditionalHostAddresses;\n this.logger = options.logger ? asLogger(options.logger) : createNullLogger();\n this.logger.info(\"[adaptive-server] Initialized\");\n }\n\n // -----------------------------------------------------------------------\n // Camera management\n // -----------------------------------------------------------------------\n\n /** Register a camera with adaptive streaming. */\n addCamera(name: string, config: CameraConfig): void {\n if (this.cameras.has(name)) {\n this.logger.warn(`[adaptive-server] Camera \"${name}\" already registered`);\n return;\n }\n\n const profiles = config.profiles;\n const initialParams = profiles[0]!.encoding;\n\n const mainFfmpegSource = new AdaptiveFfmpegSource({\n rtspUrl: config.rtspUrl,\n initialParams,\n audioMode: config.audioMode ?? \"copy\",\n ffmpegPath: this.ffmpegPath,\n logger: this.logger,\n label: `ffmpeg:${name}:main`,\n });\n\n // Create fanout from main ffmpeg source\n const mainFanout = new StreamFanout<MediaFrame>({\n maxQueueItems: 30,\n createSource: () => mainFfmpegSource.source,\n onError: (err) => {\n this.logger.error(`[adaptive-server] Main fanout error (${name}):`, err);\n },\n });\n\n // Create adaptive controller\n const controller = new AdaptiveController({\n profiles,\n onQualityChange: async (from, to) => {\n await this.handleQualityChange(name, from, to);\n },\n logger: this.logger,\n });\n\n this.cameras.set(name, {\n config,\n mainFfmpegSource,\n mainFanout,\n subFfmpegSource: null,\n subFanout: null,\n activeSourceProfile: \"main\",\n controller,\n sessions: new Map(),\n autoStopTimer: null,\n switching: false,\n });\n\n this.logger.info(`[adaptive-server] Camera \"${name}\" added`);\n }\n\n /** Remove a camera and close all its sessions. */\n async removeCamera(name: string): Promise<void> {\n const cam = this.cameras.get(name);\n if (!cam) return;\n\n // Close all sessions\n const closePs: Promise<void>[] = [];\n for (const [sid, session] of cam.sessions) {\n this.sessionCamera.delete(sid);\n closePs.push(session.close().catch(() => {}));\n }\n await Promise.all(closePs);\n cam.sessions.clear();\n\n // Stop main resources\n await cam.mainFanout.stop();\n await cam.mainFfmpegSource.stop();\n\n // Stop sub resources if they exist\n if (cam.subFanout) await cam.subFanout.stop();\n if (cam.subFfmpegSource) await cam.subFfmpegSource.stop();\n\n if (cam.autoStopTimer) {\n clearTimeout(cam.autoStopTimer);\n cam.autoStopTimer = null;\n }\n\n this.cameras.delete(name);\n this.logger.info(`[adaptive-server] Camera \"${name}\" removed`);\n }\n\n getCameraNames(): string[] {\n return [...this.cameras.keys()];\n }\n\n // -----------------------------------------------------------------------\n // Signaling (2-step: server creates offer, client sends answer)\n // -----------------------------------------------------------------------\n\n /**\n * Create an adaptive session for a camera.\n * Returns a server-generated SDP offer that the client must answer.\n *\n * Flow: createSession() → server offer → client sets remote, creates answer → handleAnswer()\n */\n async createSession(\n cameraName: string,\n ): Promise<{ sessionId: string; sdpOffer: string }> {\n if (this.stopped) throw new Error(\"Server stopped\");\n\n const cam = this.cameras.get(cameraName);\n if (!cam) throw new Error(`Camera not found: ${cameraName}`);\n\n if (cam.autoStopTimer) {\n clearTimeout(cam.autoStopTimer);\n cam.autoStopTimer = null;\n }\n\n this.ensureCameraRunning(cameraName, cam);\n\n const sessionId = crypto.randomUUID();\n const activeFanout = this.getActiveFanout(cam);\n const source = activeFanout.subscribe(sessionId);\n\n const session = new AdaptiveSession({\n sessionId,\n source,\n iceConfig: {\n stunServers: this.stunServers,\n turnServers: this.turnServers,\n portRange: this.icePortRange,\n additionalHostAddresses: this.iceAdditionalHostAddresses,\n },\n onStats: (stats: SessionStats) => {\n cam.controller.reportStats(sessionId, {\n packetLoss: stats.packetLoss,\n jitterMs: stats.jitterMs,\n rttMs: stats.rttMs,\n timestamp: stats.timestamp,\n });\n this.emit(\"session:stats\", { camera: cameraName, ...stats });\n },\n logger: this.logger,\n });\n\n cam.sessions.set(sessionId, session);\n this.sessionCamera.set(sessionId, cameraName);\n\n try {\n const offer = await session.createOffer();\n this.emit(\"session:created\", { sessionId, camera: cameraName });\n return { sessionId, sdpOffer: offer.sdp };\n } catch (err) {\n cam.sessions.delete(sessionId);\n this.sessionCamera.delete(sessionId);\n activeFanout.unsubscribe(sessionId);\n await session.close().catch(() => {});\n this.scheduleCameraAutoStop(cameraName, cam);\n throw err;\n }\n }\n\n /**\n * Handle the client's SDP answer for an adaptive session.\n * Call after createSession() with the client's answer.\n */\n async handleAnswer(sessionId: string, sdpAnswer: string): Promise<void> {\n const camName = this.sessionCamera.get(sessionId);\n if (!camName) throw new Error(`Session not found: ${sessionId}`);\n\n const cam = this.cameras.get(camName);\n if (!cam) throw new Error(`Camera not found: ${camName}`);\n\n const session = cam.sessions.get(sessionId);\n if (!session) throw new Error(`Session not found: ${sessionId}`);\n\n await session.handleAnswer({ sdp: sdpAnswer, type: \"answer\" });\n }\n\n /**\n * Convenience: handleWhepOffer is NOT supported — werift requires server-initiated offers.\n * Use createSession() + handleAnswer() instead.\n */\n async handleWhepOffer(\n _cameraName: string,\n _sdpOffer: string,\n ): Promise<{ sessionId: string; sdpAnswer: string }> {\n throw new Error(\n \"handleWhepOffer is not supported — werift requires server-initiated offers. \" +\n \"Use createSession() to get a server offer, then handleAnswer() with the client's answer.\",\n );\n }\n\n // -----------------------------------------------------------------------\n // Connection pool: pre-warmed sessions without camera assignment\n // -----------------------------------------------------------------------\n\n /** Pooled sessions: sessionId → true (idle, no camera attached). */\n private readonly pooledSessions = new Set<string>();\n\n /**\n * Create a pooled session (no camera attached yet).\n * The SDP exchange happens, ICE connects, but no ffmpeg is started.\n * Call attachCamera() later to start feeding frames.\n */\n async createPooledSession(): Promise<{ sessionId: string; sdpOffer: string }> {\n if (this.stopped) throw new Error(\"Server stopped\");\n\n const sessionId = crypto.randomUUID();\n\n // Create a session with an empty source (never yields frames)\n const emptySource: FrameSource = (async function* () {\n // Idle: will be replaced when a camera is attached\n await new Promise(() => {}); // Suspend forever\n })();\n\n const session = new AdaptiveSession({\n sessionId,\n source: emptySource,\n iceConfig: {\n stunServers: this.stunServers,\n turnServers: this.turnServers,\n portRange: this.icePortRange,\n additionalHostAddresses: this.iceAdditionalHostAddresses,\n },\n logger: this.logger,\n });\n\n // Store without camera association\n this.pooledSessions.add(sessionId);\n\n // We need a temp camera state to store the session — use a virtual pool camera\n const poolCamKey = \"__pool__\";\n if (!this.cameras.has(poolCamKey)) {\n // Create a virtual camera for pool management\n const dummyFfmpeg = new AdaptiveFfmpegSource({\n rtspUrl: \"rtsp://0.0.0.0/dummy\",\n initialParams: { maxBitrateKbps: 0, width: 0, height: 0 },\n });\n const dummyFanout = new StreamFanout<MediaFrame>({\n maxQueueItems: 1,\n createSource: () => dummyFfmpeg.source,\n });\n const dummyController = new AdaptiveController({\n profiles: createDefaultProfiles(),\n onQualityChange: async () => {},\n });\n this.cameras.set(poolCamKey, {\n config: { rtspUrl: \"\", profiles: createDefaultProfiles() },\n mainFfmpegSource: dummyFfmpeg,\n mainFanout: dummyFanout,\n subFfmpegSource: null,\n subFanout: null,\n activeSourceProfile: \"main\",\n controller: dummyController,\n sessions: new Map(),\n autoStopTimer: null,\n switching: false,\n });\n }\n\n const poolCam = this.cameras.get(poolCamKey)!;\n poolCam.sessions.set(sessionId, session);\n this.sessionCamera.set(sessionId, poolCamKey);\n\n try {\n const offer = await session.createOffer();\n this.logger.info(`[adaptive-server] Pooled session ${sessionId.slice(0, 8)} created`);\n return { sessionId, sdpOffer: offer.sdp };\n } catch (err) {\n poolCam.sessions.delete(sessionId);\n this.sessionCamera.delete(sessionId);\n this.pooledSessions.delete(sessionId);\n await session.close().catch(() => {});\n throw err;\n }\n }\n\n /**\n * Attach a camera to a pooled session.\n * Starts the ffmpeg transcoder and begins feeding frames.\n */\n async attachCamera(sessionId: string, cameraName: string): Promise<void> {\n if (!this.pooledSessions.has(sessionId)) {\n throw new Error(`Session ${sessionId} is not a pooled session`);\n }\n\n const cam = this.cameras.get(cameraName);\n if (!cam) throw new Error(`Camera not found: ${cameraName}`);\n\n // Ensure camera is running\n this.ensureCameraRunning(cameraName, cam);\n\n // Move session from pool to camera\n const poolCam = this.cameras.get(\"__pool__\");\n const session = poolCam?.sessions.get(sessionId);\n if (!session) throw new Error(`Pooled session not found: ${sessionId}`);\n\n poolCam!.sessions.delete(sessionId);\n cam.sessions.set(sessionId, session);\n this.sessionCamera.set(sessionId, cameraName);\n this.pooledSessions.delete(sessionId);\n\n // Subscribe to camera fanout and start feeding\n const activeFanout = this.getActiveFanout(cam);\n const source = activeFanout.subscribe(sessionId);\n session.replaceSource(source);\n\n this.logger.info(`[adaptive-server] Attached camera \"${cameraName}\" to session ${sessionId.slice(0, 8)}`);\n }\n\n /**\n * Detach a camera from a session (session returns to pool).\n */\n async detachCamera(sessionId: string): Promise<void> {\n const camName = this.sessionCamera.get(sessionId);\n if (!camName || camName === \"__pool__\") return;\n\n const cam = this.cameras.get(camName);\n if (!cam) return;\n\n const session = cam.sessions.get(sessionId);\n if (!session) return;\n\n // Stop feeding\n session.detachSource();\n\n // Unsubscribe from fanout\n const activeFanout = this.getActiveFanout(cam);\n activeFanout.unsubscribe(sessionId);\n\n // Move back to pool\n cam.sessions.delete(sessionId);\n const poolCam = this.cameras.get(\"__pool__\");\n if (poolCam) {\n poolCam.sessions.set(sessionId, session);\n this.sessionCamera.set(sessionId, \"__pool__\");\n this.pooledSessions.add(sessionId);\n }\n\n this.logger.info(`[adaptive-server] Detached camera \"${camName}\" from session ${sessionId.slice(0, 8)} (back to pool)`);\n this.scheduleCameraAutoStop(camName, cam);\n }\n\n /** Check if a session is in the idle pool. */\n isPooledSession(sessionId: string): boolean {\n return this.pooledSessions.has(sessionId);\n }\n\n /** Set debug flag on all sessions for a camera. */\n setDebug(cameraName: string, debug: boolean): number {\n const cam = this.cameras.get(cameraName);\n if (!cam) return 0;\n let count = 0;\n for (const session of cam.sessions.values()) {\n (session as any).debug = debug;\n count++;\n }\n return count;\n }\n\n /** Get count of idle pooled sessions. */\n getPoolSize(): number {\n return this.pooledSessions.size;\n }\n\n // -----------------------------------------------------------------------\n // Session management\n // -----------------------------------------------------------------------\n\n /** Close a specific session. */\n async closeSession(sessionId: string): Promise<void> {\n const camName = this.sessionCamera.get(sessionId);\n if (!camName) return;\n\n const cam = this.cameras.get(camName);\n if (!cam) return;\n\n const session = cam.sessions.get(sessionId);\n if (!session) return;\n\n cam.sessions.delete(sessionId);\n this.sessionCamera.delete(sessionId);\n // Unsubscribe from whichever fanout is active\n const activeFanout = this.getActiveFanout(cam);\n activeFanout.unsubscribe(sessionId);\n cam.controller.removeSession(sessionId);\n await session.close();\n\n this.logger.info(`[adaptive-server] Session ${sessionId} closed (camera \"${camName}\", remaining: ${cam.sessions.size})`);\n this.emit(\"session:closed\", { sessionId, camera: camName });\n this.scheduleCameraAutoStop(camName, cam);\n }\n\n /**\n * Report client-side stats for a session (supplements RTCP monitoring).\n * Call from tRPC route when the client pushes stats.\n */\n reportClientStats(sessionId: string, stats: StreamStats): {\n currentTier: QualityTier;\n currentBitrateKbps: number;\n currentResolution: { width: number; height: number };\n sourceProfile: \"main\" | \"sub\";\n } | null {\n const camName = this.sessionCamera.get(sessionId);\n if (!camName) return null;\n\n const cam = this.cameras.get(camName);\n if (!cam) return null;\n\n cam.controller.reportStats(sessionId, stats);\n\n const profile = cam.controller.currentProfile;\n return {\n currentTier: profile.tier,\n currentBitrateKbps: profile.encoding.maxBitrateKbps,\n currentResolution: { width: profile.encoding.width, height: profile.encoding.height },\n sourceProfile: cam.activeSourceProfile,\n };\n }\n\n /** Force quality for a camera (null = auto). */\n forceQuality(cameraName: string, tier: QualityTier | null): boolean {\n const cam = this.cameras.get(cameraName);\n if (!cam) return false;\n cam.controller.forceQuality(tier);\n return true;\n }\n\n /** Get current quality info for a camera. */\n getCameraQuality(cameraName: string): {\n tier: QualityTier;\n encoding: EncodingParams;\n isAuto: boolean;\n stats: { packetLoss: number; jitterMs: number; rttMs: number };\n sessionCount: number;\n sourceProfile: \"main\" | \"sub\";\n } | null {\n const cam = this.cameras.get(cameraName);\n if (!cam) return null;\n\n const profile = cam.controller.currentProfile;\n return {\n tier: profile.tier,\n encoding: profile.encoding,\n isAuto: cam.controller.isAuto,\n stats: cam.controller.getAggregatedStats(),\n sessionCount: cam.sessions.size,\n sourceProfile: cam.activeSourceProfile,\n };\n }\n\n /** Get all sessions. */\n getSessions(cameraName?: string): SessionInfo[] {\n const infos: SessionInfo[] = [];\n if (cameraName) {\n const cam = this.cameras.get(cameraName);\n if (cam) {\n for (const s of cam.sessions.values()) infos.push(s.getInfo());\n }\n } else {\n for (const cam of this.cameras.values()) {\n for (const s of cam.sessions.values()) infos.push(s.getInfo());\n }\n }\n return infos;\n }\n\n getSessionCount(cameraName?: string): number {\n if (cameraName) return this.cameras.get(cameraName)?.sessions.size ?? 0;\n let total = 0;\n for (const cam of this.cameras.values()) total += cam.sessions.size;\n return total;\n }\n\n /** Stop all cameras and sessions. */\n async stop(): Promise<void> {\n if (this.stopped) return;\n this.stopped = true;\n\n const closePs: Promise<void>[] = [];\n for (const [name, cam] of this.cameras) {\n if (cam.autoStopTimer) {\n clearTimeout(cam.autoStopTimer);\n cam.autoStopTimer = null;\n }\n for (const [sid, session] of cam.sessions) {\n this.sessionCamera.delete(sid);\n closePs.push(session.close().catch(() => {}));\n }\n cam.sessions.clear();\n closePs.push(cam.mainFanout.stop().catch(() => {}));\n closePs.push(cam.mainFfmpegSource.stop().catch(() => {}));\n if (cam.subFanout) closePs.push(cam.subFanout.stop().catch(() => {}));\n if (cam.subFfmpegSource) closePs.push(cam.subFfmpegSource.stop().catch(() => {}));\n }\n await Promise.all(closePs);\n this.cameras.clear();\n this.logger.info(\"[adaptive-server] Stopped\");\n this.emit(\"stopped\");\n }\n\n // -----------------------------------------------------------------------\n // Private\n // -----------------------------------------------------------------------\n\n /** Get the currently active fanout for a camera. */\n private getActiveFanout(cam: CameraState): StreamFanout<MediaFrame> {\n if (cam.activeSourceProfile === \"sub\" && cam.subFanout) {\n return cam.subFanout;\n }\n return cam.mainFanout;\n }\n\n private ensureCameraRunning(name: string, cam: CameraState): void {\n const activeFanout = this.getActiveFanout(cam);\n if (activeFanout.isRunning()) return;\n\n this.logger.info(`[adaptive-server] Starting camera \"${name}\" (${cam.activeSourceProfile})`);\n if (cam.activeSourceProfile === \"sub\" && cam.subFfmpegSource) {\n void cam.subFfmpegSource.start();\n cam.subFanout!.start();\n } else {\n void cam.mainFfmpegSource.start();\n cam.mainFanout.start();\n }\n }\n\n private scheduleCameraAutoStop(name: string, cam: CameraState): void {\n if (cam.sessions.size > 0 || this.stopped) return;\n\n if (cam.autoStopTimer) clearTimeout(cam.autoStopTimer);\n\n cam.autoStopTimer = setTimeout(async () => {\n cam.autoStopTimer = null;\n if (cam.sessions.size > 0 || this.stopped) return;\n\n this.logger.info(`[adaptive-server] No viewers for \"${name}\", stopping ffmpeg`);\n await cam.mainFanout.stop();\n await cam.mainFfmpegSource.stop();\n if (cam.subFanout) await cam.subFanout.stop();\n if (cam.subFfmpegSource) await cam.subFfmpegSource.stop();\n }, 10_000); // 10s grace period\n }\n\n // -----------------------------------------------------------------------\n // Source switching (Phase 5)\n // -----------------------------------------------------------------------\n\n /**\n * Handle a quality change from the AdaptiveController.\n * When the sourceProfile changes (main ↔ sub), performs a seamless source\n * switch for all active sessions. When only encoding params change (same\n * sourceProfile), updates ffmpeg params in-place.\n */\n private async handleQualityChange(\n cameraName: string,\n from: QualityProfile,\n to: QualityProfile,\n ): Promise<void> {\n const cam = this.cameras.get(cameraName);\n if (!cam) return;\n\n const sourceChanged = from.sourceProfile !== to.sourceProfile;\n\n if (sourceChanged) {\n await this.switchSource(cameraName, cam, to);\n } else {\n // Same source profile — just update encoding params on the active ffmpeg\n const activeSource = cam.activeSourceProfile === \"sub\"\n ? cam.subFfmpegSource\n : cam.mainFfmpegSource;\n if (activeSource) {\n await activeSource.updateParams(to.encoding);\n }\n }\n\n this.emit(\"quality:change\", {\n camera: cameraName,\n tier: to.tier,\n encoding: to.encoding,\n sourceProfile: to.sourceProfile,\n });\n }\n\n /**\n * Switch all active sessions from one source to another (main ↔ sub).\n *\n * Steps:\n * 1. Create/start the target ffmpeg source + fanout\n * 2. For each session: subscribe to new fanout, call replaceSource()\n * 3. Unsubscribe all from old fanout\n * 4. Stop old ffmpeg + fanout (save resources)\n * 5. Update activeSourceProfile\n */\n private async switchSource(\n cameraName: string,\n cam: CameraState,\n toProfile: QualityProfile,\n ): Promise<void> {\n if (cam.switching) {\n this.logger.warn(`[adaptive-server] Source switch already in progress for \"${cameraName}\", skipping`);\n return;\n }\n cam.switching = true;\n\n const switchingToSub = toProfile.sourceProfile === \"sub\";\n this.logger.info(\n `[adaptive-server] Source switch for \"${cameraName}\": ` +\n `${cam.activeSourceProfile} → ${toProfile.sourceProfile}`,\n );\n\n try {\n if (switchingToSub) {\n // Switching main → sub\n if (!cam.config.subRtspUrl) {\n this.logger.warn(\n `[adaptive-server] No subRtspUrl configured for \"${cameraName}\", ` +\n `cannot switch to sub stream — falling back to param update only`,\n );\n await cam.mainFfmpegSource.updateParams(toProfile.encoding);\n return;\n }\n\n // Lazily create sub ffmpeg source + fanout\n if (!cam.subFfmpegSource) {\n cam.subFfmpegSource = new AdaptiveFfmpegSource({\n rtspUrl: cam.config.subRtspUrl,\n initialParams: toProfile.encoding,\n ffmpegPath: this.ffmpegPath,\n logger: this.logger,\n label: `ffmpeg:${cameraName}:sub`,\n });\n\n cam.subFanout = new StreamFanout<MediaFrame>({\n maxQueueItems: 30,\n createSource: () => cam.subFfmpegSource!.source,\n onError: (err) => {\n this.logger.error(`[adaptive-server] Sub fanout error (${cameraName}):`, err);\n },\n });\n }\n\n // Start sub ffmpeg + fanout\n void cam.subFfmpegSource.start();\n cam.subFanout!.start();\n\n // Switch each session from main fanout to sub fanout\n for (const [sid, session] of cam.sessions) {\n cam.mainFanout.unsubscribe(sid);\n const newSource = cam.subFanout!.subscribe(sid);\n session.replaceSource(newSource);\n }\n\n // Stop main ffmpeg + fanout (save resources)\n await cam.mainFanout.stop();\n await cam.mainFfmpegSource.stop();\n\n cam.activeSourceProfile = \"sub\";\n } else {\n // Switching sub → main\n // Recreate main ffmpeg source (it was stopped)\n cam.mainFfmpegSource = new AdaptiveFfmpegSource({\n rtspUrl: cam.config.rtspUrl,\n initialParams: toProfile.encoding,\n ffmpegPath: this.ffmpegPath,\n logger: this.logger,\n label: `ffmpeg:${cameraName}:main`,\n });\n\n cam.mainFanout = new StreamFanout<MediaFrame>({\n maxQueueItems: 30,\n createSource: () => cam.mainFfmpegSource.source,\n onError: (err) => {\n this.logger.error(`[adaptive-server] Main fanout error (${cameraName}):`, err);\n },\n });\n\n // Start main ffmpeg + fanout\n void cam.mainFfmpegSource.start();\n cam.mainFanout.start();\n\n // Switch each session from sub fanout to main fanout\n for (const [sid, session] of cam.sessions) {\n if (cam.subFanout) cam.subFanout.unsubscribe(sid);\n const newSource = cam.mainFanout.subscribe(sid);\n session.replaceSource(newSource);\n }\n\n // Stop sub ffmpeg + fanout\n if (cam.subFanout) await cam.subFanout.stop();\n if (cam.subFfmpegSource) await cam.subFfmpegSource.stop();\n cam.subFfmpegSource = null;\n cam.subFanout = null;\n\n cam.activeSourceProfile = \"main\";\n }\n\n this.logger.info(\n `[adaptive-server] Source switch complete for \"${cameraName}\": ` +\n `now on ${cam.activeSourceProfile} stream`,\n );\n } catch (err) {\n this.logger.error(`[adaptive-server] Source switch failed for \"${cameraName}\":`, err);\n } finally {\n cam.switching = false;\n }\n }\n}\n"],"mappings":"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","names":["platform","spawn","spawn","spawn","path","sharp","randomUUID","randomUUID","sharp","path","path","detectPlatformDefaults","i","tryConvertWithLengthReader","tryConvertWithLengthReader16","tryConvertWithLengthReader24","spawn","spawn","spawn","spawn","_werift","loadWerift","frame","rtpTs"]}
|
|
1
|
+
{"version":3,"sources":["../src/recording/ffmpeg-config.ts","../src/recording/recording-db.ts","../src/recording/segment-writer.ts","../src/recording/thumbnail-extractor.ts","../src/recording/retention-manager.ts","../src/recording/playlist-generator.ts","../src/recording/storage-estimator.ts","../src/recording/recording-coordinator.ts","../src/stream-broker/decoder-registry.ts","../src/stream-broker/ffmpeg-decoder-session.ts","../src/stream-broker/frame-dropper.ts","../src/stream-broker/ffmpeg-decoder-provider.ts","../src/stream-broker/stream-broker.ts","../src/stream-broker/stream-pipe-server.ts","../src/stream-broker/stream-broker-manager.ts","../src/persistence/event-persistence.ts","../src/persistence/known-faces.ts","../src/persistence/session-tracker.ts","../src/persistence/retention.ts","../src/persistence/track-trail.ts","../src/addon.ts","../src/index.ts","../src/analysis/analysis-addon.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process'\nimport * as os from 'node:os'\nimport type { FfmpegConfig, IAddonDepsManager } from '@camstack/types'\n\n/**\n * Probe ffmpeg binary and detect optimal hwaccel for the host platform.\n * Called once at server startup, result cached.\n */\nexport function detectPlatformDefaults(ffmpegPath: string = 'ffmpeg'): Partial<FfmpegConfig> {\n let hwaccels: string[] = []\n try {\n const output = execFileSync(ffmpegPath, ['-hwaccels', '-hide_banner'], { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] })\n hwaccels = output.split('\\n').map(l => l.trim()).filter(l => l && l !== 'Hardware acceleration methods:')\n } catch {}\n\n const platform = os.platform()\n if (platform === 'darwin' && hwaccels.includes('videotoolbox')) return { hwaccel: 'videotoolbox', threads: 0 }\n if (platform === 'linux') {\n for (const hw of ['vaapi', 'qsv', 'cuda', 'v4l2m2m'] as const) {\n if (hwaccels.includes(hw)) return { hwaccel: hw, threads: 0 }\n }\n }\n return { hwaccel: 'none', threads: Math.max(1, Math.floor(os.cpus().length / 2)) }\n}\n\n/**\n * Resolve ffmpeg config for a device.\n * Merge order: detected <- global <- per-device (most specific wins).\n */\nexport function resolveFfmpegConfig(\n deviceConfig: Partial<FfmpegConfig> | undefined,\n globalConfig: Partial<FfmpegConfig>,\n detected: Partial<FfmpegConfig>,\n): FfmpegConfig {\n return {\n path: deviceConfig?.path ?? globalConfig.path ?? detected.path ?? 'ffmpeg',\n hwaccel: deviceConfig?.hwaccel ?? globalConfig.hwaccel ?? detected.hwaccel ?? 'none',\n inputArgs: deviceConfig?.inputArgs ?? globalConfig.inputArgs,\n outputArgs: deviceConfig?.outputArgs ?? globalConfig.outputArgs,\n videoCodec: deviceConfig?.videoCodec ?? globalConfig.videoCodec ?? 'copy',\n audioCodec: deviceConfig?.audioCodec ?? globalConfig.audioCodec ?? 'copy',\n threads: deviceConfig?.threads ?? globalConfig.threads ?? detected.threads,\n }\n}\n\n/**\n * Build common ffmpeg args from a resolved config.\n */\nexport function buildFfmpegInputArgs(config: FfmpegConfig, inputUrl: string): string[] {\n const args: string[] = ['-hide_banner', '-loglevel', 'warning']\n if (config.hwaccel && config.hwaccel !== 'none') args.push('-hwaccel', config.hwaccel)\n if (config.threads !== undefined) args.push('-threads', String(config.threads))\n if (config.inputArgs?.length) args.push(...config.inputArgs)\n args.push('-rtsp_transport', 'tcp', '-i', inputUrl)\n return args\n}\n\nexport function buildFfmpegOutputArgs(config: FfmpegConfig): string[] {\n const args: string[] = ['-c:v', config.videoCodec ?? 'copy', '-c:a', config.audioCodec ?? 'copy']\n if (config.outputArgs?.length) args.push(...config.outputArgs)\n return args\n}\n\n/**\n * Resolve ffmpeg binary path. Priority:\n * 1. Explicit config path\n * 2. Embedded (data/deps/ffmpeg)\n * 3. System PATH\n * 4. Download static build\n */\nexport async function resolveFfmpegBinary(\n configPath: string | undefined,\n deps: IAddonDepsManager,\n): Promise<string> {\n // Explicit config\n if (configPath && configPath !== 'ffmpeg') {\n return configPath\n }\n\n // Try embedded or download via deps manager\n return deps.ensureFfmpeg()\n}\n","import type Database from 'better-sqlite3'\nimport type {\n RecordingSegment, RecordingThumbnail, RecordingStorageConfig,\n RecordingPolicy, RecordingMode, StreamPolicy, ScheduleRule,\n CleanupQueueEntry, CleanupStatus, DataCategory,\n} from './types'\n\nexport interface StorageUsage {\n readonly totalBytes: number\n readonly segmentCount: number\n}\n\nexport interface AvailabilityRange {\n readonly startTime: number\n readonly endTime: number\n readonly streams: readonly string[]\n}\n\ninterface PolicyRow {\n readonly device_id: string\n readonly enabled: number\n readonly mode: string\n readonly streams_json: string\n readonly schedule_json: string | null\n readonly pre_buffer_sec: number\n readonly post_buffer_sec: number\n}\n\nexport class RecordingDb {\n constructor(private readonly db: Database.Database) {}\n\n initialize(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS recording_segments (\n id TEXT PRIMARY KEY,\n device_id TEXT NOT NULL,\n stream_id TEXT NOT NULL,\n start_time INTEGER NOT NULL,\n end_time INTEGER NOT NULL,\n duration REAL NOT NULL,\n path TEXT NOT NULL,\n storage_name TEXT NOT NULL,\n sub_directory TEXT NOT NULL,\n size_bytes INTEGER NOT NULL,\n codec TEXT NOT NULL,\n has_audio INTEGER NOT NULL DEFAULT 0\n );\n CREATE INDEX IF NOT EXISTS idx_segments_device_time ON recording_segments(device_id, stream_id, start_time);\n CREATE INDEX IF NOT EXISTS idx_segments_start_time ON recording_segments(start_time);\n\n CREATE TABLE IF NOT EXISTS recording_thumbnails (\n device_id TEXT NOT NULL,\n timestamp INTEGER NOT NULL,\n path TEXT NOT NULL,\n storage_name TEXT NOT NULL,\n sub_directory TEXT NOT NULL,\n size_bytes INTEGER NOT NULL,\n category TEXT NOT NULL,\n PRIMARY KEY (device_id, timestamp, category)\n );\n\n CREATE TABLE IF NOT EXISTS recording_policies (\n device_id TEXT PRIMARY KEY,\n enabled INTEGER NOT NULL DEFAULT 0,\n mode TEXT NOT NULL,\n streams_json TEXT NOT NULL,\n schedule_json TEXT,\n pre_buffer_sec INTEGER NOT NULL DEFAULT 5,\n post_buffer_sec INTEGER NOT NULL DEFAULT 10,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS recording_storage_config (\n device_id TEXT NOT NULL,\n data_category TEXT NOT NULL,\n storage_name TEXT NOT NULL,\n sub_directory TEXT NOT NULL,\n retention_days INTEGER,\n retention_gb REAL,\n PRIMARY KEY (device_id, data_category)\n );\n\n CREATE TABLE IF NOT EXISTS recording_cleanup_queue (\n device_id TEXT PRIMARY KEY,\n disabled_at INTEGER NOT NULL,\n cleanup_after INTEGER NOT NULL,\n status TEXT NOT NULL,\n started_at INTEGER\n );\n `)\n }\n\n // --- Segments ---\n\n insertSegment(seg: RecordingSegment): void {\n this.db.prepare(`\n INSERT INTO recording_segments (id, device_id, stream_id, start_time, end_time, duration, path, storage_name, sub_directory, size_bytes, codec, has_audio)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(seg.id, seg.deviceId, seg.streamId, seg.startTime, seg.endTime, seg.duration, seg.path, seg.storageName, seg.subDirectory, seg.sizeBytes, seg.codec, seg.hasAudio ? 1 : 0)\n }\n\n querySegments(deviceId: string, streamId: string, startTime: number, endTime: number): readonly RecordingSegment[] {\n const rows = this.db.prepare(`\n SELECT * FROM recording_segments\n WHERE device_id = ? AND stream_id = ? AND start_time < ? AND end_time > ?\n ORDER BY start_time ASC\n `).all(deviceId, streamId, endTime, startTime) as Array<Record<string, unknown>>\n return rows.map(rowToSegment)\n }\n\n deleteSegmentsBefore(deviceId: string, streamId: string, beforeTime: number): readonly RecordingSegment[] {\n const toDelete = this.db.prepare(`\n SELECT * FROM recording_segments WHERE device_id = ? AND stream_id = ? AND start_time < ?\n `).all(deviceId, streamId, beforeTime) as Array<Record<string, unknown>>\n this.db.prepare(`DELETE FROM recording_segments WHERE device_id = ? AND stream_id = ? AND start_time < ?`).run(deviceId, streamId, beforeTime)\n return toDelete.map(rowToSegment)\n }\n\n deleteSegmentsForDevice(deviceId: string): readonly RecordingSegment[] {\n const toDelete = this.db.prepare(`SELECT * FROM recording_segments WHERE device_id = ?`).all(deviceId) as Array<Record<string, unknown>>\n this.db.prepare(`DELETE FROM recording_segments WHERE device_id = ?`).run(deviceId)\n return toDelete.map(rowToSegment)\n }\n\n getStorageUsage(deviceId: string, streamId: string): StorageUsage {\n const row = this.db.prepare(`\n SELECT COALESCE(SUM(size_bytes), 0) as total_bytes, COUNT(*) as segment_count\n FROM recording_segments WHERE device_id = ? AND stream_id = ?\n `).get(deviceId, streamId) as { total_bytes: number; segment_count: number }\n return { totalBytes: row.total_bytes, segmentCount: row.segment_count }\n }\n\n getOldestSegments(deviceId: string, streamId: string, limit: number): readonly RecordingSegment[] {\n const rows = this.db.prepare(`\n SELECT * FROM recording_segments WHERE device_id = ? AND stream_id = ?\n ORDER BY start_time ASC LIMIT ?\n `).all(deviceId, streamId, limit) as Array<Record<string, unknown>>\n return rows.map(rowToSegment)\n }\n\n getAvailability(deviceId: string, startTime: number, endTime: number): readonly AvailabilityRange[] {\n const rows = this.db.prepare(`\n SELECT start_time, end_time, stream_id FROM recording_segments\n WHERE device_id = ? AND end_time >= ? AND start_time <= ?\n ORDER BY start_time ASC\n `).all(deviceId, startTime, endTime) as Array<{ start_time: number; end_time: number; stream_id: string }>\n\n if (rows.length === 0) return []\n\n const ranges: Array<{ startTime: number; endTime: number; streams: Set<string> }> = []\n let current = { startTime: rows[0]!.start_time, endTime: rows[0]!.end_time, streams: new Set([rows[0]!.stream_id]) }\n\n for (let i = 1; i < rows.length; i++) {\n const row = rows[i]!\n if (row.start_time <= current.endTime) {\n current.endTime = Math.max(current.endTime, row.end_time)\n current.streams.add(row.stream_id)\n } else {\n ranges.push(current)\n current = { startTime: row.start_time, endTime: row.end_time, streams: new Set([row.stream_id]) }\n }\n }\n ranges.push(current)\n\n return ranges.map(r => ({ startTime: r.startTime, endTime: r.endTime, streams: [...r.streams] }))\n }\n\n getMotionStats(deviceId: string, startTime: number, endTime: number): {\n readonly totalEvents: number\n readonly avgDurationSec: number\n readonly avgEventsPerDay: number\n readonly dutyCyclePercent: number\n } {\n const row = this.db.prepare(`\n SELECT COUNT(*) as total_events, COALESCE(AVG(duration), 0) as avg_duration, COALESCE(SUM(duration), 0) as total_duration\n FROM recording_segments\n WHERE device_id = ? AND start_time >= ? AND end_time <= ?\n `).get(deviceId, startTime, endTime) as { total_events: number; avg_duration: number; total_duration: number }\n\n const timeRangeMs = endTime - startTime\n const timeRangeDays = Math.max(timeRangeMs / (24 * 60 * 60 * 1000), 1)\n const timeRangeSec = Math.max(timeRangeMs / 1000, 1)\n\n return {\n totalEvents: row.total_events,\n avgDurationSec: Math.round(row.avg_duration * 100) / 100,\n avgEventsPerDay: Math.round((row.total_events / timeRangeDays) * 100) / 100,\n dutyCyclePercent: Math.round((row.total_duration / timeRangeSec) * 10000) / 100,\n }\n }\n\n // --- Thumbnails ---\n\n insertThumbnail(thumb: RecordingThumbnail): void {\n this.db.prepare(`\n INSERT OR REPLACE INTO recording_thumbnails (device_id, timestamp, path, storage_name, sub_directory, size_bytes, category)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `).run(thumb.deviceId, thumb.timestamp, thumb.path, thumb.storageName, thumb.subDirectory, thumb.sizeBytes, thumb.category)\n }\n\n findNearestThumbnail(deviceId: string, timestamp: number, category: string): RecordingThumbnail | null {\n const row = this.db.prepare(`\n SELECT * FROM recording_thumbnails\n WHERE device_id = ? AND category = ?\n ORDER BY ABS(timestamp - ?) ASC LIMIT 1\n `).get(deviceId, category, timestamp) as Record<string, unknown> | undefined\n return row ? rowToThumbnail(row) : null\n }\n\n deleteThumbnailsBefore(deviceId: string, beforeTime: number): number {\n const result = this.db.prepare(`DELETE FROM recording_thumbnails WHERE device_id = ? AND timestamp < ?`).run(deviceId, beforeTime)\n return result.changes\n }\n\n deleteThumbnailsForDevice(deviceId: string): number {\n const result = this.db.prepare(`DELETE FROM recording_thumbnails WHERE device_id = ?`).run(deviceId)\n return result.changes\n }\n\n // --- Policies ---\n\n upsertPolicy(policy: { deviceId: string; enabled: boolean; mode: RecordingMode; streams: readonly StreamPolicy[]; preBufferSec: number; postBufferSec: number; scheduleRules?: readonly ScheduleRule[] }): void {\n const now = Date.now()\n this.db.prepare(`\n INSERT INTO recording_policies (device_id, enabled, mode, streams_json, schedule_json, pre_buffer_sec, post_buffer_sec, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(device_id) DO UPDATE SET enabled=?, mode=?, streams_json=?, schedule_json=?, pre_buffer_sec=?, post_buffer_sec=?, updated_at=?\n `).run(\n policy.deviceId, policy.enabled ? 1 : 0, policy.mode, JSON.stringify(policy.streams), policy.scheduleRules ? JSON.stringify(policy.scheduleRules) : null, policy.preBufferSec, policy.postBufferSec, now, now,\n policy.enabled ? 1 : 0, policy.mode, JSON.stringify(policy.streams), policy.scheduleRules ? JSON.stringify(policy.scheduleRules) : null, policy.preBufferSec, policy.postBufferSec, now,\n )\n }\n\n getPolicy(deviceId: string): RecordingPolicy | null {\n const row = this.db.prepare(`SELECT * FROM recording_policies WHERE device_id = ?`).get(deviceId) as PolicyRow | undefined\n return row ? rowToPolicy(row) : null\n }\n\n getEnabledPolicies(): readonly RecordingPolicy[] {\n const rows = this.db.prepare(`SELECT * FROM recording_policies WHERE enabled = 1`).all() as PolicyRow[]\n return rows.map(rowToPolicy)\n }\n\n deletePolicy(deviceId: string): void {\n this.db.prepare(`DELETE FROM recording_policies WHERE device_id = ?`).run(deviceId)\n }\n\n // --- Storage Config ---\n\n upsertStorageConfig(config: RecordingStorageConfig): void {\n this.db.prepare(`\n INSERT INTO recording_storage_config (device_id, data_category, storage_name, sub_directory, retention_days, retention_gb)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(device_id, data_category) DO UPDATE SET storage_name=?, sub_directory=?, retention_days=?, retention_gb=?\n `).run(config.deviceId, config.dataCategory, config.storageName, config.subDirectory, config.retentionDays, config.retentionGb,\n config.storageName, config.subDirectory, config.retentionDays, config.retentionGb)\n }\n\n resolveStorageConfig(deviceId: string, category: DataCategory): RecordingStorageConfig | null {\n const specific = this.db.prepare(`SELECT * FROM recording_storage_config WHERE device_id = ? AND data_category = ?`).get(deviceId, category) as Record<string, unknown> | undefined\n if (specific) return rowToStorageConfig(specific)\n const global = this.db.prepare(`SELECT * FROM recording_storage_config WHERE device_id = '*' AND data_category = ?`).get(category) as Record<string, unknown> | undefined\n return global ? rowToStorageConfig(global) : null\n }\n\n // --- Cleanup Queue ---\n\n addToCleanupQueue(deviceId: string, disabledAt: number): void {\n const cleanupAfter = disabledAt + 24 * 60 * 60 * 1000\n this.db.prepare(`\n INSERT OR REPLACE INTO recording_cleanup_queue (device_id, disabled_at, cleanup_after, status, started_at)\n VALUES (?, ?, ?, 'pending', NULL)\n `).run(deviceId, disabledAt, cleanupAfter)\n }\n\n cancelCleanup(deviceId: string): void {\n this.db.prepare(`UPDATE recording_cleanup_queue SET status = 'cancelled' WHERE device_id = ? AND status = 'pending'`).run(deviceId)\n }\n\n getCleanupEntry(deviceId: string): CleanupQueueEntry | null {\n const row = this.db.prepare(`SELECT * FROM recording_cleanup_queue WHERE device_id = ?`).get(deviceId) as Record<string, unknown> | undefined\n return row ? rowToCleanup(row) : null\n }\n\n getPendingCleanups(): readonly CleanupQueueEntry[] {\n const now = Date.now()\n const rows = this.db.prepare(`SELECT * FROM recording_cleanup_queue WHERE status = 'pending' AND cleanup_after <= ?`).all(now) as Array<Record<string, unknown>>\n return rows.map(rowToCleanup)\n }\n\n resetStaleCleanups(maxAgeMs: number = 3600000): void {\n const cutoff = Date.now() - maxAgeMs\n this.db.prepare(`UPDATE recording_cleanup_queue SET status = 'pending', started_at = NULL WHERE status = 'in_progress' AND started_at < ?`).run(cutoff)\n }\n\n markCleanupInProgress(deviceId: string): void {\n this.db.prepare(`UPDATE recording_cleanup_queue SET status = 'in_progress', started_at = ? WHERE device_id = ?`).run(Date.now(), deviceId)\n }\n\n markCleanupCompleted(deviceId: string): void {\n this.db.prepare(`UPDATE recording_cleanup_queue SET status = 'completed' WHERE device_id = ?`).run(deviceId)\n }\n}\n\n// --- Row mappers ---\n\nfunction rowToSegment(row: Record<string, unknown>): RecordingSegment {\n return {\n id: row.id as string,\n deviceId: row.device_id as string,\n streamId: row.stream_id as string,\n startTime: row.start_time as number,\n endTime: row.end_time as number,\n duration: row.duration as number,\n path: row.path as string,\n storageName: row.storage_name as string,\n subDirectory: row.sub_directory as string,\n sizeBytes: row.size_bytes as number,\n codec: row.codec as 'h264' | 'h265',\n hasAudio: (row.has_audio as number) === 1,\n }\n}\n\nfunction rowToThumbnail(row: Record<string, unknown>): RecordingThumbnail {\n return {\n deviceId: row.device_id as string,\n timestamp: row.timestamp as number,\n path: row.path as string,\n storageName: row.storage_name as string,\n subDirectory: row.sub_directory as string,\n sizeBytes: row.size_bytes as number,\n category: row.category as 'scrub' | 'event',\n }\n}\n\nfunction rowToPolicy(row: PolicyRow): RecordingPolicy {\n return {\n deviceId: row.device_id,\n enabled: row.enabled === 1,\n mode: row.mode as RecordingMode,\n streams: JSON.parse(row.streams_json) as StreamPolicy[],\n preBufferSec: row.pre_buffer_sec,\n postBufferSec: row.post_buffer_sec,\n scheduleRules: row.schedule_json ? JSON.parse(row.schedule_json) as ScheduleRule[] : undefined,\n }\n}\n\nfunction rowToStorageConfig(row: Record<string, unknown>): RecordingStorageConfig {\n return {\n deviceId: row.device_id as string,\n dataCategory: row.data_category as DataCategory,\n storageName: row.storage_name as string,\n subDirectory: row.sub_directory as string,\n retentionDays: row.retention_days as number | null,\n retentionGb: row.retention_gb as number | null,\n }\n}\n\nfunction rowToCleanup(row: Record<string, unknown>): CleanupQueueEntry {\n return {\n deviceId: row.device_id as string,\n disabledAt: row.disabled_at as number,\n cleanupAfter: row.cleanup_after as number,\n status: row.status as CleanupStatus,\n startedAt: row.started_at as number | null,\n }\n}\n","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 type { IAddonFileStorage } from '@camstack/types'\nimport type { IScopedLogger, IEventBus, INetworkQualityTracker, FfmpegConfig } from '@camstack/types'\nimport { buildFfmpegInputArgs, buildFfmpegOutputArgs } from './ffmpeg-config'\nimport type { RecordingDb } from './recording-db'\nimport type { SegmentWriterState, RecordingSegment } from './types'\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: string\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 private readonly _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 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 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.length} buffered segments to disk`, {\n deviceId: this.config.deviceId,\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: string, 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: ${msg}`)\n this.parseSegmentOutput(msg)\n }\n })\n\n this.ffmpeg.on('error', (err) => {\n this.logger.warn(`ffmpeg process 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 code ${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: 'recording.error',\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(\n `Max restarts exceeded for ${this.config.deviceId}/${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: 'recording.health.degraded',\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 in ${backoffMs}ms (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 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 for ${elapsed}ms, restarting ffmpeg`)\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: ${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: 'recording.storage.critical',\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: 'recording.segment.written',\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: ${err}`)\n }\n } catch (err) {\n this.logger.error(`Disk space check failed: ${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: 'recording.segment.written',\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: ${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'\n\nexport interface ThumbnailExtractorConfig {\n readonly deviceId: string\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: string): void {\n this.active = true\n\n this.unsubscribe = pipeline.onVideoFrame(\n (frame) => { this.handleFrame(frame).catch((err) => this.logger.debug(`Thumbnail error: ${err}`)) },\n this.videoRequirements,\n )\n\n this.logger.info(`ThumbnailExtractor attached for ${this.config.deviceId}`)\n }\n\n detachFromPipeline(_deviceId: string): void {\n this.active = false\n if (this.unsubscribe) {\n this.unsubscribe()\n this.unsubscribe = null\n }\n this.logger.info(`ThumbnailExtractor detached for ${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: string, timestamp: number): string {\n return `${subDirectory}/${deviceId}/${timestamp}.jpg`\n }\n}\n","import type { IScopedLogger, IEventBus, IFileStorage } from '@camstack/types'\nimport type { RecordingDb } from './recording-db'\nimport type { DataCategory } from './types'\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 fileStorage: IFileStorage,\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 for ${entry.deviceId}: ${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: 'recording.retention.completed',\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: ${err}`)\n this.scheduleNextCycle(NORMAL_INTERVAL_MS)\n }\n }, intervalMs)\n }\n\n private emitStorageEvent(category: string, deviceId: string, 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.fileStorage.deleteFile(filePath)\n } catch {\n // File may already be deleted\n }\n }\n}\n","import type { RecordingDb } from './recording-db'\nimport type { RecordingSegment } from './types'\n\ninterface PlaylistOptions {\n readonly live?: boolean\n}\n\nconst FALLBACK_STREAMS = ['sub', 'mid', 'main'] as const\n\nexport class PlaylistGenerator {\n constructor(private readonly db: RecordingDb) {}\n\n generate(deviceId: string, streamId: string, startTime: number, endTime: number, options?: PlaylistOptions): string {\n const segments = this.resolveSegments(deviceId, streamId, startTime, endTime)\n return this.buildPlaylist(segments, options)\n }\n\n private resolveSegments(deviceId: string, streamId: string, startTime: number, endTime: number): readonly RecordingSegment[] {\n const segments = this.db.querySegments(deviceId, streamId, startTime, endTime)\n if (segments.length > 0) {\n return segments\n }\n\n const fallbacks = FALLBACK_STREAMS.filter(s => s !== streamId)\n for (const fb of fallbacks) {\n const fallbackSegments = this.db.querySegments(deviceId, fb, startTime, endTime)\n if (fallbackSegments.length > 0) {\n return fallbackSegments\n }\n }\n\n return []\n }\n\n private buildPlaylist(segments: readonly RecordingSegment[], options?: PlaylistOptions): string {\n const targetDuration = segments.length > 0\n ? Math.ceil(Math.max(...segments.map(s => s.duration)))\n : 4\n\n const lines: string[] = [\n '#EXTM3U',\n '#EXT-X-VERSION:7',\n `#EXT-X-TARGETDURATION:${targetDuration}`,\n '#EXT-X-MEDIA-SEQUENCE:0',\n ]\n\n if (!options?.live) {\n lines.push('#EXT-X-PLAYLIST-TYPE:VOD')\n }\n\n for (const seg of segments) {\n lines.push(`#EXTINF:${seg.duration.toFixed(3)},`)\n lines.push(seg.path)\n }\n\n if (!options?.live) {\n lines.push('#EXT-X-ENDLIST')\n }\n\n return lines.join('\\n')\n }\n}\n","import type { INetworkQualityTracker } from '@camstack/types'\nimport type { RecordingDb } from './recording-db'\nimport type { StorageEstimate, StreamEstimate } from './types'\n\ninterface MotionEstimateInput {\n readonly avgEventsPerDay: number\n readonly avgDurationSec: number\n}\n\nexport class StorageEstimator {\n constructor(\n private readonly db: RecordingDb,\n private readonly networkTracker: INetworkQualityTracker,\n ) {}\n\n estimateForDevice(deviceId: string, motionInput?: MotionEstimateInput): StorageEstimate {\n const policy = this.db.getPolicy(deviceId)\n if (!policy) return { perStream: {}, thumbnails: { estimatedGb: 0 }, totalEstimatedGb: 0 }\n\n const stats = this.networkTracker.getDeviceStats(deviceId)\n const perStream: Record<string, StreamEstimate> = {}\n let totalGb = 0\n\n const motionEstimate = motionInput ? {\n avgEventsPerDay: motionInput.avgEventsPerDay,\n avgDurationSec: motionInput.avgDurationSec,\n dutyCyclePercent: (motionInput.avgEventsPerDay * motionInput.avgDurationSec / 86400) * 100,\n } : undefined\n\n for (const sp of policy.streams) {\n const category = `recording:${sp.streamId}` as any\n const config = this.db.resolveStorageConfig(deviceId, category)\n const retentionDays = config?.retentionDays ?? null\n const retentionGb = config?.retentionGb ?? null\n\n const streamStats = stats?.streams[sp.streamId]\n const observedBitrate = streamStats?.observedBitrateKbps ?? 0\n const bitrateKbps = observedBitrate > 0\n ? observedBitrate\n : (streamStats?.nominalBitrateKbps ?? 4000)\n\n let estimatedGb = 0\n if (retentionDays !== null) {\n const retentionSeconds = retentionDays * 86400\n estimatedGb = (bitrateKbps * retentionSeconds) / 8 / 1024 / 1024 / 1024\n }\n\n if (policy.mode === 'motion' && motionEstimate) {\n estimatedGb = estimatedGb * motionEstimate.dutyCyclePercent / 100\n }\n\n let estimatedDaysAtCapacity: number | null = null\n if (retentionGb !== null && estimatedGb > retentionGb) {\n estimatedDaysAtCapacity = retentionDays !== null ? retentionDays * (retentionGb / estimatedGb) : null\n estimatedGb = retentionGb\n }\n\n perStream[sp.streamId] = { bitrateKbps, retentionDays, retentionGb, estimatedGb, estimatedDaysAtCapacity }\n totalGb += estimatedGb\n }\n\n const thumbRetentionDays = policy.streams[0]\n ? (this.db.resolveStorageConfig(deviceId, 'thumbnail:scrub' as any)?.retentionDays ?? 7)\n : 7\n const thumbGb = (2 * 24 * thumbRetentionDays) / 1024\n totalGb += thumbGb\n\n return { perStream, thumbnails: { estimatedGb: thumbGb }, totalEstimatedGb: totalGb, motionEstimate }\n }\n}\n","import type { IScopedLogger, IEventBus, SystemEvent, IStreamingEngine, IPipelineManager, INetworkQualityTracker, IFileStorage, FfmpegConfig } from '@camstack/types'\nimport { resolveFfmpegConfig } from './ffmpeg-config'\nimport type { RecordingDb } from './recording-db'\nimport type {\n RecordingPolicy, RecordingEnableConfig, ScheduleRule,\n SegmentWriterState,\n} from './types'\nimport { SegmentWriter, type SegmentWriterConfig } from './segment-writer'\nimport { ThumbnailExtractor, type ThumbnailExtractorConfig } from './thumbnail-extractor'\nimport { RetentionManager } from './retention-manager'\nimport { PlaylistGenerator } from './playlist-generator'\nimport { StorageEstimator } from './storage-estimator'\n\n// --- Per-device recording state ---\n\ninterface DeviceRecordingState {\n readonly deviceId: string\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 readonly fileStorage: IFileStorage\n readonly storagePath: string\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 fileStorage: IFileStorage\n private readonly storagePath: string\n private readonly globalFfmpegConfig: Partial<FfmpegConfig>\n private readonly detectedFfmpegConfig: Partial<FfmpegConfig>\n private readonly segmentDurationSec: number\n\n private readonly recordings = new Map<string, 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.fileStorage = config.fileStorage\n this.storagePath = config.storagePath\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.fileStorage,\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 for ${policy.deviceId}: ${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: string, 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 any)\n const storageName = storageConfig?.storageName ?? 'recordings'\n const subDirectory = storageConfig?.subDirectory ?? `recordings/${sp.streamId}`\n\n const writerConfig: SegmentWriterConfig = {\n deviceId,\n streamId: sp.streamId,\n segmentDurationSec: this.segmentDurationSec,\n storagePath: this.storagePath,\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 thumbConfig: ThumbnailExtractorConfig = {\n deviceId,\n storagePath: this.storagePath,\n storageName: thumbStorageConfig?.storageName ?? 'recordings',\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 for ${deviceId} within ${MOTION_FALLBACK_TIMEOUT_MS / 1000}s — falling back to continuous recording`)\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: 'recording.policy.fallback',\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 for ${deviceId} during fallback: ${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: 'recording.started',\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 for ${deviceId}`, { mode: policy.mode })\n }\n\n async disableRecording(deviceId: string): Promise<void> {\n const state = this.recordings.get(deviceId)\n if (!state) {\n this.logger.warn(`No active recording for ${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: 'recording.stopped',\n data: {\n deviceId,\n segmentCount: totalSegmentCount,\n totalMB,\n },\n })\n\n this.logger.info(`Recording disabled for ${deviceId}`, { segmentCount: totalSegmentCount, totalMB })\n }\n\n isRecording(deviceId: string): boolean {\n return this.recordings.has(deviceId)\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: string, 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: string, 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 for ${deviceId}: ${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: string): 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","import type { IDecoderProvider } from '@camstack/types'\n\ninterface RegisteredProvider {\n readonly provider: IDecoderProvider\n readonly priority: number\n}\n\nexport class DecoderRegistry {\n private readonly entries: RegisteredProvider[] = []\n\n register(provider: IDecoderProvider, priority: number): void {\n this.entries.push({ provider, priority })\n this.entries.sort((a, b) => a.priority - b.priority)\n }\n\n getDecoder(codec: string): IDecoderProvider | undefined {\n return this.entries.find((e) => e.provider.supportsCodec(codec))?.provider\n }\n}\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport type { IDecoderSession, DecoderSessionConfig, DecoderStats, EncodedPacket, DecodedFrame, Unsubscribe } from '@camstack/types'\nimport { FrameDropper } from './frame-dropper'\n\nconst SOI = Buffer.from([0xff, 0xd8])\nconst EOI = Buffer.from([0xff, 0xd9])\n\nfunction codecToInputFormat(codec: string): string {\n switch (codec) {\n case 'h265':\n case 'hevc':\n return 'hevc'\n case 'mjpeg':\n return 'mjpeg'\n case 'h264':\n default:\n return 'h264'\n }\n}\n\nfunction buildFfmpegArgs(config: DecoderSessionConfig): string[] {\n const inputFormat = codecToInputFormat(config.codec)\n const args = ['-hide_banner', '-loglevel', 'error', '-f', inputFormat, '-i', 'pipe:0']\n\n if (config.scale > 1) {\n args.push('-vf', `scale=iw/${config.scale}:ih/${config.scale}`)\n }\n\n args.push('-f', 'image2pipe', '-vcodec', 'mjpeg', '-q:v', '3', 'pipe:1')\n return args\n}\n\nexport class FfmpegDecoderSession implements IDecoderSession {\n private config: DecoderSessionConfig\n private frameDropper: FrameDropper\n private process: ChildProcess | null = null\n private frameCallbacks = new Set<(frame: DecodedFrame) => void>()\n private outputBuffer = Buffer.alloc(0)\n private destroyed = false\n\n // Stats tracking\n private inputPackets = 0\n private outputFrames = 0\n private droppedFrames = 0\n private totalDecodeTimeMs = 0\n private decodeCount = 0\n private startTime = Date.now()\n\n constructor(config: DecoderSessionConfig) {\n this.config = { ...config }\n this.frameDropper = new FrameDropper(config.maxFps)\n this.spawnFfmpeg()\n }\n\n private spawnFfmpeg(): void {\n if (this.destroyed) return\n\n this.killFfmpeg()\n this.outputBuffer = Buffer.alloc(0)\n\n const args = buildFfmpegArgs(this.config)\n this.process = spawn('ffmpeg', args)\n\n // Swallow EPIPE errors on stdin during shutdown\n this.process.stdin?.on('error', () => {})\n\n this.process.stdout?.on('data', (chunk: Buffer) => {\n this.handleOutputData(chunk)\n })\n\n this.process.stderr?.on('data', (data: Buffer) => {\n // ffmpeg logs errors to stderr; swallow in production, could log here if needed\n void data\n })\n\n this.process.on('error', (_err: Error) => {\n // process failed to spawn or crashed; could handle reconnect logic here\n })\n\n this.process.on('close', () => {\n if (!this.destroyed) {\n this.process = null\n }\n })\n }\n\n private killFfmpeg(): void {\n if (this.process) {\n try {\n this.process.kill('SIGKILL')\n } catch {\n // already dead\n }\n this.process = null\n }\n }\n\n private handleOutputData(chunk: Buffer): void {\n this.outputBuffer = Buffer.concat([this.outputBuffer, chunk])\n\n // Extract complete JPEG frames from the buffer\n let searchFrom = 0\n while (true) {\n const soiIndex = this.outputBuffer.indexOf(SOI, searchFrom)\n if (soiIndex === -1) break\n\n const eoiIndex = this.outputBuffer.indexOf(EOI, soiIndex + 2)\n if (eoiIndex === -1) break\n\n const frameEnd = eoiIndex + 2\n const jpegData = this.outputBuffer.slice(soiIndex, frameEnd)\n\n // Advance past the consumed frame\n searchFrom = frameEnd\n\n this.emitFrame(Buffer.from(jpegData))\n }\n\n // Keep only unprocessed tail\n if (searchFrom > 0) {\n this.outputBuffer = Buffer.from(this.outputBuffer.slice(searchFrom))\n }\n }\n\n private emitFrame(data: Buffer): void {\n const decodeStart = Date.now()\n\n if (!this.frameDropper.shouldKeep()) {\n this.droppedFrames++\n return\n }\n\n const decodeTime = Date.now() - decodeStart\n this.totalDecodeTimeMs += decodeTime\n this.decodeCount++\n this.outputFrames++\n\n const frame: DecodedFrame = {\n data,\n width: 0,\n height: 0,\n format: 'jpeg',\n timestamp: Date.now(),\n }\n\n for (const cb of this.frameCallbacks) {\n cb(frame)\n }\n }\n\n pushPacket(packet: EncodedPacket): void {\n if (this.destroyed || !this.process?.stdin) return\n this.inputPackets++\n try {\n this.process.stdin.write(packet.data)\n } catch {\n // stdin may be closed if ffmpeg crashed\n }\n }\n\n onFrame(callback: (frame: DecodedFrame) => void): Unsubscribe {\n this.frameCallbacks.add(callback)\n return () => {\n this.frameCallbacks.delete(callback)\n }\n }\n\n updateConfig(update: Partial<DecoderSessionConfig>): void {\n const needsRestart =\n (update.scale !== undefined && update.scale !== this.config.scale) ||\n (update.outputFormat !== undefined && update.outputFormat !== this.config.outputFormat) ||\n (update.codec !== undefined && update.codec !== this.config.codec)\n\n this.config = { ...this.config, ...update }\n\n if (update.maxFps !== undefined) {\n this.frameDropper.setMaxFps(update.maxFps)\n }\n\n if (needsRestart) {\n this.spawnFfmpeg()\n }\n }\n\n async destroy(): Promise<void> {\n if (this.destroyed) return\n this.destroyed = true\n this.killFfmpeg()\n this.frameCallbacks.clear()\n }\n\n getStats(): DecoderStats {\n const uptimeSec = Math.max((Date.now() - this.startTime) / 1000, 1)\n return {\n inputFps: this.inputPackets / uptimeSec,\n outputFps: this.outputFrames / uptimeSec,\n avgDecodeTimeMs: this.decodeCount > 0 ? this.totalDecodeTimeMs / this.decodeCount : 0,\n droppedFrames: this.droppedFrames,\n }\n }\n}\n","export class FrameDropper {\n private intervalMs: number\n private lastPassedAt = -Infinity\n\n constructor(maxFps: number) {\n this.intervalMs = maxFps > 0 ? 1000 / maxFps : 0\n }\n\n shouldKeep(): boolean {\n if (this.intervalMs === 0) return true\n\n const now = Date.now()\n if (now - this.lastPassedAt >= this.intervalMs) {\n this.lastPassedAt = now\n return true\n }\n return false\n }\n\n setMaxFps(maxFps: number): void {\n this.intervalMs = maxFps > 0 ? 1000 / maxFps : 0\n }\n}\n","import type { IDecoderProvider, DecoderSessionConfig, IDecoderSession } from '@camstack/types'\nimport { FfmpegDecoderSession } from './ffmpeg-decoder-session'\n\nconst SUPPORTED_CODECS = new Set(['h264', 'h265', 'hevc', 'mjpeg'])\n\nexport class FfmpegDecoderProvider implements IDecoderProvider {\n readonly id = 'ffmpeg'\n readonly name = 'FFmpeg Decoder'\n\n supportsCodec(codec: string): boolean {\n return SUPPORTED_CODECS.has(codec)\n }\n\n createSession(config: DecoderSessionConfig): IDecoderSession {\n return new FfmpegDecoderSession(config)\n }\n}\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport type {\n IStreamBroker,\n StreamSource,\n EncodedPacket,\n DecodedFrame,\n DecodedAudioChunk,\n DecodeOptions,\n BrokerStatus,\n BrokerStats,\n Unsubscribe,\n IDecoderSession,\n IRestreamer,\n IScopedLogger,\n} from '@camstack/types'\nimport type { DecoderRegistry } from './decoder-registry'\nimport { FrameDropper } from './frame-dropper'\nimport { StreamPipeServer } from './stream-pipe-server'\n\nconst DEFAULT_MAX_FPS = 5\nconst DEFAULT_FORMAT = 'jpeg' as const\nconst DEFAULT_SCALE = 1\n\nconst INITIAL_RECONNECT_DELAY_MS = 1_000\nconst MAX_RECONNECT_DELAY_MS = 30_000\nconst DECODER_TEARDOWN_GRACE_MS = 2_000\n\n/** H.264 NAL unit type 5 = IDR frame (keyframe) */\nconst H264_IDR_NAL_TYPE = 5\n/** H.265 NAL unit types 16-21 are IRAP (keyframe) pictures */\nconst H265_IRAP_NAL_TYPE_MIN = 16\nconst H265_IRAP_NAL_TYPE_MAX = 21\n\ninterface DecodedSubscriber {\n readonly callback: (frame: DecodedFrame) => void\n readonly frameDropper: FrameDropper\n}\n\nexport class StreamBroker implements IStreamBroker {\n readonly deviceId: string\n\n private _status: BrokerStatus = 'idle'\n private source: StreamSource | undefined\n private decoderSession: IDecoderSession | null = null\n private decoderUnsubscribe: Unsubscribe | null = null\n private readonly decoderRegistry: DecoderRegistry\n private readonly logger: IScopedLogger | undefined\n private readonly startedAt: number = Date.now()\n\n private ffmpegProcess: ChildProcess | undefined\n private reconnectTimer: ReturnType<typeof setTimeout> | undefined\n private reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n private manualStop = false\n private stopping = false\n private decoderTeardownTimer: ReturnType<typeof setTimeout> | undefined\n\n private restreamers: readonly IRestreamer[] = []\n private readonly pipeServer: StreamPipeServer\n\n private readonly encodedCallbacks = new Set<(packet: EncodedPacket) => void>()\n private readonly decodedSubscribers = new Map<symbol, DecodedSubscriber>()\n private readonly audioChunkCallbacks = new Set<(chunk: DecodedAudioChunk) => void>()\n\n constructor(deviceId: string, decoderRegistry: DecoderRegistry, logger?: IScopedLogger) {\n this.deviceId = deviceId\n this.decoderRegistry = decoderRegistry\n this.logger = logger\n this.pipeServer = new StreamPipeServer(logger?.child('pipe-server'))\n }\n\n get status(): BrokerStatus {\n return this._status\n }\n\n setRestreamers(restreamers: readonly IRestreamer[]): void {\n this.restreamers = restreamers\n }\n\n async start(source: StreamSource): Promise<void> {\n this.source = source\n this.manualStop = false\n this.stopping = false\n this._status = 'connecting'\n\n await this.pipeServer.start()\n this.logger?.info(\n `Pipe server started for ${this.deviceId} at ${this.pipeServer.getUrl()}`,\n )\n\n if (source.type === 'rtsp') {\n this.startRtspReader(source)\n } else {\n // For non-RTSP sources (raw TCP, pipe), data is pushed externally\n this._status = 'streaming'\n }\n }\n\n async stop(): Promise<void> {\n this.stopping = true\n this.manualStop = true\n this._status = 'stopped'\n\n this.killFfmpeg()\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = undefined\n }\n\n if (this.decoderTeardownTimer) {\n clearTimeout(this.decoderTeardownTimer)\n this.decoderTeardownTimer = undefined\n }\n\n if (this.decoderSession) {\n await this.destroyDecoder()\n }\n\n await this.pipeServer.stop()\n\n this.encodedCallbacks.clear()\n this.decodedSubscribers.clear()\n this.audioChunkCallbacks.clear()\n }\n\n pushEncodedPacket(packet: EncodedPacket): void {\n if (this.stopping) return\n\n for (const cb of this.encodedCallbacks) {\n cb(packet)\n }\n\n // Broadcast raw video bytes to TCP pipe clients (restreamers)\n if (packet.type === 'video') {\n this.pipeServer.broadcast(packet.data)\n }\n\n for (const restreamer of this.restreamers) {\n const streamId = `${this.deviceId}/video`\n restreamer.pushPacket(streamId, packet)\n }\n\n if (packet.type === 'video' && this.decoderSession) {\n this.decoderSession.pushPacket(packet)\n }\n }\n\n onEncodedData(callback: (packet: EncodedPacket) => void): Unsubscribe {\n this.encodedCallbacks.add(callback)\n\n return () => {\n this.encodedCallbacks.delete(callback)\n }\n }\n\n onDecodedFrame(callback: (frame: DecodedFrame) => void, options?: DecodeOptions): Unsubscribe {\n const maxFps = options?.maxFps ?? DEFAULT_MAX_FPS\n const subscriberId = Symbol('decoded-subscriber')\n const frameDropper = new FrameDropper(maxFps)\n\n const subscriber: DecodedSubscriber = { callback, frameDropper }\n this.decodedSubscribers.set(subscriberId, subscriber)\n\n // Cancel pending teardown if a new subscriber joins while grace period is active\n if (this.decoderTeardownTimer) {\n clearTimeout(this.decoderTeardownTimer)\n this.decoderTeardownTimer = undefined\n }\n\n // Create shared decoder on first subscriber (or reuse existing)\n if (!this.decoderSession && this.source) {\n this.createSharedDecoderSession(options)\n }\n\n return () => {\n this.decodedSubscribers.delete(subscriberId)\n\n // Schedule decoder teardown with grace period when last subscriber leaves\n if (this.decodedSubscribers.size === 0 && this.decoderSession) {\n this.decoderTeardownTimer = setTimeout(() => {\n this.decoderTeardownTimer = undefined\n if (this.decodedSubscribers.size === 0 && this.decoderSession) {\n this.destroyDecoder()\n }\n }, DECODER_TEARDOWN_GRACE_MS)\n }\n }\n }\n\n onDecodedAudioChunk(callback: (chunk: DecodedAudioChunk) => void): Unsubscribe {\n this.audioChunkCallbacks.add(callback)\n\n return () => {\n this.audioChunkCallbacks.delete(callback)\n }\n }\n\n getStats(): BrokerStats {\n const decoderStats = this.decoderSession?.getStats()\n\n return {\n status: this._status,\n inputFps: decoderStats?.inputFps ?? 0,\n decodeFps: decoderStats?.outputFps ?? 0,\n encodedSubscribers: this.encodedCallbacks.size,\n decodedSubscribers: this.decodedSubscribers.size,\n uptimeMs: Date.now() - this.startedAt,\n }\n }\n\n /**\n * Returns the local TCP URL that restreamers should connect to\n * instead of the camera's RTSP URL. This ensures the broker is\n * the sole reader from the camera.\n */\n getLocalStreamUrl(): string {\n return this.pipeServer.getUrl()\n }\n\n /** Returns the number of TCP pipe clients currently connected */\n getPipeClientCount(): number {\n return this.pipeServer.getClientCount()\n }\n\n private startRtspReader(source: StreamSource): void {\n const codec = source.videoCodec ?? 'h264'\n const isHevc = codec === 'h265' || codec === 'hevc'\n\n const args = [\n '-hide_banner',\n '-loglevel', 'error',\n '-rtsp_transport', 'tcp',\n '-i', source.url,\n '-c:v', 'copy',\n '-f', isHevc ? 'hevc' : 'h264',\n '-bsf:v', isHevc ? 'hevc_mp4toannexb' : 'h264_mp4toannexb',\n 'pipe:1',\n ]\n\n this.logger?.debug(`Spawning ffmpeg RTSP reader for ${this.deviceId}`, {\n url: source.url,\n codec,\n })\n\n const proc = spawn('ffmpeg', args, {\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n this.ffmpegProcess = proc\n\n // Swallow EPIPE errors on stdin during shutdown\n proc.stdin?.on('error', () => {})\n\n proc.stdout?.on('data', (chunk: Buffer) => {\n if (this.stopping) return\n\n this._status = 'streaming'\n this.reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS\n\n const keyframe = isHevc\n ? isHevcKeyframe(chunk)\n : isH264Keyframe(chunk)\n\n const packet: EncodedPacket = {\n type: 'video',\n data: chunk,\n pts: Date.now(),\n dts: Date.now(),\n keyframe,\n codec,\n }\n\n this.pushEncodedPacket(packet)\n })\n\n proc.stderr?.on('data', (data: Buffer) => {\n const msg = data.toString().trim()\n if (msg) {\n this.logger?.warn(`ffmpeg RTSP reader stderr [${this.deviceId}]: ${msg}`)\n }\n })\n\n proc.on('exit', (code, signal) => {\n this.ffmpegProcess = undefined\n\n if (this.manualStop) {\n return\n }\n\n this.logger?.warn(`ffmpeg RTSP reader exited for ${this.deviceId}`, {\n code,\n signal,\n })\n\n this._status = 'error'\n this.scheduleReconnect()\n })\n\n proc.on('error', (err) => {\n this.ffmpegProcess = undefined\n\n if (this.manualStop) {\n return\n }\n\n this.logger?.error(`ffmpeg RTSP reader error for ${this.deviceId}: ${err.message}`)\n this._status = 'error'\n this.scheduleReconnect()\n })\n }\n\n private scheduleReconnect(): void {\n if (this.manualStop || !this.source) {\n return\n }\n\n this.logger?.info(\n `Reconnecting RTSP reader for ${this.deviceId} in ${this.reconnectDelayMs}ms`,\n )\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = undefined\n if (!this.manualStop && this.source) {\n this._status = 'connecting'\n this.startRtspReader(this.source)\n }\n }, this.reconnectDelayMs)\n\n this.reconnectDelayMs = Math.min(\n this.reconnectDelayMs * 2,\n MAX_RECONNECT_DELAY_MS,\n )\n }\n\n private destroyDecoder(): Promise<void> {\n if (this.decoderUnsubscribe) {\n this.decoderUnsubscribe()\n this.decoderUnsubscribe = null\n }\n const session = this.decoderSession\n this.decoderSession = null\n return session?.destroy() ?? Promise.resolve()\n }\n\n private killFfmpeg(): void {\n if (this.ffmpegProcess) {\n this.ffmpegProcess.kill('SIGTERM')\n this.ffmpegProcess = undefined\n }\n }\n\n private createSharedDecoderSession(options?: DecodeOptions): void {\n const codec = this.source?.videoCodec ?? 'h264'\n const provider = this.decoderRegistry.getDecoder(codec)\n\n if (!provider) {\n return\n }\n\n // Shared decoder runs at unlimited FPS — each subscriber throttles locally\n const config = {\n codec,\n maxFps: 0,\n outputFormat: options?.format ?? DEFAULT_FORMAT,\n scale: options?.scale ?? DEFAULT_SCALE,\n }\n\n this.decoderSession = provider.createSession(config)\n\n // Single onFrame listener that fans out to all subscribers with per-subscriber throttling\n this.decoderUnsubscribe = this.decoderSession.onFrame((frame: DecodedFrame) => {\n for (const subscriber of this.decodedSubscribers.values()) {\n if (subscriber.frameDropper.shouldKeep()) {\n subscriber.callback(frame)\n }\n }\n })\n }\n}\n\n/**\n * Detect H.264 keyframe by scanning for IDR NAL unit (type 5)\n * in an Annex-B bitstream chunk.\n */\nfunction isH264Keyframe(data: Buffer): boolean {\n for (let i = 0; i < data.length - 4; i++) {\n // Look for start code: 0x00 0x00 0x01 or 0x00 0x00 0x00 0x01\n if (data[i] === 0 && data[i + 1] === 0) {\n let nalStart = -1\n if (data[i + 2] === 1) {\n nalStart = i + 3\n } else if (data[i + 2] === 0 && data[i + 3] === 1) {\n nalStart = i + 4\n }\n if (nalStart >= 0 && nalStart < data.length) {\n const nalType = data[nalStart]! & 0x1f\n if (nalType === H264_IDR_NAL_TYPE) {\n return true\n }\n }\n }\n }\n return false\n}\n\n/**\n * Detect H.265/HEVC keyframe by scanning for IRAP NAL units (types 16-21)\n * in an Annex-B bitstream chunk.\n */\nfunction isHevcKeyframe(data: Buffer): boolean {\n for (let i = 0; i < data.length - 5; i++) {\n if (data[i] === 0 && data[i + 1] === 0) {\n let nalStart = -1\n if (data[i + 2] === 1) {\n nalStart = i + 3\n } else if (data[i + 2] === 0 && data[i + 3] === 1) {\n nalStart = i + 4\n }\n if (nalStart >= 0 && nalStart < data.length) {\n const nalType = (data[nalStart]! >> 1) & 0x3f\n if (nalType >= H265_IRAP_NAL_TYPE_MIN && nalType <= H265_IRAP_NAL_TYPE_MAX) {\n return true\n }\n }\n }\n }\n return false\n}\n","import net from 'node:net'\nimport type { IScopedLogger } from '@camstack/types'\n\n/**\n * A lightweight TCP server that broadcasts raw encoded video bytes\n * (H.264/H.265 Annex-B) to all connected clients.\n *\n * Each StreamBroker owns one StreamPipeServer. Restreamers (e.g. go2rtc)\n * connect via `tcp://127.0.0.1:{port}` and receive the same byte stream\n * that the broker reads from the camera, eliminating duplicate RTSP\n * connections.\n */\nexport class StreamPipeServer {\n private readonly server: net.Server\n private readonly clients: Set<net.Socket> = new Set()\n private port = 0\n private started = false\n private readonly logger: IScopedLogger | undefined\n\n constructor(logger?: IScopedLogger) {\n this.logger = logger\n this.server = net.createServer((socket) => {\n this.handleConnection(socket)\n })\n\n this.server.on('error', (err) => {\n this.logger?.error(`StreamPipeServer error: ${err.message}`)\n })\n }\n\n async start(): Promise<void> {\n if (this.started) {\n return\n }\n\n await new Promise<void>((resolve, reject) => {\n this.server.once('error', reject)\n this.server.listen(0, '127.0.0.1', () => {\n this.server.removeListener('error', reject)\n const address = this.server.address() as net.AddressInfo\n this.port = address.port\n this.started = true\n this.logger?.debug(`StreamPipeServer listening on port ${this.port}`)\n resolve()\n })\n })\n }\n\n /**\n * Broadcast raw encoded bytes to all connected clients.\n * Silently drops data for clients that are slow or disconnected.\n */\n broadcast(data: Buffer): void {\n for (const client of this.clients) {\n if (client.destroyed) {\n this.clients.delete(client)\n continue\n }\n\n client.write(data, (err) => {\n if (err) {\n this.logger?.debug(\n `StreamPipeServer write error, removing client: ${err.message}`,\n )\n this.removeClient(client)\n }\n })\n }\n }\n\n getPort(): number {\n return this.port\n }\n\n getUrl(): string {\n return `tcp://127.0.0.1:${this.port}`\n }\n\n getClientCount(): number {\n return this.clients.size\n }\n\n isStarted(): boolean {\n return this.started\n }\n\n async stop(): Promise<void> {\n if (!this.started) {\n return\n }\n\n this.started = false\n\n for (const client of this.clients) {\n client.destroy()\n }\n this.clients.clear()\n\n await new Promise<void>((resolve) => {\n this.server.close(() => {\n this.logger?.debug('StreamPipeServer stopped')\n resolve()\n })\n })\n }\n\n private handleConnection(socket: net.Socket): void {\n this.clients.add(socket)\n this.logger?.debug(\n `StreamPipeServer client connected (total: ${this.clients.size})`,\n )\n\n socket.on('close', () => {\n this.removeClient(socket)\n })\n\n socket.on('error', () => {\n this.removeClient(socket)\n })\n }\n\n private removeClient(socket: net.Socket): void {\n const existed = this.clients.delete(socket)\n if (existed) {\n this.logger?.debug(\n `StreamPipeServer client disconnected (total: ${this.clients.size})`,\n )\n }\n if (!socket.destroyed) {\n socket.destroy()\n }\n }\n}\n","import type { StreamSource, IRestreamer, IScopedLogger } from '@camstack/types'\nimport { StreamBroker } from './stream-broker'\nimport type { DecoderRegistry } from './decoder-registry'\n\nexport class StreamBrokerManager {\n private readonly brokers = new Map<string, StreamBroker>()\n private restreamers: readonly IRestreamer[] = []\n private readonly logger: IScopedLogger\n\n constructor(\n private readonly decoderRegistry: DecoderRegistry,\n logger: IScopedLogger,\n ) {\n this.logger = logger\n }\n\n setRestreamers(restreamers: readonly IRestreamer[]): void {\n this.restreamers = restreamers\n\n for (const broker of this.brokers.values()) {\n broker.setRestreamers(restreamers)\n }\n\n this.logger.info(`Updated restreamers (count: ${restreamers.length})`)\n }\n\n getRestreamers(): readonly IRestreamer[] {\n return this.restreamers\n }\n\n /**\n * Create and start a broker.\n * brokerId can be a simple deviceId (backward compat) or a compound key like `${deviceId}/${streamId}`.\n */\n async createBroker(brokerId: string, source: StreamSource): Promise<StreamBroker> {\n const brokerLogger = this.logger.child(`broker:${brokerId}`)\n const broker = new StreamBroker(brokerId, this.decoderRegistry, brokerLogger)\n broker.setRestreamers(this.restreamers)\n await broker.start(source)\n this.brokers.set(brokerId, broker)\n return broker\n }\n\n getBroker(brokerId: string): StreamBroker | undefined {\n return this.brokers.get(brokerId)\n }\n\n async destroyBroker(brokerId: string): Promise<void> {\n const broker = this.brokers.get(brokerId)\n\n if (broker) {\n await broker.stop()\n this.brokers.delete(brokerId)\n }\n }\n\n /** Destroy all brokers matching a prefix (e.g., `${deviceId}/` to destroy all streams for a device) */\n async destroyBrokersForDevice(deviceId: string): Promise<void> {\n const prefix = `${deviceId}/`\n const toDestroy: string[] = []\n\n for (const brokerId of this.brokers.keys()) {\n if (brokerId === deviceId || brokerId.startsWith(prefix)) {\n toDestroy.push(brokerId)\n }\n }\n\n await Promise.all(toDestroy.map((id) => this.destroyBroker(id)))\n }\n\n listBrokers(): readonly StreamBroker[] {\n return [...this.brokers.values()]\n }\n\n async destroyAll(): Promise<void> {\n const stopPromises = [...this.brokers.values()].map((broker) => broker.stop())\n await Promise.all(stopPromises)\n this.brokers.clear()\n }\n}\n","/**\n * Event Persistence Service — unified persistence for detection events.\n *\n * A detection event is ONE entity with:\n * - Metadata (DB): id, timestamp, device, class, score, severity, description\n * - Media (filesystem): original frame, annotated frame, crops, faces, plates\n *\n * Both are stored together, flushed together, and deleted together by retention.\n *\n * Buffered I/O prevents filesystem flooding:\n * - Events buffered, flushed every 5s or at 50 events\n * - Media buffered, flushed every 3s or at 50MB\n * - High-priority media (thumbnails) flushed first\n */\n\nimport type { IScopedLogger, IStorageLocation, SystemEvent, VideoFrame } from '@camstack/types'\n\n/**\n * Inlined from the removed stages/object-snapshot-stage.ts.\n * These types are used by the persistence layer only.\n */\nexport interface ObjectSnapshotResult {\n readonly trackId: string\n readonly thumbnail?: Buffer\n readonly fullCrop?: Buffer\n}\n\nexport interface AnnotatedSnapshotResult {\n readonly thumbnail: Buffer\n readonly full: Buffer\n}\n\n// --- Types ---\n\nexport interface PersistableEvent {\n readonly id: string\n readonly timestamp: number\n readonly deviceId: string\n readonly category: string\n readonly className: string\n readonly score: number\n readonly trackId: string\n readonly severity: string\n readonly description: string\n readonly data: Record<string, unknown>\n /** IDs of associated media files (for cleanup) */\n readonly mediaFiles: readonly string[]\n}\n\ninterface MediaEntry {\n readonly path: string\n readonly data: Buffer\n readonly priority: 'high' | 'normal'\n readonly eventId: string\n}\n\nexport interface EventBufferStatus {\n readonly eventCount: number\n readonly mediaCount: number\n readonly mediaSizeMB: number\n}\n\nexport type TrackMediaType =\n | 'crop-thumb'\n | 'crop-full'\n | 'debug-annotated-thumb'\n | 'debug-annotated-full'\n | 'original'\n | 'original-thumb'\n | 'inline-crop'\n | 'unknown'\n\nexport interface TrackMediaFile {\n readonly path: string\n readonly type: TrackMediaType\n readonly data: Buffer\n /** Where the data was read from */\n readonly source: 'buffer' | 'storage'\n}\n\nfunction classifyMediaPath(path: string): TrackMediaType {\n if (path.endsWith('-thumb.jpg') && path.includes('/crops/')) return 'crop-thumb'\n if (path.endsWith('-full.jpg') && path.includes('/crops/')) return 'crop-full'\n if (path.includes('debug-annotated-thumb.jpg')) return 'debug-annotated-thumb'\n if (path.includes('debug-annotated-full.jpg')) return 'debug-annotated-full'\n if (path.includes('original-thumb.jpg')) return 'original-thumb'\n if (path.includes('original.jpg')) return 'original'\n if (path.includes('crop.jpg')) return 'inline-crop'\n return 'unknown'\n}\n\n// --- Dependencies ---\n\nexport interface EventPersistenceDeps {\n readonly getStorageLocation: (name: string) => IStorageLocation\n readonly subscribe: (filter: { category?: string }, handler: (event: SystemEvent) => void) => () => void\n readonly logger: IScopedLogger\n}\n\n// --- Service ---\n\nexport class EventPersistenceService {\n private eventBuffer: PersistableEvent[] = []\n private mediaBuffer: MediaEntry[] = []\n\n private eventFlushTimer?: ReturnType<typeof setInterval>\n private mediaFlushTimer?: ReturnType<typeof setInterval>\n\n private readonly EVENT_FLUSH_INTERVAL_MS = 5000\n private readonly MEDIA_FLUSH_INTERVAL_MS = 3000\n private readonly MAX_EVENT_BUFFER = 50\n private readonly MAX_MEDIA_BUFFER_MB = 50\n\n private readonly logger: IScopedLogger\n private readonly getStorageLocation: (name: string) => IStorageLocation\n private unsubscribe?: () => void\n\n constructor(private readonly deps: EventPersistenceDeps) {\n this.logger = deps.logger\n this.getStorageLocation = deps.getStorageLocation\n }\n\n start(): void {\n this.unsubscribe = this.deps.subscribe(\n { category: 'detection.event' },\n (event) => this.bufferEvent(event),\n )\n\n this.eventFlushTimer = setInterval(() => {\n this.flushEvents().catch(e => this.logger.warn(`Event flush failed: ${e}`))\n }, this.EVENT_FLUSH_INTERVAL_MS)\n\n this.mediaFlushTimer = setInterval(() => {\n this.flushMedia().catch(e => this.logger.warn(`Media flush failed: ${e}`))\n }, this.MEDIA_FLUSH_INTERVAL_MS)\n }\n\n stop(): void {\n this.unsubscribe?.()\n clearInterval(this.eventFlushTimer)\n clearInterval(this.mediaFlushTimer)\n this.flushEvents().catch(() => {})\n this.flushMedia().catch(() => {})\n }\n\n // --- Public: save media associated with an event ---\n\n /** Save object crops (thumbnail + full-res) for an event */\n saveDetectionCrops(eventId: string, crops: readonly ObjectSnapshotResult[]): void {\n for (const crop of crops) {\n if (crop.thumbnail) {\n this.bufferMedia(eventId, `events/${eventId}/crops/${crop.trackId}-thumb.jpg`, crop.thumbnail, 'high')\n }\n if (crop.fullCrop) {\n this.bufferMedia(eventId, `events/${eventId}/crops/${crop.trackId}-full.jpg`, crop.fullCrop, 'normal')\n }\n }\n }\n\n /** Save annotated frame (thumbnail + full-res) for an event */\n saveAnnotatedFrame(eventId: string, result: AnnotatedSnapshotResult): void {\n this.bufferMedia(eventId, `events/${eventId}/debug-annotated-thumb.jpg`, result.thumbnail, 'high')\n this.bufferMedia(eventId, `events/${eventId}/debug-annotated-full.jpg`, result.full, 'normal')\n }\n\n /** Save original frame for an event */\n async saveOriginalFrame(eventId: string, frame: VideoFrame): Promise<void> {\n const sharp = (await import('sharp')).default\n\n const jpeg = await sharp(frame.data, {\n raw: { width: frame.width, height: frame.height, channels: 3 },\n }).jpeg({ quality: 85 }).toBuffer()\n\n this.bufferMedia(eventId, `events/${eventId}/original.jpg`, jpeg, 'normal')\n\n const thumb = await sharp(jpeg)\n .resize(320, 240, { fit: 'inside' })\n .jpeg({ quality: 75 })\n .toBuffer()\n\n this.bufferMedia(eventId, `events/${eventId}/original-thumb.jpg`, thumb, 'high')\n }\n\n // --- Public: query track media ---\n\n /**\n * Get all media files for a specific event (from buffer or storage).\n * Returns immediately — serves from in-memory buffer if not yet flushed.\n */\n async getEventMedia(eventId: string): Promise<readonly TrackMediaFile[]> {\n const results: TrackMediaFile[] = []\n\n // 1. Check in-memory buffer first (not yet flushed)\n for (const entry of this.mediaBuffer) {\n if (entry.eventId === eventId) {\n results.push({\n path: entry.path,\n type: classifyMediaPath(entry.path),\n data: entry.data,\n source: 'buffer',\n })\n }\n }\n\n // 2. Check storage (already flushed)\n try {\n const location = this.getStorageLocation('events')\n if (location.files) {\n const files = await location.files.listFiles(`events/${eventId}/`)\n for (const file of files) {\n // Skip if already served from buffer\n if (results.some(r => r.path === file)) continue\n try {\n const data = await location.files.readFile(file)\n results.push({\n path: file,\n type: classifyMediaPath(file),\n data,\n source: 'storage',\n })\n } catch {\n // File may have been deleted by retention\n }\n }\n }\n } catch {\n // Storage not available\n }\n\n return results\n }\n\n /**\n * Get all media for a specific track across all events.\n * Useful for building a track timeline with all snapshots.\n */\n async getTrackMedia(trackId: string): Promise<readonly TrackMediaFile[]> {\n const results: TrackMediaFile[] = []\n\n // 1. Buffer: find media matching trackId in path\n for (const entry of this.mediaBuffer) {\n if (entry.path.includes(trackId)) {\n results.push({\n path: entry.path,\n type: classifyMediaPath(entry.path),\n data: entry.data,\n source: 'buffer',\n })\n }\n }\n\n // 2. Find events for this trackId in buffer\n const bufferedEventIds = this.eventBuffer\n .filter(e => e.trackId === trackId)\n .map(e => e.id)\n\n // 3. Find events for this trackId in storage\n try {\n const location = this.getStorageLocation('events')\n if (location.structured) {\n const stored = await location.structured.query('detection_events', {\n where: { trackId },\n orderBy: { field: 'timestamp', direction: 'asc' },\n limit: 100,\n })\n\n const allEventIds = new Set([\n ...bufferedEventIds,\n ...stored.map(r => r.id),\n ])\n\n // Get media for each event\n for (const eventId of allEventIds) {\n const eventMedia = await this.getEventMedia(eventId)\n for (const media of eventMedia) {\n if (!results.some(r => r.path === media.path)) {\n results.push(media)\n }\n }\n }\n }\n } catch {\n // Storage not available — serve from buffer only\n }\n\n return results\n }\n\n /**\n * Get all media for a device within a time range.\n */\n async getDeviceMedia(\n deviceId: string,\n since?: number,\n until?: number,\n ): Promise<readonly TrackMediaFile[]> {\n const results: TrackMediaFile[] = []\n\n try {\n const location = this.getStorageLocation('events')\n if (!location.structured) return results\n\n const events = await location.structured.query('detection_events', {\n where: { deviceId },\n whereBetween: since || until\n ? { timestamp: [since ?? 0, until ?? Date.now()] }\n : undefined,\n orderBy: { field: 'timestamp', direction: 'desc' },\n limit: 50,\n })\n\n for (const event of events) {\n const media = await this.getEventMedia(event.id)\n results.push(...media)\n }\n } catch {\n // Storage not available\n }\n\n return results\n }\n\n // --- Public: status ---\n\n getBufferStatus(): EventBufferStatus {\n return {\n eventCount: this.eventBuffer.length,\n mediaCount: this.mediaBuffer.length,\n mediaSizeMB: this.mediaBuffer.reduce((sum, m) => sum + m.data.length, 0) / (1024 * 1024),\n }\n }\n\n /** Force flush everything */\n async flush(): Promise<void> {\n await this.flushEvents()\n await this.flushMedia()\n }\n\n // --- Internal: buffering ---\n\n private bufferEvent(event: SystemEvent): void {\n // Collect paths of any media being buffered for this event\n const mediaPaths = this.mediaBuffer\n .filter(m => m.eventId === event.id)\n .map(m => m.path)\n\n const persistable: PersistableEvent = {\n id: event.id,\n timestamp: Date.now(),\n deviceId: (event.data.deviceId as string) ?? '',\n category: event.category,\n className: (event.data.className as string) ?? '',\n score: (event.data.score as number) ?? 0,\n trackId: (event.data.trackId as string) ?? '',\n severity: (event.data.severity as string) ?? '',\n description: (event.data.description as string) ?? '',\n data: event.data,\n mediaFiles: mediaPaths,\n }\n\n this.eventBuffer.push(persistable)\n\n // Buffer inline crop if present\n const detection = event.data.detection as Record<string, unknown> | undefined\n if (detection?.crop && Buffer.isBuffer(detection.crop)) {\n this.bufferMedia(event.id, `events/${event.id}/crop.jpg`, detection.crop as Buffer, 'high')\n }\n\n if (this.eventBuffer.length >= this.MAX_EVENT_BUFFER) {\n this.flushEvents().catch(e => this.logger.warn(`Auto-flush failed: ${e}`))\n }\n }\n\n private bufferMedia(eventId: string, path: string, data: Buffer, priority: 'high' | 'normal'): void {\n this.mediaBuffer.push({ eventId, path, data, priority })\n\n const totalMB = this.mediaBuffer.reduce((sum, m) => sum + m.data.length, 0) / (1024 * 1024)\n if (totalMB >= this.MAX_MEDIA_BUFFER_MB) {\n this.flushMedia().catch(e => this.logger.warn(`Media overflow flush failed: ${e}`))\n }\n }\n\n // --- Internal: flushing ---\n\n private async flushEvents(): Promise<void> {\n if (this.eventBuffer.length === 0) return\n\n const events = [...this.eventBuffer]\n this.eventBuffer = []\n\n try {\n const location = this.getStorageLocation('events')\n if (location.structured) {\n for (const event of events) {\n await location.structured.insert({\n collection: 'detection_events',\n id: event.id,\n data: event as unknown as Record<string, unknown>,\n })\n }\n this.logger.debug(`Flushed ${events.length} events`)\n }\n } catch (err) {\n this.logger.warn(`Failed to flush events: ${err}`)\n if (this.eventBuffer.length < this.MAX_EVENT_BUFFER * 2) {\n this.eventBuffer.push(...events)\n }\n }\n }\n\n private async flushMedia(): Promise<void> {\n if (this.mediaBuffer.length === 0) return\n\n // Sort: high priority first\n const sorted = [...this.mediaBuffer].sort((a, b) => {\n if (a.priority === 'high' && b.priority !== 'high') return -1\n if (a.priority !== 'high' && b.priority === 'high') return 1\n return 0\n })\n this.mediaBuffer = []\n\n try {\n const location = this.getStorageLocation('events')\n if (location.files) {\n for (const entry of sorted) {\n await location.files.writeFile(entry.path, entry.data)\n }\n const totalMB = sorted.reduce((s, e) => s + e.data.length, 0) / (1024 * 1024)\n this.logger.debug(`Flushed ${sorted.length} media files (${totalMB.toFixed(1)}MB)`)\n }\n } catch (err) {\n this.logger.warn(`Failed to flush media: ${err}`)\n }\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type { IScopedLogger, IStructuredStorage } from '@camstack/types'\n\n/**\n * Inlined from the removed sub-detectors/clip-recognizer.ts.\n * ClipRecognizer interface used by this service for embedding generation.\n */\nexport interface ClipRecognizer {\n getEmbedding(imageBuffer: Buffer): Promise<Float32Array>\n}\n\n/** Cosine similarity between two embedding vectors */\nexport function cosineSimilarity(a: Float32Array, b: Float32Array): number {\n if (a.length !== b.length) return 0\n let dotProduct = 0\n let normA = 0\n let normB = 0\n for (let i = 0; i < a.length; i++) {\n dotProduct += a[i]! * b[i]!\n normA += a[i]! * a[i]!\n normB += b[i]! * b[i]!\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB)\n return denom === 0 ? 0 : dotProduct / denom\n}\n\nexport interface KnownFaceEntry {\n readonly id: string\n readonly label: string\n readonly group?: string\n readonly embedding: readonly number[]\n readonly cropBase64: string\n readonly createdAt: number\n readonly updatedAt: number\n readonly source?: string\n readonly metadata?: Readonly<Record<string, unknown>>\n}\n\nexport interface KnownFacesDeps {\n readonly getStructuredStorage: () => IStructuredStorage\n readonly logger: IScopedLogger\n}\n\nconst COLLECTION = 'known_faces'\n\nexport class KnownFacesService {\n private readonly logger: IScopedLogger\n private readonly getStructuredStorage: () => IStructuredStorage\n\n constructor(deps: KnownFacesDeps) {\n this.logger = deps.logger\n this.getStructuredStorage = deps.getStructuredStorage\n }\n\n private getStorage(): IStructuredStorage {\n return this.getStructuredStorage()\n }\n\n /** Register a known face */\n async register(entry: KnownFaceEntry): Promise<void> {\n const storage = this.getStorage()\n await storage.insert({\n collection: COLLECTION,\n id: entry.id,\n data: {\n label: entry.label,\n group: entry.group ?? null,\n embedding: JSON.stringify(Array.from(entry.embedding)),\n cropBase64: entry.cropBase64,\n createdAt: entry.createdAt,\n updatedAt: entry.updatedAt,\n source: entry.source ?? null,\n metadata: entry.metadata ? JSON.stringify(entry.metadata) : null,\n },\n })\n this.logger.info(`Registered known face: ${entry.label} (${entry.id})`)\n }\n\n /** Get all known faces */\n async listAll(): Promise<readonly KnownFaceEntry[]> {\n const storage = this.getStorage()\n const records = await storage.query(COLLECTION)\n return records.map((r) => recordToEntry(r.id, r.data))\n }\n\n /** Delete a known face */\n async delete(id: string): Promise<void> {\n const storage = this.getStorage()\n await storage.delete(COLLECTION, id)\n this.logger.info(`Deleted known face: ${id}`)\n }\n\n /** Update a known face (reassign label or group) */\n async update(id: string, updates: Partial<Pick<KnownFaceEntry, 'label' | 'group'>>): Promise<void> {\n const storage = this.getStorage()\n const data: Record<string, unknown> = { updatedAt: Date.now() }\n if (updates.label !== undefined) data.label = updates.label\n if (updates.group !== undefined) data.group = updates.group\n await storage.update(COLLECTION, id, data)\n this.logger.info(`Updated known face ${id}: ${JSON.stringify(updates)}`)\n }\n\n /** Recalculate embedding for a face (re-run CLIP on the stored crop) */\n async recalculateEmbedding(id: string, clipRecognizer: ClipRecognizer): Promise<void> {\n const storage = this.getStorage()\n const records = await storage.query(COLLECTION, { where: { id } })\n if (records.length === 0) throw new Error(`Known face not found: ${id}`)\n\n const record = records[0]!\n const cropBase64 = record.data.cropBase64 as string\n const cropBuffer = Buffer.from(cropBase64, 'base64')\n\n const embedding = await clipRecognizer.getEmbedding(cropBuffer)\n await storage.update(COLLECTION, id, {\n embedding: JSON.stringify(Array.from(embedding)),\n updatedAt: Date.now(),\n })\n this.logger.info(`Recalculated embedding for known face: ${id}`)\n }\n\n /** Find best match for an embedding */\n async findMatch(\n embedding: Float32Array,\n threshold: number,\n ): Promise<{ readonly entry: KnownFaceEntry; readonly similarity: number } | null> {\n const entries = await this.listAll()\n let bestEntry: KnownFaceEntry | null = null\n let bestSimilarity = -1\n\n for (const entry of entries) {\n const knownEmbedding = new Float32Array(entry.embedding)\n const similarity = cosineSimilarity(embedding, knownEmbedding)\n if (similarity > bestSimilarity) {\n bestSimilarity = similarity\n bestEntry = entry\n }\n }\n\n if (bestEntry && bestSimilarity >= threshold) {\n return { entry: bestEntry, similarity: bestSimilarity }\n }\n\n return null\n }\n\n /** Batch import faces from crops */\n async batchRegister(\n entries: ReadonlyArray<{ readonly label: string; readonly cropBase64: string; readonly group?: string }>,\n clipRecognizer: ClipRecognizer,\n ): Promise<readonly KnownFaceEntry[]> {\n const results: KnownFaceEntry[] = []\n for (const input of entries) {\n const cropBuffer = Buffer.from(input.cropBase64, 'base64')\n const embedding = await clipRecognizer.getEmbedding(cropBuffer)\n const now = Date.now()\n const entry: KnownFaceEntry = {\n id: randomUUID(),\n label: input.label,\n group: input.group,\n embedding: Array.from(embedding),\n cropBase64: input.cropBase64,\n createdAt: now,\n updatedAt: now,\n }\n await this.register(entry)\n results.push(entry)\n }\n this.logger.info(`Batch registered ${results.length} known faces`)\n return results\n }\n}\n\nfunction recordToEntry(id: string, data: Record<string, unknown>): KnownFaceEntry {\n return {\n id,\n label: data.label as string,\n group: (data.group as string) ?? undefined,\n embedding: JSON.parse(data.embedding as string) as number[],\n cropBase64: data.cropBase64 as string,\n createdAt: data.createdAt as number,\n updatedAt: data.updatedAt as number,\n source: (data.source as string) ?? undefined,\n metadata: data.metadata ? JSON.parse(data.metadata as string) : undefined,\n }\n}\n","/**\n * Session Tracker Service — maintains persistent object tracking across\n * multiple analysis pipeline runs, linking tracked objects to their\n * CLIP embeddings for re-identification across cameras.\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type { IScopedLogger, IEventBus, AnalysisContext, ServerTrackedDetection as TrackedDetection } from '@camstack/types'\n\nexport interface SessionTrack {\n readonly trackId: string\n readonly deviceId: string\n readonly className: string\n readonly label?: string\n readonly firstSeen: number\n readonly lastSeen: number\n readonly totalFrames: number\n readonly lastDetection: TrackedDetection\n readonly state: string\n readonly positions: ReadonlyArray<{ readonly x: number; readonly y: number; readonly t: number }>\n readonly embedding?: Float32Array\n readonly globalId?: string\n readonly bestCrop?: Buffer\n}\n\nexport interface GlobalIdentity {\n readonly globalId: string\n readonly embedding: Float32Array\n readonly label?: string\n readonly firstSeen: number\n readonly lastSeen: number\n readonly deviceIds: ReadonlyArray<string>\n}\n\n/** Max positions to keep per track (~5 min at 1fps) */\nconst MAX_POSITIONS = 300\n\n/** Tracks not seen for this many ms are expired */\nconst TRACK_EXPIRY_MS = 30_000\n\nexport interface SessionTrackerDeps {\n readonly eventBus: IEventBus\n readonly logger: IScopedLogger\n}\n\nexport class SessionTrackerService {\n /** Active tracks per device: deviceId -> Map<trackId, SessionTrack> */\n private activeTracks: Map<string, Map<string, SessionTrack>> = new Map()\n\n /** Global re-id pool: globalId -> GlobalIdentity (same person across cameras) */\n private globalPool: Map<string, GlobalIdentity> = new Map()\n\n private readonly logger: IScopedLogger\n private readonly eventBus: IEventBus\n\n constructor(deps: SessionTrackerDeps) {\n this.logger = deps.logger\n this.eventBus = deps.eventBus\n }\n\n /** Update tracks from an analysis result */\n updateFromAnalysis(deviceId: string, ctx: AnalysisContext): void {\n const deviceTracks = this.getDeviceTracks(deviceId)\n const now = ctx.timestamp\n\n for (const td of ctx.trackedDetections) {\n const existing = deviceTracks.get(td.trackId)\n\n if (existing) {\n // Update existing track — create new object (immutable)\n const newPositions = [\n ...existing.positions,\n { x: td.detection.boundingBox?.[0] ?? 0, y: td.detection.boundingBox?.[1] ?? 0, t: now },\n ]\n const trimmedPositions = newPositions.length > MAX_POSITIONS\n ? newPositions.slice(newPositions.length - MAX_POSITIONS)\n : newPositions\n\n const updatedEmbedding = (td.recognitions.length > 0 && td.recognitions[0]?.embedding)\n ? new Float32Array(td.recognitions[0].embedding)\n : existing.embedding\n\n const updated: SessionTrack = {\n ...existing,\n lastSeen: now,\n totalFrames: existing.totalFrames + 1,\n lastDetection: td,\n state: td.tracking.state,\n positions: trimmedPositions,\n embedding: updatedEmbedding,\n }\n deviceTracks.set(td.trackId, updated)\n } else {\n // New track\n const track: SessionTrack = {\n trackId: td.trackId,\n deviceId,\n className: td.detection.className,\n label: td.detection.label,\n firstSeen: now,\n lastSeen: now,\n totalFrames: 1,\n lastDetection: td,\n state: td.tracking.state,\n positions: [{ x: td.detection.boundingBox?.[0] ?? 0, y: td.detection.boundingBox?.[1] ?? 0, t: now }],\n embedding: td.recognitions[0]?.embedding ? new Float32Array(td.recognitions[0].embedding) : undefined,\n globalId: undefined,\n bestCrop: td.crop,\n }\n\n deviceTracks.set(td.trackId, track)\n\n this.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'core', id: 'session-tracker' },\n category: 'session.track.new',\n data: { deviceId, trackId: td.trackId, className: td.detection.className },\n })\n }\n }\n\n // Expire old tracks (not seen for TRACK_EXPIRY_MS)\n for (const [trackId, track] of deviceTracks) {\n if (now - track.lastSeen > TRACK_EXPIRY_MS) {\n deviceTracks.delete(trackId)\n this.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'core', id: 'session-tracker' },\n category: 'session.track.expired',\n data: { deviceId, trackId, className: track.className, duration: now - track.firstSeen },\n })\n }\n }\n }\n\n /** Get all active tracks for a device */\n getActiveTracks(deviceId: string): readonly SessionTrack[] {\n return [...(this.activeTracks.get(deviceId)?.values() ?? [])]\n }\n\n /** Get all active tracks across all devices */\n getAllActiveTracks(): readonly SessionTrack[] {\n const all: SessionTrack[] = []\n for (const tracks of this.activeTracks.values()) {\n all.push(...tracks.values())\n }\n return all\n }\n\n /** Get a specific track */\n getTrack(deviceId: string, trackId: string): SessionTrack | null {\n return this.activeTracks.get(deviceId)?.get(trackId) ?? null\n }\n\n /** Get track count per device */\n getTrackCounts(): Record<string, { total: number; byClass: Record<string, number> }> {\n const counts: Record<string, { total: number; byClass: Record<string, number> }> = {}\n for (const [deviceId, tracks] of this.activeTracks) {\n const byClass: Record<string, number> = {}\n for (const track of tracks.values()) {\n byClass[track.className] = (byClass[track.className] ?? 0) + 1\n }\n counts[deviceId] = { total: tracks.size, byClass }\n }\n return counts\n }\n\n /** Clear all tracks for a device */\n clearDevice(deviceId: string): void {\n this.activeTracks.delete(deviceId)\n }\n\n /** Clear all tracks */\n clearAll(): void {\n this.activeTracks.clear()\n this.globalPool.clear()\n }\n\n /** Get the global identity pool (for diagnostics) */\n getGlobalPool(): ReadonlyMap<string, GlobalIdentity> {\n return this.globalPool\n }\n\n private getDeviceTracks(deviceId: string): Map<string, SessionTrack> {\n if (!this.activeTracks.has(deviceId)) {\n this.activeTracks.set(deviceId, new Map())\n }\n return this.activeTracks.get(deviceId)!\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type { IScopedLogger, IStorageLocation, IEventBus } from '@camstack/types'\n\n// ---------------------------------------------------------------------------\n// Config & Types\n// ---------------------------------------------------------------------------\n\nexport interface RetentionConfig {\n /** How often to run cleanup (ms). Default: 1 hour */\n readonly cleanupIntervalMs: number\n /** Detection events + associated media retention (days). Default: 30 */\n readonly detectionEventsDays: number\n /** Audio levels retention (days). Default: 7 */\n readonly audioLevelsDays: number\n /** DEPRECATED — snapshots are tied to events, use detectionEventsDays */\n readonly snapshotsDays: number\n /** Per-device overrides */\n readonly deviceOverrides?: Readonly<Record<string, Partial<Pick<RetentionConfig, 'detectionEventsDays' | 'audioLevelsDays' | 'snapshotsDays'>>>>\n}\n\nexport const DEFAULT_RETENTION: RetentionConfig = {\n cleanupIntervalMs: 60 * 60 * 1000, // 1 hour\n detectionEventsDays: 30,\n audioLevelsDays: 7,\n snapshotsDays: 14,\n}\n\nexport interface RetentionReport {\n readonly deletedEvents: number\n readonly deletedAudioRecords: number\n readonly deletedSnapshots: number\n}\n\n// ---------------------------------------------------------------------------\n// Dependencies\n// ---------------------------------------------------------------------------\n\nexport interface RetentionDeps {\n readonly getStorageLocation: (name: string) => IStorageLocation\n readonly getRetentionConfig: () => Partial<RetentionConfig> | undefined\n readonly eventBus: IEventBus\n readonly logger: IScopedLogger\n}\n\n// ---------------------------------------------------------------------------\n// Service\n// ---------------------------------------------------------------------------\n\nexport class RetentionService {\n private cleanupTimer?: ReturnType<typeof setInterval>\n private initialTimer?: ReturnType<typeof setTimeout>\n private config: RetentionConfig\n\n private readonly logger: IScopedLogger\n private readonly getStorageLocation: (name: string) => IStorageLocation\n private readonly eventBus: IEventBus\n\n constructor(private readonly deps: RetentionDeps) {\n this.logger = deps.logger\n this.getStorageLocation = deps.getStorageLocation\n this.eventBus = deps.eventBus\n\n // Load config from app configuration\n const retention = deps.getRetentionConfig()\n this.config = retention\n ? { ...DEFAULT_RETENTION, ...retention }\n : { ...DEFAULT_RETENTION }\n }\n\n start(): void {\n // Run initial cleanup after 5 minutes (let system boot first)\n this.initialTimer = setTimeout(() => {\n this.runCleanup().catch((err) => {\n this.logger.warn(`Initial cleanup failed: ${err}`)\n })\n }, 5 * 60 * 1000)\n\n // Schedule periodic cleanup\n this.cleanupTimer = setInterval(() => {\n this.runCleanup().catch((err) => {\n this.logger.warn(`Periodic cleanup failed: ${err}`)\n })\n }, this.config.cleanupIntervalMs)\n }\n\n stop(): void {\n if (this.initialTimer) {\n clearTimeout(this.initialTimer)\n this.initialTimer = undefined\n }\n if (this.cleanupTimer) {\n clearInterval(this.cleanupTimer)\n this.cleanupTimer = undefined\n }\n }\n\n /** Run all retention cleanup tasks */\n async runCleanup(): Promise<RetentionReport> {\n const report: RetentionReport = {\n deletedEvents: 0,\n deletedAudioRecords: 0,\n deletedSnapshots: 0,\n }\n\n try {\n // 1. Clean detection events + their associated media files\n const eventCutoff = Date.now() - this.config.detectionEventsDays * 24 * 60 * 60 * 1000\n const { dbDeleted: eventsDeleted, filesDeleted: snapshotsDeleted } =\n await this.cleanEventsWithMedia('detection_events', eventCutoff)\n\n // 2. Clean audio levels (DB only, no media)\n const audioCutoff = Date.now() - this.config.audioLevelsDays * 24 * 60 * 60 * 1000\n const audioDeleted = await this.cleanCollection('audio_levels', audioCutoff)\n\n // Build immutable report\n const finalReport: RetentionReport = {\n deletedEvents: eventsDeleted,\n deletedAudioRecords: audioDeleted,\n deletedSnapshots: snapshotsDeleted,\n }\n\n if (finalReport.deletedEvents > 0 || finalReport.deletedAudioRecords > 0 || finalReport.deletedSnapshots > 0) {\n this.logger.info(\n `Cleanup: ${finalReport.deletedEvents} events, ${finalReport.deletedAudioRecords} audio records, ${finalReport.deletedSnapshots} snapshots`,\n )\n\n this.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'core', id: 'retention' },\n category: 'retention.cleanup',\n data: finalReport as unknown as Record<string, unknown>,\n })\n }\n\n return finalReport\n } catch (err) {\n this.logger.warn(`Retention cleanup failed: ${err}`)\n }\n\n return report\n }\n\n /** Set retention config at runtime */\n setConfig(update: Partial<RetentionConfig>): void {\n this.config = { ...this.config, ...update }\n }\n\n /** Get current retention config */\n getConfig(): RetentionConfig {\n return { ...this.config }\n }\n\n /** Force immediate cleanup */\n async forceCleanup(): Promise<RetentionReport> {\n return this.runCleanup()\n }\n\n /**\n * Delete old events AND their associated media files.\n * Media is stored at events/{eventId}/ — delete the entire directory.\n */\n private async cleanEventsWithMedia(\n collection: string,\n cutoffTimestamp: number,\n ): Promise<{ dbDeleted: number; filesDeleted: number }> {\n let dbDeleted = 0\n let filesDeleted = 0\n\n try {\n const location = this.getStorageLocation('events')\n if (!location.structured) return { dbDeleted, filesDeleted }\n\n const old = await location.structured.query(collection, {\n whereBetween: { timestamp: [0, cutoffTimestamp] },\n limit: 1000,\n })\n\n for (const record of old) {\n const eventId = record.id\n\n // Delete associated media files\n if (location.files) {\n try {\n const files = await location.files.listFiles(`events/${eventId}/`)\n for (const file of files) {\n await location.files.deleteFile(file)\n filesDeleted++\n }\n } catch {\n // Media may not exist for every event\n }\n }\n\n // Delete DB record\n await location.structured.delete(collection, eventId)\n dbDeleted++\n }\n } catch {\n // Swallow errors — retention is best-effort\n }\n\n return { dbDeleted, filesDeleted }\n }\n\n private async cleanCollection(collection: string, cutoffTimestamp: number): Promise<number> {\n try {\n const location = this.getStorageLocation('events')\n if (!location.structured) return 0\n\n // Query old records\n const old = await location.structured.query(collection, {\n whereBetween: { timestamp: [0, cutoffTimestamp] },\n limit: 1000, // batch delete\n })\n\n // Delete each\n let deleted = 0\n for (const record of old) {\n await location.structured.delete(collection, record.id)\n deleted++\n }\n\n return deleted\n } catch {\n return 0\n }\n }\n}\n","/**\n * Track Trail Service — persists the complete trajectory of tracked objects.\n *\n * For each active track, records:\n * - Every position (x, y, timestamp) at detection FPS\n * - Periodic snapshot crops at configurable intervals\n * - Track metadata (class, first/last seen, total distance, zones visited)\n */\n\nimport type { IScopedLogger, IStorageLocation, AnalysisContext, ServerTrackedDetection as TrackedDetection } from '@camstack/types'\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport interface TrackCaptureConfig {\n /** Enable trail capture for this device (default: false) */\n readonly enabled: boolean\n /** How often to save a snapshot of the same track (ms, default: 2000 = every 2s) */\n readonly snapshotIntervalMs: number\n /** Max trail entries per track before pruning oldest (default: 500) */\n readonly maxTrailLength: number\n /** Save thumbnail crops in trail snapshots (default: true) */\n readonly saveThumbnails: boolean\n /** Thumbnail size (default: 120x120) */\n readonly thumbnailSize: { readonly width: number; readonly height: number }\n}\n\nconst DEFAULT_CONFIG: TrackCaptureConfig = {\n enabled: false,\n snapshotIntervalMs: 2000,\n maxTrailLength: 500,\n saveThumbnails: true,\n thumbnailSize: { width: 120, height: 120 },\n}\n\n// ---------------------------------------------------------------------------\n// Trail data structures\n// ---------------------------------------------------------------------------\n\nexport interface TrackPosition {\n readonly x: number\n readonly y: number\n readonly timestamp: number\n /** Bounding box at this position [x, y, w, h] normalized */\n readonly bbox: readonly [number, number, number, number]\n}\n\nexport interface TrackSnapshot {\n readonly timestamp: number\n readonly position: TrackPosition\n /** Path to saved thumbnail in storage */\n readonly thumbnailPath: string\n}\n\nexport interface TrackTrail {\n readonly trackId: string\n readonly deviceId: string\n readonly className: string\n readonly label?: string\n readonly firstSeen: number\n readonly lastSeen: number\n /** All recorded positions (at detection FPS) */\n readonly positions: readonly TrackPosition[]\n /** Periodic snapshots (at configured interval) */\n readonly snapshots: readonly TrackSnapshot[]\n /** Total distance traveled (normalized units, 0-1 = full frame width) */\n readonly totalDistance: number\n /** All zones this track has visited */\n readonly zonesVisited: readonly string[]\n /** Whether the track is still active */\n readonly active: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Dependencies\n// ---------------------------------------------------------------------------\n\nexport interface TrackTrailDeps {\n readonly getStorageLocation: (name: string) => IStorageLocation\n readonly logger: IScopedLogger\n}\n\n// ---------------------------------------------------------------------------\n// Service\n// ---------------------------------------------------------------------------\n\nexport class TrackTrailService {\n private readonly logger: IScopedLogger\n private readonly getStorageLocation: (name: string) => IStorageLocation\n\n /** Per-device config */\n private readonly deviceConfigs: Map<string, TrackCaptureConfig> = new Map()\n\n /** Active trails: trackId -> mutable trail state */\n private readonly activeTrails: Map<string, MutableTrail> = new Map()\n\n /** Last snapshot time per track (for interval throttling) */\n private readonly lastSnapshotTime: Map<string, number> = new Map()\n\n constructor(deps: TrackTrailDeps) {\n this.logger = deps.logger\n this.getStorageLocation = deps.getStorageLocation\n }\n\n // --- Config ---\n\n setConfig(deviceId: string, config: Partial<TrackCaptureConfig>): void {\n this.deviceConfigs.set(deviceId, { ...DEFAULT_CONFIG, ...config })\n }\n\n getConfig(deviceId: string): TrackCaptureConfig {\n return this.deviceConfigs.get(deviceId) ?? { ...DEFAULT_CONFIG }\n }\n\n // --- Called after each analysis pass ---\n\n /**\n * Record trail data from an analysis context.\n * Called by the analysis pipeline addon after each frame's analysis.\n */\n async recordFrame(ctx: AnalysisContext): Promise<void> {\n const config = this.getConfig(ctx.deviceId)\n if (!config.enabled) return\n\n const activeTrackIds = new Set<string>()\n\n for (const td of ctx.trackedDetections) {\n activeTrackIds.add(td.trackId)\n await this.updateTrail(ctx, td, config)\n }\n\n // Mark trails as inactive if their track is gone\n for (const [trackId, trail] of this.activeTrails) {\n if (trail.deviceId === ctx.deviceId && !activeTrackIds.has(trackId)) {\n trail.active = false\n // Persist final trail to storage\n await this.persistTrail(trail)\n this.activeTrails.delete(trackId)\n this.lastSnapshotTime.delete(trackId)\n }\n }\n }\n\n // --- Query ---\n\n /** Get the live trail for an active track (from memory) */\n getActiveTrail(trackId: string): TrackTrail | null {\n const trail = this.activeTrails.get(trackId)\n return trail ? this.toImmutable(trail) : null\n }\n\n /** Get all active trails for a device */\n getActiveTrails(deviceId: string): readonly TrackTrail[] {\n const results: TrackTrail[] = []\n for (const trail of this.activeTrails.values()) {\n if (trail.deviceId === deviceId) {\n results.push(this.toImmutable(trail))\n }\n }\n return results\n }\n\n /** Get a persisted trail from storage (after track ended) */\n async getPersistedTrail(trackId: string): Promise<TrackTrail | null> {\n try {\n const location = this.getStorageLocation('events')\n if (!location.structured) return null\n\n const results = await location.structured.query('track_trails', {\n where: { trackId },\n limit: 1,\n })\n\n if (results.length === 0) return null\n return results[0]!.data as unknown as TrackTrail\n } catch {\n return null\n }\n }\n\n /** Get trail from memory (active) or storage (persisted) */\n async getTrail(trackId: string): Promise<TrackTrail | null> {\n return this.getActiveTrail(trackId) ?? this.getPersistedTrail(trackId)\n }\n\n /** List recent persisted trails for a device */\n async listTrails(deviceId: string, options?: {\n since?: number\n until?: number\n limit?: number\n className?: string\n }): Promise<readonly TrackTrail[]> {\n const results: TrackTrail[] = []\n\n // Active trails\n for (const trail of this.activeTrails.values()) {\n if (trail.deviceId === deviceId) {\n if (options?.className && trail.className !== options.className) continue\n results.push(this.toImmutable(trail))\n }\n }\n\n // Persisted trails\n try {\n const location = this.getStorageLocation('events')\n if (location.structured) {\n const stored = await location.structured.query('track_trails', {\n where: { deviceId, ...(options?.className ? { className: options.className } : {}) },\n whereBetween: options?.since || options?.until\n ? { firstSeen: [options.since ?? 0, options.until ?? Date.now()] }\n : undefined,\n orderBy: { field: 'firstSeen', direction: 'desc' },\n limit: options?.limit ?? 50,\n })\n\n for (const record of stored) {\n const trail = record.data as unknown as TrackTrail\n // Don't duplicate active trails\n if (!this.activeTrails.has(trail.trackId)) {\n results.push(trail)\n }\n }\n }\n } catch {\n // Storage not available\n }\n\n return results\n }\n\n // --- Internal ---\n\n private async updateTrail(\n ctx: AnalysisContext,\n td: TrackedDetection,\n config: TrackCaptureConfig,\n ): Promise<void> {\n let trail = this.activeTrails.get(td.trackId)\n\n const bbox = td.detection.boundingBox\n if (!bbox || bbox.length < 4) return\n\n const center = { x: bbox[0]! + bbox[2]! / 2, y: bbox[1]! + bbox[3]! / 2 }\n\n const position: TrackPosition = {\n x: center.x,\n y: center.y,\n timestamp: ctx.timestamp,\n bbox: [bbox[0]!, bbox[1]!, bbox[2]!, bbox[3]!] as readonly [number, number, number, number],\n }\n\n if (!trail) {\n trail = {\n trackId: td.trackId,\n deviceId: ctx.deviceId,\n className: td.detection.className,\n label: td.detection.label,\n firstSeen: ctx.timestamp,\n lastSeen: ctx.timestamp,\n positions: [position],\n snapshots: [],\n totalDistance: 0,\n zonesVisited: [...td.zones],\n active: true,\n }\n this.activeTrails.set(td.trackId, trail)\n } else {\n // Compute distance from last position\n const lastPos = trail.positions[trail.positions.length - 1]\n const dist = lastPos\n ? Math.sqrt((center.x - lastPos.x) ** 2 + (center.y - lastPos.y) ** 2)\n : 0\n\n trail.lastSeen = ctx.timestamp\n trail.totalDistance += dist\n\n // Add position (respect max length)\n if (trail.positions.length >= config.maxTrailLength) {\n // Downsample: keep every other position in the first half\n const half = Math.floor(trail.positions.length / 2)\n const downsampled = trail.positions.filter((_, i) => i >= half || i % 2 === 0)\n trail.positions = [...downsampled, position]\n } else {\n trail.positions = [...trail.positions, position]\n }\n\n // Merge new zones\n for (const zone of td.zones) {\n if (!trail.zonesVisited.includes(zone)) {\n trail.zonesVisited = [...trail.zonesVisited, zone]\n }\n }\n }\n\n // Periodic snapshot capture\n const lastSnapshot = this.lastSnapshotTime.get(td.trackId) ?? 0\n if (ctx.timestamp - lastSnapshot >= config.snapshotIntervalMs && td.crop) {\n await this.captureSnapshot(trail, td, position, config)\n this.lastSnapshotTime.set(td.trackId, ctx.timestamp)\n }\n }\n\n private async captureSnapshot(\n trail: MutableTrail,\n td: TrackedDetection,\n position: TrackPosition,\n config: TrackCaptureConfig,\n ): Promise<void> {\n if (!td.crop || !config.saveThumbnails) return\n\n try {\n const sharp = (await import('sharp')).default\n const thumb = await sharp(td.crop)\n .resize(config.thumbnailSize.width, config.thumbnailSize.height, { fit: 'cover' })\n .jpeg({ quality: 75 })\n .toBuffer()\n\n const path = `debug-trails/${trail.trackId}/${position.timestamp}.jpg`\n\n // Write to storage\n const location = this.getStorageLocation('events')\n if (location.files) {\n await location.files.writeFile(path, thumb)\n }\n\n trail.snapshots = [...trail.snapshots, {\n timestamp: position.timestamp,\n position,\n thumbnailPath: path,\n }]\n } catch {\n // Snapshot capture failed — non-critical, continue\n }\n }\n\n private async persistTrail(trail: MutableTrail): Promise<void> {\n try {\n const location = this.getStorageLocation('events')\n if (!location.structured) return\n\n await location.structured.insert({\n collection: 'track_trails',\n id: trail.trackId,\n data: this.toImmutable(trail) as unknown as Record<string, unknown>,\n })\n\n this.logger.debug(\n `Trail persisted: ${trail.trackId} (${trail.positions.length} positions, ${trail.snapshots.length} snapshots, ${trail.totalDistance.toFixed(3)} distance)`,\n )\n } catch (err) {\n this.logger.warn(`Failed to persist trail ${trail.trackId}: ${err}`)\n }\n }\n\n private toImmutable(trail: MutableTrail): TrackTrail {\n return {\n trackId: trail.trackId,\n deviceId: trail.deviceId,\n className: trail.className,\n label: trail.label,\n firstSeen: trail.firstSeen,\n lastSeen: trail.lastSeen,\n positions: [...trail.positions],\n snapshots: [...trail.snapshots],\n totalDistance: trail.totalDistance,\n zonesVisited: [...trail.zonesVisited],\n active: trail.active,\n }\n }\n}\n\n// Mutable internal state (not exported)\ninterface MutableTrail {\n readonly trackId: string\n readonly deviceId: string\n readonly className: string\n readonly label?: string\n firstSeen: number\n lastSeen: number\n positions: TrackPosition[]\n snapshots: TrackSnapshot[]\n totalDistance: number\n zonesVisited: string[]\n active: boolean\n}\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n CapabilityProviderMap,\n IConfigurable,\n ConfigUISchema,\n FfmpegConfig,\n IStreamingEngine,\n IPipelineManager,\n INetworkQualityTracker,\n} from '@camstack/types'\nimport { DecoderRegistry } from './stream-broker/decoder-registry'\nimport { FfmpegDecoderProvider } from './stream-broker/ffmpeg-decoder-provider'\nimport { StreamBrokerManager } from './stream-broker/stream-broker-manager'\nimport { EventPersistenceService } from './persistence/event-persistence'\nimport { KnownFacesService } from './persistence/known-faces'\nimport { SessionTrackerService } from './persistence/session-tracker'\nimport { RetentionService } from './persistence/retention'\nimport { TrackTrailService } from './persistence/track-trail'\nimport type { IAnalysisDataPersistence } from './persistence/types'\n\n// Recording engine imports — dynamic to avoid hard dep on better-sqlite3 at load time\ntype RecordingCoordinator = import('./recording/recording-coordinator').RecordingCoordinator\ntype RecordingDb = import('./recording/recording-db').RecordingDb\n\nexport interface RecordingEngineDependencies {\n readonly streamingEngine: IStreamingEngine\n readonly pipelineManager: IPipelineManager\n readonly networkTracker: INetworkQualityTracker\n}\n\nexport type RecordingEngineV2Dependencies = RecordingEngineDependencies\n\nexport class PipelineAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'pipeline',\n name: 'CamStack Pipeline',\n version: '0.1.0',\n capabilities: [\n { name: 'stream-broker', mode: 'singleton' as const },\n { name: 'recording-engine', mode: 'singleton' as const },\n { name: 'analysis-data-persistence', mode: 'singleton' as const },\n ],\n }\n\n // Stream broker\n private brokerManager: StreamBrokerManager | null = null\n\n // Recording engine\n private coordinator: RecordingCoordinator | null = null\n private recordingDb: RecordingDb | null = null\n private sqliteDb: import('better-sqlite3').Database | null = null\n private recordingDeps: RecordingEngineDependencies | null = null\n private currentRecordingConfig = {\n ffmpegPath: 'ffmpeg',\n hwaccel: undefined as FfmpegConfig['hwaccel'] | undefined,\n threads: undefined as number | undefined,\n segmentDurationSeconds: 4,\n defaultRetentionDays: 30,\n }\n\n // Analysis persistence\n private persistenceFacade: IAnalysisDataPersistence | null = null\n\n setRecordingDependencies(deps: RecordingEngineDependencies): void {\n this.recordingDeps = deps\n }\n\n async initialize(context: AddonContext): Promise<void> {\n // --- 1. Stream broker ---\n const registry = new DecoderRegistry()\n registry.register(new FfmpegDecoderProvider(), 50)\n this.brokerManager = new StreamBrokerManager(registry, context.logger)\n context.logger.info('Stream broker manager initialized')\n\n // --- 2. Recording engine ---\n if (this.recordingDeps) {\n try {\n const Database = (await import('better-sqlite3')).default\n const path = await import('node:path')\n const { detectPlatformDefaults } = await import('./recording/ffmpeg-config')\n const { RecordingDb: RecDb } = await import('./recording/recording-db')\n const { RecordingCoordinator: RecCoord } = await import('./recording/recording-coordinator')\n\n const storagePath = context.locationPaths.recordings\n const dbPath = path.join(context.locationPaths.data, 'camstack.db')\n this.sqliteDb = new Database(dbPath)\n this.recordingDb = new RecDb(this.sqliteDb)\n this.recordingDb.initialize()\n\n const ffmpegPath = (context.addonConfig.ffmpegPath as string) ?? this.currentRecordingConfig.ffmpegPath\n const detectedFfmpegConfig = detectPlatformDefaults(ffmpegPath)\n\n const globalFfmpegConfig: Partial<FfmpegConfig> = {\n path: ffmpegPath,\n hwaccel: (context.addonConfig.hwaccel as FfmpegConfig['hwaccel']) ?? this.currentRecordingConfig.hwaccel,\n threads: (context.addonConfig.threads as number) ?? this.currentRecordingConfig.threads,\n }\n\n const segmentDurationSeconds = (context.addonConfig.segmentDurationSeconds as number)\n ?? this.currentRecordingConfig.segmentDurationSeconds\n const defaultRetentionDays = (context.addonConfig.defaultRetentionDays as number)\n ?? this.currentRecordingConfig.defaultRetentionDays\n\n this.currentRecordingConfig = {\n ffmpegPath,\n hwaccel: globalFfmpegConfig.hwaccel,\n threads: globalFfmpegConfig.threads,\n segmentDurationSeconds,\n defaultRetentionDays,\n }\n\n const fileStorage = context.storage.files\n if (!fileStorage) {\n throw new Error('PipelineAddon:file storage not available in addon context')\n }\n\n this.coordinator = new RecCoord({\n db: this.recordingDb,\n logger: context.logger,\n eventBus: context.eventBus,\n streamingEngine: this.recordingDeps.streamingEngine,\n pipelineManager: this.recordingDeps.pipelineManager,\n networkTracker: this.recordingDeps.networkTracker,\n fileStorage,\n storagePath,\n globalFfmpegConfig,\n detectedFfmpegConfig,\n segmentDurationSec: segmentDurationSeconds,\n })\n await this.coordinator.start()\n context.logger.info('Recording Engine initialized')\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n context.logger.warn(`Recording Engine failed to initialize: ${msg}`)\n }\n }\n\n // --- 3. Analysis persistence ---\n const eventPersistence = new EventPersistenceService({\n getStorageLocation: () => context.storage,\n subscribe: (filter, handler) => context.eventBus.subscribe(filter, handler),\n logger: context.logger.child('EventPersistence'),\n })\n\n const knownFaces = new KnownFacesService({\n getStructuredStorage: () => {\n if (!context.storage.structured) {\n throw new Error('Structured storage not available for analysis persistence')\n }\n return context.storage.structured\n },\n logger: context.logger.child('KnownFaces'),\n })\n\n const sessionTracker = new SessionTrackerService({\n eventBus: context.eventBus,\n logger: context.logger.child('SessionTracker'),\n })\n\n const retention = new RetentionService({\n getStorageLocation: () => context.storage,\n getRetentionConfig: () => context.addonConfig.retention as Record<string, unknown> | undefined,\n eventBus: context.eventBus,\n logger: context.logger.child('Retention'),\n })\n\n const trackTrail = new TrackTrailService({\n getStorageLocation: () => context.storage,\n logger: context.logger.child('TrackTrail'),\n })\n\n eventPersistence.start()\n retention.start()\n\n this.persistenceFacade = {\n eventPersistence,\n knownFaces,\n sessionTracker,\n retention,\n trackTrail,\n }\n\n context.logger.info('Analysis persistence initialized')\n }\n\n async shutdown(): Promise<void> {\n // Stream broker\n await this.brokerManager?.destroyAll()\n this.brokerManager = null\n\n // Recording engine\n if (this.coordinator) {\n this.coordinator.stop()\n this.coordinator = null\n }\n if (this.sqliteDb) {\n this.sqliteDb.close()\n this.sqliteDb = null\n }\n this.recordingDb = null\n\n // Analysis persistence\n if (this.persistenceFacade) {\n this.persistenceFacade.eventPersistence.stop()\n this.persistenceFacade.retention.stop()\n this.persistenceFacade = null\n }\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n switch (name) {\n case 'stream-broker':\n return this.brokerManager as unknown as CapabilityProviderMap[K]\n case 'recording-engine':\n return this.coordinator as unknown as CapabilityProviderMap[K]\n case 'analysis-data-persistence':\n return this.persistenceFacade as unknown as CapabilityProviderMap[K]\n default:\n return null\n }\n }\n\n // --- Recording engine accessors ---\n\n getCoordinator(): RecordingCoordinator {\n if (!this.coordinator) throw new Error('PipelineAddon recording not initialized')\n return this.coordinator\n }\n\n getRecordingDb(): RecordingDb {\n if (!this.recordingDb) throw new Error('PipelineAddon recording not initialized')\n return this.recordingDb\n }\n\n // --- IConfigurable ---\n\n getConfigSchema(): ConfigUISchema {\n return { sections: [] }\n }\n\n getConfig(): Record<string, unknown> {\n return { ...this.currentRecordingConfig }\n }\n\n async onConfigChange(config: Record<string, unknown>): Promise<void> {\n this.currentRecordingConfig = {\n ffmpegPath: (config.ffmpegPath as string) ?? this.currentRecordingConfig.ffmpegPath,\n hwaccel: (config.hwaccel as FfmpegConfig['hwaccel']) ?? this.currentRecordingConfig.hwaccel,\n threads: (config.threads as number) ?? this.currentRecordingConfig.threads,\n segmentDurationSeconds: (config.segmentDurationSeconds as number) ?? this.currentRecordingConfig.segmentDurationSeconds,\n defaultRetentionDays: (config.defaultRetentionDays as number) ?? this.currentRecordingConfig.defaultRetentionDays,\n }\n }\n}\n\nexport default PipelineAddon\n","// --- Unified PipelineAddon ---\nexport { PipelineAddon } from './addon'\nexport type { RecordingEngineDependencies, RecordingEngineV2Dependencies } from './addon'\n\n// --- Stream broker ---\nexport { StreamBrokerManager } from './stream-broker/stream-broker-manager'\nexport { StreamBroker } from './stream-broker/stream-broker'\nexport { DecoderRegistry } from './stream-broker/decoder-registry'\nexport { FfmpegDecoderProvider } from './stream-broker/ffmpeg-decoder-provider'\nexport { FfmpegDecoderSession } from './stream-broker/ffmpeg-decoder-session'\nexport { FrameDropper } from './stream-broker/frame-dropper'\nexport { StreamPipeServer } from './stream-broker/stream-pipe-server'\nexport type { BrokerStats, BrokerStatus } from '@camstack/types'\n\n// --- Recording engine ---\nexport { RecordingCoordinator } from './recording/recording-coordinator'\nexport { RecordingDb } from './recording/recording-db'\nexport type { StorageUsage, AvailabilityRange } from './recording/recording-db'\nexport type * from './recording/types'\n\n// --- Analysis persistence ---\nexport type { IAnalysisDataPersistence } from './persistence/types'\nexport { EventPersistenceService } from './persistence/event-persistence'\nexport type {\n EventPersistenceDeps,\n PersistableEvent,\n EventBufferStatus,\n TrackMediaType,\n TrackMediaFile,\n ObjectSnapshotResult,\n AnnotatedSnapshotResult,\n} from './persistence/event-persistence'\nexport { KnownFacesService, cosineSimilarity } from './persistence/known-faces'\nexport type { KnownFacesDeps, KnownFaceEntry, ClipRecognizer } from './persistence/known-faces'\nexport { SessionTrackerService } from './persistence/session-tracker'\nexport type { SessionTrackerDeps, SessionTrack, GlobalIdentity } from './persistence/session-tracker'\nexport { RetentionService, DEFAULT_RETENTION } from './persistence/retention'\nexport type { RetentionDeps, RetentionConfig, RetentionReport } from './persistence/retention'\nexport { TrackTrailService } from './persistence/track-trail'\nexport type {\n TrackTrailDeps,\n TrackCaptureConfig,\n TrackPosition,\n TrackSnapshot,\n TrackTrail,\n} from './persistence/track-trail'\n\n// --- Analysis pipeline ---\nexport { BuiltinAnalysisAddon } from './analysis/analysis-addon'\n","// packages/addon-pipeline-analysis/src/addon.ts\nimport type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n CapabilityProviderMap,\n IAnalysisAddon,\n IScopedLogger,\n} from '@camstack/types'\n\n/**\n * Addon wrapper around @camstack/lib-pipeline-analysis.\n * Dynamically imports the analysis package (tolerates it being absent).\n * Declares the 'analysis-pipeline' singleton capability.\n */\nexport class BuiltinAnalysisAddon implements ICamstackAddon {\n readonly manifest: AddonManifest = {\n id: 'pipeline-analysis',\n name: 'Analysis Pipeline',\n version: '1.0.0',\n capabilities: ['analysis-pipeline'] as unknown as AddonManifest['capabilities'],\n }\n\n private pipeline: IAnalysisAddon | null = null\n private logger: IScopedLogger | null = null\n\n async initialize(context: AddonContext): Promise<void> {\n this.logger = context.logger ?? null\n\n try {\n const mod = await import('@camstack/lib-pipeline-analysis')\n const instance = new mod.AnalysisPipeline()\n // AnalysisPipeline implements IAnalysisAddon\n this.pipeline = instance as unknown as IAnalysisAddon\n this.logger?.info('pipeline-analysis loaded successfully')\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.logger?.warn(`pipeline-analysis not available: ${msg} -- analysis features disabled`)\n }\n }\n\n async shutdown(): Promise<void> {\n this.pipeline = null\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'analysis-pipeline') {\n return this.pipeline as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n /** Whether the pipeline-analysis package loaded successfully */\n isAvailable(): boolean {\n return this.pipeline !== null\n }\n}\n\nexport default BuiltinAnalysisAddon\n"],"mappings":"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","names":["platform","spawn","spawn","spawn","path","sharp","randomUUID","randomUUID","sharp","path","path","detectPlatformDefaults"]}
|