@apocaliss92/nodelink-js 0.1.8 → 0.1.9
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/README.md +9 -6
- package/dist/{DiagnosticsTools-MTXG65O3.js → DiagnosticsTools-EC7DADEQ.js} +2 -2
- package/dist/{chunk-MC2BRLLE.js → chunk-TZFZ5WJX.js} +71 -9
- package/dist/chunk-TZFZ5WJX.js.map +1 -0
- package/dist/{chunk-AFUYHWWQ.js → chunk-YUBYINJF.js} +673 -64
- package/dist/chunk-YUBYINJF.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +739 -68
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +2 -2
- package/dist/index.cjs +2041 -191
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +418 -4
- package/dist/index.d.ts +365 -3
- package/dist/index.js +1202 -32
- package/dist/index.js.map +1 -1
- package/package.json +13 -3
- package/dist/chunk-AFUYHWWQ.js.map +0 -1
- package/dist/chunk-MC2BRLLE.js.map +0 -1
- /package/dist/{DiagnosticsTools-MTXG65O3.js.map → DiagnosticsTools-EC7DADEQ.js.map} +0 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/reolink/AutodiscoveryClient.ts","../src/reolink/baichuan/types.ts","../src/reolink/baichuan/endpoints-server.ts","../src/rtsp/server.ts","../src/rfc/rfc4571.ts","../src/rfc/rfc4571-server.ts","../src/multifocal/compositeStream.ts","../src/rfc/replay-http-server.ts","../src/baichuan/stream/BaichuanHttpStreamServer.ts","../src/baichuan/stream/BaichuanMjpegServer.ts","../src/baichuan/stream/MjpegTransformer.ts","../src/baichuan/stream/BaichuanWebRTCServer.ts","../src/multifocal/compositeRtspServer.ts"],"sourcesContent":["import type { Logger } from \"../debug/DebugConfig\";\nimport { discoverReolinkDevices, discoverViaHttpScan, discoverViaUdpBroadcast, type DiscoveredDevice, type DiscoveryOptions } from \"./discovery\";\n\nexport interface AutodiscoveryClientOptions {\n /** Network CIDR to scan (e.g., \"192.168.1.0/24\"). If not provided, auto-detects local network */\n networkCidr?: string;\n /** Username to use for authentication attempts (default: \"admin\") */\n username?: string;\n /** Password to use for authentication attempts (default: empty, will try unauthenticated) */\n password?: string;\n /** Timeout per HTTP probe in milliseconds (default: 2000) */\n httpProbeTimeoutMs?: number;\n /** Maximum number of concurrent HTTP probes (default: 50) */\n maxConcurrentProbes?: number;\n /** Logger instance for debug output */\n logger?: Logger;\n /** Ports to scan for HTTP (default: [80, 443]) */\n httpPorts?: number[];\n /** Interval between discovery scans in milliseconds (default: 60000 = 60 seconds) */\n scanIntervalMs?: number;\n /** Whether to start discovery automatically on construction (default: false) */\n autoStart?: boolean;\n /** Discovery method to use: \"http\" (TCP/IPC), \"udp\" (broadcast), or \"both\" (default: \"http\") */\n discoveryMethod?: \"http\" | \"udp\" | \"both\";\n /** Timeout for UDP broadcast in milliseconds (default: 5000) */\n udpBroadcastTimeoutMs?: number;\n}\n\n/**\n * Client per discovery continuato di telecamere Reolink sulla rete.\n * Supporta TCP/IPC (HTTP/HTTPS scanning) e/o UDP broadcast discovery.\n * \n * Mantiene una lista aggiornata delle telecamere discoverate e offre\n * metodi per ottenere la lista corrente e controllare il processo di discovery.\n * \n * @example\n * ```typescript\n * const client = new AutodiscoveryClient({\n * username: \"admin\",\n * password: \"password\",\n * scanIntervalMs: 30000, // 30 secondi\n * autoStart: true,\n * });\n * \n * // Ottieni la lista corrente\n * const devices = client.getDiscoveredDevices();\n * console.log(`Trovate ${devices.length} telecamere`);\n * \n * // Ferma il discovery\n * await client.stop();\n * ```\n */\nexport class AutodiscoveryClient {\n private readonly options: AutodiscoveryClientOptions & { scanIntervalMs: number };\n private readonly discoveredDevices = new Map<string, DiscoveredDevice>();\n private scanTimer: NodeJS.Timeout | null = null;\n private isRunning = false;\n private currentScanPromise: Promise<void> | null = null;\n\n /**\n * Costruttore del client di autodiscovery.\n * \n * @param options - Opzioni di configurazione per il discovery\n */\n constructor(options: AutodiscoveryClientOptions = {}) {\n this.options = {\n scanIntervalMs: options.scanIntervalMs ?? 60_000, // Default: 60 secondi\n autoStart: options.autoStart ?? false,\n };\n if (options.networkCidr !== undefined) {\n this.options.networkCidr = options.networkCidr;\n }\n if (options.username !== undefined) {\n this.options.username = options.username;\n }\n if (options.password !== undefined) {\n this.options.password = options.password;\n }\n if (options.httpProbeTimeoutMs !== undefined) {\n this.options.httpProbeTimeoutMs = options.httpProbeTimeoutMs;\n }\n if (options.maxConcurrentProbes !== undefined) {\n this.options.maxConcurrentProbes = options.maxConcurrentProbes;\n }\n if (options.logger !== undefined) {\n this.options.logger = options.logger;\n }\n if (options.httpPorts !== undefined) {\n this.options.httpPorts = options.httpPorts;\n }\n if (options.discoveryMethod !== undefined) {\n this.options.discoveryMethod = options.discoveryMethod;\n }\n if (options.udpBroadcastTimeoutMs !== undefined) {\n this.options.udpBroadcastTimeoutMs = options.udpBroadcastTimeoutMs;\n }\n\n if (this.options.autoStart) {\n this.start();\n }\n }\n\n /**\n * Avvia il discovery continuato.\n * Se già in esecuzione, non fa nulla.\n */\n start(): void {\n if (this.isRunning) {\n this.options.logger?.warn?.(\"[Autodiscovery] Discovery already running\");\n return;\n }\n\n this.isRunning = true;\n this.options.logger?.log?.(\n `[Autodiscovery] Starting continuous discovery (interval: ${this.options.scanIntervalMs}ms)`,\n );\n\n // Esegui il primo scan immediatamente\n this.performScan();\n\n // Poi programma gli scan successivi\n this.scheduleNextScan();\n }\n\n /**\n * Ferma il discovery continuato.\n * Se non è in esecuzione, non fa nulla.\n */\n stop(): void {\n if (!this.isRunning) {\n this.options.logger?.warn?.(\"[Autodiscovery] Discovery not running\");\n return;\n }\n\n this.isRunning = false;\n if (this.scanTimer) {\n clearTimeout(this.scanTimer);\n this.scanTimer = null;\n }\n\n this.options.logger?.log?.(\"[Autodiscovery] Discovery stopped\");\n }\n\n /**\n * Restituisce la lista corrente delle telecamere discoverate.\n * \n * @returns Array di dispositivi discoverati, ordinati per host\n */\n getDiscoveredDevices(): DiscoveredDevice[] {\n return Array.from(this.discoveredDevices.values()).sort((a, b) => {\n // Ordina per host (IP address)\n return a.host.localeCompare(b.host);\n });\n }\n\n /**\n * Restituisce il numero di telecamere attualmente discoverate.\n * \n * @returns Numero di dispositivi discoverati\n */\n getDeviceCount(): number {\n return this.discoveredDevices.size;\n }\n\n /**\n * Verifica se il discovery è attualmente in esecuzione.\n * \n * @returns `true` se il discovery è in esecuzione, `false` altrimenti\n */\n isActive(): boolean {\n return this.isRunning;\n }\n\n /**\n * Forza un scan immediato (non aspetta l'intervallo programmato).\n * Se uno scan è già in corso, attende il completamento prima di avviarne uno nuovo.\n * \n * @returns Promise che si risolve quando lo scan è completato\n */\n async scanNow(): Promise<void> {\n if (this.currentScanPromise) {\n this.options.logger?.log?.(\"[Autodiscovery] Scan already in progress, waiting for completion...\");\n await this.currentScanPromise;\n return;\n }\n\n await this.performScan();\n }\n\n /**\n * Rimuove un dispositivo dalla lista (utile se si sa che non è più disponibile).\n * \n * @param host - Indirizzo IP del dispositivo da rimuovere\n * @returns `true` se il dispositivo è stato rimosso, `false` se non era presente\n */\n removeDevice(host: string): boolean {\n const removed = this.discoveredDevices.delete(host);\n if (removed) {\n this.options.logger?.log?.(`[Autodiscovery] Device removed: ${host}`);\n }\n return removed;\n }\n\n /**\n * Pulisce tutte le telecamere discoverate dalla lista.\n */\n clearDevices(): void {\n const count = this.discoveredDevices.size;\n this.discoveredDevices.clear();\n this.options.logger?.log?.(`[Autodiscovery] Removed ${count} device(s) from list`);\n }\n\n /**\n * Esegue un singolo scan della rete.\n */\n private async performScan(): Promise<void> {\n const scanPromise = (async () => {\n try {\n this.options.logger?.log?.(\"[Autodiscovery] Starting scan...\");\n\n const discoveryMethod = this.options.discoveryMethod ?? \"http\";\n const discoveryOptions: DiscoveryOptions = {\n enableHttpScanning: discoveryMethod === \"http\" || discoveryMethod === \"both\",\n enableUdpDiscovery: discoveryMethod === \"udp\" || discoveryMethod === \"both\",\n };\n if (this.options.networkCidr !== undefined) {\n discoveryOptions.networkCidr = this.options.networkCidr;\n }\n if (this.options.username !== undefined) {\n discoveryOptions.username = this.options.username;\n }\n if (this.options.password !== undefined) {\n discoveryOptions.password = this.options.password;\n }\n if (this.options.httpProbeTimeoutMs !== undefined) {\n discoveryOptions.httpProbeTimeoutMs = this.options.httpProbeTimeoutMs;\n }\n if (this.options.maxConcurrentProbes !== undefined) {\n discoveryOptions.maxConcurrentProbes = this.options.maxConcurrentProbes;\n }\n if (this.options.logger !== undefined) {\n discoveryOptions.logger = this.options.logger;\n }\n if (this.options.httpPorts !== undefined) {\n discoveryOptions.httpPorts = this.options.httpPorts;\n }\n if (this.options.udpBroadcastTimeoutMs !== undefined) {\n discoveryOptions.udpBroadcastTimeoutMs = this.options.udpBroadcastTimeoutMs;\n }\n\n // Use the appropriate discovery method(s)\n let discovered: DiscoveredDevice[] = [];\n if (discoveryMethod === \"http\" || discoveryMethod === \"both\") {\n const httpDevices = await discoverViaHttpScan(discoveryOptions);\n discovered.push(...httpDevices);\n }\n if (discoveryMethod === \"udp\" || discoveryMethod === \"both\") {\n const udpDevices = await discoverViaUdpBroadcast(discoveryOptions);\n discovered.push(...udpDevices);\n }\n\n // Aggiorna la mappa dei dispositivi\n const beforeCount = this.discoveredDevices.size;\n const newDevices: DiscoveredDevice[] = [];\n const updatedDevices: DiscoveredDevice[] = [];\n\n for (const device of discovered) {\n const existing = this.discoveredDevices.get(device.host);\n if (existing) {\n // Aggiorna il dispositivo esistente con nuove informazioni\n const updated = this.mergeDeviceInfo(existing, device);\n if (updated) {\n updatedDevices.push(updated);\n }\n } else {\n // Nuovo dispositivo\n this.discoveredDevices.set(device.host, { ...device });\n newDevices.push(device);\n }\n }\n\n const afterCount = this.discoveredDevices.size;\n this.options.logger?.log?.(\n `[Autodiscovery] Scan completed: ${newDevices.length} new, ${updatedDevices.length} updated, total: ${afterCount}`,\n );\n\n // Log each new device immediately with full details\n for (const device of newDevices) {\n const details: string[] = [];\n if (device.model) details.push(`Model: ${device.model}`);\n if (device.name) details.push(`Name: ${device.name}`);\n if (device.uid) details.push(`UID: ${device.uid}`);\n if (device.firmwareVersion) details.push(`Firmware: ${device.firmwareVersion}`);\n if (device.httpPort) details.push(`HTTP Port: ${device.httpPort}`);\n if (device.httpsPort) details.push(`HTTPS Port: ${device.httpsPort}`);\n details.push(`Discovery Method: ${device.discoveryMethod}`);\n if (device.supportsHttps !== undefined) details.push(`HTTPS: ${device.supportsHttps}`);\n if (device.httpAccessible !== undefined) details.push(`HTTP Accessible: ${device.httpAccessible}`);\n\n this.options.logger?.log?.(\n `[Autodiscovery] 🆕 NEW DEVICE DISCOVERED - Host: ${device.host}${details.length > 0 ? ` | ${details.join(\" | \")}` : \"\"}`,\n );\n }\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n this.options.logger?.error?.(`[Autodiscovery] Error during scan: ${msg}`);\n }\n })();\n\n this.currentScanPromise = scanPromise;\n await scanPromise;\n this.currentScanPromise = null;\n }\n\n /**\n * Unisce le informazioni di un dispositivo esistente con quelle di un nuovo scan.\n * Restituisce il dispositivo aggiornato se ci sono state modifiche, altrimenti `null`.\n */\n private mergeDeviceInfo(existing: DiscoveredDevice, updated: DiscoveredDevice): DiscoveredDevice | null {\n let hasChanges = false;\n\n // Aggiorna i campi mancanti o più recenti\n if (!existing.model && updated.model) {\n existing.model = updated.model;\n hasChanges = true;\n }\n if (!existing.uid && updated.uid) {\n existing.uid = updated.uid;\n hasChanges = true;\n }\n if (!existing.name && updated.name) {\n existing.name = updated.name;\n hasChanges = true;\n }\n if (!existing.firmwareVersion && updated.firmwareVersion) {\n existing.firmwareVersion = updated.firmwareVersion;\n hasChanges = true;\n }\n if (updated.httpPort && !existing.httpPort) {\n existing.httpPort = updated.httpPort;\n hasChanges = true;\n }\n if (updated.httpsPort && !existing.httpsPort) {\n existing.httpsPort = updated.httpsPort;\n hasChanges = true;\n }\n if (updated.supportsHttps !== undefined && existing.supportsHttps !== updated.supportsHttps) {\n existing.supportsHttps = updated.supportsHttps;\n hasChanges = true;\n }\n if (updated.httpAccessible !== undefined && existing.httpAccessible !== updated.httpAccessible) {\n existing.httpAccessible = updated.httpAccessible;\n hasChanges = true;\n }\n\n return hasChanges ? existing : null;\n }\n\n /**\n * Programma il prossimo scan.\n */\n private scheduleNextScan(): void {\n if (!this.isRunning) {\n return;\n }\n\n this.scanTimer = setTimeout(() => {\n this.scanTimer = null;\n if (this.isRunning) {\n this.performScan().finally(() => {\n // Programma il prossimo scan solo se siamo ancora in esecuzione\n if (this.isRunning) {\n this.scheduleNextScan();\n }\n });\n }\n }, this.options.scanIntervalMs);\n }\n}\n\n","/**\n * TypeScript types for Baichuan API responses and parameters.\n * Based on Reolink API documentation.\n */\n\nexport interface OsdChannel {\n enable: number;\n name: string;\n pos: string;\n}\n\nexport interface OsdTime {\n enable: number;\n pos: string;\n}\n\nexport interface OsdConfig {\n channel: number;\n osdChannel: OsdChannel;\n osdTime: OsdTime;\n watermark: number;\n bgcolor?: number;\n}\n\nexport interface AIDetectionState {\n alarm_state: number;\n support: number;\n}\n\nexport type AiKey = \"dog_cat\" | \"face\" | \"other\" | \"package\" | \"people\";\n\nexport interface AIState {\n channel: number;\n alarm_state?: number;\n support?: number;\n [key: string]: unknown; // Allow additional AI detection types\n}\n\nexport interface PtzPreset {\n id: number;\n name: string;\n}\n\nexport type PtzPosition = {\n pan?: number;\n tilt?: number;\n};\n\nexport type ZoomFocusTriplet = {\n minPos: number;\n maxPos: number;\n curPos: number;\n};\n\nexport type ZoomFocusStatus = {\n zoom?: ZoomFocusTriplet;\n focus?: ZoomFocusTriplet;\n};\n\nexport interface PtzCommand {\n action: \"start\" | \"stop\";\n command:\n | \"Left\"\n | \"Right\"\n | \"Up\"\n | \"Down\"\n | \"ZoomIn\"\n | \"ZoomOut\"\n | \"FocusNear\"\n | \"FocusFar\";\n speed?: number;\n /** Optional: how long to move before sending an automatic stop (ms). Set to 0 to disable auto-stop. */\n autoStopMs?: number;\n}\n\nexport interface BatteryInfo {\n batteryPercent?: number;\n /** Known values include: \"charging\", \"chargeComplete\", \"none\". */\n chargeStatus?: string;\n /** Charging source/port status, e.g. \"solarPanel\". */\n adapterStatus?: string;\n /** Low power flag (0/1). */\n lowPower?: number;\n /** Battery voltage (mV) when available. */\n voltage?: number;\n /** Battery current (mA) when available (can be negative while charging). */\n current?: number;\n /** Battery temperature (°C) when available. */\n temperature?: number;\n /** Battery version info when available (commonly 2). */\n batteryVersion?: number;\n sleeping?: boolean;\n channel?: number;\n}\n\n/**\n * Minimal per-channel device summary returned by `ReolinkBaichuanApi.getDevicesInfo()`.\n *\n * This is optimized for speed and returns only common identity + battery/doorbell hints.\n */\nexport interface ReolinkBaichuanDeviceSummary {\n channel: number;\n name?: string;\n uid?: string;\n /** Camera IP (best-effort via Baichuan GetNetworkInfo/GetGeneral). */\n ip?: string;\n /** Camera MAC address (best-effort via Baichuan GetNetworkInfo/GetGeneral). */\n mac?: string;\n /** Active link / link type when available (varies by firmware). */\n activeLink?: string;\n /** Channel state from cmd_id 145 push (e.g. connect/disconnect/none). */\n state?: string;\n /** Channel index from cmd_id 145 push (often 1-based device slot). */\n index?: number;\n /** Supported streams as reported by cmd_id 145 push (e.g. mainStream,subStream,externStream). */\n streamSupport?: string[];\n wifiState?: string;\n networkSegment?: string;\n changed?: boolean;\n abilityChanged?: boolean;\n online?: boolean;\n sleeping?: boolean;\n loginState?: string;\n updatedAtMs?: number;\n /** Model string (Baichuan <type>). */\n model?: string;\n /** True when the channel likely belongs to a multifocal/dual-lens device (best-effort by model). */\n isMultifocal?: boolean;\n /** Device serial number when available. */\n serialNumber?: string;\n /** Battery percentage (0-100) when available. */\n battery?: number;\n /** True when the channel is a battery camera (best-effort via SupportInfo). */\n isBattery?: boolean;\n /** True when the channel is a doorbell (best-effort via SupportInfo). */\n isDoorbell?: boolean;\n}\n\n/** Best-effort network identity for a host or a channel. */\nexport interface ReolinkBaichuanNetworkInfo {\n ip?: string;\n mac?: string;\n activeLink?: string;\n}\n\nexport type ReolinkNvrChannelInfo = {\n channel: number;\n /** Camera model string (Baichuan: <type>, CGI: typeInfo). */\n model?: string;\n /** Camera name (OSD/name). */\n name?: string;\n /** Camera UID (when available via NVR CGI). */\n uid?: string;\n /** Online flag (when available via NVR CGI). */\n online?: boolean;\n /** Sleep flag (when available via NVR CGI, common for battery cams). */\n sleep?: boolean;\n /** Firmware version (Baichuan: firmwareVersion, CGI: firmVer). */\n firmwareVersion?: string;\n /** Board info (CGI: boardInfo). */\n boardInfo?: string;\n /** Where the info came from for this channel. */\n source: \"baichuan\" | \"cgi\";\n};\n\nexport type ReolinkBaichuanPorts = Record<string, Record<string, number>>;\n\nexport type ReolinkBaichuanChannelInfo = {\n typeInfo?: string;\n firmVer?: string;\n firmwareVersion?: string;\n boardInfo?: string;\n pakSuffix?: string;\n name?: string;\n};\n\nexport type ReolinkBaichuanChannelIdentity = {\n channel: number;\n model: string;\n name: string;\n};\n\n/**\n * NVR/HUB grouping of channels that belong to the same physical device.\n *\n * Multifocal cameras typically appear as 2+ channels that share the same UID and/or serial number.\n */\nexport interface ReolinkNvrDeviceGroupSummary {\n /** Stable group key (usually uid:* or sn:*). */\n key: string;\n uid?: string;\n serialNumber?: string;\n name?: string;\n model?: string;\n channels: number[];\n /** True when the group likely represents a multi-channel (multifocal) device. */\n isMultifocal: boolean;\n /** Human-readable heuristic used for isMultifocal. */\n reason: string;\n}\n\nexport type ReolinkNvrDeviceGroupsResult = {\n channels: number[];\n groups: ReolinkNvrDeviceGroupSummary[];\n /** Map channel -> group key. */\n channelToGroup: Record<number, string>;\n};\n\nexport type SleepState = \"awake\" | \"sleeping\" | \"unknown\";\n\n/**\n * Best-effort sleep status inference.\n *\n * Note: for battery cameras there is no universally reliable, purely passive \"sleep\" flag.\n * This status is inferred without sending any request to the camera.\n */\nexport interface SleepStatus {\n state: SleepState;\n reason: string;\n lastRxAtMs?: number;\n idleMs?: number;\n}\n\nexport type WakeUpOptions = {\n /** Timeout per single attempt (default: 20000). */\n timeoutMs?: number;\n /** Number of attempts (default: 3). */\n attempts?: number;\n /** Delay after an attempt that \"unlocks\" the camera (default: 1500). */\n waitAfterWakeMs?: number;\n /** Delay between failed attempts (default: 1500). */\n backoffMs?: number;\n /**\n * If true, closes the connection and forces a reconnect before retrying.\n * Default: true for UDP (battery), false for TCP.\n */\n reconnect?: boolean;\n};\n\nexport type LastSleepProbe = {\n atMs: number;\n status: SleepStatus;\n};\n\nexport interface PirState {\n enabled: boolean;\n state?: {\n enable?: number;\n channel?: number;\n [key: string]: unknown;\n };\n}\n\nexport type SirenState = {\n enabled: boolean;\n};\n\nexport interface WhiteLedState {\n enabled: boolean;\n brightness?: number;\n}\n\nimport type { XmlJsonValue } from \"./utils/xml\";\n\n/** Public snapshot entry returned by `ReolinkBaichuanApi.getChannelInfoFromPushCache()`. */\nexport type ChannelPushCacheEntry = {\n name: string;\n uid: string;\n state: string;\n index?: number;\n streamSupport?: string[];\n wifiState?: string;\n networkSegment?: string;\n changed?: boolean;\n abilityChanged?: boolean;\n online?: boolean;\n sleeping?: boolean;\n loginState?: string;\n updatedAtMs?: number;\n};\n\n/** Internal live entry used to maintain cmd_id 145 push state. */\nexport type ChannelPushDataEntry = ChannelPushCacheEntry & {\n /** Lowercased state for convenience (e.g. \"connect\", \"none\"). */\n stateLower?: string;\n updatedAtMs: number;\n};\n\n// --------------------\n// PCAP-derived push cache (cmd_id 78/79/464/484/623/723)\n// --------------------\n\nexport type BaichuanCachedPush<T> = {\n updatedAtMs: number;\n value: T;\n};\n\nexport type BaichuanVideoInputPush = {\n channelId?: number;\n bright?: number;\n contrast?: number;\n saturation?: number;\n hue?: number;\n sharpen?: number;\n corridorAbility?: number;\n corridorMode?: string;\n};\n\nexport type BaichuanSerialPush = {\n channelId?: number;\n baudRate?: number;\n dataBit?: string;\n stopBit?: string;\n parity?: string;\n flowControl?: string;\n controlProtocol?: string;\n controlAddress?: number;\n};\n\nexport type BaichuanNetInfoPush = {\n netType?: string;\n signal?: number;\n};\n\nexport type BaichuanDingdongListPush = {\n maxPairNumber?: number;\n /** -1 commonly means \"all\" / host-level. */\n channel?: number;\n pairedCount?: number;\n pairedList?: XmlJsonValue;\n};\n\nexport type BaichuanSleepStatusPush = {\n /** Parsed from <sleep>0/1 when present. */\n sleep?: boolean;\n rawSleep?: number;\n /** Some firmwares use <statusList>... for per-channel statuses. */\n statusListPresent?: boolean;\n};\n\nexport type BaichuanCoordinatePointListPush = {\n count: number;\n points?: Array<{ x: number; y: number }>;\n};\n\nexport type BaichuanSettingsPushCacheEntry = {\n /** Map key (0-based) when known; -1 for host-level pushes. */\n channel: number;\n videoInput?: BaichuanCachedPush<BaichuanVideoInputPush>;\n serial?: BaichuanCachedPush<BaichuanSerialPush>;\n netInfo?: BaichuanCachedPush<BaichuanNetInfoPush>;\n dingdongList?: BaichuanCachedPush<BaichuanDingdongListPush>;\n sleepStatus?: BaichuanCachedPush<BaichuanSleepStatusPush>;\n coordinatePointList?: BaichuanCachedPush<BaichuanCoordinatePointListPush>;\n};\n\n// --------------------\n// PCAP-derived getters (cmd_id 44/54/81/115/116/146/208/574/...)\n// --------------------\n\nexport type BaichuanOsdDatetime = {\n channelId?: number;\n enable?: boolean;\n topLeftX?: number;\n topLeftY?: number;\n width?: number;\n height?: number;\n language?: string;\n};\n\nexport type BaichuanOsdChannelName = {\n channelId?: number;\n name?: string;\n enable?: boolean;\n topLeftX?: number;\n topLeftY?: number;\n enWatermark?: boolean;\n enBgcolor?: boolean;\n};\n\nexport type BaichuanGetOsdDatetimeResult = {\n osdDatetime?: BaichuanOsdDatetime;\n osdChannelName?: BaichuanOsdChannelName;\n};\n\nexport type BaichuanRecordCfg = {\n channelId?: number;\n cycle?: number;\n recordAbility?: number;\n smartRecord?: number;\n recordDelayTime?: number;\n preRecordTime?: number;\n packageTime?: number;\n cycleList?: number[];\n};\n\nexport type BaichuanRecordSchedule = {\n channelId?: number;\n enable?: boolean;\n typeScheduleList?: Array<{ type: string; valueTable: string }>;\n};\n\nexport type BaichuanWifiSignal = {\n signal?: number;\n};\n\nexport type BaichuanWifi = {\n protocol?: number;\n mode?: string;\n ssid?: string;\n /** Masked in many firmwares (e.g. ***********) */\n key?: string;\n channel?: number;\n isNVRSsid?: number;\n};\n\nexport type BaichuanLedState = {\n channelId?: number;\n ledVersion?: number;\n state?: string;\n lightState?: string;\n};\n\nexport type BaichuanSleepState = {\n sleep?: number;\n mode?: number;\n panPos?: number;\n tiltPos?: number;\n imageName?: string;\n};\n\nexport type BaichuanStreamEncodeTable = {\n type?: string;\n width?: number;\n height?: number;\n videoEncType?: number;\n videoEncTypeList?: number[];\n defaultFramerate?: number;\n defaultBitrate?: number;\n framerateTable?: number[];\n bitrateTable?: number[];\n defaultGop?: number;\n};\n\nexport type BaichuanStreamInfo = {\n channelBits?: number;\n encodeTables: BaichuanStreamEncodeTable[];\n};\n\nexport type BaichuanStreamInfoList = {\n streams: BaichuanStreamInfo[];\n};\n\nexport type AiTypesCacheEntry = {\n types?: string[];\n updatedAtMs: number;\n};\n\nexport type DeviceCapabilitiesCacheEntry = {\n result: DeviceCapabilitiesResult;\n cachedAtMs: number;\n};\n\nexport type NvrChannelsSummaryCacheEntry = {\n channels: number[];\n devices: ReolinkBaichuanDeviceSummary[];\n};\n\nexport type RecordingsCacheEntry = {\n recordings: RecordingFile[];\n cachedAt: number;\n /** TTL in milliseconds (default 5 minutes) */\n ttlMs: number;\n};\n\nexport type RecordingsQueueItem = {\n run: () => Promise<void>;\n};\n\nexport interface AudioAlarmParams {\n channel: number;\n alarm_mode?: \"times\" | \"manul\";\n times?: number;\n manual_switch?: number;\n}\n\nexport interface Events {\n channel?: number;\n ai?: AIState;\n motion?: {\n state?: number;\n [key: string]: unknown;\n };\n /** Doorbell/visitor notification (instant event). Present when detected. */\n visitor?: {\n detected?: boolean;\n timestamp?: number;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\nexport type StreamProfile = \"main\" | \"sub\" | \"ext\";\n\nexport type NativeVideoStreamVariant = \"default\" | \"autotrack\" | \"telephoto\";\n\nexport type VideoCodec = \"H.264\" | \"H.265\" | \"MJPEG\" | \"MPEG4\" | string;\n\nexport interface StreamMetadata {\n profile: StreamProfile;\n audio: number; // 0 or 1\n audioCodec: string;\n width: number;\n height: number;\n videoEncType: VideoCodec;\n videoEncTypeInt: number; // Internal encoding type integer\n frameRate: number; // FPS\n bitRate: number; // Bitrate in kbps\n}\n\n/**\n * Complete media stream options with all available metadata.\n * Includes RTSP, RTMP, and native Baichuan stream information.\n */\nexport interface ReolinkSupportedStream {\n // Basic identification\n name: string;\n id: string;\n container: \"rtsp\" | \"rtmp\" | \"rtp\";\n channel?: number; // undefined for composite streams (multifocal devices)\n profile: StreamProfile;\n /**\n * Underlying device stream name used by the transport.\n * For RTSP this maps to `/...Preview_<ch>_<streamName>` (e.g. main/sub/autotrack).\n * For RTMP this maps to `/bcs/channel<ch>_<streamName>.bcs`.\n */\n streamName?: string;\n /** Optional lens hint for multifocal devices (TrackMix/Duo). */\n lens?: \"wide\" | \"telephoto\" | \"composite\";\n /** Native-only: request a non-default logical stream (e.g. TrackMix tele on NVR). */\n nativeVariant?: NativeVideoStreamVariant;\n url: string; // URL without authentication credentials\n urlWithAuth: string; // URL with authentication credentials\n streamType?: number; // Stream type: 0 for main/ext, 1 for sub (RTMP)\n path?: string; // Stream path (e.g., /h264Preview_01_main, /bcs/channel0_main.bcs)\n port?: number; // Port number (RTSP/RTMP)\n metadata?: StreamMetadata; // Complete original stream metadata\n}\n\nexport type RtspCreateOptions = {\n listenHost?: string;\n listenPort?: number;\n path?: string;\n variant?: NativeVideoStreamVariant;\n};\n\nexport type ReolinkVideoStreamOptionsResult = {\n nativeStreams: ReolinkSupportedStream[];\n rtspStreams: ReolinkSupportedStream[];\n rtmpStreams: ReolinkSupportedStream[];\n};\n\nexport type VideoStreamOptionsCacheEntry = ReolinkVideoStreamOptionsResult;\n\nexport type RunAllDiagnosticsConsecutivelyResult = {\n runDir: string;\n zipPath: string;\n diagnosticsPath: string;\n streamsDir: string;\n};\n\nexport type RunMultifocalDiagnosticsConsecutivelyResult = {\n runDir: string;\n resultsPath: string;\n streamsDir: string;\n};\n\nexport interface ChannelStreamMetadata {\n channel: number;\n streams: StreamMetadata[];\n audioEnabled: boolean; // Overall audio enabled (AND of all streams)\n}\n\nexport interface MotionEvent {\n channel: number;\n state: boolean; // true = motion detected\n timestamp?: number;\n /** Origin of motion trigger when known (e.g. PIR-only cameras). */\n source?: \"md\" | \"pir\" | \"unknown\";\n}\n\nexport interface AIEvent {\n channel: number;\n type: \"people\" | \"vehicle\" | \"dog_cat\" | \"face\" | \"package\" | \"other\";\n detected: boolean;\n timestamp?: number;\n}\n\nexport interface ReolinkMotionNotification {\n channel: number;\n type: \"motion\";\n motion: MotionEvent;\n timestamp?: number;\n}\n\nexport interface ReolinkAiNotification {\n channel: number;\n type: \"ai\";\n ai: AIEvent;\n timestamp?: number;\n}\n\nexport interface ReolinkVisitorNotification {\n channel: number;\n type: \"visitor\";\n timestamp?: number;\n}\n\nexport interface ReolinkDayNightNotification {\n channel: number;\n type: \"daynight\";\n timestamp?: number;\n}\n\nexport type ReolinkEvent =\n | ReolinkMotionNotification\n | ReolinkAiNotification\n | ReolinkVisitorNotification\n | ReolinkDayNightNotification;\n\nexport type ReolinkSimpleEventType =\n | \"motion\"\n | \"doorbell\"\n | \"people\"\n | \"vehicle\"\n | \"animal\"\n | \"face\"\n | \"package\"\n | \"daynight\"\n | \"sleeping\"\n | \"awake\"\n | \"online\"\n | \"offline\"\n | \"other\";\n\nexport interface ReolinkSimpleEvent {\n type: ReolinkSimpleEventType;\n channel: number;\n timestamp: number;\n}\n\nexport interface TwoWayAudioConfig {\n channel: number;\n enabled: boolean;\n mode?: \"mixAudioStream\" | string;\n}\n\nexport interface TalkAudioConfig {\n priority?: number;\n audioType: string;\n sampleRate: number;\n samplePrecision: number;\n lengthPerEncoder: number;\n soundTrack: string;\n}\n\nexport interface TalkAbility {\n version?: string;\n duplexList: string[];\n audioStreamModeList: string[];\n audioConfigList: TalkAudioConfig[];\n}\n\nexport interface TalkConfig {\n channel: number;\n duplex: string;\n audioStreamMode: string;\n audioConfig: TalkAudioConfig;\n}\n\nexport interface TalkSessionInfo {\n channel: number;\n audioConfig: TalkAudioConfig;\n /** ADPCM bytes per block excluding the 4-byte predictor state. */\n blockSize: number;\n /** ADPCM bytes per block including the 4-byte predictor state. */\n fullBlockSize: number;\n}\n\nexport interface TalkSession {\n readonly info: TalkSessionInfo;\n /**\n * Enqueue ADPCM DVI4 bytes (raw blocks, including the 4-byte predictor header per block).\n * The session will packetize into BcMedia ADPCM and pace delivery.\n */\n sendAudio(adpcm: Buffer): Promise<void>;\n /** Flush remaining audio and stop the talk session. */\n stop(): Promise<void>;\n}\n\n/**\n * Device ability/capability information for a specific channel or host.\n *\n * Keys are capability names (e.g., \"preview_rw\", \"control_rw\", \"motion_rw\", \"reboot_rw\").\n * Values are:\n * - 1 = capability is supported (typically with _rw suffix for read-write, _ro for read-only)\n * - 0 or undefined = capability is not supported\n * - string = metadata values (e.g., \"userName\")\n */\nexport type AbilityInfo = Record<string, number | string | undefined>;\n\n/**\n * Complete device abilities structure returned by getAbilityInfo.\n *\n * - Channel numbers (0, 1, 2, etc.): Channel-specific abilities\n * - \"Host\": Host-level/system abilities\n */\nexport type DeviceAbilities = Partial<Record<number | \"Host\", AbilityInfo>>;\n\nexport interface SupportItem {\n chnID: number;\n ptzType?: number;\n ptzPreset?: number;\n ptzPatrol?: number;\n ptzTattern?: number;\n ptzControl?: number;\n rfCfg?: number;\n noAudio?: number;\n autoFocus?: number;\n videoClip?: number;\n battery?: number;\n ispCfg?: number;\n osdCfg?: number;\n batAnalysis?: number;\n dynamicReso?: number;\n audioVersion?: number;\n ledCtrl?: number;\n motion?: number;\n [key: string]: number | string | undefined;\n}\n\nexport interface SupportInfo {\n items: SupportItem[];\n ptzMode?: string;\n\n IOInputPortNum?: number;\n IOOutputPortNum?: number;\n diskNum?: number;\n channelNum?: number;\n audioNum?: number;\n ptzCfg?: number;\n B485?: number;\n autoUpdate?: number;\n pushAlarm?: number;\n ftp?: number;\n ftpTest?: number;\n email?: number;\n wifi?: number;\n record?: number;\n wifiTest?: number;\n rtsp?: number;\n onvif?: number;\n audioTalk?: number;\n\n // Preserve unknown fields when useful.\n [key: string]: unknown;\n}\n\nexport interface DeviceCapabilities {\n channel: number;\n /** Lower-cased ptzMode when available (e.g. \"pt\", \"ptz\", \"none\"). */\n ptzMode?: string;\n hasPan: boolean;\n hasTilt: boolean;\n hasZoom: boolean;\n hasPresets: boolean;\n hasPtz: boolean;\n hasBattery: boolean;\n hasIntercom: boolean;\n hasSiren: boolean;\n hasFloodlight: boolean;\n hasPir: boolean;\n /** True when device reports doorbell support via support.items[].doorbellVersion. */\n isDoorbell: boolean;\n /** True when device supports autotracking (smartTrack in AiCfg). */\n hasAutotracking: boolean;\n}\n\nexport type DeviceObjectType = string;\n\nexport interface DeviceSupportFlags {\n rtsp?: boolean;\n onvif?: boolean;\n wifi?: boolean;\n record?: boolean;\n ftp?: boolean;\n email?: boolean;\n pushAlarm?: boolean;\n audioTalk?: boolean;\n}\n\nexport interface DeviceCapabilitiesDebugInfo {\n channel: number;\n channelId1Based: number;\n transport: \"tcp\" | \"udp\";\n encryptionKind: \"none\" | \"bc\" | \"aes\" | \"full_aes\";\n loggedIn: boolean;\n subscribed: boolean;\n abilitiesAvailable: boolean;\n supportAvailable: boolean;\n abilityMergedKeyCount?: number;\n supportItemCount?: number;\n /** Whether the device is detected as NVR/Hub */\n isNvr?: boolean;\n /** lightType from SupportItem (0=no light, 1=IR only, >=2=floodlight) */\n lightType?: number;\n /** ledCtrl bitmask from SupportItem (>0 indicates LED control capability) */\n ledCtrl?: number;\n /** ptzType from SupportItem */\n ptzType?: number;\n /** supportVolume from SupportItem (indicates siren support) */\n supportVolume?: number;\n /** supportPirSch from SupportItem (indicates PIR support) */\n supportPirSch?: number;\n /** Selected SupportItem chnID */\n supportItemChnID?: number;\n}\n\nexport interface DeviceCapabilitiesResult {\n abilities?: DeviceAbilities;\n support?: SupportInfo;\n capabilities: DeviceCapabilities;\n presets?: PtzPreset[];\n objects?: DeviceObjectType[];\n features?: DeviceSupportFlags;\n debug?: DeviceCapabilitiesDebugInfo;\n}\n\nexport type RecordingStreamType = \"mainStream\" | \"subStream\";\n\nexport type RecordingDevType = \"cam\" | \"hub\";\nexport type RecordingVodStreamHint = \"main\" | \"sub\" | \"unknown\";\n\nexport interface RecordingVodFlags {\n aiPerson?: boolean;\n aiVehicle?: boolean;\n aiAnimal?: boolean;\n aiFace?: boolean;\n aiOther?: boolean;\n motion?: boolean;\n schedule?: boolean;\n doorbell?: boolean;\n rf?: boolean;\n package?: boolean;\n}\n\nexport interface ParsedRecordingFileName {\n baseName: string;\n ext: string;\n streamHint: RecordingVodStreamHint;\n version: number;\n devType: RecordingDevType;\n start: Date;\n end: Date;\n durationMs: number;\n /** Frame rate extracted from filename hex flags (if available) */\n framerate?: number;\n flags?: RecordingVodFlags;\n rawFlags?: Record<string, number>;\n animalTypeRaw?: string;\n widthRaw?: string;\n heightRaw?: string;\n}\n\nexport interface RecordingFile {\n /** Camera-provided recording identifier (often a filename/path, e.g. \"00_YYYYMMDDHHMMSS\"). */\n fileName: string;\n /** Optional human-friendly name when provided separately (e.g. FileInfoList <name>). */\n name?: string;\n /** Optional full path/identifier when provided separately (e.g. FileInfoList <Id>). */\n id?: string;\n /** Optional size when provided by the camera (bytes). */\n sizeBytes?: number;\n /** Optional recordType when provided (e.g. md, people, sched, manual...). */\n recordType?: string;\n /** Optional start time when provided as YYYY/MM/DD etc; best-effort parsing may be absent. */\n startTime?: Date;\n /** Optional end time when provided. */\n endTime?: Date;\n\n /** Parsed metadata extracted from the file name when it matches known Reolink VOD patterns. */\n parsedFileName?: ParsedRecordingFileName;\n\n /** Detection classes for this recording (e.g. person, vehicle, animal, face, motion, doorbell, package) */\n detectionClasses?: RecordingDetectionClass[];\n}\n\n/**\n * A RecordingFile associated with an explicit logical channel.\n *\n * Useful for NVR/Hub-style listings where you query multiple channels and\n * want to keep the channel number alongside each returned file/event.\n */\nexport interface ChannelRecordingFile extends RecordingFile {\n channel: number;\n /** Optional UID used for the request (when known). */\n uid?: string;\n}\n\nexport type RecordingDetectionClass =\n | \"person\"\n | \"vehicle\"\n | \"animal\"\n | \"face\"\n | \"package\"\n | \"doorbell\"\n | \"rf\"\n | \"other\"\n | \"motion\"\n | \"schedule\";\n\nexport interface DownloadRecordingParams {\n channel: number;\n uid?: string;\n /** Recording identifier (usually one of the `fileName` returned by getVideoclips). */\n fileName: string;\n timeoutMs?: number;\n}\n\nexport type RecordingPlaybackUrls = {\n /** RTMP VOD URL for playback/export. */\n rtmpVodUrl: string;\n};\n\nexport type PlaybackSnapshotStreamInfo = {\n width?: number;\n height?: number;\n frameRate?: number;\n};\n\nexport type VideoclipThumbnailResult = {\n /** Raw I-frame data (H.264 or H.265) */\n frame: Buffer;\n /** Video encoding type detected from frame header */\n encoding: string;\n /** Frame length in bytes */\n frameLength: number;\n /** Frame timestamp (Unix seconds) if available */\n frameTime?: number;\n /** Stream metadata from header */\n streamInfo: PlaybackSnapshotStreamInfo;\n};\n\n/**\n * Detailed information about channel capabilities for dual lens models.\n */\nexport interface DualLensChannelInfo {\n /** Channel number (0-based) */\n channel: number;\n /** Indicates whether this channel supports pan */\n hasPan: boolean;\n /** Indicates whether this channel supports tilt */\n hasTilt: boolean;\n /** Indicates whether this channel supports zoom */\n hasZoom: boolean;\n /** Indicates whether this channel supports motion detection */\n hasMotion: boolean;\n /** Indicates whether this channel supports intercom (two-way audio) */\n hasIntercom: boolean;\n /** Indicates whether this channel supports PTZ presets */\n hasPresets: boolean;\n /** Channel type: \"wide\" for wide-angle lens, \"telephoto\" for telephoto lens */\n lensType?: \"wide\" | \"telephoto\" | undefined;\n /** Which Native variant maps to this lens (default=wide; autotrack/telephoto=tele lens depending on context). */\n variantType?: NativeVideoStreamVariant;\n /** Available streams for this channel */\n availableStreams: {\n /** RTSP stream available */\n rtsp: boolean;\n /** RTMP stream available */\n rtmp: boolean;\n /** Native Baichuan stream available */\n native: boolean;\n };\n}\n\n/**\n * Result of dual lens channel analysis.\n */\nexport interface DualLensChannelAnalysis {\n /** Indicates whether the device is a dual lens model */\n isDualLens: boolean;\n /** Dual lens model type: \"dual_motion\" (Duo) or \"single_motion\" (TrackMix) */\n dualLensType?: \"dual_motion\" | \"single_motion\" | undefined;\n /** Device model */\n model?: string | undefined;\n /** Total number of available stream channels */\n streamChannelCount?: number | undefined;\n /** Total number of logical channels */\n logicalChannelCount?: number | undefined;\n /** Detailed information for each channel */\n channels: DualLensChannelInfo[];\n /** Maps each capability to the list of channel numbers (0-based) that support it.\n * Use this to determine which channels to send commands to.\n */\n capabilityChannels: {\n /** Channel numbers that support pan */\n pan: number[];\n /** Channel numbers that support tilt */\n tilt: number[];\n /** Channel numbers that support zoom */\n zoom: number[];\n /** Channel numbers that support motion detection */\n motion: number[];\n /** Channel numbers that support intercom (two-way audio) */\n intercom: number[];\n /** Channel numbers that support PTZ presets */\n presets: number[];\n };\n}\n\n// ---------------------------------------------------------------------------\n// Recording download result types\n// ---------------------------------------------------------------------------\n\n/**\n * Video codec type detected from BcMedia stream.\n */\nexport type RecordingVideoCodec = \"H264\" | \"H265\";\n\n/**\n * Audio codec type detected from BcMedia stream.\n */\nexport type RecordingAudioCodec = \"Aac\" | \"Adpcm\";\n\n/**\n * Statistics returned by getRecordingVideo() about the demuxed/muxed recording.\n */\nexport interface GetRecordingVideoStats {\n /** Total bytes received from camera */\n bytesIn: number;\n /** Total video bytes after demuxing */\n videoBytesOut: number;\n /** Total audio bytes after demuxing */\n audioBytesOut: number;\n /** Number of video packets */\n videoPackets: number;\n /** Number of audio packets */\n audioPackets: number;\n /** Number of keyframes */\n keyframes: number;\n /** Calculated frames per second */\n fps: number;\n /** Duration in seconds */\n durationSeconds: number;\n /** Video codec detected */\n videoCodec: RecordingVideoCodec;\n /** Audio codec detected (null if no audio) */\n audioCodec: RecordingAudioCodec | null;\n /** Whether audio was present */\n hasAudio: boolean;\n}\n\n/**\n * Result of getRecordingVideo() - a fully muxed MP4 with stats.\n */\nexport interface GetRecordingVideoResult {\n /** MP4 file with video and audio muxed together */\n mp4: Buffer;\n /** Statistics about the demuxing/muxing process */\n stats: GetRecordingVideoStats;\n}\n\n/**\n * Parameters for getVideoclips() recording search.\n */\nexport interface GetVideoclipsParams {\n /** Channel number (0-based). Optional for standalone cameras, required for NVR. */\n channel?: number;\n /** Start time for search */\n start: Date;\n /** End time for search */\n end: Date;\n /** Stream type. Default: \"subStream\" */\n streamType?: RecordingStreamType;\n /** Comma-separated record types. Default includes all types. */\n recordType?: string;\n /** Explicit UID (skip auto-discovery if provided) */\n uid?: string;\n /** Per-request timeout in ms. Default: 15000 */\n timeoutMs?: number;\n /** Max pagination iterations. Default: 50 */\n maxIterations?: number;\n}\n\n// ============================================================================\n// Typed API Response Interfaces (extracted from XML responses)\n// These represent the actual content without the outer <body> wrapper\n// ============================================================================\n\n/**\n * AudioTask configuration - controls siren/alarm on motion detection.\n * Retrieved via cmdId=232 (GET) and set via cmdId=231 (SET).\n */\nexport interface AudioTaskConfig {\n body?: {\n AudioTask?: {\n channelId?: number;\n /** 0 = disabled, 1 = enabled */\n enable?: number;\n typeScheduleList?: {\n item?: Array<{\n type?: string;\n valueTable?: string;\n }>;\n };\n };\n };\n}\n\n/**\n * Audio configuration settings (getAudioCfg response).\n */\nexport interface AudioCfgConfig {\n body?: {\n AudioCfg?: {\n channelId?: number;\n timeout?: number;\n audioSelect?: number;\n volume?: number;\n preAlarm?: number;\n audioListId?: number;\n visitorLoudspeaker?: number;\n };\n };\n}\n\n/**\n * Day/Night threshold configuration.\n */\nexport interface DayNightThresholdConfig {\n body?: {\n DayNightThreshold?: {\n channelId?: number;\n threshold?: string;\n stat?: string;\n thresholdval?: {\n min?: number;\n max?: number;\n cur?: number;\n };\n };\n };\n}\n\n/**\n * AI denoise configuration.\n */\nexport interface AiDenoiseConfig {\n body?: {\n AiDenoise?: {\n channelId?: number;\n enable?: number;\n level?: number;\n };\n };\n}\n\n/**\n * Recording encryption configuration.\n */\nexport interface RecEncConfig {\n body?: {\n RecEnc?: {\n enable?: number;\n pwdValid?: number;\n };\n };\n}\n\n/**\n * AI configuration - includes autotracking (smartTrack) settings.\n * Retrieved via cmdId=299 (GET) and set via cmdId=300 (SET).\n */\nexport interface AiConfig {\n body?: {\n AiCfg?: {\n channelId?: number;\n /** 0 = disabled, 1 = enabled - controls autotracking */\n smartTrack?: number;\n /** Tracking mode (0=normal, 1=digital, etc.) */\n smartTrackMode?: number;\n smartTrackModeAbility?: number;\n /** Comma-separated detection types (e.g. \"people,vehicle,dog_cat\") */\n detectType?: string;\n /** Comma-separated tracking types */\n smartTrackType?: string;\n smartTrackPt?: number;\n smartTrackObjectStopDelay?: number;\n smartTrackObjectDisappearDelay?: number;\n cryDetectLevel?: number;\n cryDetectAbility?: number;\n trackPriorities?: {\n item?: string[];\n };\n };\n };\n}\n\n/**\n * Floodlight task configuration - controls floodlight on motion.\n * Retrieved via cmdId=289 (GET) and set via cmdId=290 (SET).\n */\nexport interface FloodlightTaskConfig {\n body?: {\n FloodlightTask?: {\n channelId?: number | undefined;\n /** 0 = disabled, 1 = enabled - controls floodlight on motion */\n alarmMode?: number | undefined;\n enable?: number | undefined;\n brightness_cur?: number | undefined;\n duration?: number | undefined;\n detectType?: string | undefined;\n };\n };\n}\n\n/**\n * White LED state configuration.\n */\nexport interface WhiteLedConfig {\n body?: {\n WhiteLed?: {\n channelId?: number | undefined;\n /** 0 = off, 1 = on */\n state?: number | undefined;\n /** Brightness level */\n bright?: number | undefined;\n /** LED mode */\n mode?: number | undefined;\n /** Light state (for some camera models) */\n LightState?: number | undefined;\n };\n };\n}\n\n/**\n * PIR sensor configuration.\n */\nexport interface PirConfig {\n body?: {\n PirInfo?: {\n channelId?: number | undefined;\n /** 0 = disabled, 1 = enabled */\n enable?: number | undefined;\n sensitivity?: number | undefined;\n scheduleEnable?: number | undefined;\n };\n };\n}\n\n// ============================================================================\n// Additional API Response Interfaces\n// ============================================================================\n\n/**\n * Motion alarm configuration (getMotionAlarm response).\n * cmdId=46 (GetMdAlarm)\n */\nexport interface MotionAlarmConfig {\n body?: {\n MdAlarm?: {\n channelId?: number | undefined;\n sensInfoNew?: {\n enable?: number | undefined;\n sensitivity?: number | undefined;\n alarmType?: number | undefined;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * AI alarm/detection configuration (getAiAlarmRaw response).\n * cmdId=342 (GetAiAlarm)\n */\nexport interface AiAlarmConfig {\n body?: {\n AiAlarm?: {\n channelId?: number | undefined;\n chn?: number | undefined;\n type?: string | undefined;\n enabled?: number | undefined;\n sensitivity?: number | undefined;\n trackType?: string | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Video input configuration.\n * cmdId=75 (GetVideoInput)\n */\nexport interface VideoInputConfig {\n body?: {\n VideoInput?: {\n channelId?: number | undefined;\n bright?: number | undefined;\n contrast?: number | undefined;\n saturation?: number | undefined;\n hue?: number | undefined;\n irCutSwap?: number | undefined;\n dayNight?: string | undefined;\n [key: string]: unknown;\n };\n InputAdvanceCfg?: {\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * System general configuration.\n * cmdId=77 (GetSystemGeneral)\n */\nexport interface SystemGeneralConfig {\n body?: {\n SystemGeneral?: {\n timeZone?: number | undefined;\n deviceName?: string | undefined;\n language?: string | undefined;\n dstMode?: number | undefined;\n [key: string]: unknown;\n };\n Norm?: {\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Device support/capability flags.\n * cmdId=78 (GetSupport)\n */\nexport interface SupportConfig {\n body?: {\n Support?: {\n ptzMode?: string | undefined;\n channelNum?: number | undefined;\n wifi?: number | undefined;\n rtsp?: number | undefined;\n rtmp?: number | undefined;\n onvif?: number | undefined;\n email?: number | undefined;\n ftp?: number | undefined;\n push?: number | undefined;\n cloudStorage?: number | undefined;\n ledCtrl?: number | undefined;\n audioAlarm?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Siren status information.\n * cmdId=270 (GetSirenStatus)\n */\nexport interface SirenStatusConfig {\n body?: {\n SirenStatusList?: {\n channelId?: number | undefined;\n status?: number | undefined;\n playing?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * FTP task configuration.\n */\nexport interface FtpTaskConfig {\n body?: {\n FtpTask?: {\n channelId?: number | undefined;\n enable?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Email task configuration.\n */\nexport interface EmailTaskConfig {\n body?: {\n EmailTask?: {\n channelId?: number | undefined;\n enable?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * HDD info list response.\n */\nexport interface HddInfoListConfig {\n body?: {\n HddInfoList?: {\n itemNum?: number | undefined;\n item?: Array<{\n id?: number | undefined;\n size?: number | undefined;\n used?: number | undefined;\n [key: string]: unknown;\n }>;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Timelapse configuration.\n */\nexport interface TimelapseCfgConfig {\n body?: {\n TimelapseCfg?: {\n channelId?: number | undefined;\n enable?: number | undefined;\n interval?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Access user list response.\n */\nexport interface AccessUserListConfig {\n body?: {\n AccessUserList?: {\n itemNum?: number | undefined;\n item?: Array<{\n userName?: string | undefined;\n level?: number | undefined;\n [key: string]: unknown;\n }>;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Online user list response.\n */\nexport interface OnlineUserListConfig {\n body?: {\n OnlineUserList?: {\n itemNum?: number | undefined;\n item?: Array<{\n userName?: string | undefined;\n ip?: string | undefined;\n [key: string]: unknown;\n }>;\n [key: string]: unknown;\n };\n };\n}\n\n// ============================================================================\n// Videoclip Client Detection Utilities\n// ============================================================================\n\n/**\n * Client information extracted from HTTP request headers.\n * Used to determine optimal video delivery format.\n */\nexport type VideoclipClientInfo = {\n userAgent: string | undefined;\n accept: string | undefined;\n range: string | undefined;\n secChUa: string | undefined;\n secChUaMobile: string | undefined;\n secChUaPlatform: string | undefined;\n};\n\n/**\n * Videoclip delivery mode.\n * - `passthrough`: Copy codec as-is (H.264 or H.265)\n * - `transcode-h264`: Transcode H.265 to H.264 for compatibility\n */\nexport type VideoclipTranscodeMode = \"passthrough\" | \"transcode-h264\";\n\n/**\n * Result of videoclip mode decision.\n */\nexport type VideoclipModeDecision = {\n mode: VideoclipTranscodeMode;\n reason: string;\n clientInfo: VideoclipClientInfo;\n};\n\n/**\n * Extract client info from HTTP request headers.\n */\nexport function getVideoclipClientInfo(\n headers: Record<string, string | string[] | undefined>,\n): VideoclipClientInfo {\n const getHeader = (key: string): string | undefined => {\n const val =\n headers[key] ?? headers[key.toLowerCase()] ?? headers[key.toUpperCase()];\n return Array.isArray(val) ? val[0] : val;\n };\n\n return {\n userAgent: getHeader(\"user-agent\") ?? getHeader(\"User-Agent\"),\n accept: getHeader(\"accept\") ?? getHeader(\"Accept\"),\n range: getHeader(\"range\") ?? getHeader(\"Range\"),\n secChUa: getHeader(\"sec-ch-ua\") ?? getHeader(\"Sec-CH-UA\"),\n secChUaMobile:\n getHeader(\"sec-ch-ua-mobile\") ?? getHeader(\"Sec-CH-UA-Mobile\"),\n secChUaPlatform:\n getHeader(\"sec-ch-ua-platform\") ?? getHeader(\"Sec-CH-UA-Platform\"),\n };\n}\n\n/**\n * Determine if H.265 should be transcoded to H.264 based on client capabilities.\n *\n * Decision logic:\n * - iOS devices (Safari): Need transcoding (no native H.265 in <video> without HLS)\n * - macOS Safari: Supports H.265 natively\n * - Chrome/Edge: Limited H.265 support, safer to transcode\n * - Firefox: No H.265 support, needs transcoding\n * - Android: Variable support, transcode for safety\n *\n * @param headers HTTP request headers\n * @param forceMode Optional override: \"passthrough\" or \"transcode-h264\"\n * @returns Decision with mode, reason, and client info\n */\nexport function decideVideoclipTranscodeMode(\n headers: Record<string, string | string[] | undefined>,\n forceMode?: VideoclipTranscodeMode,\n): VideoclipModeDecision {\n const clientInfo = getVideoclipClientInfo(headers);\n\n // If force mode is specified, use it\n if (forceMode) {\n return {\n mode: forceMode,\n reason: `forced: ${forceMode}`,\n clientInfo,\n };\n }\n\n const ua = (clientInfo.userAgent ?? \"\").toLowerCase();\n const platform = (clientInfo.secChUaPlatform ?? \"\")\n .toLowerCase()\n .replace(/\"/g, \"\");\n\n // iOS devices (iPhone, iPad, iPod) - no native H.265 in <video> element\n const isIos = /iphone|ipad|ipod/.test(ua);\n if (isIos) {\n return {\n mode: \"transcode-h264\",\n reason: \"iOS device detected - no native H.265 support in <video>\",\n clientInfo,\n };\n }\n\n // Firefox - no H.265 support at all\n const isFirefox = ua.includes(\"firefox\");\n if (isFirefox) {\n return {\n mode: \"transcode-h264\",\n reason: \"Firefox detected - no H.265 support\",\n clientInfo,\n };\n }\n\n // Android - variable H.265 support, safer to transcode\n const isAndroid = ua.includes(\"android\") || platform === \"android\";\n if (isAndroid) {\n return {\n mode: \"transcode-h264\",\n reason: \"Android device detected - variable H.265 support\",\n clientInfo,\n };\n }\n\n // Chrome/Edge on non-Mac - limited H.265 support\n const isChromium = ua.includes(\"chrome\") || ua.includes(\"edg\");\n const isMac = ua.includes(\"mac os\") || platform === \"macos\";\n if (isChromium && !isMac) {\n return {\n mode: \"transcode-h264\",\n reason: \"Chrome/Edge on non-Mac detected - limited H.265 support\",\n clientInfo,\n };\n }\n\n // macOS Safari or Chrome on Mac - good H.265 support via VideoToolbox\n if (isMac) {\n return {\n mode: \"passthrough\",\n reason: \"macOS detected - native H.265 hardware decoding available\",\n clientInfo,\n };\n }\n\n // Default: transcode for safety\n return {\n mode: \"transcode-h264\",\n reason: \"Unknown client - transcoding for compatibility\",\n clientInfo,\n };\n}\n","import http from \"node:http\";\nimport { spawn } from \"node:child_process\";\n\nimport type { BaichuanClientOptions } from \"../../client/BaichuanClient\";\nimport type { StreamProfile } from \"./types\";\nimport { ReolinkBaichuanApi } from \"./ReolinkBaichuanApi\";\n\ntype NativeVariantParam = \"default\" | \"autotrack\" | \"telephoto\";\n\nexport type BaichuanEndpointsServerOptions = {\n /** Port to listen on. */\n listenPort: number;\n /** Host to bind to (default: 127.0.0.1). */\n listenHost?: string;\n\n /** Connection options for Baichuan (host/username/password/transport/etc). */\n baichuan: BaichuanClientOptions;\n\n /** Where the internal RTSP servers should bind (default: 127.0.0.1). */\n rtspListenHost?: string;\n};\n\nfunction parseIntParam(v: string | null, def: number): number {\n if (v == null) return def;\n const n = Number.parseInt(v, 10);\n return Number.isFinite(n) ? n : def;\n}\n\nfunction parseBoolParam(v: string | null, def: boolean): boolean {\n if (v == null) return def;\n const s = v.trim().toLowerCase();\n if (s === \"1\" || s === \"true\" || s === \"yes\" || s === \"y\") return true;\n if (s === \"0\" || s === \"false\" || s === \"no\" || s === \"n\") return false;\n return def;\n}\n\nfunction parseProfile(v: string | null): StreamProfile {\n const p = (v ?? \"sub\").trim();\n if (p === \"main\" || p === \"sub\" || p === \"ext\") return p;\n throw new Error(\"Invalid profile (must be main, sub, or ext)\");\n}\n\nfunction parseNativeVariant(v: string | null): NativeVariantParam {\n const s = (v ?? \"default\").trim().toLowerCase();\n if (s === \"\" || s === \"default\") return \"default\";\n if (s === \"autotrack\") return \"autotrack\";\n if (s === \"telephoto\") return \"telephoto\";\n throw new Error(\"Invalid variant (must be default, autotrack, or telephoto)\");\n}\n\nfunction parseDateParam(v: string | null): Date {\n if (!v) throw new Error(\"Missing time\");\n const d = new Date(v);\n if (Number.isNaN(d.getTime()))\n throw new Error(\"Invalid time (expected ISO string)\");\n return d;\n}\n\nfunction parseDateParamNamed(name: string, v: string | null): Date {\n if (!v) throw new Error(`Missing ${name}`);\n const d = new Date(v);\n if (Number.isNaN(d.getTime()))\n throw new Error(`Invalid ${name} (expected ISO string)`);\n return d;\n}\n\n/**\n * Minimal HTTP server exposing two endpoints:\n * - `GET /stream?channel=0&profile=main` -> returns a local RTSP URL (Baichuan socket -> RTSP)\n * - `GET /download?channel=0&uid=...&fileName=...` -> downloads a recording via Baichuan socket\n */\nexport function createBaichuanEndpointsServer(\n opts: BaichuanEndpointsServerOptions,\n): http.Server {\n const api = new ReolinkBaichuanApi({\n ...opts.baichuan,\n });\n\n const listenHost = opts.listenHost ?? \"127.0.0.1\";\n const rtspListenHost = opts.rtspListenHost ?? \"127.0.0.1\";\n\n // Cache servers by channel/profile.\n const rtspServers = new Map<string, { url: string }>();\n\n const server = http.createServer(async (req, res) => {\n try {\n if (!req.url) {\n res.statusCode = 400;\n res.end(\"Bad Request\");\n return;\n }\n\n const u = new URL(req.url, `http://${listenHost}:${opts.listenPort}`);\n\n if (req.method !== \"GET\") {\n res.statusCode = 405;\n res.setHeader(\"Allow\", \"GET\");\n res.end(\"Method Not Allowed\");\n return;\n }\n\n if (u.pathname === \"/stream\") {\n const channel = parseIntParam(u.searchParams.get(\"channel\"), 0);\n const profile = parseProfile(u.searchParams.get(\"profile\"));\n const variant = parseNativeVariant(u.searchParams.get(\"variant\"));\n if (!Number.isFinite(channel) || channel < 0) {\n res.statusCode = 400;\n res.end(\"Invalid channel\");\n return;\n }\n\n const key = `${channel}:${profile}:${variant}`;\n const cached = rtspServers.get(key);\n if (cached) {\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ rtspUrl: cached.url }));\n return;\n }\n\n const rtsp = await api.createRtspStream(channel, profile, {\n listenHost: rtspListenHost,\n listenPort: 0,\n path: `/stream/${channel}/${profile}${variant === \"default\" ? \"\" : `/${variant}`}`,\n ...(variant === \"default\" ? {} : { variant }),\n });\n\n const rtspUrl = rtsp.getRtspUrl();\n rtspServers.set(key, { url: rtspUrl });\n\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ rtspUrl }));\n return;\n }\n\n if (u.pathname === \"/download\") {\n const channel = parseIntParam(u.searchParams.get(\"channel\"), 0);\n const uid = (u.searchParams.get(\"uid\") ?? \"\").trim();\n const fileName = (u.searchParams.get(\"fileName\") ?? \"\").trim();\n const timeoutMs = parseIntParam(\n u.searchParams.get(\"timeoutMs\"),\n 120_000,\n );\n\n if (!uid) {\n res.statusCode = 400;\n res.end(\"Missing uid\");\n return;\n }\n if (!fileName) {\n res.statusCode = 400;\n res.end(\"Missing fileName\");\n return;\n }\n\n const buf = await api.downloadRecording({\n channel,\n uid,\n fileName,\n timeoutMs,\n });\n\n const outName =\n fileName.split(\"/\").filter(Boolean).at(-1) ?? \"recording.bin\";\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"application/octet-stream\");\n res.setHeader(\n \"Content-Disposition\",\n `attachment; filename=\"${outName}\"`,\n );\n res.setHeader(\"Content-Length\", String(buf.length));\n res.end(buf);\n return;\n }\n\n if (u.pathname === \"/recordings\") {\n const channel = parseIntParam(u.searchParams.get(\"channel\"), 0);\n const streamType = (\n u.searchParams.get(\"streamType\") ?? \"subStream\"\n ).trim();\n const start = parseDateParamNamed(\"start\", u.searchParams.get(\"start\"));\n const end = parseDateParamNamed(\"end\", u.searchParams.get(\"end\"));\n const recordType = (u.searchParams.get(\"recordType\") ?? \"\").trim();\n const count = parseIntParam(u.searchParams.get(\"count\"), 0);\n const timeoutMs = parseIntParam(\n u.searchParams.get(\"timeoutMs\"),\n 30_000,\n );\n\n if (streamType !== \"mainStream\" && streamType !== \"subStream\") {\n res.statusCode = 400;\n res.end(\"Invalid streamType (must be mainStream or subStream)\");\n return;\n }\n\n let recordings = await api.getVideoclips({\n channel,\n start,\n end,\n streamType: streamType as any,\n ...(recordType ? { recordType } : {}),\n timeoutMs,\n });\n\n if (count > 0) recordings = recordings.slice(-count);\n\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n JSON.stringify({\n channel,\n streamType,\n start: start.toISOString(),\n end: end.toISOString(),\n recordings,\n }),\n );\n return;\n }\n\n if (u.pathname === \"/vod/stream\" || u.pathname === \"/vod/download\") {\n const channel = parseIntParam(u.searchParams.get(\"channel\"), 0);\n const fileName = (u.searchParams.get(\"fileName\") ?? \"\").trim();\n const streamType = (\n u.searchParams.get(\"streamType\") ?? \"mainStream\"\n ).trim();\n const rtspTransport = (\n u.searchParams.get(\"rtmpTransport\") ?? \"tcp\"\n ).trim();\n\n if (!fileName) {\n res.statusCode = 400;\n res.end(\"Missing fileName\");\n return;\n }\n if (streamType !== \"mainStream\" && streamType !== \"subStream\") {\n res.statusCode = 400;\n res.end(\"Invalid streamType (must be mainStream or subStream)\");\n return;\n }\n if (rtspTransport !== \"tcp\" && rtspTransport !== \"udp\") {\n res.statusCode = 400;\n res.end(\"Invalid rtmpTransport (must be tcp or udp)\");\n return;\n }\n\n const rtmpUrl = await api.getVodRtmpUrl({\n channel,\n fileName,\n streamType: streamType as any,\n ensureEnabled: true,\n });\n\n const outName =\n fileName.split(\"/\").filter(Boolean).at(-1) ?? \"recording.mp4\";\n\n if (u.pathname === \"/vod/stream\") {\n // Stream-only: RTMP -> MPEG-TS passthrough.\n res.writeHead(200, {\n \"Content-Type\": \"video/mp2t\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"close\",\n });\n\n const ff = spawn(\"ffmpeg\", [\n \"-hide_banner\",\n \"-loglevel\",\n \"error\",\n \"-rtmp_live\",\n \"live\",\n \"-i\",\n rtmpUrl,\n \"-c\",\n \"copy\",\n \"-f\",\n \"mpegts\",\n \"pipe:1\",\n ]);\n\n ff.stdout.pipe(res);\n\n const cleanup = () => {\n ff.kill(\"SIGKILL\");\n };\n req.on(\"close\", cleanup);\n res.on(\"close\", cleanup);\n ff.on(\"close\", () => {\n res.end();\n });\n return;\n }\n\n // Download/export: RTMP -> MP4 (best-effort streamable MP4).\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"video/mp4\");\n res.setHeader(\n \"Content-Disposition\",\n `attachment; filename=\"${outName}\"`,\n );\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"close\");\n\n const ff = spawn(\"ffmpeg\", [\n \"-hide_banner\",\n \"-loglevel\",\n \"error\",\n \"-rtmp_live\",\n \"live\",\n \"-i\",\n rtmpUrl,\n \"-c\",\n \"copy\",\n \"-movflags\",\n \"frag_keyframe+empty_moov\",\n \"-f\",\n \"mp4\",\n \"pipe:1\",\n ]);\n\n ff.stdout.pipe(res);\n\n const cleanup = () => {\n ff.kill(\"SIGKILL\");\n };\n req.on(\"close\", cleanup);\n res.on(\"close\", cleanup);\n ff.on(\"close\", () => {\n res.end();\n });\n return;\n }\n\n if (u.pathname === \"/replay/cover.jpg\") {\n const channel = parseIntParam(u.searchParams.get(\"channel\"), 0);\n const time = parseDateParam(u.searchParams.get(\"time\"));\n const snapType = (u.searchParams.get(\"snapType\") ?? \"sub\").trim();\n const timeoutMs = parseIntParam(\n u.searchParams.get(\"timeoutMs\"),\n 30_000,\n );\n const jpegQuality = parseIntParam(u.searchParams.get(\"jpegQuality\"), 2);\n\n if (snapType !== \"main\" && snapType !== \"sub\") {\n res.statusCode = 400;\n res.end(\"Invalid snapType (must be main or sub)\");\n return;\n }\n\n const { jpeg, snapshot } = await api.getVideoclipThumbnailJpegRaw({\n channel,\n time,\n snapType: snapType as any,\n timeoutMs,\n jpegQuality,\n });\n\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"image/jpeg\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"X-Reolink-Encoding\", snapshot.encoding);\n res.setHeader(\"X-Reolink-Frame-Length\", String(snapshot.frameLength));\n res.setHeader(\"Content-Length\", String(jpeg.length));\n res.end(jpeg);\n return;\n }\n\n if (u.pathname === \"/replay/stream.mp4\") {\n const channel = parseIntParam(u.searchParams.get(\"channel\"), 0);\n const fileName = (u.searchParams.get(\"fileName\") ?? \"\").trim();\n\n if (!fileName) {\n res.statusCode = 400;\n res.end(\"Missing fileName\");\n return;\n }\n\n const { mp4, stop } = await api.createRecordingReplayMp4Stream({\n channel,\n fileName,\n });\n\n res.writeHead(200, {\n \"Content-Type\": \"video/mp4\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"close\",\n });\n mp4.pipe(res);\n\n const cleanup = () => {\n void stop();\n try {\n mp4.destroy();\n } catch {\n // ignore\n }\n };\n req.on(\"close\", cleanup);\n res.on(\"close\", cleanup);\n mp4.on(\"end\", () => {\n res.end();\n });\n mp4.on(\"error\", () => {\n res.end();\n });\n return;\n }\n\n res.statusCode = 404;\n res.end(\"Not Found\");\n } catch (e) {\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"text/plain\");\n res.end(e instanceof Error ? e.message : String(e));\n }\n });\n\n // Best-effort cleanup when the server closes.\n server.on(\"close\", () => {\n void api.close().catch(() => undefined);\n });\n\n return server;\n}\n","import http from \"node:http\";\nimport { spawn } from \"node:child_process\";\nimport { buildRtspUrl, type RtspStreamProfile } from \"./urls\";\n\nexport type RtspProxyServerOptions = {\n listenPort: number;\n host: string;\n username: string;\n password: string;\n rtspPort?: number;\n /**\n * RTSP transport towards the camera.\n * - `tcp` is often more reliable on LAN\n * - `udp` can reduce latency but is more fragile\n */\n rtspTransport?: \"tcp\" | \"udp\";\n};\n\n/**\n * Server Node.js minimale che espone:\n * - `GET /stream?channel=0&profile=main`\n *\n * Implementation: uses `ffmpeg` to read RTSP and passthrough to MPEG-TS over HTTP.\n * Richiede `ffmpeg` installato nel sistema.\n */\nexport function createRtspProxyServer(opts: RtspProxyServerOptions): http.Server {\n return http.createServer((req, res) => {\n if (!req.url) {\n res.statusCode = 400;\n res.end(\"Bad Request\");\n return;\n }\n const u = new URL(req.url, `http://127.0.0.1:${opts.listenPort}`);\n if (u.pathname !== \"/stream\") {\n res.statusCode = 404;\n res.end(\"Not Found\");\n return;\n }\n\n const channel = Number(u.searchParams.get(\"channel\") ?? \"0\");\n const profile = (u.searchParams.get(\"profile\") ?? \"sub\") as RtspStreamProfile;\n if (!Number.isFinite(channel) || channel < 0) {\n res.statusCode = 400;\n res.end(\"Invalid channel\");\n return;\n }\n if (profile !== \"main\" && profile !== \"sub\" && profile !== \"ext\") {\n res.statusCode = 400;\n res.end(\"Invalid profile (must be main, sub, or ext)\");\n return;\n }\n\n const rtspUrl = buildRtspUrl({\n host: opts.host,\n username: opts.username,\n password: opts.password,\n channel,\n stream: profile,\n ...(opts.rtspPort === undefined ? {} : { port: opts.rtspPort }),\n });\n\n res.writeHead(200, {\n \"Content-Type\": \"video/mp2t\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"close\",\n });\n\n const rtspTransport = opts.rtspTransport ?? \"tcp\";\n const ff = spawn(\"ffmpeg\", [\n \"-hide_banner\",\n \"-loglevel\",\n \"error\",\n \"-rtsp_transport\",\n rtspTransport,\n \"-i\",\n rtspUrl,\n \"-an\",\n \"-c:v\",\n \"copy\",\n \"-f\",\n \"mpegts\",\n \"pipe:1\",\n ]);\n\n ff.stdout.pipe(res);\n ff.stderr.on(\"data\", () => {\n // opzionale: log\n });\n\n const cleanup = () => {\n ff.kill(\"SIGKILL\");\n };\n req.on(\"close\", cleanup);\n res.on(\"close\", cleanup);\n ff.on(\"close\", () => {\n res.end();\n });\n });\n}\n\n","import net from \"node:net\";\nimport crypto from \"node:crypto\";\n\nexport type VideoType = \"H264\" | \"H265\";\n\nexport type AudioConfig =\n | {\n codec: \"aac\";\n payloadType: number;\n sampleRate: number;\n channels: number;\n configHex: string;\n }\n | {\n codec: \"opus\";\n payloadType: number;\n sampleRate: number;\n channels: number;\n };\n\nexport interface VideoParamSets {\n videoType: VideoType;\n payloadType: number;\n h264?: {\n sps: Buffer;\n pps: Buffer;\n profileLevelId?: string;\n };\n h265?: {\n vps: Buffer;\n sps: Buffer;\n pps: Buffer;\n };\n}\n\nexport function buildRfc4571Sdp(\n video: VideoParamSets,\n audio?: AudioConfig,\n): string {\n let out = \"v=0\\r\\n\";\n out += \"o=- 0 0 IN IP4 0.0.0.0\\r\\n\";\n out += \"s=No Name\\r\\n\";\n out += \"t=0 0\\r\\n\";\n\n out += `m=video 0 RTP/AVP ${video.payloadType}\\r\\n`;\n out += \"c=IN IP4 0.0.0.0\\r\\n\";\n out += `a=rtpmap:${video.payloadType} ${video.videoType}/90000\\r\\n`;\n\n if (video.videoType === \"H264\" && video.h264) {\n const spsB64 = video.h264.sps.toString(\"base64\");\n const ppsB64 = video.h264.pps.toString(\"base64\");\n const pli = video.h264.profileLevelId\n ? `profile-level-id=${video.h264.profileLevelId};`\n : \"\";\n out += `a=fmtp:${video.payloadType} packetization-mode=1;${pli}sprop-parameter-sets=${spsB64},${ppsB64}\\r\\n`;\n }\n\n if (video.videoType === \"H265\" && video.h265) {\n const vpsB64 = video.h265.vps.toString(\"base64\");\n const spsB64 = video.h265.sps.toString(\"base64\");\n const ppsB64 = video.h265.pps.toString(\"base64\");\n out += `a=fmtp:${video.payloadType} sprop-vps=${vpsB64};sprop-sps=${spsB64};sprop-pps=${ppsB64}\\r\\n`;\n }\n\n if (audio?.codec === \"aac\") {\n out += `m=audio 0 RTP/AVP ${audio.payloadType}\\r\\n`;\n out += \"c=IN IP4 0.0.0.0\\r\\n\";\n out += \"b=AS:128\\r\\n\";\n out += `a=rtpmap:${audio.payloadType} MPEG4-GENERIC/${audio.sampleRate}/${audio.channels}\\r\\n`;\n out += `a=fmtp:${audio.payloadType} profile-level-id=1; mode=AAC-hbr; sizelength=13; indexlength=3; indexdeltalength=3; config=${audio.configHex}\\r\\n`;\n }\n\n if (audio?.codec === \"opus\") {\n out += `m=audio 0 RTP/AVP ${audio.payloadType}\\r\\n`;\n out += \"c=IN IP4 0.0.0.0\\r\\n\";\n out += \"b=AS:128\\r\\n\";\n out += `a=rtpmap:${audio.payloadType} opus/${audio.sampleRate}/${audio.channels}\\r\\n`;\n }\n\n return out;\n}\n\nexport function splitAnnexBToNals(annexB: Buffer): Buffer[] {\n // Returns NAL units WITHOUT start codes.\n // Keep behavior aligned with nodelink-js (no aggressive trimming), since some payloads\n // may legitimately end with 0x00.\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\nfunction findAnnexBStartCodeOffset(data: Buffer, maxScan = 64): number {\n if (!data?.length) return -1;\n const len = Math.min(data.length, Math.max(0, maxScan));\n for (let i = 0; i + 3 < len; i++) {\n if (data[i] !== 0x00 || data[i + 1] !== 0x00) continue;\n if (data[i + 2] === 0x01) return i;\n if (data[i + 2] === 0x00 && data[i + 3] === 0x01) return i;\n }\n return -1;\n}\n\nfunction stripLeadingAnnexBStartCode(data: Buffer): Buffer {\n if (data.length >= 4 && data[0] === 0x00 && data[1] === 0x00) {\n if (data[2] === 0x01) return data.subarray(3);\n if (data[2] === 0x00 && data[3] === 0x01) return data.subarray(4);\n }\n return data;\n}\n\nfunction splitLengthPrefixedToNals(\n data: Buffer,\n lengthSize: 1 | 2 | 3 | 4,\n endian: \"be\" | \"le\",\n): Buffer[] {\n // AVCC/HVCC style: [length][NAL bytes]...\n // lengthSize is typically 4, but some devices/streams use 2.\n const nals: Buffer[] = [];\n let offset = 0;\n\n const readLen = (buf: Buffer, at: number): number => {\n if (lengthSize === 4)\n return endian === \"be\" ? buf.readUInt32BE(at) : buf.readUInt32LE(at);\n if (lengthSize === 2)\n return endian === \"be\" ? buf.readUInt16BE(at) : buf.readUInt16LE(at);\n if (lengthSize === 3) {\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 return buf.readUInt8(at);\n };\n\n while (offset + lengthSize <= data.length) {\n const nalLen = readLen(data, offset);\n offset += lengthSize;\n\n // 0-length doesn't make sense for an access unit; treat as parse failure.\n if (!nalLen) return [];\n if (offset + nalLen > data.length) return [];\n\n const nal = data.subarray(offset, offset + nalLen);\n offset += nalLen;\n if (nal.length) nals.push(nal);\n }\n\n // Must consume the buffer cleanly; otherwise treat as unknown format.\n if (offset !== data.length) return [];\n return nals;\n}\n\nfunction scoreNals(videoType: VideoType | undefined, nals: Buffer[]): number {\n if (!nals.length) return Number.POSITIVE_INFINITY;\n\n let invalid = 0;\n for (const nal of nals) {\n if (!nal.length) {\n invalid++;\n continue;\n }\n\n // forbidden_zero_bit must be 0 for both H264 and H265\n if ((nal[0]! & 0x80) !== 0) {\n invalid += 10;\n continue;\n }\n\n if (videoType === \"H264\") {\n const t = nal[0]! & 0x1f;\n // 1..23 are defined; 24..31 are aggreg/fragmentation/reserved and should not appear in raw AUs.\n if (t < 1 || t > 23) invalid++;\n } else if (videoType === \"H265\") {\n if (nal.length < 2) {\n invalid++;\n continue;\n }\n const t = (nal[0]! >> 1) & 0x3f;\n // Reject reserved/forbidden types that strongly suggest mis-parsing.\n // Keep common types: 0-29 (VCL), 32-40 (VPS/SPS/PPS/AUD/SEI/etc), 48-50 (AP/FU/PACI)\n const isValid =\n (t >= 0 && t <= 29) || (t >= 32 && t <= 40) || (t >= 48 && t <= 50);\n if (!isValid) invalid++;\n }\n }\n\n return invalid;\n}\n\nfunction splitAccessUnitToNalsBestEffort(\n accessUnit: Buffer,\n videoType?: VideoType,\n): Buffer[] {\n if (!accessUnit?.length) return [];\n\n const candidates: Buffer[][] = [];\n\n // Annex-B is only considered if a start code appears at the start (or shortly after);\n // scanning the entire buffer can false-positive on random 0x000001 sequences.\n const scOffset = findAnnexBStartCodeOffset(accessUnit, 64);\n if (scOffset >= 0) {\n const annex = scOffset === 0 ? accessUnit : accessUnit.subarray(scOffset);\n const nals = splitAnnexBToNals(annex);\n if (nals.length) candidates.push(nals);\n }\n\n // If it looks like AVCC/HVCC (often starts with 0x00 0x00 ..), try length-prefixed parsing.\n // Some Reolink streams appear to use 2-byte NAL lengths.\n candidates.push(splitLengthPrefixedToNals(accessUnit, 4, \"be\"));\n candidates.push(splitLengthPrefixedToNals(accessUnit, 4, \"le\"));\n candidates.push(splitLengthPrefixedToNals(accessUnit, 3, \"be\"));\n candidates.push(splitLengthPrefixedToNals(accessUnit, 3, \"le\"));\n candidates.push(splitLengthPrefixedToNals(accessUnit, 2, \"be\"));\n candidates.push(splitLengthPrefixedToNals(accessUnit, 2, \"le\"));\n candidates.push(splitLengthPrefixedToNals(accessUnit, 1, \"be\"));\n\n // Single NAL without start code (or unknown packaging). Strip a leading start code if present.\n const stripped = stripLeadingAnnexBStartCode(accessUnit);\n if (stripped.length) candidates.push([stripped]);\n\n let best: Buffer[] = [];\n let bestScore = Number.POSITIVE_INFINITY;\n for (const cand of candidates) {\n if (!cand.length) continue;\n const s = scoreNals(videoType, cand);\n if (s < bestScore) {\n bestScore = s;\n best = cand;\n if (bestScore === 0) break;\n }\n }\n\n return best;\n}\n\nexport function extractH264ParamSetsFromAccessUnit(annexB: Buffer): {\n sps?: Buffer;\n pps?: Buffer;\n profileLevelId?: string;\n} {\n const nals = splitAccessUnitToNalsBestEffort(annexB, \"H264\");\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 const hex = Buffer.from([nal[1]!, nal[2]!, nal[3]!]).toString(\"hex\");\n profileLevelId = hex;\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\nexport function extractH265ParamSetsFromAccessUnit(annexB: Buffer): {\n vps?: Buffer;\n sps?: Buffer;\n pps?: Buffer;\n} {\n const nals = splitAccessUnitToNalsBestEffort(annexB, \"H265\");\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\nconst aacSampleRates = [\n 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025,\n 8000, 7350,\n];\n\nexport function buildAacAudioSpecificConfigHex(params: {\n sampleRate: number;\n channels: number;\n audioObjectType?: number;\n}): string | undefined {\n const { sampleRate, channels } = params;\n const audioObjectType = params.audioObjectType ?? 2; // AAC-LC\n const samplingFreqIndex = aacSampleRates.indexOf(sampleRate);\n if (samplingFreqIndex < 0) return;\n if (!Number.isFinite(channels) || channels <= 0 || channels > 15) return;\n if (\n !Number.isFinite(audioObjectType) ||\n audioObjectType <= 0 ||\n audioObjectType > 31\n )\n return;\n\n // AudioSpecificConfig (2 bytes, left-aligned)\n const asc =\n ((audioObjectType & 0x1f) << 11) |\n ((samplingFreqIndex & 0x0f) << 7) |\n ((channels & 0x0f) << 3);\n return asc.toString(16).padStart(4, \"0\");\n}\n\nexport function parseAdtsHeader(\n adtsFrame: Buffer,\n): {\n headerLength: number;\n sampleRate: number;\n channels: number;\n configHex: string;\n} | null {\n if (adtsFrame.length < 7) return null;\n if (adtsFrame[0]! !== 0xff || (adtsFrame[1]! & 0xf0) !== 0xf0) return null;\n\n const protectionAbsent = (adtsFrame[1]! & 0x01) === 1;\n const profile = (adtsFrame[2]! & 0xc0) >> 6; // 0=Main,1=LC...\n const audioObjectType = profile + 1;\n const samplingFreqIndex = (adtsFrame[2]! & 0x3c) >> 2;\n const sampleRate = aacSampleRates[samplingFreqIndex] ?? 0;\n const channels =\n ((adtsFrame[2]! & 0x01) << 2) | ((adtsFrame[3]! & 0xc0) >> 6);\n\n if (!sampleRate || !channels) return null;\n\n const headerLength = protectionAbsent ? 7 : 9;\n if (adtsFrame.length < headerLength) return null;\n\n // AudioSpecificConfig (2 bytes, left-aligned)\n const asc =\n ((audioObjectType & 0x1f) << 11) |\n ((samplingFreqIndex & 0x0f) << 7) |\n ((channels & 0x0f) << 3);\n const configHex = asc.toString(16).padStart(4, \"0\");\n\n return { headerLength, sampleRate, channels, configHex };\n}\n\nexport interface RtpPacketizationOptions {\n maxRtpPayload: number;\n}\n\nclass RtpWriter {\n private seq = 0;\n private timestamp = 0;\n private ssrc = crypto.randomBytes(4).readUInt32BE(0);\n\n constructor(private payloadType: number) {\n this.seq = crypto.randomBytes(2).readUInt16BE(0);\n // Random initial timestamp improves interoperability with RTP receivers\n // (including WebRTC bridging) and matches common RTP best practices.\n this.timestamp = crypto.randomBytes(4).readUInt32BE(0);\n }\n\n setTimestamp(ts: number) {\n this.timestamp = ts >>> 0;\n }\n\n getTimestamp(): number {\n return this.timestamp >>> 0;\n }\n\n advanceTimestamp(delta: number) {\n this.timestamp = (this.timestamp + (delta >>> 0)) >>> 0;\n }\n\n writePacket(payload: Buffer, marker: boolean): Buffer {\n const header = Buffer.alloc(12);\n header[0] = 0x80;\n header[1] = (marker ? 0x80 : 0x00) | (this.payloadType & 0x7f);\n header.writeUInt16BE(this.seq & 0xffff, 2);\n header.writeUInt32BE(this.timestamp >>> 0, 4);\n header.writeUInt32BE(this.ssrc >>> 0, 8);\n this.seq = (this.seq + 1) & 0xffff;\n return Buffer.concat([header, payload]);\n }\n}\n\nexport function packetizeH264(\n nal: Buffer,\n rtp: RtpWriter,\n opts: RtpPacketizationOptions,\n markerOnLast: boolean,\n isLastNal: boolean,\n): Buffer[] {\n const max = opts.maxRtpPayload;\n const out: Buffer[] = [];\n if (nal.length <= max) {\n out.push(rtp.writePacket(nal, markerOnLast && isLastNal));\n return out;\n }\n\n const nal0 = nal[0]!;\n const f = nal0 & 0x80;\n const nri = nal0 & 0x60;\n const type = nal0 & 0x1f;\n const fuIndicator = f | nri | 28;\n\n const data = nal.subarray(1);\n let offset = 0;\n while (offset < data.length) {\n const remaining = data.length - offset;\n const chunkLen = Math.min(remaining, max - 2);\n const start = offset === 0;\n const end = offset + chunkLen >= data.length;\n const fuHeader =\n (start ? 0x80 : 0x00) | (end ? 0x40 : 0x00) | (type & 0x1f);\n const payload = Buffer.concat([\n Buffer.from([fuIndicator, fuHeader]),\n data.subarray(offset, offset + chunkLen),\n ]);\n out.push(rtp.writePacket(payload, markerOnLast && isLastNal && end));\n offset += chunkLen;\n }\n\n return out;\n}\n\nexport function packetizeH265(\n nal: Buffer,\n rtp: RtpWriter,\n opts: RtpPacketizationOptions,\n markerOnLast: boolean,\n isLastNal: boolean,\n): Buffer[] {\n const max = opts.maxRtpPayload;\n const out: Buffer[] = [];\n if (nal.length <= max) {\n out.push(rtp.writePacket(nal, markerOnLast && isLastNal));\n return out;\n }\n\n if (nal.length < 3) return out;\n\n const nalHeader0 = nal[0]!;\n const nalHeader1 = nal[1]!;\n const nalType = (nalHeader0 >> 1) & 0x3f;\n\n // FU indicator: F + type=49 + layerId/tid bits preserved.\n const fuIndicator0 = (nalHeader0 & 0x81) | (49 << 1);\n const fuIndicator1 = nalHeader1;\n\n const data = nal.subarray(2);\n let offset = 0;\n while (offset < data.length) {\n const remaining = data.length - offset;\n const chunkLen = Math.min(remaining, max - 3);\n const start = offset === 0;\n const end = offset + chunkLen >= data.length;\n const fuHeader =\n (start ? 0x80 : 0x00) | (end ? 0x40 : 0x00) | (nalType & 0x3f);\n const payload = Buffer.concat([\n Buffer.from([fuIndicator0, fuIndicator1, fuHeader]),\n data.subarray(offset, offset + chunkLen),\n ]);\n out.push(rtp.writePacket(payload, markerOnLast && isLastNal && end));\n offset += chunkLen;\n }\n\n return out;\n}\n\nexport function packetizeAacAdtsFrame(\n adts: Buffer,\n rtp: RtpWriter,\n): {\n packets: Buffer[];\n config?: { sampleRate: number; channels: number; configHex: string };\n} {\n const parsed = parseAdtsHeader(adts);\n if (!parsed) return { packets: [] };\n const raw = adts.subarray(parsed.headerLength);\n if (!raw.length) return { packets: [] };\n\n // RFC 3640: AU-headers-length (16 bits) + AU-header (16 bits)\n const auHeadersLength = Buffer.from([0x00, 0x10]);\n const auSize = raw.length & 0x1fff;\n const auHeader = Buffer.alloc(2);\n auHeader[0] = (auSize >> 5) & 0xff;\n auHeader[1] = (auSize & 0x1f) << 3;\n\n const payload = Buffer.concat([auHeadersLength, auHeader, raw]);\n return {\n packets: [rtp.writePacket(payload, true)],\n config: {\n sampleRate: parsed.sampleRate,\n channels: parsed.channels,\n configHex: parsed.configHex,\n },\n };\n}\n\nexport function packetizeAacRawFrame(\n raw: Buffer,\n rtp: RtpWriter,\n): { packets: Buffer[] } {\n if (!raw?.length) return { packets: [] };\n\n // RFC 3640: AU-headers-length (16 bits) + AU-header (16 bits)\n const auHeadersLength = Buffer.from([0x00, 0x10]);\n const auSize = raw.length & 0x1fff;\n const auHeader = Buffer.alloc(2);\n auHeader[0] = (auSize >> 5) & 0xff;\n auHeader[1] = (auSize & 0x1f) << 3;\n\n const payload = Buffer.concat([auHeadersLength, auHeader, raw]);\n return {\n packets: [rtp.writePacket(payload, true)],\n };\n}\n\nexport interface Rfc4571Client {\n socket: net.Socket;\n needsKeyframe: boolean;\n sentVideoConfig: boolean;\n}\n\nexport class Rfc4571Muxer {\n private clients = new Set<Rfc4571Client>();\n private closed = false;\n\n private videoRtp: RtpWriter;\n private audioRtp: RtpWriter | undefined;\n\n // Timestamp tracking\n // bcmedia microseconds is a u32 clock that may wrap (2^32) and may reset on stream restarts.\n // Additionally, it may jump forward unexpectedly. Since we do not buffer, large forward jumps\n // cause downstream schedulers to detect discontinuities.\n //\n // Strategy:\n // - unwrap u32 wraps when likely\n // - treat backwards jumps as restarts\n // - use microseconds deltas only when \"reasonable\"; otherwise advance using a smoothed/fallback delta\n private videoLastUsRaw: number | undefined;\n private videoUsWrapOffset = 0;\n private videoLastAbsUs: number | undefined;\n private videoAvgDeltaUs: number | undefined;\n private readonly videoClockRate = 90000;\n private readonly fallbackVideoIncrement: number;\n private readonly fallbackVideoDeltaUs: number;\n private readonly maxTrustedDeltaUs = 500_000; // 0.5s\n private readonly emaAlpha = 0.1;\n\n // Throttled logging to avoid spamming in hot paths.\n private videoTimingLogState: {\n lastUntrustedDeltaLogMs: number;\n lastWrapLogMs: number;\n lastResetLogMs: number;\n untrustedDeltaCount: number;\n wrapCount: number;\n resetCount: number;\n } = {\n lastUntrustedDeltaLogMs: 0,\n lastWrapLogMs: 0,\n lastResetLogMs: 0,\n untrustedDeltaCount: 0,\n wrapCount: 0,\n resetCount: 0,\n };\n\n private cachedH264ParamSetsAnnexB: Buffer | undefined;\n private cachedH265ParamSetsAnnexB: Buffer | undefined;\n\n constructor(\n private logger: Console,\n private videoPayloadType: number,\n audioPayloadType: number | undefined,\n videoFpsFallback = 25,\n private maxRtpPayload = 1200,\n ) {\n this.videoRtp = new RtpWriter(videoPayloadType);\n if (audioPayloadType !== undefined) {\n this.audioRtp = new RtpWriter(audioPayloadType);\n }\n this.fallbackVideoIncrement = Math.max(\n 1,\n Math.round(this.videoClockRate / Math.max(1, videoFpsFallback)),\n );\n this.fallbackVideoDeltaUs = Math.max(\n 1,\n Math.round(\n (this.fallbackVideoIncrement * 1_000_000) / this.videoClockRate,\n ),\n );\n }\n\n addClient(socket: net.Socket) {\n if (this.closed) {\n socket.destroy();\n return;\n }\n\n const client: Rfc4571Client = {\n socket,\n needsKeyframe: true,\n sentVideoConfig: false,\n };\n this.clients.add(client);\n\n const cleanup = () => {\n this.clients.delete(client);\n try {\n socket.destroy();\n } catch {\n // ignore\n }\n };\n\n socket.on(\"error\", cleanup);\n socket.on(\"close\", cleanup);\n }\n\n private static readonly ANNEXB_START_CODE = Buffer.from([\n 0x00, 0x00, 0x00, 0x01,\n ]);\n\n private static joinAnnexBNals(\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(Rfc4571Muxer.ANNEXB_START_CODE, nal);\n }\n return Buffer.concat(parts);\n }\n\n private updateVideoParamSetsFromAccessUnit(\n videoType: VideoType,\n accessUnit: Buffer,\n ): void {\n if (!accessUnit?.length) return;\n\n if (videoType === \"H264\") {\n const { sps, pps } = extractH264ParamSetsFromAccessUnit(accessUnit);\n if (sps && pps) {\n this.cachedH264ParamSetsAnnexB = Rfc4571Muxer.joinAnnexBNals(sps, pps);\n }\n return;\n }\n\n const { vps, sps, pps } = extractH265ParamSetsFromAccessUnit(accessUnit);\n if (vps && sps && pps) {\n this.cachedH265ParamSetsAnnexB = Rfc4571Muxer.joinAnnexBNals(\n vps,\n sps,\n pps,\n );\n }\n }\n\n private sendCachedVideoConfigToClient(\n videoType: VideoType,\n client: Rfc4571Client,\n ): void {\n if (this.closed) return;\n if (client.sentVideoConfig) return;\n\n const paramSetsAnnexB =\n videoType === \"H265\"\n ? this.cachedH265ParamSetsAnnexB\n : this.cachedH264ParamSetsAnnexB;\n if (!paramSetsAnnexB?.length) return;\n\n const nals = splitAccessUnitToNalsBestEffort(paramSetsAnnexB, videoType);\n if (!nals.length) return;\n\n const opts: RtpPacketizationOptions = { maxRtpPayload: this.maxRtpPayload };\n for (let i = 0; i < nals.length; i++) {\n const nal = nals[i]!;\n const isLastNal = i === nals.length - 1;\n const packets =\n videoType === \"H265\"\n ? packetizeH265(nal, this.videoRtp, opts, false, isLastNal)\n : packetizeH264(nal, this.videoRtp, opts, false, isLastNal);\n\n for (const pkt of packets) {\n this.writeRtpPacketToClient(client, pkt);\n }\n }\n\n client.sentVideoConfig = true;\n }\n\n close() {\n if (this.closed) return;\n this.closed = true;\n for (const c of Array.from(this.clients)) {\n try {\n c.socket.destroy();\n } catch {\n // ignore\n }\n }\n this.clients.clear();\n }\n\n private writeRtpPacketToClient(client: Rfc4571Client, pkt: Buffer) {\n if (client.socket.destroyed || !client.socket.writable) return;\n\n const header = Buffer.alloc(2);\n header.writeUInt16BE(pkt.length & 0xffff, 0);\n const framed = Buffer.concat([header, pkt]);\n\n try {\n client.socket.write(framed);\n } catch {\n try {\n client.socket.destroy();\n } catch {\n // ignore\n }\n }\n }\n\n private writeRtpPacketToClients(\n pkt: Buffer,\n predicate: (client: Rfc4571Client) => boolean,\n ) {\n for (const c of this.clients) {\n if (!predicate(c)) continue;\n this.writeRtpPacketToClient(c, pkt);\n }\n }\n\n private resetVideoTimestampMapping(): void {\n // Reset microseconds tracking/mapping, but keep RTP writer state (seq/ssrc/timestamp).\n this.videoLastUsRaw = undefined;\n this.videoUsWrapOffset = 0;\n this.videoLastAbsUs = undefined;\n this.videoAvgDeltaUs = undefined;\n }\n\n private logVideoTiming(\n kind: \"untrusted-delta\" | \"wrap\" | \"reset\",\n message: string,\n ): void {\n const now = Date.now();\n const intervalMs = 5000;\n const state = this.videoTimingLogState;\n if (kind === \"untrusted-delta\") {\n state.untrustedDeltaCount++;\n if (now - state.lastUntrustedDeltaLogMs < intervalMs) return;\n state.lastUntrustedDeltaLogMs = now;\n this.logger.warn(\n `[rfc4571] video timing: ${message} (untrustedDeltaCount=${state.untrustedDeltaCount})`,\n );\n return;\n }\n if (kind === \"wrap\") {\n state.wrapCount++;\n if (now - state.lastWrapLogMs < intervalMs) return;\n state.lastWrapLogMs = now;\n this.logger.warn(\n `[rfc4571] video timing: ${message} (wrapCount=${state.wrapCount})`,\n );\n return;\n }\n state.resetCount++;\n if (now - state.lastResetLogMs < intervalMs) return;\n state.lastResetLogMs = now;\n this.logger.warn(\n `[rfc4571] video timing: ${message} (resetCount=${state.resetCount})`,\n );\n }\n\n private advanceVideoTimestampFallback(): void {\n this.videoRtp.advanceTimestamp(this.fallbackVideoIncrement);\n }\n\n setVideoTimestampFromMicroseconds(\n frameMicroseconds: number | null | undefined,\n ) {\n if (frameMicroseconds === null || frameMicroseconds === undefined) {\n this.advanceVideoTimestampFallback();\n return;\n }\n if (!Number.isFinite(frameMicroseconds)) {\n this.advanceVideoTimestampFallback();\n return;\n }\n\n const curUsRaw = (frameMicroseconds >>> 0) as number;\n\n if (this.videoLastUsRaw !== undefined) {\n const lastUsRaw = this.videoLastUsRaw;\n if (curUsRaw < lastUsRaw) {\n // Heuristic: treat as wrap only if last is near max and current is near min.\n const wrapLikely = lastUsRaw > 0xf0000000 && curUsRaw < 0x0fffffff;\n if (wrapLikely) {\n this.videoUsWrapOffset += 0x1_0000_0000;\n this.logVideoTiming(\n \"wrap\",\n `detected u32 wrap (lastUsRaw=${lastUsRaw} curUsRaw=${curUsRaw} wrapOffset=${this.videoUsWrapOffset})`,\n );\n } else {\n // Likely stream restart / discontinuity: reset mapping.\n this.logVideoTiming(\n \"reset\",\n `detected backwards jump; resetting mapping (lastUsRaw=${lastUsRaw} curUsRaw=${curUsRaw})`,\n );\n this.resetVideoTimestampMapping();\n }\n }\n }\n\n this.videoLastUsRaw = curUsRaw;\n const absUs = this.videoUsWrapOffset + curUsRaw;\n\n if (this.videoLastAbsUs === undefined) {\n this.videoLastAbsUs = absUs;\n if (this.videoAvgDeltaUs === undefined)\n this.videoAvgDeltaUs = this.fallbackVideoDeltaUs;\n return;\n }\n\n const deltaUs = absUs - this.videoLastAbsUs;\n this.videoLastAbsUs = absUs;\n\n const trusted =\n Number.isFinite(deltaUs) &&\n deltaUs > 0 &&\n deltaUs <= this.maxTrustedDeltaUs;\n let effectiveDeltaUs: number;\n if (trusted) {\n const prevAvg = this.videoAvgDeltaUs ?? deltaUs;\n this.videoAvgDeltaUs = prevAvg + (deltaUs - prevAvg) * this.emaAlpha;\n effectiveDeltaUs = deltaUs;\n } else {\n this.logVideoTiming(\n \"untrusted-delta\",\n `discarded deltaUs=${deltaUs} (absUs=${absUs} lastAbsUs=${this.videoLastAbsUs} avgDeltaUs=${this.videoAvgDeltaUs ?? \"n/a\"}); using fallback`,\n );\n effectiveDeltaUs = this.videoAvgDeltaUs ?? this.fallbackVideoDeltaUs;\n }\n\n const inc = Math.max(\n 1,\n Math.round((effectiveDeltaUs * this.videoClockRate) / 1_000_000),\n );\n this.videoRtp.advanceTimestamp(inc);\n }\n\n sendVideoAccessUnit(\n videoType: VideoType,\n accessUnitAnnexB: Buffer,\n isKeyframe: boolean,\n microseconds: number | null | undefined,\n ) {\n if (this.closed) return;\n\n // Always attempt to learn current VPS/SPS/PPS (or SPS/PPS) even if no clients are connected,\n // so that the first client can be primed immediately.\n this.updateVideoParamSetsFromAccessUnit(videoType, accessUnitAnnexB);\n\n const nals = splitAccessUnitToNalsBestEffort(accessUnitAnnexB, videoType);\n if (!nals.length) return;\n\n // Some sources mislabel keyframes. Derive a best-effort keyframe flag from NAL types\n // to avoid per-client gating getting stuck (symptom: 1 frame per GOP).\n let derivedKeyframe = false;\n if (videoType === \"H264\") {\n for (const nal of nals) {\n const t = nal[0]! & 0x1f;\n if (t === 5) {\n // IDR\n derivedKeyframe = true;\n break;\n }\n }\n } else {\n for (const nal of nals) {\n if (nal.length < 2) continue;\n const t = (nal[0]! >> 1) & 0x3f;\n // IRAP pictures: 16..23 (BLA/IDR/CRA)\n if (t >= 16 && t <= 23) {\n derivedKeyframe = true;\n break;\n }\n }\n }\n\n const effectiveKeyframe = isKeyframe || derivedKeyframe;\n\n // gate per-client until keyframe: do NOT stall existing synced clients.\n // If a new client connects (needsKeyframe=true), only that client will wait.\n const shouldSendTo = (c: Rfc4571Client) =>\n effectiveKeyframe ? true : !c.needsKeyframe;\n let hasAnyTarget = false;\n for (const c of this.clients) {\n if (shouldSendTo(c)) {\n hasAnyTarget = true;\n break;\n }\n }\n // If no clients are ready for media yet (e.g. all are waiting for keyframe),\n // we can still send cached video config to help decoders initialize.\n if (!hasAnyTarget) {\n for (const c of this.clients) {\n if (!c.needsKeyframe) continue;\n this.sendCachedVideoConfigToClient(videoType, c);\n }\n return;\n }\n\n this.setVideoTimestampFromMicroseconds(microseconds);\n\n // Prime clients waiting for keyframe with VPS/SPS/PPS (or SPS/PPS) when available.\n for (const c of this.clients) {\n if (!c.needsKeyframe) continue;\n this.sendCachedVideoConfigToClient(videoType, c);\n }\n\n const opts: RtpPacketizationOptions = { maxRtpPayload: this.maxRtpPayload };\n for (let i = 0; i < nals.length; i++) {\n const nal = nals[i]!;\n const isLastNal = i === nals.length - 1;\n const packets =\n videoType === \"H265\"\n ? packetizeH265(nal, this.videoRtp, opts, true, isLastNal)\n : packetizeH264(nal, this.videoRtp, opts, true, isLastNal);\n\n for (const pkt of packets)\n this.writeRtpPacketToClients(pkt, shouldSendTo);\n }\n\n // mark clients as started when the keyframe passes through\n if (effectiveKeyframe) {\n for (const c of this.clients) c.needsKeyframe = false;\n }\n }\n\n sendAudioAdtsFrame(adts: Buffer): {\n parsed?: { sampleRate: number; channels: number; configHex: string };\n } {\n if (this.closed) return {};\n if (!this.audioRtp) return {};\n\n const { packets, config } = packetizeAacAdtsFrame(adts, this.audioRtp);\n // keep audio aligned with video gating: only send once video has started.\n for (const pkt of packets)\n this.writeRtpPacketToClients(pkt, (c) => !c.needsKeyframe);\n\n // advance by 1024 samples per AAC-LC frame\n if (packets.length) this.audioRtp.advanceTimestamp(1024);\n\n return config ? { parsed: config } : {};\n }\n\n sendAudioAacRawFrame(raw: Buffer): void {\n if (this.closed) return;\n if (!this.audioRtp) return;\n\n const { packets } = packetizeAacRawFrame(raw, this.audioRtp);\n // keep audio aligned with video gating: only send once video has started.\n for (const pkt of packets)\n this.writeRtpPacketToClients(pkt, (c) => !c.needsKeyframe);\n\n // advance by 1024 samples per AAC-LC frame\n if (packets.length) this.audioRtp.advanceTimestamp(1024);\n }\n\n sendAudioRtpPacket(rtpPacket: Buffer): void {\n if (this.closed) return;\n if (!rtpPacket || rtpPacket.length < 12) return;\n const version = (rtpPacket[0]! >> 6) & 0x03;\n if (version !== 2) return;\n\n // keep audio aligned with video gating: only send once video has started.\n this.writeRtpPacketToClients(rtpPacket, (c) => !c.needsKeyframe);\n }\n\n private static parseRtpPayload(packet: Buffer): Buffer | undefined {\n if (!packet || packet.length < 12) return;\n const version = (packet[0]! >> 6) & 0x03;\n if (version !== 2) return;\n\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;\n\n if (extension) {\n if (offset + 4 > packet.length) return;\n const extLenWords = packet.readUInt16BE(offset + 2);\n offset += 4 + extLenWords * 4;\n if (offset > packet.length) return;\n }\n\n let end = packet.length;\n if (padding) {\n const padLen = packet[packet.length - 1]!;\n if (padLen <= 0 || padLen > packet.length) return;\n end = packet.length - padLen;\n if (end < offset) return;\n }\n\n if (end <= offset) return;\n return packet.subarray(offset, end);\n }\n\n private static isH264KeyframeLikeRtpPacket(packet: Buffer): boolean {\n const payload = Rfc4571Muxer.parseRtpPayload(packet);\n if (!payload || payload.length < 1) return false;\n\n const nalType = payload[0]! & 0x1f;\n\n // Single NAL\n if (nalType >= 1 && nalType <= 23) {\n return nalType === 5 || nalType === 7 || nalType === 8;\n }\n\n // STAP-A\n if (nalType === 24) {\n let offset = 1;\n while (offset + 2 <= payload.length) {\n const size = payload.readUInt16BE(offset);\n offset += 2;\n if (!size || offset + size > payload.length) break;\n const t = payload[offset]! & 0x1f;\n if (t === 5 || t === 7 || t === 8) return true;\n offset += size;\n }\n return false;\n }\n\n // FU-A\n if (nalType === 28 && payload.length >= 2) {\n const fuHeader = payload[1]!;\n const start = (fuHeader & 0x80) !== 0;\n const origType = fuHeader & 0x1f;\n if (!start) return false;\n return origType === 5 || origType === 7 || origType === 8;\n }\n\n return false;\n }\n\n private static isH265KeyframeLikeRtpPacket(packet: Buffer): boolean {\n const payload = Rfc4571Muxer.parseRtpPayload(packet);\n if (!payload || payload.length < 2) return false;\n\n const nalType = (payload[0]! >> 1) & 0x3f;\n\n const isKeyNalType = (t: number) => {\n // IRAP: 16..21, VPS/SPS/PPS: 32/33/34\n if (t >= 16 && t <= 21) return true;\n if (t === 32 || t === 33 || t === 34) return true;\n return false;\n };\n\n // Fragmentation Unit (FU): nalType==49, original nal type in payload[2]\n if (nalType === 49 && payload.length >= 3) {\n const fuHeader = payload[2]!;\n const start = (fuHeader & 0x80) !== 0;\n const origType = fuHeader & 0x3f;\n if (!start) return false;\n return isKeyNalType(origType);\n }\n\n return isKeyNalType(nalType);\n }\n\n sendVideoRtpPacket(videoType: VideoType, rtpPacket: Buffer): void {\n if (this.closed) return;\n if (!rtpPacket || rtpPacket.length < 12) return;\n const version = (rtpPacket[0]! >> 6) & 0x03;\n if (version !== 2) return;\n\n const isKeyframeLike =\n videoType === \"H265\"\n ? Rfc4571Muxer.isH265KeyframeLikeRtpPacket(rtpPacket)\n : Rfc4571Muxer.isH264KeyframeLikeRtpPacket(rtpPacket);\n\n const shouldSendTo = (c: Rfc4571Client) =>\n isKeyframeLike ? true : !c.needsKeyframe;\n this.writeRtpPacketToClients(rtpPacket, shouldSendTo);\n\n if (isKeyframeLike) {\n for (const c of this.clients) c.needsKeyframe = false;\n }\n }\n}\n","import type net from \"node:net\";\nimport netImpl from \"node:net\";\nimport type { ReolinkBaichuanApi } from \"../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { NativeVideoStreamVariant } from \"../reolink/baichuan/types\";\nimport type { StreamProfile } from \"../reolink/baichuan/types\";\nimport type { BaichuanClient } from \"../client/BaichuanClient\";\nimport { BaichuanVideoStream } from \"../baichuan/stream/BaichuanVideoStream\";\nimport {\n CompositeStream,\n type CompositeStreamPipOptions,\n} from \"../multifocal/compositeStream\";\nimport {\n buildRfc4571Sdp,\n buildAacAudioSpecificConfigHex,\n extractH264ParamSetsFromAccessUnit,\n extractH265ParamSetsFromAccessUnit,\n parseAdtsHeader,\n Rfc4571Muxer,\n type AudioConfig,\n type VideoParamSets,\n type VideoType,\n} from \"./rfc4571\";\n// (no RTSP URL helpers needed here: we reuse buildVideoStreamOptions)\n\nexport interface Rfc4571ApiFactoryContext {\n channel?: number;\n profile: StreamProfile;\n variant?: NativeVideoStreamVariant;\n composite: boolean;\n}\n\nexport interface Rfc4571TcpServerOptions {\n /**\n * Base Baichuan API session.\n * Prefer using `getApi` when the caller wants lazy creation and/or to ensure distinct sessions.\n */\n api?: ReolinkBaichuanApi;\n\n /**\n * Optional API factory. If provided, `createRfc4571TcpServer` will call it once to obtain the base API.\n * This is useful when the caller wants to defer login/session creation until stream startup.\n */\n getApi?: (\n ctx?: Rfc4571ApiFactoryContext,\n ) => Promise<ReolinkBaichuanApi> | ReolinkBaichuanApi;\n /** Channel number. If undefined, uses composite stream (multifocal cameras). */\n channel?: number;\n /** Stream profile. For composite streams, this selects the tele profile; wider is forced to `sub` (unless `ext`). */\n profile: StreamProfile;\n /** Native-only: TrackMix tele/autotrack variants (usually on NVR/Hub). */\n variant?: NativeVideoStreamVariant;\n logger: Console;\n\n host?: string;\n videoPayloadType?: number;\n audioPayloadType?: number;\n\n /** Used only to sanity-check / early-recreate on mismatched cache in callers. */\n expectedVideoType?: VideoType;\n\n /** How long to wait for an IDR/IRAP to extract parameter sets and produce SDP. */\n keyframeTimeoutMs?: number;\n\n /**\n * Stream uptime watchdog: if no packets are sent/received for this long,\n * the server will restart its internal pipeline (drop clients, restart the native stream).\n *\n * Default: 10s. Set to 0 to disable.\n */\n uptimeRestartMs?: number;\n\n /** Optional: auto-close when no clients are connected for a while. */\n idleTeardownMs?: number;\n\n /** If true (default), closes the passed API when tearing down. */\n closeApiOnTeardown?: boolean;\n username: string;\n password: string;\n /** If true, requires authentication before allowing stream access. Default: false. */\n requireAuth?: boolean;\n\n /** Composite stream options (only used when channel is undefined) */\n compositeOptions?: CompositeStreamPipOptions;\n\n /**\n * Optional identifier for the requested stream. Used to infer composite profile pairing\n * (e.g. \"composite-native-default-sub-sub\", \"composite-rtsp-default-sub-sub\").\n */\n requestedId?: string;\n\n /**\n * Optional AAC hint for when the camera sends raw AAC (no ADTS headers).\n * Many Reolink devices use AAC-LC mono at 8000Hz.\n */\n aacAudioHint?: { sampleRate: number; channels: number };\n\n /** Optional: dedicated APIs for composite wider/tele streams (recommended on NVR/Hub to avoid stream mixing). */\n compositeApis?: {\n widerApi: ReolinkBaichuanApi;\n teleApi: ReolinkBaichuanApi;\n };\n\n /** Optional composite API factory (called once) to obtain dedicated wider/tele sessions. */\n getCompositeApis?: () =>\n | Promise<{ widerApi: ReolinkBaichuanApi; teleApi: ReolinkBaichuanApi }>\n | { widerApi: ReolinkBaichuanApi; teleApi: ReolinkBaichuanApi };\n\n /**\n * External identifier for dedicated socket session.\n * When provided, a dedicated BaichuanClient is created for this deviceId.\n * This allows multiple concurrent streams without interference.\n * The dedicated socket is automatically closed when the stream ends.\n */\n deviceId?: string;\n}\n\nexport interface Rfc4571TcpServer {\n host: string;\n port: number;\n sdp: string;\n videoType: VideoType;\n audio?: { codec: \"aac\"; sampleRate: number; channels: number };\n username: string;\n password: string;\n\n server: net.Server;\n videoStream: BaichuanVideoStream | CompositeStream;\n\n close: (reason?: unknown) => Promise<void>;\n}\n\nexport async function createRfc4571TcpServer(\n options: Rfc4571TcpServerOptions,\n): Promise<Rfc4571TcpServer> {\n const isComposite = options.channel === undefined;\n\n const parseCompositeFromRequestedId = (\n requestedId: string | undefined,\n ):\n | {\n source: \"native\" | \"rtsp\";\n widerProfile?: StreamProfile;\n teleProfile?: StreamProfile;\n }\n | undefined => {\n if (!requestedId) return;\n const id = String(requestedId);\n\n const asProfile = (v: string | undefined): StreamProfile | undefined =>\n v === \"main\" || v === \"sub\" || v === \"ext\"\n ? (v as StreamProfile)\n : undefined;\n\n // Explicit source forms:\n // - composite-native-<wider>-<tele>\n // - composite-native-<variant>-<wider>-<tele>\n // - composite-rtsp-<wider>-<tele>\n // - composite-rtsp-<variant>-<wider>-<tele>\n if (\n id.startsWith(\"composite-native-\") ||\n id.startsWith(\"composite-rtsp-\")\n ) {\n const source: \"native\" | \"rtsp\" = id.startsWith(\"composite-rtsp-\")\n ? \"rtsp\"\n : \"native\";\n const parts = id.split(\"-\").filter(Boolean);\n // parts[0] === 'composite'\n // parts[1] === 'native' | 'rtsp'\n if (parts.length >= 4) {\n const wider = parts.length >= 5 ? parts[3] : parts[2];\n const tele = parts.length >= 5 ? parts[4] : parts[3];\n const widerProfile = asProfile(wider);\n const teleProfile = asProfile(tele);\n return {\n source,\n ...(widerProfile ? { widerProfile } : {}),\n ...(teleProfile ? { teleProfile } : {}),\n };\n }\n return { source };\n }\n\n return;\n };\n\n const apiFactoryCtx: Rfc4571ApiFactoryContext = {\n profile: options.profile,\n composite: isComposite,\n ...(options.channel !== undefined ? { channel: options.channel } : {}),\n ...(options.variant !== undefined ? { variant: options.variant } : {}),\n };\n\n const baseApi = options.api ?? (await options.getApi?.(apiFactoryCtx));\n if (!baseApi) {\n throw new Error(\"createRfc4571TcpServer: missing api/getApi\");\n }\n\n const resolvedCompositeApis =\n options.compositeApis ?? (await options.getCompositeApis?.());\n\n const {\n channel,\n profile,\n variant,\n logger,\n expectedVideoType,\n host = \"127.0.0.1\",\n videoPayloadType = 96,\n audioPayloadType = 97,\n keyframeTimeoutMs = 5000,\n uptimeRestartMs: uptimeRestartMsOpt,\n idleTeardownMs = 2500,\n closeApiOnTeardown = true,\n username,\n password,\n requireAuth = false,\n compositeOptions,\n requestedId,\n aacAudioHint,\n } = options;\n\n // Track dedicated session if deviceId is provided (for auto-release on close)\n let dedicatedSession:\n | {\n client: BaichuanClient;\n release: () => Promise<void>;\n }\n | undefined;\n\n const apisToClose = new Set<ReolinkBaichuanApi>();\n apisToClose.add(baseApi);\n if (resolvedCompositeApis?.widerApi)\n apisToClose.add(resolvedCompositeApis.widerApi);\n if (resolvedCompositeApis?.teleApi)\n apisToClose.add(resolvedCompositeApis.teleApi);\n\n // For composite (ffmpeg) streams, avoid over-aggressive restarts: a short burst of\n // backpressure or a long GOP on join can look like \"no activity\" even though the pipeline is alive.\n const uptimeRestartMs = uptimeRestartMsOpt ?? (isComposite ? 60_000 : 10_000);\n const variantSuffix =\n variant && variant !== \"default\" ? ` variant=${variant}` : \"\";\n const logPrefix = isComposite\n ? `[native-rfc4571 composite profile=${profile}${variantSuffix}${requestedId ? ` id=${requestedId}` : \"\"}]`\n : `[native-rfc4571 ch=${channel} profile=${profile}${variantSuffix}]`;\n const log = (message: string) => {\n try {\n if (logger?.info) {\n logger.info(`${logPrefix} ${message}`);\n } else if (logger?.log) {\n logger.log(`${logPrefix} ${message}`);\n }\n } catch {\n // Ignore logging errors if logger is not properly initialized\n }\n };\n\n const logSessionsSummary = (action: string) => {\n const summary = baseApi.getDedicatedSessionsSummary();\n log(\n `${action} [sessions: ${summary.count} active${summary.count > 0 ? ` (${summary.keys.join(\", \")})` : \"\"}]`,\n );\n };\n\n log(\n `starting (host=${host} videoPT=${videoPayloadType} audioPT=${audioPayloadType} expectedVideoType=${expectedVideoType ?? \"n/a\"} keyframeTimeoutMs=${keyframeTimeoutMs} uptimeRestartMs=${uptimeRestartMs} idleTeardownMs=${idleTeardownMs} composite=${isComposite})`,\n );\n\n let videoStream: BaichuanVideoStream | CompositeStream;\n let isCompositeStream = false;\n\n if (isComposite) {\n // Use composite stream for multifocal cameras\n const widerChannel = compositeOptions?.widerChannel ?? 0;\n const teleChannel = compositeOptions?.teleChannel ?? 1;\n const requested = parseCompositeFromRequestedId(requestedId);\n // `options.profile` is the *output* profile requested by the caller.\n // `requestedId` encodes *input* selection (widerProfile + teleProfile).\n const outputProfile: StreamProfile = profile;\n let teleInputProfile: StreamProfile =\n requested?.teleProfile ?? outputProfile;\n\n // Default wider selection:\n // - If explicitly requested via id: obey.\n // - Otherwise: for tele=main, prefer wider=main only when it's H.264.\n // - Otherwise: wider=sub (keep ext unchanged).\n let widerMainIsH264 = false;\n try {\n const widerApiForProbe = resolvedCompositeApis?.widerApi ?? baseApi;\n const metadata: any =\n await widerApiForProbe.getStreamMetadata(widerChannel);\n const streams: any[] = Array.isArray(metadata)\n ? metadata\n : Array.isArray(metadata?.streams)\n ? metadata.streams\n : [];\n const main = streams.find((s: any) => s?.profile === \"main\");\n const enc =\n typeof main?.videoEncType === \"string\"\n ? main.videoEncType.toLowerCase()\n : \"\";\n widerMainIsH264 = enc.includes(\"264\");\n } catch {\n // ignore\n }\n\n let widerInputProfile: StreamProfile =\n requested?.widerProfile ??\n (outputProfile === \"ext\"\n ? \"ext\"\n : outputProfile === \"main\" && widerMainIsH264\n ? \"main\"\n : \"sub\");\n\n if (requested?.teleProfile && requested.teleProfile !== outputProfile) {\n log(\n `requestedId overrides tele INPUT profile (requested=${requested.teleProfile} output=${outputProfile})`,\n );\n }\n log(\n `creating composite stream: outputProfile=${outputProfile} ` +\n `wider(ch=${widerChannel}, profile=${widerInputProfile}), tele(ch=${teleChannel}, profile=${teleInputProfile}) ` +\n `source=${requested?.source ?? \"native\"} widerMainIsH264=${widerMainIsH264}`,\n );\n\n const widerApi = resolvedCompositeApis?.widerApi ?? baseApi;\n const teleApi = resolvedCompositeApis?.teleApi ?? baseApi;\n\n // Default behavior: keep `main` untouched (may be H.265), but force H.264 inputs on `sub`.\n // Callers can still override explicitly via compositeOptions.forceH264.\n const forceH264 = compositeOptions?.forceH264;\n const defaultForceH264 = outputProfile === \"sub\";\n\n // Optional: RTSP pair inputs (requested via id: composite-rtsp-...)\n let widerRtspUrl: string | undefined;\n let teleRtspUrl: string | undefined;\n if (requested?.source === \"rtsp\") {\n const onNvr = Boolean(compositeOptions?.onNvr);\n\n const resolveLensRtspUrl = async (params: {\n channel: number;\n lens: NativeVideoStreamVariant;\n desiredLens: \"wide\" | \"telephoto\";\n profile: StreamProfile;\n }): Promise<{ urlWithAuth: string; actualProfile: StreamProfile }> => {\n const { rtspStreams } = await baseApi.buildVideoStreamOptions({\n channel: params.channel,\n onNvr,\n compositeOnly: false,\n lens: params.lens,\n });\n\n const candidates = rtspStreams.filter(\n (s) =>\n s.container === \"rtsp\" &&\n s.lens === params.desiredLens &&\n Boolean(s.urlWithAuth),\n );\n\n const exact = candidates.find((s) => s.profile === params.profile);\n if (exact?.urlWithAuth) {\n return {\n urlWithAuth: exact.urlWithAuth,\n actualProfile: exact.profile,\n };\n }\n\n // Some NVR/Hub firmwares expose tele RTSP only as `main` (e.g. Preview_XX_autotrack).\n // If `sub` is requested but missing, fall back to `main`.\n if (params.profile === \"sub\") {\n const fallbackMain = candidates.find((s) => s.profile === \"main\");\n if (fallbackMain?.urlWithAuth) {\n return {\n urlWithAuth: fallbackMain.urlWithAuth,\n actualProfile: fallbackMain.profile,\n };\n }\n }\n\n const available = rtspStreams\n .filter(\n (s) => s.container === \"rtsp\" && s.lens === params.desiredLens,\n )\n .map((s) => `${s.profile}:${s.id}`)\n .join(\", \");\n\n throw new Error(\n `Requested composite RTSP inputs, but no RTSP ${params.desiredLens} stream found for channel=${params.channel} profile=${params.profile} (onNvr=${onNvr}). ` +\n `Available: [${available || \"none\"}]. ` +\n `Use composite-native-... or choose a different profile.`,\n );\n };\n\n // Wider lens always uses the default lens variant.\n const widerResolved = await resolveLensRtspUrl({\n channel: widerChannel,\n lens: \"default\",\n desiredLens: \"wide\",\n profile: widerInputProfile,\n });\n\n widerRtspUrl = widerResolved.urlWithAuth;\n widerInputProfile = widerResolved.actualProfile;\n\n // Tele lens can be on a distinct channel (standalone) or share the same channel (NVR/Hub).\n const teleResolved = await resolveLensRtspUrl({\n channel: teleChannel,\n lens: \"telephoto\",\n desiredLens: \"telephoto\",\n profile: teleInputProfile,\n });\n\n teleRtspUrl = teleResolved.urlWithAuth;\n if (teleResolved.actualProfile !== teleInputProfile) {\n log(\n `tele RTSP profile fallback applied (requested=${teleInputProfile} actual=${teleResolved.actualProfile})`,\n );\n }\n teleInputProfile = teleResolved.actualProfile;\n }\n\n videoStream = new CompositeStream({\n api: baseApi,\n widerApi,\n teleApi,\n widerChannel,\n teleChannel,\n widerProfile: widerInputProfile,\n teleProfile: teleInputProfile,\n ...(widerRtspUrl && teleRtspUrl ? { widerRtspUrl, teleRtspUrl } : {}),\n ...(compositeOptions?.rtspTransport\n ? { rtspTransport: compositeOptions.rtspTransport }\n : {}),\n pipPosition: compositeOptions?.pipPosition ?? \"bottom-right\",\n pipSize: compositeOptions?.pipSize ?? 0.25,\n // New default is percent-friendly (1%). Values > 1 are still treated as pixels.\n pipMargin: compositeOptions?.pipMargin ?? 0.01,\n ...(compositeOptions?.onNvr !== undefined\n ? { onNvr: compositeOptions.onNvr }\n : {}),\n ...(forceH264 !== undefined\n ? { forceH264 }\n : defaultForceH264\n ? { forceH264: true }\n : {}),\n ...(compositeOptions?.assumeH264Inputs !== undefined\n ? { assumeH264Inputs: compositeOptions.assumeH264Inputs }\n : {}),\n ...(compositeOptions?.disableTranscode !== undefined\n ? { disableTranscode: compositeOptions.disableTranscode }\n : {}),\n logger,\n });\n\n isCompositeStream = true;\n await videoStream.start();\n log(\n \"composite stream started; waiting for keyframe to extract parameter sets\",\n );\n } else {\n // Use regular BaichuanVideoStream\n const ch = channel!;\n\n // If deviceId is provided, create a dedicated socket session for this stream.\n // This allows multiple concurrent streams without interference.\n // BaichuanVideoStream now passes client to startVideoStream/stopVideoStream,\n // so commands go to the correct socket.\n const deviceId = options.deviceId;\n let streamClient: BaichuanClient;\n\n if (deviceId) {\n const sessionKey = `live:${deviceId}:ch${ch}:${profile}${variant && variant !== \"default\" ? `:${variant}` : \"\"}`;\n dedicatedSession = await baseApi.createDedicatedSession(\n sessionKey,\n logger,\n );\n streamClient = dedicatedSession.client;\n logSessionsSummary(`dedicated session created: ${sessionKey}`);\n } else {\n streamClient = baseApi.client;\n }\n\n videoStream = new BaichuanVideoStream({\n client: streamClient,\n api: baseApi,\n channel: ch,\n profile,\n variant,\n logger,\n });\n\n await videoStream.start();\n log(\n `stream started (ch=${ch} profile=${profile}${deviceId ? ` dedicated=${deviceId}` : \"\"})`,\n );\n }\n\n const waitForKeyframe = async (): Promise<\n { videoType: VideoType; accessUnit: Buffer } & {\n profileLevelId?: string;\n h264?: { sps: Buffer; pps: Buffer };\n h265?: { vps: Buffer; sps: Buffer; pps: Buffer };\n }\n > => {\n if (isCompositeStream) {\n // For composite stream, wait for first video frame and extract parameter sets\n return await new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n cleanup();\n reject(\n new Error(\n `Timeout waiting for keyframe on composite stream profile=${profile}`,\n ),\n );\n }, keyframeTimeoutMs);\n\n const onError = (e: unknown) => {\n cleanup();\n reject(e instanceof Error ? e : new Error(String(e)));\n };\n\n const onFrame = (frame: Buffer) => {\n // Composite stream outputs H.264 frames from ffmpeg\n // Extract parameter sets from the first frame\n const videoType: VideoType = \"H264\"; // Composite stream always outputs H.264\n\n try {\n const { sps, pps, profileLevelId } =\n extractH264ParamSetsFromAccessUnit(frame);\n if (!sps || !pps) {\n // Not a keyframe yet, wait for next\n return;\n }\n cleanup();\n resolve({\n videoType,\n accessUnit: frame,\n ...(profileLevelId ? { profileLevelId } : {}),\n h264: { sps, pps },\n });\n } catch (e) {\n // If extraction fails, wait for next frame\n return;\n }\n };\n\n const onClose = () => {\n cleanup();\n reject(\n new Error(\n `Composite stream closed before keyframe (profile=${profile})`,\n ),\n );\n };\n\n const cleanup = () => {\n clearTimeout(timeout);\n (videoStream as CompositeStream).removeListener(\n \"error\" as any,\n onError as any,\n );\n (videoStream as CompositeStream).removeListener(\n \"videoFrame\" as any,\n onFrame as any,\n );\n (videoStream as CompositeStream).removeListener(\n \"close\" as any,\n onClose as any,\n );\n };\n\n (videoStream as CompositeStream).on(\"error\" as any, onError as any);\n (videoStream as CompositeStream).on(\n \"videoFrame\" as any,\n onFrame as any,\n );\n (videoStream as CompositeStream).on(\"close\" as any, onClose as any);\n });\n } else {\n // For regular BaichuanVideoStream\n return await new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n cleanup();\n reject(\n new Error(\n `Timeout waiting for keyframe on native stream channel=${channel} profile=${profile}`,\n ),\n );\n }, keyframeTimeoutMs);\n\n const onError = (e: unknown) => {\n cleanup();\n reject(e instanceof Error ? e : new Error(String(e)));\n };\n\n const onAu = (au: any) => {\n if (!au?.isKeyframe) return;\n const videoType = au.videoType as VideoType;\n const accessUnit = au.data as Buffer;\n\n if (videoType === \"H264\") {\n const { sps, pps, profileLevelId } =\n extractH264ParamSetsFromAccessUnit(accessUnit);\n if (!sps || !pps) return;\n cleanup();\n resolve({\n videoType,\n accessUnit,\n ...(profileLevelId ? { profileLevelId } : {}),\n h264: { sps, pps },\n });\n return;\n }\n\n const { vps, sps, pps } =\n extractH265ParamSetsFromAccessUnit(accessUnit);\n if (!vps || !sps || !pps) return;\n cleanup();\n resolve({ videoType, accessUnit, h265: { vps, sps, pps } });\n };\n\n const cleanup = () => {\n clearTimeout(timeout);\n (videoStream as BaichuanVideoStream).removeListener(\n \"error\" as any,\n onError as any,\n );\n (videoStream as BaichuanVideoStream).removeListener(\n \"videoAccessUnit\" as any,\n onAu as any,\n );\n };\n\n (videoStream as BaichuanVideoStream).on(\"error\" as any, onError as any);\n (videoStream as BaichuanVideoStream).on(\n \"videoAccessUnit\" as any,\n onAu as any,\n );\n });\n }\n };\n\n let keyframe: Awaited<ReturnType<typeof waitForKeyframe>>;\n try {\n keyframe = await waitForKeyframe();\n } catch (e) {\n // IMPORTANT: if we fail before returning a server handle, no teardown will run.\n // Stop the native/composite stream pipeline here to avoid leaving watchdogs running.\n try {\n await videoStream.stop();\n } catch {\n // ignore\n }\n\n if (closeApiOnTeardown) {\n await Promise.allSettled(\n Array.from(apisToClose).map(async (a) => {\n try {\n await a.close();\n } catch {\n // ignore\n }\n }),\n );\n } else {\n // For shared connections (common on battery/BCUDP), we cannot force-close the API here.\n // Instead, ask the underlying client to drop the UDP session soon *if* it is truly idle.\n // This reduces the chance of keeping the camera awake while remaining safe.\n const graceMs = isComposite ? 5_000 : 0;\n for (const a of Array.from(apisToClose)) {\n try {\n (a as any)?.client?.requestIdleDisconnectSoon?.(\n \"rfc4571_teardown\",\n graceMs,\n );\n } catch {\n // ignore\n }\n }\n }\n\n throw e;\n }\n if (expectedVideoType && keyframe.videoType !== expectedVideoType) {\n log(\n `expectedVideoType mismatch (expected=${expectedVideoType} actual=${keyframe.videoType})`,\n );\n }\n\n // Best-effort framerate for raw elementary-stream input.\n let fps = 25;\n try {\n if (isComposite) {\n // For composite stream, get framerate from wider stream\n const widerChannel = compositeOptions?.widerChannel ?? 0;\n const widerApi = resolvedCompositeApis?.widerApi ?? baseApi;\n const metadata: any = await widerApi.getStreamMetadata(widerChannel);\n const streams: any[] = Array.isArray(metadata)\n ? metadata\n : Array.isArray(metadata?.streams)\n ? metadata.streams\n : [];\n // Note: composite FPS should be keyed off the wider input profile, not the tele profile.\n const requested = parseCompositeFromRequestedId(requestedId);\n const effectiveTeleProfile: StreamProfile =\n requested?.teleProfile ?? profile;\n let widerMainIsH264 = false;\n try {\n const main = streams.find((s: any) => s?.profile === \"main\");\n const enc =\n typeof main?.videoEncType === \"string\"\n ? main.videoEncType.toLowerCase()\n : \"\";\n widerMainIsH264 = enc.includes(\"264\");\n } catch {\n // ignore\n }\n const widerProfile: StreamProfile =\n requested?.widerProfile ??\n (effectiveTeleProfile === \"ext\"\n ? \"ext\"\n : effectiveTeleProfile === \"main\" && widerMainIsH264\n ? \"main\"\n : \"sub\");\n const stream = streams.find((s: any) => s?.profile === widerProfile);\n const fr = Number(stream?.frameRate);\n if (Number.isFinite(fr) && fr > 0) fps = fr;\n } else {\n const metadata: any = await baseApi.getStreamMetadata(channel!);\n const streams: any[] = Array.isArray(metadata)\n ? metadata\n : Array.isArray(metadata?.streams)\n ? metadata.streams\n : [];\n const stream = streams.find((s: any) => s?.profile === profile);\n const fr = Number(stream?.frameRate);\n if (Number.isFinite(fr) && fr > 0) fps = fr;\n }\n } catch {\n // ignore\n }\n\n // Prime audio: prefer ADTS (self-describing), but support raw AAC by using a hint/default config.\n // Note: CompositeStream may forward native audio frames (typically from wider input).\n let audio:\n | {\n sampleRate: number;\n channels: number;\n configHex: string;\n mode: \"adts\" | \"raw\";\n }\n | undefined;\n const tryPrimeAudio = async (): Promise<typeof audio> => {\n return await new Promise((resolve) => {\n let sawAnyAudio = false;\n let debugLogsLeft = 3;\n const audioPrimeTimeoutMs = isCompositeStream\n ? Math.min(10_000, keyframeTimeoutMs)\n : 5000;\n const timeout = setTimeout(() => {\n cleanup();\n if (!sawAnyAudio) {\n resolve(undefined);\n return;\n }\n\n const hint = aacAudioHint ?? { sampleRate: 8000, channels: 1 };\n const configHex = buildAacAudioSpecificConfigHex({\n sampleRate: hint.sampleRate,\n channels: hint.channels,\n });\n if (!configHex) {\n logger.warn(\n `Native audio frames seen but could not derive AAC config (hint sampleRate=${hint.sampleRate} channels=${hint.channels}); cannot advertise audio track.`,\n );\n resolve(undefined);\n return;\n }\n\n logger.warn(\n `Native audio frames appear to be raw AAC (no ADTS); advertising AAC using hint sampleRate=${hint.sampleRate} channels=${hint.channels}.`,\n );\n resolve({\n sampleRate: hint.sampleRate,\n channels: hint.channels,\n configHex,\n mode: \"raw\",\n });\n }, audioPrimeTimeoutMs);\n\n const onAudio = (frame: Buffer) => {\n sawAnyAudio = true;\n const parsed = parseAdtsHeader(frame);\n if (!parsed) {\n if (debugLogsLeft-- > 0) {\n const head = frame\n .subarray(0, Math.min(16, frame.length))\n .toString(\"hex\");\n logger.warn(\n `Native audioFrame not ADTS: len=${frame.length} head=${head}`,\n );\n }\n return;\n }\n cleanup();\n resolve({\n sampleRate: parsed.sampleRate,\n channels: parsed.channels,\n configHex: parsed.configHex,\n mode: \"adts\",\n });\n };\n\n const cleanup = () => {\n clearTimeout(timeout);\n (videoStream as any)?.removeListener?.(\n \"audioFrame\" as any,\n onAudio as any,\n );\n };\n\n (videoStream as any)?.on?.(\"audioFrame\" as any, onAudio as any);\n });\n };\n\n audio = await tryPrimeAudio();\n\n const video: VideoParamSets = {\n videoType: keyframe.videoType,\n payloadType: videoPayloadType,\n ...(keyframe.videoType === \"H264\"\n ? {\n h264: {\n sps: keyframe.h264!.sps,\n pps: keyframe.h264!.pps,\n ...(keyframe.profileLevelId\n ? { profileLevelId: keyframe.profileLevelId }\n : {}),\n },\n }\n : {\n h265: {\n vps: keyframe.h265!.vps,\n sps: keyframe.h265!.sps,\n pps: keyframe.h265!.pps,\n },\n }),\n };\n\n const aacAudio: AudioConfig | undefined = audio\n ? {\n codec: \"aac\",\n payloadType: audioPayloadType,\n sampleRate: audio.sampleRate,\n channels: audio.channels,\n configHex: audio.configHex,\n }\n : undefined;\n\n const sdp = buildRfc4571Sdp(video, aacAudio);\n const makeMuxer = () =>\n new Rfc4571Muxer(\n logger,\n videoPayloadType,\n aacAudio ? audioPayloadType : undefined,\n fps,\n );\n let muxer = makeMuxer();\n\n log(\n `ready (video=${keyframe.videoType} fps=${fps}${aacAudio ? ` audio=aac/${aacAudio.sampleRate}/${aacAudio.channels}` : \" audio=none\"})`,\n );\n\n let rfcClients = 0;\n const sockets = new Set<net.Socket>();\n let idleTeardownTimer: NodeJS.Timeout | undefined;\n let tearingDown = false;\n let restarting = false;\n\n // Uptime watchdog: touch this on any observed activity (RX/TX).\n let lastActivityMs = Date.now();\n const touchActivity = () => {\n lastActivityMs = Date.now();\n };\n\n const cancelIdleTeardown = () => {\n if (!idleTeardownTimer) return;\n clearTimeout(idleTeardownTimer);\n idleTeardownTimer = undefined;\n };\n\n let uptimeTimer: NodeJS.Timeout | undefined;\n const stopUptimeMonitor = () => {\n if (!uptimeTimer) return;\n clearInterval(uptimeTimer);\n uptimeTimer = undefined;\n };\n const startUptimeMonitor = () => {\n if (!uptimeRestartMs || uptimeRestartMs <= 0) return;\n if (uptimeTimer) return;\n const tickMs = Math.max(\n 250,\n Math.min(1000, Math.floor(uptimeRestartMs / 2)),\n );\n uptimeTimer = setInterval(() => {\n if (tearingDown || restarting) return;\n const idleFor = Date.now() - lastActivityMs;\n if (idleFor < uptimeRestartMs) return;\n restart(\n new Error(\n `No stream activity for ${idleFor}ms (threshold=${uptimeRestartMs}ms)`,\n ),\n ).catch(() => {});\n }, tickMs);\n };\n\n const scheduleIdleTeardown = (\n closeFn: (reason?: unknown) => Promise<void>,\n ) => {\n if (!idleTeardownMs) return;\n if (idleTeardownTimer) return;\n idleTeardownTimer = setTimeout(() => {\n idleTeardownTimer = undefined;\n if (rfcClients === 0)\n closeFn(new Error(\"No RFC4571 clients (idle)\")).catch(() => {});\n }, idleTeardownMs);\n };\n\n const server = netImpl.createServer();\n\n const restart = async (reason?: unknown): Promise<void> => {\n if (tearingDown) return;\n if (restarting) return;\n restarting = true;\n touchActivity();\n\n cancelIdleTeardown();\n\n const message =\n (reason as any)?.message || (reason as any)?.toString?.() || reason;\n const address = server.address();\n const addrStr =\n address && typeof address !== \"string\"\n ? `${address.address}:${address.port}`\n : \"unbound\";\n if (message)\n log(\n `uptime watchdog: restarting (addr=${addrStr} clients=${rfcClients} reason=${message})`,\n );\n else\n log(\n `uptime watchdog: restarting (addr=${addrStr} clients=${rfcClients})`,\n );\n\n // Drop clients first: force reconnect and clear muxer state.\n for (const s of Array.from(sockets)) {\n try {\n s.destroy();\n } catch {\n // ignore\n }\n }\n sockets.clear();\n\n try {\n muxer.close();\n } catch {\n // ignore\n }\n muxer = makeMuxer();\n\n // Restart the native stream pipeline (best-effort).\n try {\n await videoStream.stop();\n } catch {\n // ignore\n }\n try {\n await videoStream.start();\n } catch (e) {\n // If restart fails, escalate to teardown so callers don't hang forever.\n restarting = false;\n close(e).catch(() => {});\n return;\n }\n\n // For composite stream, we need to wait for a new keyframe\n // For BaichuanVideoStream, it will emit videoAccessUnit events automatically\n if (isCompositeStream) {\n // Composite stream will emit videoFrame events automatically after restart\n // No need to wait for keyframe here as we'll get frames from ffmpeg\n }\n\n restarting = false;\n touchActivity();\n log(\"uptime watchdog: restart complete\");\n\n // If no clients are connected after restart, keep existing idle teardown behavior.\n if (rfcClients === 0) scheduleIdleTeardown(close);\n };\n\n const close = async (reason?: unknown): Promise<void> => {\n if (tearingDown) return;\n tearingDown = true;\n\n stopUptimeMonitor();\n cancelIdleTeardown();\n const reasonStr =\n (reason as any)?.message ||\n (reason as any)?.toString?.() ||\n reason ||\n \"requested\";\n\n muxer.close();\n\n try {\n await videoStream.stop();\n } catch {\n // ignore\n }\n\n // Release dedicated session if one was created\n if (dedicatedSession) {\n try {\n await dedicatedSession.release();\n logSessionsSummary(\"dedicated session released\");\n } catch {\n // ignore\n }\n }\n\n if (closeApiOnTeardown) {\n await Promise.allSettled(\n Array.from(apisToClose).map(async (a) => {\n try {\n await a.close();\n } catch {\n // ignore\n }\n }),\n );\n }\n\n try {\n server.close();\n } catch {\n // ignore\n }\n\n log(`teardown (${reasonStr})`);\n };\n\n server.on(\"connection\", (socket) => {\n touchActivity();\n const remote = `${socket.remoteAddress ?? \"unknown\"}:${socket.remotePort ?? \"unknown\"}`;\n\n const setupClient = () => {\n rfcClients++;\n cancelIdleTeardown();\n sockets.add(socket);\n\n // Track RX from client (RTCP, keepalives, etc).\n socket.on(\"data\", () => touchActivity());\n\n // Track TX to client by wrapping socket.write (used by muxer).\n try {\n const origWrite = socket.write.bind(socket) as any;\n (socket as any).write = (...args: any[]) => {\n touchActivity();\n return origWrite(...args);\n };\n } catch {\n // ignore\n }\n\n muxer.addClient(socket);\n log(`client connected (${remote} total=${rfcClients})`);\n };\n\n if (!requireAuth) {\n // No authentication required, setup client immediately\n setupClient();\n } else {\n // Authentication required: expect \"username:password\\n\" as first message\n let authenticated = false;\n let authBuffer = Buffer.alloc(0);\n const authTimeout = setTimeout(() => {\n if (!authenticated) {\n log(`client authentication timeout (remote=${remote})`);\n socket.destroy();\n }\n }, 5000); // 5 second timeout\n\n const onData = (data: Buffer) => {\n touchActivity();\n\n if (!authenticated) {\n authBuffer = Buffer.concat([authBuffer, data]);\n const authString = authBuffer.toString(\"utf8\");\n const authMatch = authString.match(/^([^:]+):([^\\n]+)\\n/);\n\n if (authMatch) {\n const [, clientUsername, clientPassword] = authMatch;\n if (clientUsername === username && clientPassword === password) {\n authenticated = true;\n clearTimeout(authTimeout);\n setupClient();\n\n // Remove auth data from buffer and process remaining data\n const authLineLength = authMatch[0].length;\n const remainingData = authBuffer.subarray(authLineLength);\n\n // Replace data handler\n socket.removeListener(\"data\", onData);\n socket.on(\"data\", () => touchActivity());\n\n // Process remaining data if any\n if (remainingData.length > 0) {\n socket.emit(\"data\", remainingData);\n }\n } else {\n log(`client authentication failed (remote=${remote})`);\n socket.destroy();\n return;\n }\n } else if (authBuffer.length > 1024) {\n // Prevent buffer overflow\n log(`client authentication buffer overflow (remote=${remote})`);\n socket.destroy();\n return;\n }\n }\n };\n\n socket.on(\"data\", onData);\n }\n\n let counted = true;\n const dec = () => {\n if (!counted) return;\n counted = false;\n rfcClients = Math.max(0, rfcClients - 1);\n sockets.delete(socket);\n log(`client disconnected (${remote} total=${rfcClients})`);\n if (rfcClients === 0) scheduleIdleTeardown(close);\n };\n\n socket.once(\"close\", dec);\n socket.once(\"error\", dec);\n });\n\n // Attach stream forwarding.\n if (isCompositeStream) {\n // Composite stream emits videoFrame (Buffer) - H.264 frames from ffmpeg in Annex-B format\n (videoStream as CompositeStream).on(\n \"videoFrame\" as any,\n (frame: Buffer) => {\n touchActivity();\n try {\n // Composite stream always outputs H.264\n // Detect if it's a keyframe by checking for IDR NAL unit (type 5)\n let isKeyframe = false;\n try {\n // Check for start codes and IDR NAL units\n for (let i = 0; i < frame.length - 4; i++) {\n if (frame[i] === 0x00 && frame[i + 1] === 0x00) {\n let nalStart = -1;\n if (frame[i + 2] === 0x01) {\n nalStart = i + 3;\n } else if (frame[i + 2] === 0x00 && frame[i + 3] === 0x01) {\n nalStart = i + 4;\n }\n\n if (nalStart >= 0 && nalStart < frame.length) {\n const nalType = (frame[nalStart] ?? 0) & 0x1f;\n if (nalType === 5) {\n // IDR NAL unit - this is a keyframe\n isKeyframe = true;\n break;\n }\n }\n }\n }\n } catch {\n // If detection fails, assume it's not a keyframe\n }\n muxer.sendVideoAccessUnit(\"H264\", frame, isKeyframe, undefined);\n } catch (e) {\n close(e).catch(() => {});\n }\n },\n );\n\n if (aacAudio) {\n (videoStream as CompositeStream).on(\n \"audioFrame\" as any,\n (frame: Buffer) => {\n touchActivity();\n try {\n if (audio?.mode === \"adts\") {\n muxer.sendAudioAdtsFrame(frame);\n } else {\n muxer.sendAudioAacRawFrame(frame);\n }\n } catch (e) {\n close(e).catch(() => {});\n }\n },\n );\n }\n } else {\n // BaichuanVideoStream emits videoAccessUnit with metadata\n (videoStream as BaichuanVideoStream).on(\n \"videoAccessUnit\" as any,\n (au: any) => {\n touchActivity();\n try {\n muxer.sendVideoAccessUnit(\n au.videoType,\n au.data,\n au.isKeyframe,\n au.microseconds,\n );\n } catch (e) {\n close(e).catch(() => {});\n }\n },\n );\n\n if (aacAudio) {\n (videoStream as BaichuanVideoStream).on(\n \"audioFrame\" as any,\n (frame: Buffer) => {\n touchActivity();\n try {\n if (audio?.mode === \"adts\") {\n muxer.sendAudioAdtsFrame(frame);\n } else {\n muxer.sendAudioAacRawFrame(frame);\n }\n } catch (e) {\n close(e).catch(() => {});\n }\n },\n );\n }\n }\n\n videoStream.on(\"error\" as any, (e: unknown) => {\n if (restarting) return;\n close(e).catch(() => {});\n });\n videoStream.on(\"close\" as any, (e: unknown) => {\n if (restarting) return;\n close(e).catch(() => {});\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, host, () => resolve());\n });\n\n const address = server.address();\n if (!address || typeof address === \"string\") {\n throw new Error(\"Failed to bind RFC TCP server\");\n }\n const port = address.port;\n if (!port) throw new Error(\"Failed to bind RFC TCP server\");\n\n log(`listening (addr=${host}:${port})`);\n\n const audioInfo = aacAudio\n ? {\n codec: \"aac\" as const,\n sampleRate: aacAudio.sampleRate,\n channels: aacAudio.channels,\n }\n : undefined;\n\n // If created with no clients, schedule idle teardown.\n scheduleIdleTeardown(close);\n startUptimeMonitor();\n\n return {\n host,\n port,\n sdp,\n videoType: keyframe.videoType,\n ...(audioInfo ? { audio: audioInfo } : {}),\n username,\n password,\n server,\n videoStream,\n close,\n };\n}\n\n// =============================================================================\n// Recording Replay Server\n// =============================================================================\n\nexport interface Rfc4571ReplayServerOptions {\n /** Baichuan API session to use for replay. */\n api: ReolinkBaichuanApi;\n /** Channel number (defaults to 0 for standalone cameras). */\n channel?: number;\n /** Recording file name (e.g., \"RecM03_20250101_120000_123.264\"). */\n fileName: string;\n /** Stream type: mainStream or subStream (inferred from fileName if not specified). */\n streamType?: \"mainStream\" | \"subStream\";\n logger: Console;\n\n host?: string;\n videoPayloadType?: number;\n audioPayloadType?: number;\n\n /** How long to wait for an IDR/IRAP to extract parameter sets and produce SDP. */\n keyframeTimeoutMs?: number;\n\n /**\n * External identifier for dedicated socket session.\n * When provided, a dedicated BaichuanClient is created for this deviceId.\n * This allows multiple concurrent replay streams without interference.\n * The dedicated socket is automatically closed when the replay ends.\n */\n deviceId?: string;\n\n /** If true (default), closes the API when replay ends or server closes. */\n closeApiOnTeardown?: boolean;\n username: string;\n password: string;\n /** If true, requires authentication before allowing stream access. Default: false. */\n requireAuth?: boolean;\n\n /**\n * Optional AAC hint for when the camera sends raw AAC (no ADTS headers).\n * Many Reolink devices use AAC-LC mono at 8000Hz.\n */\n aacAudioHint?: { sampleRate: number; channels: number };\n\n /** Force NVR mode (uses id-based XML with UID) or standalone mode (name-based XML). */\n isNvr?: boolean;\n}\n\nexport interface Rfc4571ReplayServer {\n host: string;\n port: number;\n sdp: string;\n videoType: VideoType;\n audio?: { codec: \"aac\"; sampleRate: number; channels: number };\n username: string;\n password: string;\n\n server: net.Server;\n videoStream: BaichuanVideoStream;\n\n /** Called when replay naturally ends (all data sent). */\n onReplayEnd?: () => void;\n\n close: (reason?: unknown) => Promise<void>;\n}\n\n/**\n * Create an RFC4571 TCP server for streaming a recording replay.\n *\n * This is similar to createRfc4571TcpServer but optimized for playback of recorded files:\n * - Uses startRecordingReplayStream to get native frames\n * - Automatically stops when replay ends\n * - No restart logic (single playback, not continuous like live)\n */\nexport async function createRfc4571TcpServerForReplay(\n options: Rfc4571ReplayServerOptions,\n): Promise<Rfc4571ReplayServer> {\n const {\n api,\n channel = 0,\n fileName,\n logger,\n host = \"127.0.0.1\",\n videoPayloadType = 96,\n audioPayloadType = 97,\n keyframeTimeoutMs = 15_000,\n closeApiOnTeardown = false,\n username,\n password,\n requireAuth = false,\n aacAudioHint,\n isNvr,\n } = options;\n\n // Infer streamType from fileName if not provided\n const streamType =\n options.streamType ??\n (fileName.includes(\"RecS03_\") ? \"subStream\" : \"mainStream\");\n\n const deviceId = options.deviceId;\n\n const log = (msg: string, ...args: unknown[]) =>\n logger.log(\n `[RFC4571-Replay ch=${channel} file=${fileName}] ${msg}`,\n ...args,\n );\n const warn = (msg: string, ...args: unknown[]) =>\n logger.warn(\n `[RFC4571-Replay ch=${channel} file=${fileName}] ${msg}`,\n ...args,\n );\n\n log(\n `starting replay: streamType=${streamType}${deviceId ? ` deviceId=${deviceId}` : \"\"}`,\n );\n\n // Start the recording replay stream\n const replayParams: {\n channel: number;\n fileName: string;\n streamType: \"mainStream\" | \"subStream\";\n timeoutMs: number;\n logger: Console;\n isNvr?: boolean;\n deviceId?: string;\n } = {\n channel,\n fileName,\n streamType,\n timeoutMs: keyframeTimeoutMs,\n logger,\n };\n if (isNvr !== undefined) {\n replayParams.isNvr = isNvr;\n }\n if (deviceId !== undefined) {\n replayParams.deviceId = deviceId;\n }\n\n const { stream: videoStream, stop: stopReplay } =\n await api.startRecordingReplayStream(replayParams);\n\n // Audio detection\n let audio:\n | {\n sampleRate: number;\n channels: number;\n configHex: string;\n mode: \"adts\" | \"raw\";\n }\n | undefined;\n\n const waitForKeyframe = async (): Promise<\n { videoType: VideoType; accessUnit: Buffer } & {\n profileLevelId?: string;\n h264?: { sps: Buffer; pps: Buffer };\n h265?: { vps: Buffer; sps: Buffer; pps: Buffer };\n }\n > => {\n return await new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n cleanup();\n reject(\n new Error(\n `Timeout waiting for keyframe in replay (file=${fileName})`,\n ),\n );\n }, keyframeTimeoutMs);\n\n const onError = (e: unknown) => {\n cleanup();\n reject(e instanceof Error ? e : new Error(String(e)));\n };\n\n const onAu = (au: any) => {\n if (!au?.isKeyframe) return;\n const videoType = au.videoType as VideoType;\n const accessUnit = au.data as Buffer;\n\n if (videoType === \"H264\") {\n const { sps, pps, profileLevelId } =\n extractH264ParamSetsFromAccessUnit(accessUnit);\n if (!sps || !pps) return;\n cleanup();\n resolve({\n videoType,\n accessUnit,\n ...(profileLevelId ? { profileLevelId } : {}),\n h264: { sps, pps },\n });\n return;\n }\n\n const { vps, sps, pps } =\n extractH265ParamSetsFromAccessUnit(accessUnit);\n if (!vps || !sps || !pps) return;\n cleanup();\n resolve({ videoType, accessUnit, h265: { vps, sps, pps } });\n };\n\n // Listen for audio to detect format\n const onAudioFrame = (frame: Buffer) => {\n if (audio) return;\n // Try parsing as ADTS\n try {\n const adts = parseAdtsHeader(frame);\n if (adts) {\n audio = {\n sampleRate: adts.sampleRate,\n channels: adts.channels,\n configHex: adts.configHex,\n mode: \"adts\",\n };\n log(\n `detected audio via ADTS: sr=${audio.sampleRate} ch=${audio.channels}`,\n );\n }\n } catch {\n // Not ADTS, try raw AAC hint\n if (aacAudioHint) {\n const configHex = buildAacAudioSpecificConfigHex({\n sampleRate: aacAudioHint.sampleRate,\n channels: aacAudioHint.channels,\n });\n if (configHex) {\n audio = {\n sampleRate: aacAudioHint.sampleRate,\n channels: aacAudioHint.channels,\n configHex,\n mode: \"raw\",\n };\n log(\n `using AAC hint: sr=${audio.sampleRate} ch=${audio.channels}`,\n );\n }\n }\n }\n };\n\n const cleanup = () => {\n clearTimeout(timeout);\n videoStream.removeListener(\"error\" as any, onError as any);\n videoStream.removeListener(\"videoAccessUnit\" as any, onAu as any);\n videoStream.removeListener(\"audioFrame\" as any, onAudioFrame as any);\n };\n\n videoStream.on(\"error\" as any, onError as any);\n videoStream.on(\"videoAccessUnit\" as any, onAu as any);\n videoStream.on(\"audioFrame\" as any, onAudioFrame as any);\n });\n };\n\n let keyframe: Awaited<ReturnType<typeof waitForKeyframe>>;\n try {\n keyframe = await waitForKeyframe();\n } catch (e) {\n try {\n await stopReplay();\n } catch {\n // ignore\n }\n if (closeApiOnTeardown) {\n try {\n await api.close();\n } catch {\n // ignore\n }\n }\n throw e;\n }\n\n log(`video detected: codec=${keyframe.videoType}`);\n\n const video: VideoParamSets = {\n videoType: keyframe.videoType,\n payloadType: videoPayloadType,\n ...(keyframe.videoType === \"H264\"\n ? {\n h264: {\n sps: keyframe.h264!.sps,\n pps: keyframe.h264!.pps,\n ...(keyframe.profileLevelId\n ? { profileLevelId: keyframe.profileLevelId }\n : {}),\n },\n }\n : {\n h265: {\n vps: keyframe.h265!.vps,\n sps: keyframe.h265!.sps,\n pps: keyframe.h265!.pps,\n },\n }),\n };\n\n const aacAudio: AudioConfig | undefined = audio\n ? {\n codec: \"aac\",\n payloadType: audioPayloadType,\n sampleRate: audio.sampleRate,\n channels: audio.channels,\n configHex: audio.configHex,\n }\n : undefined;\n\n const sdp = buildRfc4571Sdp(video, aacAudio);\n const fps = 25; // Best-effort default\n const muxer = new Rfc4571Muxer(\n logger,\n videoPayloadType,\n aacAudio ? audioPayloadType : undefined,\n fps,\n );\n\n log(\n `SDP ready (video=${keyframe.videoType}/90000 pt=${videoPayloadType}${aacAudio ? `, audio=aac/${aacAudio.sampleRate}/${aacAudio.channels} pt=${audioPayloadType}` : \", audio=none\"})`,\n );\n\n let rfcClients = 0;\n const sockets = new Set<net.Socket>();\n let tearingDown = false;\n let replayEnded = false;\n let onReplayEndCallback: (() => void) | undefined;\n\n const close = async (reason?: unknown): Promise<void> => {\n if (tearingDown) return;\n tearingDown = true;\n\n const message =\n (reason as any)?.message || (reason as any)?.toString?.() || reason;\n if (message) log(`closing: ${message}`);\n else log(\"closing\");\n\n try {\n await stopReplay();\n } catch {\n // ignore\n }\n\n for (const s of sockets) {\n try {\n s.destroy();\n } catch {\n // ignore\n }\n }\n sockets.clear();\n\n try {\n server.close();\n } catch {\n // ignore\n }\n\n if (closeApiOnTeardown) {\n try {\n await api.close();\n } catch {\n // ignore\n }\n }\n };\n\n const server = netImpl.createServer();\n\n server.on(\"connection\", (socket) => {\n const remote = `${socket.remoteAddress}:${socket.remotePort}`;\n log(`client connected: ${remote}`);\n\n sockets.add(socket);\n rfcClients++;\n\n const setupClient = () => {\n muxer.addClient(socket);\n };\n\n if (!requireAuth) {\n // No authentication required, setup client immediately\n setupClient();\n } else {\n // Authentication required: expect \"username:password\\n\" as first message\n let authenticated = false;\n let authBuffer = Buffer.alloc(0);\n const authTimeout = setTimeout(() => {\n if (!authenticated) {\n log(`client authentication timeout (remote=${remote})`);\n socket.destroy();\n }\n }, 5000); // 5 second timeout\n\n const onData = (data: Buffer) => {\n if (!authenticated) {\n authBuffer = Buffer.concat([authBuffer, data]);\n const authString = authBuffer.toString(\"utf8\");\n const authMatch = authString.match(/^([^:]+):([^\\n]+)\\n/);\n\n if (authMatch) {\n const [, clientUsername, clientPassword] = authMatch;\n if (clientUsername === username && clientPassword === password) {\n authenticated = true;\n clearTimeout(authTimeout);\n setupClient();\n socket.removeListener(\"data\", onData);\n } else {\n log(`client authentication failed (remote=${remote})`);\n socket.destroy();\n return;\n }\n } else if (authBuffer.length > 1024) {\n log(`client authentication buffer overflow (remote=${remote})`);\n socket.destroy();\n return;\n }\n }\n };\n\n socket.on(\"data\", onData);\n }\n\n let counted = true;\n const dec = () => {\n if (!counted) return;\n counted = false;\n rfcClients = Math.max(0, rfcClients - 1);\n sockets.delete(socket);\n log(`client disconnected (remote=${remote} clients=${rfcClients})`);\n\n // If no clients and replay ended, close server\n if (rfcClients === 0 && replayEnded) {\n close(\"replay ended and no clients\").catch(() => {});\n }\n };\n\n socket.once(\"close\", dec);\n socket.once(\"error\", (e) => {\n warn(`client socket error: ${remote}`, e?.message || String(e));\n dec();\n });\n });\n\n server.on(\"error\", (e) => {\n warn(\"server error\", e?.message || String(e));\n close(e).catch(() => {});\n });\n\n // Handle video access units\n videoStream.on(\"videoAccessUnit\" as any, (au: any) => {\n if (tearingDown) return;\n try {\n muxer.sendVideoAccessUnit(\n au.videoType,\n au.data,\n au.isKeyframe,\n au.microseconds,\n );\n } catch (e) {\n close(e).catch(() => {});\n }\n });\n\n // Handle audio frames\n if (aacAudio) {\n videoStream.on(\"audioFrame\" as any, (frame: Buffer) => {\n if (tearingDown) return;\n try {\n if (audio?.mode === \"adts\") {\n muxer.sendAudioAdtsFrame(frame);\n } else {\n muxer.sendAudioAacRawFrame(frame);\n }\n } catch (e) {\n close(e).catch(() => {});\n }\n });\n }\n\n // Handle replay end\n videoStream.on(\"end\" as any, () => {\n log(\"replay ended naturally\");\n replayEnded = true;\n onReplayEndCallback?.();\n\n // If no clients, close immediately\n if (rfcClients === 0) {\n close(\"replay ended\").catch(() => {});\n }\n });\n\n videoStream.on(\"error\" as any, (e: unknown) => {\n close(e).catch(() => {});\n });\n\n videoStream.on(\"close\" as any, (e: unknown) => {\n if (!replayEnded) {\n close(e).catch(() => {});\n }\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, host, () => resolve());\n });\n\n const address = server.address();\n if (!address || typeof address === \"string\") {\n throw new Error(\"Failed to bind RFC TCP server for replay\");\n }\n const port = address.port;\n if (!port) throw new Error(\"Failed to bind RFC TCP server for replay\");\n\n log(`listening (addr=${host}:${port})`);\n\n const audioInfo = aacAudio\n ? {\n codec: \"aac\" as const,\n sampleRate: aacAudio.sampleRate,\n channels: aacAudio.channels,\n }\n : undefined;\n\n const result: Rfc4571ReplayServer = {\n host,\n port,\n sdp,\n videoType: keyframe.videoType,\n ...(audioInfo ? { audio: audioInfo } : {}),\n username,\n password,\n server,\n videoStream,\n close,\n };\n\n // Allow setting onReplayEnd callback after creation\n Object.defineProperty(result, \"onReplayEnd\", {\n get: () => onReplayEndCallback,\n set: (fn: (() => void) | undefined) => {\n onReplayEndCallback = fn;\n },\n });\n\n return result;\n}\n","/**\n * Multifocal Camera Composite Stream\n * \n * Combines streams from a multifocal camera (wider and tele) into a new composite stream\n * with configurable picture-in-picture (PIP).\n * \n * Uses ffmpeg to overlay the tele stream on the wider stream in various positions.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { EventEmitter } from \"node:events\";\nimport type { ReolinkBaichuanApi } from \"../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { StreamProfile } from \"../reolink/baichuan/types\";\nimport type { Logger } from \"../debug/DebugConfig\";\nimport { createNativeStream } from \"../rfc/helpers\";\n\nexport type PipPosition = \n | \"top-left\" \n | \"top-right\" \n | \"bottom-left\" \n | \"bottom-right\" \n | \"center\"\n | \"top-center\"\n | \"bottom-center\"\n | \"left-center\"\n | \"right-center\";\n\nexport interface CompositeStreamPipOptions {\n /** Wider channel (default: 0) */\n widerChannel?: number;\n /** Tele channel (default: 1) */\n teleChannel?: number;\n /** PIP position (default: \"bottom-right\") */\n pipPosition?: PipPosition;\n /** PIP size (default: 0.25) */\n pipSize?: number;\n /**\n * PIP margin from edge.\n * - Preferred: fraction of output size (e.g. 0.01 = 1%).\n * - Legacy: values > 1 are treated as pixels.\n */\n pipMargin?: number;\n /** True when behind NVR/Hub (tele is selected via stream variant on the same channel). */\n onNvr?: boolean;\n /**\n * Force using an H.264 input profile when available (e.g. auto-fallback to `sub` if `main` is H.265).\n * Output is always H.264 regardless.\n */\n forceH264?: boolean;\n\n /** Assume both wider+tele inputs are already H.264 and skip codec detection. */\n assumeH264Inputs?: boolean;\n /** Best-effort knob; overlay requires re-encode in ffmpeg, so this cannot fully disable encoding. */\n disableTranscode?: boolean;\n\n /**\n * Optional: provide RTSP input URLs (H.264 only) instead of native Baichuan streams.\n * When set, CompositeStream will read directly from RTSP via ffmpeg.\n */\n widerRtspUrl?: string;\n teleRtspUrl?: string;\n /** ffmpeg `-rtsp_transport` value (default: tcp). */\n rtspTransport?: 'tcp' | 'udp';\n}\n\nexport type CompositeStreamOptions = {\n api: ReolinkBaichuanApi;\n /** Optional: dedicated API for wider stream (recommended on NVR/Hub). */\n widerApi?: ReolinkBaichuanApi;\n /** Optional: dedicated API for tele stream (recommended on NVR/Hub). */\n teleApi?: ReolinkBaichuanApi;\n /** Channel for wider stream (typically 0) */\n widerChannel: number;\n /** Channel for tele stream (typically 1) */\n teleChannel: number;\n /** Profile for wider stream */\n widerProfile: StreamProfile;\n /** Profile for tele stream */\n teleProfile: StreamProfile;\n /** PIP position of tele on wider */\n pipPosition?: PipPosition;\n /** Relative size of PIP (0.1 = 10%, 0.3 = 30%, etc.) */\n pipSize?: number;\n /** Margin from edge in pixels */\n pipMargin?: number;\n /** True when behind NVR/Hub (tele is selected via stream variant on the same channel). */\n onNvr?: boolean;\n /**\n * Force using an H.264 input profile when available (e.g. auto-fallback to `sub` if `main` is H.265).\n * Output is always H.264 regardless.\n */\n forceH264?: boolean;\n\n /** Assume both wider+tele inputs are already H.264 and skip codec detection. */\n assumeH264Inputs?: boolean;\n /** Best-effort knob; overlay requires re-encode in ffmpeg, so this cannot fully disable encoding. */\n disableTranscode?: boolean;\n /** Optional logger */\n logger?: Logger;\n\n /**\n * Optional: provide RTSP input URLs (H.264 only) instead of native Baichuan streams.\n * When set, CompositeStream will read directly from RTSP via ffmpeg.\n */\n widerRtspUrl?: string;\n teleRtspUrl?: string;\n /** ffmpeg `-rtsp_transport` value (default: tcp). */\n rtspTransport?: 'tcp' | 'udp';\n /**\n * How long to wait for each input stream to produce frames before starting ffmpeg.\n * Battery cameras can take several seconds to wake and begin streaming.\n * Default: 20000ms.\n */\n inputStartupTimeoutMs?: number;\n /**\n * If true, require a keyframe during priming (preferred for fast/clean join).\n * Default: true.\n */\n requireKeyframeOnStartup?: boolean;\n};\n\n/**\n * Calculate overlay position based on requested PIP position\n */\nfunction calculateOverlayPosition(\n position: PipPosition,\n mainWidth: number,\n mainHeight: number,\n pipWidth: number,\n pipHeight: number,\n margin: number\n): { x: number; y: number } {\n const pipW = Math.floor(pipWidth);\n const pipH = Math.floor(pipHeight);\n const m = margin;\n\n switch (position) {\n case \"top-left\":\n return { x: m, y: m };\n case \"top-right\":\n return { x: mainWidth - pipW - m, y: m };\n case \"bottom-left\":\n return { x: m, y: mainHeight - pipH - m };\n case \"bottom-right\":\n return { x: mainWidth - pipW - m, y: mainHeight - pipH - m };\n case \"center\":\n return { x: Math.floor((mainWidth - pipW) / 2), y: Math.floor((mainHeight - pipH) / 2) };\n case \"top-center\":\n return { x: Math.floor((mainWidth - pipW) / 2), y: m };\n case \"bottom-center\":\n return { x: Math.floor((mainWidth - pipW) / 2), y: mainHeight - pipH - m };\n case \"left-center\":\n return { x: m, y: Math.floor((mainHeight - pipH) / 2) };\n case \"right-center\":\n return { x: mainWidth - pipW - m, y: Math.floor((mainHeight - pipH) / 2) };\n default:\n return { x: m, y: m };\n }\n}\n\n/**\n * CompositeStream - Combines wider and tele streams with configurable PIP\n */\nexport class CompositeStream extends EventEmitter<{\n videoFrame: [Buffer];\n audioFrame: [Buffer];\n error: [Error];\n close: [];\n}> {\n private options: CompositeStreamOptions;\n private widerStream: AsyncGenerator<any, void, unknown> | null = null;\n private teleStream: AsyncGenerator<any, void, unknown> | null = null;\n private ffmpegProcess: ReturnType<typeof spawn> | null = null;\n private active = false;\n private logger: Logger;\n\n private inputMode: 'native' | 'rtsp' = 'native';\n\n // ffmpeg stdout is an H.264 Annex-B byte stream; chunk boundaries are arbitrary.\n // We need to reassemble access units (frames) for RFC4571 packetization and keyframe priming.\n private ffmpegOutBuf: Buffer = Buffer.alloc(0);\n\n // Prime frames read before ffmpeg starts (used to infer codec + seed decoder).\n private widerPrimeFrame:\n | { data: Buffer; videoType?: \"H264\" | \"H265\"; isKeyframe?: boolean; microseconds?: number | null }\n | undefined;\n private telePrimeFrame:\n | { data: Buffer; videoType?: \"H264\" | \"H265\"; isKeyframe?: boolean; microseconds?: number | null }\n | undefined;\n\n private ffmpegInputOffsetSec: { wider?: number; tele?: number } | undefined;\n\n private ffmpegStderrLastLogAtMs = 0;\n private ffmpegStderrLogBurst = 0;\n\n private audioSource: 'wider' | 'tele' | undefined;\n\n private effectiveWiderProfile: StreamProfile | undefined;\n private effectiveTeleProfile: StreamProfile | undefined;\n\n private pickH264Profile(metadata: any, preferred: StreamProfile): StreamProfile {\n try {\n const isH264 = (enc?: string) => (enc ?? '').toLowerCase().includes('264');\n const streams: any[] = Array.isArray(metadata?.streams) ? metadata.streams : [];\n const byProfile = new Map<string, any>();\n for (const s of streams) byProfile.set(s.profile, s);\n\n if (isH264(byProfile.get(preferred)?.videoEncType)) return preferred;\n\n const preferredOrder: StreamProfile[] = ['sub', 'main', 'ext'];\n for (const p of preferredOrder) {\n const si = byProfile.get(p);\n if (si && isH264(si.videoEncType)) return p;\n }\n } catch {\n // ignore\n }\n\n return preferred;\n }\n\n private closeGenerator(gen: AsyncGenerator<any, void, unknown> | null): Promise<void> {\n if (!gen) return Promise.resolve();\n const r = (gen as any).return;\n if (typeof r !== 'function') return Promise.resolve();\n try {\n return Promise.resolve(r.call(gen)).then(() => undefined).catch(() => undefined);\n } catch {\n return Promise.resolve();\n }\n }\n\n constructor(options: CompositeStreamOptions) {\n super();\n this.options = {\n pipPosition: \"bottom-right\",\n pipSize: 0.25,\n pipMargin: 10,\n ...options,\n };\n this.logger = options.logger ?? console;\n }\n\n private resolvePipMarginPx(mainWidth: number, mainHeight: number): number {\n const raw = this.options.pipMargin;\n if (raw === undefined || raw === null) return 10;\n const v = Number(raw);\n if (!Number.isFinite(v) || v < 0) return 10;\n // Legacy: treat values > 1 as pixels.\n if (v > 1) return Math.floor(v);\n // New: treat as fraction (0..1) of output size.\n const base = Math.min(mainWidth, mainHeight);\n return Math.max(0, Math.floor(base * v));\n }\n\n private describeApiClient(api: ReolinkBaichuanApi | undefined): { transport?: string; host?: string; sid?: number; uid?: string } {\n const c: any = api ? (api as any).client : undefined;\n if (!c) return {};\n try {\n const transport = typeof c.getTransport === 'function' ? c.getTransport() : undefined;\n const host = typeof c.getHost === 'function' ? c.getHost() : undefined;\n const sid = typeof c.getSocketSessionId === 'function' ? c.getSocketSessionId() : undefined;\n const uid = typeof c.getUidShort === 'function' ? c.getUidShort() : undefined;\n return { transport, host, sid, uid };\n } catch {\n return {};\n }\n }\n\n private fingerprintFrame(buf: Buffer | undefined): { len: number; headHex: string; sha1_256: string } | undefined {\n if (!buf?.length) return;\n const head = buf.subarray(0, Math.min(12, buf.length)).toString('hex');\n const slice = buf.subarray(0, Math.min(256, buf.length));\n const sha1 = createHash('sha1').update(slice).digest('hex');\n return { len: buf.length, headHex: head, sha1_256: sha1 };\n }\n\n private async primeForFfmpeg(\n gen: AsyncGenerator<any, void, unknown>,\n timeoutMs: number,\n requireKeyframe: boolean,\n ): Promise<{ data: Buffer; videoType?: \"H264\" | \"H265\"; isKeyframe?: boolean; microseconds?: number | null } | undefined> {\n const start = Date.now();\n let first: { data: Buffer; videoType?: \"H264\" | \"H265\"; isKeyframe?: boolean; microseconds?: number | null } | undefined;\n\n while (Date.now() - start < timeoutMs) {\n const r = await Promise.race([\n gen.next(),\n new Promise<IteratorResult<any>>((resolve) => setTimeout(() => resolve({ value: undefined, done: false } as any), 250)),\n ]);\n\n if (!r || (r as any).done) return first;\n const v = (r as any).value;\n if (!v || v.audio) continue;\n if (!first) first = { data: v.data, videoType: v.videoType, isKeyframe: v.isKeyframe, microseconds: v.microseconds };\n if (v.isKeyframe) return { data: v.data, videoType: v.videoType, isKeyframe: v.isKeyframe, microseconds: v.microseconds };\n }\n\n return requireKeyframe ? undefined : first;\n }\n\n /**\n * Start the composite stream\n */\n async start(): Promise<void> {\n if (this.active) {\n throw new Error(\"Composite stream already active\");\n }\n\n this.active = true;\n this.audioSource = undefined;\n this.ffmpegInputOffsetSec = undefined;\n this.logger.log?.(\"[CompositeStream] Starting composite stream...\");\n\n try {\n const widerApi = this.options.widerApi ?? this.options.api;\n const teleApi = this.options.teleApi ?? this.options.api;\n\n const widerClientInfo = this.describeApiClient(widerApi);\n const teleClientInfo = this.describeApiClient(teleApi);\n const sameClient = (widerApi as any)?.client && (teleApi as any)?.client ? (widerApi as any).client === (teleApi as any).client : false;\n this.logger.log?.(\n `[CompositeStream] Inputs: wider(ch=${this.options.widerChannel}, profile=${this.options.widerProfile}) tele(ch=${this.options.teleChannel}, profile=${this.options.teleProfile}) ` +\n `onNvr=${Boolean(this.options.onNvr)} forceH264=${Boolean(this.options.forceH264)} ` +\n `sameClient=${sameClient} ` +\n `widerClient=${JSON.stringify(widerClientInfo)} teleClient=${JSON.stringify(teleClientInfo)}`,\n );\n\n // Get metadata to determine resolutions\n const widerMetadata = await widerApi.getStreamMetadata(this.options.widerChannel);\n const teleMetadata = await teleApi.getStreamMetadata(this.options.teleChannel);\n\n const forceH264 = this.options.forceH264 === true;\n const widerProfile = forceH264\n ? this.pickH264Profile(widerMetadata, this.options.widerProfile)\n : this.options.widerProfile;\n const teleProfile = forceH264\n ? this.pickH264Profile(teleMetadata, this.options.teleProfile)\n : this.options.teleProfile;\n\n this.effectiveWiderProfile = widerProfile;\n this.effectiveTeleProfile = teleProfile;\n\n const widerStreamInfo = widerMetadata.streams.find((s) => s.profile === widerProfile);\n const teleStreamInfo = teleMetadata.streams.find((s) => s.profile === teleProfile);\n\n if (!widerStreamInfo || !teleStreamInfo) {\n throw new Error(\"Stream metadata not found\");\n }\n\n const mainWidth = widerStreamInfo.width;\n const mainHeight = widerStreamInfo.height;\n // PIP size is defined as a fraction of the OUTPUT (main) dimensions.\n // This keeps the PIP consistent even when tele input resolution differs.\n const requestedPipSizeRaw = this.options.pipSize ?? 0.25;\n const pipSize = Math.min(0.9, Math.max(0.05, Number(requestedPipSizeRaw)));\n const teleAspect = teleStreamInfo.width > 0 && teleStreamInfo.height > 0\n ? teleStreamInfo.width / teleStreamInfo.height\n : 16 / 9;\n\n let pipWidth = Math.floor(mainWidth * pipSize);\n let pipHeight = Math.floor(pipWidth / teleAspect);\n\n // If height would exceed the target fraction of main height, constrain by height.\n const maxPipHeight = Math.floor(mainHeight * pipSize);\n if (pipHeight > maxPipHeight) {\n pipHeight = maxPipHeight;\n pipWidth = Math.floor(pipHeight * teleAspect);\n }\n\n // Keep dimensions even (friendlier for encoders/filters).\n pipWidth = Math.max(2, pipWidth - (pipWidth % 2));\n pipHeight = Math.max(2, pipHeight - (pipHeight % 2));\n\n const position = calculateOverlayPosition(\n this.options.pipPosition ?? \"bottom-right\",\n mainWidth,\n mainHeight,\n pipWidth,\n pipHeight,\n this.resolvePipMarginPx(mainWidth, mainHeight)\n );\n\n this.logger.log?.(\n `[CompositeStream] Main: ${mainWidth}x${mainHeight}, PIP: ${pipWidth}x${pipHeight} ` +\n `(pipSize=${pipSize}, position=${this.options.pipPosition ?? 'bottom-right'}, margin=${this.options.pipMargin ?? 10}) ` +\n `at (${position.x}, ${position.y})`\n );\n\n const widerRtspUrl = this.options.widerRtspUrl;\n const teleRtspUrl = this.options.teleRtspUrl;\n\n if (widerRtspUrl && teleRtspUrl) {\n this.inputMode = 'rtsp';\n\n // Enforce the existing rule: only RTSP H.264 pairs.\n // If callers already know it's H.264, they can set assumeH264Inputs=true.\n if (!this.options.assumeH264Inputs) {\n const isH264 = (enc?: string) => (enc ?? '').toLowerCase().includes('264');\n const widerEnc = widerStreamInfo?.videoEncType;\n const teleEnc = teleStreamInfo?.videoEncType;\n if (!isH264(widerEnc) || !isH264(teleEnc)) {\n throw new Error(\n `[CompositeStream] RTSP pair requires H.264 inputs. ` +\n `Detected wider=${widerEnc ?? 'unknown'} tele=${teleEnc ?? 'unknown'}. ` +\n `Provide RTSP URLs that are H.264 (often the substream), or set assumeH264Inputs=true if you know they are H.264.`,\n );\n }\n }\n\n await this.startFfmpegCompositionFromRtspUrls(\n mainWidth,\n mainHeight,\n pipWidth,\n pipHeight,\n position,\n widerRtspUrl,\n teleRtspUrl,\n this.options.rtspTransport ?? 'tcp',\n );\n\n this.logger.log?.('[CompositeStream] Composite stream started (rtsp inputs)');\n return;\n }\n\n this.inputMode = 'native';\n\n // Start native streams\n // On NVR/Hub, wider+tele are typically the same logical channel, and lens selection\n // happens via native stream variant (telephoto) rather than a separate channel.\n const teleIsVariantOnSameChannel =\n !!this.options.onNvr || this.options.teleChannel === this.options.widerChannel;\n\n // Wider stream.\n this.widerStream = createNativeStream(widerApi, this.options.widerChannel, widerProfile);\n this.teleStream = teleIsVariantOnSameChannel\n ? createNativeStream(teleApi, this.options.teleChannel, teleProfile, { variant: 'telephoto' })\n : createNativeStream(teleApi, this.options.teleChannel, teleProfile);\n\n // Prime both streams BEFORE starting ffmpeg. This makes codec detection resilient on NVR/Hub where\n // metadata may not reflect tele variant, and helps ffmpeg see a clean access unit early.\n // Prefer waiting for an IDR on both inputs (startup alignment). Fallback to any video frame if needed.\n const inputStartupTimeoutMs = Math.max(1000, this.options.inputStartupTimeoutMs ?? 20_000);\n // If we assume H.264 inputs, avoid sync/gating mechanisms to minimize startup latency.\n const requireKeyframeOnStartup = this.options.assumeH264Inputs ? false : (this.options.requireKeyframeOnStartup ?? true);\n\n // Prime both streams BEFORE starting ffmpeg.\n // When requireKeyframeOnStartup=true, we ONLY accept a keyframe.\n const primeKeyframeMs = inputStartupTimeoutMs;\n const primeAnyFrameMs = Math.min(5_000, Math.max(1_000, Math.floor(inputStartupTimeoutMs / 3)));\n\n if (requireKeyframeOnStartup) {\n const [widerPrime, telePrime] = await Promise.all([\n this.primeForFfmpeg(this.widerStream, primeKeyframeMs, true),\n this.primeForFfmpeg(this.teleStream, primeKeyframeMs, true),\n ]);\n this.widerPrimeFrame = widerPrime;\n this.telePrimeFrame = telePrime;\n } else {\n const [widerPrime, telePrime] = await Promise.all([\n this.primeForFfmpeg(this.widerStream, primeAnyFrameMs, false),\n this.primeForFfmpeg(this.teleStream, primeAnyFrameMs, false),\n ]);\n this.widerPrimeFrame = widerPrime;\n this.telePrimeFrame = telePrime;\n }\n\n const widerFp = this.fingerprintFrame(this.widerPrimeFrame?.data);\n const teleFp = this.fingerprintFrame(this.telePrimeFrame?.data);\n if (widerFp && teleFp) {\n const same = widerFp.sha1_256 === teleFp.sha1_256;\n this.logger.log?.(\n `[CompositeStream] Prime fingerprints: wider=${JSON.stringify(widerFp)} tele=${JSON.stringify(teleFp)} same=${same}`,\n );\n if (same) {\n this.logger.warn?.(\n `[CompositeStream] WARNING: wider+tele prime frames look identical. ` +\n `This usually means the same underlying stream is feeding both inputs (shared socket mixup, wrong channels, or device mapping).`,\n );\n }\n }\n\n this.logger.log?.(\n `[CompositeStream] Prime: wider=${this.widerPrimeFrame?.isKeyframe ? 'keyframe' : (this.widerPrimeFrame ? 'frame' : 'none')}, tele=${this.telePrimeFrame?.isKeyframe ? 'keyframe' : (this.telePrimeFrame ? 'frame' : 'none')}`\n );\n\n // Do not start ffmpeg until BOTH inputs have produced a usable frame.\n // When requireKeyframeOnStartup=true, this means BOTH must have a keyframe.\n if (!this.widerPrimeFrame || !this.telePrimeFrame) {\n const missing = [\n !this.widerPrimeFrame ? 'wider' : null,\n !this.telePrimeFrame ? 'tele' : null,\n ].filter(Boolean).join(', ');\n throw new Error(\n `[CompositeStream] Missing input frames (${missing}) within ${inputStartupTimeoutMs}ms. ` +\n `If your camera has a very long GOP/keyframe interval, startup will be slow. ` +\n `Consider increasing inputStartupTimeoutMs or enabling forceH264 (often shorter GOP on substream).`,\n );\n }\n\n // Timestamp-based sync (ffmpeg -itsoffset) adds latency and is best-effort.\n // If we assume H.264 inputs (sub+sub fast GOP), skip all sync mechanisms to minimize startup latency.\n if (!this.options.assumeH264Inputs) {\n try {\n const wUs = this.widerPrimeFrame.microseconds;\n const tUs = this.telePrimeFrame.microseconds;\n if (typeof wUs === 'number' && typeof tUs === 'number' && Number.isFinite(wUs) && Number.isFinite(tUs)) {\n const deltaSec = (tUs - wUs) / 1_000_000;\n const abs = Math.abs(deltaSec);\n // Ignore tiny jitter; cap absurd offsets.\n if (abs >= 0.2 && abs <= 60) {\n if (deltaSec > 0) {\n // Tele timestamp is later -> delay wider.\n this.ffmpegInputOffsetSec = { wider: abs };\n } else {\n // Wider timestamp is later -> delay tele.\n this.ffmpegInputOffsetSec = { tele: abs };\n }\n this.logger.log?.(\n `[CompositeStream] Input timestamp delta: widerUs=${wUs} teleUs=${tUs} deltaSec=${deltaSec.toFixed(3)} ` +\n `applying itoffset=${abs.toFixed(3)}s to ${deltaSec > 0 ? 'wider' : 'tele'}`,\n );\n }\n }\n } catch {\n // ignore\n }\n } else {\n this.ffmpegInputOffsetSec = undefined;\n }\n\n const widerCodecFromFrames = this.widerPrimeFrame?.videoType === 'H265' ? 'hevc' : (this.widerPrimeFrame?.videoType === 'H264' ? 'h264' : undefined);\n const teleCodecFromFrames = this.telePrimeFrame?.videoType === 'H265' ? 'hevc' : (this.telePrimeFrame?.videoType === 'H264' ? 'h264' : undefined);\n\n // Start ffmpeg for composition\n await this.startFfmpegComposition(mainWidth, mainHeight, pipWidth, pipHeight, position, widerCodecFromFrames, teleCodecFromFrames, this.ffmpegInputOffsetSec);\n\n this.logger.log?.(\"[CompositeStream] Composite stream started\");\n } catch (error) {\n this.active = false;\n this.emit(\"error\", error as Error);\n throw error;\n }\n }\n\n private async startFfmpegCompositionFromRtspUrls(\n mainWidth: number,\n mainHeight: number,\n pipWidth: number,\n pipHeight: number,\n position: { x: number; y: number },\n widerRtspUrl: string,\n teleRtspUrl: string,\n rtspTransport: 'tcp' | 'udp',\n ): Promise<void> {\n // With RTSP inputs, ffmpeg handles ingest/demux. We still re-encode for overlay.\n const ffmpegArgs = [\n '-hide_banner',\n '-loglevel', 'error',\n '-fflags', '+genpts',\n\n // Input 0: wider\n '-rtsp_transport', rtspTransport,\n '-i', widerRtspUrl,\n\n // Input 1: tele\n '-rtsp_transport', rtspTransport,\n '-i', teleRtspUrl,\n\n // Filter to scale and position PIP\n '-filter_complex',\n `[0:v]scale=${mainWidth}:${mainHeight}[main];[1:v]scale=${pipWidth}:${pipHeight}[pip];[main][pip]overlay=${position.x}:${position.y}[out]`,\n '-map', '[out]',\n\n // Output: always H.264 Annex-B\n '-an',\n '-c:v', 'libx264',\n '-g', '30',\n '-keyint_min', '30',\n '-sc_threshold', '0',\n '-x264-params', 'aud=1:repeat-headers=1:keyint=30:min-keyint=30:scenecut=0',\n '-preset', 'ultrafast',\n '-tune', 'zerolatency',\n '-crf', '23',\n '-f', 'h264',\n 'pipe:1',\n ];\n\n this.logger.log?.(`[CompositeStream] Starting ffmpeg (rtsp inputs): ${ffmpegArgs.join(' ')}`);\n\n this.ffmpegProcess = spawn('ffmpeg', ffmpegArgs, {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n this.ffmpegProcess.on('error', (error) => {\n this.logger.error?.('[CompositeStream] FFmpeg error:', error);\n this.emit('error', error);\n });\n\n this.ffmpegProcess.on('close', (code) => {\n if (code !== 0 && code !== null) {\n this.logger.warn?.(`[CompositeStream] FFmpeg exited with code ${code}`);\n }\n this.emit('close');\n });\n\n this.ffmpegProcess.stdout?.on('data', (data: Buffer) => {\n this.onFfmpegStdoutData(data);\n });\n\n this.ffmpegProcess.stderr?.on('data', (data: Buffer) => {\n const output = data.toString();\n if (output.includes('error') || output.includes('Error')) {\n const now = Date.now();\n if (now - this.ffmpegStderrLastLogAtMs > 2000) {\n this.ffmpegStderrLastLogAtMs = now;\n this.ffmpegStderrLogBurst = 0;\n }\n if (this.ffmpegStderrLogBurst++ < 3) {\n this.logger.error?.('[CompositeStream] FFmpeg stderr:', output);\n }\n }\n });\n }\n\n /**\n * Start ffmpeg for composition with overlay\n */\n private async startFfmpegComposition(\n mainWidth: number,\n mainHeight: number,\n pipWidth: number,\n pipHeight: number,\n position: { x: number; y: number },\n widerCodecOverride?: \"h264\" | \"hevc\",\n teleCodecOverride?: \"h264\" | \"hevc\",\n inputOffsetSec?: { wider?: number; tele?: number },\n ): Promise<void> {\n const widerApi = this.options.widerApi ?? this.options.api;\n const teleApi = this.options.teleApi ?? this.options.api;\n\n // Determine video codec from both streams\n // If metadata is not available or inaccurate, ffmpeg will auto-detect from stream data\n const widerMetadata = await widerApi.getStreamMetadata(this.options.widerChannel);\n const teleMetadata = await teleApi.getStreamMetadata(this.options.teleChannel);\n const widerProfile = this.effectiveWiderProfile ?? this.options.widerProfile;\n const teleProfile = this.effectiveTeleProfile ?? this.options.teleProfile;\n const widerStreamInfo = widerMetadata.streams.find((s) => s.profile === widerProfile);\n const teleStreamInfo = teleMetadata.streams.find((s) => s.profile === teleProfile);\n \n // Determine codec for each input stream.\n // Prefer real frame-derived codec, because on NVR/Hub tele variant can differ from metadata.\n const assumeH264Inputs = this.options.assumeH264Inputs === true;\n const widerCodec = assumeH264Inputs\n ? \"h264\"\n : (widerCodecOverride ?? (widerStreamInfo?.videoEncType?.toLowerCase().includes(\"265\") ? \"hevc\" : \"h264\"));\n const teleCodec = assumeH264Inputs\n ? \"h264\"\n : (teleCodecOverride ?? (teleStreamInfo?.videoEncType?.toLowerCase().includes(\"265\") ? \"hevc\" : \"h264\"));\n \n // Log codec detection for debugging\n this.logger.log?.(\n `[CompositeStream] Codec detection: wider=${widerCodec} (from metadata: ${widerStreamInfo?.videoEncType || \"unknown\"}), tele=${teleCodec} (from metadata: ${teleStreamInfo?.videoEncType || \"unknown\"})`\n );\n\n if (this.options.disableTranscode) {\n // Overlay requires decode+encode; we cannot truly `-c:v copy`.\n this.logger.warn?.(\n `[CompositeStream] disableTranscode requested, but overlay requires re-encode in ffmpeg; proceeding with libx264 output.`,\n );\n }\n\n // ffmpeg args for composition\n // Input 0: wider stream (main)\n // Input 1: tele stream (PIP)\n // Output: composite stream with overlay\n // Note: For raw H264/H265 streams from pipes, we need to specify the format\n // but we add flags to help ffmpeg detect the codec more reliably\n const widerInputArgs: string[] = [\n ...(inputOffsetSec?.wider ? [\"-itsoffset\", String(inputOffsetSec.wider)] : []),\n \"-f\",\n widerCodec,\n \"-i\",\n \"pipe:0\",\n ];\n const teleInputArgs: string[] = [\n ...(inputOffsetSec?.tele ? [\"-itsoffset\", String(inputOffsetSec.tele)] : []),\n \"-f\",\n teleCodec,\n \"-i\",\n \"pipe:3\",\n ];\n\n const ffmpegArgs = [\n \"-hide_banner\",\n \"-loglevel\", \"error\",\n \"-fflags\", \"+genpts\",\n \"-probesize\", \"32\", // Small probe size for faster detection\n \"-analyzeduration\", \"500000\", // 0.5 seconds to analyze stream\n // Input 0: wider stream (main)\n ...widerInputArgs,\n // Input 1: tele stream (PIP)\n ...teleInputArgs,\n // Filter to scale and position PIP\n \"-filter_complex\",\n `[0:v]scale=${mainWidth}:${mainHeight}[main];[1:v]scale=${pipWidth}:${pipHeight}[pip];[main][pip]overlay=${position.x}:${position.y}[out]`,\n \"-map\", \"[out]\",\n \"-c:v\", \"libx264\", // Re-encode for compatibility\n // Make the stream easy to join mid-flight: frequent IDRs + in-band headers + AUD.\n // Without this, a new client may wait many seconds for the next keyframe.\n \"-g\", \"30\",\n \"-keyint_min\", \"30\",\n \"-sc_threshold\", \"0\",\n \"-x264-params\", \"aud=1:repeat-headers=1:keyint=30:min-keyint=30:scenecut=0\",\n \"-preset\", \"ultrafast\",\n \"-tune\", \"zerolatency\",\n \"-crf\", \"23\",\n \"-f\", \"h264\",\n \"pipe:1\", // Output (stdout)\n ];\n\n this.logger.log?.(\n `[CompositeStream] Starting ffmpeg: ${ffmpegArgs.join(\" \")}`\n );\n\n // We need two writable inputs: stdin (fd 0) for wider, and an extra fd (fd 3) for tele.\n // stdout (fd 1) is used for the composed H.264 output.\n this.ffmpegProcess = spawn(\"ffmpeg\", ffmpegArgs, {\n stdio: [\"pipe\", \"pipe\", \"pipe\", \"pipe\"],\n });\n\n // Handle ffmpeg errors\n this.ffmpegProcess.on(\"error\", (error) => {\n this.logger.error?.(\"[CompositeStream] FFmpeg error:\", error);\n this.emit(\"error\", error);\n });\n\n this.ffmpegProcess.on(\"close\", (code) => {\n if (code !== 0 && code !== null) {\n this.logger.warn?.(`[CompositeStream] FFmpeg exited with code ${code}`);\n }\n this.emit(\"close\");\n });\n\n // Read composite video output.\n // IMPORTANT: stdout chunking is arbitrary; split by AUD (NAL type 9) into access units.\n this.ffmpegProcess.stdout?.on(\"data\", (data: Buffer) => {\n this.onFfmpegStdoutData(data);\n });\n\n // Read stderr for debug\n this.ffmpegProcess.stderr?.on(\"data\", (data: Buffer) => {\n const output = data.toString();\n if (output.includes(\"error\") || output.includes(\"Error\")) {\n // Rate-limit: ffmpeg can spam decoder errors during corruption/resync.\n const now = Date.now();\n if (now - this.ffmpegStderrLastLogAtMs > 2000) {\n this.ffmpegStderrLastLogAtMs = now;\n this.ffmpegStderrLogBurst = 0;\n }\n if (this.ffmpegStderrLogBurst++ < 3) {\n this.logger.error?.(\"[CompositeStream] FFmpeg stderr:\", output);\n }\n }\n });\n\n // Feed frames to ffmpeg inputs\n this.feedFramesToFfmpeg();\n }\n\n private onFfmpegStdoutData(data: Buffer) {\n if (!data?.length) return;\n\n // Append new bytes.\n this.ffmpegOutBuf = this.ffmpegOutBuf.length\n ? Buffer.concat([this.ffmpegOutBuf, data], this.ffmpegOutBuf.length + data.length)\n : data;\n\n // Prevent runaway buffering if AUDs are missing for any reason.\n const MAX_BUF = 8 * 1024 * 1024;\n if (this.ffmpegOutBuf.length > MAX_BUF) {\n // Try to resync: keep only the last 1MB.\n this.logger.warn?.(`[CompositeStream] ffmpeg stdout buffer grew too large (${this.ffmpegOutBuf.length} bytes); resyncing`);\n this.ffmpegOutBuf = this.ffmpegOutBuf.subarray(this.ffmpegOutBuf.length - 1024 * 1024);\n }\n\n // Split into access units.\n // Prefer AUD (NAL type 9) when present, but some encoders/paths omit AUD.\n // Fallback: split by first-slice-of-picture for H264 (nal_type 1/5 with first_mb_in_slice==0).\n const emittedUpTo = this.emitH264AccessUnitsFromBuffer();\n if (emittedUpTo > 0) {\n this.ffmpegOutBuf = this.ffmpegOutBuf.subarray(emittedUpTo);\n }\n }\n\n private emitH264AccessUnitsFromBuffer(): number {\n const buf = this.ffmpegOutBuf;\n if (!buf?.length) return 0;\n\n const len = buf.length;\n if (len < 5) return 0;\n\n const startCodeLenAt = (i: number): number => {\n if (i + 3 <= len && buf[i] === 0x00 && buf[i + 1] === 0x00) {\n if (buf[i + 2] === 0x01) return 3;\n if (i + 4 <= len && buf[i + 2] === 0x00 && buf[i + 3] === 0x01) return 4;\n }\n return 0;\n };\n\n // Align to first start code.\n let firstSc = -1;\n for (let i = 0; i < Math.min(len - 3, 64); i++) {\n if (startCodeLenAt(i)) {\n firstSc = i;\n break;\n }\n }\n if (firstSc < 0) return 0;\n if (firstSc > 0) {\n // Drop junk before first start code.\n this.ffmpegOutBuf = buf.subarray(firstSc);\n return 0;\n }\n\n // Find all start code positions we can see.\n const startCodes: number[] = [];\n for (let i = 0; i < len - 3; i++) {\n if (startCodeLenAt(i)) startCodes.push(i);\n }\n if (startCodes.length < 2) return 0; // need at least one complete NAL to parse\n\n const removeEmulationPrevention = (rbsp: Buffer): Buffer => {\n // Remove 0x03 after 0x00 0x00.\n const out: number[] = [];\n for (let i = 0; i < rbsp.length; i++) {\n const b = rbsp[i]!;\n if (i >= 2 && rbsp[i - 1] === 0x00 && rbsp[i - 2] === 0x00 && b === 0x03) {\n continue;\n }\n out.push(b);\n }\n return Buffer.from(out);\n };\n\n const readUE = (rbsp: Buffer, bitOffset: number): { value: number; next: number } | undefined => {\n const totalBits = rbsp.length * 8;\n let i = bitOffset;\n // Count leading zeros.\n let zeros = 0;\n while (i < totalBits) {\n const byte = rbsp[i >> 3]!;\n const bit = (byte >> (7 - (i & 7))) & 1;\n if (bit === 0) {\n zeros++;\n i++;\n continue;\n }\n break;\n }\n if (i >= totalBits) return;\n // Skip the 1.\n i++;\n if (zeros === 0) return { value: 0, next: i };\n if (i + zeros > totalBits) return;\n let info = 0;\n for (let k = 0; k < zeros; k++, i++) {\n const byte = rbsp[i >> 3]!;\n const bit = (byte >> (7 - (i & 7))) & 1;\n info = (info << 1) | bit;\n }\n const value = (1 << zeros) - 1 + info;\n return { value, next: i };\n };\n\n const isFirstSliceOfPicture = (nal: Buffer): boolean => {\n if (nal.length < 2) return false;\n const nalType = nal[0]! & 0x1f;\n if (nalType !== 1 && nalType !== 5) return false;\n // RBSP starts after NAL header.\n const rbsp = removeEmulationPrevention(nal.subarray(1));\n const ue = readUE(rbsp, 0);\n if (!ue) return false;\n return ue.value === 0;\n };\n\n let currentAuStart = 0;\n let sawVcl = false;\n let emittedThrough = 0;\n\n // Iterate complete NALs (exclude last, may be incomplete).\n for (let idx = 0; idx < startCodes.length - 1; idx++) {\n const scPos = startCodes[idx]!;\n const scLen = startCodeLenAt(scPos);\n if (!scLen) continue;\n const nalStart = scPos + scLen;\n const nalEnd = startCodes[idx + 1]!;\n if (nalStart >= nalEnd || nalStart >= len) continue;\n const nal = buf.subarray(nalStart, nalEnd);\n if (!nal.length) continue;\n\n const nalType = nal[0]! & 0x1f;\n const isAud = nalType === 9;\n const isVcl = nalType === 1 || nalType === 5;\n\n if (isAud) {\n // AUD always starts a new AU.\n if (sawVcl && scPos > currentAuStart) {\n const au = buf.subarray(currentAuStart, scPos);\n if (au.length) this.emit('videoFrame', au);\n emittedThrough = scPos;\n }\n currentAuStart = scPos;\n sawVcl = false;\n continue;\n }\n\n if (isVcl) {\n const firstSlice = isFirstSliceOfPicture(nal);\n if (firstSlice) {\n if (sawVcl && scPos > currentAuStart) {\n const au = buf.subarray(currentAuStart, scPos);\n if (au.length) this.emit('videoFrame', au);\n emittedThrough = scPos;\n currentAuStart = scPos;\n }\n sawVcl = true;\n } else {\n sawVcl = true;\n }\n }\n }\n\n // Only drop bytes we've safely emitted.\n return Math.max(0, emittedThrough);\n }\n\n /**\n * Feed frames from native streams to ffmpeg\n * Uses two separate loops to write frames continuously\n */\n private async feedFramesToFfmpeg(): Promise<void> {\n if (!this.ffmpegProcess || !this.widerStream || !this.teleStream) {\n return;\n }\n\n const requireKeyframeOnStartup = this.options.assumeH264Inputs ? false : (this.options.requireKeyframeOnStartup ?? true);\n\n const widerStdin = this.ffmpegProcess.stdio[0] as NodeJS.WritableStream | null;\n const teleStdin = this.ffmpegProcess.stdio[3] as NodeJS.WritableStream | null;\n\n if (!widerStdin || !teleStdin) {\n this.logger.error?.(\"[CompositeStream] FFmpeg stdin not available\");\n return;\n }\n\n // Feed wider stream (input 0)\n const feedWider = async () => {\n try {\n let widerSynced = !requireKeyframeOnStartup;\n if (this.widerPrimeFrame?.data) {\n try {\n if (!requireKeyframeOnStartup || this.widerPrimeFrame.isKeyframe) {\n const written = widerStdin.write(this.toAnnexB(this.widerPrimeFrame.data));\n widerSynced = widerSynced || Boolean(this.widerPrimeFrame.isKeyframe);\n if (!written) {\n await new Promise<void>((resolve) => {\n widerStdin.once(\"drain\", () => resolve());\n });\n }\n }\n this.widerPrimeFrame = undefined;\n } catch {\n // ignore\n }\n }\n for await (const frame of this.widerStream!) {\n if (!this.active) break;\n if (frame.audio) {\n // Prefer audio from wider; if we already locked to tele, ignore.\n if (!this.audioSource) this.audioSource = 'wider';\n if (this.audioSource === 'wider') this.emit('audioFrame', frame.data);\n continue;\n }\n\n if (!widerSynced) {\n if (!frame.isKeyframe) continue;\n widerSynced = true;\n }\n\n try {\n const written = widerStdin.write(this.toAnnexB(frame.data));\n if (!written) {\n await new Promise<void>((resolve) => {\n widerStdin.once(\"drain\", () => resolve());\n });\n }\n } catch (error) {\n const code = (error as any)?.code;\n if (code === \"EPIPE\" || code === \"ERR_STREAM_WRITE_AFTER_END\") {\n this.logger.log?.(\"[CompositeStream] FFmpeg wider stdin closed\");\n break;\n }\n this.logger.error?.(\"[CompositeStream] Error writing wider frame:\", error);\n }\n }\n } catch (error) {\n if (this.active) {\n this.logger.error?.(\"[CompositeStream] Error in wider stream:\", error);\n }\n } finally {\n try {\n widerStdin.end();\n } catch {}\n }\n };\n\n // Feed tele stream (input 1)\n const feedTele = async () => {\n try {\n let teleSynced = !requireKeyframeOnStartup;\n if (this.telePrimeFrame?.data) {\n try {\n if (!requireKeyframeOnStartup || this.telePrimeFrame.isKeyframe) {\n const written = teleStdin.write(this.toAnnexB(this.telePrimeFrame.data));\n teleSynced = teleSynced || Boolean(this.telePrimeFrame.isKeyframe);\n if (!written) {\n await new Promise<void>((resolve) => {\n teleStdin.once(\"drain\", () => resolve());\n });\n }\n }\n this.telePrimeFrame = undefined;\n } catch {\n // ignore\n }\n }\n for await (const frame of this.teleStream!) {\n if (!this.active) break;\n if (frame.audio) {\n // Fallback: if wider is silent, use tele audio.\n if (!this.audioSource) this.audioSource = 'tele';\n if (this.audioSource === 'tele') this.emit('audioFrame', frame.data);\n continue;\n }\n\n if (!teleSynced) {\n if (!frame.isKeyframe) continue;\n teleSynced = true;\n }\n\n try {\n const written = teleStdin.write(this.toAnnexB(frame.data));\n if (!written) {\n await new Promise<void>((resolve) => {\n teleStdin.once(\"drain\", () => resolve());\n });\n }\n } catch (error) {\n const code = (error as any)?.code;\n if (code === \"EPIPE\" || code === \"ERR_STREAM_WRITE_AFTER_END\") {\n this.logger.log?.(\"[CompositeStream] FFmpeg tele stdin closed\");\n break;\n }\n this.logger.error?.(\"[CompositeStream] Error writing tele frame:\", error);\n }\n }\n } catch (error) {\n if (this.active) {\n this.logger.error?.(\"[CompositeStream] Error in tele stream:\", error);\n }\n } finally {\n try {\n teleStdin.end();\n } catch {}\n }\n };\n\n // Start both feeds in parallel\n Promise.all([feedWider(), feedTele()]).catch((error) => {\n if (this.active) {\n this.logger.error?.(\"[CompositeStream] Error in frame processing:\", error);\n this.emit(\"error\", error);\n }\n });\n }\n\n private toAnnexB(accessUnit: Buffer): Buffer {\n if (!accessUnit?.length) return accessUnit;\n\n // Already Annex-B if it starts with a start code.\n if (accessUnit.length >= 3 && accessUnit[0] === 0x00 && accessUnit[1] === 0x00) {\n if (accessUnit[2] === 0x01) return accessUnit;\n if (accessUnit.length >= 4 && accessUnit[2] === 0x00 && accessUnit[3] === 0x01) return accessUnit;\n }\n\n // Best-effort conversion of length-prefixed NAL units (AVCC/HVCC style) to Annex-B.\n // Many sources use 4-byte big-endian lengths per NAL.\n try {\n let off = 0;\n const parts: Buffer[] = [];\n while (off + 4 <= accessUnit.length) {\n const n = accessUnit.readUInt32BE(off);\n off += 4;\n if (!n || off + n > accessUnit.length) {\n return accessUnit;\n }\n parts.push(Buffer.from([0x00, 0x00, 0x00, 0x01]));\n parts.push(accessUnit.subarray(off, off + n));\n off += n;\n }\n if (!parts.length) return accessUnit;\n return Buffer.concat(parts);\n } catch {\n return accessUnit;\n }\n }\n\n /**\n * Stop the composite stream\n */\n async stop(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n this.active = false;\n this.logger.log?.(\"[CompositeStream] Stopping composite stream...\");\n\n // Stop ffmpeg\n if (this.ffmpegProcess) {\n try {\n this.ffmpegProcess.stdin?.end();\n this.ffmpegProcess.kill(\"SIGTERM\");\n setTimeout(() => {\n try {\n this.ffmpegProcess?.kill(\"SIGKILL\");\n } catch {}\n }, 1000);\n } catch {}\n this.ffmpegProcess = null;\n }\n\n if (this.inputMode === 'native') {\n // Ensure native generators are terminated so their finally{} runs (stops BaichuanVideoStream + watchdog).\n // Setting fields to null is NOT enough: any running for-await loops will keep the generator alive.\n await Promise.all([\n this.closeGenerator(this.widerStream),\n this.closeGenerator(this.teleStream),\n ]);\n this.widerStream = null;\n this.teleStream = null;\n } else {\n this.widerStream = null;\n this.teleStream = null;\n }\n\n this.logger.log?.(\"[CompositeStream] Composite stream stopped\");\n }\n\n /**\n * Check if stream is active\n */\n isActive(): boolean {\n return this.active;\n }\n}\n","import http from \"node:http\";\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { PassThrough, Readable } from \"node:stream\";\nimport type { ReolinkBaichuanApi } from \"../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { BaichuanVideoStream } from \"../baichuan/stream/BaichuanVideoStream\";\n\nexport interface ReplayHttpServerOptions {\n /** Baichuan API session to use for replay. */\n api: ReolinkBaichuanApi;\n /** Channel number (defaults to 0 for standalone cameras). */\n channel?: number;\n /** Recording file name (e.g., \"RecM03_20250101_120000_123.264\"). */\n fileName: string;\n /** Stream type: mainStream or subStream (inferred from fileName if not specified). */\n streamType?: \"mainStream\" | \"subStream\";\n logger: Console;\n /** Path to ffmpeg binary */\n ffmpegPath?: string;\n /** Host to bind (default: 127.0.0.1) */\n host?: string;\n /** Force NVR mode */\n isNvr?: boolean;\n}\n\nexport interface ReplayHttpServer {\n /** URL to access the stream */\n url: string;\n host: string;\n port: number;\n /** Video codec detected */\n videoCodec: \"h264\" | \"h265\";\n /** Whether audio is present */\n hasAudio: boolean;\n /** Close the server and cleanup */\n close: () => Promise<void>;\n}\n\ninterface CachedFrame {\n type: \"video\" | \"audio\";\n data: Buffer;\n isKeyframe?: boolean;\n videoType?: \"H264\" | \"H265\";\n timestamp?: number;\n}\n\n/**\n * Create an HTTP server that streams a recording replay using ffmpeg.\n *\n * This approach:\n * 1. Starts the replay and caches all frames in memory\n * 2. Waits for frames to be cached before starting ffmpeg\n * 3. Feeds cached frames + live frames to ffmpeg\n * 4. ffmpeg outputs fragmented MP4 which is served via HTTP\n *\n * This allows progressive playback while ensuring no frames are lost.\n */\nexport async function createReplayHttpServer(\n options: ReplayHttpServerOptions,\n): Promise<ReplayHttpServer> {\n const {\n api,\n channel = 0,\n fileName,\n logger,\n ffmpegPath = \"ffmpeg\",\n host = \"127.0.0.1\",\n isNvr,\n } = options;\n\n // Infer streamType from fileName if not provided\n const streamType =\n options.streamType ??\n (fileName.includes(\"RecS03_\") ? \"subStream\" : \"mainStream\");\n\n const log = (msg: string) =>\n logger.log(`[ReplayHTTP ch=${channel} file=${fileName.slice(-30)}] ${msg}`);\n const warn = (msg: string) =>\n logger.warn(\n `[ReplayHTTP ch=${channel} file=${fileName.slice(-30)}] ${msg}`,\n );\n\n log(`starting replay: streamType=${streamType}`);\n\n // Frame cache - stores all frames until ffmpeg consumes them\n const frameCache: CachedFrame[] = [];\n let replayEnded = false;\n let videoCodec: \"h264\" | \"h265\" = \"h264\";\n let hasAudio = false;\n let firstKeyframeReceived = false;\n\n // Start the recording replay stream\n const replayParams: {\n channel: number;\n fileName: string;\n streamType: \"mainStream\" | \"subStream\";\n timeoutMs: number;\n logger: Console;\n isNvr?: boolean;\n } = {\n channel,\n fileName,\n streamType,\n timeoutMs: 30_000,\n logger,\n };\n if (isNvr !== undefined) {\n replayParams.isNvr = isNvr;\n }\n\n const { stream: videoStream, stop: stopReplay } =\n await api.startRecordingReplayStream(replayParams);\n\n // Cache frames as they arrive\n const onVideoFrame = (au: any) => {\n if (!au?.data) return;\n\n const frame: CachedFrame = {\n type: \"video\",\n data: au.data,\n isKeyframe: au.isKeyframe,\n videoType: au.videoType,\n timestamp: au.microseconds,\n };\n\n frameCache.push(frame);\n\n if (au.videoType === \"H265\") {\n videoCodec = \"h265\";\n }\n\n if (au.isKeyframe && !firstKeyframeReceived) {\n firstKeyframeReceived = true;\n log(\n `first keyframe received, codec=${videoCodec}, cached=${frameCache.length} frames`,\n );\n }\n };\n\n const onAudioFrame = (frame: Buffer) => {\n if (!frame?.length) return;\n hasAudio = true;\n\n frameCache.push({\n type: \"audio\",\n data: frame,\n });\n };\n\n const onReplayEnd = () => {\n log(`replay ended, total frames cached: ${frameCache.length}`);\n replayEnded = true;\n };\n\n videoStream.on(\"videoAccessUnit\" as any, onVideoFrame);\n videoStream.on(\"audioFrame\" as any, onAudioFrame);\n videoStream.on(\"end\" as any, onReplayEnd);\n videoStream.on(\"close\" as any, () => {\n if (!replayEnded) {\n replayEnded = true;\n }\n });\n\n // Wait for first keyframe before proceeding\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error(\"Timeout waiting for first keyframe\"));\n }, 30_000);\n\n const check = () => {\n if (firstKeyframeReceived) {\n clearTimeout(timeout);\n resolve();\n } else if (replayEnded) {\n clearTimeout(timeout);\n reject(new Error(\"Replay ended before first keyframe\"));\n } else {\n setTimeout(check, 50);\n }\n };\n check();\n });\n\n log(\n `ready to serve: codec=${videoCodec}, hasAudio=${hasAudio}, cached=${frameCache.length} frames`,\n );\n\n // HTTP server state\n let httpServer: http.Server | null = null;\n let ffmpegProcess: ChildProcess | null = null;\n let closed = false;\n\n const close = async () => {\n if (closed) return;\n closed = true;\n\n log(\"closing...\");\n\n videoStream.removeListener(\"videoAccessUnit\" as any, onVideoFrame);\n videoStream.removeListener(\"audioFrame\" as any, onAudioFrame);\n videoStream.removeListener(\"end\" as any, onReplayEnd);\n\n try {\n await stopReplay();\n } catch {\n // ignore\n }\n\n if (ffmpegProcess) {\n try {\n ffmpegProcess.kill(\"SIGKILL\");\n } catch {\n // ignore\n }\n ffmpegProcess = null;\n }\n\n if (httpServer) {\n httpServer.close();\n httpServer = null;\n }\n };\n\n // Create HTTP server\n httpServer = http.createServer((req, res) => {\n if (closed) {\n res.writeHead(503);\n res.end(\"Server closed\");\n return;\n }\n\n log(`HTTP request: ${req.method} ${req.url}`);\n\n // Set headers for streaming\n res.writeHead(200, {\n \"Content-Type\": \"video/mp4\",\n \"Transfer-Encoding\": \"chunked\",\n \"Cache-Control\": \"no-cache, no-store\",\n Connection: \"keep-alive\",\n });\n\n // Create a PassThrough stream to pipe ffmpeg output\n const outputStream = new PassThrough();\n\n // Build ffmpeg command\n // Input: raw H.264/H.265 Annex-B from stdin\n // Output: fragmented MP4 to stdout\n const inputCodec = videoCodec === \"h265\" ? \"hevc\" : \"h264\";\n const ffmpegArgs = [\n \"-hide_banner\",\n \"-loglevel\",\n \"error\",\n // Video input\n \"-f\",\n inputCodec,\n \"-i\",\n \"pipe:0\",\n // Output settings - fragmented MP4 for streaming\n \"-c:v\",\n \"copy\",\n \"-movflags\",\n \"frag_keyframe+empty_moov+default_base_moof\",\n \"-f\",\n \"mp4\",\n \"pipe:1\",\n ];\n\n log(`spawning ffmpeg: ${ffmpegPath} ${ffmpegArgs.join(\" \")}`);\n\n ffmpegProcess = spawn(ffmpegPath, ffmpegArgs, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n // Pipe ffmpeg stdout to HTTP response\n ffmpegProcess.stdout?.pipe(outputStream).pipe(res);\n\n // Log ffmpeg errors\n ffmpegProcess.stderr?.on(\"data\", (data: Buffer) => {\n const msg = data.toString().trim();\n if (msg) {\n warn(`ffmpeg: ${msg}`);\n }\n });\n\n ffmpegProcess.on(\"error\", (err) => {\n warn(`ffmpeg error: ${err.message}`);\n res.end();\n });\n\n ffmpegProcess.on(\"close\", (code) => {\n log(`ffmpeg exited with code ${code}`);\n res.end();\n });\n\n // Handle client disconnect\n req.on(\"close\", () => {\n log(\"client disconnected\");\n if (ffmpegProcess) {\n try {\n ffmpegProcess.stdin?.end();\n ffmpegProcess.kill(\"SIGTERM\");\n } catch {\n // ignore\n }\n }\n });\n\n // Feed cached frames to ffmpeg, then continue with live frames\n const feedFrames = async () => {\n const stdin = ffmpegProcess?.stdin;\n if (!stdin) return;\n\n // Send all cached video frames\n let frameIndex = 0;\n for (const frame of frameCache) {\n if (frame.type === \"video\" && frame.data) {\n try {\n const canWrite = stdin.write(frame.data);\n if (!canWrite) {\n await new Promise<void>((resolve) =>\n stdin.once(\"drain\", resolve),\n );\n }\n } catch (e) {\n warn(`error writing cached frame ${frameIndex}: ${e}`);\n break;\n }\n }\n frameIndex++;\n }\n\n log(`fed ${frameIndex} cached frames to ffmpeg`);\n\n // Continue with live frames\n const liveFrameHandler = (au: any) => {\n if (!au?.data || !stdin.writable) return;\n try {\n stdin.write(au.data);\n } catch {\n // ignore write errors\n }\n };\n\n videoStream.on(\"videoAccessUnit\" as any, liveFrameHandler);\n\n // When replay ends, close ffmpeg stdin\n const endHandler = () => {\n log(\"replay finished, closing ffmpeg stdin\");\n videoStream.removeListener(\"videoAccessUnit\" as any, liveFrameHandler);\n try {\n stdin.end();\n } catch {\n // ignore\n }\n };\n\n if (replayEnded) {\n endHandler();\n } else {\n videoStream.once(\"end\" as any, endHandler);\n videoStream.once(\"close\" as any, endHandler);\n }\n };\n\n // Start feeding frames\n feedFrames().catch((e) => {\n warn(`error feeding frames: ${e}`);\n });\n });\n\n // Start listening\n await new Promise<void>((resolve, reject) => {\n httpServer!.once(\"error\", reject);\n httpServer!.listen(0, host, () => resolve());\n });\n\n const address = httpServer.address();\n if (!address || typeof address === \"string\") {\n throw new Error(\"Failed to bind HTTP server\");\n }\n const port = address.port;\n\n const url = `http://${host}:${port}/replay.mp4`;\n log(`HTTP server listening: ${url}`);\n\n return {\n url,\n host,\n port,\n videoCodec,\n hasAudio,\n close,\n };\n}\n","/**\n * Baichuan HTTP Stream Server - Serves a Baichuan video stream over HTTP (MPEG-TS).\n * A simpler alternative to an RTSP server.\n *\n * Uses HTTP for simplicity (alternative to RTSP).\n */\n\nimport { BaichuanVideoStream } from \"./BaichuanVideoStream\";\nimport { EventEmitter } from \"node:events\";\nimport { spawn } from \"node:child_process\";\nimport * as http from \"node:http\";\nimport type { Logger } from \"../../debug/DebugConfig\";\n\nexport interface BaichuanHttpStreamServerOptions {\n videoStream: BaichuanVideoStream;\n listenPort?: number;\n path?: string; // HTTP path (es. \"/main\" o \"/sub\")\n /**\n * Input FPS to help ffmpeg generate PTS/DTS when the input is raw H.264.\n * Defaults to 25 if not provided.\n */\n inputFps?: number;\n logger?: Logger;\n}\n\nconst NAL_START_CODE_4B = Buffer.from([0x00, 0x00, 0x00, 0x01]);\nconst NAL_START_CODE_3B = Buffer.from([0x00, 0x00, 0x01]);\n\nfunction hasAnnexBStart(data: Buffer): boolean {\n if (data.length < 4) return false;\n return data.subarray(0, 4).equals(NAL_START_CODE_4B) || data.subarray(0, 3).equals(NAL_START_CODE_3B);\n}\n\nfunction splitAnnexBNals(annexB: Buffer): Buffer[] {\n // Returns NAL payloads without start codes.\n // Supports both 3B and 4B start codes.\n const starts: Array<{ idx: number; len: number }> = [];\n for (let i = 0; i < annexB.length - 3; i++) {\n if (annexB[i] === 0x00 && annexB[i + 1] === 0x00) {\n if (annexB[i + 2] === 0x01) {\n starts.push({ idx: i, len: 3 });\n i += 2;\n } else if (annexB[i + 2] === 0x00 && annexB[i + 3] === 0x01) {\n starts.push({ idx: i, len: 4 });\n i += 3;\n }\n }\n }\n if (starts.length === 0) return [];\n\n const out: Buffer[] = [];\n for (let s = 0; s < starts.length; s++) {\n const start = starts[s]!;\n const payloadStart = start.idx + start.len;\n const next = starts[s + 1];\n const payloadEnd = next ? next.idx : annexB.length;\n if (payloadEnd > payloadStart) out.push(annexB.subarray(payloadStart, payloadEnd));\n }\n return out;\n}\n\nfunction h264NalType(nalPayload: Buffer): number | null {\n if (nalPayload.length < 1) return null;\n const b0 = nalPayload[0];\n if (b0 === undefined) return null;\n return b0 & 0x1f;\n}\n\nfunction isH264KeyframeFromAnnexB(annexB: Buffer): boolean {\n const nals = splitAnnexBNals(annexB);\n for (const nal of nals) {\n const t = h264NalType(nal);\n if (t === 5) return true; // IDR\n }\n return false;\n}\n\n/**\n * BaichuanHttpStreamServer - HTTP server that serves a Baichuan video stream as MPEG-TS.\n *\n * Receives video frames from BaichuanVideoStream and streams them as HTTP MPEG-TS.\n * Uses ffmpeg to mux raw H.264 into MPEG-TS.\n */\nexport class BaichuanHttpStreamServer extends EventEmitter<{\n client: [string]; // Connected client\n error: [Error];\n close: [];\n}> {\n private videoStream: BaichuanVideoStream;\n private listenPort: number;\n private path: string; private logger: Logger; private inputFps: number;\n private httpServer: http.Server | undefined;\n private ffmpegProcess: ReturnType<typeof spawn> | undefined;\n private active = false;\n private clients = new Set<http.ServerResponse>();\n private videoListener:\n | ((data: Buffer) => void)\n | ((unit: { data: Buffer; isKeyframe: boolean }) => void)\n | undefined;\n private usingAccessUnit = false;\n private seenKeyframe = false;\n private cachedSps: Buffer | null = null; // payload without start code\n private cachedPps: Buffer | null = null; // payload without start code\n\n constructor(options: BaichuanHttpStreamServerOptions) {\n super();\n this.videoStream = options.videoStream;\n this.listenPort = options.listenPort ?? 8080;\n this.path = options.path ?? \"/stream\";\n this.inputFps = options.inputFps ?? 25;\n this.logger = options.logger ?? console;\n }\n \n /**\n * Start HTTP stream server.\n * Starts an HTTP server that serves an MPEG-TS stream.\n */\n async start(): Promise<void> {\n if (this.active) {\n throw new Error(\"HTTP stream server already active\");\n }\n\n this.logger.info(`[BaichuanHttpStreamServer] Starting Baichuan video stream...`);\n // Start the Baichuan video stream.\n await this.videoStream.start();\n this.logger.info(`[BaichuanHttpStreamServer] Baichuan video stream started`);\n\n // Crea server HTTP\n this.httpServer = http.createServer((req, res) => {\n if (req.url === this.path || req.url === `${this.path}.ts`) {\n this.logger.info(`[BaichuanHttpStreamServer] New client connected: ${req.socket.remoteAddress}`);\n this.clients.add(res);\n this.emit(\"client\", req.socket.remoteAddress || \"unknown\");\n\n // Set headers for MPEG-TS streaming.\n res.writeHead(200, {\n \"Content-Type\": \"video/mp2t\",\n \"Cache-Control\": \"no-cache\",\n \"Connection\": \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n\n // Remove the client when it disconnects.\n req.on(\"close\", () => {\n this.clients.delete(res);\n this.logger.info(`[BaichuanHttpStreamServer] Client disconnected`);\n });\n } else {\n res.writeHead(404);\n res.end(\"Not Found\");\n }\n });\n\n // Start HTTP server.\n await new Promise<void>((resolve, reject) => {\n this.httpServer!.listen(this.listenPort, \"127.0.0.1\", () => {\n this.logger.info(`[BaichuanHttpStreamServer] HTTP server listening on port ${this.listenPort}`);\n resolve();\n });\n this.httpServer!.on(\"error\", reject);\n });\n\n // Start ffmpeg to convert H.264 to MPEG-TS and send to clients\n this.logger.info(`[BaichuanHttpStreamServer] Starting ffmpeg for H.264 -> MPEG-TS conversion...`);\n \n const ffmpeg = spawn(\"ffmpeg\", [\n \"-hide_banner\",\n // ffmpeg warnings often include non-fatal decode messages (e.g. decode_slice_header),\n // which we don't want to treat as application errors.\n \"-loglevel\", \"error\",\n // Force a known frame rate on raw H.264 input so the muxer gets valid PTS/DTS.\n \"-r\", String(this.inputFps),\n \"-fflags\", \"+genpts\",\n \"-use_wallclock_as_timestamps\", \"1\",\n \"-f\", \"h264\", // Input format (H.264 Annex-B)\n \"-i\", \"pipe:0\", // Read from stdin\n \"-c:v\", \"copy\", // Copy video codec (no re-encoding)\n \"-muxpreload\", \"0\",\n \"-muxdelay\", \"0\",\n \"-f\", \"mpegts\", // Output format MPEG-TS\n \"pipe:1\", // Write to stdout\n ], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n this.ffmpegProcess = ffmpeg;\n this.logger.info(`[BaichuanHttpStreamServer] FFmpeg process started (PID: ${ffmpeg.pid})`);\n\n // Feed video frames to ffmpeg.\n let frameCount = 0;\n const writeToFfmpeg = (videoData: Buffer) => {\n // Guardrail: if we feed non-Annex-B \"frames\", ffmpeg errors and the result is a black/corrupted video.\n // Until we have perfect P-frame parsing, drop non-Annex-B payloads.\n if (!hasAnnexBStart(videoData)) {\n return;\n }\n frameCount++;\n if (frameCount === 1) {\n this.logger.info(`[BaichuanHttpStreamServer] First video frame received (${videoData.length} bytes)`);\n }\n if (ffmpeg.stdin && !ffmpeg.stdin.destroyed) {\n try {\n ffmpeg.stdin.write(videoData);\n } catch (error) {\n this.logger.error(`[BaichuanHttpStreamServer] Error writing frame: ${error}`);\n this.emit(\"error\", error instanceof Error ? error : new Error(String(error)));\n }\n }\n };\n\n // Prefer the richer event (keyframe metadata), but keep compatibility with `videoFrame`.\n this.seenKeyframe = false;\n this.usingAccessUnit = false;\n this.cachedSps = null;\n this.cachedPps = null;\n\n this.videoListener = (unit: any) => {\n const data: Buffer = Buffer.isBuffer(unit) ? unit : unit?.data;\n const isKeyframe: boolean = Buffer.isBuffer(unit) ? isH264KeyframeFromAnnexB(data) : Boolean(unit?.isKeyframe);\n if (!Buffer.isBuffer(data)) return;\n\n // Avoid double-feeding: BaichuanVideoStream emits both `videoFrame` and `videoAccessUnit`.\n // If we are receiving `videoAccessUnit`, ignore `videoFrame` (Buffer) callbacks.\n if (!Buffer.isBuffer(unit)) {\n this.usingAccessUnit = true;\n } else if (this.usingAccessUnit) {\n return;\n }\n\n // Cache SPS/PPS (H.264) if present in the access unit.\n const nals = splitAnnexBNals(data);\n for (const nal of nals) {\n const t = h264NalType(nal);\n if (t === 7) this.cachedSps = nal;\n if (t === 8) this.cachedPps = nal;\n }\n\n // Do not feed ffmpeg until we see a keyframe: avoids starting on P-frames.\n if (!this.seenKeyframe) {\n if (!isKeyframe) return;\n this.seenKeyframe = true;\n this.logger.info(`[BaichuanHttpStreamServer] First keyframe received: starting ffmpeg feed`);\n }\n\n // If we have cached SPS/PPS, prepend them before keyframes for robustness (some muxers/players require it).\n if (isKeyframe && this.cachedSps && this.cachedPps) {\n // Avoid duplicates if already present\n let hasSps = false;\n let hasPps = false;\n for (const nal of nals) {\n const t = h264NalType(nal);\n if (t === 7) hasSps = true;\n if (t === 8) hasPps = true;\n }\n if (!hasSps || !hasPps) {\n const patched = Buffer.concat([\n NAL_START_CODE_4B, this.cachedSps,\n NAL_START_CODE_4B, this.cachedPps,\n data,\n ]);\n writeToFfmpeg(patched);\n return;\n }\n }\n\n writeToFfmpeg(data);\n };\n\n // Register both: if `videoAccessUnit` arrives we will use it; `videoFrame` remains for compatibility.\n this.videoStream.on(\"videoAccessUnit\" as any, this.videoListener as any);\n this.videoStream.on(\"videoFrame\", this.videoListener as any);\n\n // Broadcast ffmpeg MPEG-TS output to HTTP clients.\n ffmpeg.stdout.on(\"data\", (data: Buffer) => {\n // Send to all connected clients.\n for (const client of this.clients) {\n if (!client.destroyed) {\n try {\n client.write(data);\n } catch (error) {\n // Client disconnected; remove it.\n this.clients.delete(client);\n }\n }\n }\n });\n\n // Handle ffmpeg stderr.\n let ffmpegOutput = \"\";\n ffmpeg.stderr.on(\"data\", (data) => {\n const output = data.toString();\n ffmpegOutput += output;\n \n // With -loglevel error we only get errors here, but many are still \"non-fatal\"\n // during startup or on corrupted frames. Avoid crashing the app (unhandled 'error' event).\n const isKnownNonFatal =\n output.includes(\"top block unavailable\") ||\n output.includes(\"error while decoding\") ||\n output.includes(\"decode_slice_header error\") ||\n output.includes(\"no frame\") ||\n output.includes(\"concealing\") ||\n output.includes(\"left block unavailable\") ||\n output.includes(\"bottom block unavailable\");\n\n if (isKnownNonFatal) {\n // Track but do not emit 'error'\n this.logger.warn(`[BaichuanHttpStreamServer] FFmpeg decode warning: ${output.trim()}`);\n return;\n }\n\n // Truly critical errors (invalid input / broken pipe / muxer failure).\n const isCriticalError =\n output.includes(\"Invalid data found\") ||\n output.includes(\"Error opening\") ||\n output.includes(\"Could not write header\") ||\n output.includes(\"Broken pipe\") ||\n output.includes(\"Connection refused\") ||\n output.includes(\"Immediate exit\") ||\n output.includes(\"Conversion failed\");\n\n if (isCriticalError) {\n this.logger.error(`[BaichuanHttpStreamServer] FFmpeg critical error: ${output.trim()}`);\n // Emit 'error' only for truly terminal conditions.\n this.emit(\"error\", new Error(`FFmpeg error: ${output}`));\n } else {\n this.logger.warn(`[BaichuanHttpStreamServer] FFmpeg stderr: ${output.trim()}`);\n }\n });\n\n ffmpeg.on(\"close\", (code) => {\n if (code !== 0) {\n this.logger.error(`[BaichuanHttpStreamServer] FFmpeg exited with code ${code}`);\n this.emit(\"error\", new Error(`FFmpeg exited with code ${code}`));\n }\n this.active = false;\n this.emit(\"close\");\n });\n\n this.active = true;\n }\n\n /**\n * Get HTTP URL for this stream.\n */\n getStreamUrl(): string {\n return `http://127.0.0.1:${this.listenPort}${this.path}.ts`;\n }\n\n /**\n * Stop HTTP stream server.\n */\n async stop(): Promise<void> {\n // Stop must be idempotent: even if `active` is already false (e.g. ffmpeg crashed),\n // we still need to close server/socket and kill processes to allow Node to exit.\n\n // Close all clients\n for (const client of this.clients) {\n if (!client.destroyed) {\n client.end();\n }\n }\n this.clients.clear();\n\n // Stop the video stream\n try {\n await this.videoStream.stop();\n } catch {\n // ignore\n }\n\n // Remove listeners to avoid leaks between start/stop\n if (this.videoListener) {\n this.videoStream.removeListener(\"videoAccessUnit\" as any, this.videoListener as any);\n this.videoStream.removeListener(\"videoFrame\", this.videoListener as any);\n }\n this.videoListener = undefined;\n\n // Stop ffmpeg\n if (this.ffmpegProcess) {\n const proc = this.ffmpegProcess;\n try {\n proc.kill(\"SIGTERM\");\n } catch {\n // ignore\n }\n // If it doesn't exit, force SIGKILL so it doesn't hang.\n await new Promise<void>((resolve) => {\n const t = setTimeout(() => {\n try {\n proc.kill(\"SIGKILL\");\n } catch {\n // ignore\n }\n resolve();\n }, 1500);\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n (t as any)?.unref?.();\n proc.once(\"close\", () => {\n clearTimeout(t);\n resolve();\n });\n });\n }\n this.ffmpegProcess = undefined;\n\n // Stop HTTP server\n if (this.httpServer) {\n await new Promise<void>((resolve) => {\n // best-effort: close connections and then the server (modern Node versions)\n (this.httpServer as any)?.closeAllConnections?.();\n (this.httpServer as any)?.closeIdleConnections?.();\n this.httpServer!.close(() => resolve());\n });\n this.httpServer = undefined;\n }\n\n this.active = false;\n }\n\n isActive(): boolean {\n return this.active;\n }\n}\n\n","/**\n * Baichuan MJPEG Server - HTTP server that serves MJPEG streams from Baichuan cameras\n *\n * Converts H.264/H.265 video streams to MJPEG for browser viewing.\n * Supports multiple concurrent viewers with efficient stream sharing.\n */\n\nimport { EventEmitter } from \"node:events\";\nimport * as http from \"node:http\";\nimport type { StreamProfile } from \"../../reolink/baichuan/types\";\nimport type { ReolinkBaichuanApi } from \"../../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { NativeVideoStreamVariant } from \"../../reolink/baichuan/types\";\nimport { createNativeStream } from \"../../rfc/helpers\";\nimport {\n MjpegTransformer,\n createMjpegBoundary,\n getMjpegContentType,\n formatMjpegFrame,\n type MjpegFrame,\n} from \"./MjpegTransformer\";\nimport { convertToAnnexB as convertH264ToAnnexB } from \"./H264Converter\";\nimport { convertToAnnexB as convertH265ToAnnexB } from \"./H265Converter\";\nimport { detectVideoCodecFromNal } from \"./BcMediaAnnexBDecoder\";\n\nexport interface BaichuanMjpegServerOptions {\n /** API instance (required) */\n api: ReolinkBaichuanApi;\n /** Channel number (required) */\n channel: number;\n /** Stream profile (required) */\n profile: StreamProfile;\n /** Native-only: TrackMix tele/autotrack variants */\n variant?: NativeVideoStreamVariant;\n /** HTTP server port (default: 8080) */\n port?: number;\n /** HTTP server host (default: \"0.0.0.0\") */\n host?: string;\n /** URL path for MJPEG stream (default: \"/mjpeg\") */\n path?: string;\n /** JPEG quality (1-31, lower is better, default: 5) */\n quality?: number;\n /** Output width (optional) */\n width?: number;\n /** Output height (optional) */\n height?: number;\n /** Max FPS (optional) */\n maxFps?: number;\n /** Logger callback */\n logger?: (\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n ) => void;\n}\n\ninterface MjpegClient {\n id: string;\n response: http.ServerResponse;\n boundary: string;\n connectedAt: number;\n}\n\n/**\n * BaichuanMjpegServer - MJPEG HTTP server for Baichuan video streams\n *\n * Events:\n * - 'started': Server started\n * - 'stopped': Server stopped\n * - 'client-connected': Client connected\n * - 'client-disconnected': Client disconnected\n * - 'error': Error occurred\n */\nexport class BaichuanMjpegServer extends EventEmitter {\n private readonly options: BaichuanMjpegServerOptions;\n private readonly clients = new Map<string, MjpegClient>();\n private httpServer: http.Server | null = null;\n private transformer: MjpegTransformer | null = null;\n private nativeStream: AsyncGenerator<any, void, unknown> | null = null;\n private streamPump: Promise<void> | null = null;\n private detectedCodec: \"h264\" | \"h265\" | null = null;\n private started = false;\n private clientIdCounter = 0;\n\n constructor(options: BaichuanMjpegServerOptions) {\n super();\n this.options = options;\n }\n\n /**\n * Start the MJPEG server\n */\n async start(): Promise<void> {\n if (this.started) return;\n this.started = true;\n\n const port = this.options.port ?? 8080;\n const host = this.options.host ?? \"0.0.0.0\";\n const path = this.options.path ?? \"/mjpeg\";\n\n this.httpServer = http.createServer((req, res) => {\n this.handleRequest(req, res, path);\n });\n\n return new Promise<void>((resolve, reject) => {\n this.httpServer!.on(\"error\", (err) => {\n this.log(\"error\", `HTTP server error: ${err.message}`);\n reject(err);\n });\n\n this.httpServer!.listen(port, host, () => {\n this.log(\n \"info\",\n `MJPEG server started on http://${host}:${port}${path}`,\n );\n this.emit(\"started\", { host, port, path });\n resolve();\n });\n });\n }\n\n /**\n * Stop the MJPEG server\n */\n async stop(): Promise<void> {\n if (!this.started) return;\n this.started = false;\n\n // Close all clients\n for (const [id, client] of this.clients) {\n try {\n client.response.end();\n } catch {\n // ignore\n }\n this.clients.delete(id);\n }\n\n // Stop stream\n await this.stopStream();\n\n // Close HTTP server\n if (this.httpServer) {\n await new Promise<void>((resolve) => {\n this.httpServer!.close(() => resolve());\n });\n this.httpServer = null;\n }\n\n this.log(\"info\", \"MJPEG server stopped\");\n this.emit(\"stopped\");\n }\n\n /**\n * Handle HTTP request\n */\n private handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n expectedPath: string,\n ): void {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host}`);\n\n if (url.pathname !== expectedPath) {\n res.statusCode = 404;\n res.end(\"Not Found\");\n return;\n }\n\n if (req.method !== \"GET\") {\n res.statusCode = 405;\n res.end(\"Method Not Allowed\");\n return;\n }\n\n this.handleMjpegClient(req, res);\n }\n\n /**\n * Handle new MJPEG client\n */\n private handleMjpegClient(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ): void {\n const clientId = `client-${++this.clientIdCounter}`;\n const boundary = createMjpegBoundary();\n\n const client: MjpegClient = {\n id: clientId,\n response: res,\n boundary,\n connectedAt: Date.now(),\n };\n\n this.clients.set(clientId, client);\n this.log(\n \"info\",\n `MJPEG client connected: ${clientId} (total: ${this.clients.size})`,\n );\n this.emit(\"client-connected\", { id: clientId, total: this.clients.size });\n\n // Set MJPEG headers\n res.writeHead(200, {\n \"Content-Type\": getMjpegContentType(boundary),\n \"Cache-Control\": \"no-cache, no-store, must-revalidate\",\n Pragma: \"no-cache\",\n Expires: \"0\",\n Connection: \"close\",\n });\n\n // Handle client disconnect\n const cleanup = () => {\n this.clients.delete(clientId);\n this.log(\n \"info\",\n `MJPEG client disconnected: ${clientId} (remaining: ${this.clients.size})`,\n );\n this.emit(\"client-disconnected\", {\n id: clientId,\n total: this.clients.size,\n });\n\n // Stop stream if no clients\n if (this.clients.size === 0) {\n this.stopStream();\n }\n };\n\n req.on(\"close\", cleanup);\n res.on(\"close\", cleanup);\n res.on(\"error\", cleanup);\n\n // Start stream if not running\n if (!this.transformer) {\n this.startStream();\n }\n }\n\n /**\n * Start the native video stream and MJPEG transformer\n */\n private async startStream(): Promise<void> {\n if (this.transformer) return;\n\n this.log(\"info\", \"Starting native video stream...\");\n\n const { api, channel, profile, variant, quality, width, height, maxFps } =\n this.options;\n\n try {\n // Create native stream\n this.nativeStream = createNativeStream(\n api,\n channel,\n profile,\n variant ? { variant } : undefined,\n );\n\n // Pump stream to detect codec and get frames\n this.streamPump = this.pumpStream();\n } catch (err) {\n this.log(\"error\", `Failed to start stream: ${err}`);\n this.emit(\"error\", err);\n }\n }\n\n /**\n * Pump native stream and feed to transformer\n */\n private async pumpStream(): Promise<void> {\n if (!this.nativeStream) return;\n\n let frameBuffer: Buffer[] = [];\n let waitingForKeyframe = true;\n\n try {\n for await (const frame of this.nativeStream) {\n if (!this.started || this.clients.size === 0) break;\n\n // Frame has: type, data, microseconds, videoType\n const { type, data, microseconds, videoType } = frame;\n\n if (type !== \"Iframe\" && type !== \"Pframe\") continue;\n if (!data || data.length === 0) continue;\n\n // Convert to Annex-B format\n let annexB: Buffer;\n if (videoType === \"H265\") {\n annexB = convertH265ToAnnexB(data);\n if (!this.detectedCodec) {\n this.detectedCodec = \"h265\";\n this.initTransformer();\n }\n } else {\n annexB = convertH264ToAnnexB(data);\n if (!this.detectedCodec) {\n this.detectedCodec = \"h264\";\n this.initTransformer();\n }\n }\n\n // Wait for keyframe to start\n if (waitingForKeyframe) {\n if (type === \"Iframe\") {\n waitingForKeyframe = false;\n } else {\n continue;\n }\n }\n\n // Push to transformer\n if (this.transformer) {\n this.transformer.push(annexB, microseconds);\n }\n }\n } catch (err) {\n if (this.started) {\n this.log(\"error\", `Stream error: ${err}`);\n this.emit(\"error\", err);\n }\n }\n }\n\n /**\n * Initialize MJPEG transformer once codec is detected\n */\n private initTransformer(): void {\n if (this.transformer || !this.detectedCodec) return;\n\n const { quality, width, height, maxFps } = this.options;\n\n this.transformer = new MjpegTransformer({\n codec: this.detectedCodec,\n quality,\n width,\n height,\n maxFps,\n logger: this.options.logger,\n });\n\n this.transformer.on(\"frame\", (frame: MjpegFrame) => {\n this.broadcastFrame(frame);\n });\n\n this.transformer.on(\"error\", (err) => {\n this.log(\"error\", `Transformer error: ${err}`);\n });\n\n this.transformer.on(\"close\", () => {\n this.log(\"debug\", \"Transformer closed\");\n });\n\n this.transformer.start();\n this.log(\n \"info\",\n `MJPEG transformer started (codec: ${this.detectedCodec})`,\n );\n }\n\n /**\n * Broadcast JPEG frame to all connected clients\n */\n private broadcastFrame(frame: MjpegFrame): void {\n for (const client of this.clients.values()) {\n try {\n const mjpegData = formatMjpegFrame(frame.data, client.boundary);\n client.response.write(mjpegData);\n } catch {\n // Client might have disconnected\n }\n }\n }\n\n /**\n * Stop the stream and transformer\n */\n private async stopStream(): Promise<void> {\n // Stop transformer\n if (this.transformer) {\n await this.transformer.stop();\n this.transformer = null;\n }\n\n // Stop native stream\n if (this.nativeStream) {\n try {\n await this.nativeStream.return(undefined as any);\n } catch {\n // ignore\n }\n this.nativeStream = null;\n }\n\n // Wait for pump to finish\n if (this.streamPump) {\n try {\n await this.streamPump;\n } catch {\n // ignore\n }\n this.streamPump = null;\n }\n\n this.detectedCodec = null;\n this.log(\"debug\", \"Stream stopped\");\n }\n\n /**\n * Get current number of connected clients\n */\n getClientCount(): number {\n return this.clients.size;\n }\n\n /**\n * Get server status\n */\n getStatus(): {\n running: boolean;\n clients: number;\n codec: string | null;\n frames: number;\n } {\n return {\n running: this.started,\n clients: this.clients.size,\n codec: this.detectedCodec,\n frames: this.transformer?.getFrameCount() ?? 0,\n };\n }\n\n private log(\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n ): void {\n this.options.logger?.(level, `[BaichuanMjpegServer] ${message}`);\n }\n}\n","/**\n * MJPEG Transformer - Converts H.264/H.265 video frames to MJPEG stream\n *\n * Uses FFmpeg as a persistent process for efficient decoding and JPEG encoding.\n * Receives Annex-B access units and outputs JPEG frames.\n */\n\nimport { EventEmitter } from \"node:events\";\nimport { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\n\n/** Logger callback type for MjpegTransformer */\nexport type LoggerCallback = (\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n) => void;\n\nexport interface MjpegTransformerOptions {\n /** Video codec (required) */\n codec: \"h264\" | \"h265\";\n /** JPEG quality (1-31, lower is better, default: 5) */\n quality?: number | undefined;\n /** Output width (optional, maintains aspect ratio if only one dimension set) */\n width?: number | undefined;\n /** Output height (optional, maintains aspect ratio if only one dimension set) */\n height?: number | undefined;\n /** Frame rate limit (optional, e.g., 10 for max 10 fps) */\n maxFps?: number | undefined;\n /** Logger callback */\n logger?: LoggerCallback | undefined;\n}\n\nexport interface MjpegFrame {\n /** JPEG data */\n data: Buffer;\n /** Timestamp in microseconds */\n timestamp: number;\n}\n\nconst JPEG_SOI = Buffer.from([0xff, 0xd8]); // Start of Image\nconst JPEG_EOI = Buffer.from([0xff, 0xd9]); // End of Image\n\n/**\n * MjpegTransformer - Transforms H.264/H.265 access units to JPEG frames\n *\n * Events:\n * - 'frame': Emitted when a JPEG frame is ready (MjpegFrame)\n * - 'error': Emitted on error\n * - 'close': Emitted when transformer is closed\n */\nexport class MjpegTransformer extends EventEmitter {\n private readonly options: Required<\n Omit<MjpegTransformerOptions, \"logger\" | \"width\" | \"height\" | \"maxFps\">\n > &\n Pick<MjpegTransformerOptions, \"logger\" | \"width\" | \"height\" | \"maxFps\">;\n private ffmpeg: ChildProcessWithoutNullStreams | null = null;\n private started = false;\n private closed = false;\n private jpegBuffer = Buffer.alloc(0);\n private frameCount = 0;\n private lastTimestamp = 0;\n\n constructor(options: MjpegTransformerOptions) {\n super();\n this.options = {\n codec: options.codec,\n quality: options.quality ?? 5,\n width: options.width,\n height: options.height,\n maxFps: options.maxFps,\n logger: options.logger,\n };\n }\n\n /**\n * Start the transformer (spawns FFmpeg process)\n */\n start(): void {\n if (this.started || this.closed) return;\n this.started = true;\n\n const { codec, quality, width, height, maxFps } = this.options;\n\n // Build FFmpeg arguments\n const args: string[] = [\n \"-hide_banner\",\n \"-loglevel\",\n \"error\",\n // Input: raw video from stdin\n \"-f\",\n codec === \"h265\" ? \"hevc\" : \"h264\",\n \"-i\",\n \"pipe:0\",\n ];\n\n // Video filters\n const filters: string[] = [];\n\n // Scale if dimensions specified\n if (width || height) {\n const w = width ?? -1;\n const h = height ?? -1;\n filters.push(`scale=${w}:${h}`);\n }\n\n // Frame rate limit\n if (maxFps) {\n filters.push(`fps=${maxFps}`);\n }\n\n if (filters.length > 0) {\n args.push(\"-vf\", filters.join(\",\"));\n }\n\n // Output: MJPEG to stdout\n args.push(\n \"-c:v\",\n \"mjpeg\",\n \"-q:v\",\n String(quality),\n \"-f\",\n \"mjpeg\",\n \"pipe:1\",\n );\n\n this.log(\"debug\", `Starting FFmpeg with args: ${args.join(\" \")}`);\n\n this.ffmpeg = spawn(\"ffmpeg\", args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n this.ffmpeg.stdout.on(\"data\", (data: Buffer) => {\n this.handleJpegData(data);\n });\n\n this.ffmpeg.stderr.on(\"data\", (data: Buffer) => {\n const msg = data.toString().trim();\n if (msg) {\n this.log(\"debug\", `FFmpeg: ${msg}`);\n }\n });\n\n this.ffmpeg.on(\"close\", (code) => {\n this.log(\"debug\", `FFmpeg closed with code ${code}`);\n this.ffmpeg = null;\n if (!this.closed) {\n this.emit(\"close\", code);\n }\n });\n\n this.ffmpeg.on(\"error\", (err) => {\n this.log(\"error\", `FFmpeg error: ${err.message}`);\n this.emit(\"error\", err);\n });\n }\n\n /**\n * Push an H.264/H.265 access unit (Annex-B format with start codes)\n */\n push(accessUnit: Buffer, timestamp?: number): void {\n if (!this.started || this.closed || !this.ffmpeg) {\n return;\n }\n\n this.lastTimestamp = timestamp ?? Date.now() * 1000;\n\n try {\n this.ffmpeg.stdin.write(accessUnit);\n } catch (err) {\n this.log(\"error\", `Failed to write to FFmpeg: ${err}`);\n }\n }\n\n /**\n * Handle JPEG data from FFmpeg stdout\n * FFmpeg outputs complete JPEG images, each starting with SOI (0xFFD8)\n * and ending with EOI (0xFFD9)\n */\n private handleJpegData(data: Buffer): void {\n this.jpegBuffer = Buffer.concat([this.jpegBuffer, data]);\n\n // Process all complete JPEG frames in buffer\n while (true) {\n // Find SOI marker\n const soiIndex = this.jpegBuffer.indexOf(JPEG_SOI);\n if (soiIndex < 0) {\n // No start marker, clear buffer\n this.jpegBuffer = Buffer.alloc(0);\n break;\n }\n\n // Discard data before SOI\n if (soiIndex > 0) {\n this.jpegBuffer = this.jpegBuffer.subarray(soiIndex);\n }\n\n // Find EOI marker (must be after SOI)\n const eoiIndex = this.jpegBuffer.indexOf(JPEG_EOI, 2);\n if (eoiIndex < 0) {\n // No end marker yet, wait for more data\n break;\n }\n\n // Extract complete JPEG frame (including EOI)\n const frameEnd = eoiIndex + 2;\n const jpegFrame = this.jpegBuffer.subarray(0, frameEnd);\n\n // Remove processed frame from buffer\n this.jpegBuffer = this.jpegBuffer.subarray(frameEnd);\n\n // Emit frame\n this.frameCount++;\n const frame: MjpegFrame = {\n data: jpegFrame,\n timestamp: this.lastTimestamp,\n };\n this.emit(\"frame\", frame);\n }\n }\n\n /**\n * Stop the transformer\n */\n async stop(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n\n if (this.ffmpeg) {\n try {\n this.ffmpeg.stdin.end();\n } catch {\n // ignore\n }\n\n // Give FFmpeg a moment to flush\n await new Promise<void>((resolve) => {\n const ff = this.ffmpeg;\n if (!ff) {\n resolve();\n return;\n }\n\n const timeout = setTimeout(() => {\n ff.kill(\"SIGKILL\");\n resolve();\n }, 1000);\n\n ff.once(\"close\", () => {\n clearTimeout(timeout);\n resolve();\n });\n\n try {\n ff.kill(\"SIGTERM\");\n } catch {\n clearTimeout(timeout);\n resolve();\n }\n });\n\n this.ffmpeg = null;\n }\n\n this.emit(\"close\", 0);\n }\n\n /**\n * Get frame count\n */\n getFrameCount(): number {\n return this.frameCount;\n }\n\n /**\n * Check if running\n */\n isRunning(): boolean {\n return this.started && !this.closed && this.ffmpeg !== null;\n }\n\n private log(\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n ): void {\n this.options.logger?.(level, `[MjpegTransformer] ${message}`);\n }\n}\n\n/**\n * Create an MJPEG HTTP response handler\n *\n * Usage:\n * ```ts\n * const transformer = new MjpegTransformer({ codec: \"h264\" });\n * transformer.start();\n *\n * // In your stream handler:\n * stream.on(\"accessUnit\", (au) => transformer.push(au.data, au.timestamp));\n *\n * // HTTP handler:\n * app.get(\"/mjpeg\", (req, res) => {\n * serveMjpeg(res, transformer);\n * });\n * ```\n */\nexport function createMjpegBoundary(): string {\n return `mjpegboundary${Date.now()}`;\n}\n\nexport function getMjpegContentType(boundary: string): string {\n return `multipart/x-mixed-replace; boundary=${boundary}`;\n}\n\nexport function formatMjpegFrame(frame: Buffer, boundary: string): Buffer {\n const header = Buffer.from(\n `--${boundary}\\r\\nContent-Type: image/jpeg\\r\\nContent-Length: ${frame.length}\\r\\n\\r\\n`,\n );\n return Buffer.concat([header, frame, Buffer.from(\"\\r\\n\")]);\n}\n","/**\n * Baichuan WebRTC Server - WebRTC streaming from Baichuan cameras\n *\n * Provides ultra-low latency video/audio streaming using native Baichuan protocol.\n * Supports bidirectional audio (intercom) via WebRTC DataChannel.\n *\n * Architecture:\n * - Camera → Baichuan native stream → H.264/H.265 frames → RTP → WebRTC\n * - Browser mic → WebRTC DataChannel → ADPCM encoding → Baichuan Talk → Camera speaker\n *\n * Usage:\n * ```typescript\n * const webrtc = new BaichuanWebRTCServer({\n * api: reolinkApi,\n * channel: 0,\n * profile: \"main\",\n * enableIntercom: true,\n * });\n *\n * // Create offer for client\n * const { sessionId, offer } = await webrtc.createSession();\n *\n * // Send offer to browser via signaling, receive answer\n * await webrtc.handleAnswer(sessionId, answer);\n *\n * // Handle ICE candidates\n * await webrtc.addIceCandidate(sessionId, candidate);\n *\n * // Close session\n * await webrtc.closeSession(sessionId);\n * ```\n */\n\nimport { EventEmitter } from \"node:events\";\nimport type { StreamProfile } from \"../../reolink/baichuan/types\";\nimport type { ReolinkBaichuanApi } from \"../../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { NativeVideoStreamVariant } from \"../../reolink/baichuan/types\";\nimport { createNativeStream, Intercom } from \"../../rfc/helpers\";\nimport { detectVideoCodecFromNal } from \"./BcMediaAnnexBDecoder\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BaichuanWebRTCServerOptions {\n /** API instance (required) */\n api: ReolinkBaichuanApi;\n /** Channel number (required) */\n channel: number;\n /** Stream profile (required) */\n profile: StreamProfile;\n /** Native-only: TrackMix tele/autotrack variants */\n variant?: NativeVideoStreamVariant;\n /** Enable two-way audio intercom (default: false) */\n enableIntercom?: boolean;\n /** STUN servers for ICE (default: Google STUN) */\n stunServers?: string[];\n /** TURN servers for NAT traversal (optional) */\n turnServers?: Array<{\n urls: string;\n username?: string;\n credential?: string;\n }>;\n /** Logger callback */\n logger?: (\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n ) => void;\n}\n\nexport interface WebRTCSessionInfo {\n id: string;\n state: \"connecting\" | \"connected\" | \"disconnected\" | \"failed\";\n createdAt: Date;\n stats: {\n videoFrames: number;\n audioFrames: number;\n bytesSent: number;\n intercomBytesSent: number;\n };\n}\n\nexport interface WebRTCOffer {\n sdp: string;\n type: \"offer\";\n}\n\nexport interface WebRTCAnswer {\n sdp: string;\n type: \"answer\";\n}\n\nexport interface WebRTCIceCandidate {\n candidate: string;\n sdpMid?: string;\n sdpMLineIndex?: number;\n}\n\ninterface WebRTCSession {\n id: string;\n peerConnection: any; // RTCPeerConnection from werift\n videoTrack: any; // MediaStreamTrack from werift\n audioTrack: any; // MediaStreamTrack from werift\n videoDataChannel: any; // RTCDataChannel for H.265 video frames\n nativeStream: AsyncGenerator<any, void, unknown> | null;\n intercom: Intercom | null;\n dataChannel: any; // RTCDataChannel for intercom\n videoCodec: \"H264\" | \"H265\" | null;\n createdAt: Date;\n state: \"connecting\" | \"connected\" | \"disconnected\" | \"failed\";\n stats: {\n videoFrames: number;\n audioFrames: number;\n bytesSent: number;\n intercomBytesSent: number;\n };\n cleanup?: () => void;\n}\n\n// ============================================================================\n// H.264/H.265 NAL Parsing Helpers\n// ============================================================================\n\n/**\n * Parse NAL units from Annex-B format\n * Annex-B uses start codes (0x00000001 or 0x000001) to delimit NALUs\n */\nfunction parseAnnexBNalUnits(annexB: Buffer): Buffer[] {\n const nalUnits: Buffer[] = [];\n let offset = 0;\n\n while (offset < annexB.length) {\n // Find start code\n let startCodeLen = 0;\n if (\n offset + 4 <= annexB.length &&\n annexB[offset] === 0 &&\n annexB[offset + 1] === 0 &&\n annexB[offset + 2] === 0 &&\n annexB[offset + 3] === 1\n ) {\n startCodeLen = 4;\n } else if (\n offset + 3 <= annexB.length &&\n annexB[offset] === 0 &&\n annexB[offset + 1] === 0 &&\n annexB[offset + 2] === 1\n ) {\n startCodeLen = 3;\n } else {\n offset++;\n continue;\n }\n\n const naluStart = offset + startCodeLen;\n\n // Find next start code or end of buffer\n let naluEnd = annexB.length;\n for (let i = naluStart; i < annexB.length - 2; i++) {\n if (\n annexB[i] === 0 &&\n annexB[i + 1] === 0 &&\n (annexB[i + 2] === 1 ||\n (i + 3 < annexB.length && annexB[i + 2] === 0 && annexB[i + 3] === 1))\n ) {\n naluEnd = i;\n break;\n }\n }\n\n if (naluEnd > naluStart) {\n nalUnits.push(annexB.subarray(naluStart, naluEnd));\n }\n\n offset = naluEnd;\n }\n\n return nalUnits;\n}\n\n/**\n * Get H.264 NAL unit type from first byte\n */\nfunction getH264NalType(nalUnit: Buffer): number {\n return nalUnit[0]! & 0x1f;\n}\n\n/**\n * Get H.265 NAL unit type from first two bytes\n */\nfunction getH265NalType(nalUnit: Buffer): number {\n return (nalUnit[0]! >> 1) & 0x3f;\n}\n\n// ============================================================================\n// BaichuanWebRTCServer\n// ============================================================================\n\n/**\n * BaichuanWebRTCServer - WebRTC server for Baichuan video streams\n *\n * Events:\n * - 'session-created': New session created\n * - 'session-connected': Session connected (ICE complete)\n * - 'session-closed': Session closed\n * - 'intercom-started': Intercom started for session\n * - 'intercom-stopped': Intercom stopped for session\n * - 'error': Error occurred\n */\nexport class BaichuanWebRTCServer extends EventEmitter {\n private readonly options: BaichuanWebRTCServerOptions;\n private readonly sessions = new Map<string, WebRTCSession>();\n private sessionIdCounter = 0;\n private weriftModule: any = null;\n\n constructor(options: BaichuanWebRTCServerOptions) {\n super();\n this.options = options;\n }\n\n /**\n * Initialize werift module (lazy load to avoid requiring it if not used)\n */\n private async loadWerift(): Promise<any> {\n if (this.weriftModule) return this.weriftModule;\n\n try {\n this.weriftModule = await import(\"werift\");\n return this.weriftModule;\n } catch (err) {\n throw new Error(\n `Failed to load werift module. Make sure it's installed: npm install werift\\nError: ${err}`,\n );\n }\n }\n\n /**\n * Create a new WebRTC session\n * Returns a session ID and SDP offer to send to the browser\n */\n async createSession(): Promise<{ sessionId: string; offer: WebRTCOffer }> {\n const werift = await this.loadWerift();\n const { RTCPeerConnection, MediaStreamTrack, RTCRtpCodecParameters } =\n werift;\n\n const sessionId = `webrtc-${++this.sessionIdCounter}-${Date.now()}`;\n\n this.log(\"info\", `Creating WebRTC session ${sessionId}`);\n\n // Configure ICE servers\n const iceServers: any[] = [];\n\n // Add STUN servers\n const stunServers = this.options.stunServers ?? [\n \"stun:stun.l.google.com:19302\",\n ];\n for (const urls of stunServers) {\n iceServers.push({ urls });\n }\n\n // Add TURN servers if provided\n if (this.options.turnServers) {\n iceServers.push(...this.options.turnServers);\n }\n\n // Create peer connection with H.264 codec\n const peerConnection = new RTCPeerConnection({\n iceServers,\n codecs: {\n video: [\n new RTCRtpCodecParameters({\n mimeType: \"video/H264\",\n clockRate: 90000,\n rtcpFeedback: [\n { type: \"nack\" },\n { type: \"nack\", parameter: \"pli\" },\n { type: \"goog-remb\" },\n ],\n parameters:\n \"packetization-mode=1;profile-level-id=42e01f;level-asymmetry-allowed=1\",\n }),\n ],\n audio: [\n new RTCRtpCodecParameters({\n mimeType: \"audio/opus\",\n clockRate: 48000,\n channels: 2,\n }),\n ],\n },\n });\n\n // Create session object\n const session: WebRTCSession = {\n id: sessionId,\n peerConnection,\n videoTrack: null,\n audioTrack: null,\n videoDataChannel: null,\n nativeStream: null,\n intercom: null,\n dataChannel: null,\n videoCodec: null,\n createdAt: new Date(),\n state: \"connecting\",\n stats: {\n videoFrames: 0,\n audioFrames: 0,\n bytesSent: 0,\n intercomBytesSent: 0,\n },\n };\n\n this.sessions.set(sessionId, session);\n\n // Add video track (H.264 - will be used only for H.264 streams)\n const videoTrack = new MediaStreamTrack({ kind: \"video\" });\n peerConnection.addTrack(videoTrack);\n session.videoTrack = videoTrack;\n\n // Add audio track (Opus for WebRTC)\n const audioTrack = new MediaStreamTrack({ kind: \"audio\" });\n peerConnection.addTrack(audioTrack);\n session.audioTrack = audioTrack;\n\n // Create data channel for H.265 video frames (browser will decode with WebCodecs)\n const videoDataChannel = peerConnection.createDataChannel(\"video\", {\n ordered: true,\n maxRetransmits: 0, // Unreliable for real-time video\n });\n session.videoDataChannel = videoDataChannel;\n\n videoDataChannel.onopen = () => {\n this.log(\"info\", `Video data channel opened for session ${sessionId}`);\n };\n\n // Create data channel for intercom audio (browser → camera)\n if (this.options.enableIntercom) {\n const dataChannel = peerConnection.createDataChannel(\"intercom\", {\n ordered: true,\n });\n session.dataChannel = dataChannel;\n\n dataChannel.onopen = () => {\n this.log(\n \"info\",\n `Intercom data channel opened for session ${sessionId}`,\n );\n this.emit(\"intercom-started\", { sessionId });\n };\n\n dataChannel.onmessage = async (event: any) => {\n // Handle incoming audio from browser for intercom\n if (session.intercom && event.data instanceof ArrayBuffer) {\n try {\n const audioData = Buffer.from(event.data);\n await session.intercom.sendAudio(audioData);\n session.stats.intercomBytesSent += audioData.length;\n } catch (err) {\n this.log(\"error\", `Failed to send intercom audio: ${err}`);\n }\n }\n };\n\n dataChannel.onclose = () => {\n this.log(\n \"info\",\n `Intercom data channel closed for session ${sessionId}`,\n );\n this.emit(\"intercom-stopped\", { sessionId });\n };\n }\n\n // Handle ICE connection state changes\n peerConnection.iceConnectionStateChange.subscribe((state: string) => {\n this.log(\"debug\", `ICE connection state for ${sessionId}: ${state}`);\n if (state === \"connected\") {\n session.state = \"connected\";\n this.emit(\"session-connected\", { sessionId });\n } else if (state === \"disconnected\" || state === \"failed\") {\n session.state = state as any;\n this.closeSession(sessionId).catch((err) => {\n this.log(\"error\", `Error closing session on ICE ${state}: ${err}`);\n });\n }\n });\n\n // Handle peer connection state changes\n peerConnection.connectionStateChange.subscribe((state: string) => {\n this.log(\"debug\", `Connection state for ${sessionId}: ${state}`);\n if (state === \"closed\" || state === \"failed\") {\n this.closeSession(sessionId).catch((err) => {\n this.log(\n \"error\",\n `Error closing session on connection ${state}: ${err}`,\n );\n });\n }\n });\n\n // Create offer\n const offer = await peerConnection.createOffer();\n await peerConnection.setLocalDescription(offer);\n\n // Wait for ICE gathering to complete (or timeout)\n await this.waitForIceGathering(peerConnection, 3000);\n\n const localDescription = peerConnection.localDescription;\n if (!localDescription) {\n throw new Error(\"Failed to create local description\");\n }\n\n this.emit(\"session-created\", { sessionId });\n\n return {\n sessionId,\n offer: {\n sdp: localDescription.sdp,\n type: \"offer\",\n },\n };\n }\n\n /**\n * Handle WebRTC answer from browser and start streaming\n */\n async handleAnswer(sessionId: string, answer: WebRTCAnswer): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (!session) {\n throw new Error(`Session ${sessionId} not found`);\n }\n\n const werift = await this.loadWerift();\n const { RTCSessionDescription } = werift;\n\n this.log(\"info\", `Handling WebRTC answer for session ${sessionId}`);\n\n // Set remote description\n await session.peerConnection.setRemoteDescription(\n new RTCSessionDescription(answer.sdp, answer.type),\n );\n\n // Start native stream\n await this.startNativeStream(session);\n\n // Start intercom if enabled\n if (this.options.enableIntercom && session.dataChannel) {\n await this.startIntercom(session);\n }\n }\n\n /**\n * Add ICE candidate from browser\n */\n async addIceCandidate(\n sessionId: string,\n candidate: WebRTCIceCandidate,\n ): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (!session) {\n throw new Error(`Session ${sessionId} not found`);\n }\n\n const werift = await this.loadWerift();\n const { RTCIceCandidate } = werift;\n\n await session.peerConnection.addIceCandidate(\n new RTCIceCandidate(candidate.candidate, candidate.sdpMid ?? \"0\"),\n );\n }\n\n /**\n * Close a WebRTC session\n */\n async closeSession(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (!session) return;\n\n this.log(\"info\", `Closing WebRTC session ${sessionId}`);\n session.state = \"disconnected\";\n\n // Stop intercom\n if (session.intercom) {\n try {\n await session.intercom.stop();\n } catch (err) {\n this.log(\"debug\", `Error stopping intercom: ${err}`);\n }\n session.intercom = null;\n }\n\n // Close data channel\n if (session.dataChannel) {\n try {\n session.dataChannel.close();\n } catch (err) {\n this.log(\"debug\", `Error closing data channel: ${err}`);\n }\n session.dataChannel = null;\n }\n\n // Call cleanup if defined\n if (session.cleanup) {\n session.cleanup();\n }\n\n // Close peer connection\n try {\n await session.peerConnection.close();\n } catch (err) {\n this.log(\"debug\", `Error closing peer connection: ${err}`);\n }\n\n this.sessions.delete(sessionId);\n this.emit(\"session-closed\", { sessionId });\n\n this.log(\n \"info\",\n `WebRTC session ${sessionId} closed (active sessions: ${this.sessions.size})`,\n );\n }\n\n /**\n * Get information about all active sessions\n */\n getSessions(): WebRTCSessionInfo[] {\n return Array.from(this.sessions.values()).map((s) => ({\n id: s.id,\n state: s.state,\n createdAt: s.createdAt,\n stats: { ...s.stats },\n }));\n }\n\n /**\n * Get information about a specific session\n */\n getSession(sessionId: string): WebRTCSessionInfo | null {\n const session = this.sessions.get(sessionId);\n if (!session) return null;\n\n return {\n id: session.id,\n state: session.state,\n createdAt: session.createdAt,\n stats: { ...session.stats },\n };\n }\n\n /**\n * Close all sessions and stop the server\n */\n async stop(): Promise<void> {\n this.log(\"info\", \"Stopping WebRTC server\");\n const sessionIds = Array.from(this.sessions.keys());\n await Promise.all(sessionIds.map((id) => this.closeSession(id)));\n this.log(\"info\", \"WebRTC server stopped\");\n }\n\n /**\n * Get the number of active sessions\n */\n get sessionCount(): number {\n return this.sessions.size;\n }\n\n // ============================================================================\n // Private Methods\n // ============================================================================\n\n /**\n * Wait for ICE gathering to complete\n */\n private async waitForIceGathering(pc: any, timeoutMs: number): Promise<void> {\n if (pc.iceGatheringState === \"complete\") return;\n\n return new Promise((resolve) => {\n const timeout = setTimeout(() => {\n resolve();\n }, timeoutMs);\n\n pc.iceGatheringStateChange.subscribe((state: string) => {\n if (state === \"complete\") {\n clearTimeout(timeout);\n resolve();\n }\n });\n });\n }\n\n /**\n * Start native Baichuan stream and pump frames to WebRTC\n */\n private async startNativeStream(session: WebRTCSession): Promise<void> {\n this.log(\n \"info\",\n `Starting native stream for session ${session.id} (channel=${this.options.channel}, profile=${this.options.profile})`,\n );\n\n // Create native stream generator\n session.nativeStream = createNativeStream(\n this.options.api,\n this.options.channel,\n this.options.profile,\n this.options.variant !== undefined\n ? { variant: this.options.variant }\n : undefined,\n );\n\n // Pump frames to WebRTC\n this.pumpFramesToWebRTC(session).catch((err) => {\n this.log(\"error\", `Frame pump error for session ${session.id}: ${err}`);\n this.closeSession(session.id).catch(() => {});\n });\n }\n\n /**\n * Pump frames from native stream to WebRTC tracks\n * H.264 → RTP media track (standard WebRTC)\n * H.265 → DataChannel with raw Annex-B frames (decoded by WebCodecs in browser)\n */\n private async pumpFramesToWebRTC(session: WebRTCSession): Promise<void> {\n if (!session.nativeStream) {\n this.log(\"warn\", `No native stream for session ${session.id}`);\n return;\n }\n\n this.log(\"info\", `Starting frame pump for session ${session.id}`);\n\n const werift = await this.loadWerift();\n const { RtpPacket, RtpHeader } = werift;\n\n let sequenceNumber = Math.floor(Math.random() * 65535);\n let timestamp = Math.floor(Math.random() * 0xffffffff);\n const videoClockRate = 90000; // Standard RTP clock rate for video\n let lastTimeMicros = 0;\n let lastLogTime = Date.now();\n let packetsSentSinceLastLog = 0;\n let frameNumber = 0;\n\n try {\n this.log(\"info\", `Entering frame loop for session ${session.id}`);\n\n for await (const frame of session.nativeStream) {\n if (session.state === \"disconnected\" || session.state === \"failed\") {\n this.log(\n \"debug\",\n `Session ${session.id} state is ${session.state}, breaking frame loop`,\n );\n break;\n }\n\n if (frame.audio) {\n // Audio frame - would need transcoding to Opus for WebRTC\n // For now, track stats only\n // TODO: Implement AAC/ADPCM to Opus transcoding\n session.stats.audioFrames++;\n } else {\n // Video frame\n if (frame.data) {\n // Detect codec on first video frame\n if (!session.videoCodec && frame.videoType) {\n session.videoCodec = frame.videoType;\n this.log(\"info\", `Detected video codec: ${session.videoCodec}`);\n\n // Send codec info to client via data channel\n if (\n session.videoDataChannel &&\n session.videoDataChannel.readyState === \"open\"\n ) {\n const codecInfo = JSON.stringify({\n type: \"codec\",\n codec: session.videoCodec,\n width: frame.width || 0,\n height: frame.height || 0,\n });\n session.videoDataChannel.send(codecInfo);\n }\n }\n\n // Calculate timestamp\n if (frame.microseconds && lastTimeMicros > 0) {\n const deltaMicros = frame.microseconds - lastTimeMicros;\n const deltaTicks = Math.floor(\n (deltaMicros / 1000000) * videoClockRate,\n );\n timestamp = (timestamp + deltaTicks) >>> 0;\n } else {\n timestamp = (timestamp + 3000) >>> 0; // ~33ms for 30fps\n }\n lastTimeMicros = frame.microseconds || 0;\n\n if (session.videoCodec === \"H264\") {\n // H.264 → send via RTP media track\n await this.sendH264Frame(\n session,\n werift,\n frame.data,\n sequenceNumber,\n timestamp,\n );\n sequenceNumber =\n (sequenceNumber + Math.ceil(frame.data.length / 1200)) & 0xffff;\n packetsSentSinceLastLog++;\n } else if (session.videoCodec === \"H265\") {\n // H.265 → send via DataChannel (WebCodecs will decode in browser)\n await this.sendH265Frame(session, frame, frameNumber);\n packetsSentSinceLastLog++;\n }\n\n frameNumber++;\n session.stats.videoFrames++;\n session.stats.bytesSent += frame.data.length;\n\n // Log progress every 5 seconds\n const now = Date.now();\n if (now - lastLogTime >= 5000) {\n this.log(\n \"debug\",\n `WebRTC session ${session.id} [${session.videoCodec}]: sent ${session.stats.videoFrames} frames, ${packetsSentSinceLastLog} packets, ${Math.round(session.stats.bytesSent / 1024)} KB`,\n );\n lastLogTime = now;\n packetsSentSinceLastLog = 0;\n }\n }\n }\n }\n } catch (err) {\n this.log(\n \"error\",\n `Error pumping frames for session ${session.id}: ${err}`,\n );\n }\n\n this.log(\"info\", `Native stream ended for session ${session.id}`);\n }\n\n /**\n * Send H.264 frame via RTP media track\n */\n private async sendH264Frame(\n session: WebRTCSession,\n werift: any,\n frameData: Buffer,\n sequenceNumber: number,\n timestamp: number,\n ): Promise<void> {\n const nalUnits = parseAnnexBNalUnits(frameData);\n\n for (let i = 0; i < nalUnits.length; i++) {\n const nalUnit = nalUnits[i]!;\n if (nalUnit.length === 0) continue;\n\n const isLastNalu = i === nalUnits.length - 1;\n const nalType = getH264NalType(nalUnit);\n\n // Skip AUD (Access Unit Delimiter) NAL units\n if (nalType === 9) continue;\n\n // Create and send RTP packets\n const rtpPackets = this.createH264RtpPackets(\n werift,\n nalUnit,\n sequenceNumber,\n timestamp,\n isLastNalu,\n );\n\n for (const rtpPacket of rtpPackets) {\n session.videoTrack.writeRtp(rtpPacket);\n sequenceNumber = (sequenceNumber + 1) & 0xffff;\n }\n }\n }\n\n /**\n * Send H.265 frame via DataChannel\n * Format: 12-byte header + Annex-B data\n * Header: [frameNum (4)] [timestamp (4)] [flags (1)] [keyframe (1)] [reserved (2)]\n */\n private async sendH265Frame(\n session: WebRTCSession,\n frame: any,\n frameNumber: number,\n ): Promise<void> {\n if (!session.videoDataChannel) {\n if (frameNumber === 0) {\n this.log(\"warn\", `No video data channel for session ${session.id}`);\n }\n return;\n }\n\n if (session.videoDataChannel.readyState !== \"open\") {\n if (frameNumber === 0) {\n this.log(\n \"warn\",\n `Video data channel not open for session ${session.id}: ${session.videoDataChannel.readyState}`,\n );\n }\n return;\n }\n\n // Use isKeyframe from the frame if available, otherwise detect from NAL units\n let isKeyframe = frame.isKeyframe === true;\n\n // Double-check by scanning NAL units if frame.isKeyframe is not set\n if (!isKeyframe && frame.isKeyframe === undefined) {\n const nalUnits = parseAnnexBNalUnits(frame.data);\n for (const nalUnit of nalUnits) {\n if (nalUnit.length === 0) continue;\n const nalType = getH265NalType(nalUnit);\n // VPS=32, SPS=33, PPS=34, IDR_W_RADL=19, IDR_N_LP=20\n if (\n nalType === 32 ||\n nalType === 33 ||\n nalType === 34 ||\n nalType === 19 ||\n nalType === 20\n ) {\n isKeyframe = true;\n break;\n }\n }\n }\n\n // Create header (12 bytes)\n const header = Buffer.alloc(12);\n header.writeUInt32BE(frameNumber, 0); // Frame number\n header.writeUInt32BE(frame.microseconds ? frame.microseconds / 1000 : 0, 4); // Timestamp in ms\n header.writeUInt8(0x01, 8); // Flags: 0x01 = H.265\n header.writeUInt8(isKeyframe ? 1 : 0, 9); // Keyframe flag\n header.writeUInt16BE(0, 10); // Reserved\n\n // Combine header + raw Annex-B data\n const packet = Buffer.concat([header, frame.data]);\n\n // Log first few frames\n if (frameNumber < 3) {\n this.log(\n \"info\",\n `Sending H.265 frame ${frameNumber}: ${packet.length} bytes, keyframe=${isKeyframe}`,\n );\n }\n\n // Send via DataChannel (may need to chunk for large frames)\n const MAX_CHUNK_SIZE = 16000; // Safe size for DataChannel\n\n try {\n if (packet.length <= MAX_CHUNK_SIZE) {\n session.videoDataChannel.send(packet);\n } else {\n // Chunk large frames\n const totalChunks = Math.ceil(packet.length / MAX_CHUNK_SIZE);\n for (let i = 0; i < totalChunks; i++) {\n const start = i * MAX_CHUNK_SIZE;\n const end = Math.min(start + MAX_CHUNK_SIZE, packet.length);\n const chunk = packet.subarray(start, end);\n\n // Prepend chunk header: [chunk index (1)] [total chunks (1)] [data...]\n const chunkHeader = Buffer.alloc(2);\n chunkHeader.writeUInt8(i, 0);\n chunkHeader.writeUInt8(totalChunks, 1);\n\n session.videoDataChannel.send(Buffer.concat([chunkHeader, chunk]));\n }\n }\n } catch (err) {\n this.log(\"error\", `Error sending H.265 frame ${frameNumber}: ${err}`);\n }\n }\n\n /**\n * Create RTP packets for H.264 NAL unit\n * Handles single NAL, STAP-A aggregation, and FU-A fragmentation\n */\n private createH264RtpPackets(\n werift: any,\n nalUnit: Buffer,\n sequenceNumber: number,\n timestamp: number,\n marker: boolean,\n ): any[] {\n const { RtpPacket, RtpHeader } = werift;\n const MTU = 1200; // Safe MTU for RTP payload\n\n const packets: any[] = [];\n\n if (nalUnit.length <= MTU) {\n // Single NAL unit - send as-is\n const header = new RtpHeader();\n header.payloadType = 96; // Dynamic payload type for H.264\n header.sequenceNumber = sequenceNumber;\n header.timestamp = timestamp;\n header.marker = marker;\n\n packets.push(new RtpPacket(header, nalUnit));\n } else {\n // FU-A fragmentation for large NAL units\n const nalHeader = nalUnit[0]!;\n const nalType = nalHeader & 0x1f;\n const nri = nalHeader & 0x60;\n\n // FU indicator: same NRI, type = 28 (FU-A)\n const fuIndicator = (nri | 28) & 0xff;\n\n let offset = 1; // Skip NAL header\n let isFirst = true;\n\n while (offset < nalUnit.length) {\n const remaining = nalUnit.length - offset;\n const chunkSize = Math.min(remaining, MTU - 2); // -2 for FU indicator + FU header\n const isLast = offset + chunkSize >= nalUnit.length;\n\n // FU header: S bit (start), E bit (end), R bit (reserved), Type\n let fuHeader = nalType;\n if (isFirst) fuHeader |= 0x80; // S bit\n if (isLast) fuHeader |= 0x40; // E bit\n\n // Create FU-A payload\n const fuPayload = Buffer.alloc(2 + chunkSize);\n fuPayload[0] = fuIndicator;\n fuPayload[1] = fuHeader;\n nalUnit.copy(fuPayload, 2, offset, offset + chunkSize);\n\n const header = new RtpHeader();\n header.payloadType = 96;\n header.sequenceNumber = (sequenceNumber + packets.length) & 0xffff;\n header.timestamp = timestamp;\n header.marker = isLast && marker;\n\n packets.push(new RtpPacket(header, fuPayload));\n\n offset += chunkSize;\n isFirst = false;\n }\n }\n\n return packets;\n }\n\n /**\n * Start intercom (two-way audio)\n */\n private async startIntercom(session: WebRTCSession): Promise<void> {\n try {\n session.intercom = new Intercom({\n api: this.options.api,\n channel: this.options.channel,\n });\n\n await session.intercom.start();\n this.log(\"info\", `Intercom started for session ${session.id}`);\n } catch (err) {\n this.log(\n \"error\",\n `Failed to start intercom for session ${session.id}: ${err}`,\n );\n // Don't fail the whole session, just disable intercom\n session.intercom = null;\n }\n }\n\n /**\n * Log helper\n */\n private log(\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n ): void {\n if (this.options.logger) {\n this.options.logger(level, message);\n }\n }\n}\n","/**\n * RTSP Server per stream composito multifocal\n * \n * Espone un server RTSP che serve lo stream composito (wider + tele con PIP)\n * come un nuovo stream \"rtp\" configurabile.\n */\n\nimport { BaichuanRtspServer } from \"../baichuan/stream/BaichuanRtspServer\";\nimport { CompositeStream, type CompositeStreamOptions, type PipPosition } from \"./compositeStream\";\nimport type { ReolinkBaichuanApi } from \"../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { StreamProfile } from \"../reolink/baichuan/types\";\nimport type { Logger } from \"../debug/DebugConfig\";\nimport { EventEmitter } from \"node:events\";\nimport { spawn } from \"node:child_process\";\nimport * as net from \"node:net\";\n\nexport type CompositeRtspServerOptions = {\n api: ReolinkBaichuanApi;\n widerChannel: number;\n teleChannel: number;\n widerProfile: StreamProfile;\n teleProfile: StreamProfile;\n pipPosition?: PipPosition;\n pipSize?: number;\n pipMargin?: number;\n listenHost?: string;\n listenPort?: number;\n path?: string;\n logger?: Logger;\n};\n\n/**\n * RTSP Server che serve uno stream composito multifocal\n * \n * Utilizza ffmpeg per creare un server RTSP che legge dallo stream composito\n */\nexport class CompositeRtspServer extends EventEmitter<{\n client: [string];\n clientDisconnected: [string];\n error: [Error];\n close: [];\n}> {\n private options: CompositeRtspServerOptions;\n private compositeStream: CompositeStream | null = null;\n private rtspServer: net.Server | null = null;\n private ffmpegProcess: ReturnType<typeof spawn> | null = null;\n private active = false;\n private logger: Logger;\n private connectedClients = new Set<string>();\n\n constructor(options: CompositeRtspServerOptions) {\n super();\n this.options = {\n listenHost: \"127.0.0.1\",\n listenPort: 8554,\n path: \"/composite\",\n ...options,\n };\n this.logger = options.logger ?? console;\n }\n\n /**\n * Avvia il server RTSP composito\n */\n async start(): Promise<void> {\n if (this.active) {\n throw new Error(\"Composite RTSP server already active\");\n }\n\n this.active = true;\n this.logger.log?.(\"[CompositeRtspServer] Starting composite RTSP server...\");\n\n try {\n // Crea e avvia composite stream\n this.compositeStream = new CompositeStream({\n api: this.options.api,\n widerChannel: this.options.widerChannel,\n teleChannel: this.options.teleChannel,\n widerProfile: this.options.widerProfile,\n teleProfile: this.options.teleProfile,\n ...(this.options.pipPosition ? { pipPosition: this.options.pipPosition } : {}),\n ...(this.options.pipSize !== undefined ? { pipSize: this.options.pipSize } : {}),\n ...(this.options.pipMargin !== undefined ? { pipMargin: this.options.pipMargin } : {}),\n logger: this.logger,\n });\n\n this.compositeStream.on(\"error\", (error) => {\n this.logger.error?.(\"[CompositeRtspServer] Composite stream error:\", error);\n this.emit(\"error\", error);\n });\n\n this.compositeStream.on(\"close\", () => {\n this.logger.log?.(\"[CompositeRtspServer] Composite stream closed\");\n });\n\n await this.compositeStream.start();\n\n // Avvia server RTSP che legge dallo stream composito\n await this.startRtspServer();\n\n this.logger.log?.(\n `[CompositeRtspServer] Server started on ${this.options.listenHost}:${this.options.listenPort}${this.options.path}`\n );\n } catch (error) {\n this.active = false;\n this.emit(\"error\", error as Error);\n throw error;\n }\n }\n\n /**\n * Avvia server RTSP usando ffmpeg\n */\n private async startRtspServer(): Promise<void> {\n // Ottieni metadata per determinare risoluzione output\n const widerMetadata = await this.options.api.getStreamMetadata(this.options.widerChannel);\n const widerStreamInfo = widerMetadata.streams.find((s) => s.profile === this.options.widerProfile);\n const width = widerStreamInfo?.width ?? 1920;\n const height = widerStreamInfo?.height ?? 1080;\n const fps = widerStreamInfo?.frameRate ?? 25;\n\n // Avvia server RTSP TCP\n this.rtspServer = net.createServer((socket) => {\n this.handleRtspConnection(socket);\n });\n\n await new Promise<void>((resolve, reject) => {\n this.rtspServer!.listen(this.options.listenPort, this.options.listenHost, () => {\n const address = this.rtspServer!.address();\n if (address && typeof address === \"object\" && \"port\" in address) {\n (this.options as any).listenPort = address.port;\n }\n resolve();\n });\n this.rtspServer!.on(\"error\", reject);\n });\n\n // Avvia ffmpeg RTSP server che legge dallo stream composito\n this.startFfmpegRtspServer(width, height, fps);\n }\n\n /**\n * Avvia ffmpeg come server RTSP che legge dallo stream composito\n */\n private startFfmpegRtspServer(width: number, height: number, fps: number): void {\n const rtspUrl = `rtsp://${this.options.listenHost}:${this.options.listenPort}${this.options.path}`;\n\n // ffmpeg legge H.264 da stdin e lo serve come RTSP\n const ffmpegArgs = [\n \"-hide_banner\",\n \"-loglevel\", \"error\",\n \"-f\", \"h264\",\n \"-i\", \"pipe:0\",\n \"-c:v\", \"copy\",\n \"-f\", \"rtsp\",\n \"-rtsp_flags\", \"listen\",\n rtspUrl,\n ];\n\n this.logger.log?.(\n `[CompositeRtspServer] Starting ffmpeg RTSP server: ${ffmpegArgs.join(\" \")}`\n );\n\n this.ffmpegProcess = spawn(\"ffmpeg\", ffmpegArgs, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n this.ffmpegProcess.on(\"error\", (error) => {\n this.logger.error?.(\"[CompositeRtspServer] FFmpeg error:\", error);\n this.emit(\"error\", error);\n });\n\n this.ffmpegProcess.on(\"close\", (code) => {\n if (code !== 0 && code !== null) {\n this.logger.warn?.(`[CompositeRtspServer] FFmpeg exited with code ${code}`);\n }\n });\n\n // Feed frames compositi a ffmpeg\n this.compositeStream!.on(\"videoFrame\", (frame: Buffer) => {\n if (this.ffmpegProcess?.stdin && !this.ffmpegProcess.stdin.destroyed) {\n try {\n const written = this.ffmpegProcess.stdin.write(frame);\n if (!written) {\n this.ffmpegProcess.stdin.once(\"drain\", () => {});\n }\n } catch (error) {\n const code = (error as any)?.code;\n if (code !== \"EPIPE\" && code !== \"ERR_STREAM_WRITE_AFTER_END\") {\n this.logger.error?.(\"[CompositeRtspServer] Error writing to ffmpeg:\", error);\n }\n }\n }\n });\n\n // Log stderr per debug\n this.ffmpegProcess.stderr?.on(\"data\", (data: Buffer) => {\n const output = data.toString();\n if (output.includes(\"error\") || output.includes(\"Error\")) {\n this.logger.error?.(\"[CompositeRtspServer] FFmpeg stderr:\", output);\n }\n });\n }\n\n /**\n * Gestisce connessioni RTSP (semplificato - delega a ffmpeg)\n */\n private handleRtspConnection(socket: net.Socket): void {\n const clientId = `${socket.remoteAddress}:${socket.remotePort}`;\n this.logger.log?.(`[CompositeRtspServer] RTSP client connected: ${clientId}`);\n this.connectedClients.add(clientId);\n this.emit(\"client\", clientId);\n\n socket.on(\"close\", () => {\n this.connectedClients.delete(clientId);\n this.emit(\"clientDisconnected\", clientId);\n this.logger.log?.(`[CompositeRtspServer] RTSP client disconnected: ${clientId}`);\n });\n\n socket.on(\"error\", (error) => {\n this.logger.error?.(`[CompositeRtspServer] RTSP client error:`, error);\n });\n\n // Nota: ffmpeg gestisce il protocollo RTSP, quindi qui potremmo solo fare proxy\n // oppure lasciare che ffmpeg gestisca tutto direttamente\n // Per semplicità, lasciamo che ffmpeg gestisca tutto\n }\n\n /**\n * Ferma il server RTSP composito\n */\n async stop(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n this.active = false;\n this.logger.log?.(\"[CompositeRtspServer] Stopping server...\");\n\n // Ferma composite stream\n if (this.compositeStream) {\n await this.compositeStream.stop();\n this.compositeStream = null;\n }\n\n // Ferma ffmpeg\n if (this.ffmpegProcess) {\n try {\n this.ffmpegProcess.stdin?.end();\n this.ffmpegProcess.kill(\"SIGTERM\");\n setTimeout(() => {\n try {\n this.ffmpegProcess?.kill(\"SIGKILL\");\n } catch {}\n }, 1000);\n } catch {}\n this.ffmpegProcess = null;\n }\n\n // Chiudi server RTSP\n if (this.rtspServer) {\n await new Promise<void>((resolve) => {\n this.rtspServer?.close(() => resolve());\n });\n this.rtspServer = null;\n }\n\n this.connectedClients.clear();\n this.emit(\"close\");\n this.logger.log?.(\"[CompositeRtspServer] Server stopped\");\n }\n\n /**\n * Ottieni URL RTSP\n */\n getRtspUrl(): string {\n return `rtsp://${this.options.listenHost}:${this.options.listenPort}${this.options.path}`;\n }\n\n /**\n * Verifica se il server è attivo\n */\n isActive(): boolean {\n return this.active;\n }\n\n /**\n * Numero di client connessi\n */\n getClientCount(): number {\n return this.connectedClients.size;\n }\n}\n\n"],"mappings":"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","names":["ff","cleanup","http","spawn","http","spawn","i","sps","pps","spawn","spawn","requestedId","sps","pps","address","http","spawn","EventEmitter","spawn","http","EventEmitter","http","EventEmitter","spawn","EventEmitter","convertToAnnexB","EventEmitter","getH265NalType","EventEmitter","EventEmitter","spawn"]}
|
|
1
|
+
{"version":3,"sources":["../src/reolink/baichuan/HlsSessionManager.ts","../src/reolink/AutodiscoveryClient.ts","../src/reolink/baichuan/types.ts","../src/reolink/baichuan/endpoints-server.ts","../src/rtsp/server.ts","../src/rfc/rfc4571.ts","../src/rfc/rfc4571-server.ts","../src/multifocal/compositeStream.ts","../src/rfc/replay-http-server.ts","../src/baichuan/stream/BaichuanHttpStreamServer.ts","../src/baichuan/stream/BaichuanMjpegServer.ts","../src/baichuan/stream/MjpegTransformer.ts","../src/baichuan/stream/BaichuanWebRTCServer.ts","../src/baichuan/stream/BaichuanHlsServer.ts","../src/multifocal/compositeRtspServer.ts"],"sourcesContent":["/**\n * HLS Session Manager for Reolink recording replay.\n *\n * This module provides a complete HLS streaming solution that manages:\n * - Session caching with TTL\n * - Automatic cleanup of expired sessions\n * - Playlist URL rewriting for absolute paths\n * - HTTP response generation for both playlists and segments\n *\n * Usage:\n * ```ts\n * const manager = new HlsSessionManager(api, { logger });\n *\n * // In your HTTP handler:\n * const result = await manager.handleRequest({\n * sessionKey: `${deviceId}:${fileId}`,\n * hlsPath: request.query.hls || \"playlist.m3u8\",\n * requestUrl: request.url,\n * createSession: async () => ({\n * channel: 0,\n * fileName: fileId,\n * isNvr: false,\n * }),\n * });\n *\n * response.send(result.body, {\n * code: result.statusCode,\n * headers: result.headers,\n * });\n * ```\n */\n\nimport type { Logger } from \"../../logging/logger\";\nimport type { ReolinkBaichuanApi } from \"./ReolinkBaichuanApi\";\n\nconst withTimeout = async <T>(\n p: Promise<T>,\n ms: number,\n label: string,\n): Promise<T> => {\n let t: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n p,\n new Promise<T>((_, reject) => {\n t = setTimeout(\n () => reject(new Error(`${label} timed out after ${ms}ms`)),\n ms,\n );\n }),\n ]);\n } finally {\n if (t) clearTimeout(t);\n }\n};\n\n/**\n * HLS session returned by createRecordingReplayHlsSession.\n */\nexport interface HlsSession {\n /** Get the current HLS playlist content (.m3u8) */\n getPlaylist: () => string;\n /** Get a segment file by name */\n getSegment: (name: string) => Buffer | undefined;\n /** List all available segment names */\n listSegments: () => string[];\n /** Wait for the HLS session to be ready */\n waitForReady: () => Promise<void>;\n /** Stop the HLS session and cleanup */\n stop: () => Promise<void>;\n /** Path to the temporary directory */\n tempDir: string;\n}\n\n/**\n * Internal cache entry for HLS sessions.\n */\ninterface HlsSessionEntry {\n session: HlsSession;\n createdAt: number;\n lastAccessAt: number;\n}\n\n/**\n * Parameters for creating a new HLS session.\n */\nexport interface HlsSessionParams {\n /** Channel number */\n channel: number;\n /** Recording file name/path */\n fileName: string;\n /** Whether this is an NVR recording */\n isNvr?: boolean;\n /** External device ID for dedicated socket */\n deviceId?: string;\n /** Transcode H.265 to H.264 */\n transcodeH265ToH264?: boolean;\n /** HLS segment duration in seconds */\n hlsSegmentDuration?: number;\n}\n\n/**\n * HTTP response result.\n */\nexport interface HlsHttpResponse {\n /** HTTP status code */\n statusCode: number;\n /** Response headers */\n headers: Record<string, string>;\n /** Response body (string for playlist, Buffer for segment) */\n body: string | Buffer;\n}\n\n/**\n * Options for HlsSessionManager constructor.\n */\nexport interface HlsSessionManagerOptions {\n /** Logger instance */\n logger?: Logger;\n /** Session TTL in milliseconds (default: 5 minutes) */\n sessionTtlMs?: number;\n /** Cleanup interval in milliseconds (default: 30 seconds) */\n cleanupIntervalMs?: number;\n}\n\n/**\n * Manages HLS sessions with caching, TTL, and HTTP response generation.\n */\nexport class HlsSessionManager {\n private sessions = new Map<string, HlsSessionEntry>();\n private readonly logger: Logger | undefined;\n private readonly sessionTtlMs: number;\n private cleanupTimer: ReturnType<typeof setInterval> | undefined;\n private creationLocks = new Map<string, Promise<void>>();\n\n constructor(\n private readonly api: ReolinkBaichuanApi,\n options?: HlsSessionManagerOptions,\n ) {\n this.logger = options?.logger;\n this.sessionTtlMs = options?.sessionTtlMs ?? 5 * 60 * 1000; // 5 minutes\n\n // Start cleanup interval\n const cleanupIntervalMs = options?.cleanupIntervalMs ?? 30_000;\n this.cleanupTimer = setInterval(() => {\n void this.cleanupExpiredSessions();\n }, cleanupIntervalMs);\n }\n\n /**\n * Handle an HLS request and return the HTTP response.\n *\n * @param params - Request parameters\n * @returns HTTP response ready to be sent\n */\n async handleRequest(params: {\n /** Unique session key (e.g., `${deviceId}:${fileId}`) */\n sessionKey: string;\n /** HLS path: \"playlist.m3u8\" or segment name like \"segment_001.ts\" */\n hlsPath: string;\n /** Full request URL for rewriting playlist URLs */\n requestUrl: string;\n /** Function to create session params if session doesn't exist */\n createSession: () => Promise<HlsSessionParams> | HlsSessionParams;\n /**\n * Optional prefix used to ensure only one active HLS session per logical client.\n * When a new session is created, any other sessions whose keys start with this\n * prefix will be stopped. This prevents replay/ffmpeg queue starvation when\n * clients quickly switch clips.\n */\n exclusiveKeyPrefix?: string;\n }): Promise<HlsHttpResponse> {\n const {\n sessionKey,\n hlsPath,\n requestUrl,\n createSession,\n exclusiveKeyPrefix,\n } = params;\n\n try {\n // Get or create session\n let entry = this.sessions.get(sessionKey);\n\n const isPlaylist = hlsPath === \"playlist.m3u8\" || hlsPath === \"\";\n const isSegment = hlsPath.endsWith(\".ts\");\n\n // IMPORTANT: Never create a new session from a segment request.\n // When clients switch clips, they may continue requesting old segments for a while.\n // If we created sessions from those late segment requests, we'd preempt the new clip\n // and cause a deadlock/thrash (devices often allow only one replay stream at a time).\n if (!entry && isSegment) {\n this.logger?.debug?.(\n `[HlsSessionManager] Segment request without session (likely stale after clip switch): ${sessionKey} ${hlsPath}`,\n );\n return {\n statusCode: 404,\n headers: {\n \"Content-Type\": \"text/plain\",\n \"Cache-Control\": \"no-store, no-cache, must-revalidate, max-age=0\",\n Pragma: \"no-cache\",\n \"Retry-After\": \"1\",\n },\n body: \"Segment not found\",\n };\n }\n\n if (!entry) {\n // Only create sessions on playlist requests.\n if (!isPlaylist) {\n return {\n statusCode: 400,\n headers: { \"Content-Type\": \"text/plain\" },\n body: \"Invalid HLS path\",\n };\n }\n\n // Serialize creation for the same logical client to avoid races during clip switches.\n // iOS can issue multiple playlist requests back-to-back, and without a lock we may end\n // up creating two sessions concurrently (queue starvation / device single-stream limit).\n const lockKey = exclusiveKeyPrefix ?? sessionKey;\n await this.withCreationLock(lockKey, async () => {\n // Re-check under the lock.\n entry = this.sessions.get(sessionKey);\n if (entry) return;\n\n if (exclusiveKeyPrefix) {\n await this.stopOtherSessionsWithPrefix(\n exclusiveKeyPrefix,\n sessionKey,\n );\n }\n\n this.logger?.log?.(\n `[HlsSessionManager] Creating new session: ${sessionKey}`,\n );\n\n this.logger?.debug?.(\n `[HlsSessionManager] createSession(): ${sessionKey}`,\n );\n const sessionParams = await createSession();\n\n this.logger?.debug?.(\n `[HlsSessionManager] Starting createRecordingReplayHlsSession: ${sessionKey}`,\n );\n const session = await withTimeout(\n this.api.createRecordingReplayHlsSession({\n channel: sessionParams.channel,\n fileName: sessionParams.fileName,\n ...(sessionParams.isNvr !== undefined && {\n isNvr: sessionParams.isNvr,\n }),\n ...(this.logger && { logger: this.logger }),\n ...(sessionParams.deviceId && {\n deviceId: sessionParams.deviceId,\n }),\n transcodeH265ToH264: sessionParams.transcodeH265ToH264 ?? true,\n hlsSegmentDuration: sessionParams.hlsSegmentDuration ?? 4,\n }),\n 20_000,\n \"createRecordingReplayHlsSession\",\n );\n\n // Wait for first segment to be ready.\n // Never hang the HTTP request indefinitely: iOS will retry playlist/segments.\n try {\n await withTimeout(\n session.waitForReady(),\n 12_000,\n \"hls waitForReady\",\n );\n } catch (e) {\n this.logger?.warn?.(\n `[HlsSessionManager] waitForReady did not complete in time for ${sessionKey}: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n\n entry = {\n session,\n createdAt: Date.now(),\n lastAccessAt: Date.now(),\n };\n this.sessions.set(sessionKey, entry);\n\n this.logger?.log?.(\n `[HlsSessionManager] Session ready: ${sessionKey}`,\n );\n });\n\n // Ensure the entry is available after creation.\n entry = this.sessions.get(sessionKey);\n if (!entry) {\n return {\n statusCode: 500,\n headers: {\n \"Content-Type\": \"text/plain\",\n \"Cache-Control\": \"no-store, no-cache, must-revalidate, max-age=0\",\n Pragma: \"no-cache\",\n },\n body: \"HLS session was not created\",\n };\n }\n }\n\n // Update last access time\n entry.lastAccessAt = Date.now();\n\n // Handle playlist request\n if (isPlaylist) {\n return this.servePlaylist(entry.session, requestUrl, sessionKey);\n }\n\n // Handle segment request\n if (isSegment) {\n return this.serveSegment(entry.session, hlsPath, sessionKey);\n }\n\n // Invalid path\n return {\n statusCode: 400,\n headers: { \"Content-Type\": \"text/plain\" },\n body: \"Invalid HLS path\",\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n this.logger?.error?.(\n `[HlsSessionManager] Error handling request: ${message}`,\n );\n\n return {\n statusCode: 500,\n headers: { \"Content-Type\": \"text/plain\" },\n body: `HLS error: ${message}`,\n };\n }\n }\n\n private async withCreationLock(\n lockKey: string,\n fn: () => Promise<void>,\n ): Promise<void> {\n const prev = this.creationLocks.get(lockKey) ?? Promise.resolve();\n let release!: () => void;\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n const chained = prev.then(\n () => current,\n () => current,\n );\n this.creationLocks.set(lockKey, chained);\n\n await prev.catch(() => {});\n try {\n await fn();\n } finally {\n release();\n if (this.creationLocks.get(lockKey) === chained) {\n this.creationLocks.delete(lockKey);\n }\n }\n }\n\n /**\n * Check if a session exists for the given key.\n */\n hasSession(sessionKey: string): boolean {\n return this.sessions.has(sessionKey);\n }\n\n /**\n * Stop a specific session.\n */\n async stopSession(sessionKey: string): Promise<void> {\n const entry = this.sessions.get(sessionKey);\n if (entry) {\n this.logger?.debug?.(\n `[HlsSessionManager] Stopping session: ${sessionKey}`,\n );\n this.sessions.delete(sessionKey);\n await entry.session.stop().catch(() => {});\n }\n }\n\n /**\n * Stop all sessions and cleanup.\n */\n async stopAll(): Promise<void> {\n this.logger?.debug?.(`[HlsSessionManager] Stopping all sessions`);\n\n if (this.cleanupTimer) {\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n\n const stopPromises = Array.from(this.sessions.values()).map((entry) =>\n entry.session.stop().catch(() => {}),\n );\n this.sessions.clear();\n await Promise.all(stopPromises);\n }\n\n /**\n * Get the number of active sessions.\n */\n get sessionCount(): number {\n return this.sessions.size;\n }\n\n /**\n * Serve the HLS playlist with rewritten segment URLs.\n */\n private servePlaylist(\n session: HlsSession,\n requestUrl: string,\n sessionKey: string,\n ): HlsHttpResponse {\n let playlist = session.getPlaylist();\n\n // Rewrite segment references to use absolute URLs with ?hls= parameter\n // The original playlist has relative refs like \"segment_000.ts\"\n // We need to rewrite them to full paths with query param\n\n try {\n const url = new URL(requestUrl, \"http://localhost\");\n const basePath = url.pathname;\n\n // Preserve original query params (auth, etc.) but drop existing hls param.\n const baseParams = new URLSearchParams(url.searchParams);\n baseParams.delete(\"hls\");\n\n // Rewrite segment references in playlist\n playlist = playlist.replace(/^(segment_\\d+\\.ts)$/gm, (match) => {\n const params = new URLSearchParams(baseParams);\n params.set(\"hls\", match);\n return `${basePath}?${params.toString()}`;\n });\n } catch {\n // If URL parsing fails, keep original playlist\n }\n\n this.logger?.debug?.(\n `[HlsSessionManager] Serving playlist: ${sessionKey}, length=${playlist.length}`,\n );\n\n return {\n statusCode: 200,\n headers: {\n \"Content-Type\": \"application/vnd.apple.mpegurl\",\n \"Cache-Control\": \"no-store, no-cache, must-revalidate, max-age=0\",\n Pragma: \"no-cache\",\n },\n body: playlist,\n };\n }\n\n /**\n * Serve an HLS segment.\n */\n private serveSegment(\n session: HlsSession,\n segmentName: string,\n sessionKey: string,\n ): HlsHttpResponse {\n const segment = session.getSegment(segmentName);\n\n if (!segment) {\n this.logger?.warn?.(\n `[HlsSessionManager] Segment not found: ${segmentName}`,\n );\n return {\n statusCode: 404,\n headers: {\n \"Content-Type\": \"text/plain\",\n \"Cache-Control\": \"no-store, no-cache, must-revalidate, max-age=0\",\n Pragma: \"no-cache\",\n \"Retry-After\": \"1\",\n },\n body: \"Segment not found\",\n };\n }\n\n this.logger?.debug?.(\n `[HlsSessionManager] Serving segment: ${segmentName} for ${sessionKey}, size=${segment.length}`,\n );\n\n return {\n statusCode: 200,\n headers: {\n \"Content-Type\": \"video/mp2t\",\n \"Cache-Control\": \"no-store, no-cache, must-revalidate, max-age=0\",\n Pragma: \"no-cache\",\n \"Content-Length\": String(segment.length),\n },\n body: segment,\n };\n }\n\n /**\n * Cleanup expired sessions.\n */\n private async cleanupExpiredSessions(): Promise<void> {\n const now = Date.now();\n const expiredKeys: string[] = [];\n\n for (const [key, entry] of this.sessions) {\n if (now - entry.lastAccessAt > this.sessionTtlMs) {\n expiredKeys.push(key);\n }\n }\n\n if (!expiredKeys.length) return;\n\n await Promise.allSettled(\n expiredKeys.map(async (key) => {\n const entry = this.sessions.get(key);\n if (!entry) return;\n\n this.logger?.log?.(\n `[HlsSessionManager] TTL expired: stopping session ${key}`,\n );\n this.sessions.delete(key);\n\n try {\n await entry.session.stop();\n } catch {\n // ignore\n }\n }),\n );\n }\n\n private async stopOtherSessionsWithPrefix(\n prefix: string,\n exceptKey: string,\n ): Promise<void> {\n const toStop: string[] = [];\n for (const key of this.sessions.keys()) {\n if (key !== exceptKey && key.startsWith(prefix)) toStop.push(key);\n }\n\n if (!toStop.length) return;\n\n this.logger?.log?.(\n `[HlsSessionManager] Switch: stopping ${toStop.length} session(s) for prefix=${prefix}`,\n );\n\n await Promise.all(\n toStop.map(async (key) => {\n const entry = this.sessions.get(key);\n if (!entry) return;\n this.sessions.delete(key);\n await entry.session.stop().catch(() => {});\n }),\n );\n }\n}\n\n/**\n * Detect if the request is from an iOS device that needs HLS.\n *\n * @param userAgent - The User-Agent header from the request\n * @returns Object with iOS detection results\n */\nexport function detectIosClient(userAgent: string | undefined): {\n isIos: boolean;\n isIosInstalledApp: boolean;\n needsHls: boolean;\n} {\n const ua = (userAgent ?? \"\").toLowerCase();\n const isIos = /iphone|ipad|ipod/.test(ua);\n const isIosInstalledApp = ua.includes(\"installedapp\");\n\n return {\n isIos,\n isIosInstalledApp,\n // iOS InstalledApp needs HLS for video playback\n needsHls: isIos && isIosInstalledApp,\n };\n}\n\n/**\n * Build the HLS redirect URL from the original request URL.\n *\n * @param originalUrl - The original request URL\n * @returns The URL with ?hls=playlist.m3u8 appended\n */\nexport function buildHlsRedirectUrl(originalUrl: string): string {\n return `${originalUrl}${originalUrl.includes(\"?\") ? \"&\" : \"?\"}hls=playlist.m3u8`;\n}\n","import type { Logger } from \"../debug/DebugConfig\";\nimport { discoverReolinkDevices, discoverViaHttpScan, discoverViaUdpBroadcast, type DiscoveredDevice, type DiscoveryOptions } from \"./discovery\";\n\nexport interface AutodiscoveryClientOptions {\n /** Network CIDR to scan (e.g., \"192.168.1.0/24\"). If not provided, auto-detects local network */\n networkCidr?: string;\n /** Username to use for authentication attempts (default: \"admin\") */\n username?: string;\n /** Password to use for authentication attempts (default: empty, will try unauthenticated) */\n password?: string;\n /** Timeout per HTTP probe in milliseconds (default: 2000) */\n httpProbeTimeoutMs?: number;\n /** Maximum number of concurrent HTTP probes (default: 50) */\n maxConcurrentProbes?: number;\n /** Logger instance for debug output */\n logger?: Logger;\n /** Ports to scan for HTTP (default: [80, 443]) */\n httpPorts?: number[];\n /** Interval between discovery scans in milliseconds (default: 60000 = 60 seconds) */\n scanIntervalMs?: number;\n /** Whether to start discovery automatically on construction (default: false) */\n autoStart?: boolean;\n /** Discovery method to use: \"http\" (TCP/IPC), \"udp\" (broadcast), or \"both\" (default: \"http\") */\n discoveryMethod?: \"http\" | \"udp\" | \"both\";\n /** Timeout for UDP broadcast in milliseconds (default: 5000) */\n udpBroadcastTimeoutMs?: number;\n}\n\n/**\n * Client per discovery continuato di telecamere Reolink sulla rete.\n * Supporta TCP/IPC (HTTP/HTTPS scanning) e/o UDP broadcast discovery.\n * \n * Mantiene una lista aggiornata delle telecamere discoverate e offre\n * metodi per ottenere la lista corrente e controllare il processo di discovery.\n * \n * @example\n * ```typescript\n * const client = new AutodiscoveryClient({\n * username: \"admin\",\n * password: \"password\",\n * scanIntervalMs: 30000, // 30 secondi\n * autoStart: true,\n * });\n * \n * // Ottieni la lista corrente\n * const devices = client.getDiscoveredDevices();\n * console.log(`Trovate ${devices.length} telecamere`);\n * \n * // Ferma il discovery\n * await client.stop();\n * ```\n */\nexport class AutodiscoveryClient {\n private readonly options: AutodiscoveryClientOptions & { scanIntervalMs: number };\n private readonly discoveredDevices = new Map<string, DiscoveredDevice>();\n private scanTimer: NodeJS.Timeout | null = null;\n private isRunning = false;\n private currentScanPromise: Promise<void> | null = null;\n\n /**\n * Costruttore del client di autodiscovery.\n * \n * @param options - Opzioni di configurazione per il discovery\n */\n constructor(options: AutodiscoveryClientOptions = {}) {\n this.options = {\n scanIntervalMs: options.scanIntervalMs ?? 60_000, // Default: 60 secondi\n autoStart: options.autoStart ?? false,\n };\n if (options.networkCidr !== undefined) {\n this.options.networkCidr = options.networkCidr;\n }\n if (options.username !== undefined) {\n this.options.username = options.username;\n }\n if (options.password !== undefined) {\n this.options.password = options.password;\n }\n if (options.httpProbeTimeoutMs !== undefined) {\n this.options.httpProbeTimeoutMs = options.httpProbeTimeoutMs;\n }\n if (options.maxConcurrentProbes !== undefined) {\n this.options.maxConcurrentProbes = options.maxConcurrentProbes;\n }\n if (options.logger !== undefined) {\n this.options.logger = options.logger;\n }\n if (options.httpPorts !== undefined) {\n this.options.httpPorts = options.httpPorts;\n }\n if (options.discoveryMethod !== undefined) {\n this.options.discoveryMethod = options.discoveryMethod;\n }\n if (options.udpBroadcastTimeoutMs !== undefined) {\n this.options.udpBroadcastTimeoutMs = options.udpBroadcastTimeoutMs;\n }\n\n if (this.options.autoStart) {\n this.start();\n }\n }\n\n /**\n * Avvia il discovery continuato.\n * Se già in esecuzione, non fa nulla.\n */\n start(): void {\n if (this.isRunning) {\n this.options.logger?.warn?.(\"[Autodiscovery] Discovery already running\");\n return;\n }\n\n this.isRunning = true;\n this.options.logger?.log?.(\n `[Autodiscovery] Starting continuous discovery (interval: ${this.options.scanIntervalMs}ms)`,\n );\n\n // Esegui il primo scan immediatamente\n this.performScan();\n\n // Poi programma gli scan successivi\n this.scheduleNextScan();\n }\n\n /**\n * Ferma il discovery continuato.\n * Se non è in esecuzione, non fa nulla.\n */\n stop(): void {\n if (!this.isRunning) {\n this.options.logger?.warn?.(\"[Autodiscovery] Discovery not running\");\n return;\n }\n\n this.isRunning = false;\n if (this.scanTimer) {\n clearTimeout(this.scanTimer);\n this.scanTimer = null;\n }\n\n this.options.logger?.log?.(\"[Autodiscovery] Discovery stopped\");\n }\n\n /**\n * Restituisce la lista corrente delle telecamere discoverate.\n * \n * @returns Array di dispositivi discoverati, ordinati per host\n */\n getDiscoveredDevices(): DiscoveredDevice[] {\n return Array.from(this.discoveredDevices.values()).sort((a, b) => {\n // Ordina per host (IP address)\n return a.host.localeCompare(b.host);\n });\n }\n\n /**\n * Restituisce il numero di telecamere attualmente discoverate.\n * \n * @returns Numero di dispositivi discoverati\n */\n getDeviceCount(): number {\n return this.discoveredDevices.size;\n }\n\n /**\n * Verifica se il discovery è attualmente in esecuzione.\n * \n * @returns `true` se il discovery è in esecuzione, `false` altrimenti\n */\n isActive(): boolean {\n return this.isRunning;\n }\n\n /**\n * Forza un scan immediato (non aspetta l'intervallo programmato).\n * Se uno scan è già in corso, attende il completamento prima di avviarne uno nuovo.\n * \n * @returns Promise che si risolve quando lo scan è completato\n */\n async scanNow(): Promise<void> {\n if (this.currentScanPromise) {\n this.options.logger?.log?.(\"[Autodiscovery] Scan already in progress, waiting for completion...\");\n await this.currentScanPromise;\n return;\n }\n\n await this.performScan();\n }\n\n /**\n * Rimuove un dispositivo dalla lista (utile se si sa che non è più disponibile).\n * \n * @param host - Indirizzo IP del dispositivo da rimuovere\n * @returns `true` se il dispositivo è stato rimosso, `false` se non era presente\n */\n removeDevice(host: string): boolean {\n const removed = this.discoveredDevices.delete(host);\n if (removed) {\n this.options.logger?.log?.(`[Autodiscovery] Device removed: ${host}`);\n }\n return removed;\n }\n\n /**\n * Pulisce tutte le telecamere discoverate dalla lista.\n */\n clearDevices(): void {\n const count = this.discoveredDevices.size;\n this.discoveredDevices.clear();\n this.options.logger?.log?.(`[Autodiscovery] Removed ${count} device(s) from list`);\n }\n\n /**\n * Esegue un singolo scan della rete.\n */\n private async performScan(): Promise<void> {\n const scanPromise = (async () => {\n try {\n this.options.logger?.log?.(\"[Autodiscovery] Starting scan...\");\n\n const discoveryMethod = this.options.discoveryMethod ?? \"http\";\n const discoveryOptions: DiscoveryOptions = {\n enableHttpScanning: discoveryMethod === \"http\" || discoveryMethod === \"both\",\n enableUdpDiscovery: discoveryMethod === \"udp\" || discoveryMethod === \"both\",\n };\n if (this.options.networkCidr !== undefined) {\n discoveryOptions.networkCidr = this.options.networkCidr;\n }\n if (this.options.username !== undefined) {\n discoveryOptions.username = this.options.username;\n }\n if (this.options.password !== undefined) {\n discoveryOptions.password = this.options.password;\n }\n if (this.options.httpProbeTimeoutMs !== undefined) {\n discoveryOptions.httpProbeTimeoutMs = this.options.httpProbeTimeoutMs;\n }\n if (this.options.maxConcurrentProbes !== undefined) {\n discoveryOptions.maxConcurrentProbes = this.options.maxConcurrentProbes;\n }\n if (this.options.logger !== undefined) {\n discoveryOptions.logger = this.options.logger;\n }\n if (this.options.httpPorts !== undefined) {\n discoveryOptions.httpPorts = this.options.httpPorts;\n }\n if (this.options.udpBroadcastTimeoutMs !== undefined) {\n discoveryOptions.udpBroadcastTimeoutMs = this.options.udpBroadcastTimeoutMs;\n }\n\n // Use the appropriate discovery method(s)\n let discovered: DiscoveredDevice[] = [];\n if (discoveryMethod === \"http\" || discoveryMethod === \"both\") {\n const httpDevices = await discoverViaHttpScan(discoveryOptions);\n discovered.push(...httpDevices);\n }\n if (discoveryMethod === \"udp\" || discoveryMethod === \"both\") {\n const udpDevices = await discoverViaUdpBroadcast(discoveryOptions);\n discovered.push(...udpDevices);\n }\n\n // Aggiorna la mappa dei dispositivi\n const beforeCount = this.discoveredDevices.size;\n const newDevices: DiscoveredDevice[] = [];\n const updatedDevices: DiscoveredDevice[] = [];\n\n for (const device of discovered) {\n const existing = this.discoveredDevices.get(device.host);\n if (existing) {\n // Aggiorna il dispositivo esistente con nuove informazioni\n const updated = this.mergeDeviceInfo(existing, device);\n if (updated) {\n updatedDevices.push(updated);\n }\n } else {\n // Nuovo dispositivo\n this.discoveredDevices.set(device.host, { ...device });\n newDevices.push(device);\n }\n }\n\n const afterCount = this.discoveredDevices.size;\n this.options.logger?.log?.(\n `[Autodiscovery] Scan completed: ${newDevices.length} new, ${updatedDevices.length} updated, total: ${afterCount}`,\n );\n\n // Log each new device immediately with full details\n for (const device of newDevices) {\n const details: string[] = [];\n if (device.model) details.push(`Model: ${device.model}`);\n if (device.name) details.push(`Name: ${device.name}`);\n if (device.uid) details.push(`UID: ${device.uid}`);\n if (device.firmwareVersion) details.push(`Firmware: ${device.firmwareVersion}`);\n if (device.httpPort) details.push(`HTTP Port: ${device.httpPort}`);\n if (device.httpsPort) details.push(`HTTPS Port: ${device.httpsPort}`);\n details.push(`Discovery Method: ${device.discoveryMethod}`);\n if (device.supportsHttps !== undefined) details.push(`HTTPS: ${device.supportsHttps}`);\n if (device.httpAccessible !== undefined) details.push(`HTTP Accessible: ${device.httpAccessible}`);\n\n this.options.logger?.log?.(\n `[Autodiscovery] 🆕 NEW DEVICE DISCOVERED - Host: ${device.host}${details.length > 0 ? ` | ${details.join(\" | \")}` : \"\"}`,\n );\n }\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n this.options.logger?.error?.(`[Autodiscovery] Error during scan: ${msg}`);\n }\n })();\n\n this.currentScanPromise = scanPromise;\n await scanPromise;\n this.currentScanPromise = null;\n }\n\n /**\n * Unisce le informazioni di un dispositivo esistente con quelle di un nuovo scan.\n * Restituisce il dispositivo aggiornato se ci sono state modifiche, altrimenti `null`.\n */\n private mergeDeviceInfo(existing: DiscoveredDevice, updated: DiscoveredDevice): DiscoveredDevice | null {\n let hasChanges = false;\n\n // Aggiorna i campi mancanti o più recenti\n if (!existing.model && updated.model) {\n existing.model = updated.model;\n hasChanges = true;\n }\n if (!existing.uid && updated.uid) {\n existing.uid = updated.uid;\n hasChanges = true;\n }\n if (!existing.name && updated.name) {\n existing.name = updated.name;\n hasChanges = true;\n }\n if (!existing.firmwareVersion && updated.firmwareVersion) {\n existing.firmwareVersion = updated.firmwareVersion;\n hasChanges = true;\n }\n if (updated.httpPort && !existing.httpPort) {\n existing.httpPort = updated.httpPort;\n hasChanges = true;\n }\n if (updated.httpsPort && !existing.httpsPort) {\n existing.httpsPort = updated.httpsPort;\n hasChanges = true;\n }\n if (updated.supportsHttps !== undefined && existing.supportsHttps !== updated.supportsHttps) {\n existing.supportsHttps = updated.supportsHttps;\n hasChanges = true;\n }\n if (updated.httpAccessible !== undefined && existing.httpAccessible !== updated.httpAccessible) {\n existing.httpAccessible = updated.httpAccessible;\n hasChanges = true;\n }\n\n return hasChanges ? existing : null;\n }\n\n /**\n * Programma il prossimo scan.\n */\n private scheduleNextScan(): void {\n if (!this.isRunning) {\n return;\n }\n\n this.scanTimer = setTimeout(() => {\n this.scanTimer = null;\n if (this.isRunning) {\n this.performScan().finally(() => {\n // Programma il prossimo scan solo se siamo ancora in esecuzione\n if (this.isRunning) {\n this.scheduleNextScan();\n }\n });\n }\n }, this.options.scanIntervalMs);\n }\n}\n\n","/**\n * TypeScript types for Baichuan API responses and parameters.\n * Based on Reolink API documentation.\n */\n\nexport interface OsdChannel {\n enable: number;\n name: string;\n pos: string;\n}\n\nexport interface OsdTime {\n enable: number;\n pos: string;\n}\n\nexport interface OsdConfig {\n channel: number;\n osdChannel: OsdChannel;\n osdTime: OsdTime;\n watermark: number;\n bgcolor?: number;\n}\n\nexport interface AIDetectionState {\n alarm_state: number;\n support: number;\n}\n\nexport type AiKey = \"dog_cat\" | \"face\" | \"other\" | \"package\" | \"people\";\n\nexport interface AIState {\n channel: number;\n alarm_state?: number;\n support?: number;\n [key: string]: unknown; // Allow additional AI detection types\n}\n\nexport interface PtzPreset {\n id: number;\n name: string;\n}\n\nexport type PtzPosition = {\n pan?: number;\n tilt?: number;\n};\n\nexport type ZoomFocusTriplet = {\n minPos: number;\n maxPos: number;\n curPos: number;\n};\n\nexport type ZoomFocusStatus = {\n zoom?: ZoomFocusTriplet;\n focus?: ZoomFocusTriplet;\n};\n\nexport interface PtzCommand {\n action: \"start\" | \"stop\";\n command:\n | \"Left\"\n | \"Right\"\n | \"Up\"\n | \"Down\"\n | \"ZoomIn\"\n | \"ZoomOut\"\n | \"FocusNear\"\n | \"FocusFar\";\n speed?: number;\n /** Optional: how long to move before sending an automatic stop (ms). Set to 0 to disable auto-stop. */\n autoStopMs?: number;\n}\n\nexport interface BatteryInfo {\n batteryPercent?: number;\n /** Known values include: \"charging\", \"chargeComplete\", \"none\". */\n chargeStatus?: string;\n /** Charging source/port status, e.g. \"solarPanel\". */\n adapterStatus?: string;\n /** Low power flag (0/1). */\n lowPower?: number;\n /** Battery voltage (mV) when available. */\n voltage?: number;\n /** Battery current (mA) when available (can be negative while charging). */\n current?: number;\n /** Battery temperature (°C) when available. */\n temperature?: number;\n /** Battery version info when available (commonly 2). */\n batteryVersion?: number;\n sleeping?: boolean;\n channel?: number;\n}\n\n/**\n * Minimal per-channel device summary returned by `ReolinkBaichuanApi.getDevicesInfo()`.\n *\n * This is optimized for speed and returns only common identity + battery/doorbell hints.\n */\nexport interface ReolinkBaichuanDeviceSummary {\n channel: number;\n name?: string;\n uid?: string;\n /** Camera IP (best-effort via Baichuan GetNetworkInfo/GetGeneral). */\n ip?: string;\n /** Camera MAC address (best-effort via Baichuan GetNetworkInfo/GetGeneral). */\n mac?: string;\n /** Active link / link type when available (varies by firmware). */\n activeLink?: string;\n /** Channel state from cmd_id 145 push (e.g. connect/disconnect/none). */\n state?: string;\n /** Channel index from cmd_id 145 push (often 1-based device slot). */\n index?: number;\n /** Supported streams as reported by cmd_id 145 push (e.g. mainStream,subStream,externStream). */\n streamSupport?: string[];\n wifiState?: string;\n networkSegment?: string;\n changed?: boolean;\n abilityChanged?: boolean;\n online?: boolean;\n sleeping?: boolean;\n loginState?: string;\n updatedAtMs?: number;\n /** Model string (Baichuan <type>). */\n model?: string;\n /** True when the channel likely belongs to a multifocal/dual-lens device (best-effort by model). */\n isMultifocal?: boolean;\n /** Device serial number when available. */\n serialNumber?: string;\n /** Battery percentage (0-100) when available. */\n battery?: number;\n /** True when the channel is a battery camera (best-effort via SupportInfo). */\n isBattery?: boolean;\n /** True when the channel is a doorbell (best-effort via SupportInfo). */\n isDoorbell?: boolean;\n}\n\n/** Best-effort network identity for a host or a channel. */\nexport interface ReolinkBaichuanNetworkInfo {\n ip?: string;\n mac?: string;\n activeLink?: string;\n}\n\nexport type ReolinkNvrChannelInfo = {\n channel: number;\n /** Camera model string (Baichuan: <type>, CGI: typeInfo). */\n model?: string;\n /** Camera name (OSD/name). */\n name?: string;\n /** Camera UID (when available via NVR CGI). */\n uid?: string;\n /** Online flag (when available via NVR CGI). */\n online?: boolean;\n /** Sleep flag (when available via NVR CGI, common for battery cams). */\n sleep?: boolean;\n /** Firmware version (Baichuan: firmwareVersion, CGI: firmVer). */\n firmwareVersion?: string;\n /** Board info (CGI: boardInfo). */\n boardInfo?: string;\n /** Where the info came from for this channel. */\n source: \"baichuan\" | \"cgi\";\n};\n\nexport type ReolinkBaichuanPorts = Record<string, Record<string, number>>;\n\nexport type ReolinkBaichuanChannelInfo = {\n typeInfo?: string;\n firmVer?: string;\n firmwareVersion?: string;\n boardInfo?: string;\n pakSuffix?: string;\n name?: string;\n};\n\nexport type ReolinkBaichuanChannelIdentity = {\n channel: number;\n model: string;\n name: string;\n};\n\n/**\n * NVR/HUB grouping of channels that belong to the same physical device.\n *\n * Multifocal cameras typically appear as 2+ channels that share the same UID and/or serial number.\n */\nexport interface ReolinkNvrDeviceGroupSummary {\n /** Stable group key (usually uid:* or sn:*). */\n key: string;\n uid?: string;\n serialNumber?: string;\n name?: string;\n model?: string;\n channels: number[];\n /** True when the group likely represents a multi-channel (multifocal) device. */\n isMultifocal: boolean;\n /** Human-readable heuristic used for isMultifocal. */\n reason: string;\n}\n\nexport type ReolinkNvrDeviceGroupsResult = {\n channels: number[];\n groups: ReolinkNvrDeviceGroupSummary[];\n /** Map channel -> group key. */\n channelToGroup: Record<number, string>;\n};\n\nexport type SleepState = \"awake\" | \"sleeping\" | \"unknown\";\n\n/**\n * Best-effort sleep status inference.\n *\n * Note: for battery cameras there is no universally reliable, purely passive \"sleep\" flag.\n * This status is inferred without sending any request to the camera.\n */\nexport interface SleepStatus {\n state: SleepState;\n reason: string;\n lastRxAtMs?: number;\n idleMs?: number;\n}\n\nexport type WakeUpOptions = {\n /** Timeout per single attempt (default: 20000). */\n timeoutMs?: number;\n /** Number of attempts (default: 3). */\n attempts?: number;\n /** Delay after an attempt that \"unlocks\" the camera (default: 1500). */\n waitAfterWakeMs?: number;\n /** Delay between failed attempts (default: 1500). */\n backoffMs?: number;\n /**\n * If true, closes the connection and forces a reconnect before retrying.\n * Default: true for UDP (battery), false for TCP.\n */\n reconnect?: boolean;\n};\n\nexport type LastSleepProbe = {\n atMs: number;\n status: SleepStatus;\n};\n\nexport interface PirState {\n enabled: boolean;\n state?: {\n enable?: number;\n channel?: number;\n [key: string]: unknown;\n };\n}\n\nexport type SirenState = {\n enabled: boolean;\n};\n\nexport interface WhiteLedState {\n enabled: boolean;\n brightness?: number;\n}\n\nimport type { XmlJsonValue } from \"./utils/xml\";\n\n/** Public snapshot entry returned by `ReolinkBaichuanApi.getChannelInfoFromPushCache()`. */\nexport type ChannelPushCacheEntry = {\n name: string;\n uid: string;\n state: string;\n index?: number;\n streamSupport?: string[];\n wifiState?: string;\n networkSegment?: string;\n changed?: boolean;\n abilityChanged?: boolean;\n online?: boolean;\n sleeping?: boolean;\n loginState?: string;\n updatedAtMs?: number;\n};\n\n/** Internal live entry used to maintain cmd_id 145 push state. */\nexport type ChannelPushDataEntry = ChannelPushCacheEntry & {\n /** Lowercased state for convenience (e.g. \"connect\", \"none\"). */\n stateLower?: string;\n updatedAtMs: number;\n};\n\n// --------------------\n// PCAP-derived push cache (cmd_id 78/79/464/484/623/723)\n// --------------------\n\nexport type BaichuanCachedPush<T> = {\n updatedAtMs: number;\n value: T;\n};\n\nexport type BaichuanVideoInputPush = {\n channelId?: number;\n bright?: number;\n contrast?: number;\n saturation?: number;\n hue?: number;\n sharpen?: number;\n corridorAbility?: number;\n corridorMode?: string;\n};\n\nexport type BaichuanSerialPush = {\n channelId?: number;\n baudRate?: number;\n dataBit?: string;\n stopBit?: string;\n parity?: string;\n flowControl?: string;\n controlProtocol?: string;\n controlAddress?: number;\n};\n\nexport type BaichuanNetInfoPush = {\n netType?: string;\n signal?: number;\n};\n\nexport type BaichuanDingdongListPush = {\n maxPairNumber?: number;\n /** -1 commonly means \"all\" / host-level. */\n channel?: number;\n pairedCount?: number;\n pairedList?: XmlJsonValue;\n};\n\nexport type BaichuanSleepStatusPush = {\n /** Parsed from <sleep>0/1 when present. */\n sleep?: boolean;\n rawSleep?: number;\n /** Some firmwares use <statusList>... for per-channel statuses. */\n statusListPresent?: boolean;\n};\n\nexport type BaichuanCoordinatePointListPush = {\n count: number;\n points?: Array<{ x: number; y: number }>;\n};\n\nexport type BaichuanSettingsPushCacheEntry = {\n /** Map key (0-based) when known; -1 for host-level pushes. */\n channel: number;\n videoInput?: BaichuanCachedPush<BaichuanVideoInputPush>;\n serial?: BaichuanCachedPush<BaichuanSerialPush>;\n netInfo?: BaichuanCachedPush<BaichuanNetInfoPush>;\n dingdongList?: BaichuanCachedPush<BaichuanDingdongListPush>;\n sleepStatus?: BaichuanCachedPush<BaichuanSleepStatusPush>;\n coordinatePointList?: BaichuanCachedPush<BaichuanCoordinatePointListPush>;\n};\n\n// --------------------\n// PCAP-derived getters (cmd_id 44/54/81/115/116/146/208/574/...)\n// --------------------\n\nexport type BaichuanOsdDatetime = {\n channelId?: number;\n enable?: boolean;\n topLeftX?: number;\n topLeftY?: number;\n width?: number;\n height?: number;\n language?: string;\n};\n\nexport type BaichuanOsdChannelName = {\n channelId?: number;\n name?: string;\n enable?: boolean;\n topLeftX?: number;\n topLeftY?: number;\n enWatermark?: boolean;\n enBgcolor?: boolean;\n};\n\nexport type BaichuanGetOsdDatetimeResult = {\n osdDatetime?: BaichuanOsdDatetime;\n osdChannelName?: BaichuanOsdChannelName;\n};\n\nexport type BaichuanRecordCfg = {\n channelId?: number;\n cycle?: number;\n recordAbility?: number;\n smartRecord?: number;\n recordDelayTime?: number;\n preRecordTime?: number;\n packageTime?: number;\n cycleList?: number[];\n};\n\nexport type BaichuanRecordSchedule = {\n channelId?: number;\n enable?: boolean;\n typeScheduleList?: Array<{ type: string; valueTable: string }>;\n};\n\nexport type BaichuanWifiSignal = {\n signal?: number;\n};\n\nexport type BaichuanWifi = {\n protocol?: number;\n mode?: string;\n ssid?: string;\n /** Masked in many firmwares (e.g. ***********) */\n key?: string;\n channel?: number;\n isNVRSsid?: number;\n};\n\nexport type BaichuanLedState = {\n channelId?: number;\n ledVersion?: number;\n state?: string;\n lightState?: string;\n};\n\nexport type BaichuanSleepState = {\n sleep?: number;\n mode?: number;\n panPos?: number;\n tiltPos?: number;\n imageName?: string;\n};\n\nexport type BaichuanStreamEncodeTable = {\n type?: string;\n width?: number;\n height?: number;\n videoEncType?: number;\n videoEncTypeList?: number[];\n defaultFramerate?: number;\n defaultBitrate?: number;\n framerateTable?: number[];\n bitrateTable?: number[];\n defaultGop?: number;\n};\n\nexport type BaichuanStreamInfo = {\n channelBits?: number;\n encodeTables: BaichuanStreamEncodeTable[];\n};\n\nexport type BaichuanStreamInfoList = {\n streams: BaichuanStreamInfo[];\n};\n\nexport type AiTypesCacheEntry = {\n types?: string[];\n updatedAtMs: number;\n};\n\nexport type DeviceCapabilitiesCacheEntry = {\n result: DeviceCapabilitiesResult;\n cachedAtMs: number;\n};\n\nexport type NvrChannelsSummaryCacheEntry = {\n channels: number[];\n devices: ReolinkBaichuanDeviceSummary[];\n};\n\nexport type RecordingsCacheEntry = {\n recordings: RecordingFile[];\n cachedAt: number;\n /** TTL in milliseconds (default 5 minutes) */\n ttlMs: number;\n};\n\nexport type RecordingsQueueItem = {\n run: () => Promise<void>;\n};\n\nexport interface AudioAlarmParams {\n channel: number;\n alarm_mode?: \"times\" | \"manul\";\n times?: number;\n manual_switch?: number;\n}\n\nexport interface Events {\n channel?: number;\n ai?: AIState;\n motion?: {\n state?: number;\n [key: string]: unknown;\n };\n /** Doorbell/visitor notification (instant event). Present when detected. */\n visitor?: {\n detected?: boolean;\n timestamp?: number;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\nexport type StreamProfile = \"main\" | \"sub\" | \"ext\";\n\nexport type NativeVideoStreamVariant = \"default\" | \"autotrack\" | \"telephoto\";\n\nexport type VideoCodec = \"H.264\" | \"H.265\" | \"MJPEG\" | \"MPEG4\" | string;\n\nexport interface StreamMetadata {\n profile: StreamProfile;\n audio: number; // 0 or 1\n audioCodec: string;\n width: number;\n height: number;\n videoEncType: VideoCodec;\n videoEncTypeInt: number; // Internal encoding type integer\n frameRate: number; // FPS\n bitRate: number; // Bitrate in kbps\n}\n\n/**\n * Complete media stream options with all available metadata.\n * Includes RTSP, RTMP, and native Baichuan stream information.\n */\nexport interface ReolinkSupportedStream {\n // Basic identification\n name: string;\n id: string;\n container: \"rtsp\" | \"rtmp\" | \"rtp\";\n channel?: number; // undefined for composite streams (multifocal devices)\n profile: StreamProfile;\n /**\n * Underlying device stream name used by the transport.\n * For RTSP this maps to `/...Preview_<ch>_<streamName>` (e.g. main/sub/autotrack).\n * For RTMP this maps to `/bcs/channel<ch>_<streamName>.bcs`.\n */\n streamName?: string;\n /** Optional lens hint for multifocal devices (TrackMix/Duo). */\n lens?: \"wide\" | \"telephoto\" | \"composite\";\n /** Native-only: request a non-default logical stream (e.g. TrackMix tele on NVR). */\n nativeVariant?: NativeVideoStreamVariant;\n url: string; // URL without authentication credentials\n urlWithAuth: string; // URL with authentication credentials\n streamType?: number; // Stream type: 0 for main/ext, 1 for sub (RTMP)\n path?: string; // Stream path (e.g., /h264Preview_01_main, /bcs/channel0_main.bcs)\n port?: number; // Port number (RTSP/RTMP)\n metadata?: StreamMetadata; // Complete original stream metadata\n}\n\nexport type RtspCreateOptions = {\n listenHost?: string;\n listenPort?: number;\n path?: string;\n variant?: NativeVideoStreamVariant;\n};\n\nexport type ReolinkVideoStreamOptionsResult = {\n nativeStreams: ReolinkSupportedStream[];\n rtspStreams: ReolinkSupportedStream[];\n rtmpStreams: ReolinkSupportedStream[];\n};\n\nexport type VideoStreamOptionsCacheEntry = ReolinkVideoStreamOptionsResult;\n\nexport type RunAllDiagnosticsConsecutivelyResult = {\n runDir: string;\n zipPath: string;\n diagnosticsPath: string;\n streamsDir: string;\n};\n\nexport type RunMultifocalDiagnosticsConsecutivelyResult = {\n runDir: string;\n resultsPath: string;\n streamsDir: string;\n};\n\nexport interface ChannelStreamMetadata {\n channel: number;\n streams: StreamMetadata[];\n audioEnabled: boolean; // Overall audio enabled (AND of all streams)\n}\n\nexport interface MotionEvent {\n channel: number;\n state: boolean; // true = motion detected\n timestamp?: number;\n /** Origin of motion trigger when known (e.g. PIR-only cameras). */\n source?: \"md\" | \"pir\" | \"unknown\";\n}\n\nexport interface AIEvent {\n channel: number;\n type: \"people\" | \"vehicle\" | \"dog_cat\" | \"face\" | \"package\" | \"other\";\n detected: boolean;\n timestamp?: number;\n}\n\nexport interface ReolinkMotionNotification {\n channel: number;\n type: \"motion\";\n motion: MotionEvent;\n timestamp?: number;\n}\n\nexport interface ReolinkAiNotification {\n channel: number;\n type: \"ai\";\n ai: AIEvent;\n timestamp?: number;\n}\n\nexport interface ReolinkVisitorNotification {\n channel: number;\n type: \"visitor\";\n timestamp?: number;\n}\n\nexport interface ReolinkDayNightNotification {\n channel: number;\n type: \"daynight\";\n timestamp?: number;\n}\n\nexport type ReolinkEvent =\n | ReolinkMotionNotification\n | ReolinkAiNotification\n | ReolinkVisitorNotification\n | ReolinkDayNightNotification;\n\nexport type ReolinkSimpleEventType =\n | \"motion\"\n | \"doorbell\"\n | \"people\"\n | \"vehicle\"\n | \"animal\"\n | \"face\"\n | \"package\"\n | \"daynight\"\n | \"sleeping\"\n | \"awake\"\n | \"online\"\n | \"offline\"\n | \"other\";\n\nexport interface ReolinkSimpleEvent {\n type: ReolinkSimpleEventType;\n channel: number;\n timestamp: number;\n}\n\nexport interface TwoWayAudioConfig {\n channel: number;\n enabled: boolean;\n mode?: \"mixAudioStream\" | string;\n}\n\nexport interface TalkAudioConfig {\n priority?: number;\n audioType: string;\n sampleRate: number;\n samplePrecision: number;\n lengthPerEncoder: number;\n soundTrack: string;\n}\n\nexport interface TalkAbility {\n version?: string;\n duplexList: string[];\n audioStreamModeList: string[];\n audioConfigList: TalkAudioConfig[];\n}\n\nexport interface TalkConfig {\n channel: number;\n duplex: string;\n audioStreamMode: string;\n audioConfig: TalkAudioConfig;\n}\n\nexport interface TalkSessionInfo {\n channel: number;\n audioConfig: TalkAudioConfig;\n /** ADPCM bytes per block excluding the 4-byte predictor state. */\n blockSize: number;\n /** ADPCM bytes per block including the 4-byte predictor state. */\n fullBlockSize: number;\n}\n\nexport interface TalkSession {\n readonly info: TalkSessionInfo;\n /**\n * Enqueue ADPCM DVI4 bytes (raw blocks, including the 4-byte predictor header per block).\n * The session will packetize into BcMedia ADPCM and pace delivery.\n */\n sendAudio(adpcm: Buffer): Promise<void>;\n /** Flush remaining audio and stop the talk session. */\n stop(): Promise<void>;\n}\n\n/**\n * Device ability/capability information for a specific channel or host.\n *\n * Keys are capability names (e.g., \"preview_rw\", \"control_rw\", \"motion_rw\", \"reboot_rw\").\n * Values are:\n * - 1 = capability is supported (typically with _rw suffix for read-write, _ro for read-only)\n * - 0 or undefined = capability is not supported\n * - string = metadata values (e.g., \"userName\")\n */\nexport type AbilityInfo = Record<string, number | string | undefined>;\n\n/**\n * Complete device abilities structure returned by getAbilityInfo.\n *\n * - Channel numbers (0, 1, 2, etc.): Channel-specific abilities\n * - \"Host\": Host-level/system abilities\n */\nexport type DeviceAbilities = Partial<Record<number | \"Host\", AbilityInfo>>;\n\nexport interface SupportItem {\n chnID: number;\n ptzType?: number;\n ptzPreset?: number;\n ptzPatrol?: number;\n ptzTattern?: number;\n ptzControl?: number;\n rfCfg?: number;\n noAudio?: number;\n autoFocus?: number;\n videoClip?: number;\n battery?: number;\n ispCfg?: number;\n osdCfg?: number;\n batAnalysis?: number;\n dynamicReso?: number;\n audioVersion?: number;\n ledCtrl?: number;\n motion?: number;\n [key: string]: number | string | undefined;\n}\n\nexport interface SupportInfo {\n items: SupportItem[];\n ptzMode?: string;\n\n IOInputPortNum?: number;\n IOOutputPortNum?: number;\n diskNum?: number;\n channelNum?: number;\n audioNum?: number;\n ptzCfg?: number;\n B485?: number;\n autoUpdate?: number;\n pushAlarm?: number;\n ftp?: number;\n ftpTest?: number;\n email?: number;\n wifi?: number;\n record?: number;\n wifiTest?: number;\n rtsp?: number;\n onvif?: number;\n audioTalk?: number;\n\n // Preserve unknown fields when useful.\n [key: string]: unknown;\n}\n\nexport interface DeviceCapabilities {\n channel: number;\n /** Lower-cased ptzMode when available (e.g. \"pt\", \"ptz\", \"none\"). */\n ptzMode?: string;\n hasPan: boolean;\n hasTilt: boolean;\n hasZoom: boolean;\n hasPresets: boolean;\n hasPtz: boolean;\n hasBattery: boolean;\n hasIntercom: boolean;\n hasSiren: boolean;\n hasFloodlight: boolean;\n hasPir: boolean;\n /** True when device reports doorbell support via support.items[].doorbellVersion. */\n isDoorbell: boolean;\n /** True when device supports autotracking (smartTrack in AiCfg). */\n hasAutotracking: boolean;\n}\n\nexport type DeviceObjectType = string;\n\nexport interface DeviceSupportFlags {\n rtsp?: boolean;\n onvif?: boolean;\n wifi?: boolean;\n record?: boolean;\n ftp?: boolean;\n email?: boolean;\n pushAlarm?: boolean;\n audioTalk?: boolean;\n}\n\nexport interface DeviceCapabilitiesDebugInfo {\n channel: number;\n channelId1Based: number;\n transport: \"tcp\" | \"udp\";\n encryptionKind: \"none\" | \"bc\" | \"aes\" | \"full_aes\";\n loggedIn: boolean;\n subscribed: boolean;\n abilitiesAvailable: boolean;\n supportAvailable: boolean;\n abilityMergedKeyCount?: number;\n supportItemCount?: number;\n /** Whether the device is detected as NVR/Hub */\n isNvr?: boolean;\n /** lightType from SupportItem (0=no light, 1=IR only, >=2=floodlight) */\n lightType?: number;\n /** ledCtrl bitmask from SupportItem (>0 indicates LED control capability) */\n ledCtrl?: number;\n /** ptzType from SupportItem */\n ptzType?: number;\n /** supportVolume from SupportItem (indicates siren support) */\n supportVolume?: number;\n /** supportPirSch from SupportItem (indicates PIR support) */\n supportPirSch?: number;\n /** Selected SupportItem chnID */\n supportItemChnID?: number;\n}\n\nexport interface DeviceCapabilitiesResult {\n abilities?: DeviceAbilities;\n support?: SupportInfo;\n capabilities: DeviceCapabilities;\n presets?: PtzPreset[];\n objects?: DeviceObjectType[];\n features?: DeviceSupportFlags;\n debug?: DeviceCapabilitiesDebugInfo;\n}\n\nexport type RecordingStreamType = \"mainStream\" | \"subStream\";\n\nexport type RecordingDevType = \"cam\" | \"hub\";\nexport type RecordingVodStreamHint = \"main\" | \"sub\" | \"unknown\";\n\nexport interface RecordingVodFlags {\n aiPerson?: boolean;\n aiVehicle?: boolean;\n aiAnimal?: boolean;\n aiFace?: boolean;\n aiOther?: boolean;\n motion?: boolean;\n schedule?: boolean;\n doorbell?: boolean;\n rf?: boolean;\n package?: boolean;\n}\n\nexport interface ParsedRecordingFileName {\n baseName: string;\n ext: string;\n streamHint: RecordingVodStreamHint;\n version: number;\n devType: RecordingDevType;\n start: Date;\n end: Date;\n durationMs: number;\n /** Frame rate extracted from filename hex flags (if available) */\n framerate?: number;\n /** File size in bytes extracted from filename (last hex field before extension) */\n sizeBytes?: number;\n flags?: RecordingVodFlags;\n rawFlags?: Record<string, number>;\n animalTypeRaw?: string;\n widthRaw?: string;\n heightRaw?: string;\n}\n\nexport interface RecordingFile {\n /** Camera-provided recording identifier (often a filename/path, e.g. \"00_YYYYMMDDHHMMSS\"). */\n fileName: string;\n /** Optional human-friendly name when provided separately (e.g. FileInfoList <name>). */\n name?: string;\n /** Optional full path/identifier when provided separately (e.g. FileInfoList <Id>). */\n id?: string;\n /** Optional size when provided by the camera (bytes). */\n sizeBytes?: number;\n /** Optional recordType when provided (e.g. md, people, sched, manual...). */\n recordType?: string;\n /** Optional start time when provided as YYYY/MM/DD etc; best-effort parsing may be absent. */\n startTime?: Date;\n /** Optional end time when provided. */\n endTime?: Date;\n\n /** Parsed metadata extracted from the file name when it matches known Reolink VOD patterns. */\n parsedFileName?: ParsedRecordingFileName;\n\n /** Detection classes for this recording (e.g. person, vehicle, animal, face, motion, doorbell, package) */\n detectionClasses?: RecordingDetectionClass[];\n}\n\n/**\n * A RecordingFile associated with an explicit logical channel.\n *\n * Useful for NVR/Hub-style listings where you query multiple channels and\n * want to keep the channel number alongside each returned file/event.\n */\nexport interface ChannelRecordingFile extends RecordingFile {\n channel: number;\n /** Optional UID used for the request (when known). */\n uid?: string;\n}\n\nexport type RecordingDetectionClass =\n | \"person\"\n | \"vehicle\"\n | \"animal\"\n | \"face\"\n | \"package\"\n | \"doorbell\"\n | \"rf\"\n | \"other\"\n | \"motion\"\n | \"schedule\";\n\nexport interface DownloadRecordingParams {\n channel: number;\n uid?: string;\n /** Recording identifier (usually one of the `fileName` returned by getVideoclips). */\n fileName: string;\n timeoutMs?: number;\n}\n\nexport type RecordingPlaybackUrls = {\n /** RTMP VOD URL for playback/export. */\n rtmpVodUrl: string;\n};\n\nexport type PlaybackSnapshotStreamInfo = {\n width?: number;\n height?: number;\n frameRate?: number;\n};\n\nexport type VideoclipThumbnailResult = {\n /** Raw I-frame data (H.264 or H.265) */\n frame: Buffer;\n /** Video encoding type detected from frame header */\n encoding: string;\n /** Frame length in bytes */\n frameLength: number;\n /** Frame timestamp (Unix seconds) if available */\n frameTime?: number;\n /** Stream metadata from header */\n streamInfo: PlaybackSnapshotStreamInfo;\n};\n\n/**\n * Detailed information about channel capabilities for dual lens models.\n */\nexport interface DualLensChannelInfo {\n /** Channel number (0-based) */\n channel: number;\n /** Indicates whether this channel supports pan */\n hasPan: boolean;\n /** Indicates whether this channel supports tilt */\n hasTilt: boolean;\n /** Indicates whether this channel supports zoom */\n hasZoom: boolean;\n /** Indicates whether this channel supports motion detection */\n hasMotion: boolean;\n /** Indicates whether this channel supports intercom (two-way audio) */\n hasIntercom: boolean;\n /** Indicates whether this channel supports PTZ presets */\n hasPresets: boolean;\n /** Channel type: \"wide\" for wide-angle lens, \"telephoto\" for telephoto lens */\n lensType?: \"wide\" | \"telephoto\" | undefined;\n /** Which Native variant maps to this lens (default=wide; autotrack/telephoto=tele lens depending on context). */\n variantType?: NativeVideoStreamVariant;\n /** Available streams for this channel */\n availableStreams: {\n /** RTSP stream available */\n rtsp: boolean;\n /** RTMP stream available */\n rtmp: boolean;\n /** Native Baichuan stream available */\n native: boolean;\n };\n}\n\n/**\n * Result of dual lens channel analysis.\n */\nexport interface DualLensChannelAnalysis {\n /** Indicates whether the device is a dual lens model */\n isDualLens: boolean;\n /** Dual lens model type: \"dual_motion\" (Duo) or \"single_motion\" (TrackMix) */\n dualLensType?: \"dual_motion\" | \"single_motion\" | undefined;\n /** Device model */\n model?: string | undefined;\n /** Total number of available stream channels */\n streamChannelCount?: number | undefined;\n /** Total number of logical channels */\n logicalChannelCount?: number | undefined;\n /** Detailed information for each channel */\n channels: DualLensChannelInfo[];\n /** Maps each capability to the list of channel numbers (0-based) that support it.\n * Use this to determine which channels to send commands to.\n */\n capabilityChannels: {\n /** Channel numbers that support pan */\n pan: number[];\n /** Channel numbers that support tilt */\n tilt: number[];\n /** Channel numbers that support zoom */\n zoom: number[];\n /** Channel numbers that support motion detection */\n motion: number[];\n /** Channel numbers that support intercom (two-way audio) */\n intercom: number[];\n /** Channel numbers that support PTZ presets */\n presets: number[];\n };\n}\n\n// ---------------------------------------------------------------------------\n// Recording download result types\n// ---------------------------------------------------------------------------\n\n/**\n * Video codec type detected from BcMedia stream.\n */\nexport type RecordingVideoCodec = \"H264\" | \"H265\";\n\n/**\n * Audio codec type detected from BcMedia stream.\n */\nexport type RecordingAudioCodec = \"Aac\" | \"Adpcm\";\n\n/**\n * Statistics returned by getRecordingVideo() about the demuxed/muxed recording.\n */\nexport interface GetRecordingVideoStats {\n /** Total bytes received from camera */\n bytesIn: number;\n /** Total video bytes after demuxing */\n videoBytesOut: number;\n /** Total audio bytes after demuxing */\n audioBytesOut: number;\n /** Number of video packets */\n videoPackets: number;\n /** Number of audio packets */\n audioPackets: number;\n /** Number of keyframes */\n keyframes: number;\n /** Calculated frames per second */\n fps: number;\n /** Duration in seconds */\n durationSeconds: number;\n /** Video codec detected */\n videoCodec: RecordingVideoCodec;\n /** Audio codec detected (null if no audio) */\n audioCodec: RecordingAudioCodec | null;\n /** Whether audio was present */\n hasAudio: boolean;\n}\n\n/**\n * Result of getRecordingVideo() - a fully muxed MP4 with stats.\n */\nexport interface GetRecordingVideoResult {\n /** MP4 file with video and audio muxed together */\n mp4: Buffer;\n /** Statistics about the demuxing/muxing process */\n stats: GetRecordingVideoStats;\n}\n\n/**\n * Parameters for getVideoclips() recording search.\n */\nexport interface GetVideoclipsParams {\n /** Channel number (0-based). Optional for standalone cameras, required for NVR. */\n channel?: number;\n /** Start time for search */\n start: Date;\n /** End time for search */\n end: Date;\n /** Stream type. Default: \"subStream\" */\n streamType?: RecordingStreamType;\n /** Comma-separated record types. Default includes all types. */\n recordType?: string;\n /** Explicit UID (skip auto-discovery if provided) */\n uid?: string;\n /** Per-request timeout in ms. Default: 15000 */\n timeoutMs?: number;\n /** Max pagination iterations. Default: 50 */\n maxIterations?: number;\n}\n\n// ============================================================================\n// Typed API Response Interfaces (extracted from XML responses)\n// These represent the actual content without the outer <body> wrapper\n// ============================================================================\n\n/**\n * AudioTask configuration - controls siren/alarm on motion detection.\n * Retrieved via cmdId=232 (GET) and set via cmdId=231 (SET).\n */\nexport interface AudioTaskConfig {\n body?: {\n AudioTask?: {\n channelId?: number;\n /** 0 = disabled, 1 = enabled */\n enable?: number;\n typeScheduleList?: {\n item?: Array<{\n type?: string;\n valueTable?: string;\n }>;\n };\n };\n };\n}\n\n/**\n * Audio configuration settings (getAudioCfg response).\n */\nexport interface AudioCfgConfig {\n body?: {\n AudioCfg?: {\n channelId?: number;\n timeout?: number;\n audioSelect?: number;\n volume?: number;\n preAlarm?: number;\n audioListId?: number;\n visitorLoudspeaker?: number;\n };\n };\n}\n\n/**\n * Day/Night threshold configuration.\n */\nexport interface DayNightThresholdConfig {\n body?: {\n DayNightThreshold?: {\n channelId?: number;\n threshold?: string;\n stat?: string;\n thresholdval?: {\n min?: number;\n max?: number;\n cur?: number;\n };\n };\n };\n}\n\n/**\n * AI denoise configuration.\n */\nexport interface AiDenoiseConfig {\n body?: {\n AiDenoise?: {\n channelId?: number;\n enable?: number;\n level?: number;\n };\n };\n}\n\n/**\n * Recording encryption configuration.\n */\nexport interface RecEncConfig {\n body?: {\n RecEnc?: {\n enable?: number;\n pwdValid?: number;\n };\n };\n}\n\n/**\n * AI configuration - includes autotracking (smartTrack) settings.\n * Retrieved via cmdId=299 (GET) and set via cmdId=300 (SET).\n */\nexport interface AiConfig {\n body?: {\n AiCfg?: {\n channelId?: number;\n /** 0 = disabled, 1 = enabled - controls autotracking */\n smartTrack?: number;\n /** Tracking mode (0=normal, 1=digital, etc.) */\n smartTrackMode?: number;\n smartTrackModeAbility?: number;\n /** Comma-separated detection types (e.g. \"people,vehicle,dog_cat\") */\n detectType?: string;\n /** Comma-separated tracking types */\n smartTrackType?: string;\n smartTrackPt?: number;\n smartTrackObjectStopDelay?: number;\n smartTrackObjectDisappearDelay?: number;\n cryDetectLevel?: number;\n cryDetectAbility?: number;\n trackPriorities?: {\n item?: string[];\n };\n };\n };\n}\n\n/**\n * Floodlight task configuration - controls floodlight on motion.\n * Retrieved via cmdId=289 (GET) and set via cmdId=290 (SET).\n */\nexport interface FloodlightTaskConfig {\n body?: {\n FloodlightTask?: {\n channelId?: number | undefined;\n /** 0 = disabled, 1 = enabled - controls floodlight on motion */\n alarmMode?: number | undefined;\n enable?: number | undefined;\n brightness_cur?: number | undefined;\n duration?: number | undefined;\n detectType?: string | undefined;\n };\n };\n}\n\n/**\n * White LED state configuration.\n */\nexport interface WhiteLedConfig {\n body?: {\n WhiteLed?: {\n channelId?: number | undefined;\n /** 0 = off, 1 = on */\n state?: number | undefined;\n /** Brightness level */\n bright?: number | undefined;\n /** LED mode */\n mode?: number | undefined;\n /** Light state (for some camera models) */\n LightState?: number | undefined;\n };\n };\n}\n\n/**\n * PIR sensor configuration.\n */\nexport interface PirConfig {\n body?: {\n PirInfo?: {\n channelId?: number | undefined;\n /** 0 = disabled, 1 = enabled */\n enable?: number | undefined;\n sensitivity?: number | undefined;\n scheduleEnable?: number | undefined;\n };\n };\n}\n\n// ============================================================================\n// Additional API Response Interfaces\n// ============================================================================\n\n/**\n * Motion alarm configuration (getMotionAlarm response).\n * cmdId=46 (GetMdAlarm)\n */\nexport interface MotionAlarmConfig {\n body?: {\n MdAlarm?: {\n channelId?: number | undefined;\n sensInfoNew?: {\n enable?: number | undefined;\n sensitivity?: number | undefined;\n alarmType?: number | undefined;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * AI alarm/detection configuration (getAiAlarmRaw response).\n * cmdId=342 (GetAiAlarm)\n */\nexport interface AiAlarmConfig {\n body?: {\n AiAlarm?: {\n channelId?: number | undefined;\n chn?: number | undefined;\n type?: string | undefined;\n enabled?: number | undefined;\n sensitivity?: number | undefined;\n trackType?: string | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Video input configuration.\n * cmdId=75 (GetVideoInput)\n */\nexport interface VideoInputConfig {\n body?: {\n VideoInput?: {\n channelId?: number | undefined;\n bright?: number | undefined;\n contrast?: number | undefined;\n saturation?: number | undefined;\n hue?: number | undefined;\n irCutSwap?: number | undefined;\n dayNight?: string | undefined;\n [key: string]: unknown;\n };\n InputAdvanceCfg?: {\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * System general configuration.\n * cmdId=77 (GetSystemGeneral)\n */\nexport interface SystemGeneralConfig {\n body?: {\n SystemGeneral?: {\n timeZone?: number | undefined;\n deviceName?: string | undefined;\n language?: string | undefined;\n dstMode?: number | undefined;\n [key: string]: unknown;\n };\n Norm?: {\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Device support/capability flags.\n * cmdId=78 (GetSupport)\n */\nexport interface SupportConfig {\n body?: {\n Support?: {\n ptzMode?: string | undefined;\n channelNum?: number | undefined;\n wifi?: number | undefined;\n rtsp?: number | undefined;\n rtmp?: number | undefined;\n onvif?: number | undefined;\n email?: number | undefined;\n ftp?: number | undefined;\n push?: number | undefined;\n cloudStorage?: number | undefined;\n ledCtrl?: number | undefined;\n audioAlarm?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Siren status information.\n * cmdId=270 (GetSirenStatus)\n */\nexport interface SirenStatusConfig {\n body?: {\n SirenStatusList?: {\n channelId?: number | undefined;\n status?: number | undefined;\n playing?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * FTP task configuration.\n */\nexport interface FtpTaskConfig {\n body?: {\n FtpTask?: {\n channelId?: number | undefined;\n enable?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Email task configuration.\n */\nexport interface EmailTaskConfig {\n body?: {\n EmailTask?: {\n channelId?: number | undefined;\n enable?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * HDD info list response.\n */\nexport interface HddInfoListConfig {\n body?: {\n HddInfoList?: {\n itemNum?: number | undefined;\n item?: Array<{\n id?: number | undefined;\n size?: number | undefined;\n used?: number | undefined;\n [key: string]: unknown;\n }>;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Timelapse configuration.\n */\nexport interface TimelapseCfgConfig {\n body?: {\n TimelapseCfg?: {\n channelId?: number | undefined;\n enable?: number | undefined;\n interval?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Access user list response.\n */\nexport interface AccessUserListConfig {\n body?: {\n AccessUserList?: {\n itemNum?: number | undefined;\n item?: Array<{\n userName?: string | undefined;\n level?: number | undefined;\n [key: string]: unknown;\n }>;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Online user list response.\n */\nexport interface OnlineUserListConfig {\n body?: {\n OnlineUserList?: {\n itemNum?: number | undefined;\n item?: Array<{\n userName?: string | undefined;\n ip?: string | undefined;\n [key: string]: unknown;\n }>;\n [key: string]: unknown;\n };\n };\n}\n\n// ============================================================================\n// Videoclip Client Detection Utilities\n// ============================================================================\n\n/**\n * Client information extracted from HTTP request headers.\n * Used to determine optimal video delivery format.\n */\nexport type VideoclipClientInfo = {\n userAgent: string | undefined;\n accept: string | undefined;\n range: string | undefined;\n secChUa: string | undefined;\n secChUaMobile: string | undefined;\n secChUaPlatform: string | undefined;\n};\n\n/**\n * Videoclip delivery mode.\n * - `passthrough`: Copy codec as-is (H.264 or H.265)\n * - `transcode-h264`: Transcode H.265 to H.264 for compatibility\n */\nexport type VideoclipTranscodeMode = \"passthrough\" | \"transcode-h264\";\n\n/**\n * Result of videoclip mode decision.\n */\nexport type VideoclipModeDecision = {\n mode: VideoclipTranscodeMode;\n reason: string;\n clientInfo: VideoclipClientInfo;\n};\n\n/**\n * Extract client info from HTTP request headers.\n */\nexport function getVideoclipClientInfo(\n headers: Record<string, string | string[] | undefined>,\n): VideoclipClientInfo {\n const getHeader = (key: string): string | undefined => {\n const val =\n headers[key] ?? headers[key.toLowerCase()] ?? headers[key.toUpperCase()];\n return Array.isArray(val) ? val[0] : val;\n };\n\n return {\n userAgent: getHeader(\"user-agent\") ?? getHeader(\"User-Agent\"),\n accept: getHeader(\"accept\") ?? getHeader(\"Accept\"),\n range: getHeader(\"range\") ?? getHeader(\"Range\"),\n secChUa: getHeader(\"sec-ch-ua\") ?? getHeader(\"Sec-CH-UA\"),\n secChUaMobile:\n getHeader(\"sec-ch-ua-mobile\") ?? getHeader(\"Sec-CH-UA-Mobile\"),\n secChUaPlatform:\n getHeader(\"sec-ch-ua-platform\") ?? getHeader(\"Sec-CH-UA-Platform\"),\n };\n}\n\n/**\n * Determine if H.265 should be transcoded to H.264 based on client capabilities.\n *\n * Decision logic:\n * - iOS devices (Safari): Need transcoding (no native H.265 in <video> without HLS)\n * - macOS Safari: Supports H.265 natively\n * - Chrome/Edge: Limited H.265 support, safer to transcode\n * - Firefox: No H.265 support, needs transcoding\n * - Android: Variable support, transcode for safety\n *\n * @param headers - HTTP request headers\n * @param forceMode - Optional override: \"passthrough\" or \"transcode-h264\"\n * @returns Decision with mode, reason, and client info\n */\nexport function decideVideoclipTranscodeMode(\n headers: Record<string, string | string[] | undefined>,\n forceMode?: VideoclipTranscodeMode,\n): VideoclipModeDecision {\n const clientInfo = getVideoclipClientInfo(headers);\n\n // If force mode is specified, use it\n if (forceMode) {\n return {\n mode: forceMode,\n reason: `forced: ${forceMode}`,\n clientInfo,\n };\n }\n\n const ua = (clientInfo.userAgent ?? \"\").toLowerCase();\n const platform = (clientInfo.secChUaPlatform ?? \"\")\n .toLowerCase()\n .replace(/\"/g, \"\");\n\n // iOS devices (iPhone, iPad, iPod) - no native H.265 in <video> element\n const isIos = /iphone|ipad|ipod/.test(ua);\n if (isIos) {\n return {\n mode: \"transcode-h264\",\n reason: \"iOS device detected - no native H.265 support in <video>\",\n clientInfo,\n };\n }\n\n // Firefox - no H.265 support at all\n const isFirefox = ua.includes(\"firefox\");\n if (isFirefox) {\n return {\n mode: \"transcode-h264\",\n reason: \"Firefox detected - no H.265 support\",\n clientInfo,\n };\n }\n\n // Android - variable H.265 support, safer to transcode\n const isAndroid = ua.includes(\"android\") || platform === \"android\";\n if (isAndroid) {\n return {\n mode: \"transcode-h264\",\n reason: \"Android device detected - variable H.265 support\",\n clientInfo,\n };\n }\n\n // Chrome/Edge on non-Mac - limited H.265 support\n const isChromium = ua.includes(\"chrome\") || ua.includes(\"edg\");\n const isMac = ua.includes(\"mac os\") || platform === \"macos\";\n if (isChromium && !isMac) {\n return {\n mode: \"transcode-h264\",\n reason: \"Chrome/Edge on non-Mac detected - limited H.265 support\",\n clientInfo,\n };\n }\n\n // macOS Safari or Chrome on Mac - good H.265 support via VideoToolbox\n if (isMac) {\n return {\n mode: \"passthrough\",\n reason: \"macOS detected - native H.265 hardware decoding available\",\n clientInfo,\n };\n }\n\n // Default: transcode for safety\n return {\n mode: \"transcode-h264\",\n reason: \"Unknown client - transcoding for compatibility\",\n clientInfo,\n };\n}\n","import http from \"node:http\";\nimport { spawn } from \"node:child_process\";\n\nimport type { BaichuanClientOptions } from \"../../client/BaichuanClient\";\nimport type { StreamProfile } from \"./types\";\nimport { HlsSessionManager } from \"./HlsSessionManager\";\nimport { ReolinkBaichuanApi } from \"./ReolinkBaichuanApi\";\n\ntype NativeVariantParam = \"default\" | \"autotrack\" | \"telephoto\";\n\nexport type BaichuanEndpointsServerOptions = {\n /** Port to listen on. */\n listenPort: number;\n /** Host to bind to (default: 127.0.0.1). */\n listenHost?: string;\n\n /** Connection options for Baichuan (host/username/password/transport/etc). */\n baichuan: BaichuanClientOptions;\n\n /** Where the internal RTSP servers should bind (default: 127.0.0.1). */\n rtspListenHost?: string;\n};\n\nfunction parseIntParam(v: string | null, def: number): number {\n if (v == null) return def;\n const n = Number.parseInt(v, 10);\n return Number.isFinite(n) ? n : def;\n}\n\nfunction parseBoolParam(v: string | null, def: boolean): boolean {\n if (v == null) return def;\n const s = v.trim().toLowerCase();\n if (s === \"1\" || s === \"true\" || s === \"yes\" || s === \"y\") return true;\n if (s === \"0\" || s === \"false\" || s === \"no\" || s === \"n\") return false;\n return def;\n}\n\nfunction parseProfile(v: string | null): StreamProfile {\n const p = (v ?? \"sub\").trim();\n if (p === \"main\" || p === \"sub\" || p === \"ext\") return p;\n throw new Error(\"Invalid profile (must be main, sub, or ext)\");\n}\n\nfunction parseNativeVariant(v: string | null): NativeVariantParam {\n const s = (v ?? \"default\").trim().toLowerCase();\n if (s === \"\" || s === \"default\") return \"default\";\n if (s === \"autotrack\") return \"autotrack\";\n if (s === \"telephoto\") return \"telephoto\";\n throw new Error(\"Invalid variant (must be default, autotrack, or telephoto)\");\n}\n\nfunction parseDateParam(v: string | null): Date {\n if (!v) throw new Error(\"Missing time\");\n const d = new Date(v);\n if (Number.isNaN(d.getTime()))\n throw new Error(\"Invalid time (expected ISO string)\");\n return d;\n}\n\nfunction parseDateParamNamed(name: string, v: string | null): Date {\n if (!v) throw new Error(`Missing ${name}`);\n const d = new Date(v);\n if (Number.isNaN(d.getTime()))\n throw new Error(`Invalid ${name} (expected ISO string)`);\n return d;\n}\n\n/**\n * Minimal HTTP server exposing two endpoints:\n * - `GET /stream?channel=0&profile=main` -> returns a local RTSP URL (Baichuan socket -> RTSP)\n * - `GET /download?channel=0&uid=...&fileName=...` -> downloads a recording via Baichuan socket\n */\nexport function createBaichuanEndpointsServer(\n opts: BaichuanEndpointsServerOptions,\n): http.Server {\n const api = new ReolinkBaichuanApi({\n ...opts.baichuan,\n });\n\n const hlsManager = new HlsSessionManager(api, {\n logger: console as any,\n sessionTtlMs: 60_000,\n cleanupIntervalMs: 5_000,\n });\n\n const listenHost = opts.listenHost ?? \"127.0.0.1\";\n const rtspListenHost = opts.rtspListenHost ?? \"127.0.0.1\";\n\n // Cache servers by channel/profile.\n const rtspServers = new Map<string, { url: string }>();\n\n const server = http.createServer(async (req, res) => {\n try {\n if (!req.url) {\n res.statusCode = 400;\n res.end(\"Bad Request\");\n return;\n }\n\n const u = new URL(req.url, `http://${listenHost}:${opts.listenPort}`);\n\n if (req.method !== \"GET\") {\n res.statusCode = 405;\n res.setHeader(\"Allow\", \"GET\");\n res.end(\"Method Not Allowed\");\n return;\n }\n\n if (u.pathname === \"/stream\") {\n const channel = parseIntParam(u.searchParams.get(\"channel\"), 0);\n const profile = parseProfile(u.searchParams.get(\"profile\"));\n const variant = parseNativeVariant(u.searchParams.get(\"variant\"));\n if (!Number.isFinite(channel) || channel < 0) {\n res.statusCode = 400;\n res.end(\"Invalid channel\");\n return;\n }\n\n const key = `${channel}:${profile}:${variant}`;\n const cached = rtspServers.get(key);\n if (cached) {\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ rtspUrl: cached.url }));\n return;\n }\n\n const rtsp = await api.createRtspStream(channel, profile, {\n listenHost: rtspListenHost,\n listenPort: 0,\n path: `/stream/${channel}/${profile}${variant === \"default\" ? \"\" : `/${variant}`}`,\n ...(variant === \"default\" ? {} : { variant }),\n });\n\n const rtspUrl = rtsp.getRtspUrl();\n rtspServers.set(key, { url: rtspUrl });\n\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ rtspUrl }));\n return;\n }\n\n if (u.pathname === \"/hls\") {\n const channel = parseIntParam(u.searchParams.get(\"channel\"), 0);\n const fileName = (u.searchParams.get(\"fileName\") ?? \"\").trim();\n const deviceId = (u.searchParams.get(\"deviceId\") ?? \"anon\").trim();\n const isNvr = parseBoolParam(u.searchParams.get(\"isNvr\"), false);\n const transcode = parseBoolParam(u.searchParams.get(\"transcode\"), true);\n const hlsSegmentDuration = parseIntParam(\n u.searchParams.get(\"hlsSegmentDuration\"),\n 2,\n );\n const hlsPath = (u.searchParams.get(\"hls\") ?? \"playlist.m3u8\").trim();\n\n if (!fileName) {\n res.statusCode = 400;\n res.end(\"Missing fileName\");\n return;\n }\n\n const sessionKey = `hls:${deviceId}:ch${channel}:${fileName}`;\n const exclusiveKeyPrefix = `hls:${deviceId}:ch${channel}:`;\n\n // Preserve full request URL for playlist URL rewriting.\n const requestUrl = `http://${listenHost}:${opts.listenPort}${u.pathname}${u.search}`;\n\n const result = await hlsManager.handleRequest({\n sessionKey,\n hlsPath,\n requestUrl,\n exclusiveKeyPrefix,\n createSession: () => ({\n channel,\n fileName,\n isNvr,\n deviceId,\n transcodeH265ToH264: transcode,\n hlsSegmentDuration,\n }),\n });\n\n res.statusCode = result.statusCode;\n for (const [k, v] of Object.entries(result.headers)) {\n res.setHeader(k, v);\n }\n res.end(result.body);\n return;\n }\n\n if (u.pathname === \"/download\") {\n const channel = parseIntParam(u.searchParams.get(\"channel\"), 0);\n const uid = (u.searchParams.get(\"uid\") ?? \"\").trim();\n const fileName = (u.searchParams.get(\"fileName\") ?? \"\").trim();\n const timeoutMs = parseIntParam(\n u.searchParams.get(\"timeoutMs\"),\n 120_000,\n );\n\n if (!uid) {\n res.statusCode = 400;\n res.end(\"Missing uid\");\n return;\n }\n if (!fileName) {\n res.statusCode = 400;\n res.end(\"Missing fileName\");\n return;\n }\n\n const buf = await api.downloadRecording({\n channel,\n uid,\n fileName,\n timeoutMs,\n });\n\n const outName =\n fileName.split(\"/\").filter(Boolean).at(-1) ?? \"recording.bin\";\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"application/octet-stream\");\n res.setHeader(\n \"Content-Disposition\",\n `attachment; filename=\"${outName}\"`,\n );\n res.setHeader(\"Content-Length\", String(buf.length));\n res.end(buf);\n return;\n }\n\n if (u.pathname === \"/recordings\") {\n const channel = parseIntParam(u.searchParams.get(\"channel\"), 0);\n const streamType = (\n u.searchParams.get(\"streamType\") ?? \"subStream\"\n ).trim();\n const start = parseDateParamNamed(\"start\", u.searchParams.get(\"start\"));\n const end = parseDateParamNamed(\"end\", u.searchParams.get(\"end\"));\n const recordType = (u.searchParams.get(\"recordType\") ?? \"\").trim();\n const count = parseIntParam(u.searchParams.get(\"count\"), 0);\n const timeoutMs = parseIntParam(\n u.searchParams.get(\"timeoutMs\"),\n 30_000,\n );\n\n if (streamType !== \"mainStream\" && streamType !== \"subStream\") {\n res.statusCode = 400;\n res.end(\"Invalid streamType (must be mainStream or subStream)\");\n return;\n }\n\n let recordings = await api.getVideoclips({\n channel,\n start,\n end,\n streamType: streamType as any,\n ...(recordType ? { recordType } : {}),\n timeoutMs,\n });\n\n if (count > 0) recordings = recordings.slice(-count);\n\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n JSON.stringify({\n channel,\n streamType,\n start: start.toISOString(),\n end: end.toISOString(),\n recordings,\n }),\n );\n return;\n }\n\n if (u.pathname === \"/vod/stream\" || u.pathname === \"/vod/download\") {\n const channel = parseIntParam(u.searchParams.get(\"channel\"), 0);\n const fileName = (u.searchParams.get(\"fileName\") ?? \"\").trim();\n const streamType = (\n u.searchParams.get(\"streamType\") ?? \"mainStream\"\n ).trim();\n const rtspTransport = (\n u.searchParams.get(\"rtmpTransport\") ?? \"tcp\"\n ).trim();\n\n if (!fileName) {\n res.statusCode = 400;\n res.end(\"Missing fileName\");\n return;\n }\n if (streamType !== \"mainStream\" && streamType !== \"subStream\") {\n res.statusCode = 400;\n res.end(\"Invalid streamType (must be mainStream or subStream)\");\n return;\n }\n if (rtspTransport !== \"tcp\" && rtspTransport !== \"udp\") {\n res.statusCode = 400;\n res.end(\"Invalid rtmpTransport (must be tcp or udp)\");\n return;\n }\n\n const rtmpUrl = await api.getVodRtmpUrl({\n channel,\n fileName,\n streamType: streamType as any,\n ensureEnabled: true,\n });\n\n const outName =\n fileName.split(\"/\").filter(Boolean).at(-1) ?? \"recording.mp4\";\n\n if (u.pathname === \"/vod/stream\") {\n // Stream-only: RTMP -> MPEG-TS passthrough.\n res.writeHead(200, {\n \"Content-Type\": \"video/mp2t\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"close\",\n });\n\n const ff = spawn(\"ffmpeg\", [\n \"-hide_banner\",\n \"-loglevel\",\n \"error\",\n \"-rtmp_live\",\n \"live\",\n \"-i\",\n rtmpUrl,\n \"-c\",\n \"copy\",\n \"-f\",\n \"mpegts\",\n \"pipe:1\",\n ]);\n\n ff.stdout.pipe(res);\n\n const cleanup = () => {\n ff.kill(\"SIGKILL\");\n };\n req.on(\"close\", cleanup);\n res.on(\"close\", cleanup);\n ff.on(\"close\", () => {\n res.end();\n });\n return;\n }\n\n // Download/export: RTMP -> MP4 (best-effort streamable MP4).\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"video/mp4\");\n res.setHeader(\n \"Content-Disposition\",\n `attachment; filename=\"${outName}\"`,\n );\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"close\");\n\n const ff = spawn(\"ffmpeg\", [\n \"-hide_banner\",\n \"-loglevel\",\n \"error\",\n \"-rtmp_live\",\n \"live\",\n \"-i\",\n rtmpUrl,\n \"-c\",\n \"copy\",\n \"-movflags\",\n \"frag_keyframe+empty_moov\",\n \"-f\",\n \"mp4\",\n \"pipe:1\",\n ]);\n\n ff.stdout.pipe(res);\n\n const cleanup = () => {\n ff.kill(\"SIGKILL\");\n };\n req.on(\"close\", cleanup);\n res.on(\"close\", cleanup);\n ff.on(\"close\", () => {\n res.end();\n });\n return;\n }\n\n if (u.pathname === \"/replay/cover.jpg\") {\n const channel = parseIntParam(u.searchParams.get(\"channel\"), 0);\n const time = parseDateParam(u.searchParams.get(\"time\"));\n const snapType = (u.searchParams.get(\"snapType\") ?? \"sub\").trim();\n const timeoutMs = parseIntParam(\n u.searchParams.get(\"timeoutMs\"),\n 30_000,\n );\n const jpegQuality = parseIntParam(u.searchParams.get(\"jpegQuality\"), 2);\n\n if (snapType !== \"main\" && snapType !== \"sub\") {\n res.statusCode = 400;\n res.end(\"Invalid snapType (must be main or sub)\");\n return;\n }\n\n const { jpeg, snapshot } = await api.getVideoclipThumbnailJpegRaw({\n channel,\n time,\n snapType: snapType as any,\n timeoutMs,\n jpegQuality,\n });\n\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"image/jpeg\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"X-Reolink-Encoding\", snapshot.encoding);\n res.setHeader(\"X-Reolink-Frame-Length\", String(snapshot.frameLength));\n res.setHeader(\"Content-Length\", String(jpeg.length));\n res.end(jpeg);\n return;\n }\n\n if (u.pathname === \"/replay/stream.mp4\") {\n const channel = parseIntParam(u.searchParams.get(\"channel\"), 0);\n const fileName = (u.searchParams.get(\"fileName\") ?? \"\").trim();\n\n if (!fileName) {\n res.statusCode = 400;\n res.end(\"Missing fileName\");\n return;\n }\n\n const { mp4, stop } = await api.createRecordingReplayMp4Stream({\n channel,\n fileName,\n });\n\n res.writeHead(200, {\n \"Content-Type\": \"video/mp4\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"close\",\n });\n mp4.pipe(res);\n\n const cleanup = () => {\n void stop();\n try {\n mp4.destroy();\n } catch {\n // ignore\n }\n };\n req.on(\"close\", cleanup);\n res.on(\"close\", cleanup);\n mp4.on(\"end\", () => {\n res.end();\n });\n mp4.on(\"error\", () => {\n res.end();\n });\n return;\n }\n\n res.statusCode = 404;\n res.end(\"Not Found\");\n } catch (e) {\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"text/plain\");\n res.end(e instanceof Error ? e.message : String(e));\n }\n });\n\n // Best-effort cleanup when the server closes.\n server.on(\"close\", () => {\n void api.close().catch(() => undefined);\n });\n\n return server;\n}\n","import http from \"node:http\";\nimport { spawn } from \"node:child_process\";\nimport { buildRtspUrl, type RtspStreamProfile } from \"./urls\";\n\nexport type RtspProxyServerOptions = {\n listenPort: number;\n host: string;\n username: string;\n password: string;\n rtspPort?: number;\n /**\n * RTSP transport towards the camera.\n * - `tcp` is often more reliable on LAN\n * - `udp` can reduce latency but is more fragile\n */\n rtspTransport?: \"tcp\" | \"udp\";\n};\n\n/**\n * Server Node.js minimale che espone:\n * - `GET /stream?channel=0&profile=main`\n *\n * Implementation: uses `ffmpeg` to read RTSP and passthrough to MPEG-TS over HTTP.\n * Richiede `ffmpeg` installato nel sistema.\n */\nexport function createRtspProxyServer(opts: RtspProxyServerOptions): http.Server {\n return http.createServer((req, res) => {\n if (!req.url) {\n res.statusCode = 400;\n res.end(\"Bad Request\");\n return;\n }\n const u = new URL(req.url, `http://127.0.0.1:${opts.listenPort}`);\n if (u.pathname !== \"/stream\") {\n res.statusCode = 404;\n res.end(\"Not Found\");\n return;\n }\n\n const channel = Number(u.searchParams.get(\"channel\") ?? \"0\");\n const profile = (u.searchParams.get(\"profile\") ?? \"sub\") as RtspStreamProfile;\n if (!Number.isFinite(channel) || channel < 0) {\n res.statusCode = 400;\n res.end(\"Invalid channel\");\n return;\n }\n if (profile !== \"main\" && profile !== \"sub\" && profile !== \"ext\") {\n res.statusCode = 400;\n res.end(\"Invalid profile (must be main, sub, or ext)\");\n return;\n }\n\n const rtspUrl = buildRtspUrl({\n host: opts.host,\n username: opts.username,\n password: opts.password,\n channel,\n stream: profile,\n ...(opts.rtspPort === undefined ? {} : { port: opts.rtspPort }),\n });\n\n res.writeHead(200, {\n \"Content-Type\": \"video/mp2t\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"close\",\n });\n\n const rtspTransport = opts.rtspTransport ?? \"tcp\";\n const ff = spawn(\"ffmpeg\", [\n \"-hide_banner\",\n \"-loglevel\",\n \"error\",\n \"-rtsp_transport\",\n rtspTransport,\n \"-i\",\n rtspUrl,\n \"-an\",\n \"-c:v\",\n \"copy\",\n \"-f\",\n \"mpegts\",\n \"pipe:1\",\n ]);\n\n ff.stdout.pipe(res);\n ff.stderr.on(\"data\", () => {\n // opzionale: log\n });\n\n const cleanup = () => {\n ff.kill(\"SIGKILL\");\n };\n req.on(\"close\", cleanup);\n res.on(\"close\", cleanup);\n ff.on(\"close\", () => {\n res.end();\n });\n });\n}\n\n","import net from \"node:net\";\nimport crypto from \"node:crypto\";\n\nexport type VideoType = \"H264\" | \"H265\";\n\nexport type AudioConfig =\n | {\n codec: \"aac\";\n payloadType: number;\n sampleRate: number;\n channels: number;\n configHex: string;\n }\n | {\n codec: \"opus\";\n payloadType: number;\n sampleRate: number;\n channels: number;\n };\n\nexport interface VideoParamSets {\n videoType: VideoType;\n payloadType: number;\n h264?: {\n sps: Buffer;\n pps: Buffer;\n profileLevelId?: string;\n };\n h265?: {\n vps: Buffer;\n sps: Buffer;\n pps: Buffer;\n };\n}\n\nexport function buildRfc4571Sdp(\n video: VideoParamSets,\n audio?: AudioConfig,\n): string {\n let out = \"v=0\\r\\n\";\n out += \"o=- 0 0 IN IP4 0.0.0.0\\r\\n\";\n out += \"s=No Name\\r\\n\";\n out += \"t=0 0\\r\\n\";\n\n out += `m=video 0 RTP/AVP ${video.payloadType}\\r\\n`;\n out += \"c=IN IP4 0.0.0.0\\r\\n\";\n out += `a=rtpmap:${video.payloadType} ${video.videoType}/90000\\r\\n`;\n\n if (video.videoType === \"H264\" && video.h264) {\n const spsB64 = video.h264.sps.toString(\"base64\");\n const ppsB64 = video.h264.pps.toString(\"base64\");\n const pli = video.h264.profileLevelId\n ? `profile-level-id=${video.h264.profileLevelId};`\n : \"\";\n out += `a=fmtp:${video.payloadType} packetization-mode=1;${pli}sprop-parameter-sets=${spsB64},${ppsB64}\\r\\n`;\n }\n\n if (video.videoType === \"H265\" && video.h265) {\n const vpsB64 = video.h265.vps.toString(\"base64\");\n const spsB64 = video.h265.sps.toString(\"base64\");\n const ppsB64 = video.h265.pps.toString(\"base64\");\n out += `a=fmtp:${video.payloadType} sprop-vps=${vpsB64};sprop-sps=${spsB64};sprop-pps=${ppsB64}\\r\\n`;\n }\n\n if (audio?.codec === \"aac\") {\n out += `m=audio 0 RTP/AVP ${audio.payloadType}\\r\\n`;\n out += \"c=IN IP4 0.0.0.0\\r\\n\";\n out += \"b=AS:128\\r\\n\";\n out += `a=rtpmap:${audio.payloadType} MPEG4-GENERIC/${audio.sampleRate}/${audio.channels}\\r\\n`;\n out += `a=fmtp:${audio.payloadType} profile-level-id=1; mode=AAC-hbr; sizelength=13; indexlength=3; indexdeltalength=3; config=${audio.configHex}\\r\\n`;\n }\n\n if (audio?.codec === \"opus\") {\n out += `m=audio 0 RTP/AVP ${audio.payloadType}\\r\\n`;\n out += \"c=IN IP4 0.0.0.0\\r\\n\";\n out += \"b=AS:128\\r\\n\";\n out += `a=rtpmap:${audio.payloadType} opus/${audio.sampleRate}/${audio.channels}\\r\\n`;\n }\n\n return out;\n}\n\nexport function splitAnnexBToNals(annexB: Buffer): Buffer[] {\n // Returns NAL units WITHOUT start codes.\n // Keep behavior aligned with nodelink-js (no aggressive trimming), since some payloads\n // may legitimately end with 0x00.\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\nfunction findAnnexBStartCodeOffset(data: Buffer, maxScan = 64): number {\n if (!data?.length) return -1;\n const len = Math.min(data.length, Math.max(0, maxScan));\n for (let i = 0; i + 3 < len; i++) {\n if (data[i] !== 0x00 || data[i + 1] !== 0x00) continue;\n if (data[i + 2] === 0x01) return i;\n if (data[i + 2] === 0x00 && data[i + 3] === 0x01) return i;\n }\n return -1;\n}\n\nfunction stripLeadingAnnexBStartCode(data: Buffer): Buffer {\n if (data.length >= 4 && data[0] === 0x00 && data[1] === 0x00) {\n if (data[2] === 0x01) return data.subarray(3);\n if (data[2] === 0x00 && data[3] === 0x01) return data.subarray(4);\n }\n return data;\n}\n\nfunction splitLengthPrefixedToNals(\n data: Buffer,\n lengthSize: 1 | 2 | 3 | 4,\n endian: \"be\" | \"le\",\n): Buffer[] {\n // AVCC/HVCC style: [length][NAL bytes]...\n // lengthSize is typically 4, but some devices/streams use 2.\n const nals: Buffer[] = [];\n let offset = 0;\n\n const readLen = (buf: Buffer, at: number): number => {\n if (lengthSize === 4)\n return endian === \"be\" ? buf.readUInt32BE(at) : buf.readUInt32LE(at);\n if (lengthSize === 2)\n return endian === \"be\" ? buf.readUInt16BE(at) : buf.readUInt16LE(at);\n if (lengthSize === 3) {\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 return buf.readUInt8(at);\n };\n\n while (offset + lengthSize <= data.length) {\n const nalLen = readLen(data, offset);\n offset += lengthSize;\n\n // 0-length doesn't make sense for an access unit; treat as parse failure.\n if (!nalLen) return [];\n if (offset + nalLen > data.length) return [];\n\n const nal = data.subarray(offset, offset + nalLen);\n offset += nalLen;\n if (nal.length) nals.push(nal);\n }\n\n // Must consume the buffer cleanly; otherwise treat as unknown format.\n if (offset !== data.length) return [];\n return nals;\n}\n\nfunction scoreNals(videoType: VideoType | undefined, nals: Buffer[]): number {\n if (!nals.length) return Number.POSITIVE_INFINITY;\n\n let invalid = 0;\n for (const nal of nals) {\n if (!nal.length) {\n invalid++;\n continue;\n }\n\n // forbidden_zero_bit must be 0 for both H264 and H265\n if ((nal[0]! & 0x80) !== 0) {\n invalid += 10;\n continue;\n }\n\n if (videoType === \"H264\") {\n const t = nal[0]! & 0x1f;\n // 1..23 are defined; 24..31 are aggreg/fragmentation/reserved and should not appear in raw AUs.\n if (t < 1 || t > 23) invalid++;\n } else if (videoType === \"H265\") {\n if (nal.length < 2) {\n invalid++;\n continue;\n }\n const t = (nal[0]! >> 1) & 0x3f;\n // Reject reserved/forbidden types that strongly suggest mis-parsing.\n // Keep common types: 0-29 (VCL), 32-40 (VPS/SPS/PPS/AUD/SEI/etc), 48-50 (AP/FU/PACI)\n const isValid =\n (t >= 0 && t <= 29) || (t >= 32 && t <= 40) || (t >= 48 && t <= 50);\n if (!isValid) invalid++;\n }\n }\n\n return invalid;\n}\n\nfunction splitAccessUnitToNalsBestEffort(\n accessUnit: Buffer,\n videoType?: VideoType,\n): Buffer[] {\n if (!accessUnit?.length) return [];\n\n const candidates: Buffer[][] = [];\n\n // Annex-B is only considered if a start code appears at the start (or shortly after);\n // scanning the entire buffer can false-positive on random 0x000001 sequences.\n const scOffset = findAnnexBStartCodeOffset(accessUnit, 64);\n if (scOffset >= 0) {\n const annex = scOffset === 0 ? accessUnit : accessUnit.subarray(scOffset);\n const nals = splitAnnexBToNals(annex);\n if (nals.length) candidates.push(nals);\n }\n\n // If it looks like AVCC/HVCC (often starts with 0x00 0x00 ..), try length-prefixed parsing.\n // Some Reolink streams appear to use 2-byte NAL lengths.\n candidates.push(splitLengthPrefixedToNals(accessUnit, 4, \"be\"));\n candidates.push(splitLengthPrefixedToNals(accessUnit, 4, \"le\"));\n candidates.push(splitLengthPrefixedToNals(accessUnit, 3, \"be\"));\n candidates.push(splitLengthPrefixedToNals(accessUnit, 3, \"le\"));\n candidates.push(splitLengthPrefixedToNals(accessUnit, 2, \"be\"));\n candidates.push(splitLengthPrefixedToNals(accessUnit, 2, \"le\"));\n candidates.push(splitLengthPrefixedToNals(accessUnit, 1, \"be\"));\n\n // Single NAL without start code (or unknown packaging). Strip a leading start code if present.\n const stripped = stripLeadingAnnexBStartCode(accessUnit);\n if (stripped.length) candidates.push([stripped]);\n\n let best: Buffer[] = [];\n let bestScore = Number.POSITIVE_INFINITY;\n for (const cand of candidates) {\n if (!cand.length) continue;\n const s = scoreNals(videoType, cand);\n if (s < bestScore) {\n bestScore = s;\n best = cand;\n if (bestScore === 0) break;\n }\n }\n\n return best;\n}\n\nexport function extractH264ParamSetsFromAccessUnit(annexB: Buffer): {\n sps?: Buffer;\n pps?: Buffer;\n profileLevelId?: string;\n} {\n const nals = splitAccessUnitToNalsBestEffort(annexB, \"H264\");\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 const hex = Buffer.from([nal[1]!, nal[2]!, nal[3]!]).toString(\"hex\");\n profileLevelId = hex;\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\nexport function extractH265ParamSetsFromAccessUnit(annexB: Buffer): {\n vps?: Buffer;\n sps?: Buffer;\n pps?: Buffer;\n} {\n const nals = splitAccessUnitToNalsBestEffort(annexB, \"H265\");\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\nconst aacSampleRates = [\n 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025,\n 8000, 7350,\n];\n\nexport function buildAacAudioSpecificConfigHex(params: {\n sampleRate: number;\n channels: number;\n audioObjectType?: number;\n}): string | undefined {\n const { sampleRate, channels } = params;\n const audioObjectType = params.audioObjectType ?? 2; // AAC-LC\n const samplingFreqIndex = aacSampleRates.indexOf(sampleRate);\n if (samplingFreqIndex < 0) return;\n if (!Number.isFinite(channels) || channels <= 0 || channels > 15) return;\n if (\n !Number.isFinite(audioObjectType) ||\n audioObjectType <= 0 ||\n audioObjectType > 31\n )\n return;\n\n // AudioSpecificConfig (2 bytes, left-aligned)\n const asc =\n ((audioObjectType & 0x1f) << 11) |\n ((samplingFreqIndex & 0x0f) << 7) |\n ((channels & 0x0f) << 3);\n return asc.toString(16).padStart(4, \"0\");\n}\n\nexport function parseAdtsHeader(\n adtsFrame: Buffer,\n): {\n headerLength: number;\n sampleRate: number;\n channels: number;\n configHex: string;\n} | null {\n if (adtsFrame.length < 7) return null;\n if (adtsFrame[0]! !== 0xff || (adtsFrame[1]! & 0xf0) !== 0xf0) return null;\n\n const protectionAbsent = (adtsFrame[1]! & 0x01) === 1;\n const profile = (adtsFrame[2]! & 0xc0) >> 6; // 0=Main,1=LC...\n const audioObjectType = profile + 1;\n const samplingFreqIndex = (adtsFrame[2]! & 0x3c) >> 2;\n const sampleRate = aacSampleRates[samplingFreqIndex] ?? 0;\n const channels =\n ((adtsFrame[2]! & 0x01) << 2) | ((adtsFrame[3]! & 0xc0) >> 6);\n\n if (!sampleRate || !channels) return null;\n\n const headerLength = protectionAbsent ? 7 : 9;\n if (adtsFrame.length < headerLength) return null;\n\n // AudioSpecificConfig (2 bytes, left-aligned)\n const asc =\n ((audioObjectType & 0x1f) << 11) |\n ((samplingFreqIndex & 0x0f) << 7) |\n ((channels & 0x0f) << 3);\n const configHex = asc.toString(16).padStart(4, \"0\");\n\n return { headerLength, sampleRate, channels, configHex };\n}\n\nexport interface RtpPacketizationOptions {\n maxRtpPayload: number;\n}\n\nclass RtpWriter {\n private seq = 0;\n private timestamp = 0;\n private ssrc = crypto.randomBytes(4).readUInt32BE(0);\n\n constructor(private payloadType: number) {\n this.seq = crypto.randomBytes(2).readUInt16BE(0);\n // Random initial timestamp improves interoperability with RTP receivers\n // (including WebRTC bridging) and matches common RTP best practices.\n this.timestamp = crypto.randomBytes(4).readUInt32BE(0);\n }\n\n setTimestamp(ts: number) {\n this.timestamp = ts >>> 0;\n }\n\n getTimestamp(): number {\n return this.timestamp >>> 0;\n }\n\n advanceTimestamp(delta: number) {\n this.timestamp = (this.timestamp + (delta >>> 0)) >>> 0;\n }\n\n writePacket(payload: Buffer, marker: boolean): Buffer {\n const header = Buffer.alloc(12);\n header[0] = 0x80;\n header[1] = (marker ? 0x80 : 0x00) | (this.payloadType & 0x7f);\n header.writeUInt16BE(this.seq & 0xffff, 2);\n header.writeUInt32BE(this.timestamp >>> 0, 4);\n header.writeUInt32BE(this.ssrc >>> 0, 8);\n this.seq = (this.seq + 1) & 0xffff;\n return Buffer.concat([header, payload]);\n }\n}\n\nexport function packetizeH264(\n nal: Buffer,\n rtp: RtpWriter,\n opts: RtpPacketizationOptions,\n markerOnLast: boolean,\n isLastNal: boolean,\n): Buffer[] {\n const max = opts.maxRtpPayload;\n const out: Buffer[] = [];\n if (nal.length <= max) {\n out.push(rtp.writePacket(nal, markerOnLast && isLastNal));\n return out;\n }\n\n const nal0 = nal[0]!;\n const f = nal0 & 0x80;\n const nri = nal0 & 0x60;\n const type = nal0 & 0x1f;\n const fuIndicator = f | nri | 28;\n\n const data = nal.subarray(1);\n let offset = 0;\n while (offset < data.length) {\n const remaining = data.length - offset;\n const chunkLen = Math.min(remaining, max - 2);\n const start = offset === 0;\n const end = offset + chunkLen >= data.length;\n const fuHeader =\n (start ? 0x80 : 0x00) | (end ? 0x40 : 0x00) | (type & 0x1f);\n const payload = Buffer.concat([\n Buffer.from([fuIndicator, fuHeader]),\n data.subarray(offset, offset + chunkLen),\n ]);\n out.push(rtp.writePacket(payload, markerOnLast && isLastNal && end));\n offset += chunkLen;\n }\n\n return out;\n}\n\nexport function packetizeH265(\n nal: Buffer,\n rtp: RtpWriter,\n opts: RtpPacketizationOptions,\n markerOnLast: boolean,\n isLastNal: boolean,\n): Buffer[] {\n const max = opts.maxRtpPayload;\n const out: Buffer[] = [];\n if (nal.length <= max) {\n out.push(rtp.writePacket(nal, markerOnLast && isLastNal));\n return out;\n }\n\n if (nal.length < 3) return out;\n\n const nalHeader0 = nal[0]!;\n const nalHeader1 = nal[1]!;\n const nalType = (nalHeader0 >> 1) & 0x3f;\n\n // FU indicator: F + type=49 + layerId/tid bits preserved.\n const fuIndicator0 = (nalHeader0 & 0x81) | (49 << 1);\n const fuIndicator1 = nalHeader1;\n\n const data = nal.subarray(2);\n let offset = 0;\n while (offset < data.length) {\n const remaining = data.length - offset;\n const chunkLen = Math.min(remaining, max - 3);\n const start = offset === 0;\n const end = offset + chunkLen >= data.length;\n const fuHeader =\n (start ? 0x80 : 0x00) | (end ? 0x40 : 0x00) | (nalType & 0x3f);\n const payload = Buffer.concat([\n Buffer.from([fuIndicator0, fuIndicator1, fuHeader]),\n data.subarray(offset, offset + chunkLen),\n ]);\n out.push(rtp.writePacket(payload, markerOnLast && isLastNal && end));\n offset += chunkLen;\n }\n\n return out;\n}\n\nexport function packetizeAacAdtsFrame(\n adts: Buffer,\n rtp: RtpWriter,\n): {\n packets: Buffer[];\n config?: { sampleRate: number; channels: number; configHex: string };\n} {\n const parsed = parseAdtsHeader(adts);\n if (!parsed) return { packets: [] };\n const raw = adts.subarray(parsed.headerLength);\n if (!raw.length) return { packets: [] };\n\n // RFC 3640: AU-headers-length (16 bits) + AU-header (16 bits)\n const auHeadersLength = Buffer.from([0x00, 0x10]);\n const auSize = raw.length & 0x1fff;\n const auHeader = Buffer.alloc(2);\n auHeader[0] = (auSize >> 5) & 0xff;\n auHeader[1] = (auSize & 0x1f) << 3;\n\n const payload = Buffer.concat([auHeadersLength, auHeader, raw]);\n return {\n packets: [rtp.writePacket(payload, true)],\n config: {\n sampleRate: parsed.sampleRate,\n channels: parsed.channels,\n configHex: parsed.configHex,\n },\n };\n}\n\nexport function packetizeAacRawFrame(\n raw: Buffer,\n rtp: RtpWriter,\n): { packets: Buffer[] } {\n if (!raw?.length) return { packets: [] };\n\n // RFC 3640: AU-headers-length (16 bits) + AU-header (16 bits)\n const auHeadersLength = Buffer.from([0x00, 0x10]);\n const auSize = raw.length & 0x1fff;\n const auHeader = Buffer.alloc(2);\n auHeader[0] = (auSize >> 5) & 0xff;\n auHeader[1] = (auSize & 0x1f) << 3;\n\n const payload = Buffer.concat([auHeadersLength, auHeader, raw]);\n return {\n packets: [rtp.writePacket(payload, true)],\n };\n}\n\nexport interface Rfc4571Client {\n socket: net.Socket;\n needsKeyframe: boolean;\n sentVideoConfig: boolean;\n}\n\nexport class Rfc4571Muxer {\n private clients = new Set<Rfc4571Client>();\n private closed = false;\n\n private videoRtp: RtpWriter;\n private audioRtp: RtpWriter | undefined;\n\n // Timestamp tracking\n // bcmedia microseconds is a u32 clock that may wrap (2^32) and may reset on stream restarts.\n // Additionally, it may jump forward unexpectedly. Since we do not buffer, large forward jumps\n // cause downstream schedulers to detect discontinuities.\n //\n // Strategy:\n // - unwrap u32 wraps when likely\n // - treat backwards jumps as restarts\n // - use microseconds deltas only when \"reasonable\"; otherwise advance using a smoothed/fallback delta\n private videoLastUsRaw: number | undefined;\n private videoUsWrapOffset = 0;\n private videoLastAbsUs: number | undefined;\n private videoAvgDeltaUs: number | undefined;\n private readonly videoClockRate = 90000;\n private readonly fallbackVideoIncrement: number;\n private readonly fallbackVideoDeltaUs: number;\n private readonly maxTrustedDeltaUs = 500_000; // 0.5s\n private readonly emaAlpha = 0.1;\n\n // Throttled logging to avoid spamming in hot paths.\n private videoTimingLogState: {\n lastUntrustedDeltaLogMs: number;\n lastWrapLogMs: number;\n lastResetLogMs: number;\n untrustedDeltaCount: number;\n wrapCount: number;\n resetCount: number;\n } = {\n lastUntrustedDeltaLogMs: 0,\n lastWrapLogMs: 0,\n lastResetLogMs: 0,\n untrustedDeltaCount: 0,\n wrapCount: 0,\n resetCount: 0,\n };\n\n private cachedH264ParamSetsAnnexB: Buffer | undefined;\n private cachedH265ParamSetsAnnexB: Buffer | undefined;\n\n constructor(\n private logger: Console,\n private videoPayloadType: number,\n audioPayloadType: number | undefined,\n videoFpsFallback = 25,\n private maxRtpPayload = 1200,\n ) {\n this.videoRtp = new RtpWriter(videoPayloadType);\n if (audioPayloadType !== undefined) {\n this.audioRtp = new RtpWriter(audioPayloadType);\n }\n this.fallbackVideoIncrement = Math.max(\n 1,\n Math.round(this.videoClockRate / Math.max(1, videoFpsFallback)),\n );\n this.fallbackVideoDeltaUs = Math.max(\n 1,\n Math.round(\n (this.fallbackVideoIncrement * 1_000_000) / this.videoClockRate,\n ),\n );\n }\n\n addClient(socket: net.Socket) {\n if (this.closed) {\n socket.destroy();\n return;\n }\n\n const client: Rfc4571Client = {\n socket,\n needsKeyframe: true,\n sentVideoConfig: false,\n };\n this.clients.add(client);\n\n const cleanup = () => {\n this.clients.delete(client);\n try {\n socket.destroy();\n } catch {\n // ignore\n }\n };\n\n socket.on(\"error\", cleanup);\n socket.on(\"close\", cleanup);\n }\n\n private static readonly ANNEXB_START_CODE = Buffer.from([\n 0x00, 0x00, 0x00, 0x01,\n ]);\n\n private static joinAnnexBNals(\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(Rfc4571Muxer.ANNEXB_START_CODE, nal);\n }\n return Buffer.concat(parts);\n }\n\n private updateVideoParamSetsFromAccessUnit(\n videoType: VideoType,\n accessUnit: Buffer,\n ): void {\n if (!accessUnit?.length) return;\n\n if (videoType === \"H264\") {\n const { sps, pps } = extractH264ParamSetsFromAccessUnit(accessUnit);\n if (sps && pps) {\n this.cachedH264ParamSetsAnnexB = Rfc4571Muxer.joinAnnexBNals(sps, pps);\n }\n return;\n }\n\n const { vps, sps, pps } = extractH265ParamSetsFromAccessUnit(accessUnit);\n if (vps && sps && pps) {\n this.cachedH265ParamSetsAnnexB = Rfc4571Muxer.joinAnnexBNals(\n vps,\n sps,\n pps,\n );\n }\n }\n\n private sendCachedVideoConfigToClient(\n videoType: VideoType,\n client: Rfc4571Client,\n ): void {\n if (this.closed) return;\n if (client.sentVideoConfig) return;\n\n const paramSetsAnnexB =\n videoType === \"H265\"\n ? this.cachedH265ParamSetsAnnexB\n : this.cachedH264ParamSetsAnnexB;\n if (!paramSetsAnnexB?.length) return;\n\n const nals = splitAccessUnitToNalsBestEffort(paramSetsAnnexB, videoType);\n if (!nals.length) return;\n\n const opts: RtpPacketizationOptions = { maxRtpPayload: this.maxRtpPayload };\n for (let i = 0; i < nals.length; i++) {\n const nal = nals[i]!;\n const isLastNal = i === nals.length - 1;\n const packets =\n videoType === \"H265\"\n ? packetizeH265(nal, this.videoRtp, opts, false, isLastNal)\n : packetizeH264(nal, this.videoRtp, opts, false, isLastNal);\n\n for (const pkt of packets) {\n this.writeRtpPacketToClient(client, pkt);\n }\n }\n\n client.sentVideoConfig = true;\n }\n\n close() {\n if (this.closed) return;\n this.closed = true;\n for (const c of Array.from(this.clients)) {\n try {\n c.socket.destroy();\n } catch {\n // ignore\n }\n }\n this.clients.clear();\n }\n\n private writeRtpPacketToClient(client: Rfc4571Client, pkt: Buffer) {\n if (client.socket.destroyed || !client.socket.writable) return;\n\n const header = Buffer.alloc(2);\n header.writeUInt16BE(pkt.length & 0xffff, 0);\n const framed = Buffer.concat([header, pkt]);\n\n try {\n client.socket.write(framed);\n } catch {\n try {\n client.socket.destroy();\n } catch {\n // ignore\n }\n }\n }\n\n private writeRtpPacketToClients(\n pkt: Buffer,\n predicate: (client: Rfc4571Client) => boolean,\n ) {\n for (const c of this.clients) {\n if (!predicate(c)) continue;\n this.writeRtpPacketToClient(c, pkt);\n }\n }\n\n private resetVideoTimestampMapping(): void {\n // Reset microseconds tracking/mapping, but keep RTP writer state (seq/ssrc/timestamp).\n this.videoLastUsRaw = undefined;\n this.videoUsWrapOffset = 0;\n this.videoLastAbsUs = undefined;\n this.videoAvgDeltaUs = undefined;\n }\n\n private logVideoTiming(\n kind: \"untrusted-delta\" | \"wrap\" | \"reset\",\n message: string,\n ): void {\n const now = Date.now();\n const intervalMs = 5000;\n const state = this.videoTimingLogState;\n if (kind === \"untrusted-delta\") {\n state.untrustedDeltaCount++;\n if (now - state.lastUntrustedDeltaLogMs < intervalMs) return;\n state.lastUntrustedDeltaLogMs = now;\n this.logger.warn(\n `[rfc4571] video timing: ${message} (untrustedDeltaCount=${state.untrustedDeltaCount})`,\n );\n return;\n }\n if (kind === \"wrap\") {\n state.wrapCount++;\n if (now - state.lastWrapLogMs < intervalMs) return;\n state.lastWrapLogMs = now;\n this.logger.warn(\n `[rfc4571] video timing: ${message} (wrapCount=${state.wrapCount})`,\n );\n return;\n }\n state.resetCount++;\n if (now - state.lastResetLogMs < intervalMs) return;\n state.lastResetLogMs = now;\n this.logger.warn(\n `[rfc4571] video timing: ${message} (resetCount=${state.resetCount})`,\n );\n }\n\n private advanceVideoTimestampFallback(): void {\n this.videoRtp.advanceTimestamp(this.fallbackVideoIncrement);\n }\n\n setVideoTimestampFromMicroseconds(\n frameMicroseconds: number | null | undefined,\n ) {\n if (frameMicroseconds === null || frameMicroseconds === undefined) {\n this.advanceVideoTimestampFallback();\n return;\n }\n if (!Number.isFinite(frameMicroseconds)) {\n this.advanceVideoTimestampFallback();\n return;\n }\n\n const curUsRaw = (frameMicroseconds >>> 0) as number;\n\n if (this.videoLastUsRaw !== undefined) {\n const lastUsRaw = this.videoLastUsRaw;\n if (curUsRaw < lastUsRaw) {\n // Heuristic: treat as wrap only if last is near max and current is near min.\n const wrapLikely = lastUsRaw > 0xf0000000 && curUsRaw < 0x0fffffff;\n if (wrapLikely) {\n this.videoUsWrapOffset += 0x1_0000_0000;\n this.logVideoTiming(\n \"wrap\",\n `detected u32 wrap (lastUsRaw=${lastUsRaw} curUsRaw=${curUsRaw} wrapOffset=${this.videoUsWrapOffset})`,\n );\n } else {\n // Likely stream restart / discontinuity: reset mapping.\n this.logVideoTiming(\n \"reset\",\n `detected backwards jump; resetting mapping (lastUsRaw=${lastUsRaw} curUsRaw=${curUsRaw})`,\n );\n this.resetVideoTimestampMapping();\n }\n }\n }\n\n this.videoLastUsRaw = curUsRaw;\n const absUs = this.videoUsWrapOffset + curUsRaw;\n\n if (this.videoLastAbsUs === undefined) {\n this.videoLastAbsUs = absUs;\n if (this.videoAvgDeltaUs === undefined)\n this.videoAvgDeltaUs = this.fallbackVideoDeltaUs;\n return;\n }\n\n const deltaUs = absUs - this.videoLastAbsUs;\n this.videoLastAbsUs = absUs;\n\n const trusted =\n Number.isFinite(deltaUs) &&\n deltaUs > 0 &&\n deltaUs <= this.maxTrustedDeltaUs;\n let effectiveDeltaUs: number;\n if (trusted) {\n const prevAvg = this.videoAvgDeltaUs ?? deltaUs;\n this.videoAvgDeltaUs = prevAvg + (deltaUs - prevAvg) * this.emaAlpha;\n effectiveDeltaUs = deltaUs;\n } else {\n this.logVideoTiming(\n \"untrusted-delta\",\n `discarded deltaUs=${deltaUs} (absUs=${absUs} lastAbsUs=${this.videoLastAbsUs} avgDeltaUs=${this.videoAvgDeltaUs ?? \"n/a\"}); using fallback`,\n );\n effectiveDeltaUs = this.videoAvgDeltaUs ?? this.fallbackVideoDeltaUs;\n }\n\n const inc = Math.max(\n 1,\n Math.round((effectiveDeltaUs * this.videoClockRate) / 1_000_000),\n );\n this.videoRtp.advanceTimestamp(inc);\n }\n\n sendVideoAccessUnit(\n videoType: VideoType,\n accessUnitAnnexB: Buffer,\n isKeyframe: boolean,\n microseconds: number | null | undefined,\n ) {\n if (this.closed) return;\n\n // Always attempt to learn current VPS/SPS/PPS (or SPS/PPS) even if no clients are connected,\n // so that the first client can be primed immediately.\n this.updateVideoParamSetsFromAccessUnit(videoType, accessUnitAnnexB);\n\n const nals = splitAccessUnitToNalsBestEffort(accessUnitAnnexB, videoType);\n if (!nals.length) return;\n\n // Some sources mislabel keyframes. Derive a best-effort keyframe flag from NAL types\n // to avoid per-client gating getting stuck (symptom: 1 frame per GOP).\n let derivedKeyframe = false;\n if (videoType === \"H264\") {\n for (const nal of nals) {\n const t = nal[0]! & 0x1f;\n if (t === 5) {\n // IDR\n derivedKeyframe = true;\n break;\n }\n }\n } else {\n for (const nal of nals) {\n if (nal.length < 2) continue;\n const t = (nal[0]! >> 1) & 0x3f;\n // IRAP pictures: 16..23 (BLA/IDR/CRA)\n if (t >= 16 && t <= 23) {\n derivedKeyframe = true;\n break;\n }\n }\n }\n\n const effectiveKeyframe = isKeyframe || derivedKeyframe;\n\n // gate per-client until keyframe: do NOT stall existing synced clients.\n // If a new client connects (needsKeyframe=true), only that client will wait.\n const shouldSendTo = (c: Rfc4571Client) =>\n effectiveKeyframe ? true : !c.needsKeyframe;\n let hasAnyTarget = false;\n for (const c of this.clients) {\n if (shouldSendTo(c)) {\n hasAnyTarget = true;\n break;\n }\n }\n // If no clients are ready for media yet (e.g. all are waiting for keyframe),\n // we can still send cached video config to help decoders initialize.\n if (!hasAnyTarget) {\n for (const c of this.clients) {\n if (!c.needsKeyframe) continue;\n this.sendCachedVideoConfigToClient(videoType, c);\n }\n return;\n }\n\n this.setVideoTimestampFromMicroseconds(microseconds);\n\n // Prime clients waiting for keyframe with VPS/SPS/PPS (or SPS/PPS) when available.\n for (const c of this.clients) {\n if (!c.needsKeyframe) continue;\n this.sendCachedVideoConfigToClient(videoType, c);\n }\n\n const opts: RtpPacketizationOptions = { maxRtpPayload: this.maxRtpPayload };\n for (let i = 0; i < nals.length; i++) {\n const nal = nals[i]!;\n const isLastNal = i === nals.length - 1;\n const packets =\n videoType === \"H265\"\n ? packetizeH265(nal, this.videoRtp, opts, true, isLastNal)\n : packetizeH264(nal, this.videoRtp, opts, true, isLastNal);\n\n for (const pkt of packets)\n this.writeRtpPacketToClients(pkt, shouldSendTo);\n }\n\n // mark clients as started when the keyframe passes through\n if (effectiveKeyframe) {\n for (const c of this.clients) c.needsKeyframe = false;\n }\n }\n\n sendAudioAdtsFrame(adts: Buffer): {\n parsed?: { sampleRate: number; channels: number; configHex: string };\n } {\n if (this.closed) return {};\n if (!this.audioRtp) return {};\n\n const { packets, config } = packetizeAacAdtsFrame(adts, this.audioRtp);\n // keep audio aligned with video gating: only send once video has started.\n for (const pkt of packets)\n this.writeRtpPacketToClients(pkt, (c) => !c.needsKeyframe);\n\n // advance by 1024 samples per AAC-LC frame\n if (packets.length) this.audioRtp.advanceTimestamp(1024);\n\n return config ? { parsed: config } : {};\n }\n\n sendAudioAacRawFrame(raw: Buffer): void {\n if (this.closed) return;\n if (!this.audioRtp) return;\n\n const { packets } = packetizeAacRawFrame(raw, this.audioRtp);\n // keep audio aligned with video gating: only send once video has started.\n for (const pkt of packets)\n this.writeRtpPacketToClients(pkt, (c) => !c.needsKeyframe);\n\n // advance by 1024 samples per AAC-LC frame\n if (packets.length) this.audioRtp.advanceTimestamp(1024);\n }\n\n sendAudioRtpPacket(rtpPacket: Buffer): void {\n if (this.closed) return;\n if (!rtpPacket || rtpPacket.length < 12) return;\n const version = (rtpPacket[0]! >> 6) & 0x03;\n if (version !== 2) return;\n\n // keep audio aligned with video gating: only send once video has started.\n this.writeRtpPacketToClients(rtpPacket, (c) => !c.needsKeyframe);\n }\n\n private static parseRtpPayload(packet: Buffer): Buffer | undefined {\n if (!packet || packet.length < 12) return;\n const version = (packet[0]! >> 6) & 0x03;\n if (version !== 2) return;\n\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;\n\n if (extension) {\n if (offset + 4 > packet.length) return;\n const extLenWords = packet.readUInt16BE(offset + 2);\n offset += 4 + extLenWords * 4;\n if (offset > packet.length) return;\n }\n\n let end = packet.length;\n if (padding) {\n const padLen = packet[packet.length - 1]!;\n if (padLen <= 0 || padLen > packet.length) return;\n end = packet.length - padLen;\n if (end < offset) return;\n }\n\n if (end <= offset) return;\n return packet.subarray(offset, end);\n }\n\n private static isH264KeyframeLikeRtpPacket(packet: Buffer): boolean {\n const payload = Rfc4571Muxer.parseRtpPayload(packet);\n if (!payload || payload.length < 1) return false;\n\n const nalType = payload[0]! & 0x1f;\n\n // Single NAL\n if (nalType >= 1 && nalType <= 23) {\n return nalType === 5 || nalType === 7 || nalType === 8;\n }\n\n // STAP-A\n if (nalType === 24) {\n let offset = 1;\n while (offset + 2 <= payload.length) {\n const size = payload.readUInt16BE(offset);\n offset += 2;\n if (!size || offset + size > payload.length) break;\n const t = payload[offset]! & 0x1f;\n if (t === 5 || t === 7 || t === 8) return true;\n offset += size;\n }\n return false;\n }\n\n // FU-A\n if (nalType === 28 && payload.length >= 2) {\n const fuHeader = payload[1]!;\n const start = (fuHeader & 0x80) !== 0;\n const origType = fuHeader & 0x1f;\n if (!start) return false;\n return origType === 5 || origType === 7 || origType === 8;\n }\n\n return false;\n }\n\n private static isH265KeyframeLikeRtpPacket(packet: Buffer): boolean {\n const payload = Rfc4571Muxer.parseRtpPayload(packet);\n if (!payload || payload.length < 2) return false;\n\n const nalType = (payload[0]! >> 1) & 0x3f;\n\n const isKeyNalType = (t: number) => {\n // IRAP: 16..21, VPS/SPS/PPS: 32/33/34\n if (t >= 16 && t <= 21) return true;\n if (t === 32 || t === 33 || t === 34) return true;\n return false;\n };\n\n // Fragmentation Unit (FU): nalType==49, original nal type in payload[2]\n if (nalType === 49 && payload.length >= 3) {\n const fuHeader = payload[2]!;\n const start = (fuHeader & 0x80) !== 0;\n const origType = fuHeader & 0x3f;\n if (!start) return false;\n return isKeyNalType(origType);\n }\n\n return isKeyNalType(nalType);\n }\n\n sendVideoRtpPacket(videoType: VideoType, rtpPacket: Buffer): void {\n if (this.closed) return;\n if (!rtpPacket || rtpPacket.length < 12) return;\n const version = (rtpPacket[0]! >> 6) & 0x03;\n if (version !== 2) return;\n\n const isKeyframeLike =\n videoType === \"H265\"\n ? Rfc4571Muxer.isH265KeyframeLikeRtpPacket(rtpPacket)\n : Rfc4571Muxer.isH264KeyframeLikeRtpPacket(rtpPacket);\n\n const shouldSendTo = (c: Rfc4571Client) =>\n isKeyframeLike ? true : !c.needsKeyframe;\n this.writeRtpPacketToClients(rtpPacket, shouldSendTo);\n\n if (isKeyframeLike) {\n for (const c of this.clients) c.needsKeyframe = false;\n }\n }\n}\n","import type net from \"node:net\";\nimport netImpl from \"node:net\";\nimport type { ReolinkBaichuanApi } from \"../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { NativeVideoStreamVariant } from \"../reolink/baichuan/types\";\nimport type { StreamProfile } from \"../reolink/baichuan/types\";\nimport type { BaichuanClient } from \"../client/BaichuanClient\";\nimport { BaichuanVideoStream } from \"../baichuan/stream/BaichuanVideoStream\";\nimport {\n CompositeStream,\n type CompositeStreamPipOptions,\n} from \"../multifocal/compositeStream\";\nimport {\n buildRfc4571Sdp,\n buildAacAudioSpecificConfigHex,\n extractH264ParamSetsFromAccessUnit,\n extractH265ParamSetsFromAccessUnit,\n parseAdtsHeader,\n Rfc4571Muxer,\n type AudioConfig,\n type VideoParamSets,\n type VideoType,\n} from \"./rfc4571\";\n// (no RTSP URL helpers needed here: we reuse buildVideoStreamOptions)\n\nexport interface Rfc4571ApiFactoryContext {\n channel?: number;\n profile: StreamProfile;\n variant?: NativeVideoStreamVariant;\n composite: boolean;\n}\n\nexport interface Rfc4571TcpServerOptions {\n /**\n * Base Baichuan API session.\n * Prefer using `getApi` when the caller wants lazy creation and/or to ensure distinct sessions.\n */\n api?: ReolinkBaichuanApi;\n\n /**\n * Optional API factory. If provided, `createRfc4571TcpServer` will call it once to obtain the base API.\n * This is useful when the caller wants to defer login/session creation until stream startup.\n */\n getApi?: (\n ctx?: Rfc4571ApiFactoryContext,\n ) => Promise<ReolinkBaichuanApi> | ReolinkBaichuanApi;\n /** Channel number. If undefined, uses composite stream (multifocal cameras). */\n channel?: number;\n /** Stream profile. For composite streams, this selects the tele profile; wider is forced to `sub` (unless `ext`). */\n profile: StreamProfile;\n /** Native-only: TrackMix tele/autotrack variants (usually on NVR/Hub). */\n variant?: NativeVideoStreamVariant;\n logger: Console;\n\n host?: string;\n videoPayloadType?: number;\n audioPayloadType?: number;\n\n /** Used only to sanity-check / early-recreate on mismatched cache in callers. */\n expectedVideoType?: VideoType;\n\n /** How long to wait for an IDR/IRAP to extract parameter sets and produce SDP. */\n keyframeTimeoutMs?: number;\n\n /**\n * Stream uptime watchdog: if no packets are sent/received for this long,\n * the server will restart its internal pipeline (drop clients, restart the native stream).\n *\n * Default: 10s. Set to 0 to disable.\n */\n uptimeRestartMs?: number;\n\n /** Optional: auto-close when no clients are connected for a while. */\n idleTeardownMs?: number;\n\n /** If true (default), closes the passed API when tearing down. */\n closeApiOnTeardown?: boolean;\n username: string;\n password: string;\n /** If true, requires authentication before allowing stream access. Default: false. */\n requireAuth?: boolean;\n\n /** Composite stream options (only used when channel is undefined) */\n compositeOptions?: CompositeStreamPipOptions;\n\n /**\n * Optional identifier for the requested stream. Used to infer composite profile pairing\n * (e.g. \"composite-native-default-sub-sub\", \"composite-rtsp-default-sub-sub\").\n */\n requestedId?: string;\n\n /**\n * Optional AAC hint for when the camera sends raw AAC (no ADTS headers).\n * Many Reolink devices use AAC-LC mono at 8000Hz.\n */\n aacAudioHint?: { sampleRate: number; channels: number };\n\n /** Optional: dedicated APIs for composite wider/tele streams (recommended on NVR/Hub to avoid stream mixing). */\n compositeApis?: {\n widerApi: ReolinkBaichuanApi;\n teleApi: ReolinkBaichuanApi;\n };\n\n /** Optional composite API factory (called once) to obtain dedicated wider/tele sessions. */\n getCompositeApis?: () =>\n | Promise<{ widerApi: ReolinkBaichuanApi; teleApi: ReolinkBaichuanApi }>\n | { widerApi: ReolinkBaichuanApi; teleApi: ReolinkBaichuanApi };\n\n /**\n * External identifier for dedicated socket session.\n * When provided, a dedicated BaichuanClient is created for this deviceId.\n * This allows multiple concurrent streams without interference.\n * The dedicated socket is automatically closed when the stream ends.\n */\n deviceId?: string;\n}\n\nexport interface Rfc4571TcpServer {\n host: string;\n port: number;\n sdp: string;\n videoType: VideoType;\n audio?: { codec: \"aac\"; sampleRate: number; channels: number };\n username: string;\n password: string;\n\n server: net.Server;\n videoStream: BaichuanVideoStream | CompositeStream;\n\n close: (reason?: unknown) => Promise<void>;\n}\n\nexport async function createRfc4571TcpServer(\n options: Rfc4571TcpServerOptions,\n): Promise<Rfc4571TcpServer> {\n const isComposite = options.channel === undefined;\n\n const parseCompositeFromRequestedId = (\n requestedId: string | undefined,\n ):\n | {\n source: \"native\" | \"rtsp\";\n widerProfile?: StreamProfile;\n teleProfile?: StreamProfile;\n }\n | undefined => {\n if (!requestedId) return;\n const id = String(requestedId);\n\n const asProfile = (v: string | undefined): StreamProfile | undefined =>\n v === \"main\" || v === \"sub\" || v === \"ext\"\n ? (v as StreamProfile)\n : undefined;\n\n // Explicit source forms:\n // - composite-native-<wider>-<tele>\n // - composite-native-<variant>-<wider>-<tele>\n // - composite-rtsp-<wider>-<tele>\n // - composite-rtsp-<variant>-<wider>-<tele>\n if (\n id.startsWith(\"composite-native-\") ||\n id.startsWith(\"composite-rtsp-\")\n ) {\n const source: \"native\" | \"rtsp\" = id.startsWith(\"composite-rtsp-\")\n ? \"rtsp\"\n : \"native\";\n const parts = id.split(\"-\").filter(Boolean);\n // parts[0] === 'composite'\n // parts[1] === 'native' | 'rtsp'\n if (parts.length >= 4) {\n const wider = parts.length >= 5 ? parts[3] : parts[2];\n const tele = parts.length >= 5 ? parts[4] : parts[3];\n const widerProfile = asProfile(wider);\n const teleProfile = asProfile(tele);\n return {\n source,\n ...(widerProfile ? { widerProfile } : {}),\n ...(teleProfile ? { teleProfile } : {}),\n };\n }\n return { source };\n }\n\n return;\n };\n\n const apiFactoryCtx: Rfc4571ApiFactoryContext = {\n profile: options.profile,\n composite: isComposite,\n ...(options.channel !== undefined ? { channel: options.channel } : {}),\n ...(options.variant !== undefined ? { variant: options.variant } : {}),\n };\n\n const baseApi = options.api ?? (await options.getApi?.(apiFactoryCtx));\n if (!baseApi) {\n throw new Error(\"createRfc4571TcpServer: missing api/getApi\");\n }\n\n const resolvedCompositeApis =\n options.compositeApis ?? (await options.getCompositeApis?.());\n\n const {\n channel,\n profile,\n variant,\n logger,\n expectedVideoType,\n host = \"127.0.0.1\",\n videoPayloadType = 96,\n audioPayloadType = 97,\n keyframeTimeoutMs = 5000,\n uptimeRestartMs: uptimeRestartMsOpt,\n idleTeardownMs = 2500,\n closeApiOnTeardown = true,\n username,\n password,\n requireAuth = false,\n compositeOptions,\n requestedId,\n aacAudioHint,\n } = options;\n\n // Track dedicated session if deviceId is provided (for auto-release on close)\n let dedicatedSession:\n | {\n client: BaichuanClient;\n release: () => Promise<void>;\n }\n | undefined;\n\n const apisToClose = new Set<ReolinkBaichuanApi>();\n apisToClose.add(baseApi);\n if (resolvedCompositeApis?.widerApi)\n apisToClose.add(resolvedCompositeApis.widerApi);\n if (resolvedCompositeApis?.teleApi)\n apisToClose.add(resolvedCompositeApis.teleApi);\n\n // For composite (ffmpeg) streams, avoid over-aggressive restarts: a short burst of\n // backpressure or a long GOP on join can look like \"no activity\" even though the pipeline is alive.\n const uptimeRestartMs = uptimeRestartMsOpt ?? (isComposite ? 60_000 : 10_000);\n const variantSuffix =\n variant && variant !== \"default\" ? ` variant=${variant}` : \"\";\n const logPrefix = isComposite\n ? `[native-rfc4571 composite profile=${profile}${variantSuffix}${requestedId ? ` id=${requestedId}` : \"\"}]`\n : `[native-rfc4571 ch=${channel} profile=${profile}${variantSuffix}]`;\n const log = (message: string) => {\n try {\n if (logger?.info) {\n logger.info(`${logPrefix} ${message}`);\n } else if (logger?.log) {\n logger.log(`${logPrefix} ${message}`);\n }\n } catch {\n // Ignore logging errors if logger is not properly initialized\n }\n };\n\n const logSessionsSummary = (action: string) => {\n const summary = baseApi.getDedicatedSessionsSummary();\n log(\n `${action} [sessions: ${summary.count} active${summary.count > 0 ? ` (${summary.keys.join(\", \")})` : \"\"}]`,\n );\n };\n\n log(\n `starting (host=${host} videoPT=${videoPayloadType} audioPT=${audioPayloadType} expectedVideoType=${expectedVideoType ?? \"n/a\"} keyframeTimeoutMs=${keyframeTimeoutMs} uptimeRestartMs=${uptimeRestartMs} idleTeardownMs=${idleTeardownMs} composite=${isComposite})`,\n );\n\n let videoStream: BaichuanVideoStream | CompositeStream;\n let isCompositeStream = false;\n\n if (isComposite) {\n // Use composite stream for multifocal cameras\n const widerChannel = compositeOptions?.widerChannel ?? 0;\n const teleChannel = compositeOptions?.teleChannel ?? 1;\n const requested = parseCompositeFromRequestedId(requestedId);\n // `options.profile` is the *output* profile requested by the caller.\n // `requestedId` encodes *input* selection (widerProfile + teleProfile).\n const outputProfile: StreamProfile = profile;\n let teleInputProfile: StreamProfile =\n requested?.teleProfile ?? outputProfile;\n\n // Default wider selection:\n // - If explicitly requested via id: obey.\n // - Otherwise: for tele=main, prefer wider=main only when it's H.264.\n // - Otherwise: wider=sub (keep ext unchanged).\n let widerMainIsH264 = false;\n try {\n const widerApiForProbe = resolvedCompositeApis?.widerApi ?? baseApi;\n const metadata: any =\n await widerApiForProbe.getStreamMetadata(widerChannel);\n const streams: any[] = Array.isArray(metadata)\n ? metadata\n : Array.isArray(metadata?.streams)\n ? metadata.streams\n : [];\n const main = streams.find((s: any) => s?.profile === \"main\");\n const enc =\n typeof main?.videoEncType === \"string\"\n ? main.videoEncType.toLowerCase()\n : \"\";\n widerMainIsH264 = enc.includes(\"264\");\n } catch {\n // ignore\n }\n\n let widerInputProfile: StreamProfile =\n requested?.widerProfile ??\n (outputProfile === \"ext\"\n ? \"ext\"\n : outputProfile === \"main\" && widerMainIsH264\n ? \"main\"\n : \"sub\");\n\n if (requested?.teleProfile && requested.teleProfile !== outputProfile) {\n log(\n `requestedId overrides tele INPUT profile (requested=${requested.teleProfile} output=${outputProfile})`,\n );\n }\n log(\n `creating composite stream: outputProfile=${outputProfile} ` +\n `wider(ch=${widerChannel}, profile=${widerInputProfile}), tele(ch=${teleChannel}, profile=${teleInputProfile}) ` +\n `source=${requested?.source ?? \"native\"} widerMainIsH264=${widerMainIsH264}`,\n );\n\n const widerApi = resolvedCompositeApis?.widerApi ?? baseApi;\n const teleApi = resolvedCompositeApis?.teleApi ?? baseApi;\n\n // Default behavior: keep `main` untouched (may be H.265), but force H.264 inputs on `sub`.\n // Callers can still override explicitly via compositeOptions.forceH264.\n const forceH264 = compositeOptions?.forceH264;\n const defaultForceH264 = outputProfile === \"sub\";\n\n // Optional: RTSP pair inputs (requested via id: composite-rtsp-...)\n let widerRtspUrl: string | undefined;\n let teleRtspUrl: string | undefined;\n if (requested?.source === \"rtsp\") {\n const onNvr = Boolean(compositeOptions?.onNvr);\n\n const resolveLensRtspUrl = async (params: {\n channel: number;\n lens: NativeVideoStreamVariant;\n desiredLens: \"wide\" | \"telephoto\";\n profile: StreamProfile;\n }): Promise<{ urlWithAuth: string; actualProfile: StreamProfile }> => {\n const { rtspStreams } = await baseApi.buildVideoStreamOptions({\n channel: params.channel,\n onNvr,\n compositeOnly: false,\n lens: params.lens,\n });\n\n const candidates = rtspStreams.filter(\n (s) =>\n s.container === \"rtsp\" &&\n s.lens === params.desiredLens &&\n Boolean(s.urlWithAuth),\n );\n\n const exact = candidates.find((s) => s.profile === params.profile);\n if (exact?.urlWithAuth) {\n return {\n urlWithAuth: exact.urlWithAuth,\n actualProfile: exact.profile,\n };\n }\n\n // Some NVR/Hub firmwares expose tele RTSP only as `main` (e.g. Preview_XX_autotrack).\n // If `sub` is requested but missing, fall back to `main`.\n if (params.profile === \"sub\") {\n const fallbackMain = candidates.find((s) => s.profile === \"main\");\n if (fallbackMain?.urlWithAuth) {\n return {\n urlWithAuth: fallbackMain.urlWithAuth,\n actualProfile: fallbackMain.profile,\n };\n }\n }\n\n const available = rtspStreams\n .filter(\n (s) => s.container === \"rtsp\" && s.lens === params.desiredLens,\n )\n .map((s) => `${s.profile}:${s.id}`)\n .join(\", \");\n\n throw new Error(\n `Requested composite RTSP inputs, but no RTSP ${params.desiredLens} stream found for channel=${params.channel} profile=${params.profile} (onNvr=${onNvr}). ` +\n `Available: [${available || \"none\"}]. ` +\n `Use composite-native-... or choose a different profile.`,\n );\n };\n\n // Wider lens always uses the default lens variant.\n const widerResolved = await resolveLensRtspUrl({\n channel: widerChannel,\n lens: \"default\",\n desiredLens: \"wide\",\n profile: widerInputProfile,\n });\n\n widerRtspUrl = widerResolved.urlWithAuth;\n widerInputProfile = widerResolved.actualProfile;\n\n // Tele lens can be on a distinct channel (standalone) or share the same channel (NVR/Hub).\n const teleResolved = await resolveLensRtspUrl({\n channel: teleChannel,\n lens: \"telephoto\",\n desiredLens: \"telephoto\",\n profile: teleInputProfile,\n });\n\n teleRtspUrl = teleResolved.urlWithAuth;\n if (teleResolved.actualProfile !== teleInputProfile) {\n log(\n `tele RTSP profile fallback applied (requested=${teleInputProfile} actual=${teleResolved.actualProfile})`,\n );\n }\n teleInputProfile = teleResolved.actualProfile;\n }\n\n videoStream = new CompositeStream({\n api: baseApi,\n widerApi,\n teleApi,\n widerChannel,\n teleChannel,\n widerProfile: widerInputProfile,\n teleProfile: teleInputProfile,\n ...(widerRtspUrl && teleRtspUrl ? { widerRtspUrl, teleRtspUrl } : {}),\n ...(compositeOptions?.rtspTransport\n ? { rtspTransport: compositeOptions.rtspTransport }\n : {}),\n pipPosition: compositeOptions?.pipPosition ?? \"bottom-right\",\n pipSize: compositeOptions?.pipSize ?? 0.25,\n // New default is percent-friendly (1%). Values > 1 are still treated as pixels.\n pipMargin: compositeOptions?.pipMargin ?? 0.01,\n ...(compositeOptions?.onNvr !== undefined\n ? { onNvr: compositeOptions.onNvr }\n : {}),\n ...(forceH264 !== undefined\n ? { forceH264 }\n : defaultForceH264\n ? { forceH264: true }\n : {}),\n ...(compositeOptions?.assumeH264Inputs !== undefined\n ? { assumeH264Inputs: compositeOptions.assumeH264Inputs }\n : {}),\n ...(compositeOptions?.disableTranscode !== undefined\n ? { disableTranscode: compositeOptions.disableTranscode }\n : {}),\n logger,\n });\n\n isCompositeStream = true;\n await videoStream.start();\n log(\n \"composite stream started; waiting for keyframe to extract parameter sets\",\n );\n } else {\n // Use regular BaichuanVideoStream\n const ch = channel!;\n\n // If deviceId is provided, create a dedicated socket session for this stream.\n // This allows multiple concurrent streams without interference.\n // BaichuanVideoStream now passes client to startVideoStream/stopVideoStream,\n // so commands go to the correct socket.\n const deviceId = options.deviceId;\n let streamClient: BaichuanClient;\n\n if (deviceId) {\n const sessionKey = `live:${deviceId}:ch${ch}:${profile}${variant && variant !== \"default\" ? `:${variant}` : \"\"}`;\n dedicatedSession = await baseApi.createDedicatedSession(\n sessionKey,\n logger,\n );\n streamClient = dedicatedSession.client;\n logSessionsSummary(`dedicated session created: ${sessionKey}`);\n } else {\n streamClient = baseApi.client;\n }\n\n videoStream = new BaichuanVideoStream({\n client: streamClient,\n api: baseApi,\n channel: ch,\n profile,\n variant,\n logger,\n });\n\n await videoStream.start();\n log(\n `stream started (ch=${ch} profile=${profile}${deviceId ? ` dedicated=${deviceId}` : \"\"})`,\n );\n }\n\n const waitForKeyframe = async (): Promise<\n { videoType: VideoType; accessUnit: Buffer } & {\n profileLevelId?: string;\n h264?: { sps: Buffer; pps: Buffer };\n h265?: { vps: Buffer; sps: Buffer; pps: Buffer };\n }\n > => {\n if (isCompositeStream) {\n // For composite stream, wait for first video frame and extract parameter sets\n return await new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n cleanup();\n reject(\n new Error(\n `Timeout waiting for keyframe on composite stream profile=${profile}`,\n ),\n );\n }, keyframeTimeoutMs);\n\n const onError = (e: unknown) => {\n cleanup();\n reject(e instanceof Error ? e : new Error(String(e)));\n };\n\n const onFrame = (frame: Buffer) => {\n // Composite stream outputs H.264 frames from ffmpeg\n // Extract parameter sets from the first frame\n const videoType: VideoType = \"H264\"; // Composite stream always outputs H.264\n\n try {\n const { sps, pps, profileLevelId } =\n extractH264ParamSetsFromAccessUnit(frame);\n if (!sps || !pps) {\n // Not a keyframe yet, wait for next\n return;\n }\n cleanup();\n resolve({\n videoType,\n accessUnit: frame,\n ...(profileLevelId ? { profileLevelId } : {}),\n h264: { sps, pps },\n });\n } catch (e) {\n // If extraction fails, wait for next frame\n return;\n }\n };\n\n const onClose = () => {\n cleanup();\n reject(\n new Error(\n `Composite stream closed before keyframe (profile=${profile})`,\n ),\n );\n };\n\n const cleanup = () => {\n clearTimeout(timeout);\n (videoStream as CompositeStream).removeListener(\n \"error\" as any,\n onError as any,\n );\n (videoStream as CompositeStream).removeListener(\n \"videoFrame\" as any,\n onFrame as any,\n );\n (videoStream as CompositeStream).removeListener(\n \"close\" as any,\n onClose as any,\n );\n };\n\n (videoStream as CompositeStream).on(\"error\" as any, onError as any);\n (videoStream as CompositeStream).on(\n \"videoFrame\" as any,\n onFrame as any,\n );\n (videoStream as CompositeStream).on(\"close\" as any, onClose as any);\n });\n } else {\n // For regular BaichuanVideoStream\n return await new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n cleanup();\n reject(\n new Error(\n `Timeout waiting for keyframe on native stream channel=${channel} profile=${profile}`,\n ),\n );\n }, keyframeTimeoutMs);\n\n const onError = (e: unknown) => {\n cleanup();\n reject(e instanceof Error ? e : new Error(String(e)));\n };\n\n const onAu = (au: any) => {\n if (!au?.isKeyframe) return;\n const videoType = au.videoType as VideoType;\n const accessUnit = au.data as Buffer;\n\n if (videoType === \"H264\") {\n const { sps, pps, profileLevelId } =\n extractH264ParamSetsFromAccessUnit(accessUnit);\n if (!sps || !pps) return;\n cleanup();\n resolve({\n videoType,\n accessUnit,\n ...(profileLevelId ? { profileLevelId } : {}),\n h264: { sps, pps },\n });\n return;\n }\n\n const { vps, sps, pps } =\n extractH265ParamSetsFromAccessUnit(accessUnit);\n if (!vps || !sps || !pps) return;\n cleanup();\n resolve({ videoType, accessUnit, h265: { vps, sps, pps } });\n };\n\n const cleanup = () => {\n clearTimeout(timeout);\n (videoStream as BaichuanVideoStream).removeListener(\n \"error\" as any,\n onError as any,\n );\n (videoStream as BaichuanVideoStream).removeListener(\n \"videoAccessUnit\" as any,\n onAu as any,\n );\n };\n\n (videoStream as BaichuanVideoStream).on(\"error\" as any, onError as any);\n (videoStream as BaichuanVideoStream).on(\n \"videoAccessUnit\" as any,\n onAu as any,\n );\n });\n }\n };\n\n let keyframe: Awaited<ReturnType<typeof waitForKeyframe>>;\n try {\n keyframe = await waitForKeyframe();\n } catch (e) {\n // IMPORTANT: if we fail before returning a server handle, no teardown will run.\n // Stop the native/composite stream pipeline here to avoid leaving watchdogs running.\n try {\n await videoStream.stop();\n } catch {\n // ignore\n }\n\n if (closeApiOnTeardown) {\n await Promise.allSettled(\n Array.from(apisToClose).map(async (a) => {\n try {\n await a.close();\n } catch {\n // ignore\n }\n }),\n );\n } else {\n // For shared connections (common on battery/BCUDP), we cannot force-close the API here.\n // Instead, ask the underlying client to drop the UDP session soon *if* it is truly idle.\n // This reduces the chance of keeping the camera awake while remaining safe.\n const graceMs = isComposite ? 5_000 : 0;\n for (const a of Array.from(apisToClose)) {\n try {\n (a as any)?.client?.requestIdleDisconnectSoon?.(\n \"rfc4571_teardown\",\n graceMs,\n );\n } catch {\n // ignore\n }\n }\n }\n\n throw e;\n }\n if (expectedVideoType && keyframe.videoType !== expectedVideoType) {\n log(\n `expectedVideoType mismatch (expected=${expectedVideoType} actual=${keyframe.videoType})`,\n );\n }\n\n // Best-effort framerate for raw elementary-stream input.\n let fps = 25;\n try {\n if (isComposite) {\n // For composite stream, get framerate from wider stream\n const widerChannel = compositeOptions?.widerChannel ?? 0;\n const widerApi = resolvedCompositeApis?.widerApi ?? baseApi;\n const metadata: any = await widerApi.getStreamMetadata(widerChannel);\n const streams: any[] = Array.isArray(metadata)\n ? metadata\n : Array.isArray(metadata?.streams)\n ? metadata.streams\n : [];\n // Note: composite FPS should be keyed off the wider input profile, not the tele profile.\n const requested = parseCompositeFromRequestedId(requestedId);\n const effectiveTeleProfile: StreamProfile =\n requested?.teleProfile ?? profile;\n let widerMainIsH264 = false;\n try {\n const main = streams.find((s: any) => s?.profile === \"main\");\n const enc =\n typeof main?.videoEncType === \"string\"\n ? main.videoEncType.toLowerCase()\n : \"\";\n widerMainIsH264 = enc.includes(\"264\");\n } catch {\n // ignore\n }\n const widerProfile: StreamProfile =\n requested?.widerProfile ??\n (effectiveTeleProfile === \"ext\"\n ? \"ext\"\n : effectiveTeleProfile === \"main\" && widerMainIsH264\n ? \"main\"\n : \"sub\");\n const stream = streams.find((s: any) => s?.profile === widerProfile);\n const fr = Number(stream?.frameRate);\n if (Number.isFinite(fr) && fr > 0) fps = fr;\n } else {\n const metadata: any = await baseApi.getStreamMetadata(channel!);\n const streams: any[] = Array.isArray(metadata)\n ? metadata\n : Array.isArray(metadata?.streams)\n ? metadata.streams\n : [];\n const stream = streams.find((s: any) => s?.profile === profile);\n const fr = Number(stream?.frameRate);\n if (Number.isFinite(fr) && fr > 0) fps = fr;\n }\n } catch {\n // ignore\n }\n\n // Prime audio: prefer ADTS (self-describing), but support raw AAC by using a hint/default config.\n // Note: CompositeStream may forward native audio frames (typically from wider input).\n let audio:\n | {\n sampleRate: number;\n channels: number;\n configHex: string;\n mode: \"adts\" | \"raw\";\n }\n | undefined;\n const tryPrimeAudio = async (): Promise<typeof audio> => {\n return await new Promise((resolve) => {\n let sawAnyAudio = false;\n let debugLogsLeft = 3;\n const audioPrimeTimeoutMs = isCompositeStream\n ? Math.min(10_000, keyframeTimeoutMs)\n : 5000;\n const timeout = setTimeout(() => {\n cleanup();\n if (!sawAnyAudio) {\n resolve(undefined);\n return;\n }\n\n const hint = aacAudioHint ?? { sampleRate: 8000, channels: 1 };\n const configHex = buildAacAudioSpecificConfigHex({\n sampleRate: hint.sampleRate,\n channels: hint.channels,\n });\n if (!configHex) {\n logger.warn(\n `Native audio frames seen but could not derive AAC config (hint sampleRate=${hint.sampleRate} channels=${hint.channels}); cannot advertise audio track.`,\n );\n resolve(undefined);\n return;\n }\n\n logger.warn(\n `Native audio frames appear to be raw AAC (no ADTS); advertising AAC using hint sampleRate=${hint.sampleRate} channels=${hint.channels}.`,\n );\n resolve({\n sampleRate: hint.sampleRate,\n channels: hint.channels,\n configHex,\n mode: \"raw\",\n });\n }, audioPrimeTimeoutMs);\n\n const onAudio = (frame: Buffer) => {\n sawAnyAudio = true;\n const parsed = parseAdtsHeader(frame);\n if (!parsed) {\n if (debugLogsLeft-- > 0) {\n const head = frame\n .subarray(0, Math.min(16, frame.length))\n .toString(\"hex\");\n logger.warn(\n `Native audioFrame not ADTS: len=${frame.length} head=${head}`,\n );\n }\n return;\n }\n cleanup();\n resolve({\n sampleRate: parsed.sampleRate,\n channels: parsed.channels,\n configHex: parsed.configHex,\n mode: \"adts\",\n });\n };\n\n const cleanup = () => {\n clearTimeout(timeout);\n (videoStream as any)?.removeListener?.(\n \"audioFrame\" as any,\n onAudio as any,\n );\n };\n\n (videoStream as any)?.on?.(\"audioFrame\" as any, onAudio as any);\n });\n };\n\n audio = await tryPrimeAudio();\n\n const video: VideoParamSets = {\n videoType: keyframe.videoType,\n payloadType: videoPayloadType,\n ...(keyframe.videoType === \"H264\"\n ? {\n h264: {\n sps: keyframe.h264!.sps,\n pps: keyframe.h264!.pps,\n ...(keyframe.profileLevelId\n ? { profileLevelId: keyframe.profileLevelId }\n : {}),\n },\n }\n : {\n h265: {\n vps: keyframe.h265!.vps,\n sps: keyframe.h265!.sps,\n pps: keyframe.h265!.pps,\n },\n }),\n };\n\n const aacAudio: AudioConfig | undefined = audio\n ? {\n codec: \"aac\",\n payloadType: audioPayloadType,\n sampleRate: audio.sampleRate,\n channels: audio.channels,\n configHex: audio.configHex,\n }\n : undefined;\n\n const sdp = buildRfc4571Sdp(video, aacAudio);\n const makeMuxer = () =>\n new Rfc4571Muxer(\n logger,\n videoPayloadType,\n aacAudio ? audioPayloadType : undefined,\n fps,\n );\n let muxer = makeMuxer();\n\n log(\n `ready (video=${keyframe.videoType} fps=${fps}${aacAudio ? ` audio=aac/${aacAudio.sampleRate}/${aacAudio.channels}` : \" audio=none\"})`,\n );\n\n let rfcClients = 0;\n const sockets = new Set<net.Socket>();\n let idleTeardownTimer: NodeJS.Timeout | undefined;\n let tearingDown = false;\n let restarting = false;\n\n // Uptime watchdog: touch this on any observed activity (RX/TX).\n let lastActivityMs = Date.now();\n const touchActivity = () => {\n lastActivityMs = Date.now();\n };\n\n const cancelIdleTeardown = () => {\n if (!idleTeardownTimer) return;\n clearTimeout(idleTeardownTimer);\n idleTeardownTimer = undefined;\n };\n\n let uptimeTimer: NodeJS.Timeout | undefined;\n const stopUptimeMonitor = () => {\n if (!uptimeTimer) return;\n clearInterval(uptimeTimer);\n uptimeTimer = undefined;\n };\n const startUptimeMonitor = () => {\n if (!uptimeRestartMs || uptimeRestartMs <= 0) return;\n if (uptimeTimer) return;\n const tickMs = Math.max(\n 250,\n Math.min(1000, Math.floor(uptimeRestartMs / 2)),\n );\n uptimeTimer = setInterval(() => {\n if (tearingDown || restarting) return;\n const idleFor = Date.now() - lastActivityMs;\n if (idleFor < uptimeRestartMs) return;\n restart(\n new Error(\n `No stream activity for ${idleFor}ms (threshold=${uptimeRestartMs}ms)`,\n ),\n ).catch(() => {});\n }, tickMs);\n };\n\n const scheduleIdleTeardown = (\n closeFn: (reason?: unknown) => Promise<void>,\n ) => {\n if (!idleTeardownMs) return;\n if (idleTeardownTimer) return;\n idleTeardownTimer = setTimeout(() => {\n idleTeardownTimer = undefined;\n if (rfcClients === 0)\n closeFn(new Error(\"No RFC4571 clients (idle)\")).catch(() => {});\n }, idleTeardownMs);\n };\n\n const server = netImpl.createServer();\n\n const restart = async (reason?: unknown): Promise<void> => {\n if (tearingDown) return;\n if (restarting) return;\n restarting = true;\n touchActivity();\n\n cancelIdleTeardown();\n\n const message =\n (reason as any)?.message || (reason as any)?.toString?.() || reason;\n const address = server.address();\n const addrStr =\n address && typeof address !== \"string\"\n ? `${address.address}:${address.port}`\n : \"unbound\";\n if (message)\n log(\n `uptime watchdog: restarting (addr=${addrStr} clients=${rfcClients} reason=${message})`,\n );\n else\n log(\n `uptime watchdog: restarting (addr=${addrStr} clients=${rfcClients})`,\n );\n\n // Drop clients first: force reconnect and clear muxer state.\n for (const s of Array.from(sockets)) {\n try {\n s.destroy();\n } catch {\n // ignore\n }\n }\n sockets.clear();\n\n try {\n muxer.close();\n } catch {\n // ignore\n }\n muxer = makeMuxer();\n\n // Restart the native stream pipeline (best-effort).\n try {\n await videoStream.stop();\n } catch {\n // ignore\n }\n try {\n await videoStream.start();\n } catch (e) {\n // If restart fails, escalate to teardown so callers don't hang forever.\n restarting = false;\n close(e).catch(() => {});\n return;\n }\n\n // For composite stream, we need to wait for a new keyframe\n // For BaichuanVideoStream, it will emit videoAccessUnit events automatically\n if (isCompositeStream) {\n // Composite stream will emit videoFrame events automatically after restart\n // No need to wait for keyframe here as we'll get frames from ffmpeg\n }\n\n restarting = false;\n touchActivity();\n log(\"uptime watchdog: restart complete\");\n\n // If no clients are connected after restart, keep existing idle teardown behavior.\n if (rfcClients === 0) scheduleIdleTeardown(close);\n };\n\n const close = async (reason?: unknown): Promise<void> => {\n if (tearingDown) return;\n tearingDown = true;\n\n stopUptimeMonitor();\n cancelIdleTeardown();\n const reasonStr =\n (reason as any)?.message ||\n (reason as any)?.toString?.() ||\n reason ||\n \"requested\";\n\n muxer.close();\n\n try {\n await videoStream.stop();\n } catch {\n // ignore\n }\n\n // Release dedicated session if one was created\n if (dedicatedSession) {\n try {\n await dedicatedSession.release();\n logSessionsSummary(\"dedicated session released\");\n } catch {\n // ignore\n }\n }\n\n if (closeApiOnTeardown) {\n await Promise.allSettled(\n Array.from(apisToClose).map(async (a) => {\n try {\n await a.close();\n } catch {\n // ignore\n }\n }),\n );\n }\n\n try {\n server.close();\n } catch {\n // ignore\n }\n\n log(`teardown (${reasonStr})`);\n };\n\n server.on(\"connection\", (socket) => {\n touchActivity();\n const remote = `${socket.remoteAddress ?? \"unknown\"}:${socket.remotePort ?? \"unknown\"}`;\n\n const setupClient = () => {\n rfcClients++;\n cancelIdleTeardown();\n sockets.add(socket);\n\n // Track RX from client (RTCP, keepalives, etc).\n socket.on(\"data\", () => touchActivity());\n\n // Track TX to client by wrapping socket.write (used by muxer).\n try {\n const origWrite = socket.write.bind(socket) as any;\n (socket as any).write = (...args: any[]) => {\n touchActivity();\n return origWrite(...args);\n };\n } catch {\n // ignore\n }\n\n muxer.addClient(socket);\n log(`client connected (${remote} total=${rfcClients})`);\n };\n\n if (!requireAuth) {\n // No authentication required, setup client immediately\n setupClient();\n } else {\n // Authentication required: expect \"username:password\\n\" as first message\n let authenticated = false;\n let authBuffer = Buffer.alloc(0);\n const authTimeout = setTimeout(() => {\n if (!authenticated) {\n log(`client authentication timeout (remote=${remote})`);\n socket.destroy();\n }\n }, 5000); // 5 second timeout\n\n const onData = (data: Buffer) => {\n touchActivity();\n\n if (!authenticated) {\n authBuffer = Buffer.concat([authBuffer, data]);\n const authString = authBuffer.toString(\"utf8\");\n const authMatch = authString.match(/^([^:]+):([^\\n]+)\\n/);\n\n if (authMatch) {\n const [, clientUsername, clientPassword] = authMatch;\n if (clientUsername === username && clientPassword === password) {\n authenticated = true;\n clearTimeout(authTimeout);\n setupClient();\n\n // Remove auth data from buffer and process remaining data\n const authLineLength = authMatch[0].length;\n const remainingData = authBuffer.subarray(authLineLength);\n\n // Replace data handler\n socket.removeListener(\"data\", onData);\n socket.on(\"data\", () => touchActivity());\n\n // Process remaining data if any\n if (remainingData.length > 0) {\n socket.emit(\"data\", remainingData);\n }\n } else {\n log(`client authentication failed (remote=${remote})`);\n socket.destroy();\n return;\n }\n } else if (authBuffer.length > 1024) {\n // Prevent buffer overflow\n log(`client authentication buffer overflow (remote=${remote})`);\n socket.destroy();\n return;\n }\n }\n };\n\n socket.on(\"data\", onData);\n }\n\n let counted = true;\n const dec = () => {\n if (!counted) return;\n counted = false;\n rfcClients = Math.max(0, rfcClients - 1);\n sockets.delete(socket);\n log(`client disconnected (${remote} total=${rfcClients})`);\n if (rfcClients === 0) scheduleIdleTeardown(close);\n };\n\n socket.once(\"close\", dec);\n socket.once(\"error\", dec);\n });\n\n // Attach stream forwarding.\n if (isCompositeStream) {\n // Composite stream emits videoFrame (Buffer) - H.264 frames from ffmpeg in Annex-B format\n (videoStream as CompositeStream).on(\n \"videoFrame\" as any,\n (frame: Buffer) => {\n touchActivity();\n try {\n // Composite stream always outputs H.264\n // Detect if it's a keyframe by checking for IDR NAL unit (type 5)\n let isKeyframe = false;\n try {\n // Check for start codes and IDR NAL units\n for (let i = 0; i < frame.length - 4; i++) {\n if (frame[i] === 0x00 && frame[i + 1] === 0x00) {\n let nalStart = -1;\n if (frame[i + 2] === 0x01) {\n nalStart = i + 3;\n } else if (frame[i + 2] === 0x00 && frame[i + 3] === 0x01) {\n nalStart = i + 4;\n }\n\n if (nalStart >= 0 && nalStart < frame.length) {\n const nalType = (frame[nalStart] ?? 0) & 0x1f;\n if (nalType === 5) {\n // IDR NAL unit - this is a keyframe\n isKeyframe = true;\n break;\n }\n }\n }\n }\n } catch {\n // If detection fails, assume it's not a keyframe\n }\n muxer.sendVideoAccessUnit(\"H264\", frame, isKeyframe, undefined);\n } catch (e) {\n close(e).catch(() => {});\n }\n },\n );\n\n if (aacAudio) {\n (videoStream as CompositeStream).on(\n \"audioFrame\" as any,\n (frame: Buffer) => {\n touchActivity();\n try {\n if (audio?.mode === \"adts\") {\n muxer.sendAudioAdtsFrame(frame);\n } else {\n muxer.sendAudioAacRawFrame(frame);\n }\n } catch (e) {\n close(e).catch(() => {});\n }\n },\n );\n }\n } else {\n // BaichuanVideoStream emits videoAccessUnit with metadata\n (videoStream as BaichuanVideoStream).on(\n \"videoAccessUnit\" as any,\n (au: any) => {\n touchActivity();\n try {\n muxer.sendVideoAccessUnit(\n au.videoType,\n au.data,\n au.isKeyframe,\n au.microseconds,\n );\n } catch (e) {\n close(e).catch(() => {});\n }\n },\n );\n\n if (aacAudio) {\n (videoStream as BaichuanVideoStream).on(\n \"audioFrame\" as any,\n (frame: Buffer) => {\n touchActivity();\n try {\n if (audio?.mode === \"adts\") {\n muxer.sendAudioAdtsFrame(frame);\n } else {\n muxer.sendAudioAacRawFrame(frame);\n }\n } catch (e) {\n close(e).catch(() => {});\n }\n },\n );\n }\n }\n\n videoStream.on(\"error\" as any, (e: unknown) => {\n if (restarting) return;\n close(e).catch(() => {});\n });\n videoStream.on(\"close\" as any, (e: unknown) => {\n if (restarting) return;\n close(e).catch(() => {});\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, host, () => resolve());\n });\n\n const address = server.address();\n if (!address || typeof address === \"string\") {\n throw new Error(\"Failed to bind RFC TCP server\");\n }\n const port = address.port;\n if (!port) throw new Error(\"Failed to bind RFC TCP server\");\n\n log(`listening (addr=${host}:${port})`);\n\n const audioInfo = aacAudio\n ? {\n codec: \"aac\" as const,\n sampleRate: aacAudio.sampleRate,\n channels: aacAudio.channels,\n }\n : undefined;\n\n // If created with no clients, schedule idle teardown.\n scheduleIdleTeardown(close);\n startUptimeMonitor();\n\n return {\n host,\n port,\n sdp,\n videoType: keyframe.videoType,\n ...(audioInfo ? { audio: audioInfo } : {}),\n username,\n password,\n server,\n videoStream,\n close,\n };\n}\n\n// =============================================================================\n// Recording Replay Server\n// =============================================================================\n\nexport interface Rfc4571ReplayServerOptions {\n /** Baichuan API session to use for replay. */\n api: ReolinkBaichuanApi;\n /** Channel number (defaults to 0 for standalone cameras). */\n channel?: number;\n /** Recording file name (e.g., \"RecM03_20250101_120000_123.264\"). */\n fileName: string;\n /** Stream type: mainStream or subStream (inferred from fileName if not specified). */\n streamType?: \"mainStream\" | \"subStream\";\n logger: Console;\n\n host?: string;\n videoPayloadType?: number;\n audioPayloadType?: number;\n\n /** How long to wait for an IDR/IRAP to extract parameter sets and produce SDP. */\n keyframeTimeoutMs?: number;\n\n /**\n * External identifier for dedicated socket session.\n * When provided, a dedicated BaichuanClient is created for this deviceId.\n * This allows multiple concurrent replay streams without interference.\n * The dedicated socket is automatically closed when the replay ends.\n */\n deviceId?: string;\n\n /** If true (default), closes the API when replay ends or server closes. */\n closeApiOnTeardown?: boolean;\n username: string;\n password: string;\n /** If true, requires authentication before allowing stream access. Default: false. */\n requireAuth?: boolean;\n\n /**\n * Optional AAC hint for when the camera sends raw AAC (no ADTS headers).\n * Many Reolink devices use AAC-LC mono at 8000Hz.\n */\n aacAudioHint?: { sampleRate: number; channels: number };\n\n /** Force NVR mode (uses id-based XML with UID) or standalone mode (name-based XML). */\n isNvr?: boolean;\n}\n\nexport interface Rfc4571ReplayServer {\n host: string;\n port: number;\n sdp: string;\n videoType: VideoType;\n audio?: { codec: \"aac\"; sampleRate: number; channels: number };\n username: string;\n password: string;\n\n server: net.Server;\n videoStream: BaichuanVideoStream;\n\n /** Called when replay naturally ends (all data sent). */\n onReplayEnd?: () => void;\n\n close: (reason?: unknown) => Promise<void>;\n}\n\n/**\n * Create an RFC4571 TCP server for streaming a recording replay.\n *\n * This is similar to createRfc4571TcpServer but optimized for playback of recorded files:\n * - Uses startRecordingReplayStream to get native frames\n * - Automatically stops when replay ends\n * - No restart logic (single playback, not continuous like live)\n */\nexport async function createRfc4571TcpServerForReplay(\n options: Rfc4571ReplayServerOptions,\n): Promise<Rfc4571ReplayServer> {\n const {\n api,\n channel = 0,\n fileName,\n logger,\n host = \"127.0.0.1\",\n videoPayloadType = 96,\n audioPayloadType = 97,\n keyframeTimeoutMs = 15_000,\n closeApiOnTeardown = false,\n username,\n password,\n requireAuth = false,\n aacAudioHint,\n isNvr,\n } = options;\n\n // Infer streamType from fileName if not provided\n const streamType =\n options.streamType ??\n (fileName.includes(\"RecS03_\") ? \"subStream\" : \"mainStream\");\n\n const deviceId = options.deviceId;\n\n const log = (msg: string, ...args: unknown[]) =>\n logger.log(\n `[RFC4571-Replay ch=${channel} file=${fileName}] ${msg}`,\n ...args,\n );\n const warn = (msg: string, ...args: unknown[]) =>\n logger.warn(\n `[RFC4571-Replay ch=${channel} file=${fileName}] ${msg}`,\n ...args,\n );\n\n log(\n `starting replay: streamType=${streamType}${deviceId ? ` deviceId=${deviceId}` : \"\"}`,\n );\n\n // Start the recording replay stream\n const replayParams: {\n channel: number;\n fileName: string;\n streamType: \"mainStream\" | \"subStream\";\n timeoutMs: number;\n logger: Console;\n isNvr?: boolean;\n deviceId?: string;\n } = {\n channel,\n fileName,\n streamType,\n timeoutMs: keyframeTimeoutMs,\n logger,\n };\n if (isNvr !== undefined) {\n replayParams.isNvr = isNvr;\n }\n if (deviceId !== undefined) {\n replayParams.deviceId = deviceId;\n }\n\n const { stream: videoStream, stop: stopReplay } =\n await api.startRecordingReplayStream(replayParams);\n\n // Audio detection\n let audio:\n | {\n sampleRate: number;\n channels: number;\n configHex: string;\n mode: \"adts\" | \"raw\";\n }\n | undefined;\n\n const waitForKeyframe = async (): Promise<\n { videoType: VideoType; accessUnit: Buffer } & {\n profileLevelId?: string;\n h264?: { sps: Buffer; pps: Buffer };\n h265?: { vps: Buffer; sps: Buffer; pps: Buffer };\n }\n > => {\n return await new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n cleanup();\n reject(\n new Error(\n `Timeout waiting for keyframe in replay (file=${fileName})`,\n ),\n );\n }, keyframeTimeoutMs);\n\n const onError = (e: unknown) => {\n cleanup();\n reject(e instanceof Error ? e : new Error(String(e)));\n };\n\n const onAu = (au: any) => {\n if (!au?.isKeyframe) return;\n const videoType = au.videoType as VideoType;\n const accessUnit = au.data as Buffer;\n\n if (videoType === \"H264\") {\n const { sps, pps, profileLevelId } =\n extractH264ParamSetsFromAccessUnit(accessUnit);\n if (!sps || !pps) return;\n cleanup();\n resolve({\n videoType,\n accessUnit,\n ...(profileLevelId ? { profileLevelId } : {}),\n h264: { sps, pps },\n });\n return;\n }\n\n const { vps, sps, pps } =\n extractH265ParamSetsFromAccessUnit(accessUnit);\n if (!vps || !sps || !pps) return;\n cleanup();\n resolve({ videoType, accessUnit, h265: { vps, sps, pps } });\n };\n\n // Listen for audio to detect format\n const onAudioFrame = (frame: Buffer) => {\n if (audio) return;\n // Try parsing as ADTS\n try {\n const adts = parseAdtsHeader(frame);\n if (adts) {\n audio = {\n sampleRate: adts.sampleRate,\n channels: adts.channels,\n configHex: adts.configHex,\n mode: \"adts\",\n };\n log(\n `detected audio via ADTS: sr=${audio.sampleRate} ch=${audio.channels}`,\n );\n }\n } catch {\n // Not ADTS, try raw AAC hint\n if (aacAudioHint) {\n const configHex = buildAacAudioSpecificConfigHex({\n sampleRate: aacAudioHint.sampleRate,\n channels: aacAudioHint.channels,\n });\n if (configHex) {\n audio = {\n sampleRate: aacAudioHint.sampleRate,\n channels: aacAudioHint.channels,\n configHex,\n mode: \"raw\",\n };\n log(\n `using AAC hint: sr=${audio.sampleRate} ch=${audio.channels}`,\n );\n }\n }\n }\n };\n\n const cleanup = () => {\n clearTimeout(timeout);\n videoStream.removeListener(\"error\" as any, onError as any);\n videoStream.removeListener(\"videoAccessUnit\" as any, onAu as any);\n videoStream.removeListener(\"audioFrame\" as any, onAudioFrame as any);\n };\n\n videoStream.on(\"error\" as any, onError as any);\n videoStream.on(\"videoAccessUnit\" as any, onAu as any);\n videoStream.on(\"audioFrame\" as any, onAudioFrame as any);\n });\n };\n\n let keyframe: Awaited<ReturnType<typeof waitForKeyframe>>;\n try {\n keyframe = await waitForKeyframe();\n } catch (e) {\n try {\n await stopReplay();\n } catch {\n // ignore\n }\n if (closeApiOnTeardown) {\n try {\n await api.close();\n } catch {\n // ignore\n }\n }\n throw e;\n }\n\n log(`video detected: codec=${keyframe.videoType}`);\n\n const video: VideoParamSets = {\n videoType: keyframe.videoType,\n payloadType: videoPayloadType,\n ...(keyframe.videoType === \"H264\"\n ? {\n h264: {\n sps: keyframe.h264!.sps,\n pps: keyframe.h264!.pps,\n ...(keyframe.profileLevelId\n ? { profileLevelId: keyframe.profileLevelId }\n : {}),\n },\n }\n : {\n h265: {\n vps: keyframe.h265!.vps,\n sps: keyframe.h265!.sps,\n pps: keyframe.h265!.pps,\n },\n }),\n };\n\n const aacAudio: AudioConfig | undefined = audio\n ? {\n codec: \"aac\",\n payloadType: audioPayloadType,\n sampleRate: audio.sampleRate,\n channels: audio.channels,\n configHex: audio.configHex,\n }\n : undefined;\n\n const sdp = buildRfc4571Sdp(video, aacAudio);\n const fps = 25; // Best-effort default\n const muxer = new Rfc4571Muxer(\n logger,\n videoPayloadType,\n aacAudio ? audioPayloadType : undefined,\n fps,\n );\n\n log(\n `SDP ready (video=${keyframe.videoType}/90000 pt=${videoPayloadType}${aacAudio ? `, audio=aac/${aacAudio.sampleRate}/${aacAudio.channels} pt=${audioPayloadType}` : \", audio=none\"})`,\n );\n\n let rfcClients = 0;\n const sockets = new Set<net.Socket>();\n let tearingDown = false;\n let replayEnded = false;\n let onReplayEndCallback: (() => void) | undefined;\n\n const close = async (reason?: unknown): Promise<void> => {\n if (tearingDown) return;\n tearingDown = true;\n\n const message =\n (reason as any)?.message || (reason as any)?.toString?.() || reason;\n if (message) log(`closing: ${message}`);\n else log(\"closing\");\n\n try {\n await stopReplay();\n } catch {\n // ignore\n }\n\n for (const s of sockets) {\n try {\n s.destroy();\n } catch {\n // ignore\n }\n }\n sockets.clear();\n\n try {\n server.close();\n } catch {\n // ignore\n }\n\n if (closeApiOnTeardown) {\n try {\n await api.close();\n } catch {\n // ignore\n }\n }\n };\n\n const server = netImpl.createServer();\n\n server.on(\"connection\", (socket) => {\n const remote = `${socket.remoteAddress}:${socket.remotePort}`;\n log(`client connected: ${remote}`);\n\n sockets.add(socket);\n rfcClients++;\n\n const setupClient = () => {\n muxer.addClient(socket);\n };\n\n if (!requireAuth) {\n // No authentication required, setup client immediately\n setupClient();\n } else {\n // Authentication required: expect \"username:password\\n\" as first message\n let authenticated = false;\n let authBuffer = Buffer.alloc(0);\n const authTimeout = setTimeout(() => {\n if (!authenticated) {\n log(`client authentication timeout (remote=${remote})`);\n socket.destroy();\n }\n }, 5000); // 5 second timeout\n\n const onData = (data: Buffer) => {\n if (!authenticated) {\n authBuffer = Buffer.concat([authBuffer, data]);\n const authString = authBuffer.toString(\"utf8\");\n const authMatch = authString.match(/^([^:]+):([^\\n]+)\\n/);\n\n if (authMatch) {\n const [, clientUsername, clientPassword] = authMatch;\n if (clientUsername === username && clientPassword === password) {\n authenticated = true;\n clearTimeout(authTimeout);\n setupClient();\n socket.removeListener(\"data\", onData);\n } else {\n log(`client authentication failed (remote=${remote})`);\n socket.destroy();\n return;\n }\n } else if (authBuffer.length > 1024) {\n log(`client authentication buffer overflow (remote=${remote})`);\n socket.destroy();\n return;\n }\n }\n };\n\n socket.on(\"data\", onData);\n }\n\n let counted = true;\n const dec = () => {\n if (!counted) return;\n counted = false;\n rfcClients = Math.max(0, rfcClients - 1);\n sockets.delete(socket);\n log(`client disconnected (remote=${remote} clients=${rfcClients})`);\n\n // If no clients and replay ended, close server\n if (rfcClients === 0 && replayEnded) {\n close(\"replay ended and no clients\").catch(() => {});\n }\n };\n\n socket.once(\"close\", dec);\n socket.once(\"error\", (e) => {\n warn(`client socket error: ${remote}`, e?.message || String(e));\n dec();\n });\n });\n\n server.on(\"error\", (e) => {\n warn(\"server error\", e?.message || String(e));\n close(e).catch(() => {});\n });\n\n // Handle video access units\n videoStream.on(\"videoAccessUnit\" as any, (au: any) => {\n if (tearingDown) return;\n try {\n muxer.sendVideoAccessUnit(\n au.videoType,\n au.data,\n au.isKeyframe,\n au.microseconds,\n );\n } catch (e) {\n close(e).catch(() => {});\n }\n });\n\n // Handle audio frames\n if (aacAudio) {\n videoStream.on(\"audioFrame\" as any, (frame: Buffer) => {\n if (tearingDown) return;\n try {\n if (audio?.mode === \"adts\") {\n muxer.sendAudioAdtsFrame(frame);\n } else {\n muxer.sendAudioAacRawFrame(frame);\n }\n } catch (e) {\n close(e).catch(() => {});\n }\n });\n }\n\n // Handle replay end\n videoStream.on(\"end\" as any, () => {\n log(\"replay ended naturally\");\n replayEnded = true;\n onReplayEndCallback?.();\n\n // If no clients, close immediately\n if (rfcClients === 0) {\n close(\"replay ended\").catch(() => {});\n }\n });\n\n videoStream.on(\"error\" as any, (e: unknown) => {\n close(e).catch(() => {});\n });\n\n videoStream.on(\"close\" as any, (e: unknown) => {\n if (!replayEnded) {\n close(e).catch(() => {});\n }\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, host, () => resolve());\n });\n\n const address = server.address();\n if (!address || typeof address === \"string\") {\n throw new Error(\"Failed to bind RFC TCP server for replay\");\n }\n const port = address.port;\n if (!port) throw new Error(\"Failed to bind RFC TCP server for replay\");\n\n log(`listening (addr=${host}:${port})`);\n\n const audioInfo = aacAudio\n ? {\n codec: \"aac\" as const,\n sampleRate: aacAudio.sampleRate,\n channels: aacAudio.channels,\n }\n : undefined;\n\n const result: Rfc4571ReplayServer = {\n host,\n port,\n sdp,\n videoType: keyframe.videoType,\n ...(audioInfo ? { audio: audioInfo } : {}),\n username,\n password,\n server,\n videoStream,\n close,\n };\n\n // Allow setting onReplayEnd callback after creation\n Object.defineProperty(result, \"onReplayEnd\", {\n get: () => onReplayEndCallback,\n set: (fn: (() => void) | undefined) => {\n onReplayEndCallback = fn;\n },\n });\n\n return result;\n}\n","/**\n * Multifocal Camera Composite Stream\n * \n * Combines streams from a multifocal camera (wider and tele) into a new composite stream\n * with configurable picture-in-picture (PIP).\n * \n * Uses ffmpeg to overlay the tele stream on the wider stream in various positions.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { EventEmitter } from \"node:events\";\nimport type { ReolinkBaichuanApi } from \"../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { StreamProfile } from \"../reolink/baichuan/types\";\nimport type { Logger } from \"../debug/DebugConfig\";\nimport { createNativeStream } from \"../rfc/helpers\";\n\nexport type PipPosition = \n | \"top-left\" \n | \"top-right\" \n | \"bottom-left\" \n | \"bottom-right\" \n | \"center\"\n | \"top-center\"\n | \"bottom-center\"\n | \"left-center\"\n | \"right-center\";\n\nexport interface CompositeStreamPipOptions {\n /** Wider channel (default: 0) */\n widerChannel?: number;\n /** Tele channel (default: 1) */\n teleChannel?: number;\n /** PIP position (default: \"bottom-right\") */\n pipPosition?: PipPosition;\n /** PIP size (default: 0.25) */\n pipSize?: number;\n /**\n * PIP margin from edge.\n * - Preferred: fraction of output size (e.g. 0.01 = 1%).\n * - Legacy: values > 1 are treated as pixels.\n */\n pipMargin?: number;\n /** True when behind NVR/Hub (tele is selected via stream variant on the same channel). */\n onNvr?: boolean;\n /**\n * Force using an H.264 input profile when available (e.g. auto-fallback to `sub` if `main` is H.265).\n * Output is always H.264 regardless.\n */\n forceH264?: boolean;\n\n /** Assume both wider+tele inputs are already H.264 and skip codec detection. */\n assumeH264Inputs?: boolean;\n /** Best-effort knob; overlay requires re-encode in ffmpeg, so this cannot fully disable encoding. */\n disableTranscode?: boolean;\n\n /**\n * Optional: provide RTSP input URLs (H.264 only) instead of native Baichuan streams.\n * When set, CompositeStream will read directly from RTSP via ffmpeg.\n */\n widerRtspUrl?: string;\n teleRtspUrl?: string;\n /** ffmpeg `-rtsp_transport` value (default: tcp). */\n rtspTransport?: 'tcp' | 'udp';\n}\n\nexport type CompositeStreamOptions = {\n api: ReolinkBaichuanApi;\n /** Optional: dedicated API for wider stream (recommended on NVR/Hub). */\n widerApi?: ReolinkBaichuanApi;\n /** Optional: dedicated API for tele stream (recommended on NVR/Hub). */\n teleApi?: ReolinkBaichuanApi;\n /** Channel for wider stream (typically 0) */\n widerChannel: number;\n /** Channel for tele stream (typically 1) */\n teleChannel: number;\n /** Profile for wider stream */\n widerProfile: StreamProfile;\n /** Profile for tele stream */\n teleProfile: StreamProfile;\n /** PIP position of tele on wider */\n pipPosition?: PipPosition;\n /** Relative size of PIP (0.1 = 10%, 0.3 = 30%, etc.) */\n pipSize?: number;\n /** Margin from edge in pixels */\n pipMargin?: number;\n /** True when behind NVR/Hub (tele is selected via stream variant on the same channel). */\n onNvr?: boolean;\n /**\n * Force using an H.264 input profile when available (e.g. auto-fallback to `sub` if `main` is H.265).\n * Output is always H.264 regardless.\n */\n forceH264?: boolean;\n\n /** Assume both wider+tele inputs are already H.264 and skip codec detection. */\n assumeH264Inputs?: boolean;\n /** Best-effort knob; overlay requires re-encode in ffmpeg, so this cannot fully disable encoding. */\n disableTranscode?: boolean;\n /** Optional logger */\n logger?: Logger;\n\n /**\n * Optional: provide RTSP input URLs (H.264 only) instead of native Baichuan streams.\n * When set, CompositeStream will read directly from RTSP via ffmpeg.\n */\n widerRtspUrl?: string;\n teleRtspUrl?: string;\n /** ffmpeg `-rtsp_transport` value (default: tcp). */\n rtspTransport?: 'tcp' | 'udp';\n /**\n * How long to wait for each input stream to produce frames before starting ffmpeg.\n * Battery cameras can take several seconds to wake and begin streaming.\n * Default: 20000ms.\n */\n inputStartupTimeoutMs?: number;\n /**\n * If true, require a keyframe during priming (preferred for fast/clean join).\n * Default: true.\n */\n requireKeyframeOnStartup?: boolean;\n};\n\n/**\n * Calculate overlay position based on requested PIP position\n */\nfunction calculateOverlayPosition(\n position: PipPosition,\n mainWidth: number,\n mainHeight: number,\n pipWidth: number,\n pipHeight: number,\n margin: number\n): { x: number; y: number } {\n const pipW = Math.floor(pipWidth);\n const pipH = Math.floor(pipHeight);\n const m = margin;\n\n switch (position) {\n case \"top-left\":\n return { x: m, y: m };\n case \"top-right\":\n return { x: mainWidth - pipW - m, y: m };\n case \"bottom-left\":\n return { x: m, y: mainHeight - pipH - m };\n case \"bottom-right\":\n return { x: mainWidth - pipW - m, y: mainHeight - pipH - m };\n case \"center\":\n return { x: Math.floor((mainWidth - pipW) / 2), y: Math.floor((mainHeight - pipH) / 2) };\n case \"top-center\":\n return { x: Math.floor((mainWidth - pipW) / 2), y: m };\n case \"bottom-center\":\n return { x: Math.floor((mainWidth - pipW) / 2), y: mainHeight - pipH - m };\n case \"left-center\":\n return { x: m, y: Math.floor((mainHeight - pipH) / 2) };\n case \"right-center\":\n return { x: mainWidth - pipW - m, y: Math.floor((mainHeight - pipH) / 2) };\n default:\n return { x: m, y: m };\n }\n}\n\n/**\n * CompositeStream - Combines wider and tele streams with configurable PIP\n */\nexport class CompositeStream extends EventEmitter<{\n videoFrame: [Buffer];\n audioFrame: [Buffer];\n error: [Error];\n close: [];\n}> {\n private options: CompositeStreamOptions;\n private widerStream: AsyncGenerator<any, void, unknown> | null = null;\n private teleStream: AsyncGenerator<any, void, unknown> | null = null;\n private ffmpegProcess: ReturnType<typeof spawn> | null = null;\n private active = false;\n private logger: Logger;\n\n private inputMode: 'native' | 'rtsp' = 'native';\n\n // ffmpeg stdout is an H.264 Annex-B byte stream; chunk boundaries are arbitrary.\n // We need to reassemble access units (frames) for RFC4571 packetization and keyframe priming.\n private ffmpegOutBuf: Buffer = Buffer.alloc(0);\n\n // Prime frames read before ffmpeg starts (used to infer codec + seed decoder).\n private widerPrimeFrame:\n | { data: Buffer; videoType?: \"H264\" | \"H265\"; isKeyframe?: boolean; microseconds?: number | null }\n | undefined;\n private telePrimeFrame:\n | { data: Buffer; videoType?: \"H264\" | \"H265\"; isKeyframe?: boolean; microseconds?: number | null }\n | undefined;\n\n private ffmpegInputOffsetSec: { wider?: number; tele?: number } | undefined;\n\n private ffmpegStderrLastLogAtMs = 0;\n private ffmpegStderrLogBurst = 0;\n\n private audioSource: 'wider' | 'tele' | undefined;\n\n private effectiveWiderProfile: StreamProfile | undefined;\n private effectiveTeleProfile: StreamProfile | undefined;\n\n private pickH264Profile(metadata: any, preferred: StreamProfile): StreamProfile {\n try {\n const isH264 = (enc?: string) => (enc ?? '').toLowerCase().includes('264');\n const streams: any[] = Array.isArray(metadata?.streams) ? metadata.streams : [];\n const byProfile = new Map<string, any>();\n for (const s of streams) byProfile.set(s.profile, s);\n\n if (isH264(byProfile.get(preferred)?.videoEncType)) return preferred;\n\n const preferredOrder: StreamProfile[] = ['sub', 'main', 'ext'];\n for (const p of preferredOrder) {\n const si = byProfile.get(p);\n if (si && isH264(si.videoEncType)) return p;\n }\n } catch {\n // ignore\n }\n\n return preferred;\n }\n\n private closeGenerator(gen: AsyncGenerator<any, void, unknown> | null): Promise<void> {\n if (!gen) return Promise.resolve();\n const r = (gen as any).return;\n if (typeof r !== 'function') return Promise.resolve();\n try {\n return Promise.resolve(r.call(gen)).then(() => undefined).catch(() => undefined);\n } catch {\n return Promise.resolve();\n }\n }\n\n constructor(options: CompositeStreamOptions) {\n super();\n this.options = {\n pipPosition: \"bottom-right\",\n pipSize: 0.25,\n pipMargin: 10,\n ...options,\n };\n this.logger = options.logger ?? console;\n }\n\n private resolvePipMarginPx(mainWidth: number, mainHeight: number): number {\n const raw = this.options.pipMargin;\n if (raw === undefined || raw === null) return 10;\n const v = Number(raw);\n if (!Number.isFinite(v) || v < 0) return 10;\n // Legacy: treat values > 1 as pixels.\n if (v > 1) return Math.floor(v);\n // New: treat as fraction (0..1) of output size.\n const base = Math.min(mainWidth, mainHeight);\n return Math.max(0, Math.floor(base * v));\n }\n\n private describeApiClient(api: ReolinkBaichuanApi | undefined): { transport?: string; host?: string; sid?: number; uid?: string } {\n const c: any = api ? (api as any).client : undefined;\n if (!c) return {};\n try {\n const transport = typeof c.getTransport === 'function' ? c.getTransport() : undefined;\n const host = typeof c.getHost === 'function' ? c.getHost() : undefined;\n const sid = typeof c.getSocketSessionId === 'function' ? c.getSocketSessionId() : undefined;\n const uid = typeof c.getUidShort === 'function' ? c.getUidShort() : undefined;\n return { transport, host, sid, uid };\n } catch {\n return {};\n }\n }\n\n private fingerprintFrame(buf: Buffer | undefined): { len: number; headHex: string; sha1_256: string } | undefined {\n if (!buf?.length) return;\n const head = buf.subarray(0, Math.min(12, buf.length)).toString('hex');\n const slice = buf.subarray(0, Math.min(256, buf.length));\n const sha1 = createHash('sha1').update(slice).digest('hex');\n return { len: buf.length, headHex: head, sha1_256: sha1 };\n }\n\n private async primeForFfmpeg(\n gen: AsyncGenerator<any, void, unknown>,\n timeoutMs: number,\n requireKeyframe: boolean,\n ): Promise<{ data: Buffer; videoType?: \"H264\" | \"H265\"; isKeyframe?: boolean; microseconds?: number | null } | undefined> {\n const start = Date.now();\n let first: { data: Buffer; videoType?: \"H264\" | \"H265\"; isKeyframe?: boolean; microseconds?: number | null } | undefined;\n\n while (Date.now() - start < timeoutMs) {\n const r = await Promise.race([\n gen.next(),\n new Promise<IteratorResult<any>>((resolve) => setTimeout(() => resolve({ value: undefined, done: false } as any), 250)),\n ]);\n\n if (!r || (r as any).done) return first;\n const v = (r as any).value;\n if (!v || v.audio) continue;\n if (!first) first = { data: v.data, videoType: v.videoType, isKeyframe: v.isKeyframe, microseconds: v.microseconds };\n if (v.isKeyframe) return { data: v.data, videoType: v.videoType, isKeyframe: v.isKeyframe, microseconds: v.microseconds };\n }\n\n return requireKeyframe ? undefined : first;\n }\n\n /**\n * Start the composite stream\n */\n async start(): Promise<void> {\n if (this.active) {\n throw new Error(\"Composite stream already active\");\n }\n\n this.active = true;\n this.audioSource = undefined;\n this.ffmpegInputOffsetSec = undefined;\n this.logger.log?.(\"[CompositeStream] Starting composite stream...\");\n\n try {\n const widerApi = this.options.widerApi ?? this.options.api;\n const teleApi = this.options.teleApi ?? this.options.api;\n\n const widerClientInfo = this.describeApiClient(widerApi);\n const teleClientInfo = this.describeApiClient(teleApi);\n const sameClient = (widerApi as any)?.client && (teleApi as any)?.client ? (widerApi as any).client === (teleApi as any).client : false;\n this.logger.log?.(\n `[CompositeStream] Inputs: wider(ch=${this.options.widerChannel}, profile=${this.options.widerProfile}) tele(ch=${this.options.teleChannel}, profile=${this.options.teleProfile}) ` +\n `onNvr=${Boolean(this.options.onNvr)} forceH264=${Boolean(this.options.forceH264)} ` +\n `sameClient=${sameClient} ` +\n `widerClient=${JSON.stringify(widerClientInfo)} teleClient=${JSON.stringify(teleClientInfo)}`,\n );\n\n // Get metadata to determine resolutions\n const widerMetadata = await widerApi.getStreamMetadata(this.options.widerChannel);\n const teleMetadata = await teleApi.getStreamMetadata(this.options.teleChannel);\n\n const forceH264 = this.options.forceH264 === true;\n const widerProfile = forceH264\n ? this.pickH264Profile(widerMetadata, this.options.widerProfile)\n : this.options.widerProfile;\n const teleProfile = forceH264\n ? this.pickH264Profile(teleMetadata, this.options.teleProfile)\n : this.options.teleProfile;\n\n this.effectiveWiderProfile = widerProfile;\n this.effectiveTeleProfile = teleProfile;\n\n const widerStreamInfo = widerMetadata.streams.find((s) => s.profile === widerProfile);\n const teleStreamInfo = teleMetadata.streams.find((s) => s.profile === teleProfile);\n\n if (!widerStreamInfo || !teleStreamInfo) {\n throw new Error(\"Stream metadata not found\");\n }\n\n const mainWidth = widerStreamInfo.width;\n const mainHeight = widerStreamInfo.height;\n // PIP size is defined as a fraction of the OUTPUT (main) dimensions.\n // This keeps the PIP consistent even when tele input resolution differs.\n const requestedPipSizeRaw = this.options.pipSize ?? 0.25;\n const pipSize = Math.min(0.9, Math.max(0.05, Number(requestedPipSizeRaw)));\n const teleAspect = teleStreamInfo.width > 0 && teleStreamInfo.height > 0\n ? teleStreamInfo.width / teleStreamInfo.height\n : 16 / 9;\n\n let pipWidth = Math.floor(mainWidth * pipSize);\n let pipHeight = Math.floor(pipWidth / teleAspect);\n\n // If height would exceed the target fraction of main height, constrain by height.\n const maxPipHeight = Math.floor(mainHeight * pipSize);\n if (pipHeight > maxPipHeight) {\n pipHeight = maxPipHeight;\n pipWidth = Math.floor(pipHeight * teleAspect);\n }\n\n // Keep dimensions even (friendlier for encoders/filters).\n pipWidth = Math.max(2, pipWidth - (pipWidth % 2));\n pipHeight = Math.max(2, pipHeight - (pipHeight % 2));\n\n const position = calculateOverlayPosition(\n this.options.pipPosition ?? \"bottom-right\",\n mainWidth,\n mainHeight,\n pipWidth,\n pipHeight,\n this.resolvePipMarginPx(mainWidth, mainHeight)\n );\n\n this.logger.log?.(\n `[CompositeStream] Main: ${mainWidth}x${mainHeight}, PIP: ${pipWidth}x${pipHeight} ` +\n `(pipSize=${pipSize}, position=${this.options.pipPosition ?? 'bottom-right'}, margin=${this.options.pipMargin ?? 10}) ` +\n `at (${position.x}, ${position.y})`\n );\n\n const widerRtspUrl = this.options.widerRtspUrl;\n const teleRtspUrl = this.options.teleRtspUrl;\n\n if (widerRtspUrl && teleRtspUrl) {\n this.inputMode = 'rtsp';\n\n // Enforce the existing rule: only RTSP H.264 pairs.\n // If callers already know it's H.264, they can set assumeH264Inputs=true.\n if (!this.options.assumeH264Inputs) {\n const isH264 = (enc?: string) => (enc ?? '').toLowerCase().includes('264');\n const widerEnc = widerStreamInfo?.videoEncType;\n const teleEnc = teleStreamInfo?.videoEncType;\n if (!isH264(widerEnc) || !isH264(teleEnc)) {\n throw new Error(\n `[CompositeStream] RTSP pair requires H.264 inputs. ` +\n `Detected wider=${widerEnc ?? 'unknown'} tele=${teleEnc ?? 'unknown'}. ` +\n `Provide RTSP URLs that are H.264 (often the substream), or set assumeH264Inputs=true if you know they are H.264.`,\n );\n }\n }\n\n await this.startFfmpegCompositionFromRtspUrls(\n mainWidth,\n mainHeight,\n pipWidth,\n pipHeight,\n position,\n widerRtspUrl,\n teleRtspUrl,\n this.options.rtspTransport ?? 'tcp',\n );\n\n this.logger.log?.('[CompositeStream] Composite stream started (rtsp inputs)');\n return;\n }\n\n this.inputMode = 'native';\n\n // Start native streams\n // On NVR/Hub, wider+tele are typically the same logical channel, and lens selection\n // happens via native stream variant (telephoto) rather than a separate channel.\n const teleIsVariantOnSameChannel =\n !!this.options.onNvr || this.options.teleChannel === this.options.widerChannel;\n\n // Wider stream.\n this.widerStream = createNativeStream(widerApi, this.options.widerChannel, widerProfile);\n this.teleStream = teleIsVariantOnSameChannel\n ? createNativeStream(teleApi, this.options.teleChannel, teleProfile, { variant: 'telephoto' })\n : createNativeStream(teleApi, this.options.teleChannel, teleProfile);\n\n // Prime both streams BEFORE starting ffmpeg. This makes codec detection resilient on NVR/Hub where\n // metadata may not reflect tele variant, and helps ffmpeg see a clean access unit early.\n // Prefer waiting for an IDR on both inputs (startup alignment). Fallback to any video frame if needed.\n const inputStartupTimeoutMs = Math.max(1000, this.options.inputStartupTimeoutMs ?? 20_000);\n // If we assume H.264 inputs, avoid sync/gating mechanisms to minimize startup latency.\n const requireKeyframeOnStartup = this.options.assumeH264Inputs ? false : (this.options.requireKeyframeOnStartup ?? true);\n\n // Prime both streams BEFORE starting ffmpeg.\n // When requireKeyframeOnStartup=true, we ONLY accept a keyframe.\n const primeKeyframeMs = inputStartupTimeoutMs;\n const primeAnyFrameMs = Math.min(5_000, Math.max(1_000, Math.floor(inputStartupTimeoutMs / 3)));\n\n if (requireKeyframeOnStartup) {\n const [widerPrime, telePrime] = await Promise.all([\n this.primeForFfmpeg(this.widerStream, primeKeyframeMs, true),\n this.primeForFfmpeg(this.teleStream, primeKeyframeMs, true),\n ]);\n this.widerPrimeFrame = widerPrime;\n this.telePrimeFrame = telePrime;\n } else {\n const [widerPrime, telePrime] = await Promise.all([\n this.primeForFfmpeg(this.widerStream, primeAnyFrameMs, false),\n this.primeForFfmpeg(this.teleStream, primeAnyFrameMs, false),\n ]);\n this.widerPrimeFrame = widerPrime;\n this.telePrimeFrame = telePrime;\n }\n\n const widerFp = this.fingerprintFrame(this.widerPrimeFrame?.data);\n const teleFp = this.fingerprintFrame(this.telePrimeFrame?.data);\n if (widerFp && teleFp) {\n const same = widerFp.sha1_256 === teleFp.sha1_256;\n this.logger.log?.(\n `[CompositeStream] Prime fingerprints: wider=${JSON.stringify(widerFp)} tele=${JSON.stringify(teleFp)} same=${same}`,\n );\n if (same) {\n this.logger.warn?.(\n `[CompositeStream] WARNING: wider+tele prime frames look identical. ` +\n `This usually means the same underlying stream is feeding both inputs (shared socket mixup, wrong channels, or device mapping).`,\n );\n }\n }\n\n this.logger.log?.(\n `[CompositeStream] Prime: wider=${this.widerPrimeFrame?.isKeyframe ? 'keyframe' : (this.widerPrimeFrame ? 'frame' : 'none')}, tele=${this.telePrimeFrame?.isKeyframe ? 'keyframe' : (this.telePrimeFrame ? 'frame' : 'none')}`\n );\n\n // Do not start ffmpeg until BOTH inputs have produced a usable frame.\n // When requireKeyframeOnStartup=true, this means BOTH must have a keyframe.\n if (!this.widerPrimeFrame || !this.telePrimeFrame) {\n const missing = [\n !this.widerPrimeFrame ? 'wider' : null,\n !this.telePrimeFrame ? 'tele' : null,\n ].filter(Boolean).join(', ');\n throw new Error(\n `[CompositeStream] Missing input frames (${missing}) within ${inputStartupTimeoutMs}ms. ` +\n `If your camera has a very long GOP/keyframe interval, startup will be slow. ` +\n `Consider increasing inputStartupTimeoutMs or enabling forceH264 (often shorter GOP on substream).`,\n );\n }\n\n // Timestamp-based sync (ffmpeg -itsoffset) adds latency and is best-effort.\n // If we assume H.264 inputs (sub+sub fast GOP), skip all sync mechanisms to minimize startup latency.\n if (!this.options.assumeH264Inputs) {\n try {\n const wUs = this.widerPrimeFrame.microseconds;\n const tUs = this.telePrimeFrame.microseconds;\n if (typeof wUs === 'number' && typeof tUs === 'number' && Number.isFinite(wUs) && Number.isFinite(tUs)) {\n const deltaSec = (tUs - wUs) / 1_000_000;\n const abs = Math.abs(deltaSec);\n // Ignore tiny jitter; cap absurd offsets.\n if (abs >= 0.2 && abs <= 60) {\n if (deltaSec > 0) {\n // Tele timestamp is later -> delay wider.\n this.ffmpegInputOffsetSec = { wider: abs };\n } else {\n // Wider timestamp is later -> delay tele.\n this.ffmpegInputOffsetSec = { tele: abs };\n }\n this.logger.log?.(\n `[CompositeStream] Input timestamp delta: widerUs=${wUs} teleUs=${tUs} deltaSec=${deltaSec.toFixed(3)} ` +\n `applying itoffset=${abs.toFixed(3)}s to ${deltaSec > 0 ? 'wider' : 'tele'}`,\n );\n }\n }\n } catch {\n // ignore\n }\n } else {\n this.ffmpegInputOffsetSec = undefined;\n }\n\n const widerCodecFromFrames = this.widerPrimeFrame?.videoType === 'H265' ? 'hevc' : (this.widerPrimeFrame?.videoType === 'H264' ? 'h264' : undefined);\n const teleCodecFromFrames = this.telePrimeFrame?.videoType === 'H265' ? 'hevc' : (this.telePrimeFrame?.videoType === 'H264' ? 'h264' : undefined);\n\n // Start ffmpeg for composition\n await this.startFfmpegComposition(mainWidth, mainHeight, pipWidth, pipHeight, position, widerCodecFromFrames, teleCodecFromFrames, this.ffmpegInputOffsetSec);\n\n this.logger.log?.(\"[CompositeStream] Composite stream started\");\n } catch (error) {\n this.active = false;\n this.emit(\"error\", error as Error);\n throw error;\n }\n }\n\n private async startFfmpegCompositionFromRtspUrls(\n mainWidth: number,\n mainHeight: number,\n pipWidth: number,\n pipHeight: number,\n position: { x: number; y: number },\n widerRtspUrl: string,\n teleRtspUrl: string,\n rtspTransport: 'tcp' | 'udp',\n ): Promise<void> {\n // With RTSP inputs, ffmpeg handles ingest/demux. We still re-encode for overlay.\n const ffmpegArgs = [\n '-hide_banner',\n '-loglevel', 'error',\n '-fflags', '+genpts',\n\n // Input 0: wider\n '-rtsp_transport', rtspTransport,\n '-i', widerRtspUrl,\n\n // Input 1: tele\n '-rtsp_transport', rtspTransport,\n '-i', teleRtspUrl,\n\n // Filter to scale and position PIP\n '-filter_complex',\n `[0:v]scale=${mainWidth}:${mainHeight}[main];[1:v]scale=${pipWidth}:${pipHeight}[pip];[main][pip]overlay=${position.x}:${position.y}[out]`,\n '-map', '[out]',\n\n // Output: always H.264 Annex-B\n '-an',\n '-c:v', 'libx264',\n '-g', '30',\n '-keyint_min', '30',\n '-sc_threshold', '0',\n '-x264-params', 'aud=1:repeat-headers=1:keyint=30:min-keyint=30:scenecut=0',\n '-preset', 'ultrafast',\n '-tune', 'zerolatency',\n '-crf', '23',\n '-f', 'h264',\n 'pipe:1',\n ];\n\n this.logger.log?.(`[CompositeStream] Starting ffmpeg (rtsp inputs): ${ffmpegArgs.join(' ')}`);\n\n this.ffmpegProcess = spawn('ffmpeg', ffmpegArgs, {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n this.ffmpegProcess.on('error', (error) => {\n this.logger.error?.('[CompositeStream] FFmpeg error:', error);\n this.emit('error', error);\n });\n\n this.ffmpegProcess.on('close', (code) => {\n if (code !== 0 && code !== null) {\n this.logger.warn?.(`[CompositeStream] FFmpeg exited with code ${code}`);\n }\n this.emit('close');\n });\n\n this.ffmpegProcess.stdout?.on('data', (data: Buffer) => {\n this.onFfmpegStdoutData(data);\n });\n\n this.ffmpegProcess.stderr?.on('data', (data: Buffer) => {\n const output = data.toString();\n if (output.includes('error') || output.includes('Error')) {\n const now = Date.now();\n if (now - this.ffmpegStderrLastLogAtMs > 2000) {\n this.ffmpegStderrLastLogAtMs = now;\n this.ffmpegStderrLogBurst = 0;\n }\n if (this.ffmpegStderrLogBurst++ < 3) {\n this.logger.error?.('[CompositeStream] FFmpeg stderr:', output);\n }\n }\n });\n }\n\n /**\n * Start ffmpeg for composition with overlay\n */\n private async startFfmpegComposition(\n mainWidth: number,\n mainHeight: number,\n pipWidth: number,\n pipHeight: number,\n position: { x: number; y: number },\n widerCodecOverride?: \"h264\" | \"hevc\",\n teleCodecOverride?: \"h264\" | \"hevc\",\n inputOffsetSec?: { wider?: number; tele?: number },\n ): Promise<void> {\n const widerApi = this.options.widerApi ?? this.options.api;\n const teleApi = this.options.teleApi ?? this.options.api;\n\n // Determine video codec from both streams\n // If metadata is not available or inaccurate, ffmpeg will auto-detect from stream data\n const widerMetadata = await widerApi.getStreamMetadata(this.options.widerChannel);\n const teleMetadata = await teleApi.getStreamMetadata(this.options.teleChannel);\n const widerProfile = this.effectiveWiderProfile ?? this.options.widerProfile;\n const teleProfile = this.effectiveTeleProfile ?? this.options.teleProfile;\n const widerStreamInfo = widerMetadata.streams.find((s) => s.profile === widerProfile);\n const teleStreamInfo = teleMetadata.streams.find((s) => s.profile === teleProfile);\n \n // Determine codec for each input stream.\n // Prefer real frame-derived codec, because on NVR/Hub tele variant can differ from metadata.\n const assumeH264Inputs = this.options.assumeH264Inputs === true;\n const widerCodec = assumeH264Inputs\n ? \"h264\"\n : (widerCodecOverride ?? (widerStreamInfo?.videoEncType?.toLowerCase().includes(\"265\") ? \"hevc\" : \"h264\"));\n const teleCodec = assumeH264Inputs\n ? \"h264\"\n : (teleCodecOverride ?? (teleStreamInfo?.videoEncType?.toLowerCase().includes(\"265\") ? \"hevc\" : \"h264\"));\n \n // Log codec detection for debugging\n this.logger.log?.(\n `[CompositeStream] Codec detection: wider=${widerCodec} (from metadata: ${widerStreamInfo?.videoEncType || \"unknown\"}), tele=${teleCodec} (from metadata: ${teleStreamInfo?.videoEncType || \"unknown\"})`\n );\n\n if (this.options.disableTranscode) {\n // Overlay requires decode+encode; we cannot truly `-c:v copy`.\n this.logger.warn?.(\n `[CompositeStream] disableTranscode requested, but overlay requires re-encode in ffmpeg; proceeding with libx264 output.`,\n );\n }\n\n // ffmpeg args for composition\n // Input 0: wider stream (main)\n // Input 1: tele stream (PIP)\n // Output: composite stream with overlay\n // Note: For raw H264/H265 streams from pipes, we need to specify the format\n // but we add flags to help ffmpeg detect the codec more reliably\n const widerInputArgs: string[] = [\n ...(inputOffsetSec?.wider ? [\"-itsoffset\", String(inputOffsetSec.wider)] : []),\n \"-f\",\n widerCodec,\n \"-i\",\n \"pipe:0\",\n ];\n const teleInputArgs: string[] = [\n ...(inputOffsetSec?.tele ? [\"-itsoffset\", String(inputOffsetSec.tele)] : []),\n \"-f\",\n teleCodec,\n \"-i\",\n \"pipe:3\",\n ];\n\n const ffmpegArgs = [\n \"-hide_banner\",\n \"-loglevel\", \"error\",\n \"-fflags\", \"+genpts\",\n \"-probesize\", \"32\", // Small probe size for faster detection\n \"-analyzeduration\", \"500000\", // 0.5 seconds to analyze stream\n // Input 0: wider stream (main)\n ...widerInputArgs,\n // Input 1: tele stream (PIP)\n ...teleInputArgs,\n // Filter to scale and position PIP\n \"-filter_complex\",\n `[0:v]scale=${mainWidth}:${mainHeight}[main];[1:v]scale=${pipWidth}:${pipHeight}[pip];[main][pip]overlay=${position.x}:${position.y}[out]`,\n \"-map\", \"[out]\",\n \"-c:v\", \"libx264\", // Re-encode for compatibility\n // Make the stream easy to join mid-flight: frequent IDRs + in-band headers + AUD.\n // Without this, a new client may wait many seconds for the next keyframe.\n \"-g\", \"30\",\n \"-keyint_min\", \"30\",\n \"-sc_threshold\", \"0\",\n \"-x264-params\", \"aud=1:repeat-headers=1:keyint=30:min-keyint=30:scenecut=0\",\n \"-preset\", \"ultrafast\",\n \"-tune\", \"zerolatency\",\n \"-crf\", \"23\",\n \"-f\", \"h264\",\n \"pipe:1\", // Output (stdout)\n ];\n\n this.logger.log?.(\n `[CompositeStream] Starting ffmpeg: ${ffmpegArgs.join(\" \")}`\n );\n\n // We need two writable inputs: stdin (fd 0) for wider, and an extra fd (fd 3) for tele.\n // stdout (fd 1) is used for the composed H.264 output.\n this.ffmpegProcess = spawn(\"ffmpeg\", ffmpegArgs, {\n stdio: [\"pipe\", \"pipe\", \"pipe\", \"pipe\"],\n });\n\n // Handle ffmpeg errors\n this.ffmpegProcess.on(\"error\", (error) => {\n this.logger.error?.(\"[CompositeStream] FFmpeg error:\", error);\n this.emit(\"error\", error);\n });\n\n this.ffmpegProcess.on(\"close\", (code) => {\n if (code !== 0 && code !== null) {\n this.logger.warn?.(`[CompositeStream] FFmpeg exited with code ${code}`);\n }\n this.emit(\"close\");\n });\n\n // Read composite video output.\n // IMPORTANT: stdout chunking is arbitrary; split by AUD (NAL type 9) into access units.\n this.ffmpegProcess.stdout?.on(\"data\", (data: Buffer) => {\n this.onFfmpegStdoutData(data);\n });\n\n // Read stderr for debug\n this.ffmpegProcess.stderr?.on(\"data\", (data: Buffer) => {\n const output = data.toString();\n if (output.includes(\"error\") || output.includes(\"Error\")) {\n // Rate-limit: ffmpeg can spam decoder errors during corruption/resync.\n const now = Date.now();\n if (now - this.ffmpegStderrLastLogAtMs > 2000) {\n this.ffmpegStderrLastLogAtMs = now;\n this.ffmpegStderrLogBurst = 0;\n }\n if (this.ffmpegStderrLogBurst++ < 3) {\n this.logger.error?.(\"[CompositeStream] FFmpeg stderr:\", output);\n }\n }\n });\n\n // Feed frames to ffmpeg inputs\n this.feedFramesToFfmpeg();\n }\n\n private onFfmpegStdoutData(data: Buffer) {\n if (!data?.length) return;\n\n // Append new bytes.\n this.ffmpegOutBuf = this.ffmpegOutBuf.length\n ? Buffer.concat([this.ffmpegOutBuf, data], this.ffmpegOutBuf.length + data.length)\n : data;\n\n // Prevent runaway buffering if AUDs are missing for any reason.\n const MAX_BUF = 8 * 1024 * 1024;\n if (this.ffmpegOutBuf.length > MAX_BUF) {\n // Try to resync: keep only the last 1MB.\n this.logger.warn?.(`[CompositeStream] ffmpeg stdout buffer grew too large (${this.ffmpegOutBuf.length} bytes); resyncing`);\n this.ffmpegOutBuf = this.ffmpegOutBuf.subarray(this.ffmpegOutBuf.length - 1024 * 1024);\n }\n\n // Split into access units.\n // Prefer AUD (NAL type 9) when present, but some encoders/paths omit AUD.\n // Fallback: split by first-slice-of-picture for H264 (nal_type 1/5 with first_mb_in_slice==0).\n const emittedUpTo = this.emitH264AccessUnitsFromBuffer();\n if (emittedUpTo > 0) {\n this.ffmpegOutBuf = this.ffmpegOutBuf.subarray(emittedUpTo);\n }\n }\n\n private emitH264AccessUnitsFromBuffer(): number {\n const buf = this.ffmpegOutBuf;\n if (!buf?.length) return 0;\n\n const len = buf.length;\n if (len < 5) return 0;\n\n const startCodeLenAt = (i: number): number => {\n if (i + 3 <= len && buf[i] === 0x00 && buf[i + 1] === 0x00) {\n if (buf[i + 2] === 0x01) return 3;\n if (i + 4 <= len && buf[i + 2] === 0x00 && buf[i + 3] === 0x01) return 4;\n }\n return 0;\n };\n\n // Align to first start code.\n let firstSc = -1;\n for (let i = 0; i < Math.min(len - 3, 64); i++) {\n if (startCodeLenAt(i)) {\n firstSc = i;\n break;\n }\n }\n if (firstSc < 0) return 0;\n if (firstSc > 0) {\n // Drop junk before first start code.\n this.ffmpegOutBuf = buf.subarray(firstSc);\n return 0;\n }\n\n // Find all start code positions we can see.\n const startCodes: number[] = [];\n for (let i = 0; i < len - 3; i++) {\n if (startCodeLenAt(i)) startCodes.push(i);\n }\n if (startCodes.length < 2) return 0; // need at least one complete NAL to parse\n\n const removeEmulationPrevention = (rbsp: Buffer): Buffer => {\n // Remove 0x03 after 0x00 0x00.\n const out: number[] = [];\n for (let i = 0; i < rbsp.length; i++) {\n const b = rbsp[i]!;\n if (i >= 2 && rbsp[i - 1] === 0x00 && rbsp[i - 2] === 0x00 && b === 0x03) {\n continue;\n }\n out.push(b);\n }\n return Buffer.from(out);\n };\n\n const readUE = (rbsp: Buffer, bitOffset: number): { value: number; next: number } | undefined => {\n const totalBits = rbsp.length * 8;\n let i = bitOffset;\n // Count leading zeros.\n let zeros = 0;\n while (i < totalBits) {\n const byte = rbsp[i >> 3]!;\n const bit = (byte >> (7 - (i & 7))) & 1;\n if (bit === 0) {\n zeros++;\n i++;\n continue;\n }\n break;\n }\n if (i >= totalBits) return;\n // Skip the 1.\n i++;\n if (zeros === 0) return { value: 0, next: i };\n if (i + zeros > totalBits) return;\n let info = 0;\n for (let k = 0; k < zeros; k++, i++) {\n const byte = rbsp[i >> 3]!;\n const bit = (byte >> (7 - (i & 7))) & 1;\n info = (info << 1) | bit;\n }\n const value = (1 << zeros) - 1 + info;\n return { value, next: i };\n };\n\n const isFirstSliceOfPicture = (nal: Buffer): boolean => {\n if (nal.length < 2) return false;\n const nalType = nal[0]! & 0x1f;\n if (nalType !== 1 && nalType !== 5) return false;\n // RBSP starts after NAL header.\n const rbsp = removeEmulationPrevention(nal.subarray(1));\n const ue = readUE(rbsp, 0);\n if (!ue) return false;\n return ue.value === 0;\n };\n\n let currentAuStart = 0;\n let sawVcl = false;\n let emittedThrough = 0;\n\n // Iterate complete NALs (exclude last, may be incomplete).\n for (let idx = 0; idx < startCodes.length - 1; idx++) {\n const scPos = startCodes[idx]!;\n const scLen = startCodeLenAt(scPos);\n if (!scLen) continue;\n const nalStart = scPos + scLen;\n const nalEnd = startCodes[idx + 1]!;\n if (nalStart >= nalEnd || nalStart >= len) continue;\n const nal = buf.subarray(nalStart, nalEnd);\n if (!nal.length) continue;\n\n const nalType = nal[0]! & 0x1f;\n const isAud = nalType === 9;\n const isVcl = nalType === 1 || nalType === 5;\n\n if (isAud) {\n // AUD always starts a new AU.\n if (sawVcl && scPos > currentAuStart) {\n const au = buf.subarray(currentAuStart, scPos);\n if (au.length) this.emit('videoFrame', au);\n emittedThrough = scPos;\n }\n currentAuStart = scPos;\n sawVcl = false;\n continue;\n }\n\n if (isVcl) {\n const firstSlice = isFirstSliceOfPicture(nal);\n if (firstSlice) {\n if (sawVcl && scPos > currentAuStart) {\n const au = buf.subarray(currentAuStart, scPos);\n if (au.length) this.emit('videoFrame', au);\n emittedThrough = scPos;\n currentAuStart = scPos;\n }\n sawVcl = true;\n } else {\n sawVcl = true;\n }\n }\n }\n\n // Only drop bytes we've safely emitted.\n return Math.max(0, emittedThrough);\n }\n\n /**\n * Feed frames from native streams to ffmpeg\n * Uses two separate loops to write frames continuously\n */\n private async feedFramesToFfmpeg(): Promise<void> {\n if (!this.ffmpegProcess || !this.widerStream || !this.teleStream) {\n return;\n }\n\n const requireKeyframeOnStartup = this.options.assumeH264Inputs ? false : (this.options.requireKeyframeOnStartup ?? true);\n\n const widerStdin = this.ffmpegProcess.stdio[0] as NodeJS.WritableStream | null;\n const teleStdin = this.ffmpegProcess.stdio[3] as NodeJS.WritableStream | null;\n\n if (!widerStdin || !teleStdin) {\n this.logger.error?.(\"[CompositeStream] FFmpeg stdin not available\");\n return;\n }\n\n // Feed wider stream (input 0)\n const feedWider = async () => {\n try {\n let widerSynced = !requireKeyframeOnStartup;\n if (this.widerPrimeFrame?.data) {\n try {\n if (!requireKeyframeOnStartup || this.widerPrimeFrame.isKeyframe) {\n const written = widerStdin.write(this.toAnnexB(this.widerPrimeFrame.data));\n widerSynced = widerSynced || Boolean(this.widerPrimeFrame.isKeyframe);\n if (!written) {\n await new Promise<void>((resolve) => {\n widerStdin.once(\"drain\", () => resolve());\n });\n }\n }\n this.widerPrimeFrame = undefined;\n } catch {\n // ignore\n }\n }\n for await (const frame of this.widerStream!) {\n if (!this.active) break;\n if (frame.audio) {\n // Prefer audio from wider; if we already locked to tele, ignore.\n if (!this.audioSource) this.audioSource = 'wider';\n if (this.audioSource === 'wider') this.emit('audioFrame', frame.data);\n continue;\n }\n\n if (!widerSynced) {\n if (!frame.isKeyframe) continue;\n widerSynced = true;\n }\n\n try {\n const written = widerStdin.write(this.toAnnexB(frame.data));\n if (!written) {\n await new Promise<void>((resolve) => {\n widerStdin.once(\"drain\", () => resolve());\n });\n }\n } catch (error) {\n const code = (error as any)?.code;\n if (code === \"EPIPE\" || code === \"ERR_STREAM_WRITE_AFTER_END\") {\n this.logger.log?.(\"[CompositeStream] FFmpeg wider stdin closed\");\n break;\n }\n this.logger.error?.(\"[CompositeStream] Error writing wider frame:\", error);\n }\n }\n } catch (error) {\n if (this.active) {\n this.logger.error?.(\"[CompositeStream] Error in wider stream:\", error);\n }\n } finally {\n try {\n widerStdin.end();\n } catch {}\n }\n };\n\n // Feed tele stream (input 1)\n const feedTele = async () => {\n try {\n let teleSynced = !requireKeyframeOnStartup;\n if (this.telePrimeFrame?.data) {\n try {\n if (!requireKeyframeOnStartup || this.telePrimeFrame.isKeyframe) {\n const written = teleStdin.write(this.toAnnexB(this.telePrimeFrame.data));\n teleSynced = teleSynced || Boolean(this.telePrimeFrame.isKeyframe);\n if (!written) {\n await new Promise<void>((resolve) => {\n teleStdin.once(\"drain\", () => resolve());\n });\n }\n }\n this.telePrimeFrame = undefined;\n } catch {\n // ignore\n }\n }\n for await (const frame of this.teleStream!) {\n if (!this.active) break;\n if (frame.audio) {\n // Fallback: if wider is silent, use tele audio.\n if (!this.audioSource) this.audioSource = 'tele';\n if (this.audioSource === 'tele') this.emit('audioFrame', frame.data);\n continue;\n }\n\n if (!teleSynced) {\n if (!frame.isKeyframe) continue;\n teleSynced = true;\n }\n\n try {\n const written = teleStdin.write(this.toAnnexB(frame.data));\n if (!written) {\n await new Promise<void>((resolve) => {\n teleStdin.once(\"drain\", () => resolve());\n });\n }\n } catch (error) {\n const code = (error as any)?.code;\n if (code === \"EPIPE\" || code === \"ERR_STREAM_WRITE_AFTER_END\") {\n this.logger.log?.(\"[CompositeStream] FFmpeg tele stdin closed\");\n break;\n }\n this.logger.error?.(\"[CompositeStream] Error writing tele frame:\", error);\n }\n }\n } catch (error) {\n if (this.active) {\n this.logger.error?.(\"[CompositeStream] Error in tele stream:\", error);\n }\n } finally {\n try {\n teleStdin.end();\n } catch {}\n }\n };\n\n // Start both feeds in parallel\n Promise.all([feedWider(), feedTele()]).catch((error) => {\n if (this.active) {\n this.logger.error?.(\"[CompositeStream] Error in frame processing:\", error);\n this.emit(\"error\", error);\n }\n });\n }\n\n private toAnnexB(accessUnit: Buffer): Buffer {\n if (!accessUnit?.length) return accessUnit;\n\n // Already Annex-B if it starts with a start code.\n if (accessUnit.length >= 3 && accessUnit[0] === 0x00 && accessUnit[1] === 0x00) {\n if (accessUnit[2] === 0x01) return accessUnit;\n if (accessUnit.length >= 4 && accessUnit[2] === 0x00 && accessUnit[3] === 0x01) return accessUnit;\n }\n\n // Best-effort conversion of length-prefixed NAL units (AVCC/HVCC style) to Annex-B.\n // Many sources use 4-byte big-endian lengths per NAL.\n try {\n let off = 0;\n const parts: Buffer[] = [];\n while (off + 4 <= accessUnit.length) {\n const n = accessUnit.readUInt32BE(off);\n off += 4;\n if (!n || off + n > accessUnit.length) {\n return accessUnit;\n }\n parts.push(Buffer.from([0x00, 0x00, 0x00, 0x01]));\n parts.push(accessUnit.subarray(off, off + n));\n off += n;\n }\n if (!parts.length) return accessUnit;\n return Buffer.concat(parts);\n } catch {\n return accessUnit;\n }\n }\n\n /**\n * Stop the composite stream\n */\n async stop(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n this.active = false;\n this.logger.log?.(\"[CompositeStream] Stopping composite stream...\");\n\n // Stop ffmpeg\n if (this.ffmpegProcess) {\n try {\n this.ffmpegProcess.stdin?.end();\n this.ffmpegProcess.kill(\"SIGTERM\");\n setTimeout(() => {\n try {\n this.ffmpegProcess?.kill(\"SIGKILL\");\n } catch {}\n }, 1000);\n } catch {}\n this.ffmpegProcess = null;\n }\n\n if (this.inputMode === 'native') {\n // Ensure native generators are terminated so their finally{} runs (stops BaichuanVideoStream + watchdog).\n // Setting fields to null is NOT enough: any running for-await loops will keep the generator alive.\n await Promise.all([\n this.closeGenerator(this.widerStream),\n this.closeGenerator(this.teleStream),\n ]);\n this.widerStream = null;\n this.teleStream = null;\n } else {\n this.widerStream = null;\n this.teleStream = null;\n }\n\n this.logger.log?.(\"[CompositeStream] Composite stream stopped\");\n }\n\n /**\n * Check if stream is active\n */\n isActive(): boolean {\n return this.active;\n }\n}\n","import http from \"node:http\";\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { PassThrough, Readable } from \"node:stream\";\nimport type { ReolinkBaichuanApi } from \"../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { BaichuanVideoStream } from \"../baichuan/stream/BaichuanVideoStream\";\n\nexport interface ReplayHttpServerOptions {\n /** Baichuan API session to use for replay. */\n api: ReolinkBaichuanApi;\n /** Channel number (defaults to 0 for standalone cameras). */\n channel?: number;\n /** Recording file name (e.g., \"RecM03_20250101_120000_123.264\"). */\n fileName: string;\n /** Stream type: mainStream or subStream (inferred from fileName if not specified). */\n streamType?: \"mainStream\" | \"subStream\";\n logger: Console;\n /** Path to ffmpeg binary */\n ffmpegPath?: string;\n /** Host to bind (default: 127.0.0.1) */\n host?: string;\n /** Force NVR mode */\n isNvr?: boolean;\n}\n\nexport interface ReplayHttpServer {\n /** URL to access the stream */\n url: string;\n host: string;\n port: number;\n /** Video codec detected */\n videoCodec: \"h264\" | \"h265\";\n /** Whether audio is present */\n hasAudio: boolean;\n /** Close the server and cleanup */\n close: () => Promise<void>;\n}\n\ninterface CachedFrame {\n type: \"video\" | \"audio\";\n data: Buffer;\n isKeyframe?: boolean;\n videoType?: \"H264\" | \"H265\";\n timestamp?: number;\n}\n\n/**\n * Create an HTTP server that streams a recording replay using ffmpeg.\n *\n * This approach:\n * 1. Starts the replay and caches all frames in memory\n * 2. Waits for frames to be cached before starting ffmpeg\n * 3. Feeds cached frames + live frames to ffmpeg\n * 4. ffmpeg outputs fragmented MP4 which is served via HTTP\n *\n * This allows progressive playback while ensuring no frames are lost.\n */\nexport async function createReplayHttpServer(\n options: ReplayHttpServerOptions,\n): Promise<ReplayHttpServer> {\n const {\n api,\n channel = 0,\n fileName,\n logger,\n ffmpegPath = \"ffmpeg\",\n host = \"127.0.0.1\",\n isNvr,\n } = options;\n\n // Infer streamType from fileName if not provided\n const streamType =\n options.streamType ??\n (fileName.includes(\"RecS03_\") ? \"subStream\" : \"mainStream\");\n\n const log = (msg: string) =>\n logger.log(`[ReplayHTTP ch=${channel} file=${fileName.slice(-30)}] ${msg}`);\n const warn = (msg: string) =>\n logger.warn(\n `[ReplayHTTP ch=${channel} file=${fileName.slice(-30)}] ${msg}`,\n );\n\n log(`starting replay: streamType=${streamType}`);\n\n // Frame cache - stores all frames until ffmpeg consumes them\n const frameCache: CachedFrame[] = [];\n let replayEnded = false;\n let videoCodec: \"h264\" | \"h265\" = \"h264\";\n let hasAudio = false;\n let firstKeyframeReceived = false;\n\n // Start the recording replay stream\n const replayParams: {\n channel: number;\n fileName: string;\n streamType: \"mainStream\" | \"subStream\";\n timeoutMs: number;\n logger: Console;\n isNvr?: boolean;\n } = {\n channel,\n fileName,\n streamType,\n timeoutMs: 30_000,\n logger,\n };\n if (isNvr !== undefined) {\n replayParams.isNvr = isNvr;\n }\n\n const { stream: videoStream, stop: stopReplay } =\n await api.startRecordingReplayStream(replayParams);\n\n // Cache frames as they arrive\n const onVideoFrame = (au: any) => {\n if (!au?.data) return;\n\n const frame: CachedFrame = {\n type: \"video\",\n data: au.data,\n isKeyframe: au.isKeyframe,\n videoType: au.videoType,\n timestamp: au.microseconds,\n };\n\n frameCache.push(frame);\n\n if (au.videoType === \"H265\") {\n videoCodec = \"h265\";\n }\n\n if (au.isKeyframe && !firstKeyframeReceived) {\n firstKeyframeReceived = true;\n log(\n `first keyframe received, codec=${videoCodec}, cached=${frameCache.length} frames`,\n );\n }\n };\n\n const onAudioFrame = (frame: Buffer) => {\n if (!frame?.length) return;\n hasAudio = true;\n\n frameCache.push({\n type: \"audio\",\n data: frame,\n });\n };\n\n const onReplayEnd = () => {\n log(`replay ended, total frames cached: ${frameCache.length}`);\n replayEnded = true;\n };\n\n videoStream.on(\"videoAccessUnit\" as any, onVideoFrame);\n videoStream.on(\"audioFrame\" as any, onAudioFrame);\n videoStream.on(\"end\" as any, onReplayEnd);\n videoStream.on(\"close\" as any, () => {\n if (!replayEnded) {\n replayEnded = true;\n }\n });\n\n // Wait for first keyframe before proceeding\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error(\"Timeout waiting for first keyframe\"));\n }, 30_000);\n\n const check = () => {\n if (firstKeyframeReceived) {\n clearTimeout(timeout);\n resolve();\n } else if (replayEnded) {\n clearTimeout(timeout);\n reject(new Error(\"Replay ended before first keyframe\"));\n } else {\n setTimeout(check, 50);\n }\n };\n check();\n });\n\n log(\n `ready to serve: codec=${videoCodec}, hasAudio=${hasAudio}, cached=${frameCache.length} frames`,\n );\n\n // HTTP server state\n let httpServer: http.Server | null = null;\n let ffmpegProcess: ChildProcess | null = null;\n let closed = false;\n\n const close = async () => {\n if (closed) return;\n closed = true;\n\n log(\"closing...\");\n\n videoStream.removeListener(\"videoAccessUnit\" as any, onVideoFrame);\n videoStream.removeListener(\"audioFrame\" as any, onAudioFrame);\n videoStream.removeListener(\"end\" as any, onReplayEnd);\n\n try {\n await stopReplay();\n } catch {\n // ignore\n }\n\n if (ffmpegProcess) {\n try {\n ffmpegProcess.kill(\"SIGKILL\");\n } catch {\n // ignore\n }\n ffmpegProcess = null;\n }\n\n if (httpServer) {\n httpServer.close();\n httpServer = null;\n }\n };\n\n // Create HTTP server\n httpServer = http.createServer((req, res) => {\n if (closed) {\n res.writeHead(503);\n res.end(\"Server closed\");\n return;\n }\n\n log(`HTTP request: ${req.method} ${req.url}`);\n\n // Set headers for streaming\n res.writeHead(200, {\n \"Content-Type\": \"video/mp4\",\n \"Transfer-Encoding\": \"chunked\",\n \"Cache-Control\": \"no-cache, no-store\",\n Connection: \"keep-alive\",\n });\n\n // Create a PassThrough stream to pipe ffmpeg output\n const outputStream = new PassThrough();\n\n // Build ffmpeg command\n // Input: raw H.264/H.265 Annex-B from stdin\n // Output: fragmented MP4 to stdout\n const inputCodec = videoCodec === \"h265\" ? \"hevc\" : \"h264\";\n const ffmpegArgs = [\n \"-hide_banner\",\n \"-loglevel\",\n \"error\",\n // Video input\n \"-f\",\n inputCodec,\n \"-i\",\n \"pipe:0\",\n // Output settings - fragmented MP4 for streaming\n \"-c:v\",\n \"copy\",\n \"-movflags\",\n \"frag_keyframe+empty_moov+default_base_moof\",\n \"-f\",\n \"mp4\",\n \"pipe:1\",\n ];\n\n log(`spawning ffmpeg: ${ffmpegPath} ${ffmpegArgs.join(\" \")}`);\n\n ffmpegProcess = spawn(ffmpegPath, ffmpegArgs, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n // Pipe ffmpeg stdout to HTTP response\n ffmpegProcess.stdout?.pipe(outputStream).pipe(res);\n\n // Log ffmpeg errors\n ffmpegProcess.stderr?.on(\"data\", (data: Buffer) => {\n const msg = data.toString().trim();\n if (msg) {\n warn(`ffmpeg: ${msg}`);\n }\n });\n\n ffmpegProcess.on(\"error\", (err) => {\n warn(`ffmpeg error: ${err.message}`);\n res.end();\n });\n\n ffmpegProcess.on(\"close\", (code) => {\n log(`ffmpeg exited with code ${code}`);\n res.end();\n });\n\n // Handle client disconnect\n req.on(\"close\", () => {\n log(\"client disconnected\");\n if (ffmpegProcess) {\n try {\n ffmpegProcess.stdin?.end();\n ffmpegProcess.kill(\"SIGTERM\");\n } catch {\n // ignore\n }\n }\n });\n\n // Feed cached frames to ffmpeg, then continue with live frames\n const feedFrames = async () => {\n const stdin = ffmpegProcess?.stdin;\n if (!stdin) return;\n\n // Send all cached video frames\n let frameIndex = 0;\n for (const frame of frameCache) {\n if (frame.type === \"video\" && frame.data) {\n try {\n const canWrite = stdin.write(frame.data);\n if (!canWrite) {\n await new Promise<void>((resolve) =>\n stdin.once(\"drain\", resolve),\n );\n }\n } catch (e) {\n warn(`error writing cached frame ${frameIndex}: ${e}`);\n break;\n }\n }\n frameIndex++;\n }\n\n log(`fed ${frameIndex} cached frames to ffmpeg`);\n\n // Continue with live frames\n const liveFrameHandler = (au: any) => {\n if (!au?.data || !stdin.writable) return;\n try {\n stdin.write(au.data);\n } catch {\n // ignore write errors\n }\n };\n\n videoStream.on(\"videoAccessUnit\" as any, liveFrameHandler);\n\n // When replay ends, close ffmpeg stdin\n const endHandler = () => {\n log(\"replay finished, closing ffmpeg stdin\");\n videoStream.removeListener(\"videoAccessUnit\" as any, liveFrameHandler);\n try {\n stdin.end();\n } catch {\n // ignore\n }\n };\n\n if (replayEnded) {\n endHandler();\n } else {\n videoStream.once(\"end\" as any, endHandler);\n videoStream.once(\"close\" as any, endHandler);\n }\n };\n\n // Start feeding frames\n feedFrames().catch((e) => {\n warn(`error feeding frames: ${e}`);\n });\n });\n\n // Start listening\n await new Promise<void>((resolve, reject) => {\n httpServer!.once(\"error\", reject);\n httpServer!.listen(0, host, () => resolve());\n });\n\n const address = httpServer.address();\n if (!address || typeof address === \"string\") {\n throw new Error(\"Failed to bind HTTP server\");\n }\n const port = address.port;\n\n const url = `http://${host}:${port}/replay.mp4`;\n log(`HTTP server listening: ${url}`);\n\n return {\n url,\n host,\n port,\n videoCodec,\n hasAudio,\n close,\n };\n}\n","/**\n * Baichuan HTTP Stream Server - Serves a Baichuan video stream over HTTP (MPEG-TS).\n * A simpler alternative to an RTSP server.\n *\n * Uses HTTP for simplicity (alternative to RTSP).\n */\n\nimport { BaichuanVideoStream } from \"./BaichuanVideoStream\";\nimport { EventEmitter } from \"node:events\";\nimport { spawn } from \"node:child_process\";\nimport * as http from \"node:http\";\nimport type { Logger } from \"../../debug/DebugConfig\";\n\nexport interface BaichuanHttpStreamServerOptions {\n videoStream: BaichuanVideoStream;\n listenPort?: number;\n path?: string; // HTTP path (es. \"/main\" o \"/sub\")\n /**\n * Input FPS to help ffmpeg generate PTS/DTS when the input is raw H.264.\n * Defaults to 25 if not provided.\n */\n inputFps?: number;\n logger?: Logger;\n}\n\nconst NAL_START_CODE_4B = Buffer.from([0x00, 0x00, 0x00, 0x01]);\nconst NAL_START_CODE_3B = Buffer.from([0x00, 0x00, 0x01]);\n\nfunction hasAnnexBStart(data: Buffer): boolean {\n if (data.length < 4) return false;\n return data.subarray(0, 4).equals(NAL_START_CODE_4B) || data.subarray(0, 3).equals(NAL_START_CODE_3B);\n}\n\nfunction splitAnnexBNals(annexB: Buffer): Buffer[] {\n // Returns NAL payloads without start codes.\n // Supports both 3B and 4B start codes.\n const starts: Array<{ idx: number; len: number }> = [];\n for (let i = 0; i < annexB.length - 3; i++) {\n if (annexB[i] === 0x00 && annexB[i + 1] === 0x00) {\n if (annexB[i + 2] === 0x01) {\n starts.push({ idx: i, len: 3 });\n i += 2;\n } else if (annexB[i + 2] === 0x00 && annexB[i + 3] === 0x01) {\n starts.push({ idx: i, len: 4 });\n i += 3;\n }\n }\n }\n if (starts.length === 0) return [];\n\n const out: Buffer[] = [];\n for (let s = 0; s < starts.length; s++) {\n const start = starts[s]!;\n const payloadStart = start.idx + start.len;\n const next = starts[s + 1];\n const payloadEnd = next ? next.idx : annexB.length;\n if (payloadEnd > payloadStart) out.push(annexB.subarray(payloadStart, payloadEnd));\n }\n return out;\n}\n\nfunction h264NalType(nalPayload: Buffer): number | null {\n if (nalPayload.length < 1) return null;\n const b0 = nalPayload[0];\n if (b0 === undefined) return null;\n return b0 & 0x1f;\n}\n\nfunction isH264KeyframeFromAnnexB(annexB: Buffer): boolean {\n const nals = splitAnnexBNals(annexB);\n for (const nal of nals) {\n const t = h264NalType(nal);\n if (t === 5) return true; // IDR\n }\n return false;\n}\n\n/**\n * BaichuanHttpStreamServer - HTTP server that serves a Baichuan video stream as MPEG-TS.\n *\n * Receives video frames from BaichuanVideoStream and streams them as HTTP MPEG-TS.\n * Uses ffmpeg to mux raw H.264 into MPEG-TS.\n */\nexport class BaichuanHttpStreamServer extends EventEmitter<{\n client: [string]; // Connected client\n error: [Error];\n close: [];\n}> {\n private videoStream: BaichuanVideoStream;\n private listenPort: number;\n private path: string; private logger: Logger; private inputFps: number;\n private httpServer: http.Server | undefined;\n private ffmpegProcess: ReturnType<typeof spawn> | undefined;\n private active = false;\n private clients = new Set<http.ServerResponse>();\n private videoListener:\n | ((data: Buffer) => void)\n | ((unit: { data: Buffer; isKeyframe: boolean }) => void)\n | undefined;\n private usingAccessUnit = false;\n private seenKeyframe = false;\n private cachedSps: Buffer | null = null; // payload without start code\n private cachedPps: Buffer | null = null; // payload without start code\n\n constructor(options: BaichuanHttpStreamServerOptions) {\n super();\n this.videoStream = options.videoStream;\n this.listenPort = options.listenPort ?? 8080;\n this.path = options.path ?? \"/stream\";\n this.inputFps = options.inputFps ?? 25;\n this.logger = options.logger ?? console;\n }\n \n /**\n * Start HTTP stream server.\n * Starts an HTTP server that serves an MPEG-TS stream.\n */\n async start(): Promise<void> {\n if (this.active) {\n throw new Error(\"HTTP stream server already active\");\n }\n\n this.logger.info(`[BaichuanHttpStreamServer] Starting Baichuan video stream...`);\n // Start the Baichuan video stream.\n await this.videoStream.start();\n this.logger.info(`[BaichuanHttpStreamServer] Baichuan video stream started`);\n\n // Crea server HTTP\n this.httpServer = http.createServer((req, res) => {\n if (req.url === this.path || req.url === `${this.path}.ts`) {\n this.logger.info(`[BaichuanHttpStreamServer] New client connected: ${req.socket.remoteAddress}`);\n this.clients.add(res);\n this.emit(\"client\", req.socket.remoteAddress || \"unknown\");\n\n // Set headers for MPEG-TS streaming.\n res.writeHead(200, {\n \"Content-Type\": \"video/mp2t\",\n \"Cache-Control\": \"no-cache\",\n \"Connection\": \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n\n // Remove the client when it disconnects.\n req.on(\"close\", () => {\n this.clients.delete(res);\n this.logger.info(`[BaichuanHttpStreamServer] Client disconnected`);\n });\n } else {\n res.writeHead(404);\n res.end(\"Not Found\");\n }\n });\n\n // Start HTTP server.\n await new Promise<void>((resolve, reject) => {\n this.httpServer!.listen(this.listenPort, \"127.0.0.1\", () => {\n this.logger.info(`[BaichuanHttpStreamServer] HTTP server listening on port ${this.listenPort}`);\n resolve();\n });\n this.httpServer!.on(\"error\", reject);\n });\n\n // Start ffmpeg to convert H.264 to MPEG-TS and send to clients\n this.logger.info(`[BaichuanHttpStreamServer] Starting ffmpeg for H.264 -> MPEG-TS conversion...`);\n \n const ffmpeg = spawn(\"ffmpeg\", [\n \"-hide_banner\",\n // ffmpeg warnings often include non-fatal decode messages (e.g. decode_slice_header),\n // which we don't want to treat as application errors.\n \"-loglevel\", \"error\",\n // Force a known frame rate on raw H.264 input so the muxer gets valid PTS/DTS.\n \"-r\", String(this.inputFps),\n \"-fflags\", \"+genpts\",\n \"-use_wallclock_as_timestamps\", \"1\",\n \"-f\", \"h264\", // Input format (H.264 Annex-B)\n \"-i\", \"pipe:0\", // Read from stdin\n \"-c:v\", \"copy\", // Copy video codec (no re-encoding)\n \"-muxpreload\", \"0\",\n \"-muxdelay\", \"0\",\n \"-f\", \"mpegts\", // Output format MPEG-TS\n \"pipe:1\", // Write to stdout\n ], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n this.ffmpegProcess = ffmpeg;\n this.logger.info(`[BaichuanHttpStreamServer] FFmpeg process started (PID: ${ffmpeg.pid})`);\n\n // Feed video frames to ffmpeg.\n let frameCount = 0;\n const writeToFfmpeg = (videoData: Buffer) => {\n // Guardrail: if we feed non-Annex-B \"frames\", ffmpeg errors and the result is a black/corrupted video.\n // Until we have perfect P-frame parsing, drop non-Annex-B payloads.\n if (!hasAnnexBStart(videoData)) {\n return;\n }\n frameCount++;\n if (frameCount === 1) {\n this.logger.info(`[BaichuanHttpStreamServer] First video frame received (${videoData.length} bytes)`);\n }\n if (ffmpeg.stdin && !ffmpeg.stdin.destroyed) {\n try {\n ffmpeg.stdin.write(videoData);\n } catch (error) {\n this.logger.error(`[BaichuanHttpStreamServer] Error writing frame: ${error}`);\n this.emit(\"error\", error instanceof Error ? error : new Error(String(error)));\n }\n }\n };\n\n // Prefer the richer event (keyframe metadata), but keep compatibility with `videoFrame`.\n this.seenKeyframe = false;\n this.usingAccessUnit = false;\n this.cachedSps = null;\n this.cachedPps = null;\n\n this.videoListener = (unit: any) => {\n const data: Buffer = Buffer.isBuffer(unit) ? unit : unit?.data;\n const isKeyframe: boolean = Buffer.isBuffer(unit) ? isH264KeyframeFromAnnexB(data) : Boolean(unit?.isKeyframe);\n if (!Buffer.isBuffer(data)) return;\n\n // Avoid double-feeding: BaichuanVideoStream emits both `videoFrame` and `videoAccessUnit`.\n // If we are receiving `videoAccessUnit`, ignore `videoFrame` (Buffer) callbacks.\n if (!Buffer.isBuffer(unit)) {\n this.usingAccessUnit = true;\n } else if (this.usingAccessUnit) {\n return;\n }\n\n // Cache SPS/PPS (H.264) if present in the access unit.\n const nals = splitAnnexBNals(data);\n for (const nal of nals) {\n const t = h264NalType(nal);\n if (t === 7) this.cachedSps = nal;\n if (t === 8) this.cachedPps = nal;\n }\n\n // Do not feed ffmpeg until we see a keyframe: avoids starting on P-frames.\n if (!this.seenKeyframe) {\n if (!isKeyframe) return;\n this.seenKeyframe = true;\n this.logger.info(`[BaichuanHttpStreamServer] First keyframe received: starting ffmpeg feed`);\n }\n\n // If we have cached SPS/PPS, prepend them before keyframes for robustness (some muxers/players require it).\n if (isKeyframe && this.cachedSps && this.cachedPps) {\n // Avoid duplicates if already present\n let hasSps = false;\n let hasPps = false;\n for (const nal of nals) {\n const t = h264NalType(nal);\n if (t === 7) hasSps = true;\n if (t === 8) hasPps = true;\n }\n if (!hasSps || !hasPps) {\n const patched = Buffer.concat([\n NAL_START_CODE_4B, this.cachedSps,\n NAL_START_CODE_4B, this.cachedPps,\n data,\n ]);\n writeToFfmpeg(patched);\n return;\n }\n }\n\n writeToFfmpeg(data);\n };\n\n // Register both: if `videoAccessUnit` arrives we will use it; `videoFrame` remains for compatibility.\n this.videoStream.on(\"videoAccessUnit\" as any, this.videoListener as any);\n this.videoStream.on(\"videoFrame\", this.videoListener as any);\n\n // Broadcast ffmpeg MPEG-TS output to HTTP clients.\n ffmpeg.stdout.on(\"data\", (data: Buffer) => {\n // Send to all connected clients.\n for (const client of this.clients) {\n if (!client.destroyed) {\n try {\n client.write(data);\n } catch (error) {\n // Client disconnected; remove it.\n this.clients.delete(client);\n }\n }\n }\n });\n\n // Handle ffmpeg stderr.\n let ffmpegOutput = \"\";\n ffmpeg.stderr.on(\"data\", (data) => {\n const output = data.toString();\n ffmpegOutput += output;\n \n // With -loglevel error we only get errors here, but many are still \"non-fatal\"\n // during startup or on corrupted frames. Avoid crashing the app (unhandled 'error' event).\n const isKnownNonFatal =\n output.includes(\"top block unavailable\") ||\n output.includes(\"error while decoding\") ||\n output.includes(\"decode_slice_header error\") ||\n output.includes(\"no frame\") ||\n output.includes(\"concealing\") ||\n output.includes(\"left block unavailable\") ||\n output.includes(\"bottom block unavailable\");\n\n if (isKnownNonFatal) {\n // Track but do not emit 'error'\n this.logger.warn(`[BaichuanHttpStreamServer] FFmpeg decode warning: ${output.trim()}`);\n return;\n }\n\n // Truly critical errors (invalid input / broken pipe / muxer failure).\n const isCriticalError =\n output.includes(\"Invalid data found\") ||\n output.includes(\"Error opening\") ||\n output.includes(\"Could not write header\") ||\n output.includes(\"Broken pipe\") ||\n output.includes(\"Connection refused\") ||\n output.includes(\"Immediate exit\") ||\n output.includes(\"Conversion failed\");\n\n if (isCriticalError) {\n this.logger.error(`[BaichuanHttpStreamServer] FFmpeg critical error: ${output.trim()}`);\n // Emit 'error' only for truly terminal conditions.\n this.emit(\"error\", new Error(`FFmpeg error: ${output}`));\n } else {\n this.logger.warn(`[BaichuanHttpStreamServer] FFmpeg stderr: ${output.trim()}`);\n }\n });\n\n ffmpeg.on(\"close\", (code) => {\n if (code !== 0) {\n this.logger.error(`[BaichuanHttpStreamServer] FFmpeg exited with code ${code}`);\n this.emit(\"error\", new Error(`FFmpeg exited with code ${code}`));\n }\n this.active = false;\n this.emit(\"close\");\n });\n\n this.active = true;\n }\n\n /**\n * Get HTTP URL for this stream.\n */\n getStreamUrl(): string {\n return `http://127.0.0.1:${this.listenPort}${this.path}.ts`;\n }\n\n /**\n * Stop HTTP stream server.\n */\n async stop(): Promise<void> {\n // Stop must be idempotent: even if `active` is already false (e.g. ffmpeg crashed),\n // we still need to close server/socket and kill processes to allow Node to exit.\n\n // Close all clients\n for (const client of this.clients) {\n if (!client.destroyed) {\n client.end();\n }\n }\n this.clients.clear();\n\n // Stop the video stream\n try {\n await this.videoStream.stop();\n } catch {\n // ignore\n }\n\n // Remove listeners to avoid leaks between start/stop\n if (this.videoListener) {\n this.videoStream.removeListener(\"videoAccessUnit\" as any, this.videoListener as any);\n this.videoStream.removeListener(\"videoFrame\", this.videoListener as any);\n }\n this.videoListener = undefined;\n\n // Stop ffmpeg\n if (this.ffmpegProcess) {\n const proc = this.ffmpegProcess;\n try {\n proc.kill(\"SIGTERM\");\n } catch {\n // ignore\n }\n // If it doesn't exit, force SIGKILL so it doesn't hang.\n await new Promise<void>((resolve) => {\n const t = setTimeout(() => {\n try {\n proc.kill(\"SIGKILL\");\n } catch {\n // ignore\n }\n resolve();\n }, 1500);\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n (t as any)?.unref?.();\n proc.once(\"close\", () => {\n clearTimeout(t);\n resolve();\n });\n });\n }\n this.ffmpegProcess = undefined;\n\n // Stop HTTP server\n if (this.httpServer) {\n await new Promise<void>((resolve) => {\n // best-effort: close connections and then the server (modern Node versions)\n (this.httpServer as any)?.closeAllConnections?.();\n (this.httpServer as any)?.closeIdleConnections?.();\n this.httpServer!.close(() => resolve());\n });\n this.httpServer = undefined;\n }\n\n this.active = false;\n }\n\n isActive(): boolean {\n return this.active;\n }\n}\n\n","/**\n * Baichuan MJPEG Server - HTTP server that serves MJPEG streams from Baichuan cameras\n *\n * Converts H.264/H.265 video streams to MJPEG for browser viewing.\n * Supports multiple concurrent viewers with efficient stream sharing.\n */\n\nimport { EventEmitter } from \"node:events\";\nimport * as http from \"node:http\";\nimport type { StreamProfile } from \"../../reolink/baichuan/types\";\nimport type { ReolinkBaichuanApi } from \"../../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { NativeVideoStreamVariant } from \"../../reolink/baichuan/types\";\nimport { createNativeStream } from \"../../rfc/helpers\";\nimport {\n MjpegTransformer,\n createMjpegBoundary,\n getMjpegContentType,\n formatMjpegFrame,\n type MjpegFrame,\n} from \"./MjpegTransformer\";\nimport { convertToAnnexB as convertH264ToAnnexB } from \"./H264Converter\";\nimport { convertToAnnexB as convertH265ToAnnexB } from \"./H265Converter\";\nimport { detectVideoCodecFromNal } from \"./BcMediaAnnexBDecoder\";\n\nexport interface BaichuanMjpegServerOptions {\n /** API instance (required) */\n api: ReolinkBaichuanApi;\n /** Channel number (required) */\n channel: number;\n /** Stream profile (required) */\n profile: StreamProfile;\n /** Native-only: TrackMix tele/autotrack variants */\n variant?: NativeVideoStreamVariant;\n /** HTTP server port (default: 8080) */\n port?: number;\n /** HTTP server host (default: \"0.0.0.0\") */\n host?: string;\n /** URL path for MJPEG stream (default: \"/mjpeg\") */\n path?: string;\n /** JPEG quality (1-31, lower is better, default: 5) */\n quality?: number;\n /** Output width (optional) */\n width?: number;\n /** Output height (optional) */\n height?: number;\n /** Max FPS (optional) */\n maxFps?: number;\n /** Logger callback */\n logger?: (\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n ) => void;\n}\n\ninterface MjpegClient {\n id: string;\n response: http.ServerResponse;\n boundary: string;\n connectedAt: number;\n}\n\n/**\n * BaichuanMjpegServer - MJPEG HTTP server for Baichuan video streams\n *\n * Events:\n * - 'started': Server started\n * - 'stopped': Server stopped\n * - 'client-connected': Client connected\n * - 'client-disconnected': Client disconnected\n * - 'error': Error occurred\n */\nexport class BaichuanMjpegServer extends EventEmitter {\n private readonly options: BaichuanMjpegServerOptions;\n private readonly clients = new Map<string, MjpegClient>();\n private httpServer: http.Server | null = null;\n private transformer: MjpegTransformer | null = null;\n private nativeStream: AsyncGenerator<any, void, unknown> | null = null;\n private streamPump: Promise<void> | null = null;\n private detectedCodec: \"h264\" | \"h265\" | null = null;\n private started = false;\n private clientIdCounter = 0;\n\n constructor(options: BaichuanMjpegServerOptions) {\n super();\n this.options = options;\n }\n\n /**\n * Start the MJPEG server\n */\n async start(): Promise<void> {\n if (this.started) return;\n this.started = true;\n\n const port = this.options.port ?? 8080;\n const host = this.options.host ?? \"0.0.0.0\";\n const path = this.options.path ?? \"/mjpeg\";\n\n this.httpServer = http.createServer((req, res) => {\n this.handleRequest(req, res, path);\n });\n\n return new Promise<void>((resolve, reject) => {\n this.httpServer!.on(\"error\", (err) => {\n this.log(\"error\", `HTTP server error: ${err.message}`);\n reject(err);\n });\n\n this.httpServer!.listen(port, host, () => {\n this.log(\n \"info\",\n `MJPEG server started on http://${host}:${port}${path}`,\n );\n this.emit(\"started\", { host, port, path });\n resolve();\n });\n });\n }\n\n /**\n * Stop the MJPEG server\n */\n async stop(): Promise<void> {\n if (!this.started) return;\n this.started = false;\n\n // Close all clients\n for (const [id, client] of this.clients) {\n try {\n client.response.end();\n } catch {\n // ignore\n }\n this.clients.delete(id);\n }\n\n // Stop stream\n await this.stopStream();\n\n // Close HTTP server\n if (this.httpServer) {\n await new Promise<void>((resolve) => {\n this.httpServer!.close(() => resolve());\n });\n this.httpServer = null;\n }\n\n this.log(\"info\", \"MJPEG server stopped\");\n this.emit(\"stopped\");\n }\n\n /**\n * Handle HTTP request\n */\n private handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n expectedPath: string,\n ): void {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host}`);\n\n if (url.pathname !== expectedPath) {\n res.statusCode = 404;\n res.end(\"Not Found\");\n return;\n }\n\n if (req.method !== \"GET\") {\n res.statusCode = 405;\n res.end(\"Method Not Allowed\");\n return;\n }\n\n this.handleMjpegClient(req, res);\n }\n\n /**\n * Handle new MJPEG client\n */\n private handleMjpegClient(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ): void {\n const clientId = `client-${++this.clientIdCounter}`;\n const boundary = createMjpegBoundary();\n\n const client: MjpegClient = {\n id: clientId,\n response: res,\n boundary,\n connectedAt: Date.now(),\n };\n\n this.clients.set(clientId, client);\n this.log(\n \"info\",\n `MJPEG client connected: ${clientId} (total: ${this.clients.size})`,\n );\n this.emit(\"client-connected\", { id: clientId, total: this.clients.size });\n\n // Set MJPEG headers\n res.writeHead(200, {\n \"Content-Type\": getMjpegContentType(boundary),\n \"Cache-Control\": \"no-cache, no-store, must-revalidate\",\n Pragma: \"no-cache\",\n Expires: \"0\",\n Connection: \"close\",\n });\n\n // Handle client disconnect\n const cleanup = () => {\n this.clients.delete(clientId);\n this.log(\n \"info\",\n `MJPEG client disconnected: ${clientId} (remaining: ${this.clients.size})`,\n );\n this.emit(\"client-disconnected\", {\n id: clientId,\n total: this.clients.size,\n });\n\n // Stop stream if no clients\n if (this.clients.size === 0) {\n this.stopStream();\n }\n };\n\n req.on(\"close\", cleanup);\n res.on(\"close\", cleanup);\n res.on(\"error\", cleanup);\n\n // Start stream if not running\n if (!this.transformer) {\n this.startStream();\n }\n }\n\n /**\n * Start the native video stream and MJPEG transformer\n */\n private async startStream(): Promise<void> {\n if (this.transformer) return;\n\n this.log(\"info\", \"Starting native video stream...\");\n\n const { api, channel, profile, variant, quality, width, height, maxFps } =\n this.options;\n\n try {\n // Create native stream\n this.nativeStream = createNativeStream(\n api,\n channel,\n profile,\n variant ? { variant } : undefined,\n );\n\n // Pump stream to detect codec and get frames\n this.streamPump = this.pumpStream();\n } catch (err) {\n this.log(\"error\", `Failed to start stream: ${err}`);\n this.emit(\"error\", err);\n }\n }\n\n /**\n * Pump native stream and feed to transformer\n */\n private async pumpStream(): Promise<void> {\n if (!this.nativeStream) return;\n\n let frameBuffer: Buffer[] = [];\n let waitingForKeyframe = true;\n\n try {\n for await (const frame of this.nativeStream) {\n if (!this.started || this.clients.size === 0) break;\n\n // Frame has: type, data, microseconds, videoType\n const { type, data, microseconds, videoType } = frame;\n\n if (type !== \"Iframe\" && type !== \"Pframe\") continue;\n if (!data || data.length === 0) continue;\n\n // Convert to Annex-B format\n let annexB: Buffer;\n if (videoType === \"H265\") {\n annexB = convertH265ToAnnexB(data);\n if (!this.detectedCodec) {\n this.detectedCodec = \"h265\";\n this.initTransformer();\n }\n } else {\n annexB = convertH264ToAnnexB(data);\n if (!this.detectedCodec) {\n this.detectedCodec = \"h264\";\n this.initTransformer();\n }\n }\n\n // Wait for keyframe to start\n if (waitingForKeyframe) {\n if (type === \"Iframe\") {\n waitingForKeyframe = false;\n } else {\n continue;\n }\n }\n\n // Push to transformer\n if (this.transformer) {\n this.transformer.push(annexB, microseconds);\n }\n }\n } catch (err) {\n if (this.started) {\n this.log(\"error\", `Stream error: ${err}`);\n this.emit(\"error\", err);\n }\n }\n }\n\n /**\n * Initialize MJPEG transformer once codec is detected\n */\n private initTransformer(): void {\n if (this.transformer || !this.detectedCodec) return;\n\n const { quality, width, height, maxFps } = this.options;\n\n this.transformer = new MjpegTransformer({\n codec: this.detectedCodec,\n quality,\n width,\n height,\n maxFps,\n logger: this.options.logger,\n });\n\n this.transformer.on(\"frame\", (frame: MjpegFrame) => {\n this.broadcastFrame(frame);\n });\n\n this.transformer.on(\"error\", (err) => {\n this.log(\"error\", `Transformer error: ${err}`);\n });\n\n this.transformer.on(\"close\", () => {\n this.log(\"debug\", \"Transformer closed\");\n });\n\n this.transformer.start();\n this.log(\n \"info\",\n `MJPEG transformer started (codec: ${this.detectedCodec})`,\n );\n }\n\n /**\n * Broadcast JPEG frame to all connected clients\n */\n private broadcastFrame(frame: MjpegFrame): void {\n for (const client of this.clients.values()) {\n try {\n const mjpegData = formatMjpegFrame(frame.data, client.boundary);\n client.response.write(mjpegData);\n } catch {\n // Client might have disconnected\n }\n }\n }\n\n /**\n * Stop the stream and transformer\n */\n private async stopStream(): Promise<void> {\n // Stop transformer\n if (this.transformer) {\n await this.transformer.stop();\n this.transformer = null;\n }\n\n // Stop native stream\n if (this.nativeStream) {\n try {\n await this.nativeStream.return(undefined as any);\n } catch {\n // ignore\n }\n this.nativeStream = null;\n }\n\n // Wait for pump to finish\n if (this.streamPump) {\n try {\n await this.streamPump;\n } catch {\n // ignore\n }\n this.streamPump = null;\n }\n\n this.detectedCodec = null;\n this.log(\"debug\", \"Stream stopped\");\n }\n\n /**\n * Get current number of connected clients\n */\n getClientCount(): number {\n return this.clients.size;\n }\n\n /**\n * Get server status\n */\n getStatus(): {\n running: boolean;\n clients: number;\n codec: string | null;\n frames: number;\n } {\n return {\n running: this.started,\n clients: this.clients.size,\n codec: this.detectedCodec,\n frames: this.transformer?.getFrameCount() ?? 0,\n };\n }\n\n private log(\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n ): void {\n this.options.logger?.(level, `[BaichuanMjpegServer] ${message}`);\n }\n}\n","/**\n * MJPEG Transformer - Converts H.264/H.265 video frames to MJPEG stream\n *\n * Uses FFmpeg as a persistent process for efficient decoding and JPEG encoding.\n * Receives Annex-B access units and outputs JPEG frames.\n */\n\nimport { EventEmitter } from \"node:events\";\nimport { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\n\n/** Logger callback type for MjpegTransformer */\nexport type LoggerCallback = (\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n) => void;\n\nexport interface MjpegTransformerOptions {\n /** Video codec (required) */\n codec: \"h264\" | \"h265\";\n /** JPEG quality (1-31, lower is better, default: 5) */\n quality?: number | undefined;\n /** Output width (optional, maintains aspect ratio if only one dimension set) */\n width?: number | undefined;\n /** Output height (optional, maintains aspect ratio if only one dimension set) */\n height?: number | undefined;\n /** Frame rate limit (optional, e.g., 10 for max 10 fps) */\n maxFps?: number | undefined;\n /** Logger callback */\n logger?: LoggerCallback | undefined;\n}\n\nexport interface MjpegFrame {\n /** JPEG data */\n data: Buffer;\n /** Timestamp in microseconds */\n timestamp: number;\n}\n\nconst JPEG_SOI = Buffer.from([0xff, 0xd8]); // Start of Image\nconst JPEG_EOI = Buffer.from([0xff, 0xd9]); // End of Image\n\n/**\n * MjpegTransformer - Transforms H.264/H.265 access units to JPEG frames\n *\n * Events:\n * - 'frame': Emitted when a JPEG frame is ready (MjpegFrame)\n * - 'error': Emitted on error\n * - 'close': Emitted when transformer is closed\n */\nexport class MjpegTransformer extends EventEmitter {\n private readonly options: Required<\n Omit<MjpegTransformerOptions, \"logger\" | \"width\" | \"height\" | \"maxFps\">\n > &\n Pick<MjpegTransformerOptions, \"logger\" | \"width\" | \"height\" | \"maxFps\">;\n private ffmpeg: ChildProcessWithoutNullStreams | null = null;\n private started = false;\n private closed = false;\n private jpegBuffer = Buffer.alloc(0);\n private frameCount = 0;\n private lastTimestamp = 0;\n\n constructor(options: MjpegTransformerOptions) {\n super();\n this.options = {\n codec: options.codec,\n quality: options.quality ?? 5,\n width: options.width,\n height: options.height,\n maxFps: options.maxFps,\n logger: options.logger,\n };\n }\n\n /**\n * Start the transformer (spawns FFmpeg process)\n */\n start(): void {\n if (this.started || this.closed) return;\n this.started = true;\n\n const { codec, quality, width, height, maxFps } = this.options;\n\n // Build FFmpeg arguments\n const args: string[] = [\n \"-hide_banner\",\n \"-loglevel\",\n \"error\",\n // Input: raw video from stdin\n \"-f\",\n codec === \"h265\" ? \"hevc\" : \"h264\",\n \"-i\",\n \"pipe:0\",\n ];\n\n // Video filters\n const filters: string[] = [];\n\n // Scale if dimensions specified\n if (width || height) {\n const w = width ?? -1;\n const h = height ?? -1;\n filters.push(`scale=${w}:${h}`);\n }\n\n // Frame rate limit\n if (maxFps) {\n filters.push(`fps=${maxFps}`);\n }\n\n if (filters.length > 0) {\n args.push(\"-vf\", filters.join(\",\"));\n }\n\n // Output: MJPEG to stdout\n args.push(\n \"-c:v\",\n \"mjpeg\",\n \"-q:v\",\n String(quality),\n \"-f\",\n \"mjpeg\",\n \"pipe:1\",\n );\n\n this.log(\"debug\", `Starting FFmpeg with args: ${args.join(\" \")}`);\n\n this.ffmpeg = spawn(\"ffmpeg\", args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n this.ffmpeg.stdout.on(\"data\", (data: Buffer) => {\n this.handleJpegData(data);\n });\n\n this.ffmpeg.stderr.on(\"data\", (data: Buffer) => {\n const msg = data.toString().trim();\n if (msg) {\n this.log(\"debug\", `FFmpeg: ${msg}`);\n }\n });\n\n this.ffmpeg.on(\"close\", (code) => {\n this.log(\"debug\", `FFmpeg closed with code ${code}`);\n this.ffmpeg = null;\n if (!this.closed) {\n this.emit(\"close\", code);\n }\n });\n\n this.ffmpeg.on(\"error\", (err) => {\n this.log(\"error\", `FFmpeg error: ${err.message}`);\n this.emit(\"error\", err);\n });\n }\n\n /**\n * Push an H.264/H.265 access unit (Annex-B format with start codes)\n */\n push(accessUnit: Buffer, timestamp?: number): void {\n if (!this.started || this.closed || !this.ffmpeg) {\n return;\n }\n\n this.lastTimestamp = timestamp ?? Date.now() * 1000;\n\n try {\n this.ffmpeg.stdin.write(accessUnit);\n } catch (err) {\n this.log(\"error\", `Failed to write to FFmpeg: ${err}`);\n }\n }\n\n /**\n * Handle JPEG data from FFmpeg stdout\n * FFmpeg outputs complete JPEG images, each starting with SOI (0xFFD8)\n * and ending with EOI (0xFFD9)\n */\n private handleJpegData(data: Buffer): void {\n this.jpegBuffer = Buffer.concat([this.jpegBuffer, data]);\n\n // Process all complete JPEG frames in buffer\n while (true) {\n // Find SOI marker\n const soiIndex = this.jpegBuffer.indexOf(JPEG_SOI);\n if (soiIndex < 0) {\n // No start marker, clear buffer\n this.jpegBuffer = Buffer.alloc(0);\n break;\n }\n\n // Discard data before SOI\n if (soiIndex > 0) {\n this.jpegBuffer = this.jpegBuffer.subarray(soiIndex);\n }\n\n // Find EOI marker (must be after SOI)\n const eoiIndex = this.jpegBuffer.indexOf(JPEG_EOI, 2);\n if (eoiIndex < 0) {\n // No end marker yet, wait for more data\n break;\n }\n\n // Extract complete JPEG frame (including EOI)\n const frameEnd = eoiIndex + 2;\n const jpegFrame = this.jpegBuffer.subarray(0, frameEnd);\n\n // Remove processed frame from buffer\n this.jpegBuffer = this.jpegBuffer.subarray(frameEnd);\n\n // Emit frame\n this.frameCount++;\n const frame: MjpegFrame = {\n data: jpegFrame,\n timestamp: this.lastTimestamp,\n };\n this.emit(\"frame\", frame);\n }\n }\n\n /**\n * Stop the transformer\n */\n async stop(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n\n if (this.ffmpeg) {\n try {\n this.ffmpeg.stdin.end();\n } catch {\n // ignore\n }\n\n // Give FFmpeg a moment to flush\n await new Promise<void>((resolve) => {\n const ff = this.ffmpeg;\n if (!ff) {\n resolve();\n return;\n }\n\n const timeout = setTimeout(() => {\n ff.kill(\"SIGKILL\");\n resolve();\n }, 1000);\n\n ff.once(\"close\", () => {\n clearTimeout(timeout);\n resolve();\n });\n\n try {\n ff.kill(\"SIGTERM\");\n } catch {\n clearTimeout(timeout);\n resolve();\n }\n });\n\n this.ffmpeg = null;\n }\n\n this.emit(\"close\", 0);\n }\n\n /**\n * Get frame count\n */\n getFrameCount(): number {\n return this.frameCount;\n }\n\n /**\n * Check if running\n */\n isRunning(): boolean {\n return this.started && !this.closed && this.ffmpeg !== null;\n }\n\n private log(\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n ): void {\n this.options.logger?.(level, `[MjpegTransformer] ${message}`);\n }\n}\n\n/**\n * Create an MJPEG HTTP response handler\n *\n * Usage:\n * ```ts\n * const transformer = new MjpegTransformer({ codec: \"h264\" });\n * transformer.start();\n *\n * // In your stream handler:\n * stream.on(\"accessUnit\", (au) => transformer.push(au.data, au.timestamp));\n *\n * // HTTP handler:\n * app.get(\"/mjpeg\", (req, res) => {\n * serveMjpeg(res, transformer);\n * });\n * ```\n */\nexport function createMjpegBoundary(): string {\n return `mjpegboundary${Date.now()}`;\n}\n\nexport function getMjpegContentType(boundary: string): string {\n return `multipart/x-mixed-replace; boundary=${boundary}`;\n}\n\nexport function formatMjpegFrame(frame: Buffer, boundary: string): Buffer {\n const header = Buffer.from(\n `--${boundary}\\r\\nContent-Type: image/jpeg\\r\\nContent-Length: ${frame.length}\\r\\n\\r\\n`,\n );\n return Buffer.concat([header, frame, Buffer.from(\"\\r\\n\")]);\n}\n","/**\n * Baichuan WebRTC Server - WebRTC streaming from Baichuan cameras\n *\n * Provides ultra-low latency video/audio streaming using native Baichuan protocol.\n * Supports bidirectional audio (intercom) via WebRTC DataChannel.\n *\n * Architecture:\n * - Camera → Baichuan native stream → H.264/H.265 frames → RTP → WebRTC\n * - Browser mic → WebRTC DataChannel → ADPCM encoding → Baichuan Talk → Camera speaker\n *\n * Usage:\n * ```typescript\n * const webrtc = new BaichuanWebRTCServer({\n * api: reolinkApi,\n * channel: 0,\n * profile: \"main\",\n * enableIntercom: true,\n * });\n *\n * // Create offer for client\n * const { sessionId, offer } = await webrtc.createSession();\n *\n * // Send offer to browser via signaling, receive answer\n * await webrtc.handleAnswer(sessionId, answer);\n *\n * // Handle ICE candidates\n * await webrtc.addIceCandidate(sessionId, candidate);\n *\n * // Close session\n * await webrtc.closeSession(sessionId);\n * ```\n */\n\nimport { EventEmitter } from \"node:events\";\nimport type { StreamProfile } from \"../../reolink/baichuan/types\";\nimport type { ReolinkBaichuanApi } from \"../../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { NativeVideoStreamVariant } from \"../../reolink/baichuan/types\";\nimport { createNativeStream, Intercom } from \"../../rfc/helpers\";\nimport { detectVideoCodecFromNal } from \"./BcMediaAnnexBDecoder\";\nimport {\n convertToAnnexB as convertH264ToAnnexB,\n isH264KeyframeAnnexB,\n splitAnnexBToNalPayloads as splitH264AnnexBToNalPayloads,\n} from \"./H264Converter\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BaichuanWebRTCServerOptions {\n /** API instance (required) */\n api: ReolinkBaichuanApi;\n /** Channel number (required) */\n channel: number;\n /** Stream profile (required) */\n profile: StreamProfile;\n /** Native-only: TrackMix tele/autotrack variants */\n variant?: NativeVideoStreamVariant;\n /** Enable two-way audio intercom (default: false) */\n enableIntercom?: boolean;\n /** STUN servers for ICE (default: Google STUN) */\n stunServers?: string[];\n /** TURN servers for NAT traversal (optional) */\n turnServers?: Array<{\n urls: string;\n username?: string;\n credential?: string;\n }>;\n /** Logger callback */\n logger?: (\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n ) => void;\n}\n\nexport interface WebRTCSessionInfo {\n id: string;\n state: \"connecting\" | \"connected\" | \"disconnected\" | \"failed\";\n createdAt: Date;\n stats: {\n videoFrames: number;\n audioFrames: number;\n bytesSent: number;\n intercomBytesSent: number;\n };\n}\n\nexport interface WebRTCOffer {\n sdp: string;\n type: \"offer\";\n}\n\nexport interface WebRTCAnswer {\n sdp: string;\n type: \"answer\";\n}\n\nexport interface WebRTCIceCandidate {\n candidate: string;\n sdpMid?: string;\n sdpMLineIndex?: number;\n}\n\ninterface WebRTCSession {\n id: string;\n peerConnection: any; // RTCPeerConnection from werift\n videoTrack: any; // MediaStreamTrack from werift\n audioTrack: any; // MediaStreamTrack from werift\n videoDataChannel: any; // RTCDataChannel for H.265 video frames\n nativeStream: AsyncGenerator<any, void, unknown> | null;\n intercom: Intercom | null;\n dataChannel: any; // RTCDataChannel for intercom\n videoCodec: \"H264\" | \"H265\" | null;\n // H.264 parameter sets cache\n lastH264Sps?: Buffer | null;\n lastH264Pps?: Buffer | null;\n // H.265 parameter sets cache\n lastH265Vps?: Buffer | null;\n lastH265Sps?: Buffer | null;\n lastH265Pps?: Buffer | null;\n hasReceivedKeyframe?: boolean; // Track if we've received an IDR frame\n createdAt: Date;\n state: \"connecting\" | \"connected\" | \"disconnected\" | \"failed\";\n stats: {\n videoFrames: number;\n audioFrames: number;\n bytesSent: number;\n intercomBytesSent: number;\n };\n cleanup?: () => void;\n}\n\n// ============================================================================\n// H.264/H.265 NAL Parsing Helpers\n// ============================================================================\n\n/**\n * Parse NAL units from Annex-B format\n * Annex-B uses start codes (0x00000001 or 0x000001) to delimit NALUs\n */\nfunction parseAnnexBNalUnits(annexB: Buffer): Buffer[] {\n const nalUnits: Buffer[] = [];\n let offset = 0;\n\n while (offset < annexB.length) {\n // Find start code\n let startCodeLen = 0;\n if (\n offset + 4 <= annexB.length &&\n annexB[offset] === 0 &&\n annexB[offset + 1] === 0 &&\n annexB[offset + 2] === 0 &&\n annexB[offset + 3] === 1\n ) {\n startCodeLen = 4;\n } else if (\n offset + 3 <= annexB.length &&\n annexB[offset] === 0 &&\n annexB[offset + 1] === 0 &&\n annexB[offset + 2] === 1\n ) {\n startCodeLen = 3;\n } else {\n offset++;\n continue;\n }\n\n const naluStart = offset + startCodeLen;\n\n // Find next start code or end of buffer\n let naluEnd = annexB.length;\n for (let i = naluStart; i < annexB.length - 2; i++) {\n if (\n annexB[i] === 0 &&\n annexB[i + 1] === 0 &&\n (annexB[i + 2] === 1 ||\n (i + 3 < annexB.length && annexB[i + 2] === 0 && annexB[i + 3] === 1))\n ) {\n naluEnd = i;\n break;\n }\n }\n\n if (naluEnd > naluStart) {\n nalUnits.push(annexB.subarray(naluStart, naluEnd));\n }\n\n offset = naluEnd;\n }\n\n return nalUnits;\n}\n\n/**\n * Get H.264 NAL unit type from first byte\n */\nfunction getH264NalType(nalUnit: Buffer): number {\n return nalUnit[0]! & 0x1f;\n}\n\n/**\n * Get H.265 NAL unit type from first two bytes\n */\nfunction getH265NalType(nalUnit: Buffer): number {\n return (nalUnit[0]! >> 1) & 0x3f;\n}\n\n// ============================================================================\n// BaichuanWebRTCServer\n// ============================================================================\n\n/**\n * BaichuanWebRTCServer - WebRTC server for Baichuan video streams\n *\n * Events:\n * - 'session-created': New session created\n * - 'session-connected': Session connected (ICE complete)\n * - 'session-closed': Session closed\n * - 'intercom-started': Intercom started for session\n * - 'intercom-stopped': Intercom stopped for session\n * - 'error': Error occurred\n */\nexport class BaichuanWebRTCServer extends EventEmitter {\n private readonly options: BaichuanWebRTCServerOptions;\n private readonly sessions = new Map<string, WebRTCSession>();\n private sessionIdCounter = 0;\n private weriftModule: any = null;\n\n constructor(options: BaichuanWebRTCServerOptions) {\n super();\n this.options = options;\n }\n\n /**\n * Initialize werift module (lazy load to avoid requiring it if not used)\n */\n private async loadWerift(): Promise<any> {\n if (this.weriftModule) return this.weriftModule;\n\n try {\n this.weriftModule = await import(\"werift\");\n return this.weriftModule;\n } catch (err) {\n throw new Error(\n `Failed to load werift module. Make sure it's installed: npm install werift\\nError: ${err}`,\n );\n }\n }\n\n /**\n * Create a new WebRTC session\n * Returns a session ID and SDP offer to send to the browser\n */\n async createSession(): Promise<{ sessionId: string; offer: WebRTCOffer }> {\n const werift = await this.loadWerift();\n const { RTCPeerConnection, MediaStreamTrack, RTCRtpCodecParameters } =\n werift;\n\n const sessionId = `webrtc-${++this.sessionIdCounter}-${Date.now()}`;\n\n this.log(\"info\", `Creating WebRTC session ${sessionId}`);\n\n // Configure ICE servers\n const iceServers: any[] = [];\n\n // Add STUN servers\n const stunServers = this.options.stunServers ?? [\n \"stun:stun.l.google.com:19302\",\n ];\n for (const urls of stunServers) {\n iceServers.push({ urls });\n }\n\n // Add TURN servers if provided\n if (this.options.turnServers) {\n iceServers.push(...this.options.turnServers);\n }\n\n // Create peer connection with H.264 codec\n const peerConnection = new RTCPeerConnection({\n iceServers,\n codecs: {\n video: [\n new RTCRtpCodecParameters({\n mimeType: \"video/H264\",\n clockRate: 90000,\n rtcpFeedback: [\n { type: \"nack\" },\n { type: \"nack\", parameter: \"pli\" },\n { type: \"goog-remb\" },\n ],\n parameters:\n \"packetization-mode=1;profile-level-id=42e01f;level-asymmetry-allowed=1\",\n }),\n ],\n audio: [\n new RTCRtpCodecParameters({\n mimeType: \"audio/opus\",\n clockRate: 48000,\n channels: 2,\n }),\n ],\n },\n });\n\n // Create session object\n const session: WebRTCSession = {\n id: sessionId,\n peerConnection,\n videoTrack: null,\n audioTrack: null,\n videoDataChannel: null,\n nativeStream: null,\n intercom: null,\n dataChannel: null,\n videoCodec: null,\n createdAt: new Date(),\n state: \"connecting\",\n stats: {\n videoFrames: 0,\n audioFrames: 0,\n bytesSent: 0,\n intercomBytesSent: 0,\n },\n };\n\n this.sessions.set(sessionId, session);\n\n // Add video track (H.264 - will be used only for H.264 streams)\n // Generate a random SSRC for the video track\n const videoSsrc = (Math.random() * 0xffffffff) >>> 0;\n const videoTrack = new MediaStreamTrack({ kind: \"video\", ssrc: videoSsrc });\n const videoSender = peerConnection.addTrack(videoTrack);\n session.videoTrack = videoTrack;\n\n // Log SSRC for debugging\n this.log(\n \"info\",\n `Video track created: ssrc=${videoTrack.ssrc}, sender params=${JSON.stringify(videoSender?.getParameters?.() ?? {})}`,\n );\n\n // Add audio track (Opus for WebRTC)\n const audioSsrc = (Math.random() * 0xffffffff) >>> 0;\n const audioTrack = new MediaStreamTrack({ kind: \"audio\", ssrc: audioSsrc });\n peerConnection.addTrack(audioTrack);\n session.audioTrack = audioTrack;\n\n // Create data channel for H.265 video frames (browser will decode with WebCodecs)\n const videoDataChannel = peerConnection.createDataChannel(\"video\", {\n ordered: true,\n maxRetransmits: 0, // Unreliable for real-time video\n });\n session.videoDataChannel = videoDataChannel;\n\n videoDataChannel.onopen = () => {\n this.log(\"info\", `Video data channel opened for session ${sessionId}`);\n };\n\n // Create data channel for intercom audio (browser → camera)\n if (this.options.enableIntercom) {\n const dataChannel = peerConnection.createDataChannel(\"intercom\", {\n ordered: true,\n });\n session.dataChannel = dataChannel;\n\n dataChannel.onopen = () => {\n this.log(\n \"info\",\n `Intercom data channel opened for session ${sessionId}`,\n );\n this.emit(\"intercom-started\", { sessionId });\n };\n\n dataChannel.onmessage = async (event: any) => {\n // Handle incoming audio from browser for intercom\n if (session.intercom && event.data instanceof ArrayBuffer) {\n try {\n const audioData = Buffer.from(event.data);\n await session.intercom.sendAudio(audioData);\n session.stats.intercomBytesSent += audioData.length;\n } catch (err) {\n this.log(\"error\", `Failed to send intercom audio: ${err}`);\n }\n }\n };\n\n dataChannel.onclose = () => {\n this.log(\n \"info\",\n `Intercom data channel closed for session ${sessionId}`,\n );\n this.emit(\"intercom-stopped\", { sessionId });\n };\n }\n\n // Handle ICE connection state changes\n peerConnection.iceConnectionStateChange.subscribe((state: string) => {\n this.log(\"info\", `ICE connection state for ${sessionId}: ${state}`);\n if (state === \"connected\") {\n session.state = \"connected\";\n this.emit(\"session-connected\", { sessionId });\n } else if (state === \"failed\") {\n session.state = state as any;\n this.closeSession(sessionId).catch((err) => {\n this.log(\"error\", `Error closing session on ICE ${state}: ${err}`);\n });\n }\n // Note: \"disconnected\" is often transient on LAN, don't immediately close\n });\n\n // Handle peer connection state changes\n peerConnection.connectionStateChange.subscribe((state: string) => {\n this.log(\"debug\", `Connection state for ${sessionId}: ${state}`);\n if (state === \"closed\" || state === \"failed\") {\n this.closeSession(sessionId).catch((err) => {\n this.log(\n \"error\",\n `Error closing session on connection ${state}: ${err}`,\n );\n });\n }\n });\n\n // Create offer\n const offer = await peerConnection.createOffer();\n await peerConnection.setLocalDescription(offer);\n\n // Wait for ICE gathering to complete (or timeout)\n await this.waitForIceGathering(peerConnection, 3000);\n\n const localDescription = peerConnection.localDescription;\n if (!localDescription) {\n throw new Error(\"Failed to create local description\");\n }\n\n this.emit(\"session-created\", { sessionId });\n\n return {\n sessionId,\n offer: {\n sdp: localDescription.sdp,\n type: \"offer\",\n },\n };\n }\n\n /**\n * Handle WebRTC answer from browser and start streaming\n */\n async handleAnswer(sessionId: string, answer: WebRTCAnswer): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (!session) {\n throw new Error(`Session ${sessionId} not found`);\n }\n\n const werift = await this.loadWerift();\n const { RTCSessionDescription } = werift;\n\n this.log(\"info\", `Handling WebRTC answer for session ${sessionId}`);\n\n // Set remote description\n await session.peerConnection.setRemoteDescription(\n new RTCSessionDescription(answer.sdp, answer.type),\n );\n\n // Start native stream\n await this.startNativeStream(session);\n\n // Start intercom if enabled\n if (this.options.enableIntercom && session.dataChannel) {\n await this.startIntercom(session);\n }\n }\n\n /**\n * Add ICE candidate from browser\n */\n async addIceCandidate(\n sessionId: string,\n candidate: WebRTCIceCandidate,\n ): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (!session) {\n throw new Error(`Session ${sessionId} not found`);\n }\n\n const werift = await this.loadWerift();\n const { RTCIceCandidate } = werift;\n\n await session.peerConnection.addIceCandidate(\n new RTCIceCandidate(candidate.candidate, candidate.sdpMid ?? \"0\"),\n );\n }\n\n /**\n * Close a WebRTC session\n */\n async closeSession(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (!session) return;\n\n this.log(\"info\", `Closing WebRTC session ${sessionId}`);\n session.state = \"disconnected\";\n\n // Stop intercom\n if (session.intercom) {\n try {\n await session.intercom.stop();\n } catch (err) {\n this.log(\"debug\", `Error stopping intercom: ${err}`);\n }\n session.intercom = null;\n }\n\n // Close data channel\n if (session.dataChannel) {\n try {\n session.dataChannel.close();\n } catch (err) {\n this.log(\"debug\", `Error closing data channel: ${err}`);\n }\n session.dataChannel = null;\n }\n\n // Call cleanup if defined\n if (session.cleanup) {\n session.cleanup();\n }\n\n // Close peer connection\n try {\n await session.peerConnection.close();\n } catch (err) {\n this.log(\"debug\", `Error closing peer connection: ${err}`);\n }\n\n this.sessions.delete(sessionId);\n this.emit(\"session-closed\", { sessionId });\n\n this.log(\n \"info\",\n `WebRTC session ${sessionId} closed (active sessions: ${this.sessions.size})`,\n );\n }\n\n /**\n * Get information about all active sessions\n */\n getSessions(): WebRTCSessionInfo[] {\n return Array.from(this.sessions.values()).map((s) => ({\n id: s.id,\n state: s.state,\n createdAt: s.createdAt,\n stats: { ...s.stats },\n }));\n }\n\n /**\n * Get information about a specific session\n */\n getSession(sessionId: string): WebRTCSessionInfo | null {\n const session = this.sessions.get(sessionId);\n if (!session) return null;\n\n return {\n id: session.id,\n state: session.state,\n createdAt: session.createdAt,\n stats: { ...session.stats },\n };\n }\n\n /**\n * Close all sessions and stop the server\n */\n async stop(): Promise<void> {\n this.log(\"info\", \"Stopping WebRTC server\");\n const sessionIds = Array.from(this.sessions.keys());\n await Promise.all(sessionIds.map((id) => this.closeSession(id)));\n this.log(\"info\", \"WebRTC server stopped\");\n }\n\n /**\n * Get the number of active sessions\n */\n get sessionCount(): number {\n return this.sessions.size;\n }\n\n // ============================================================================\n // Private Methods\n // ============================================================================\n\n /**\n * Wait for ICE gathering to complete\n */\n private async waitForIceGathering(pc: any, timeoutMs: number): Promise<void> {\n if (pc.iceGatheringState === \"complete\") return;\n\n return new Promise((resolve) => {\n const timeout = setTimeout(() => {\n resolve();\n }, timeoutMs);\n\n pc.iceGatheringStateChange.subscribe((state: string) => {\n if (state === \"complete\") {\n clearTimeout(timeout);\n resolve();\n }\n });\n });\n }\n\n /**\n * Start native Baichuan stream and pump frames to WebRTC\n */\n private async startNativeStream(session: WebRTCSession): Promise<void> {\n this.log(\n \"info\",\n `Starting native stream for session ${session.id} (channel=${this.options.channel}, profile=${this.options.profile})`,\n );\n\n // Create native stream generator\n session.nativeStream = createNativeStream(\n this.options.api,\n this.options.channel,\n this.options.profile,\n this.options.variant !== undefined\n ? { variant: this.options.variant }\n : undefined,\n );\n\n // Pump frames to WebRTC\n this.pumpFramesToWebRTC(session).catch((err) => {\n this.log(\"error\", `Frame pump error for session ${session.id}: ${err}`);\n this.closeSession(session.id).catch(() => {});\n });\n }\n\n /**\n * Pump frames from native stream to WebRTC tracks\n * H.264 → RTP media track (standard WebRTC)\n * H.265 → DataChannel with raw Annex-B frames (decoded by WebCodecs in browser)\n */\n private async pumpFramesToWebRTC(session: WebRTCSession): Promise<void> {\n if (!session.nativeStream) {\n this.log(\"warn\", `No native stream for session ${session.id}`);\n return;\n }\n\n this.log(\"info\", `Starting frame pump for session ${session.id}`);\n\n const werift = await this.loadWerift();\n const { RtpPacket, RtpHeader } = werift;\n\n let sequenceNumber = Math.floor(Math.random() * 65535);\n let timestamp = Math.floor(Math.random() * 0xffffffff);\n const videoClockRate = 90000; // Standard RTP clock rate for video\n let lastTimeMicros = 0;\n let lastLogTime = Date.now();\n let packetsSentSinceLastLog = 0;\n let frameNumber = 0;\n\n try {\n this.log(\"info\", `Entering frame loop for session ${session.id}`);\n\n for await (const frame of session.nativeStream) {\n if (session.state === \"disconnected\" || session.state === \"failed\") {\n this.log(\n \"debug\",\n `Session ${session.id} state is ${session.state}, breaking frame loop`,\n );\n break;\n }\n\n if (frame.audio) {\n // Audio frame - would need transcoding to Opus for WebRTC\n // For now, track stats only\n // TODO: Implement AAC/ADPCM to Opus transcoding\n session.stats.audioFrames++;\n } else {\n // Video frame\n if (frame.data) {\n // Detect codec on first video frame\n if (!session.videoCodec && frame.videoType) {\n const detected = detectVideoCodecFromNal(frame.data);\n session.videoCodec = (detected ?? frame.videoType) as any;\n this.log(\"info\", `Detected video codec: ${session.videoCodec}`);\n\n // Send codec info to client via data channel\n if (\n session.videoDataChannel &&\n session.videoDataChannel.readyState === \"open\"\n ) {\n const codecInfo = JSON.stringify({\n type: \"codec\",\n codec: session.videoCodec,\n width: frame.width || 0,\n height: frame.height || 0,\n });\n session.videoDataChannel.send(codecInfo);\n }\n }\n\n // Calculate timestamp\n if (frame.microseconds && lastTimeMicros > 0) {\n const deltaMicros = frame.microseconds - lastTimeMicros;\n const deltaTicks = Math.floor(\n (deltaMicros / 1000000) * videoClockRate,\n );\n timestamp = (timestamp + deltaTicks) >>> 0;\n } else {\n timestamp = (timestamp + 3000) >>> 0; // ~33ms for 30fps\n }\n lastTimeMicros = frame.microseconds || 0;\n\n if (session.videoCodec === \"H264\") {\n // H.264 → send via DataChannel (WebCodecs will decode in browser)\n // Check if connection is ready\n const connState = session.peerConnection.connectionState;\n const iceState = session.peerConnection.iceConnectionState;\n\n // Accept various \"connected\" states - werift may use different values\n const isConnected =\n connState === \"connected\" ||\n iceState === \"connected\" ||\n iceState === \"completed\";\n\n if (!isConnected) {\n // Wait for connection, but don't block forever - drop frames until connected\n if (frameNumber < 10) {\n this.log(\n \"debug\",\n `Waiting for connection, dropping H.264 frame ${frameNumber}`,\n );\n }\n frameNumber++;\n continue;\n }\n\n // H.264 → send via RTP media track (standard WebRTC)\n const packetsSent = await this.sendH264Frame(\n session,\n werift,\n frame.data,\n sequenceNumber,\n timestamp,\n );\n sequenceNumber = (sequenceNumber + packetsSent) & 0xffff;\n packetsSentSinceLastLog += packetsSent;\n frameNumber++;\n session.stats.videoFrames++;\n session.stats.bytesSent += frame.data.length;\n } else if (session.videoCodec === \"H265\") {\n // H.265 → send via DataChannel (WebCodecs will decode in browser)\n const sent = await this.sendVideoFrameViaDataChannel(\n session,\n frame,\n frameNumber,\n \"H265\",\n );\n if (sent) {\n packetsSentSinceLastLog++;\n frameNumber++;\n session.stats.videoFrames++;\n session.stats.bytesSent += frame.data.length;\n }\n }\n\n // Log progress every 5 seconds\n const now = Date.now();\n if (now - lastLogTime >= 5000) {\n this.log(\n \"debug\",\n `WebRTC session ${session.id} [${session.videoCodec}]: sent ${session.stats.videoFrames} frames, ${packetsSentSinceLastLog} packets, ${Math.round(session.stats.bytesSent / 1024)} KB`,\n );\n lastLogTime = now;\n packetsSentSinceLastLog = 0;\n }\n }\n }\n }\n } catch (err) {\n this.log(\n \"error\",\n `Error pumping frames for session ${session.id}: ${err}`,\n );\n }\n\n this.log(\"info\", `Native stream ended for session ${session.id}`);\n }\n\n /**\n * Send H.264 frame via RTP media track\n * Returns the number of RTP packets sent\n */\n private async sendH264Frame(\n session: WebRTCSession,\n werift: any,\n frameData: Buffer,\n sequenceNumber: number,\n timestamp: number,\n ): Promise<number> {\n // Frame data may be length-prefixed; normalize to Annex-B.\n const annexB = convertH264ToAnnexB(frameData);\n const nalUnits = splitH264AnnexBToNalPayloads(annexB);\n\n // Categorize NAL units\n let hasSps = false;\n let hasPps = false;\n let hasIdr = false;\n const nalTypes: number[] = [];\n\n for (const nal of nalUnits) {\n const t = (nal[0] ?? 0) & 0x1f;\n nalTypes.push(t);\n if (t === 7) {\n hasSps = true;\n session.lastH264Sps = nal;\n }\n if (t === 8) {\n hasPps = true;\n session.lastH264Pps = nal;\n }\n if (t === 5) hasIdr = true;\n }\n\n // Log NAL types for first few frames to debug\n if (session.stats.videoFrames < 10) {\n this.log(\n \"debug\",\n `H.264 frame NAL types: [${nalTypes.join(\",\")}] (5=IDR, 7=SPS, 8=PPS, 1=P-slice)`,\n );\n }\n\n // A keyframe is an IDR frame (type 5). SPS/PPS may come separately.\n const isKeyframe = hasIdr;\n let nalList = nalUnits;\n\n // If this is an IDR but missing SPS/PPS, prepend cached ones\n if (hasIdr && (!hasSps || !hasPps)) {\n const prepend: Buffer[] = [];\n if (!hasSps && session.lastH264Sps) {\n prepend.push(session.lastH264Sps);\n this.log(\"debug\", `Prepending cached SPS to IDR frame`);\n }\n if (!hasPps && session.lastH264Pps) {\n prepend.push(session.lastH264Pps);\n this.log(\"debug\", `Prepending cached PPS to IDR frame`);\n }\n if (prepend.length > 0) {\n nalList = [...prepend, ...nalUnits];\n } else if (!session.lastH264Sps || !session.lastH264Pps) {\n // IDR without SPS/PPS and no cache - decoder can't use this\n this.log(\n \"warn\",\n `IDR frame without SPS/PPS and no cached parameters - frame may not decode`,\n );\n }\n }\n\n // Wait for first keyframe before sending any frames\n if (!session.hasReceivedKeyframe) {\n if (hasIdr && session.lastH264Sps && session.lastH264Pps) {\n session.hasReceivedKeyframe = true;\n this.log(\n \"info\",\n `First H.264 keyframe received with SPS/PPS - starting video stream`,\n );\n // Continue to send this keyframe below\n } else if (hasIdr) {\n this.log(\n \"debug\",\n `IDR received but waiting for SPS/PPS before starting stream`,\n );\n return 0;\n } else {\n // P-frame before first keyframe - drop it\n if (session.stats.videoFrames < 5) {\n this.log(\n \"debug\",\n `Dropping P-frame ${session.stats.videoFrames} while waiting for keyframe`,\n );\n }\n return 0;\n }\n }\n\n let totalPacketsSent = 0;\n let currentSeqNum = sequenceNumber;\n\n // Get SSRC from video track\n const ssrc = session.videoTrack.ssrc || 0;\n\n for (let i = 0; i < nalList.length; i++) {\n const nalUnit = nalList[i]!;\n if (nalUnit.length === 0) continue;\n\n const isLastNalu = i === nalList.length - 1;\n const nalType = getH264NalType(nalUnit);\n\n // Skip AUD (Access Unit Delimiter) NAL units\n if (nalType === 9) continue;\n\n // Create and send RTP packets\n const rtpPackets = this.createH264RtpPackets(\n werift,\n nalUnit,\n currentSeqNum,\n timestamp,\n isLastNalu,\n ssrc,\n );\n\n // Log NAL processing for first few frames\n if (session.stats.videoFrames < 3) {\n this.log(\n \"info\",\n `NAL ${i}: type=${nalType}, size=${nalUnit.length}, rtpPackets=${rtpPackets.length}`,\n );\n }\n\n for (const rtpPacket of rtpPackets) {\n try {\n session.videoTrack.writeRtp(rtpPacket);\n currentSeqNum = (currentSeqNum + 1) & 0xffff;\n totalPacketsSent++;\n } catch (err) {\n this.log(\n \"error\",\n `Error writing RTP packet for session ${session.id}: ${err}`,\n );\n // Continue trying to send more packets\n }\n }\n }\n\n // Log first few frames for debugging\n if (session.stats.videoFrames < 3) {\n this.log(\n \"info\",\n `H.264 frame sent: nalCount=${nalList.length} packets=${totalPacketsSent} seq=${sequenceNumber}->${currentSeqNum} ts=${timestamp} keyframe=${isKeyframe}`,\n );\n }\n\n return totalPacketsSent;\n }\n\n /**\n * Send video frame via DataChannel (works for both H.264 and H.265)\n * Format: 12-byte header + Annex-B data\n * Header: [frameNum (4)] [timestamp (4)] [flags (1)] [keyframe (1)] [reserved (2)]\n * Flags: 0x01 = H.265, 0x02 = H.264\n */\n private async sendVideoFrameViaDataChannel(\n session: WebRTCSession,\n frame: any,\n frameNumber: number,\n codec: \"H264\" | \"H265\",\n ): Promise<boolean> {\n if (!session.videoDataChannel) {\n if (frameNumber === 0) {\n this.log(\"warn\", `No video data channel for session ${session.id}`);\n }\n return false;\n }\n\n if (session.videoDataChannel.readyState !== \"open\") {\n if (frameNumber === 0) {\n this.log(\n \"warn\",\n `Video data channel not open for session ${session.id}: ${session.videoDataChannel.readyState}`,\n );\n }\n return false;\n }\n\n // Parse NAL units to detect keyframe and cache SPS/PPS\n const nalUnits = parseAnnexBNalUnits(frame.data);\n let isKeyframe = frame.isKeyframe === true;\n let hasIdr = false;\n let hasSps = false;\n let hasPps = false;\n let hasVps = false;\n const nalTypes: number[] = [];\n\n for (const nalUnit of nalUnits) {\n if (nalUnit.length === 0) continue;\n\n if (codec === \"H265\") {\n const nalType = getH265NalType(nalUnit);\n nalTypes.push(nalType);\n // VPS=32, SPS=33, PPS=34, IDR_W_RADL=19, IDR_N_LP=20\n if (nalType === 32) {\n hasVps = true;\n session.lastH265Vps = nalUnit;\n }\n if (nalType === 33) {\n hasSps = true;\n session.lastH265Sps = nalUnit;\n }\n if (nalType === 34) {\n hasPps = true;\n session.lastH265Pps = nalUnit;\n }\n if (nalType === 19 || nalType === 20) {\n hasIdr = true;\n isKeyframe = true;\n }\n } else {\n // H.264\n const nalType = getH264NalType(nalUnit);\n nalTypes.push(nalType);\n // SPS=7, PPS=8, IDR=5\n if (nalType === 7) {\n hasSps = true;\n session.lastH264Sps = nalUnit;\n }\n if (nalType === 8) {\n hasPps = true;\n session.lastH264Pps = nalUnit;\n }\n if (nalType === 5) {\n hasIdr = true;\n isKeyframe = true;\n }\n }\n }\n\n // Log NAL types for first few frames\n if (frameNumber < 5) {\n this.log(\n \"debug\",\n `${codec} frame ${frameNumber} NAL types: [${nalTypes.join(\",\")}] hasIdr=${hasIdr} hasSps=${hasSps} hasPps=${hasPps}`,\n );\n }\n\n // Wait for first keyframe before sending any frames\n if (!session.hasReceivedKeyframe) {\n if (codec === \"H264\") {\n // For H.264, we need SPS+PPS+IDR\n if (hasIdr && session.lastH264Sps && session.lastH264Pps) {\n session.hasReceivedKeyframe = true;\n this.log(\n \"info\",\n `First H.264 keyframe received with SPS/PPS - starting video stream`,\n );\n } else if (hasSps || hasPps) {\n // Got parameter sets, wait for IDR\n this.log(\"debug\", `Received H.264 parameter sets, waiting for IDR`);\n return false;\n } else if (hasIdr) {\n // IDR without SPS/PPS - can't use yet\n this.log(\"debug\", `IDR received but waiting for SPS/PPS`);\n return false;\n } else {\n // P-frame - drop it\n if (frameNumber < 10) {\n this.log(\n \"debug\",\n `Dropping H.264 P-frame ${frameNumber} while waiting for keyframe`,\n );\n }\n return false;\n }\n } else {\n // For H.265, we need VPS+SPS+PPS+IDR\n if (\n hasIdr &&\n session.lastH265Vps &&\n session.lastH265Sps &&\n session.lastH265Pps\n ) {\n session.hasReceivedKeyframe = true;\n this.log(\n \"info\",\n `First H.265 keyframe received with VPS/SPS/PPS - starting video stream`,\n );\n } else if (hasVps || hasSps || hasPps) {\n this.log(\"debug\", `Received H.265 parameter sets, waiting for IDR`);\n return false;\n } else if (hasIdr) {\n this.log(\"debug\", `H.265 IDR received but waiting for VPS/SPS/PPS`);\n return false;\n } else {\n if (frameNumber < 10) {\n this.log(\n \"debug\",\n `Dropping H.265 P-frame ${frameNumber} while waiting for keyframe`,\n );\n }\n return false;\n }\n }\n }\n\n // Build the frame data, prepending cached parameter sets if needed for IDR\n let frameData = frame.data;\n\n if (hasIdr) {\n if (codec === \"H264\" && (!hasSps || !hasPps)) {\n // Prepend cached SPS/PPS to IDR\n const parts: Buffer[] = [];\n if (!hasSps && session.lastH264Sps) {\n parts.push(Buffer.from([0x00, 0x00, 0x00, 0x01]));\n parts.push(session.lastH264Sps);\n }\n if (!hasPps && session.lastH264Pps) {\n parts.push(Buffer.from([0x00, 0x00, 0x00, 0x01]));\n parts.push(session.lastH264Pps);\n }\n if (parts.length > 0) {\n frameData = Buffer.concat([...parts, frame.data]);\n this.log(\"debug\", `Prepended cached SPS/PPS to H.264 IDR frame`);\n }\n } else if (codec === \"H265\" && (!hasVps || !hasSps || !hasPps)) {\n // Prepend cached VPS/SPS/PPS to IDR\n const parts: Buffer[] = [];\n if (!hasVps && session.lastH265Vps) {\n parts.push(Buffer.from([0x00, 0x00, 0x00, 0x01]));\n parts.push(session.lastH265Vps);\n }\n if (!hasSps && session.lastH265Sps) {\n parts.push(Buffer.from([0x00, 0x00, 0x00, 0x01]));\n parts.push(session.lastH265Sps);\n }\n if (!hasPps && session.lastH265Pps) {\n parts.push(Buffer.from([0x00, 0x00, 0x00, 0x01]));\n parts.push(session.lastH265Pps);\n }\n if (parts.length > 0) {\n frameData = Buffer.concat([...parts, frame.data]);\n this.log(\"debug\", `Prepended cached VPS/SPS/PPS to H.265 IDR frame`);\n }\n }\n }\n\n // Create header (12 bytes)\n const header = Buffer.alloc(12);\n header.writeUInt32BE(frameNumber, 0); // Frame number\n header.writeUInt32BE(frame.microseconds ? frame.microseconds / 1000 : 0, 4); // Timestamp in ms\n header.writeUInt8(codec === \"H265\" ? 0x01 : 0x02, 8); // Flags: 0x01 = H.265, 0x02 = H.264\n header.writeUInt8(isKeyframe ? 1 : 0, 9); // Keyframe flag\n header.writeUInt16BE(0, 10); // Reserved\n\n // Combine header + raw Annex-B data\n const packet = Buffer.concat([header, frameData]);\n\n // Log first few frames\n if (frameNumber < 3) {\n this.log(\n \"info\",\n `Sending ${codec} frame ${frameNumber}: ${packet.length} bytes, keyframe=${isKeyframe}`,\n );\n }\n\n // Send via DataChannel (may need to chunk for large frames)\n const MAX_CHUNK_SIZE = 16000; // Safe size for DataChannel\n\n try {\n if (packet.length <= MAX_CHUNK_SIZE) {\n session.videoDataChannel.send(packet);\n } else {\n // Chunk large frames\n const totalChunks = Math.ceil(packet.length / MAX_CHUNK_SIZE);\n for (let i = 0; i < totalChunks; i++) {\n const start = i * MAX_CHUNK_SIZE;\n const end = Math.min(start + MAX_CHUNK_SIZE, packet.length);\n const chunk = packet.subarray(start, end);\n\n // Prepend chunk header: [chunk index (1)] [total chunks (1)] [data...]\n const chunkHeader = Buffer.alloc(2);\n chunkHeader.writeUInt8(i, 0);\n chunkHeader.writeUInt8(totalChunks, 1);\n\n session.videoDataChannel.send(Buffer.concat([chunkHeader, chunk]));\n }\n }\n return true;\n } catch (err) {\n this.log(\"error\", `Error sending ${codec} frame ${frameNumber}: ${err}`);\n return false;\n }\n }\n\n /**\n * Send H.265 frame via DataChannel\n * Format: 12-byte header + Annex-B data\n * Header: [frameNum (4)] [timestamp (4)] [flags (1)] [keyframe (1)] [reserved (2)]\n */\n private async sendH265Frame(\n session: WebRTCSession,\n frame: any,\n frameNumber: number,\n ): Promise<void> {\n if (!session.videoDataChannel) {\n if (frameNumber === 0) {\n this.log(\"warn\", `No video data channel for session ${session.id}`);\n }\n return;\n }\n\n if (session.videoDataChannel.readyState !== \"open\") {\n if (frameNumber === 0) {\n this.log(\n \"warn\",\n `Video data channel not open for session ${session.id}: ${session.videoDataChannel.readyState}`,\n );\n }\n return;\n }\n\n // Use isKeyframe from the frame if available, otherwise detect from NAL units\n let isKeyframe = frame.isKeyframe === true;\n\n // Double-check by scanning NAL units if frame.isKeyframe is not set\n if (!isKeyframe && frame.isKeyframe === undefined) {\n const nalUnits = parseAnnexBNalUnits(frame.data);\n for (const nalUnit of nalUnits) {\n if (nalUnit.length === 0) continue;\n const nalType = getH265NalType(nalUnit);\n // VPS=32, SPS=33, PPS=34, IDR_W_RADL=19, IDR_N_LP=20\n if (\n nalType === 32 ||\n nalType === 33 ||\n nalType === 34 ||\n nalType === 19 ||\n nalType === 20\n ) {\n isKeyframe = true;\n break;\n }\n }\n }\n\n // Create header (12 bytes)\n const header = Buffer.alloc(12);\n header.writeUInt32BE(frameNumber, 0); // Frame number\n header.writeUInt32BE(frame.microseconds ? frame.microseconds / 1000 : 0, 4); // Timestamp in ms\n header.writeUInt8(0x01, 8); // Flags: 0x01 = H.265\n header.writeUInt8(isKeyframe ? 1 : 0, 9); // Keyframe flag\n header.writeUInt16BE(0, 10); // Reserved\n\n // Combine header + raw Annex-B data\n const packet = Buffer.concat([header, frame.data]);\n\n // Log first few frames\n if (frameNumber < 3) {\n this.log(\n \"info\",\n `Sending H.265 frame ${frameNumber}: ${packet.length} bytes, keyframe=${isKeyframe}`,\n );\n }\n\n // Send via DataChannel (may need to chunk for large frames)\n const MAX_CHUNK_SIZE = 16000; // Safe size for DataChannel\n\n try {\n if (packet.length <= MAX_CHUNK_SIZE) {\n session.videoDataChannel.send(packet);\n } else {\n // Chunk large frames\n const totalChunks = Math.ceil(packet.length / MAX_CHUNK_SIZE);\n for (let i = 0; i < totalChunks; i++) {\n const start = i * MAX_CHUNK_SIZE;\n const end = Math.min(start + MAX_CHUNK_SIZE, packet.length);\n const chunk = packet.subarray(start, end);\n\n // Prepend chunk header: [chunk index (1)] [total chunks (1)] [data...]\n const chunkHeader = Buffer.alloc(2);\n chunkHeader.writeUInt8(i, 0);\n chunkHeader.writeUInt8(totalChunks, 1);\n\n session.videoDataChannel.send(Buffer.concat([chunkHeader, chunk]));\n }\n }\n } catch (err) {\n this.log(\"error\", `Error sending H.265 frame ${frameNumber}: ${err}`);\n }\n }\n\n /**\n * Create RTP packets for H.264 NAL unit\n * Handles single NAL, STAP-A aggregation, and FU-A fragmentation\n */\n private createH264RtpPackets(\n werift: any,\n nalUnit: Buffer,\n sequenceNumber: number,\n timestamp: number,\n marker: boolean,\n ssrc: number,\n ): any[] {\n const { RtpPacket, RtpHeader } = werift;\n const MTU = 1200; // Safe MTU for RTP payload\n\n const packets: any[] = [];\n\n if (nalUnit.length <= MTU) {\n // Single NAL unit - send as-is\n const header = new RtpHeader();\n header.payloadType = 96; // Dynamic payload type for H.264\n header.sequenceNumber = sequenceNumber;\n header.timestamp = timestamp;\n header.marker = marker;\n header.ssrc = ssrc;\n\n packets.push(new RtpPacket(header, nalUnit));\n } else {\n // FU-A fragmentation for large NAL units\n const nalHeader = nalUnit[0]!;\n const nalType = nalHeader & 0x1f;\n const nri = nalHeader & 0x60;\n\n // FU indicator: same NRI, type = 28 (FU-A)\n const fuIndicator = (nri | 28) & 0xff;\n\n let offset = 1; // Skip NAL header\n let isFirst = true;\n\n while (offset < nalUnit.length) {\n const remaining = nalUnit.length - offset;\n const chunkSize = Math.min(remaining, MTU - 2); // -2 for FU indicator + FU header\n const isLast = offset + chunkSize >= nalUnit.length;\n\n // FU header: S bit (start), E bit (end), R bit (reserved), Type\n let fuHeader = nalType;\n if (isFirst) fuHeader |= 0x80; // S bit\n if (isLast) fuHeader |= 0x40; // E bit\n\n // Create FU-A payload\n const fuPayload = Buffer.alloc(2 + chunkSize);\n fuPayload[0] = fuIndicator;\n fuPayload[1] = fuHeader;\n nalUnit.copy(fuPayload, 2, offset, offset + chunkSize);\n\n const header = new RtpHeader();\n header.payloadType = 96;\n header.sequenceNumber = (sequenceNumber + packets.length) & 0xffff;\n header.timestamp = timestamp;\n header.marker = isLast && marker;\n header.ssrc = ssrc;\n\n packets.push(new RtpPacket(header, fuPayload));\n\n offset += chunkSize;\n isFirst = false;\n }\n }\n\n return packets;\n }\n\n /**\n * Start intercom (two-way audio)\n */\n private async startIntercom(session: WebRTCSession): Promise<void> {\n try {\n session.intercom = new Intercom({\n api: this.options.api,\n channel: this.options.channel,\n });\n\n await session.intercom.start();\n this.log(\"info\", `Intercom started for session ${session.id}`);\n } catch (err) {\n this.log(\n \"error\",\n `Failed to start intercom for session ${session.id}: ${err}`,\n );\n // Don't fail the whole session, just disable intercom\n session.intercom = null;\n }\n }\n\n /**\n * Log helper\n */\n private log(\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n ): void {\n if (this.options.logger) {\n this.options.logger(level, message);\n }\n }\n}\n","/**\n * Baichuan HLS Server - HLS streaming from Baichuan cameras\n *\n * Provides HLS (HTTP Live Streaming) output from Baichuan native video streams.\n * Uses ffmpeg to transcode H.265 to H.264 for browser compatibility, or copies H.264 directly.\n *\n * Architecture:\n * - Camera → Baichuan native stream → H.264/H.265 frames → ffmpeg → HLS segments\n *\n * Usage:\n * ```typescript\n * const hls = new BaichuanHlsServer({\n * api: reolinkApi,\n * channel: 0,\n * profile: \"main\",\n * outputDir: \"/tmp/hls-output\",\n * });\n *\n * // Start streaming\n * await hls.start();\n *\n * // Get playlist path\n * const playlistPath = hls.getPlaylistPath();\n *\n * // Stop streaming\n * await hls.stop();\n * ```\n */\n\nimport { EventEmitter } from \"node:events\";\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { spawn, type ChildProcess } from \"node:child_process\";\n\nimport type { StreamProfile } from \"../../reolink/baichuan/types\";\nimport type { ReolinkBaichuanApi } from \"../../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { NativeVideoStreamVariant } from \"../../reolink/baichuan/types\";\nimport { createNativeStream } from \"../../rfc/helpers\";\nimport { detectVideoCodecFromNal } from \"./BcMediaAnnexBDecoder\";\nimport { convertToAnnexB as convertH264ToAnnexB } from \"./H264Converter\";\nimport { convertToAnnexB as convertH265ToAnnexB } from \"./H265Converter\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type HlsCodec = \"h264\" | \"h265\";\n\nexport interface BaichuanHlsServerOptions {\n /** API instance (required) */\n api: ReolinkBaichuanApi;\n /** Channel number (required) */\n channel: number;\n /** Stream profile (required) */\n profile: StreamProfile;\n /** Native-only: TrackMix tele/autotrack variants */\n variant?: NativeVideoStreamVariant;\n /** Output directory for HLS segments. If not provided, a temp directory will be created. */\n outputDir?: string;\n /** HLS segment duration in seconds (default: 2) */\n segmentDuration?: number;\n /** Number of segments to keep in playlist (default: 5) */\n playlistSize?: number;\n /** ffmpeg binary path (default: \"ffmpeg\") */\n ffmpegPath?: string;\n /** Logger callback */\n logger?: (\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n ) => void;\n}\n\nexport interface HlsServerStatus {\n state: \"idle\" | \"starting\" | \"running\" | \"stopping\" | \"stopped\" | \"error\";\n codec: HlsCodec | null;\n framesReceived: number;\n ffmpegRunning: boolean;\n playlistPath: string | null;\n outputDir: string | null;\n startedAt: Date | null;\n error: string | null;\n}\n\n// ============================================================================\n// NAL Unit Parsing Utilities\n// ============================================================================\n\nfunction parseAnnexBNalUnits(data: Buffer): Buffer[] {\n const units: Buffer[] = [];\n const len = data.length;\n\n const findStart = (from: number): number => {\n for (let i = from; i + 3 < len; i++) {\n if (data[i] === 0x00 && data[i + 1] === 0x00) {\n if (data[i + 2] === 0x01) return i;\n if (i + 4 < len && data[i + 2] === 0x00 && data[i + 3] === 0x01)\n return i;\n }\n }\n return -1;\n };\n\n const startCodeLenAt = (i: number): number => {\n if (i + 3 < len && data[i] === 0x00 && data[i + 1] === 0x00) {\n if (data[i + 2] === 0x01) return 3;\n if (i + 4 < len && data[i + 2] === 0x00 && data[i + 3] === 0x01) return 4;\n }\n return 0;\n };\n\n let start = findStart(0);\n if (start < 0) return units;\n\n while (start >= 0) {\n const scLen = startCodeLenAt(start);\n if (!scLen) break;\n const nalStart = start + scLen;\n let next = findStart(nalStart);\n if (next < 0) next = len;\n if (nalStart < next) units.push(data.subarray(nalStart, next));\n start = next < len ? next : -1;\n }\n\n return units;\n}\n\n/**\n * Check if frame is a true keyframe (IDR/CRA/BLA) that can start decoding.\n * Parameter sets alone (SPS/PPS/VPS) are NOT enough - we need an actual intra frame.\n *\n * H.265 Random Access Points:\n * - IDR_W_RADL (19), IDR_N_LP (20): Instantaneous Decoder Refresh\n * - CRA_NUT (21): Clean Random Access\n * - BLA_W_LP (16), BLA_W_RADL (17), BLA_N_LP (18): Broken Link Access\n */\nfunction isKeyframeAnnexB(codec: HlsCodec, annexB: Buffer): boolean {\n const nals = parseAnnexBNalUnits(annexB);\n\n for (const nal of nals) {\n if (!nal || nal.length === 0) continue;\n if (codec === \"h264\") {\n const nalType = nal[0]! & 0x1f;\n // IDR = 5\n if (nalType === 5) return true;\n } else {\n const nalType = (nal[0]! >> 1) & 0x3f;\n // H.265 Random Access Point NAL types: 16-21\n if (nalType >= 16 && nalType <= 21) return true;\n }\n }\n\n return false;\n}\n\n/**\n * Check if frame contains parameter sets\n */\nfunction hasParamSets(codec: HlsCodec, annexB: Buffer): boolean {\n const nals = parseAnnexBNalUnits(annexB);\n for (const nal of nals) {\n if (!nal || nal.length === 0) continue;\n if (codec === \"h264\") {\n const nalType = nal[0]! & 0x1f;\n if (nalType === 7 || nalType === 8) return true;\n } else {\n const nalType = (nal[0]! >> 1) & 0x3f;\n if (nalType === 32 || nalType === 33 || nalType === 34) return true;\n }\n }\n return false;\n}\n\n/**\n * Get NAL types from Annex-B data for debugging\n */\nfunction getNalTypes(codec: HlsCodec, annexB: Buffer): number[] {\n const nals = parseAnnexBNalUnits(annexB);\n return nals.map((nal) => {\n if (codec === \"h265\") {\n return (nal[0]! >> 1) & 0x3f;\n } else {\n return nal[0]! & 0x1f;\n }\n });\n}\n\n// ============================================================================\n// BaichuanHlsServer Class\n// ============================================================================\n\nexport class BaichuanHlsServer extends EventEmitter {\n private readonly api: ReolinkBaichuanApi;\n private readonly channel: number;\n private readonly profile: StreamProfile;\n private readonly variant: NativeVideoStreamVariant | undefined;\n private readonly segmentDuration: number;\n private readonly playlistSize: number;\n private readonly ffmpegPath: string;\n private readonly log: (\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n ) => void;\n\n private outputDir: string | null = null;\n private createdTempDir: boolean = false;\n private playlistPath: string | null = null;\n private segmentPattern: string | null = null;\n\n private state: HlsServerStatus[\"state\"] = \"idle\";\n private codec: HlsCodec | null = null;\n private framesReceived: number = 0;\n private ffmpeg: ChildProcess | null = null;\n private nativeStream: AsyncGenerator<any, void, unknown> | null = null;\n private pumpPromise: Promise<void> | null = null;\n private startedAt: Date | null = null;\n private lastError: string | null = null;\n\n constructor(options: BaichuanHlsServerOptions) {\n super();\n this.api = options.api;\n this.channel = options.channel;\n this.profile = options.profile;\n this.variant = options.variant ?? undefined;\n this.segmentDuration = options.segmentDuration ?? 2;\n this.playlistSize = options.playlistSize ?? 5;\n this.ffmpegPath = options.ffmpegPath ?? \"ffmpeg\";\n\n if (options.outputDir) {\n this.outputDir = options.outputDir;\n this.createdTempDir = false;\n }\n\n this.log = options.logger ?? (() => {});\n }\n\n /**\n * Start HLS streaming\n */\n async start(): Promise<void> {\n if (this.state === \"running\" || this.state === \"starting\") {\n return;\n }\n\n this.state = \"starting\";\n this.lastError = null;\n\n try {\n // Create output directory if needed\n if (!this.outputDir) {\n this.outputDir = await fsp.mkdtemp(\n path.join(os.tmpdir(), `nodelink-hls-${this.profile}-`),\n );\n this.createdTempDir = true;\n } else {\n await fsp.mkdir(this.outputDir, { recursive: true });\n }\n\n this.playlistPath = path.join(this.outputDir, \"playlist.m3u8\");\n this.segmentPattern = path.join(this.outputDir, \"segment_%05d.ts\");\n\n this.log(\"info\", `Starting HLS stream to ${this.outputDir}`);\n\n // Start native stream\n this.nativeStream = createNativeStream(\n this.api,\n this.channel,\n this.profile,\n this.variant ? { variant: this.variant } : undefined,\n );\n\n // Start pumping frames to ffmpeg\n this.pumpPromise = this.pumpNativeToFfmpeg();\n this.startedAt = new Date();\n this.state = \"running\";\n\n this.emit(\"started\", { outputDir: this.outputDir });\n } catch (err) {\n this.state = \"error\";\n this.lastError = String(err);\n this.log(\"error\", `Failed to start HLS: ${err}`);\n throw err;\n }\n }\n\n /**\n * Stop HLS streaming\n */\n async stop(): Promise<void> {\n if (this.state === \"idle\" || this.state === \"stopped\") {\n return;\n }\n\n this.state = \"stopping\";\n this.log(\"info\", \"Stopping HLS stream\");\n\n // Close ffmpeg stdin\n try {\n this.ffmpeg?.stdin?.end();\n } catch {\n // ignore\n }\n\n // Kill ffmpeg\n try {\n this.ffmpeg?.kill(\"SIGKILL\");\n } catch {\n // ignore\n }\n this.ffmpeg = null;\n\n // Stop native stream\n if (this.nativeStream) {\n try {\n await this.nativeStream.return(undefined as any);\n } catch {\n // ignore\n }\n this.nativeStream = null;\n }\n\n // Wait for pump to finish\n if (this.pumpPromise) {\n try {\n await this.pumpPromise;\n } catch {\n // ignore\n }\n this.pumpPromise = null;\n }\n\n // Clean up temp directory if we created it\n if (this.createdTempDir && this.outputDir) {\n try {\n await fsp.rm(this.outputDir, { recursive: true, force: true });\n } catch {\n // ignore\n }\n }\n\n this.state = \"stopped\";\n this.emit(\"stopped\");\n }\n\n /**\n * Get current status\n */\n getStatus(): HlsServerStatus {\n return {\n state: this.state,\n codec: this.codec,\n framesReceived: this.framesReceived,\n ffmpegRunning: this.ffmpeg !== null && !this.ffmpeg.killed,\n playlistPath: this.playlistPath,\n outputDir: this.outputDir,\n startedAt: this.startedAt,\n error: this.lastError,\n };\n }\n\n /**\n * Get playlist file path\n */\n getPlaylistPath(): string | null {\n return this.playlistPath;\n }\n\n /**\n * Get output directory\n */\n getOutputDir(): string | null {\n return this.outputDir;\n }\n\n /**\n * Check if playlist file exists\n */\n async waitForPlaylist(timeoutMs: number = 20000): Promise<boolean> {\n if (!this.playlistPath) return false;\n\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (fs.existsSync(this.playlistPath)) {\n return true;\n }\n await new Promise((r) => setTimeout(r, 150));\n }\n return false;\n }\n\n /**\n * Read an HLS asset (playlist or segment)\n */\n async readAsset(\n assetName: string,\n ): Promise<{ data: Buffer; contentType: string } | null> {\n if (!this.outputDir) return null;\n\n // Validate asset name (security)\n const safe = assetName.replace(/^\\/+/, \"\");\n if (safe.includes(\"..\") || safe.includes(\"/\")) {\n return null;\n }\n\n const filePath = path.join(this.outputDir, safe);\n if (!fs.existsSync(filePath)) {\n return null;\n }\n\n const data = await fsp.readFile(filePath);\n let contentType = \"application/octet-stream\";\n if (safe.endsWith(\".m3u8\")) {\n contentType = \"application/vnd.apple.mpegurl\";\n } else if (safe.endsWith(\".ts\")) {\n contentType = \"video/mp2t\";\n }\n\n return { data, contentType };\n }\n\n // ============================================================================\n // Private Methods\n // ============================================================================\n\n private async pumpNativeToFfmpeg(): Promise<void> {\n if (!this.nativeStream || !this.playlistPath || !this.segmentPattern) {\n return;\n }\n\n let startedFfmpeg = false;\n let pendingParamSets: Buffer[] = [];\n const MAX_FRAMES_WAIT_FOR_KEYFRAME = 180;\n\n const collectParamSets = (codec: HlsCodec, annexB: Buffer) => {\n const nals = parseAnnexBNalUnits(annexB);\n for (const nal of nals) {\n if (!nal || nal.length === 0) continue;\n if (codec === \"h264\") {\n const t = nal[0]! & 0x1f;\n if (t === 7 || t === 8) {\n pendingParamSets.push(\n Buffer.concat([Buffer.from([0, 0, 0, 1]), nal]),\n );\n }\n } else {\n const t = (nal[0]! >> 1) & 0x3f;\n if (t === 32 || t === 33 || t === 34) {\n pendingParamSets.push(\n Buffer.concat([Buffer.from([0, 0, 0, 1]), nal]),\n );\n }\n }\n }\n if (pendingParamSets.length > 12) {\n pendingParamSets = pendingParamSets.slice(-12);\n }\n };\n\n try {\n for await (const frame of this.nativeStream) {\n if (this.state !== \"running\") break;\n\n // Only video\n if (frame.audio) continue;\n if (!frame.data || frame.data.length === 0) continue;\n\n // Detect codec\n if (!this.codec) {\n const detected = detectVideoCodecFromNal(frame.data);\n const fromMeta: HlsCodec =\n frame.videoType === \"H265\" ? \"h265\" : \"h264\";\n this.codec = detected\n ? (detected.toLowerCase() as HlsCodec)\n : fromMeta;\n this.log(\n \"info\",\n `HLS codec detected: meta=${fromMeta} detected=${detected} (using ${this.codec})`,\n );\n this.emit(\"codec-detected\", { codec: this.codec });\n }\n\n // Convert to Annex-B\n const annexB =\n this.codec === \"h265\"\n ? convertH265ToAnnexB(frame.data)\n : convertH264ToAnnexB(frame.data);\n\n this.framesReceived++;\n\n // Debug logging for first frames and periodically\n const shouldLog =\n this.framesReceived <= 5 ||\n (this.framesReceived <= 60 && this.framesReceived % 10 === 0);\n if (shouldLog) {\n const nalTypes = getNalTypes(this.codec, annexB);\n const hasIdr = isKeyframeAnnexB(this.codec, annexB);\n const hasParams = hasParamSets(this.codec, annexB);\n this.log(\n \"debug\",\n `HLS frame#${this.framesReceived}: bytes=${annexB.length} nalTypes=[${nalTypes.join(\",\")}] hasIDR=${hasIdr} hasParams=${hasParams}`,\n );\n }\n\n collectParamSets(this.codec, annexB);\n\n // Only start ffmpeg on a TRUE keyframe (IDR for H.264, IDR/CRA/BLA for H.265)\n // \"Logical keyframes\" (VPS/SPS/PPS + TRAIL) don't work because TRAIL frames\n // reference previous frames that we don't have.\n const isKeyframe = isKeyframeAnnexB(this.codec, annexB);\n\n // Wait for keyframe before starting ffmpeg\n if (!isKeyframe && !startedFfmpeg) {\n if (this.framesReceived < MAX_FRAMES_WAIT_FOR_KEYFRAME) {\n continue;\n }\n this.log(\n \"warn\",\n `No keyframe after ${this.framesReceived} frames, starting ffmpeg anyway`,\n );\n }\n\n if (!startedFfmpeg) {\n this.log(\n \"info\",\n `Starting ffmpeg: codec=${this.codec} framesSeen=${this.framesReceived} isKeyframe=${isKeyframe} paramSets=${pendingParamSets.length}`,\n );\n this.ffmpeg = this.spawnFfmpeg();\n startedFfmpeg = true;\n this.emit(\"ffmpeg-started\");\n\n // Prepend parameter sets\n try {\n if (this.ffmpeg?.stdin && !this.ffmpeg.stdin.destroyed) {\n for (const ps of pendingParamSets) {\n this.ffmpeg.stdin.write(ps);\n }\n }\n } catch {\n // ignore\n }\n }\n\n if (!this.ffmpeg || !this.ffmpeg.stdin || this.ffmpeg.stdin.destroyed) {\n this.log(\"warn\", \"ffmpeg stdin not available, stopping pump\");\n break;\n }\n\n try {\n this.ffmpeg.stdin.write(annexB);\n // Log progress periodically\n if (\n this.framesReceived % 100 === 0 ||\n this.framesReceived <= 5 ||\n (this.framesReceived <= 50 && this.framesReceived % 10 === 0)\n ) {\n this.log(\n \"debug\",\n `HLS fed frame #${this.framesReceived} to ffmpeg (${annexB.length} bytes)`,\n );\n }\n } catch (err) {\n this.log(\"error\", `Failed to write to ffmpeg: ${err}`);\n break;\n }\n }\n } catch (e) {\n this.log(\"error\", `HLS pump error: ${e}`);\n this.lastError = String(e);\n this.state = \"error\";\n this.emit(\"error\", e);\n }\n }\n\n private spawnFfmpeg(): ChildProcess {\n if (!this.playlistPath || !this.segmentPattern) {\n throw new Error(\"Playlist path not set\");\n }\n\n const codec = this.codec ?? \"h264\";\n\n const args: string[] = [\n \"-hide_banner\",\n \"-loglevel\",\n \"warning\",\n \"-fflags\",\n \"+genpts\",\n \"-use_wallclock_as_timestamps\",\n \"1\",\n \"-r\",\n \"25\",\n \"-f\",\n codec === \"h265\" ? \"hevc\" : \"h264\",\n \"-i\",\n \"pipe:0\",\n ];\n\n if (codec === \"h265\") {\n // Transcode H.265 to H.264 for browser compatibility\n args.push(\n \"-c:v\",\n \"libx264\",\n \"-preset\",\n \"veryfast\",\n \"-tune\",\n \"zerolatency\",\n \"-pix_fmt\",\n \"yuv420p\",\n );\n } else {\n // Copy H.264 directly\n args.push(\"-c:v\", \"copy\");\n }\n\n args.push(\n \"-f\",\n \"hls\",\n \"-hls_time\",\n String(this.segmentDuration),\n \"-hls_list_size\",\n String(this.playlistSize),\n \"-hls_flags\",\n \"delete_segments+append_list+omit_endlist\",\n \"-hls_segment_filename\",\n this.segmentPattern,\n this.playlistPath,\n );\n\n const p = spawn(this.ffmpegPath, args, {\n stdio: [\"pipe\", \"ignore\", \"pipe\"],\n });\n\n p.on(\"error\", (err) => {\n this.log(\"error\", `ffmpeg spawn error: ${err}`);\n this.emit(\"ffmpeg-error\", err);\n });\n\n p.stderr?.on(\"data\", (d) => {\n const s = String(d ?? \"\").trim();\n if (s) this.log(\"warn\", `[ffmpeg] ${s}`);\n });\n\n p.on(\"exit\", (code, signal) => {\n this.log(\n \"warn\",\n `ffmpeg exited (code=${code ?? \"?\"} signal=${signal ?? \"?\"})`,\n );\n this.emit(\"ffmpeg-exited\", { code, signal });\n });\n\n return p;\n }\n}\n","/**\n * RTSP Server per stream composito multifocal\n * \n * Espone un server RTSP che serve lo stream composito (wider + tele con PIP)\n * come un nuovo stream \"rtp\" configurabile.\n */\n\nimport { BaichuanRtspServer } from \"../baichuan/stream/BaichuanRtspServer\";\nimport { CompositeStream, type CompositeStreamOptions, type PipPosition } from \"./compositeStream\";\nimport type { ReolinkBaichuanApi } from \"../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { StreamProfile } from \"../reolink/baichuan/types\";\nimport type { Logger } from \"../debug/DebugConfig\";\nimport { EventEmitter } from \"node:events\";\nimport { spawn } from \"node:child_process\";\nimport * as net from \"node:net\";\n\nexport type CompositeRtspServerOptions = {\n api: ReolinkBaichuanApi;\n widerChannel: number;\n teleChannel: number;\n widerProfile: StreamProfile;\n teleProfile: StreamProfile;\n pipPosition?: PipPosition;\n pipSize?: number;\n pipMargin?: number;\n listenHost?: string;\n listenPort?: number;\n path?: string;\n logger?: Logger;\n};\n\n/**\n * RTSP Server che serve uno stream composito multifocal\n * \n * Utilizza ffmpeg per creare un server RTSP che legge dallo stream composito\n */\nexport class CompositeRtspServer extends EventEmitter<{\n client: [string];\n clientDisconnected: [string];\n error: [Error];\n close: [];\n}> {\n private options: CompositeRtspServerOptions;\n private compositeStream: CompositeStream | null = null;\n private rtspServer: net.Server | null = null;\n private ffmpegProcess: ReturnType<typeof spawn> | null = null;\n private active = false;\n private logger: Logger;\n private connectedClients = new Set<string>();\n\n constructor(options: CompositeRtspServerOptions) {\n super();\n this.options = {\n listenHost: \"127.0.0.1\",\n listenPort: 8554,\n path: \"/composite\",\n ...options,\n };\n this.logger = options.logger ?? console;\n }\n\n /**\n * Avvia il server RTSP composito\n */\n async start(): Promise<void> {\n if (this.active) {\n throw new Error(\"Composite RTSP server already active\");\n }\n\n this.active = true;\n this.logger.log?.(\"[CompositeRtspServer] Starting composite RTSP server...\");\n\n try {\n // Crea e avvia composite stream\n this.compositeStream = new CompositeStream({\n api: this.options.api,\n widerChannel: this.options.widerChannel,\n teleChannel: this.options.teleChannel,\n widerProfile: this.options.widerProfile,\n teleProfile: this.options.teleProfile,\n ...(this.options.pipPosition ? { pipPosition: this.options.pipPosition } : {}),\n ...(this.options.pipSize !== undefined ? { pipSize: this.options.pipSize } : {}),\n ...(this.options.pipMargin !== undefined ? { pipMargin: this.options.pipMargin } : {}),\n logger: this.logger,\n });\n\n this.compositeStream.on(\"error\", (error) => {\n this.logger.error?.(\"[CompositeRtspServer] Composite stream error:\", error);\n this.emit(\"error\", error);\n });\n\n this.compositeStream.on(\"close\", () => {\n this.logger.log?.(\"[CompositeRtspServer] Composite stream closed\");\n });\n\n await this.compositeStream.start();\n\n // Avvia server RTSP che legge dallo stream composito\n await this.startRtspServer();\n\n this.logger.log?.(\n `[CompositeRtspServer] Server started on ${this.options.listenHost}:${this.options.listenPort}${this.options.path}`\n );\n } catch (error) {\n this.active = false;\n this.emit(\"error\", error as Error);\n throw error;\n }\n }\n\n /**\n * Avvia server RTSP usando ffmpeg\n */\n private async startRtspServer(): Promise<void> {\n // Ottieni metadata per determinare risoluzione output\n const widerMetadata = await this.options.api.getStreamMetadata(this.options.widerChannel);\n const widerStreamInfo = widerMetadata.streams.find((s) => s.profile === this.options.widerProfile);\n const width = widerStreamInfo?.width ?? 1920;\n const height = widerStreamInfo?.height ?? 1080;\n const fps = widerStreamInfo?.frameRate ?? 25;\n\n // Avvia server RTSP TCP\n this.rtspServer = net.createServer((socket) => {\n this.handleRtspConnection(socket);\n });\n\n await new Promise<void>((resolve, reject) => {\n this.rtspServer!.listen(this.options.listenPort, this.options.listenHost, () => {\n const address = this.rtspServer!.address();\n if (address && typeof address === \"object\" && \"port\" in address) {\n (this.options as any).listenPort = address.port;\n }\n resolve();\n });\n this.rtspServer!.on(\"error\", reject);\n });\n\n // Avvia ffmpeg RTSP server che legge dallo stream composito\n this.startFfmpegRtspServer(width, height, fps);\n }\n\n /**\n * Avvia ffmpeg come server RTSP che legge dallo stream composito\n */\n private startFfmpegRtspServer(width: number, height: number, fps: number): void {\n const rtspUrl = `rtsp://${this.options.listenHost}:${this.options.listenPort}${this.options.path}`;\n\n // ffmpeg legge H.264 da stdin e lo serve come RTSP\n const ffmpegArgs = [\n \"-hide_banner\",\n \"-loglevel\", \"error\",\n \"-f\", \"h264\",\n \"-i\", \"pipe:0\",\n \"-c:v\", \"copy\",\n \"-f\", \"rtsp\",\n \"-rtsp_flags\", \"listen\",\n rtspUrl,\n ];\n\n this.logger.log?.(\n `[CompositeRtspServer] Starting ffmpeg RTSP server: ${ffmpegArgs.join(\" \")}`\n );\n\n this.ffmpegProcess = spawn(\"ffmpeg\", ffmpegArgs, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n this.ffmpegProcess.on(\"error\", (error) => {\n this.logger.error?.(\"[CompositeRtspServer] FFmpeg error:\", error);\n this.emit(\"error\", error);\n });\n\n this.ffmpegProcess.on(\"close\", (code) => {\n if (code !== 0 && code !== null) {\n this.logger.warn?.(`[CompositeRtspServer] FFmpeg exited with code ${code}`);\n }\n });\n\n // Feed frames compositi a ffmpeg\n this.compositeStream!.on(\"videoFrame\", (frame: Buffer) => {\n if (this.ffmpegProcess?.stdin && !this.ffmpegProcess.stdin.destroyed) {\n try {\n const written = this.ffmpegProcess.stdin.write(frame);\n if (!written) {\n this.ffmpegProcess.stdin.once(\"drain\", () => {});\n }\n } catch (error) {\n const code = (error as any)?.code;\n if (code !== \"EPIPE\" && code !== \"ERR_STREAM_WRITE_AFTER_END\") {\n this.logger.error?.(\"[CompositeRtspServer] Error writing to ffmpeg:\", error);\n }\n }\n }\n });\n\n // Log stderr per debug\n this.ffmpegProcess.stderr?.on(\"data\", (data: Buffer) => {\n const output = data.toString();\n if (output.includes(\"error\") || output.includes(\"Error\")) {\n this.logger.error?.(\"[CompositeRtspServer] FFmpeg stderr:\", output);\n }\n });\n }\n\n /**\n * Gestisce connessioni RTSP (semplificato - delega a ffmpeg)\n */\n private handleRtspConnection(socket: net.Socket): void {\n const clientId = `${socket.remoteAddress}:${socket.remotePort}`;\n this.logger.log?.(`[CompositeRtspServer] RTSP client connected: ${clientId}`);\n this.connectedClients.add(clientId);\n this.emit(\"client\", clientId);\n\n socket.on(\"close\", () => {\n this.connectedClients.delete(clientId);\n this.emit(\"clientDisconnected\", clientId);\n this.logger.log?.(`[CompositeRtspServer] RTSP client disconnected: ${clientId}`);\n });\n\n socket.on(\"error\", (error) => {\n this.logger.error?.(`[CompositeRtspServer] RTSP client error:`, error);\n });\n\n // Nota: ffmpeg gestisce il protocollo RTSP, quindi qui potremmo solo fare proxy\n // oppure lasciare che ffmpeg gestisca tutto direttamente\n // Per semplicità, lasciamo che ffmpeg gestisca tutto\n }\n\n /**\n * Ferma il server RTSP composito\n */\n async stop(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n this.active = false;\n this.logger.log?.(\"[CompositeRtspServer] Stopping server...\");\n\n // Ferma composite stream\n if (this.compositeStream) {\n await this.compositeStream.stop();\n this.compositeStream = null;\n }\n\n // Ferma ffmpeg\n if (this.ffmpegProcess) {\n try {\n this.ffmpegProcess.stdin?.end();\n this.ffmpegProcess.kill(\"SIGTERM\");\n setTimeout(() => {\n try {\n this.ffmpegProcess?.kill(\"SIGKILL\");\n } catch {}\n }, 1000);\n } catch {}\n this.ffmpegProcess = null;\n }\n\n // Chiudi server RTSP\n if (this.rtspServer) {\n await new Promise<void>((resolve) => {\n this.rtspServer?.close(() => resolve());\n });\n this.rtspServer = null;\n }\n\n this.connectedClients.clear();\n this.emit(\"close\");\n this.logger.log?.(\"[CompositeRtspServer] Server stopped\");\n }\n\n /**\n * Ottieni URL RTSP\n */\n getRtspUrl(): string {\n return `rtsp://${this.options.listenHost}:${this.options.listenPort}${this.options.path}`;\n }\n\n /**\n * Verifica se il server è attivo\n */\n isActive(): boolean {\n return this.active;\n }\n\n /**\n * Numero di client connessi\n */\n getClientCount(): number {\n return this.connectedClients.size;\n }\n}\n\n"],"mappings":"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","names":["ff","cleanup","http","spawn","http","spawn","i","sps","pps","spawn","spawn","requestedId","sps","pps","address","http","spawn","EventEmitter","spawn","http","EventEmitter","http","EventEmitter","spawn","EventEmitter","path","convertToAnnexB","EventEmitter","getH265NalType","EventEmitter","EventEmitter","spawn","parseAnnexBNalUnits","EventEmitter","convertToAnnexB","spawn","EventEmitter","spawn"]}
|